/* Endurance Coaching Platform — Phase 1 base styles */

/* Scope: minimal overrides only. Bootstrap 5 handles layout. */

/*
 * Coaching stimulus and surface colour tokens.
 *
 * Activity tile colour is now computed per-session in
 * services/coach_athlete_service.py (hue = activity type, saturation/
 * lightness = detected coaching intensity) and applied inline via
 * templates/components/timeline.html — see _colour_for_session().
 * --tile-race remains a shared token for the fixed race/competition red.
 * --tile-easy/--tile-cross remain only for the legacy .rhythm-bar
 * modifiers below (unreachable dead-code template, kept for reference).
 */
:root {
  --tile-easy:  #90b4ce;          /* steel blue — legacy rhythm-bar only */
  --tile-race:  #c0392b;          /* red — race */
  --tile-cross: #e9c46a;          /* warm amber — legacy rhythm-bar only */

  /* Brief 33 (session register), section 0 principle 6: "One lift" —
     the shared lift ECP uses for anything raised off the page, so every
     caller references one token instead of copying the same values.
     .tpv2-today and .board-row--today below predate this and are
     updated to reference these instead of their own copies, with no
     change to their rendered values. No dark variant yet — ECP has no
     dark theme (data-bs-theme/prefers-color-scheme) for anything else
     to diverge from; the register page (which does have one) keeps its
     own dark values in static/css/register.css, which never loads
     alongside this file. */
  --lift-shadow: 0 6px 16px rgba(15, 118, 110, 0.18), 0 1px 3px rgba(31, 42, 55, 0.10);
  --lift-y: -2px;

  /* Surface tokens — for future use once surface detection is reliable */
  --tile-surface-track:  #c09484; /* muted terracotta / salmon — synthetic track */
  --tile-surface-road:   #90b4ce; /* same as easy — road running */
  --tile-surface-grass:  #8aab92; /* muted green — grass/parkland */
  --tile-surface-trail:  #a8956a; /* warm tan — trail/off-road */

  /* Round F — sticky header stack. Measured (not hardcoded) by JS in
     templates/base.html on load/resize via ResizeObserver, so these stay
     correct across the mobile collapsed-menu state and viewport changes.
     Defaults below are only the pre-JS/no-JS fallback. --sticky-tabs-height
     is only ever overridden on pages that actually include a tab bar
     (.athlete-tabs / .workspace-tabs) — pages without one keep the 0px
     default, which is exactly correct for their layer-3 offset. */
  --sticky-nav-height: 56px;
  --sticky-tabs-height: 0px;
}

.subsquad-roster-sheet__athlete--own,
.subsquad-roster-sheet__athlete--elsewhere {
  color: #687787;
}

.subsquad-roster-sheet__own-label {
  display: block;
  font-size: 0.68rem;
  color: #7b8794;
}

/* Round S — the one shared gradient banner now lives on the actual site
   navbar (templates/base.html: .navbar-gradient, replacing bg-dark), not
   stacked as a second coloured bar under it. See also the body.role-*
   tint rules above. */
.navbar-gradient {
  background: linear-gradient(90deg, #2DD4BF, #3878C2);
  padding-top: env(safe-area-inset-top);
}

.app-shell-bar {
  position: sticky;
  top: 0;
  z-index: 1100;
  min-height: calc(48px + env(safe-area-inset-top));
  margin: 0;
  padding: env(safe-area-inset-top) 0 0;
}

.app-shell-bar__inner {
  display: grid;
  grid-template-columns: 3rem minmax(0, 1fr) 3rem;
  align-items: center;
  gap: 0.4rem;
  height: 48px;
  padding: 0 0.75rem;
}

.app-shell-bar__button,
.app-shell-bar__title {
  min-width: 0;
  color: #fff;
}

.app-shell-bar__button {
  display: inline-flex;
  justify-content: center;
  align-items: center;
  border: 0;
  padding: 0.25rem;
  background: transparent;
  color: #fff;
  font: inherit;
  text-decoration: none;
  white-space: nowrap;
}

.app-shell-bar__menu-trigger {
  justify-self: start;
  width: 2.5rem;
  height: 2.5rem;
  font-size: 1.35rem;
  line-height: 1;
}

.app-shell-bar__menu-placeholder {
  width: 2.5rem;
}

.app-shell-bar__title {
  overflow: hidden;
  text-align: center;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 1rem;
  font-weight: 700;
}

.app-shell-bar__title-button {
  display: inline-flex;
  justify-content: center;
  align-items: center;
  gap: 0.4rem;
  border: 0;
  padding: 0.25rem 0.4rem;
  background: transparent;
  font: inherit;
}

.app-shell-bar__chevron {
  flex: 0 0 auto;
  opacity: 0.85;
}

.app-shell-bar__today,
.app-shell-bar__login {
  justify-self: end;
  font-size: 0.85rem;
  font-weight: 600;
}

.app-shell-popover {
  position: fixed;
  top: calc(var(--sticky-nav-height) + 0.25rem);
  z-index: 1300;
  width: min(20rem, calc(100vw - 1.5rem));
  max-height: calc(100dvh - var(--sticky-nav-height) - 1rem);
  overflow-y: auto;
  padding: 0.45rem;
  border: 1px solid #c7d0d9;
  border-radius: 0.75rem;
  background: #fff;
  box-shadow: 0 8px 24px rgba(26, 35, 50, 0.22);
  opacity: 0;
  transform: scale(0.96);
  transform-origin: top left;
  transition: opacity 240ms ease, transform 240ms ease;
}

.app-shell-popover.is-open {
  opacity: 1;
  transform: scale(1);
}

.app-shell-popover[hidden] {
  display: none;
}

.app-shell-menu {
  left: 0.75rem;
}

.app-shell-squad-menu {
  left: 50%;
  transform-origin: top center;
  translate: -50% 0;
}

.app-shell-squad-menu.is-open {
  transform: scale(1);
}

.app-shell-menu__section + .app-shell-menu__section {
  margin-top: 0.4rem;
  padding-top: 0.4rem;
  border-top: 1px solid #e2e7ee;
}

.app-shell-menu__heading {
  margin: 0;
  padding: 0.35rem 0.65rem 0.2rem;
  color: #627184;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.app-shell-menu form {
  margin: 0;
}

.app-shell-menu__item {
  display: flex;
  width: 100%;
  justify-content: space-between;
  align-items: center;
  gap: 0.75rem;
  border: 0;
  border-radius: 0.45rem;
  padding: 0.55rem 0.65rem;
  background: transparent;
  color: #1a2332;
  font: inherit;
  text-align: left;
  text-decoration: none;
}

.app-shell-menu__item:hover,
.app-shell-menu__item:active {
  background: #eef3f9;
  color: #1a2332;
  text-decoration: none;
}

.app-shell-menu__item.is-active {
  color: #0f766e;
  font-weight: 700;
}

.app-shell-menu__check {
  min-width: 1rem;
  text-align: center;
}

.app-shell-menu__divider {
  margin: 0.35rem 0.2rem;
  border-top: 1px solid #e2e7ee;
}

.app-shell-squad-menu {
  left: 50%;
  width: min(23rem, calc(100vw - 1.5rem));
}

.app-shell-squad-menu__groups {
  padding: 0.1rem 0.75rem 0.55rem 1.25rem;
  color: #6c7a89;
  font-size: 0.78rem;
}

.app-shell-content {
  padding-top: 1rem;
}

body:has(.workspace-page) .app-shell-content {
  padding-top: 0;
}

@media (prefers-reduced-motion: reduce) {
  .app-shell-popover {
    transition: opacity 1ms linear;
    transform: none;
  }

  .app-shell-squad-menu {
    translate: -50% 0;
  }
}

.board-cell--own:not(.board-cell--draft) .board-cell__pill::after {
  content: 'Own';
  position: absolute;
  top: 0.15rem;
  right: 3px;
  font-size: 0.55rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  opacity: 0.6;
  pointer-events: none;
}

.board-cell--draft.board-cell--own .board-cell__pill::after {
  content: 'Not shared \00b7 Own';
  position: absolute;
  top: 0.15rem;
  right: 3px;
  font-size: 0.55rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  opacity: 0.6;
  pointer-events: none;
}

/* Round F — layer 3: generic sticky page-header row (the common
   `<h1>` + controls pattern). Pages whose layer-3 header uses a different
   component (.tp-grid__header, .workspace-nav) get the same treatment
   applied directly to that component's own rule instead — see below.
   Round S: no longer its own coloured bar — flat, role-conditional tint
   (shared by both coach and athlete templates) so it reads as part of
   the tinted page; sticky only to occlude content scrolling underneath. */
.sticky-page-header {
  position: sticky;
  top: calc(var(--sticky-nav-height) + var(--sticky-tabs-height));
  z-index: 1000;
}

body.role-coach   .sticky-page-header { background: #EAF0F8; }
body.role-athlete .sticky-page-header { background: #DCE6F0; }

/* NAVIGATION_RULES.md rule 7 (David, 20 Oct 2026) — the one shared Back
   component (templates/components/back_link.html). Plain text, no
   button chrome, no background tint of its own — deliberately NOT inside
   .sticky-page-header or any coloured/card surface, so its contrast
   never depends on whatever colour that page's header happens to be
   (that dependency is exactly what made the teal athlete-page header's
   outline-button Back/Edit hard to read). Negative margins bring the
   larger tap target back into visual alignment with the page's own left
   edge/baseline rather than indenting the label itself. */
.back-link-row {
  margin: 0 0 0.75rem -0.5rem;
}

.back-link {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0.25rem 0.5rem;
  color: #1a2332;
  font-weight: 500;
  font-size: 0.95rem;
  text-decoration: none;
  border-radius: 6px;
}

.back-link:hover,
.back-link:focus-visible {
  color: #1a2332;
  text-decoration: underline;
  background: rgba(26, 35, 50, 0.06);
}

/* Round AH: .nav-tabs-bar (the shared top-level tab row, see the
   SHARED TAB BAR section below) is a separate sticky component that
   Round AD missed — same role-scoped tint as .sticky-page-header above,
   for the same reason (reads as part of the tinted page, not a plain
   white bar sitting on top of it). */
body.role-coach   .nav-tabs-bar { background: #EAF0F8; }
body.role-athlete .nav-tabs-bar { background: #DCE6F0; }

/* Round R's outline-button re-tint patch lived here (needed because the
   gradient banner made default Bootstrap outline colours unreadable).
   Removed in Round S — against the flat pale tints above, the default
   btn-outline-primary/-secondary colours contrast at ~3.5-3.9:1, clearly
   legible, so the patch is no longer needed. */

html {
  /* Safety net: iOS Safari can still pan the whole page sideways if
   * html's own scrollable width expands, even when body has its own
   * overflow-x: hidden (see body rule below) — belt-and-braces so this
   * guard is complete at every level, on every page. */
  overflow-x: hidden;
}

body.role-coach   { background: #EAF0F8; }
body.role-athlete { background: #DCE6F0; }

body {
  font-size: 0.95rem;
  color: #212529;
  /* Round F: overflow-x: hidden used to be set here too ("belt-and-braces"
   * against the now-fixed .row.g-3 negative-gutter bug). Removed — when
   * BOTH html and body have a non-visible overflow-x, the CSS overflow
   * computed-value coupling rule forces overflow-y: auto on both of them
   * as well, turning body into its own independent scroll container
   * nested inside html's. That silently breaks position: sticky for
   * every sticky element on the page (their containing scrollport
   * resolves to body, which then never actually scrolls, since the real
   * page scroll happens on html) — confirmed empirically, this broke the
   * sticky header stack above outright, and would have already been
   * silently breaking the pre-existing .athlete-tabs sticky rule too.
   * Keeping overflow-x: hidden on html alone is sufficient protection
   * against the original horizontal-pan symptom.
   */
}

/* Activity tables — tighter row padding for data density */
.table-sm td,
.table-sm th {
  padding: 0.35rem 0.5rem;
}

/* Timescale buttons — consistent sizing */
.btn-group .btn {
  min-width: 70px;
}

/* Pace formatting — monospace for alignment */
td.text-end {
  font-variant-numeric: tabular-nums;
}

/* Dashboard badge alignment */
.badge {
  font-size: 0.78rem;
  font-weight: 500;
}

/* ======================================================================
   SQUAD DASHBOARD — Coaching Cards
   ====================================================================== */

/* Group heading between card grids */
.squad-group-heading {
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #9aa5b0;
  margin-bottom: 0.6rem;
  margin-top: 0.1rem;
}

/* ---- Squad tabs -------------------------------------------------------- */
.squad-tabs {
  display: flex;
  gap: 0.25rem;
  border-bottom: 1px solid #e3e7ea;
  margin-bottom: 1rem;
}
.squad-tab {
  display: inline-block;
  padding: 0.4rem 0.85rem;
  font-size: 0.8rem;
  font-weight: 600;
  color: #6c757d;
  text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}
.squad-tab:hover {
  color: #212529;
}
.squad-tab--active {
  color: #1a2332;
  border-bottom-color: #1a2332;
}

/* ---- Athlete card ---------------------------------------------------- */
.athlete-card {
  display: block;
  background: #F6F9FD;
  border: 1px solid #dbe8f5;
  border-radius: 8px;
  padding: 0.7rem 0.85rem 0.6rem;
  color: #212529;
  cursor: pointer;
  transition: border-color 0.12s, box-shadow 0.12s, transform 0.12s;
  height: 100%;
}

/* Back from an athlete page lands on their card ("#card-N"), clear of
   the sticky top bar, with a brief teal ring (quick tweaks, 25 Oct). */
.athlete-card { scroll-margin-top: 72px; }
@media (prefers-reduced-motion: no-preference) {
  .athlete-card:target { animation: card-return 1.6s ease-out 1; }
}
@keyframes card-return { 0%, 40% { box-shadow: 0 0 0 3px rgba(45,212,191,.7); } 100% { box-shadow: 0 0 0 3px rgba(45,212,191,0); } }

.athlete-card:hover {
  border-color: #9fb3c0;
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.08);
  transform: translateY(-1px);
  color: #212529;
}

/* ---- Round R: Squad Overview athlete-card expand-in-place panel ------ */
/* Sits below the whole #squad-cards grid (not nested inside any one
   card), populated by the generalized Session Panel Controller
   (base.html) with the full Athlete Detail body fragment. Scrollable
   in its own right if taller than the viewport, rather than growing
   the whole page indefinitely — a coach who opened it should never
   lose the top of the card content by scrolling too far past it. */
.athlete-panel-container {
  margin-bottom: 1.5rem;
  max-height: 85vh;
  overflow-y: auto;
  background: #fff;
  border: 1px solid #e3e7ea;
  border-radius: 8px;
  padding: 1.25rem 1.5rem;
}

/* ---- Header row (name + optional badge) ------------------------------ */
.athlete-card__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.4rem;
}

.athlete-card__name {
  font-size: 1rem;
  font-weight: 600;
  color: #1a2332;
  line-height: 1.2;
}

.athlete-card__badge {
  position: relative;   /* sit above the card's stretched-link overlay */
  z-index: 2;
  flex-shrink: 0;
  font-size: 0.7rem;
  font-weight: 500;
  border-radius: 4px;
  padding: 0.15em 0.5em;
  line-height: 1.4;
}

.athlete-card__badge--warn {
  background: #fff3cd;
  color: #856404;
  border: 1px solid #ffc107;
}

/* Role badge — "Coach" marker on a dual-role athlete card (spec 42 Part A) */
.athlete-card__badge--role {
  background: #eef2ff;
  color: #3730a3;
  border: 1px solid #c7d2fe;
}

/* Planner-match suggestion badge — an unlinked historical planner column
   (Apple Numbers import) whose name exactly matches this athlete. Neutral
   informational tone, distinct from the warning-toned Strava badge — this
   is a helpful suggestion, not a problem needing attention. */
.athlete-card__badge--info {
  background: #e7f5ff;
  color: #0c5aa6;
  border: 1px solid #a5d8ff;
}

/* Onboarding checklist — compact Invited/Accepted/Consented/Strava row.
   Only rendered while onboarding is incomplete; collapses entirely once
   done so it never competes with the rest of the card for attention. */
.onboarding-checklist {
  position: relative;   /* sit above the squad card's stretched-link overlay, so item tooltips still fire */
  z-index: 2;
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 0.5rem;
  margin-bottom: 0.35rem;
}

.onboarding-checklist__item {
  display: inline-flex;
  align-items: center;
  gap: 0.25em;
  font-size: 0.68rem;
  font-weight: 500;
  cursor: default;
}

.onboarding-checklist__item--done {
  color: #2f7d4f;
}

.onboarding-checklist__item--pending {
  color: #9aa5b0;
}

/* ── Admin roster table (spec 42 Part B) ───────────────────────────── */
.roster-table {
  font-size: 0.85rem;
}

.roster-table__name {
  font-weight: 500;
  white-space: nowrap;
}

.roster-table__person-link {
  font-size: 0.7rem;
  color: #9ca3af;
  margin-left: 0.3rem;
  vertical-align: middle;
  cursor: help;
}

.roster-table__detail {
  color: #374151;
}

/* Shared pill base for roster role badges */
.roster-badge {
  display: inline-block;
  font-size: 0.68rem;
  font-weight: 600;
  border-radius: 4px;
  padding: 0.15em 0.55em;
  line-height: 1.4;
  vertical-align: middle;
}

.roster-badge--athlete {
  background: #f0fdf4;
  color: #166534;
  border: 1px solid #bbf7d0;
}

.roster-badge--coach {
  background: #eef2ff;
  color: #3730a3;
  border: 1px solid #c7d2fe;
}

.roster-badge--dual {
  background: #fef9c3;
  color: #713f12;
  border: 1px solid #fde68a;
}

.roster-badge--ok {
  background: #f0fdf4;
  color: #166534;
  border: 1px solid #bbf7d0;
}

.roster-badge--warn {
  background: #fff7ed;
  color: #9a3412;
  border: 1px solid #fed7aa;
}

.roster-badge--admin {
  background: #1a2332;
  color: #fff;
  border: 1px solid #1a2332;
}

/* ---- 14-day training rhythm bars ------------------------------------- */

/*
 * Strip: 14 equal-width day columns, oldest left → newest (today) right.
 * Each column: a bars-area (fixed 48px tall, bars grow from bottom) +
 *              a day-label (M/T/W/T/F/S/S).
 * Week-start (Monday) columns gain a left separator to mark week boundaries.
 * Today's label is slightly more prominent; no other marker is needed since
 * today is always the rightmost column.
 */
.rhythm-strip {
  display: flex;
  align-items: stretch;
  gap: 1px;
  margin-bottom: 0.25rem;
}

.rhythm-day {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
}

/* Week boundary: thin left separator before each Monday */
.rhythm-day--week-start {
  border-left: 1px solid #dde3e8;
}

/* Fixed-height bar container — bars grow upward from the bottom */
.rhythm-bars-area {
  width: 100%;
  height: 48px;
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 1px;
}

/* Week totals below each week column — modality-coloured labels */
.timeline-week-totals {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin-top: 4px;
  min-height: 14px;
}

.wt {
  font-size: 0.62rem;
  font-weight: 600;
  line-height: 1.3;
  white-space: nowrap;
}

.wt--run     { color: #4a5568; }
.wt--cycling { color: #457b9d; }
.wt--swim    { color: #2a9d8f; }
.wt--cross   { color: #c9920a; }

/*
 * Each bar represents one coaching session.
 * Height is set inline (style="height: Xpx") by the template.
 * Multiple bars side-by-side = multiple sessions that day.
 * flex: 1 1 0 ensures bars share column width equally:
 *   single session  → one full-width bar
 *   double session  → two narrower bars side by side
 *   triple session  → three narrower bars
 */
.rhythm-bar {
  flex: 1 1 0;
  min-width: 0;
  background: #4a5568;
  border-radius: 1px 1px 0 0;
  min-height: 2px;
}

/* Session category colour modifiers (used in Athlete Timeline workspace only) */
.rhythm-bar--easy  { background: var(--tile-easy); }
.rhythm-bar--race  { background: var(--tile-race); }
.rhythm-bar--cross { background: var(--tile-cross); }

/* --- Squad card session tile-stack ------------------------------------ */
/*
 * Tiles replace the bar-height metaphor on the Coach Home athlete cards.
 * Each tile = one coaching session; tiles stack vertically (oldest at bottom,
 * newest on top). Colour communicates coaching stimulus; icon communicates
 * activity type.
 *
 * column-reverse stacks so the first rendered child appears at the bottom.
 * justify-content: flex-end pushes tiles to the bottom of the min-height area,
 * preserving visual rhythm across different athlete day patterns.
 */
.tile-stack {
  width: 100%;
  display: flex;
  flex-direction: column-reverse;
  gap: 1px;
  min-height: 34px;            /* compact — squad page shows many cards at once */
  justify-content: flex-start; /* first tile at visual bottom; rest stack up */
}

.session-tile {
  position: relative;          /* sit above the squad card's stretched-link overlay */
  z-index: 2;
  width: 100%;
  height: 15px;                /* compact — squad page shows many cards at once */
  border-radius: 4px;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.15);
  box-sizing: border-box;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1px;
  flex-shrink: 0;
  text-decoration: none;
  cursor: default;
  /* Colours come from CSS custom properties, not a plain inline
     background/color, so the Run-only rule below can override them by
     selector specificity alone — an inline background/color declaration
     would always win over any external stylesheet rule, custom property
     or not. --tile-bg/--tile-fg (All view) and --tile-bg-run/--tile-fg-run
     (Run-only view) are set inline per-session — see
     _colour_for_session()/_detect_run_surface()/_fg_for_hex() in
     coach_athlete_service.py and templates/components/timeline.html.
     session-tile--race is kept as a marker class only (no styling) for
     JS/test hooks. */
  background: var(--tile-bg);
  color: var(--tile-fg);
}

/* Run-only view: swap in the surface-aware colour for running tiles (see
 * _detect_run_surface() in coach_athlete_service.py). --tile-bg-run/
 * --tile-fg-run equal --tile-bg/--tile-fg whenever no surface signal was
 * found, so this rule is a no-op for those tiles even when it applies. */
#squad-cards.squad-run-only .session-tile {
  background: var(--tile-bg-run);
  color: var(--tile-fg-run);
}

/* Run-only view: non-running tiles are hidden entirely rather than just
 * recoloured — display:none removes them from the column-reverse flex
 * flow cleanly, no gap left behind. */
#squad-cards.squad-run-only .session-tile[data-is-running="false"] {
  display: none;
}

a.session-tile[href]:hover {
  filter: brightness(1.1);
  cursor: pointer;
}

.session-tile__icon {
  font-size: 12px;               /* bumped from 9px — 9px read as a coloured dot,
                                     not a shape, on the squad card. Tile height
                                     (15px) still comfortably contains this. */
  opacity: 0.85;
  line-height: 1;
  pointer-events: none;
}

/* Hand-built inline SVG pictograms (running / elliptical / yoga+pilates —
   see _CUSTOM_SVG_ICON_KEYS in coach_athlete_service.py). These don't scale
   with font-size like the Bootstrap Icon font glyphs, so size explicitly to
   match. */
.session-tile__icon--svg {
  display: block;
  width: 12px;
  height: 12px;
}

/* Intensity letter badge (Round P) — drawn alongside the activity icon,
   never replacing it. "T" (threshold/tempo) and "i" (hard/interval) share
   this one rule (Courier, same size/weight, same small white halo) — the
   --hard/--threshold modifier classes in the template markup are kept only
   as styling hooks for if a future badge needs to diverge visually. */
.session-tile__badge {
  font-size: 11px;
  font-weight: 900;
  color: #1a2332;
  font-family: 'Courier New', Courier, monospace;
  line-height: 1;
  text-shadow:
    -0.5px -0.5px 0 #fff, 0.5px -0.5px 0 #fff,
    -0.5px 0.5px 0 #fff, 0.5px 0.5px 0 #fff,
    0 0 1px rgba(255, 255, 255, 0.85);
}

/* --- Coaching preview popup ------------------------------------------- */
/*
 * Singleton positioned popup shown on tile hover. Uses position: fixed so
 * it is never clipped by athlete card overflow. The JS controller populates
 * content from data-preview-* attributes and positions the element relative
 * to the hovered tile using getBoundingClientRect().
 *
 * pointer-events: none while hidden prevents interfering with mouse events;
 * switched to auto when visible so the coach can move into the popup and
 * follow the "Open activity" link without the popup closing.
 */
.session-preview {
  position: fixed;
  z-index: 1080;
  background: #fff;
  border: 1px solid #d0d7de;
  border-radius: 6px;
  padding: 0.75rem 1rem;
  min-width: 190px;
  max-width: 260px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.13);
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.08s ease;
}

.session-preview--visible {
  opacity: 1;
  pointer-events: auto;
}

/* Session type label — 'Training Week' in week-circle hover mode */
.session-preview__type {
  font-size: 0.58rem;
  font-weight: 700;
  color: #b0bac3;
  text-transform: uppercase;
  letter-spacing: 0.09em;
  margin-bottom: 0.12rem;
}

.session-preview__date {
  font-size: 0.65rem;
  font-weight: 700;
  color: #9aa5b0;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 0.3rem;
}

.session-preview__title {
  font-size: 0.9rem;
  font-weight: 600;
  color: #1a2332;
  margin-bottom: 0.15rem;
  line-height: 1.35;
}

.session-preview__label {
  font-size: 0.78rem;
  color: #6c757d;
  margin-bottom: 0.3rem;
  line-height: 1.4;
}

/* Rich coaching summary body (volume hover) */
.session-preview__body {
  margin-top: 0.3rem;
  padding-top: 0.3rem;
  border-top: 1px solid #eef0f2;
  margin-bottom: 0.4rem;
}

.sp-body-row {
  display: flex;
  align-items: baseline;
  gap: 0.3em;
  font-size: 0.73rem;
  color: #4a5568;
  line-height: 1.6;
}

.sp-body-label {
  font-weight: 600;
  flex: 0 0 auto;
  color: #2d3748;
}

.sp-body-km {
  color: #1a2332;
  font-weight: 600;
}

.sp-body-count {
  color: #9aa5b0;
  font-size: 0.68rem;
  margin-left: auto;
}

.session-preview__link {
  font-size: 0.75rem;
  color: #457b9d;
  text-decoration: none;
}

.session-preview__link:hover {
  text-decoration: underline;
}

/* Day-of-week label beneath the bars area */
.rhythm-day-label {
  font-size: 0.6rem;
  color: #b0bac3;
  line-height: 1;
  user-select: none;
}

/* Today: label is slightly more prominent */
.rhythm-day--today .rhythm-day-label {
  color: #4a5568;
  font-weight: 600;
}

/* Per-week totals — split by activity type (run km / other duration),
 * one block per half of the strip. Block widths are set inline (flex-grow
 * driven by previous_cols/current_cols/spillover_cols) so each block lines
 * up under its own span of days in the 14-day rhythm strip above, using
 * the same 1fr-per-day proportions as .rhythm-day. previous_cols is always
 * a full 7 (one complete week); any leading days that belong to neither
 * week sit in an unlabelled .rhythm-week-totals__spacer. */
.rhythm-week-totals {
  display: flex;
  margin-bottom: 0.3rem;
  gap: 1px;
}

.rhythm-week-totals__spacer {
  min-width: 0;
}

.rhythm-week-totals__block {
  min-width: 0;
  font-size: 0.73rem;
  color: #6c757d;
  line-height: 1.3;
}

.rhythm-week-totals__block--previous,
.rhythm-week-totals__block--current {
  text-align: right;
}

.rhythm-week-totals__line {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Run-only view: only the run-km figure stays; the endurance-other
 * duration line (S+C, cross-training, etc.) is out of scope for a
 * running-only read of the week and would just be noise here. */
#squad-cards.squad-run-only .rhythm-week-totals__line--other {
  display: none;
}

/*
 * Connector: visually ties each total to the exact span of days it
 * covers, extending the rhythm strip's Monday separator
 * (.rhythm-day--week-start, same colour/weight) down through this row.
 * The polyline draws an "L" — a vertical leg at the block's left edge
 * (the week's Monday boundary), turning into a horizontal leg that runs
 * under the days being summed. preserveAspectRatio="none" lets the
 * horizontal leg stretch to fill the block's own width — 1 day early in
 * the current week, up to 7 for a complete week — while
 * vector-effect="non-scaling-stroke" keeps the line weight constant
 * regardless of that stretch.
 */
.rhythm-week-connector-wrap {
  position: relative;
  height: 12px;
}

.rhythm-week-connector {
  display: block;
  width: 100%;
  height: 12px;
}

.rhythm-week-connector polyline {
  fill: none;
  stroke: #dde3e8;
  stroke-width: 1;
}

/* Current (in-progress) week only: bolder, fixed-size arrowhead at the end
 * of the line, signalling the total is still accumulating. Deliberately
 * higher-contrast than the connector line itself (which stays pale/
 * unobtrusive) so it actually registers as an arrow at a glance. Rendered
 * in its own small, unstretched viewBox so it keeps its shape regardless
 * of how narrow the current-week block is (as little as 1 day on a
 * Monday). */
.rhythm-week-connector__arrow {
  position: absolute;
  top: 0;
  right: -1px;
  width: 12px;
  height: 12px;
}

.rhythm-week-connector__arrow polygon {
  fill: #4a5568;
}

/* ---- Most recent session --------------------------------------------- */
.athlete-card__session {
  font-size: 0.82rem;
  color: #495057;
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  min-height: 1.2em;
  overflow: hidden;
}

/* Both --all and --run-only variants are always rendered (see
 * templates/coach/home.html) so the Run-only toggle can switch between
 * them instantly, purely via CSS, with no JS text-swapping. All shows by
 * default; Run-only variant takes over under #squad-cards.squad-run-only. */
.athlete-card__session--run-only {
  display: none;
}

#squad-cards.squad-run-only .athlete-card__session--all {
  display: none;
}

#squad-cards.squad-run-only .athlete-card__session--run-only {
  display: flex;
}

.athlete-card__session--none {
  color: #adb5bd;
  font-style: italic;
}

.athlete-card__session-title {
  color: #1a2332;
  font-weight: 500;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;          /* required for ellipsis in flex child */
}

.athlete-card__session-sep {
  color: #adb5bd;
  flex-shrink: 0;           /* separator never truncates */
}

.athlete-card__session-when {
  color: #6c757d;
}

/* ---- Gap flag (7+ days without training) ----------------------------- */
.athlete-card__flag {
  margin-top: 0.55rem;
  font-size: 0.75rem;
  color: #856404;
  background: #fff8e6;
  border: 1px solid #ffe08a;
  border-radius: 4px;
  padding: 0.2em 0.55em;
  display: inline-block;
}

/* ======================================================================
   ATHLETE WORKSPACE
   ====================================================================== */

/*
 * Persistent coaching context — the fixed upper layer of the workspace.
 * Remains visible regardless of which view (CCS, Activity History) is active.
 * Purpose: orientation. The coach always knows who they are coaching and
 * carries the essential ambient context before engaging with the active view.
 */
.workspace-context {
  border-bottom: 2px solid #e3e7ea;
  padding-bottom: 0;
  margin-bottom: 1.5rem;
}

/* Identity row: athlete name + squad + admin actions */
.workspace-context__identity {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 0.9rem;
  /* Round R — same shared banner as .sticky-page-header/.tp-grid__header;
     recommendation not pixel-mocked for this page specifically, applied
     here as the closest equivalent to the other pages' title row. */
  background: linear-gradient(90deg, #2DD4BF, #3878C2);
  padding: 0.6rem 0.9rem;
  border-radius: 6px;
}

.workspace-context__name {
  font-size: 1.45rem;
  font-weight: 600;
  color: #1a2332;
  line-height: 1.2;
}

.workspace-context__squad {
  display: block;
  font-size: 0.82rem;
  color: #6c757d;
  margin-top: 0.15rem;
}

/* "Edit" readability on the teal→blue gradient identity row (item 2,
   David's phone check, 20 Oct 2026): the gradient's teal end gives
   plain grey/white text under ~2:1 contrast — nowhere near readable
   regardless of which outline colour Bootstrap's default
   .btn-outline-secondary picks. A solid near-white chip with dark text
   keeps contrast fixed and good (~15:1) no matter where the button
   falls along the gradient, instead of chasing a text colour that
   only works at one end of it. */
.workspace-context__edit-btn {
  background: rgba(255, 255, 255, 0.92);
  border-color: rgba(255, 255, 255, 0.92);
  color: #1a2332;
  font-weight: 600;
}

.workspace-context__edit-btn:hover,
.workspace-context__edit-btn:focus-visible {
  background: #fff;
  border-color: #fff;
  color: #1a2332;
}

/* Meta row: last activity, strava status, reviewed at, gap flag */
.workspace-context__meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.6rem 1rem;
  font-size: 0.82rem;
  color: #6c757d;
  margin-top: 0.35rem;
  margin-bottom: 0.75rem;
}

.workspace-context__last-activity {
  color: #495057;
}

.workspace-context__no-activity {
  color: #adb5bd;
  font-style: italic;
}

.workspace-context__reviewed {
  color: #9aa5b0;
}

.workspace-context__gap {
  color: #856404;
  background: #fff8e6;
  border: 1px solid #ffe08a;
  border-radius: 4px;
  padding: 0.1em 0.5em;
  font-size: 0.78rem;
}

/* Ambient coaching context: most recent journal entry above nav tabs */
.workspace-context__ccs-summary {
  margin: 0.5rem 0 0.75rem;
  padding: 0.6rem 0.75rem;
  background: #f8f9fa;
  border-left: 2px solid #dde3e8;
  border-radius: 0 4px 4px 0;
}

.workspace-context__ccs-date {
  font-size: 0.65rem;
  font-weight: 700;
  color: #9aa5b0;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 0.25rem;
  display: block;
}

.workspace-context__ccs-text {
  font-size: 0.88rem;
  color: #2d3748;
  line-height: 1.55;
  margin: 0;
  white-space: pre-wrap;
  /* Clamp to 2 lines by default; JS toggle adds .is-expanded */
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.workspace-context__ccs-text.is-expanded {
  display: block;
  -webkit-line-clamp: unset;
}

.workspace-context__ccs-toggle {
  background: none;
  border: none;
  padding: 0;
  margin-top: 0.2rem;
  font-size: 0.72rem;
  color: #9aa5b0;
  cursor: pointer;
  display: block;
}

.workspace-context__ccs-toggle:hover {
  color: #4a5568;
}

/* View navigation tabs */
/* Round F — this is athlete_detail.html's own layer-3 sticky header: the
   view switcher (Current Situation / Activity History / Profile). The
   larger .workspace-context block above it (identity row + training
   timeline) is deliberately NOT sticky — it's too tall to pin without
   eating most of the viewport, so it scrolls away normally and this nav
   is what stays reachable, mirroring the tab-bar pattern used elsewhere. */
.workspace-nav {
  display: flex;
  gap: 0;
  margin-top: 0.25rem;
  position: sticky;
  top: calc(var(--sticky-nav-height) + var(--sticky-tabs-height));
  z-index: 1000;
  background: #fff;
}

.workspace-nav__tab {
  display: inline-block;
  padding: 0.45rem 1.1rem;
  font-size: 0.875rem;
  font-weight: 500;
  color: #6c757d;
  text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -2px;
  transition: color 0.1s;
}

.workspace-nav__tab:hover {
  color: #1a2332;
  text-decoration: none;
}

.workspace-nav__tab--active {
  color: #1a2332;
  border-bottom-color: #1a2332;
}

/* ======================================================================
   CURRENT COACHING SITUATION VIEW
   ====================================================================== */

.ccs-view {
  margin-top: 1.25rem;
}

/*
 * The narrative text area is the primary content of the CCS view.
 * It should feel calm and spacious — inviting the coach to write
 * rather than demanding data entry.
 */
.ccs-notes__textarea {
  display: block;
  width: 100%;
  /* ~5 visible lines at 1.65 line-height × 0.95rem. The textarea expands
     automatically via JavaScript (oninput handler) as the coach types. */
  min-height: 100px;
  border: 1px solid #dde3e8;
  border-radius: 6px;
  padding: 0.9rem 1rem;
  font-size: 0.95rem;
  line-height: 1.65;
  color: #1a2332;
  background: #fff;
  resize: none;   /* manual resize disabled — auto-expand replaces it */
  font-family: inherit;
  overflow: hidden;  /* prevents scrollbar flash during auto-expand */
}

.ccs-notes__textarea:focus {
  outline: none;
  border-color: #9fb3c0;
  box-shadow: 0 0 0 3px rgba(159, 179, 192, 0.15);
}

.ccs-notes__textarea::placeholder {
  color: #b0bac3;
  font-style: italic;
}

.ccs-notes__actions {
  margin-top: 0.65rem;
  display: flex;
  justify-content: flex-end;
}

/* ======================================================================
   COACHING JOURNAL
   Append-only coaching narrative — the coach's evolving story of the
   athlete. Each entry is permanent. Displayed chronologically, oldest
   first, newest at the bottom. The new-entry textarea sits at the foot.
   ====================================================================== */

/*
 * Side-by-side CCS panels — Current Entry (left) and Recent Journal (right).
 * Together they occupy the space previously taken by the stacked journal + form,
 * keeping the Training Volume graph visible without scrolling.
 */
.ccs-panels {
  display: flex;
  gap: 1.25rem;
  align-items: flex-start;
  margin-bottom: 1rem;
}

/* Left: editable current situation entry */
.ccs-panel--current {
  flex: 0 0 44%;
  display: flex;
  flex-direction: column;
}

/* Right: compact read-only historical journal */
.ccs-panel--journal {
  flex: 1 1 0;
  min-width: 0;
}

/* Panel section label ("Current Situation" / "Recent Journal") */
.ccs-panel__label {
  font-size: 0.65rem;
  font-weight: 700;
  color: #9aa5b0;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 6px;
  display: flex;
  align-items: center;
  gap: 6px;
}

/* Entry count badge in the Recent Journal label */
.ccs-journal-count {
  font-size: 0.65rem;
  font-weight: 600;
  color: #c8d0d8;
}

/* Current Situation textarea */
.ccs-panel__textarea {
  width: 100%;
  border: 1px solid #e2e8f0;
  border-radius: 6px;
  padding: 0.55rem 0.75rem;
  font-size: 0.88rem;
  color: #2d3748;
  resize: none;
  min-height: 95px;
  max-height: 155px;
  line-height: 1.55;
  background: #fff;
  font-family: inherit;
}
.ccs-panel__textarea::placeholder {
  color: #b0bec9;
  font-size: 0.84rem;
}
.ccs-panel__textarea:focus {
  outline: none;
  border-color: #a0aec0;
  box-shadow: 0 0 0 2px rgba(160,174,192,0.15);
}

/* "Add Entry" button row */
.ccs-panel__actions {
  display: flex;
  justify-content: flex-end;
  margin-top: 0.45rem;
}

.coaching-journal {
  margin-bottom: 1.5rem;
}

/*
 * Entries wrapper provides position context for the top-fade gradient
 * that signals to the coach that older entries exist above the visible area.
 */
.coaching-journal__entries-wrap {
  position: relative;
  margin-bottom: 1rem;
}

/*
 * Top-fade overlay: a translucent gradient that appears when the journal
 * is scrolled (i.e. when there are entries above the visible area).
 * Applied with a CSS pseudo-element on a wrapper so no extra DOM nodes
 * are required. The JS scroll handler adds/removes .has-above.
 */
.coaching-journal__entries-wrap::before {
  content: '';
  position: absolute;
  top: 1px;          /* inside the border-radius of the entries div */
  left: 1px;
  right: 1px;
  height: 32px;
  background: linear-gradient(to bottom, #fafbfc 0%, transparent 100%);
  border-radius: 6px 6px 0 0;
  pointer-events: none;
  z-index: 1;
  opacity: 0;
  transition: opacity 0.15s;
}
.coaching-journal__entries-wrap.has-above::before {
  opacity: 1;
}

.coaching-journal__entries {
  /*
   * Compact fixed-height panel: approximately two to three typical entries.
   * Internal scroll reveals older entries. The Training Volume graph must
   * remain visible without scrolling on a standard desktop display.
   */
  max-height: 155px;  /* matches ccs-panel--current max-height for visual alignment */
  overflow-y: auto;
  border: 1px solid #e8ecf0;
  border-radius: 6px;
  padding: 0.5rem 0;
  background: #fafbfc;
  scrollbar-width: thin;
  scrollbar-color: #dde3e8 transparent;
}

/* Entry count badge — appears below the entries when there are more than 3 */
.coaching-journal__count {
  font-size: 0.65rem;
  color: #9aa5b0;
  text-align: right;
  margin-top: 3px;
  padding-right: 4px;
}

.journal-entry {
  padding: 0.6rem 1rem;
  border-bottom: 1px solid #edf0f3;
}

.journal-entry:last-child {
  border-bottom: none;
}

.journal-entry__date {
  font-size: 0.68rem;
  font-weight: 700;
  color: #9aa5b0;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  margin-bottom: 0.25rem;
}

.journal-entry__content {
  font-size: 0.9rem;
  line-height: 1.6;
  color: #2d3748;
  white-space: pre-wrap;
  /* Truncate to 3 lines by default. JS adds .is-expanded to remove clamp. */
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.journal-entry__content.is-expanded {
  display: block;   /* remove line-clamp */
  overflow: visible;
}

.journal-entry__read-more {
  display: none;    /* shown by JS only when content actually overflows */
  background: none;
  border: none;
  padding: 0;
  font-size: 0.75rem;
  color: #9aa5b0;
  cursor: pointer;
  margin-top: 2px;
  line-height: 1;
}
.journal-entry__read-more:hover {
  color: #4a5568;
}

.coaching-journal__textarea {
  min-height: 80px;
}

/* ======================================================================
   ATHLETE TIMELINE
   Week-aligned chronological view in the Athlete Workspace header.
   Three equal-width week columns: prev complete week, current week,
   next week (all future). Reuses existing .rhythm-* CSS for the bar
   rendering; adds week group structure on top.
   ====================================================================== */

/* "Athlete Timeline" section label above the week strip */
.athlete-timeline__header {
  font-size: 0.65rem;
  font-weight: 700;
  color: #9aa5b0;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 4px;
}

.athlete-timeline {
  display: flex;
  gap: 8px;                  /* visual separation between week groups */
  margin-bottom: 0.35rem;
  min-width: max-content;    /* prevents week columns from shrinking during scroll */
}

/*
 * Scrollable wrapper for the Athlete Timeline.
 * Allows horizontal exploration through training history while the default
 * view is centred on the current coaching period by JS on page load.
 */
.athlete-timeline-scroll {
  overflow-x: auto;
  overflow-y: hidden;
  scrollbar-width: thin;
  scrollbar-color: #dde3e8 transparent;
  margin-bottom: 0;
  padding-bottom: 4px;       /* room for thin scrollbar on macOS */
  -webkit-overflow-scrolling: touch;
}

.athlete-timeline-scroll::-webkit-scrollbar {
  height: 3px;
}
.athlete-timeline-scroll::-webkit-scrollbar-track {
  background: transparent;
}
.athlete-timeline-scroll::-webkit-scrollbar-thumb {
  background: #dde3e8;
  border-radius: 2px;
}

/* Timeline: ribbon wrapper + integrated edge-handle chevrons ------------ */
.athlete-timeline-wrap {
  position: relative;
}

/* CSS mask fades: create the continuous-ribbon effect at both edges.
   Content vanishes into the edges; handles sit within the fade zone.
   mask-image is not supported in all older browsers, but degrades
   gracefully (full visibility, no handles overlay). */
.athlete-timeline-scroll {
  -webkit-mask-image: linear-gradient(
    to right,
    transparent 0px,
    rgba(0,0,0,0.6) 18px,
    black 52px,
    black calc(100% - 52px),
    rgba(0,0,0,0.6) calc(100% - 18px),
    transparent 100%
  );
  mask-image: linear-gradient(
    to right,
    transparent 0px,
    rgba(0,0,0,0.6) 18px,
    black 52px,
    black calc(100% - 52px),
    rgba(0,0,0,0.6) calc(100% - 18px),
    transparent 100%
  );
}

/* Chevron handles — positioned over the fade zone at each edge */
.timeline-handle {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 52px;
  padding: 0;
  background: transparent;
  border: none;
  cursor: pointer;
  z-index: 3;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 2.25rem;
  font-weight: 300;
  color: rgba(100, 120, 145, 0.6);
  user-select: none;
  transition: color 0.15s;
}

.timeline-handle--prev { left:  0; }
.timeline-handle--next { right: 0; }

.timeline-handle:hover  { color: rgba(55, 85, 115, 0.70); }
.timeline-handle:active { color: rgba(30, 60, 100, 0.90); }

/* Return to Today — appears centred below the ribbon when scrolled away */
.timeline-footer {
  display: flex;
  justify-content: center;
  min-height: 22px;
  margin-top: 3px;
}

.return-to-today {
  font-size: 0.67rem;
  padding: 0.08rem 0.5rem;
  color: #b0bac3;
  background: transparent;
  border: 1px solid #e8ecf0;
  border-radius: 4px;
  cursor: pointer;
  transition: color 0.12s, border-color 0.12s;
}

.return-to-today:hover {
  color: #457b9d;
  border-color: #a8c4d8;
}

.athlete-timeline__week {
  /*
   * Fixed width: weeks do not flex-grow into the available space.
   * This ensures the inner timeline div overflows the scroll container
   * horizontally, making only ~5 weeks visible in a standard viewport.
   * Coaches navigate by scrolling; the JS centres the current week on load.
   */
  flex: 0 0 200px;
  min-width: 0;
}

/* Week date label ("14 Jul") above the bar strip */
.athlete-timeline__week-header {
  font-size: 0.62rem;
  font-weight: 600;
  color: #9aa5b0;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin-bottom: 2px;
  white-space: nowrap;
}

.athlete-timeline__week--current .athlete-timeline__week-header {
  color: #4a5568;
}

/* Week total beneath the bar strip */
.athlete-timeline__week-total {
  font-size: 0.65rem;
  color: #6c757d;
  text-align: right;
  margin-top: 2px;
  line-height: 1;
}

/* Days container — 7 columns within each week, no gap between weeks */
.athlete-timeline__days {
  margin-bottom: 0;
}

/* Future days: muted appearance — empty columns, not missing columns.
   Absence is meaningful coaching information (rest, travel, tapering). */
.rhythm-day--future {
  opacity: 0.3;
}

/* Today: subtle background accent so the column stands out at a glance */
.rhythm-day--today {
  background: rgba(74, 85, 104, 0.07);
  border-radius: 3px;
}

.rhythm-day--future .rhythm-day-label {
  color: #c8d0d8;
}

/* ======================================================================
   SHARED TAB BAR (Round H — unifies what were previously two
   byte-identical blocks: .athlete-tabs, shared between
   templates/athlete/dashboard.html and training_history.html, and
   .workspace-tabs, shared between templates/coach/home.html and
   board.html. Both now render via templates/components/_tab_bar.html.)
   ====================================================================== */

.nav-tabs-bar {
  display: flex;
  gap: 0.25rem;
  border-bottom: 1px solid #e3e7ea;
  position: sticky;
  top: var(--sticky-nav-height);
  z-index: 1010;
  /* Round AH: unconditional background: #fff removed — role-scoped tint
     now set near .sticky-page-header above (~line 60), matching that
     component's pattern; both coach and athlete pages always set one of
     body.role-coach/role-athlete (see base.html), so no fallback needed. */
}

.nav-tabs-bar__tab {
  padding: 0.5rem 0.9rem;
  font-size: 0.85rem;
  font-weight: 500;
  color: #6c757d;
  text-decoration: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
}

.nav-tabs-bar__tab:hover {
  color: #1a2332;
}

.nav-tabs-bar__tab--active {
  color: #1a2332;
  border-bottom-color: #1a2332;
}

/* ── Phone-only app-bar replacement for .nav-tabs-bar (mockup-matching
 * round, item 1) ───────────────────────────────────────────────────
 * Rendered as a sibling right after .nav-tabs-bar by every page that
 * includes _workspace_nav_tabs.html — hidden above the phone breakpoint
 * (full rule set lives in the shared phone media query, main.css,
 * alongside the board's other phone-only chrome); the base rule here
 * just keeps it out of the box tree at every wider width. */
.board-phone-topbar {
  display: none;
}

/* Board's two-panel layout is fixed-height (calc(100vh - navbar height)) so
   the planner and CCS panel never scroll the whole page. Adding the tab bar
   above it needs its own flex wrapper so that height budget still adds
   up — .workspace-shell no longer owns the full available height itself,
   its flex-grown parent (.workspace-page) does. Home isn't height
   constrained, so it doesn't need this wrapper; the tab bar there is a
   normal block at the top of the page. Round F: the navbar height was
   previously hardcoded as 56px here — now references the same measured
   custom property the sticky header stack uses, so this stays correct
   too if the navbar's rendered height ever changes. */
.workspace-page {
  display: flex;
  flex-direction: column;
  height: calc(100vh - var(--sticky-nav-height));
  /* Round AO: renders outside base.html's .container (see
     full_width_content block) so it gets the real viewport width
     instead of Bootstrap's centred, max-width column — but shouldn't
     sit flush against the browser edge either, hence this small flat
     gutter (much narrower than .container's old ~280px side margins
     on a wide screen). Reduced further below 900px so mobile isn't
     worse off than it was under .container's own ~12px gutter. */
  padding: 0 20px;
}

.workspace-page .nav-tabs-bar {
  flex: 0 0 auto;
}

.workspace-page .board-phone-topbar {
  flex: 0 0 auto;
}

.workspace-page .workspace-shell {
  flex: 1 1 auto;
  height: auto;
  min-height: 0;
}

/* ======================================================================
   RACE CALENDAR (fixture list)
   templates/athlete/fixtures.html (round 10, relaid-out) — race/race_key
   planner entries across all squads, chronological, upcoming-first.

   Two fixed rows per entry rather than one reflowing flex row: main info
   (date, chip, title) on top, scope + Details always on their own row
   underneath. Keeps Details right-aligned every time regardless of how
   long the title is or whether it wraps — the previous single-row layout
   let a long title push Details onto its own line flush-left instead of
   staying right-aligned, which read as misaligned/untidy down the list.
   ====================================================================== */

.fixture-item {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

/* Round AD: pale blue tint, coach view only — .fixture-item is shared
   with the athlete's own Race Calendar view (templates/athlete/
   fixtures.html), which must stay on Bootstrap's plain white
   .list-group-item default (unaffected by this round). */
body.role-coach .fixture-item {
  background: #F6F9FD;
  border-color: #dbe8f5;
}

.fixture-item--past {
  color: var(--bs-secondary-color, #6c757d);
}

.fixture-item__main {
  /* Round V — grid, not flex. Flex-wrap let a title that didn't fit the
     remaining space on the date+chip's line relocate as a whole flex item
     onto a new line starting flush at the container's left edge (ragged,
     inconsistent with shorter titles that stayed inline). Grid columns are
     fixed-width and a child never relocates out of its own column — the
     title simply wraps *within* its column at a consistent start position
     every time. `minmax(0, 1fr)` (not bare `1fr`) so a long unbroken word
     in the title can't push the grid wider than its container. */
  display: grid;
  grid-template-columns: 5rem 5.5rem minmax(0, 1fr);
  align-items: center;
  gap: 0.85rem;
}

.fixture-date {
  /* min-width/flex-shrink were flexbox-only sizing hints — the grid
     column width (5rem, above) now fixes this column's width directly.
     Round W: widened from 3.5rem to 5rem — 3.5rem was too narrow to fit
     "Tue 25 Aug"-style dates on one line (text-nowrap below), so the
     date text visually overflowed into the chip's column and sat behind
     it — a second, more severe symptom of the same Round V grid
     conversion the dead-flex fix (below) alone didn't cover. */
  line-height: 1.25;
}

.fixture-date__day {
  font-weight: 600;
}

.fixture-date__year {
  font-size: 0.8rem;
  color: var(--bs-secondary-color, #6c757d);
}

.fixture-chip {
  display: inline-block;
  /* Round W — the grid column itself (5.5rem, .fixture-item__main's
     grid-template-columns) already fixes this element's available width
     now that Round V converted the parent to display: grid. The old
     `flex: 0 0 5.5rem` (Round T) did nothing inside a grid container —
     dead code left behind by Round V's flex→grid change, and the actual
     cause of the chip rendering chopped-off (a grid item ignores flex-
     basis/grow/shrink entirely; only `width` below governs its size). */
  width: 5.5rem;           /* fixed — was auto-width via padding only */
  white-space: nowrap;
  overflow: hidden;
  text-align: center;      /* centre "Race"/"Key Race" within the fixed width */
  padding: 0.15rem 0.4rem; /* narrower now width is fixed (was 0.15rem 0.6rem) */
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 600;
  line-height: 1.4;
  background: #d5d5d5;
  color: #1a2332;
}

.fixture-chip--key {
  font-weight: 700;
}

.fixture-item__title {
  font-weight: 500;
  min-width: 0;
}

.fixture-item__meta {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
  padding-left: calc(3.5rem + 0.85rem);
}

.fixture-item__actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  padding-left: calc(3.5rem + 0.85rem);
}

.fixture-item__scope {
  font-size: 0.85rem;
  color: var(--bs-secondary-color, #6c757d);
}

/* ======================================================================
   FIXTURE LIST v2 — brief 31 v0.5 (feature-flagged "training_plan_v2")
   routes/fixtures_v2.py, templates/athlete/fixtures_v2_list.html /
   fixtures_v2_detail.html. A NEW, separate layout — .fixture-item above
   is untouched and still exactly what an unflagged athlete sees.

   Row is 3 columns: date block / title+meta / one right-aligned action.
   No badge, no club/eligibility line, no Details/Manage buttons on the
   row itself (all moved to the fixture detail page). ====================================================================== */

.fx2-month-header {
  font-size: 0.78rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--bs-secondary-color, #6c757d);
  padding: 0.9rem 0.25rem 0.25rem;
}

.fx2-month-header:first-child {
  padding-top: 0.25rem;
}

.fx2-row {
  display: grid;
  grid-template-columns: 4.5rem minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.85rem;
  padding: 0.6rem 0.5rem;
  text-decoration: none;
  color: inherit;
  border-bottom: 1px solid var(--bs-border-color, #dee2e6);
}

.fx2-row:hover {
  background: #f6f9fd;
}

.fx2-row--past {
  color: var(--bs-secondary-color, #6c757d);
}

.fx2-row__date {
  line-height: 1.2;
  font-weight: 600;
  text-align: center;
}

.fx2-row__weekday {
  display: block;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  color: var(--bs-secondary-color, #6c757d);
}

.fx2-row__daymonth {
  display: block;
}

.fx2-row__main {
  min-width: 0;
}

.fx2-row__title {
  font-weight: 500;
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
}

.fx2-row__star {
  color: #caa000;
  font-size: 0.9rem;
}

.fx2-row__action {
  white-space: nowrap;
}

.fx2-today-divider {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  color: var(--bs-primary, #0d6efd);
  font-size: 0.78rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0.5rem 0.25rem;
}

.fx2-today-divider::after {
  content: "";
  flex: 1 1 auto;
  border-top: 1px solid var(--bs-primary, #0d6efd);
  opacity: 0.35;
}

.fx2-earlier-toggle {
  margin: 0 0 1rem;
}

/* Round 3: race-type filter chips \u2014 horizontally scrolling within
   their own row so a long chip set never causes page-level horizontal
   scroll on phone (see tests/test_phone_no_horizontal_scroll.py). */
.fx2-filter-chips {
  display: flex;
  gap: 0.4rem;
  overflow-x: auto;
  flex-wrap: nowrap;
  margin: 0 0 1rem;
  padding-bottom: 0.15rem;
  -webkit-overflow-scrolling: touch;
}

.fx2-chip {
  flex: 0 0 auto;
  white-space: nowrap;
}

.fx2-chip.active {
  background-color: var(--bs-primary, #0d6efd);
  color: #fff;
  border-color: var(--bs-primary, #0d6efd);
}

/* 3-state "Add to my plan" button (brief 31 section 3) */
.fx2-add-btn {
  font-size: 0.82rem;
}

.fx2-day-chips {
  display: flex;
  gap: 0.35rem;
  flex-wrap: wrap;
  margin-top: 0.4rem;
}

.fx2-day-chip {
  font-size: 0.75rem;
  padding: 0.2rem 0.55rem;
}

.fx2-key-followup {
  margin-top: 0.4rem;
  font-size: 0.82rem;
}

/* ----------------------------------------------------------------------
   Race Calendar redesign (29 Oct 2026, switch "race_calendar_redesign").
   Everything here is under .fx2r-* or .fx2-list--redesign, so the
   current Race Calendar (switch off) is untouched.

   Month cards: every month a see-through card with a coloured header,
   alternating the banner's teal and blue (not race-type colours, which
   would read as meaning something). Rows
   have no button: a teal "✓ Racing" chip on the athlete's own races
   (teal = go/done in ECP's palette), a chevron otherwise. Athlete pages
   have no dark theme yet; if one comes, the band tint and chip need dark
   values too.
   ---------------------------------------------------------------------- */
/* David, 29 Oct 2026 (option E): every month is a see-through card in
   "the look" (brief 48: white at 50%, a fine inset edge, blur), never
   solid white. The colour sits in the month header, alternating two
   deep shades of the banner gradient's own teal (#2DD4BF) and blue
   (#3878C2) — ECP's identity colours, not the status teal of "✓ Racing".
   Matched on relative luminance (both ≈0.16) so no month looks more
   prominent, and both give white text 5:1 contrast. */
.fx2r-month {
  --fx2r-month-head: #237c71;
  border-radius: 12px;
  margin-bottom: 0.6rem;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.5);
  box-shadow: inset 0 0 0 0.5px rgba(47, 95, 158, 0.14);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
}

.fx2r-month--alt {
  --fx2r-month-head: #3571b7;
}

.fx2-list--redesign .fx2r-month .fx2-month-header {
  background: var(--fx2r-month-head);
  color: #fff;
  font-size: 0.95rem;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  padding: 0.5rem 0.85rem;
}

.fx2r-month .fx2r-row,
.fx2r-month .fx2-today-divider {
  margin: 0 0.35rem;
}

.fx2r-month > :last-child {
  margin-bottom: 0.3rem;
}

.fx2-row.fx2r-row {
  grid-template-columns: 3rem minmax(0, 1fr) auto;
  gap: 0.75rem;
  padding: 0.6rem 0.4rem;
  min-height: 3.5rem;
  border-bottom: 0;
  border-top: 1px solid rgba(47, 95, 158, 0.12);
  text-decoration: none;
  color: inherit;
}

.fx2-month-header + .fx2r-row,
.fx2-today-divider + .fx2r-row {
  border-top: 0;
}

.fx2-row.fx2r-row:hover,
.fx2-row.fx2r-row:focus-visible {
  background: rgba(15, 118, 110, 0.06);
  border-radius: 8px;
}

.fx2r-tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.1;
  text-align: center;
}

.fx2r-tile__day {
  font-size: 1.45rem;
  font-weight: 600;
  color: #1a2332;
  font-variant-numeric: tabular-nums;
}

.fx2r-tile__weekday {
  font-size: 0.75rem;
  color: #475569;
}

.fx2r-tile__to {
  font-size: 0.7rem;
  color: #475569;
  margin-top: 0.1rem;
  white-space: nowrap;
}

.fx2r-main {
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.fx2r-title {
  font-weight: 500;
  line-height: 1.3;
  color: #1a2332;
  overflow-wrap: anywhere;
}

/* Joined to the last word with a no-break space, so it wraps with it. */
.fx2r-star {
  color: #b7791f;
  font-size: 0.9rem;
}

.fx2r-meta {
  font-size: 0.8rem;
  color: #475569;
  margin-top: 0.1rem;
}

.fx2r-chip {
  font-size: 0.8rem;
  font-weight: 500;
  white-space: nowrap;
  padding: 0.2rem 0.6rem;
  border-radius: 999px;
  background: rgba(15, 118, 110, 0.12);
  color: #0f766e;
}

.fx2r-chevron {
  font-size: 1.4rem;
  line-height: 1;
  color: #94a3b8;
  padding: 0 0.15rem;
}

/* Past races: muted throughout, including the tile, star and chip. */
.fx2r-row--past .fx2r-tile__day,
.fx2r-row--past .fx2r-title,
.fx2r-row--past .fx2r-meta,
.fx2r-row--past .fx2r-tile__weekday {
  color: #8a94a3;
}

.fx2r-row--past .fx2r-star {
  color: #cbc3ad;
}

.fx2r-chip--past {
  background: rgba(100, 116, 139, 0.12);
  color: #64748b;
}

/* Fixture page, redesign: "Add to my plan" is the main action. */
.fx2r-add-main {
  display: block;
  width: 100%;
  min-height: 44px;
  border: 0;
  border-radius: 12px;
  background: #0f766e;
  color: #fff;
  font-weight: 600;
  font-size: 1rem;
}

.fx2r-add-main:hover,
.fx2r-add-main:focus-visible {
  background: #0d655e;
}

.fx2r-add-main:disabled {
  opacity: 0.7;
}

.fx2r-key-ask {
  margin: 0 0 0.75rem;
  font-size: 0.95rem;
}

/* Fixture detail page */
.fx2-detail-header {
  margin-bottom: 1.25rem;
}

.fx2-detail-date {
  font-size: 0.9rem;
  color: var(--bs-secondary-color, #6c757d);
}

.fx2-race-entry-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--bs-border-color, #dee2e6);
}

.fx2-squadmates {
  font-size: 0.88rem;
}

/* ======================================================================
   Volume history —
   SVG line graph showing training trajectory over ~12 weeks.
   Architecture reserves space for Context Interval bands (background
   bands representing coaching context such as altitude camp, illness,
   competition phase). Empty in Phase 1 — no interval data model yet.
   ====================================================================== */

.volume-history {
  margin-top: 1.75rem;
  padding-top: 1rem;
  border-top: 1px solid #f0f2f4;
}

.volume-history__header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 0.5rem;
}

.volume-history__title {
  font-size: 0.72rem;
  font-weight: 600;
  color: #9aa5b0;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* "This week so far" partial-total label — A.7, sits directly under the
   chart, styled with the same accent colour as the dashed current-week
   line/dot so the two visually read as one signal. */
.volume-history__current-week-label {
  margin: 0.35rem 0 0;
  font-size: 0.74rem;
  font-weight: 600;
  color: #457b9d;
}

/* Separator between timescale toggles and modality overlay toggles */
.volume-history__overlay-sep {
  font-size: 0.75rem;
  color: #c8d0d8;
  padding: 0 2px;
  user-select: none;
}

/* The SVG fills the container width; height comes from viewBox aspect ratio */
.volume-history__svg {
  width: 100%;
  display: block;
  overflow: visible;        /* allow labels to escape viewBox slightly */
}

/* ==========================================================================
   Running volume week readout (brief 48 Stage 4 item 3) — the
   coach's athlete page and Squad Overview's athlete panel
   ========================================================================== */
.vol-readout {
  min-height: 1.4em;
  margin: .15rem 0 .4rem;
  font-size: .95rem;
  font-weight: 600;
  color: #1a2332;
  font-variant-numeric: tabular-nums;
}

.vol-chart {
  position: relative;
  touch-action: pan-y;      /* a sideways finger drag scrubs the weeks */
}

.vol-announce {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* The readout above the chart replaces the old "This week so far" line. */
.vol-chart .volume-history__current-week-label { display: none; }

/* Keyboard/screen-reader week buttons over the running plot only. They
   take no pointer events, so the SVG's own tooltips (altitude bands,
   overlay points, other-sports bars) still work; the readout script maps
   pointer positions to weeks itself. */
.vol-hits {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  pointer-events: none;
}

.vol-hit {
  position: absolute;
  top: 0;
  bottom: 0;
  margin: 0;
  padding: 0;
  border: 0;
  border-radius: 4px;
  background: transparent;
  pointer-events: none;
}

.vol-hit.is-selected { background: rgba(45, 212, 191, .12); }
.vol-hit:focus-visible { outline: 2px solid #0b7a6d; outline-offset: -2px; }

.vol-marker {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 0;
  border-left: 1.5px solid rgba(11, 122, 109, .7);
  transform: translateX(-.75px);
  pointer-events: none;
}

/* Modality overlay toggle "on" state: a clearer pressed/filled look than a
   plain colour darken, so it's obvious at a glance without hovering. */
.modality-toggle--on {
  background-color: #1a2332;
  border-color: #1a2332;
  color: #fff;
}

.modality-toggle--on:hover,
.modality-toggle--on:focus {
  background-color: #1a2332;
  border-color: #1a2332;
  color: #fff;
}

/* ======================================================================
   VOLUME CHART + RECENT SESSIONS ROW (CCS view)
   ======================================================================
   Wraps .volume-history and .recent-sessions side by side so Recent
   Sessions doesn't sit below-the-fold under the chart on a typical
   laptop viewport. Falls back to the existing full-width stacked
   layout below 900px (same breakpoint as .workspace-page's own
   stacked fallback above) rather than force a cramped two-column
   squeeze on narrow/mobile viewports.

   The nested selectors below reset the top margin/padding/border that
   .volume-history and .recent-sessions normally carry (they were
   designed as independently-stacked full-width blocks) — those base
   rules stay untouched since both classes are reused elsewhere
   (athlete/dashboard.html, coach/board.html) where they still stack.
   ====================================================================== */
.ccs-volume-row {
  display: flex;
  gap: 1.75rem;
  align-items: flex-start;
  margin-top: 1.75rem;
  padding-top: 1rem;
  border-top: 1px solid #f0f2f4;
}

.ccs-volume-row #volume-history-region {
  flex: 3 1 0;
  min-width: 0;
}

.ccs-volume-row .volume-history {
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

.ccs-volume-row .recent-sessions {
  flex: 2 1 0;
  min-width: 0;
  margin-top: 0;
  padding-top: 0;
  border-top: none;
}

@media (max-width: 900px) {
  .ccs-volume-row {
    flex-direction: column;
  }
  .ccs-volume-row .recent-sessions {
    margin-top: 1.5rem;
    padding-top: 1rem;
    border-top: 1px solid #f0f2f4;
  }
}

/* ======================================================================
   SESSION PANEL — inline activity detail
   ======================================================================
   Sits between the persistent context header and the active view (CCS /
   History). Loaded via fetch() when a timeline tile is clicked. The panel
   should feel like a natural extension of the workspace, not a modal.

   Design principles:
   • Calm: no drop shadow, no bright border. A quiet left accent is enough.
   • Coherent: same typographic scale as the workspace context above.
   • Focused: shows the three things a coach needs — when, what, key metrics.
   • Dismissible: close button (top-right), Escape, or click outside.
   ====================================================================== */

.session-panel-container {
  margin-bottom: 1.5rem;
}

/* The rendered fragment — a single flat card with a coloured left edge */
.session-panel-container > :first-child {
  background: #fff;
  border: 1px solid #e3e7ea;
  border-left: 3px solid #4a5568;
  border-radius: 0 6px 6px 0;
  padding: 1.1rem 1.25rem 0.9rem;
}

/* Header: date + title + close button */
.sp-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  margin-bottom: 0.85rem;
}

.sp-header__left {
  min-width: 0;
}

.sp-date {
  font-size: 0.65rem;
  font-weight: 700;
  color: #9aa5b0;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 0.2rem;
}

.sp-title {
  font-size: 1.1rem;
  font-weight: 600;
  color: #1a2332;
  margin: 0 0 0.15rem;
  line-height: 1.3;
}

.sp-type {
  font-size: 0.78rem;
  color: #6c757d;
}

.sp-close {
  flex-shrink: 0;
  background: none;
  border: none;
  padding: 0.1rem 0.25rem;
  margin-left: 1rem;
  margin-top: -0.15rem;
  color: #b0bac3;
  font-size: 1.25rem;
  line-height: 1;
  cursor: pointer;
  border-radius: 3px;
  transition: color 0.1s;
}

.sp-close:hover {
  color: #4a5568;
}

/* Metric row */
.sp-metrics {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  margin-bottom: 0.9rem;
}

.sp-metric {
  display: flex;
  align-items: baseline;
  gap: 0.2rem;
}

.sp-metric__value {
  font-size: 1.15rem;
  font-weight: 700;
  color: #1a2332;
  line-height: 1;
}

.sp-metric__unit {
  font-size: 0.72rem;
  color: #9aa5b0;
  font-weight: 500;
}

/* Coaching context: nearest journal note */
.sp-context {
  padding: 0.55rem 0.75rem;
  background: #f8f9fa;
  border-left: 2px solid #dde3e8;
  border-radius: 0 3px 3px 0;
  margin-bottom: 0.85rem;
}

.sp-context__label {
  font-size: 0.62rem;
  font-weight: 700;
  color: #9aa5b0;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 0.2rem;
}

.sp-context__text {
  font-size: 0.83rem;
  color: #2d3748;
  line-height: 1.5;
  white-space: pre-wrap;
}

/* Footer: link to full details page */
.sp-footer {
  display: flex;
  justify-content: flex-end;
}

.sp-footer__link {
  font-size: 0.8rem;
  color: #4a5568;
  text-decoration: none;
}

.sp-footer__link:hover {
  color: #1a2332;
  text-decoration: underline;
}

/* Brief 48 Stage 4: "View on Strava" beside "Full details". Bold and a
   darkened Strava orange, #b83d00 (Strava's #FC5200 is under 3:1 on these light
   surfaces) so it stays identifiable as Strava's link and readable. */
.sp-footer { gap: .5rem 1.25rem; flex-wrap: wrap; }
.sp-footer__link--strava { color: #b83d00; font-weight: 600; }
.sp-footer__link--strava:hover { color: #962f00; }

/* Loading spinner placeholder */
.sp-loading {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.25rem 0;
}

.sp-loading__dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  background: #b0bac3;
  border-radius: 50%;
  animation: sp-pulse 0.9s ease-in-out infinite;
}

@keyframes sp-pulse {
  0%, 100% { opacity: 0.3; transform: scale(0.85); }
  50%       { opacity: 1;   transform: scale(1); }
}

/* Active tile indicator — subtle brightness lift while panel is open */
.session-tile--active {
  filter: brightness(1.15);
  outline: 2px solid rgba(26, 35, 50, 0.35);
  outline-offset: 1px;
}

/* ======================================================================
   ALLOCATE SHEET (brief 32 Stage B, Priority 1)
   ======================================================================
   #allocate-sheet-container is opened via the Session Panel Controller
   (templates/base.html, data-panel-url) from the board's per-day
   "Allocate" button and from Squad Settings' "Usual allocation" link.
   Unlike .session-panel-container (a plain in-flow card), this is a
   true overlay: a right-edge side panel on desktop, a bottom sheet on
   phone — same breakpoint as #planner-sheet's phone treatment, for a
   consistent feel across the board's overlays.
   ====================================================================== */

#allocate-sheet-container:not([hidden]) {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: 380px;
  max-width: 100vw;
  z-index: 1220;
  background: #fff;
  box-shadow: -8px 0 24px rgba(26, 35, 50, 0.25), 0 0 0 100vmax rgba(26, 35, 50, 0.35);
  overflow-y: auto;
}

/* Same overlay chrome, reused for the Manage page's group/sessions
   screens (brief 39 item 3) — one shared geometry rule rather than a
   second copy, extended to this id. */
#squad-manage-sheet-container:not([hidden]) {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: 380px;
  max-width: 100vw;
  z-index: 1220;
  background: #fff;
  box-shadow: -8px 0 24px rgba(26, 35, 50, 0.25), 0 0 0 100vmax rgba(26, 35, 50, 0.35);
  overflow-y: auto;
}

@media (max-width: 520px), (pointer: coarse) and (max-height: 500px) {
  :root {
    --workspace-phone-topbar-height: 0px;
  }

  #allocate-sheet-container:not([hidden]),
  #squad-manage-sheet-container:not([hidden]) {
    top: auto;
    left: 0;
    right: 0;
    width: 100%;
    height: auto;
    max-height: 85vh;
    border-radius: 14px 14px 0 0;
    box-shadow: 0 -8px 24px rgba(26, 35, 50, 0.25), 0 0 0 100vmax rgba(26, 35, 50, 0.35);
  }
}

.allocate-sheet {
  display: flex;
  flex-direction: column;
  min-height: 100%;
  padding: 1rem 1.1rem max(1rem, env(safe-area-inset-bottom));
}

.allocate-sheet__header {
  margin-bottom: 0.75rem;
}

.allocate-sheet__date {
  font-size: 0.72rem;
  font-weight: 700;
  color: #9aa5b0;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.allocate-sheet__title {
  font-size: 1.05rem;
  font-weight: 700;
  color: #1a2332;
  margin: 0.1rem 0 0;
}

.allocate-sheet__totals {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin-bottom: 0.85rem;
}

.allocate-total-chip {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  font-size: 0.78rem;
  color: #2d3748;
  border: 1px solid #dde3e8;
  border-radius: 999px;
  padding: 0.15rem 0.6rem;
}

.allocate-total-chip__dot {
  width: 0.55em;
  height: 0.55em;
  border-radius: 50%;
  flex: 0 0 auto;
}

.allocate-total-chip--unallocated {
  color: #9aa5b0;
  border-color: #dde3e8;
}

.allocate-total-chip--own-programme {
  color: #0f766e;
  border-color: #0f766e;
}

/* Transient "Replaces the change from …" confirmation (Claude's review
   round, fix 1) — appears briefly after a standing save that revoked
   an existing later-dated standing row, auto-removed by its own JS. */
.allocate-sheet__replaced-note {
  font-size: 0.78rem;
  font-weight: 600;
  color: #0f766e;
  background: #ecfdf5;
  border: 1px solid #a7f3d0;
  border-radius: 4px;
  padding: 0.4rem 0.6rem;
  margin-bottom: 0.75rem;
}

.allocate-sheet__scope {
  margin-bottom: 0.9rem;
}

.allocate-sheet__rows {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
  overflow-y: auto;
  padding-bottom: 0.5rem;
}

.allocate-row {
  border-bottom: 1px solid #f0f2f4;
  padding-bottom: 0.5rem;
}

.allocate-row__name {
  font-size: 0.88rem;
  font-weight: 600;
  color: #1a2332;
  margin-bottom: 0.3rem;
}

.allocate-row__switch {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
}

.allocate-switch-btn {
  font-size: 0.78rem;
  font-weight: 600;
  color: #4a5568;
  background: #f5f8fb;
  border: 1px solid #dde3e8;
  border-radius: 999px;
  padding: 0.25rem 0.7rem;
}

.allocate-switch-btn.is-active {
  background: #1a2332;
  border-color: #1a2332;
  color: #fff;
}

/* Brief 38, Stage 1.4 — a separate toggle, not another group choice:
 * a small gap plus its own border sets it apart from the Group 1/
 * Group 2/"–" row of mutually-exclusive choices beside it. Active
 * state uses the same amber as .allocate-row__note's "moved"/"new"
 * text below, since both signal "this athlete needs a second look". */
.allocate-switch-btn--elsewhere {
  margin-left: 0.4rem;
  border-color: #e0c08a;
}

.allocate-switch-btn--elsewhere.is-active {
  background: #c07a1e;
  border-color: #c07a1e;
  color: #fff;
}

/* Brief 39, item 2 — equal-weight choice alongside the group buttons
 * (NOT offset like .allocate-switch-btn--elsewhere, which is a
 * separate on/off toggle, not a mutually-exclusive third option; see
 * the brief's own wording: "three equal buttons"). Teal to match the
 * Today card's own own-programme/Elsewhere tag colours. */
.allocate-switch-btn--own-programme {
  border-color: #0f766e;
}

.allocate-switch-btn--own-programme.is-active {
  background: #0f766e;
  border-color: #0f766e;
  color: #fff;
}

/* Brief 33 section 6: athletes added from the register who aren't on ECP yet. */
.allocate-row__tag {
  font-size: 0.75rem;
  font-weight: 400;
  color: #4a5568;
  white-space: nowrap;
}

.allocate-row__note {
  font-size: 0.72rem;
  color: #c07a1e;
  margin-top: 0.25rem;
}

.allocate-sheet__footer {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.75rem;
  padding-top: 0.75rem;
  border-top: 1px solid #dde3e8;
  position: sticky;
  bottom: 0;
  background: #fff;
}

.allocate-sheet__footer button {
  flex: 1 1 0;
}

/* Trigger button in the board's stacked sub-squad indicator */
.board-subsquad-allocate-btn {
  font-size: 0.68rem;
  font-weight: 600;
  color: #4a5568;
  background: none;
  border: 1px dashed #b0bac3;
  border-radius: 999px;
  padding: 0.05rem 0.5rem;
  margin-left: 0.2rem;
}

/* ======================================================================
   MANAGE SQUADS & GROUPS (brief 39 item 3 — phone-first redesign)
   ======================================================================
   Large tappable rows (>=54px) grouped into cards (Athletes/Groups/
   Sessions); group/sessions sub-screens and the archive confirm sheet
   reuse #squad-manage-sheet-container's overlay chrome (defined above,
   alongside #allocate-sheet-container). Desktop uses the same
   structure in a centred ~640px column rather than a second layout.
   ====================================================================== */

.manage-page-container {
  max-width: 640px;
  margin: 0 auto;
}

.squad-tab-new {
  position: relative;
  display: inline-block;
}

.squad-tab-new > summary {
  list-style: none;
  cursor: pointer;
}

.squad-tab-new > summary::-webkit-details-marker {
  display: none;
}

.squad-tab-new__form {
  position: absolute;
  top: 100%;
  left: 0;
  z-index: 10;
  background: #fff;
  border: 1px solid #e3e7ea;
  border-radius: 6px;
  box-shadow: 0 4px 16px rgba(26, 35, 50, 0.12);
  padding: 0.75rem;
  margin-top: 0.25rem;
  white-space: nowrap;
}

/* "+ Add group" (brief 39 item 3 review round) — same disclosure
   pattern as .squad-tab-new, but in-flow rather than an absolute
   popover (there's no chips row's cramped horizontal space to escape
   here; the form just appears below the button within the card). */
.manage-add-disclosure > summary {
  list-style: none;
  cursor: pointer;
}

.manage-add-disclosure > summary::-webkit-details-marker {
  display: none;
}

.squad-manage-swatch {
  display: inline-block;
  width: 0.9em;
  height: 0.9em;
  border-radius: 50%;
  flex-shrink: 0;
}

.manage-row {
  display: flex;
  align-items: center;
  width: 100%;
  min-height: 54px;
  padding: 0.65rem 1rem;
  gap: 0.75rem;
  background: none;
  border: none;
  border-bottom: 1px solid #f0f2f4;
  text-align: left;
  text-decoration: none;
  color: inherit;
  cursor: pointer;
}

.manage-row:last-of-type {
  border-bottom: none;
}

.manage-row:hover {
  background: #f8f9fa;
}

.manage-row__icon {
  flex-shrink: 0;
  font-size: 1.2rem;
  width: 0.9em;
  text-align: center;
}

.manage-row__body {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.manage-row__title {
  font-size: 0.92rem;
  font-weight: 600;
  color: #1a2332;
}

.manage-row__summary {
  font-size: 0.78rem;
  color: #6c757d;
  white-space: normal;
  overflow-wrap: break-word;
}

.manage-row__chevron {
  flex-shrink: 0;
  font-size: 1.3rem;
  color: #b0bac3;
}

.manage-row-footer {
  padding: 0.65rem 1rem 0.85rem;
  border-top: 1px solid #f0f2f4;
}

.manage-archive-row {
  margin: 1rem 0 2rem;
  text-align: center;
}

/* Group/sessions screens injected into #squad-manage-sheet-container */
.manage-screen {
  display: flex;
  flex-direction: column;
  min-height: 100%;
  padding: 1rem 1.1rem max(1rem, env(safe-area-inset-bottom));
}

.manage-screen__header {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 0.9rem;
}

.manage-screen__title {
  flex: 1;
  font-size: 1.05rem;
  font-weight: 700;
  color: #1a2332;
  margin: 0;
}

.manage-screen__close {
  background: none;
  border: none;
  font-size: 1.4rem;
  line-height: 1;
  color: #b0bac3;
  cursor: pointer;
  padding: 0.1rem 0.3rem;
}

.manage-screen__close:hover {
  color: #4a5568;
}

/* Brief 48 Stage 1 (D5): sheets save in place. "Saved ✓" beside the
   button reserves its width so nothing shifts when it appears; teal
   text (a done/chosen meaning). Errors sit inside the sheet. */
.sheet-saved {
  display: inline-block;
  min-width: 4.8em;
  font-size: 0.9rem;
  font-weight: 600;
  color: #0b7a6d;
}

.sheet-save-error {
  color: #b42318;
  font-size: 0.9rem;
  font-weight: 500;
}

.manage-screen__archive {
  align-self: flex-start;
  margin-top: 1.25rem;
}

/* Archive confirm sheet — a plain in-flow card (used both as the
   Manage page's top-level panel content, and nested inside a group
   screen) rather than a second fixed overlay stacked on top of one. */
.confirm-sheet {
  background: #fff;
  border: 1px solid #e3e7ea;
  border-radius: 6px;
  padding: 1rem 1.1rem;
}

.confirm-sheet__title {
  font-size: 1rem;
  font-weight: 700;
  color: #1a2332;
  margin: 0 0 0.5rem;
}

.confirm-sheet__body {
  font-size: 0.85rem;
  color: #4a5568;
  line-height: 1.5;
  margin-bottom: 1rem;
}

.confirm-sheet__actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
}

/* ======================================================================
   SUB-SQUAD SESSION PANEL (brief 32 Stage B, Priority 2)
   ======================================================================
   #subsquad-session-container is opened via the Session Panel Controller
   (templates/base.html, data-panel-url) from a board chip's tap-to-edit
   trigger (.board-subsquad-chip). Same overlay chrome as the Allocate
   sheet above — a right-edge side panel on desktop, a bottom sheet on
   phone — for a consistent feel across the board's overlays.
   ====================================================================== */

#subsquad-session-container:not([hidden]) {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: 380px;
  max-width: 100vw;
  z-index: 1220;
  background: #fff;
  box-shadow: -8px 0 24px rgba(26, 35, 50, 0.25), 0 0 0 100vmax rgba(26, 35, 50, 0.35);
  overflow-y: auto;
}

@media (max-width: 520px), (pointer: coarse) and (max-height: 500px) {
  #subsquad-session-container:not([hidden]) {
    top: auto;
    left: 0;
    right: 0;
    width: 100%;
    height: auto;
    max-height: 85vh;
    border-radius: 14px 14px 0 0;
    box-shadow: 0 -8px 24px rgba(26, 35, 50, 0.25), 0 0 0 100vmax rgba(26, 35, 50, 0.35);
  }
}

.subsquad-session-panel {
  display: flex;
  flex-direction: column;
  min-height: 100%;
  padding: 1rem 1.1rem;
}

.subsquad-session-panel__header {
  margin-bottom: 0.75rem;
}

.subsquad-session-panel__date {
  font-size: 0.72rem;
  font-weight: 700;
  color: #9aa5b0;
  text-transform: uppercase;
  letter-spacing: 0.06em;
}

.subsquad-session-panel__title {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 1.05rem;
  font-weight: 700;
  color: #1a2332;
  margin: 0.1rem 0 0;
}

.subsquad-session-panel__dot {
  width: 0.7em;
  height: 0.7em;
  border-radius: 50%;
  flex: 0 0 auto;
}

#subsquad-session-text {
  resize: vertical;
}

.subsquad-session-panel__footer {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.9rem;
  padding-top: 0.75rem;
  border-top: 1px solid #dde3e8;
  position: sticky;
  bottom: 0;
  background: #fff;
}

.subsquad-session-panel__footer button {
  flex: 1 1 0;
}

/* ======================================================================
   SUB-SQUAD ROSTER SHEET (mockup-matching round, item 6)
   ======================================================================
   #subsquad-roster-container — "who's in this sub-squad today", opened
   by tapping a filled/inherited sub-squad bar on phone (board.html's
   openSubSquadRosterSheet(), a plain fetch — NOT the generic Session
   Panel Controller, see the comment above the container in board.html).
   Phone-only feature (board.html never opens it off-phone), so unlike
   the Allocate/sub-squad-session panels above there's no separate
   desktop side-panel treatment — it's always a bottom sheet.
   ====================================================================== */

#subsquad-roster-container {
  display: flex;
  flex-direction: column;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1220;
  max-height: 70vh;
  background: #fff;
  border-radius: 14px 14px 0 0;
  box-shadow: 0 -8px 24px rgba(26, 35, 50, 0.25);
  overflow-y: auto;
  /* Item 10 (motion polish): slide-up + fade on open, matching
   * #planner-sheet's treatment. board.html waits a frame after
   * removing [hidden] before adding .is-open (see openSubSquadRosterSheet),
   * so the browser paints this closed state at least once first —
   * without that, display:none -> .is-open in the same frame would skip
   * the transition entirely. Close is a plain (non-animated) disappear:
   * this sheet's content is re-fetched/replaced on every open, so an
   * exit transition would need to delay clearing its innerHTML — not
   * worth the added JS complexity for a sheet users dismiss and rarely
   * look back at. */
  transform: translateY(100%);
  opacity: 0;
  pointer-events: none;
  transition: transform 260ms cubic-bezier(0.22, 0.9, 0.3, 1), opacity 200ms ease-out;
}

#subsquad-roster-container.is-open {
  transform: translateY(0);
  opacity: 1;
  pointer-events: auto;
}

#subsquad-roster-container[hidden] {
  display: none;
}

.subsquad-roster-sheet__inner {
  display: flex;
  flex-direction: column;
  padding: 0 1.1rem max(1rem, env(safe-area-inset-bottom));
}

.subsquad-roster-sheet__handle {
  flex: 0 0 auto;
  width: 36px;
  height: 4px;
  margin: 0.6rem auto 0.3rem;
  border-radius: 999px;
  background: #d8dee5;
}

.subsquad-roster-sheet__header {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 0.5rem;
  padding-bottom: 0.6rem;
  margin-bottom: 0.6rem;
  border-bottom: 1px solid #dde3e8;
}

.subsquad-roster-sheet__date {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  color: #5a6f88;
  text-transform: uppercase;
}

.subsquad-roster-sheet__title {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-top: 0.1rem;
  font-size: 1rem;
  font-weight: 700;
  color: #1a2332;
}

.subsquad-roster-sheet__dot {
  flex: 0 0 auto;
  width: 0.65rem;
  height: 0.65rem;
  border-radius: 50%;
  background: #457b9d;
}

/* Brief 32 item 3: the sheet's own draft status, surfaced in the heading
 * rather than buried in the session row — matches the "Draft" wording
 * used on the board's own cells/bars. */
.subsquad-roster-sheet__draft-heading {
  margin-top: 0.15rem;
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: #8a6d00;
}

.subsquad-roster-sheet__close {
  flex: 0 0 auto;
  width: 2rem;
  height: 2rem;
  line-height: 1;
  font-size: 1.3rem;
  border: none;
  background: none;
  color: #5a6f88;
  border-radius: 50%;
  margin-top: -0.2rem;
}

.subsquad-roster-sheet__close:active {
  background: #e3edf9;
}

.subsquad-roster-sheet__session-row {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.6rem;
  margin-bottom: 0.8rem;
  border-radius: 6px;
  background: #f5f8fb;
}

.subsquad-roster-sheet__session-text {
  flex: 1 1 auto;
  min-width: 0;
  font-size: 0.85rem;
  color: #1a2332;
  overflow-wrap: break-word;
}

.subsquad-roster-sheet__inherited-tag {
  opacity: 0.75;
}

.subsquad-roster-sheet__no-session {
  color: #9aa5b0;
  font-style: italic;
}

.subsquad-roster-sheet__edit-btn {
  flex: 0 0 auto;
}

.subsquad-roster-sheet__grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.4rem 0.6rem;
  margin-bottom: 0.9rem;
}

.subsquad-roster-sheet__athlete {
  font-size: 0.85rem;
  color: #1a2332;
  padding: 0.3rem 0.5rem;
  border-radius: 4px;
  background: #f0f2f4;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.subsquad-roster-sheet__empty {
  grid-column: 1 / -1;
  margin: 0;
}

.subsquad-roster-sheet__move-btn {
  width: 100%;
}

/* ======================================================================
   RECENT SESSIONS — compact evidence list in CCS view
   ======================================================================
   Sits below the volume chart in the Current Coaching Situation view.
   Shows the 5 most recent activities as lightweight, scannable rows.
   Clicking a row opens the session panel inline (no page navigation).
   ====================================================================== */

.recent-sessions {
  margin-top: 1.5rem;
  padding-top: 1rem;
  border-top: 1px solid #f0f2f4;
}

.recent-sessions__label {
  font-size: 0.65rem;
  font-weight: 700;
  color: #9aa5b0;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 0.5rem;
}

.recent-session {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  padding: 0.35rem 0.4rem;
  border-radius: 4px;
  text-decoration: none;
  color: inherit;
  transition: background 0.08s;
  cursor: pointer;
}

.recent-session:hover {
  background: #f5f7f9;
  text-decoration: none;
  color: inherit;
}

.recent-session__date {
  font-size: 0.72rem;
  color: #9aa5b0;
  font-weight: 600;
  white-space: nowrap;
  min-width: 3.5rem;
}

.recent-session__title {
  font-size: 0.83rem;
  color: #1a2332;
  font-weight: 500;
  flex: 1;
  min-width: 0;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.recent-session__metrics {
  font-size: 0.78rem;
  color: #6c757d;
  white-space: nowrap;
  flex-shrink: 0;
}

/* ======================================================================
   COACH BOARD
   ======================================================================
   The planner-centred squad view. Renders the onboarded Numbers planner
   as a scrollable grid. The date column and group session column are
   sticky; all athlete columns scroll horizontally.

   Design principles from document 12 (Planner Onboarding):
   • Immediately familiar — mirrors the planner's visual rhythm
   • Preserve colour language — race_key red, race orange, road grey, grass green
   • Calm — no heavy borders; week separators via subtle row gaps
   • Dense but readable — small type, compact rows
   ====================================================================== */

/* ── Page header ─────────────────────────────────────────────────── */
.board-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 0.3rem 0.75rem;
  flex-shrink: 0;
}

.board-header__titles {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.board-header__title {
  font-size: 0.9rem;
  font-weight: 600;
  color: #1a2332;
  margin: 0;
}

/* Live month/year label (month-header-and-dividers round) — replaces
   the old centred .planner-watermark for this page. .board-header sits
   above #board-wrap as a non-scrolling sibling (see .workspace-planner/
   .board-main — only #board-wrap itself scrolls), so this is already
   visually "sticky" without needing position: sticky. Same teal accent
   and text-only update behaviour as the athlete Training Plan's
   .tpv2-header__month. */
.board-header__month {
  font-size: 14px;
  font-weight: 600;
  color: #0f766e;
  min-height: 1em;
}

.board-header__controls {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* Item 3 (phone header declutter): the "⋯" menu only exists for phone —
   desktop keeps the separate Library/Share… buttons it's always had.
   Base (non-phone) rule hides the whole wrapper; the phone media query
   below reverses this and hides the two separate buttons instead. */
.board-phone-menu {
  display: none;
  position: relative;
}

.board-phone-menu__btn {
  font-size: 1.1rem;
  line-height: 1;
  padding: 0.25rem 0.6rem;
}

.board-phone-menu__list {
  position: absolute;
  top: 100%;
  right: 0;
  z-index: 70;
  margin-top: 0.25rem;
  min-width: 9rem;
  background: #fff;
  border: 1px solid #c7d0d9;
  border-radius: 6px;
  box-shadow: 0 6px 18px rgba(26, 35, 50, 0.18);
  display: flex;
  flex-direction: column;
  padding: 0.25rem;
}

.board-phone-menu__item {
  display: block;
  width: 100%;
  text-align: left;
  border: none;
  background: none;
  padding: 0.45rem 0.6rem;
  border-radius: 4px;
  font-size: 0.9rem;
  color: #1a2332;
}

.board-phone-menu__item:active,
.board-phone-menu__item:hover {
  background: #eef3f9;
}

/* ── Empty state ─────────────────────────────────────────────────── */
.board-empty {
  padding: 2rem;
  text-align: center;
  color: #9aa5b0;
}

.board-empty__message {
  font-size: 1rem;
  margin-bottom: 0.5rem;
}

.board-empty__hint {
  font-size: 0.85rem;
}

/* ── Scroll wrapper ──────────────────────────────────────────────── */
.board-wrap {
  overflow-x: auto;
  overflow-y: auto;
  border: 1px solid #e3e7ea;
  border-radius: 6px 6px 0 0;
  position: relative;   /* so row.offsetTop is meaningful for the sticky
                            month/year strip's scroll tracking, below */
}

/* ── Sticky month/year strip (Round N) ───────────────────────────────
 * A persistent, non-scrolling column sitting immediately to the left of
 * a scrollable planner grid (the coach board's #board-wrap, or the
 * athlete Training Plan's #tp-grid-days) — a sibling of the scroll
 * container, not inside it, so it never interacts with .tp-grid__days's
 * fade-gradient pseudo-elements. Same "persistent column" idea as the
 * board's .board-col--sticky/--sticky2 date/group columns, applied to a
 * new axis (vertical scroll position, JS-driven) rather than horizontal
 * position: sticky. Because this element itself never scrolls, whatever
 * text it renders just stays visually in place while its sibling
 * scrolls underneath — no CSS position: sticky needed on the strip.
 */
.planner-scroll-row {
  display: flex;
  align-items: stretch;
  gap: 0.35rem;
  position: relative;   /* containing block for .planner-watermark, below */
}

.planner-scroll-row > .board-wrap,
.planner-scroll-row > .tp-grid__days {
  flex: 1 1 auto;
  min-width: 0;
}

/* ── Month/year watermark (Round AF) ─────────────────────────────────
 * Replaces the sticky strip above with a large, low-opacity month/year
 * label absolutely centred over the scroll container's VISIBLE
 * viewport. A sibling of the scroll container (#board-wrap /
 * #tp-grid-days) within the shared, non-scrolling .planner-scroll-row,
 * so it stays visually fixed on screen while content scrolls
 * underneath — the same positioning trick the old strip relied on
 * (sibling, not descendant, of the scroll container), just centred
 * instead of pinned to one side. mix-blend-mode + low opacity keep it
 * legible-but-faint over every cell background (tinted columns, race/
 * session pills) without per-surface colour tuning. z-index is higher
 * than anything inside either scroll container (sticky columns/headers
 * top out at 4; .tp-grid__days's fade-gradient pseudo-elements sit at
 * 2) so it always renders on top regardless of DOM order. pointer-
 * events: none so it never intercepts clicks/drags/taps on the grid
 * underneath. JS (dashboard.html) sets its text and toggles --fading on
 * month change; the CSS rules below only define appearance and the
 * crossfade. Month-header-and-dividers round: the Coach Workspace board
 * (board.html) no longer uses this element — it shows the same live
 * month/year label in its own sticky header instead (#board-header-
 * month) — but dashboard.html (the old, unflagged v1 Training Plan
 * page) still does, so this class/rule stays in place for that page. */
.planner-watermark {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  /* 72px (the mockup's value) fits Coach Workspace's ~58%-width planner
     column comfortably, but would overflow/clip a narrow phone screen
     for the athlete Training Plan — clamp() scales it down there
     without needing a separate breakpoint or per-page override. */
  font-size: clamp(40px, 10vw, 72px);
  font-weight: 800;
  letter-spacing: 0.02em;
  color: #1a2332;
  opacity: 0.06;
  mix-blend-mode: multiply;
  pointer-events: none;
  user-select: none;
  white-space: nowrap;
  z-index: 5;
  transition: opacity 0.12s ease;
}

.planner-watermark--fading {
  opacity: 0;
}

.month-year-strip {
  flex: 0 0 30px;
  width: 30px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 1px solid #b9cfec;
  border-radius: 6px;
  background: #dbe7f7;
  overflow: hidden;
}

.month-year-strip__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  transition: opacity 0.1s ease, transform 0.1s ease;
  opacity: 1;
  transform: translateY(0);
}

.month-year-strip--fading .month-year-strip__inner {
  opacity: 0;
  transform: translateY(-4px);
}

.month-year-strip__month,
.month-year-strip__year {
  display: flex;
  flex-direction: column;
  align-items: center;
  line-height: 1.05;
  font-weight: 700;
  color: #6c757d;
  letter-spacing: 0.02em;
}

.month-year-strip__month { font-size: 0.68rem; }
.month-year-strip__year  { font-size: 0.82rem; color: #2a4a75; }

/* ── Table + zoom ────────────────────────────────────────────────── */
/*
 * --planner-zoom drives font-size and row density.  The JS zoom slider
 * writes this property to :root.
 *
 * Density targets (900 px viewport):
 *   zoom 0.6 → ~6 weeks   (overview)
 *   zoom 1.0 → ~4 weeks   (comfortable planning — matches Numbers planner)
 *   zoom 1.4 → ~2 weeks   (detailed coaching mode, text wraps)
 *
 * Row height at zoom 1.0 ≈ 25 px:
 *   date cell content (2 em-based spans)   ~21 px
 *   top + bottom padding (0.1 rem × 2)     ~3.2 px
 *   border                                   1 px
 *
 * Session text wraps within the column's fixed width at every zoom
 * level, with row height allowed to grow to fit — but capped at ~3
 * lines (.board-cell__text below), beyond which it clips rather than
 * growing indefinitely. The full text is always available via the
 * hover tooltip and the click-to-open inline editor, regardless of cap.
 */
:root { --planner-zoom: 1; }

.board-table {
  border-collapse: collapse;
  font-size: calc(0.78rem * var(--planner-zoom));
  table-layout: fixed;   /* real fixed column widths (below) — a long
                            single-line cell can no longer widen the whole
                            table, which was the actual cause of horizontal
                            scroll. No `width` set here: with table-layout:
                            fixed, the table's own width then becomes the
                            sum of the specified column widths, rather than
                            stretching them proportionally to fill 100% —
                            which would have thrown off the sticky date/
                            group column offsets (left: 0 / left: var(--ecp-col-date)). */
}

/* Semantic zoom levels — applied to .board-wrap via JS data-zoom attribute */
/*
 * tiny (< 0.40): text hidden — ultra-compact colour overview. At every
 * other zoom level text wraps identically (see .board-cell__text below —
 * there is no longer a separate one-line-truncated vs. always-wraps tier).
 */
[data-zoom="tiny"]  .board-cell__text { display: none; }

/* ── Sticky column helpers ───────────────────────────────────────── */
/*
 * Two sticky columns: date (left: 0) and group session (left: 72px).
 * Both need a solid background so scrolled content doesn't show through.
 * z-index: 2 keeps them above the scrolling body cells.
 */
.board-col--sticky {
  position: sticky;
  left: 0;
  z-index: 2;
  background: #E3EDF9;
}

.board-col--sticky2 {
  position: sticky;
  left: var(--ecp-col-date);     /* width of the date column */
  z-index: 2;
  background: #EAF1FB;
}

/* ── Column widths ───────────────────────────────────────────────── */
.board-col--date {
  width: var(--ecp-col-date);
  min-width: var(--ecp-col-date);
  max-width: var(--ecp-col-date);
}

/* Group shares a fixed width with the "+ Add athlete" pinned column's
   siblings above; the two fixed plan-slot columns (A.2) get their own
   190px rule below, separate from this. */
.board-col--group {
  width: 260px;
  min-width: 260px;
  max-width: 260px;
}

/* A.2: two ALWAYS-PRESENT fixed 190px plan-column slots. Every athlete
   <th>/<td> defaults to display:none regardless of its native DOM
   position — the pill-strip JS (board.html) physically moves whichever
   athlete cells are currently open to sit directly before one of the
   two slot-anchor cells below, and adds .board-cell--in-slot to make
   that cell visible at this fixed width. Closing an athlete needs no
   "move back" step: display:none doesn't reserve column width or
   position no matter where in the DOM the (still-hidden) cell sits. */
.board-col--athlete {
  display: none;
}

.board-col--athlete.board-cell--in-slot {
  display: table-cell;
  width: 190px;
  min-width: 190px;
  max-width: 190px;
  border-left: 2px solid #dde3e8;
  background: #EFF5FB;
}

/* A.2 follow-up — eviction animation. Applied only to the slot-1
   "survivor" cells during a 3rd-athlete-clicked eviction (see
   animateSurvivorSlide() in the "ATHLETE PILL STRIP + FIXED PLAN SLOTS"
   script). The slide itself (transform: translateX(...) -> translateX(0))
   is driven by an inline transition set from JS, since the FLIP delta is
   computed per-cell at runtime — this class only supplies the lift-shadow
   keyframe (peaks mid-slide, gone by either end) and a stacking context
   so the shadow isn't clipped by neighbouring table cells. */
.board-cell--evict-slide {
  position: relative;
  z-index: 2;
  animation: ecp-evict-lift 560ms ease;
}

@keyframes ecp-evict-lift {
  0% { box-shadow: 0 0 0 rgba(26, 35, 50, 0); }
  50% { box-shadow: 0 14px 30px rgba(26, 35, 50, 0.22); }
  100% { box-shadow: 0 0 0 rgba(26, 35, 50, 0); }
}

/* Belt-and-braces alongside board.html's JS-level prefersReducedMotion
   check (which skips adding this class / the inline slide+fade
   transitions entirely) — if this class ever ends up applied anyway,
   never animate it. */
@media (prefers-reduced-motion: reduce) {
  .board-cell--evict-slide {
    animation: none;
  }

  /* Item 10 (motion polish): everything else this round animates with
   * transform (slide/scale) drops the transform component entirely here
   * and falls back to a quick opacity-only fade — motion (position/size
   * change) is what reduced-motion users want removed; a short fade is
   * still an acceptable, non-motion way to mark the state change. */
  #planner-sheet,
  #subsquad-roster-container {
    transform: none !important;
    transition-duration: 150ms !important;
  }

  .board-today-expand-btn__chevron {
    transition: opacity 150ms ease-out !important;
  }
}


/* Pure DOM position markers for the two fixed plan slots above — never
   shown, never given real content; see the "ATHLETE PILL STRIP + FIXED
   PLAN SLOTS" script block in board.html. */
.board-col--slot-anchor {
  display: none;
}

/* ── Athlete pill strip (A.1) — the sole trigger for opening/closing an
   athlete's plan column and switching Current Focus. ──────────────── */
.board-pill-strip {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  padding: 0.5rem 0.75rem;
  background: #F5F8FB;
  border-bottom: 1px solid #dde3e8;
}

.board-pill-strip__label {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: #6b7785;
  margin-right: 0.25rem;
}

.board-pill {
  border: 1px solid #c7d0d9;
  background: #fff;
  border-radius: 999px;
  padding: 0.22rem 0.75rem;
  font-size: 0.78rem;
  font-weight: 600;
  color: #33404d;
  cursor: pointer;
  line-height: 1.3;
}

.board-pill:hover {
  border-color: #9aa8b6;
}

.board-pill--open {
  background: #2f6fed;
  border-color: #2f6fed;
  color: #fff;
}

/* Item 4 — a squad athlete with no board column yet. Dashed border +
   muted "+" prefix reads as "not yet added", distinct from an ordinary
   (solid-border) pill for an athlete who already has one. Replaces the
   old separate header-row "+ Add athlete" button/column entirely. */
.board-pill--unlinked {
  border-style: dashed;
  border-color: #9aa8b6;
  background: #fbfcfd;
  color: #5a6f88;
}

.board-pill--unlinked:hover {
  border-color: #2f6fed;
  color: #2f6fed;
}

/* ── Header row ──────────────────────────────────────────────────── */
.board-thead th {
  padding: 0.22rem 0.55rem;
  font-size: 0.68rem;
  font-weight: 700;
  color: #9aa5b0;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  border-bottom: 2px solid #e3e7ea;
  white-space: nowrap;
  background: #dbe7f7;
  position: sticky;
  top: 0;
  z-index: 3;           /* above sticky body columns */
}

/* Header cells that are ALSO sticky columns need z-index: 4 */
.board-thead .board-col--sticky  { z-index: 4; }
.board-thead .board-col--sticky2 { z-index: 4; }

/* ── Body rows ───────────────────────────────────────────────────── */
/* Gridlines outline every cell on all four sides (top/bottom via
   border-bottom on each row plus the next row's top edge, right via
   border-right here, left via the table's/column's own left border) —
   fixed 1px stroke at every zoom level; only spacing scales with
   --planner-zoom, not stroke width (Round N). */
.board-row td {
  padding: calc(0.1rem * var(--planner-zoom)) 0.5rem;
  border-bottom: 1px solid #e5e8eb;
  border-right: 1px solid #e5e8eb;
  vertical-align: top;
  line-height: 1.4;
}

.board-row:hover td {
  filter: brightness(0.96);  /* subtle hover response; preserves exact planner colours */
}

/* Today row: left accent + subtle highlight */
.board-row--today td {
  background-color: #f0f6ff;
}

.board-row--today .board-col--date {
  border-left: 3px solid #457b9d;
}

/* Today lift (coach-board-parity round) — same subtle "lift" as the
   athlete Training Plan's .tpv2-day--today (box-shadow + translateY),
   layered on top of the existing highlight/border above, not replacing
   it. Applies in every layout — the phone-only box-shadow override
   (main.css phone media query) adds its own shadow instead of this one
   since the two visual languages (table row vs. floated card) differ,
   but both are a "lift", consistent with the brief. */
.board-row--today {
  box-shadow: var(--lift-shadow);
  /* translateZ(0) preserved alongside the lift's translateY — .board-row
     (above) already promotes every row to its own compositing layer to
     avoid a sub-pixel seam between sticky/non-sticky cells; a second
     `transform` rule here would otherwise silently replace it rather
     than add to it. */
  transform: translateY(var(--lift-y)) translateZ(0);
}

/* Future rows: no longer muted (David, 15 Oct 2026) — upcoming sessions
   show at full colour. The class stays on the row for any future use. */

/* Week separator: extra vertical spacing, no border */
.board-row--week-sep td {
  padding: 0;
  height: 6px;
  border: none;
  background: transparent !important;
}

/* Month divider (coach-board-parity round) — mirrors the athlete
   Training Plan's .tpv2-month-divider: a plain, non-interactive label
   row, never a .board-row (so it never picks up that class's hover/
   highlight/card styling), shown in every layout — desktop table,
   phone portrait card-stack and phone landscape alike (deliberately
   NOT hidden in the phone media query, unlike .board-row--week-sep). */
.board-month-divider td {
  padding: 0.5rem 0.6rem;
  height: 40px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #64748b;
  border: none;
  background: transparent !important;
}

/* ── Date cell ───────────────────────────────────────────────────── */
/*
 * Two-line layout — scales with --planner-zoom because em is relative
 * to the board-table font-size (which is calc(0.78rem * zoom)).
 *
 * Line 1 (.board-date__dm):  "Fri 3"  — day-of-week (.board-date__dow,
 *                            hidden at data-zoom="large") + day number
 * Line 2 (.board-date__mon): "Jul"    — month abbreviation
 *
 * Combined height at zoom 1.0 ≈ 21 px (vs. ≈ 38 px for the old 3-span
 * layout), which is the primary contributor to achieving 4-week density.
 */
.board-date-cell {
  white-space: nowrap;
  color: #6c757d;
  user-select: none;
}

/* Day-of-week + day number on one line — dominant visual element */
.board-date__dm {
  display: block;
  font-size: 0.82em;          /* em: scales with zoom */
  font-weight: 700;
  color: #1a2332;
  line-height: 1.1;
  letter-spacing: 0.01em;
}

/* Month below — smaller, muted */
.board-date__mon {
  display: block;
  font-size: 0.72em;          /* em: scales with zoom */
  color: #5a6f88;
  line-height: 1.05;
}

/* Weekday (.board-date__dow, inside .board-date__dm): it used to be
   hidden at the densest zoom presets to fit a 52px column; the column is
   now 88px (--ecp-col-date), so "Mon 24" always fits and always shows. */

/* Today: date text in the accent colour */
.board-row--today .board-date__dm  { color: #457b9d; }
.board-row--today .board-date__mon { color: #7ab8d4; }

/* "Today" badge (mockup-matching round, item 1/2) — small-caps accent
 * label under the date, distinct from the already-accented date text
 * itself so today's row is identifiable even once the coach has
 * scrolled it away from the near-top position it used to pin to. */
.board-date__today-badge {
  display: block;
  margin-top: 0.1rem;
  font-size: 0.56em;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: #457b9d;
}

/* Empty-day "+" prompt (item 3) — the ONLY thing a genuinely empty day
 * shows in the Group column; no chips, no Allocate button, no
 * unallocated text until the coach actually taps it. */
.board-day-add-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.4rem;
  height: 1.4rem;
  margin-top: 0.15rem;
  border: 1px dashed #b0bac3;
  border-radius: 999px;
  background: none;
  color: #6c757d;
  font-size: 0.8rem;
  line-height: 1;
}

.board-day-add-btn:hover,
.board-day-add-btn:active {
  border-color: #2f6fed;
  color: #2f6fed;
}

/* Brief 32 item 4(b): date-line "n drafts · Share all" one-tap share. */
.board-day-share-all-btn {
  display: block;
  margin-top: 0.15rem;
  padding: 0;
  border: none;
  background: none;
  font-size: 0.68rem;
  font-weight: 600;
  color: #b4690e;
  text-decoration: underline;
  text-underline-offset: 2px;
  white-space: nowrap;
}
/* Desktop and iPad: the date column is a fixed width, so let
   "1 draft · Share all" wrap onto two lines rather than run past it. */
@media (min-width: 521px) and (not ((pointer: coarse) and (max-height: 500px))) {
  .board-day-share-all-btn { white-space: normal; text-align: left; line-height: 1.2; }
}

.board-day-share-all-btn:hover,
.board-day-share-all-btn:active {
  color: #8a4f09;
}

.board-day-share-all-btn:disabled {
  opacity: 0.6;
  text-decoration: none;
}

/* Weekend orientation — Saturday & Sunday date cells carry a soft blue
   background matching the original Numbers planner.  This gives instant
   orientation: week boundaries and race weekends are readable at a glance
   without reading the date.
   The :not(.board-row--today) guard keeps today's highlight primary on
   the rare occasion that today is a Saturday or Sunday.
   Round N: scoped to the date cell only — group/athlete cells on a
   weekend row render exactly like any other day (no row-wide tint). */
.board-row--weekend:not(.board-row--today) .board-col--date.board-col--sticky {
  background: #65BFFF;
}

/* ── Session cells ───────────────────────────────────────────────── */
.board-cell {
  /* no min-height — row height is driven by the date cell */
  position: relative;   /* anchor for .board-cell__unlinked-badge */
}

/* On displays with a fractional device pixel ratio (common with
   scaled-resolution external monitors), a position: sticky cell (its
   own GPU-composited layer) can round its edges to the physical pixel
   grid independently from an ordinary in-flow sibling in the same row
   — producing a sub-pixel seam between the Group and Athlete columns
   that isn't present on integer-ratio displays. Promoting the whole
   row to a single compositing layer keeps sticky and non-sticky cells
   snapping together. */
.board-row {
  transform: translateZ(0);
}

/* Body cells for the two sticky columns (date, group) carry BOTH
   .board-cell and .board-col--sticky/--sticky2. Both selectors have
   equal specificity, so source order alone decided which "position"
   won — .board-cell (below) was overriding position: sticky back to
   relative, which combined with .board-col--sticky2's left: 52px
   shifted the whole cell 52px into the athlete column instead of
   sticking it in place. Re-assert sticky here with higher specificity
   so it always wins regardless of source order. */
.board-cell.board-col--sticky,
.board-cell.board-col--sticky2 {
  position: sticky;
}

.board-cell__text {
  /*
   * Wraps within the column's fixed width at every zoom level, and the
   * row grows to fit — but capped at 3 lines (~3x a single-line row)
   * via line-clamp, beyond which it clips rather than growing further.
   * This applies uniformly, including to the active/selected cell:
   * the hover tooltip and click-to-open inline editor already show the
   * full text regardless, so no separate uncapped state is needed.
   */
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  overflow: hidden;
  color: inherit;
  line-height: 1.45;
  white-space: pre-wrap;
  word-break: break-word;
}

/* Round W — real inner pill, genuine visual parity with the Training
   Plan's .tp-planned pill (same shape language: border-radius, padding,
   same-hue darker border via the border() Jinja global). Previously the
   background/colour were painted directly onto the whole <td>
   (board-cell) with no separate tile element at all; now the <td> stays
   neutral/transparent and this inner span carries the fill, matching
   Training Plan's already-established pattern. Padding/border-radius
   kept small and .board-cell__text nested unchanged inside it, so the
   dense 4-week board layout and existing line-clamp behaviour aren't
   affected. */
.board-cell__pill {
  display: block;
  position: relative;
  border-radius: 4px;
  padding: 0.12rem 0.3rem;
  border: 1.5px solid transparent;
  /* Round Z — neutral fallback so free-text entries whose surface
     keywords don't resolve a colour (display_colour() returns nothing)
     still render as a visible pill rather than bare unstyled text.
     Inline style="background:..." (set whenever a colour DOES resolve)
     always wins over this class rule, so it's a pure fallback with no
     effect on entries that already render correctly. Matches the
     Training Plan's equivalent .tp-planned / .tp-row__summary-text--
     planned base fallback (#f0f2f4), which is why those never showed
     this gap. */
  background: #f0f2f4;
}

/* ── Brief 32 Stage B: sub-squad session indicator ──────────────────
 * Chips under the Group column's main pill, shown only on a day where
 * one or more sub-squads have their own session (distinct from the
 * whole-squad one already in the pill above). Priority 2: tap-to-edit —
 * each chip is a [data-panel-url] trigger opening a small edit panel
 * (templates/coach/_subsquad_session_panel.html), not the board's
 * click/drag per-cell editor, which is built around exactly one entry
 * per <td> and can't represent several stacked entries in one cell. */
.board-subsquad-indicator {
  display: flex;
  flex-wrap: wrap;
  gap: 0.2rem;
  margin-top: 0.2rem;
}
.board-subsquad-chip {
  display: inline-block;
  font: inherit;
  font-size: 0.65rem;
  line-height: 1.2;
  padding: 0.05rem 0.3rem;
  border: none;
  border-radius: 3px;
  background: #d8dde2;
  color: #1a1a1a;
  cursor: pointer;
}
.board-subsquad-chip:hover {
  filter: brightness(0.95);
}
/* Bug 3 (bugfix round, Oct 2026): a chip now renders for every
 * sub-squad every day, not just ones with a session — "filled" (own
 * session today, solid colour as before) or "empty" ("+ <name>", dashed
 * outline, no fill) so the board shows at a glance which sub-squads are
 * simply following the whole-squad plan unchanged, with a tap target to
 * give one its own session. */
.board-subsquad-chip--empty {
  background: transparent;
  border: 1px dashed #b0bac3;
  color: #6c757d;
  font-style: italic;
}
.board-subsquad-chip--empty:hover {
  filter: none;
  border-color: #6c757d;
  color: #1a1a1a;
}
.board-subsquad-unallocated {
  display: inline-block;
  font-size: 0.65rem;
  line-height: 1.2;
  padding: 0.05rem 0.3rem;
  color: #6c757d;
  font-style: italic;
}

/* ── Stage B fix round, item (c): "split days at a glance" ───────────
 * Each sub-squad is now a full-width bar (not a small inline chip) that
 * ALWAYS shows text — its own session when it has one today ("filled"),
 * else the whole-squad session's text with a "· squad session" suffix
 * ("inherited") rather than a content-free "+ add" prompt. The coloured
 * label at the start of the bar identifies which sub-squad it is, so a
 * day with several sub-squads reads as one labelled bar per sub-squad
 * rather than a single shared session block the coach has to mentally
 * split. .board-subsquad-chip--empty (truly nothing to show, no
 * whole-squad session either) keeps its old small dashed-outline look —
 * stretched to full width by the column flex layout below, for the same
 * glance-friendly one-per-line scan. */
.board-subsquad-indicator {
  flex-direction: column;
  flex-wrap: nowrap;
  align-items: stretch;
}
.board-subsquad-bar {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.15rem 0.4rem;
  border: 1px solid var(--subsquad-bar-border, transparent);
  border-radius: 4px;
  font-size: 0.7rem;
  line-height: 1.25;
  text-align: left;
  background: var(--subsquad-bar-bg, #eef1f4);
  color: var(--subsquad-bar-fg, #1a1a1a);
}
.board-subsquad-bar__label {
  flex: 0 0 auto;
  font-weight: 600;
  font-size: 0.6rem;
  padding: 0.05rem 0.3rem;
  border-radius: 3px;
  background: rgba(0, 0, 0, 0.08);
}
.board-subsquad-bar__text {
  flex: 1 1 auto;
  min-width: 0;
  overflow-wrap: break-word;
}
.board-subsquad-bar__suffix {
  flex: 0 0 auto;
  font-style: italic;
  opacity: 0.75;
  font-size: 0.62rem;
}
/* "11 ›" athlete-count badge (mockup-matching round, item 5) — only on
 * the filled/inherited bar (the genuinely-empty "+ name" chip has
 * nothing to count). */
.board-subsquad-bar__count {
  flex: 0 0 auto;
  font-size: 0.65rem;
  font-weight: 600;
  opacity: 0.7;
  white-space: nowrap;
}
.board-subsquad-chip--inherited {
  border-style: dashed;
}

/* Brief 32 item 3: make a TAC 1/TAC 2 (group) bar's own draft status
 * visible on the bar itself, same signal as .board-cell--draft elsewhere
 * — previously s.published was computed but never consumed here, so a
 * brand-new sub-squad session looked identical to an already-shared one
 * until the coach opened its edit panel. Dashed border matches the
 * existing --inherited treatment (both mean "not the athlete's final
 * view yet"); the text badge (not an ::after corner label) keeps the
 * flex-row bar's existing label/text/suffix/count layout intact instead
 * of overlapping it. */
.board-subsquad-bar--draft {
  border-style: dashed;
}
.board-subsquad-bar__draft-badge {
  flex: 0 0 auto;
  font-size: 0.55rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  opacity: 0.65;
  padding: 0.02rem 0.25rem;
  border: 1px solid currentColor;
  border-radius: 3px;
}

/* ── Round AC: multi-column athlete browsing (collapse/expand concept
 * retired — see A.1/A.2. .board-col__toggle, initials, the collapsed
 * width variant, and .board-cell__dot have all been removed; every
 * athlete column is now either fully open at 190px in one of the two
 * fixed plan slots, or fully hidden — see .board-col--athlete and
 * .board-col--athlete.board-cell--in-slot near the top of this file. */

/* ── Colour semantics ────────────────────────────────────────────── */
/*
 * No semantic CSS classes are applied to board cells.
 * Text colour is set via inline style (computed from background brightness
 * in the template).  Colour meaning is preserved as evidence only —
 * interpretation is deferred to the coaching layer.
 */

/* ── Provenance note ─────────────────────────────────────────────── */
.board-provenance {
  margin-top: 0.65rem;
  font-size: 0.65rem;
  color: #b0bac3;
  text-align: right;
}

/* ======================================================================
   COACH WORKSPACE — two-panel shell
   ======================================================================
   The workspace-shell is a flex row: planner on the left, coaching
   context on the right.  On narrow viewports (<900px) it stacks
   vertically so the planner comes first.

   Real fixed-width constants for the row (date/group/slot/add-athlete/
   library/focus columns are all real pixel widths, never a guessed
   percentage) — JS (board.html's "ROW WIDTH MATH" script block) mirrors
   these exact numbers to compute --board-content-w (the planner table's
   true rendered width, which varies with how many plan slots are open)
   and to decide whether Current Focus fits side-by-side. Kept as CSS
   vars so both the stylesheet and the JS that drives it read from the
   same documented source instead of two independently-hand-tuned copies.
   ====================================================================== */

:root {
  /* 88px (was 52px): room for each day's Register button and its
     "1 draft · Share all" line without cutting them off on desktop and
     iPad (quick tweaks, 25 Oct). Phones lay the date out as a header
     line instead, so this width doesn't apply there. board.html's row
     width maths reads this value, so change it only here. */
  --ecp-col-date: 88px;
  --ecp-col-group: 260px;
  --ecp-col-slot: 190px;
  --ecp-board-wrap-border: 2px;      /* #board-wrap's 1px border × 2 sides */
  --ecp-library-w: 440px;
  --ecp-library-gap: 0.75rem;        /* .board-library-panel's margin-left */
  --ecp-focus-w: 225px;
  --ecp-focus-split-w: 460px;        /* item 1 — two ~220px columns + gap */
  --ecp-focus-collapsed-w: 48px;
  /* Set/overridden by JS to date+group+(open slots × slot)+border — see
     recomputeRowLayout() in board.html. Falls back to the zero-slots
     width so nothing renders unstyled before JS first runs. Item 4: no
     longer reserves space for the old pinned "+ Add athlete" header
     column (removed — athletes are added via the Athletes pill row). */
  --board-content-w: calc(var(--ecp-col-date) + var(--ecp-col-group) + var(--ecp-board-wrap-border));
}

.workspace-shell {
  display: flex;
  flex-direction: row;
  gap: 0;
  height: calc(100vh - var(--sticky-nav-height));   /* full viewport below the navbar */
  overflow: hidden;
}

/* Left panel — planner navigation.
 * Was a hard `flex: 0 0 58%; max-width: 58%` — a percentage cap tuned
 * before A.2/A.3/A.4 fixed the plan-slot columns (2×190px), Current
 * Focus (#workspace-context, 225px) and the library panel (440px) all
 * to real pixel widths. With every OTHER piece of the row now fixed,
 * capping this one panel to a fixed 58% of viewport (rather than
 * letting it fill whatever's left) both under-supplies it — squeezing
 * .board-main below the grid's real minimum content width once both
 * plan slots are open, so the library panel's fixed-position left edge
 * ends up sitting hard against a not-fully-visible 2nd slot column —
 * and wastes real space to the right of #workspace-context on any
 * screen wider than the two fixed panels combined (58% + 225px), since
 * nothing was set to grow into it. flex: 1 1 auto lets this panel claim
 * all space #workspace-context's fixed width doesn't need. */
.workspace-planner {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  border-right: 1px solid #e3e7ea;
}

/* The .planner-scroll-row (board-wrap's flex-row wrapper — see the
   month/year watermark section above) must fill remaining planner
   height, and needs an explicit min-height: 0 to actually shrink to that
   space rather than growing to fit its content (~9000px of virtual-scroll
   rows) — a flex column child otherwise refuses to shrink below its
   content's intrinsic size. #board-wrap itself is no longer a direct flex
   child of .workspace-planner (it's nested one level deeper inside
   .planner-scroll-row), so the flex-grow that used to live on #board-wrap
   directly has moved up to its wrapper; #board-wrap still gets its own
   overflow: auto scrollbar from the base .board-wrap rule above, and
   fills the row's now-constrained height via align-items: stretch. */
.workspace-planner .planner-scroll-row {
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

.workspace-planner .board-wrap {
  max-height: none;   /* override the original fixed max-height */
}

/* ── Board body — grid + optional session-library side panel ────────
 * Part 4(b) round 2: the library panel moved from a full-width block
 * stacked ABOVE the grid to a fixed-width sidebar BESIDE it (.board-main
 * + .board-library-panel side by side), so opening it never resizes or
 * displaces the plan as more sessions get saved. .board-body is now the
 * flex-row container that fills the remaining planner height (the role
 * .planner-scroll-row used to have directly); .board-main is a flex
 * column carrying that same "fill remaining height" responsibility down
 * to .planner-scroll-row, one level deeper than before — see the repo's
 * dev-server notes on this exact flexbox height-chain gotcha when
 * inserting a new wrapper div.
 *
 * Row width math (fixes the wide-screen gap + narrow-screen overlap
 * both reported against the old flex-grow/percentage version): .board-
 * main used to be flex: 1 1 auto, so it ALWAYS grew to fill whatever
 * width .board-body had available — on a wide screen that stretched it
 * far past the planner table's real content width, leaving a dead gap
 * between the table and the library panel; on a narrow screen the same
 * flex-grow (plus min-width: 0) let it get squeezed thinner than the
 * table's real minimum, with the library panel's fixed 440px never
 * shrinking to compensate. Neither symptom is a one-width-only bug —
 * both are the same root cause (sizing from leftover flex space instead
 * of the row's actual real content width), so .board-main is now an
 * explicit width: var(--board-content-w) (JS-computed real pixels —
 * date + group + open-slot columns + add-athlete + border; see
 * recomputeRowLayout() in board.html) with flex-grow disabled, so it's
 * always EXACTLY the table's own width, at every screen width — no
 * gap, nothing to squeeze. .board-body switches overflow-x from hidden
 * to auto so that if the true combined content width (table + library)
 * ever exceeds the available row width, the whole row scrolls as one
 * unit instead of clipping or overlapping. */
.board-body {
  display: flex;
  flex-direction: row;
  flex: 1 1 auto;
  min-height: 0;
  overflow-x: auto;
  overflow-y: hidden;
}

.board-main {
  display: flex;
  flex-direction: column;
  flex: 0 0 auto;
  width: var(--board-content-w);
  min-width: var(--board-content-w);
  overflow: hidden;
}

/* Right panel — Current Coaching Situation */
.workspace-context {
  flex: 1 1 auto;
  min-width: 0;
  overflow-y: auto;
  padding: 1.25rem 1.5rem;
  background: #fafbfc;
}

/* A.3: fixed 225px width on the Coach Workspace board specifically —
 * scoped by id (like the collapsed-state rule below) rather than
 * changing the shared .workspace-context class rule above, which
 * _athlete_detail_body.html's unrelated sidebar also relies on. */
#workspace-context {
  flex: 0 0 var(--ecp-focus-w);
  width: var(--ecp-focus-w);
  max-width: var(--ecp-focus-w);
  position: relative; /* anchors .workspace-context__close, item 7 */
}

/* ── Current Focus panel — collapsible rail (Part 4(b) round 2) ─────
 * Scoped to #workspace-context (board.html's id, unique to this page)
 * rather than the bare .workspace-context class — that class is reused
 * by templates/coach/_athlete_detail_body.html for an unrelated sidebar
 * with entirely different inner markup/classes; scoping here avoids any
 * collision with that page.
 *
 * Collapsed by default on a "cold" page load (no athlete explicitly
 * focused — see ccs_focus_explicit in routes/coach.py:board()): a
 * narrow, empty rail with no last-viewed-athlete indicator or other
 * content, matching the invisible-until-asked-for CCS design brief.
 * Expanded by clicking an athlete's column/cell on the grid, or their
 * name on Squad Overview — both handled client-side, see board.html's
 * "CURRENT FOCUS PANEL" script block. */
#workspace-context.workspace-context--collapsed {
  flex: 0 0 48px;
  min-width: 48px;
  max-width: 48px;
  padding: 1.25rem 0;
  overflow: hidden;
  display: flex;
  align-items: flex-start;
  justify-content: center;
}

.workspace-context__rail {
  display: none;
}

/* Item 7 — explicit close button, desktop and phone alike. Hidden
 * whenever the panel is collapsed (nothing to close); absolutely
 * positioned in its top-right corner regardless of layout mode
 * (side-by-side/tab-switched/tabbed-overlay/phone bottom sheet). */
.workspace-context__close {
  display: none;
  position: absolute;
  top: 0.5rem;
  right: 0.6rem;
  z-index: 5;
  border: none;
  background: none;
  font-size: 1.3rem;
  line-height: 1;
  color: #6b7785;
  cursor: pointer;
  padding: 0.15rem 0.4rem;
  border-radius: 4px;
}

.workspace-context__close:hover {
  background: rgba(0, 0, 0, 0.06);
  color: #1a2332;
}

#workspace-context:not(.workspace-context--collapsed) .workspace-context__close {
  display: block;
}

#workspace-context.workspace-context--collapsed .workspace-context__rail {
  display: flex;
  align-items: center;
  justify-content: center;
  writing-mode: vertical-rl;
  text-orientation: mixed;
  transform: rotate(180deg);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #5a6f88;
  height: 100%;
}

#workspace-context.workspace-context--collapsed .workspace-context__body {
  display: none;
}

/* Freed width redistribution: when the Current Focus panel is collapsed
 * the session library side panel benefits too (.workspace-planner
 * itself already grows to fill all space #workspace-context isn't using
 * — see the base .workspace-planner rule above — so there is no
 * separate "collapsed" override needed for it here anymore). */
.workspace-shell--focus-collapsed .workspace-context {
  flex: 0 0 48px;
}

/* ── Current Focus — responsive "tabbed" mode ────────────────────────
 * JS (recomputeRowLayout() in board.html) measures the row's real
 * required width (the planner table's actual column widths + the
 * library panel's real 440px+gap if open, per the row-width-math
 * section above) against the genuinely available width and toggles
 * this class on .workspace-shell — no guessed breakpoint number, just
 * whichever mode the real numbers say fits right now, re-checked on
 * every resize and every slot/library open-close.
 *
 * In this mode #workspace-context never claims permanent row width
 * beyond the collapsed rail's 48px — expanding it (same collapsed/
 * expanded state as side-by-side mode; every existing call site that
 * already toggles workspace-context--collapsed keeps working untouched)
 * renders it as a floating panel over the board instead, so it never
 * has to fight the table + library for space it doesn't have. The rail
 * stays visible even while expanded here (unlike side-by-side mode,
 * where it's simply not needed once open) so it still works as the
 * close/reopen tab. */
.workspace-shell--focus-tabbed #workspace-context {
  flex: 0 0 var(--ecp-focus-collapsed-w);
  width: var(--ecp-focus-collapsed-w);
  max-width: var(--ecp-focus-collapsed-w);
}

.workspace-shell--focus-tabbed #workspace-context:not(.workspace-context--collapsed) {
  position: fixed;
  top: var(--sticky-nav-height);
  right: 0;
  bottom: 0;
  width: min(360px, calc(100vw - var(--ecp-focus-collapsed-w) - 12px));
  max-width: none;
  z-index: 40;
  box-shadow: -8px 0 20px rgba(26, 35, 50, 0.2);
  border-left: 1px solid #dde3e8;
}

.workspace-shell--focus-tabbed #workspace-context .workspace-context__rail {
  display: flex;
  align-items: center;
  justify-content: center;
  writing-mode: vertical-rl;
  text-orientation: mixed;
  transform: rotate(180deg);
  font-size: 0.72rem;
  font-weight: 600;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: #5a6f88;
  height: 100%;
  cursor: pointer;
}

.workspace-shell--focus-tabbed #workspace-context:not(.workspace-context--collapsed) .workspace-context__rail {
  position: absolute;
  left: -1px;
  top: 0;
  transform: translateX(-100%) rotate(180deg);
  width: var(--ecp-focus-collapsed-w);
  height: 100%;
  background: #fafbfc;
  border: 1px solid #dde3e8;
  border-right: none;
}

/* ── Current Focus — side-by-side split mode (item 1) ────────────────
 * JS (recomputeRowLayout() in board.html) only adds this class when
 * BOTH plan slots are occupied AND the row's real required width still
 * leaves room for ~2×220px — same "real numbers, no guessed breakpoint"
 * approach as the tabbed-overlay mode above. #workspace-context widens
 * from its normal single-column 225px to fit two independent copies of
 * _board_focus_panel.html (each a .workspace-context__col, rendered by
 * board.html's fetchSplitColumns()) side by side; the tab row is moot
 * here (both athletes are already visible at once) so it's hidden
 * outright rather than left for JS to remember to suppress. */
.workspace-shell--focus-split #workspace-context:not(.workspace-context--collapsed) {
  flex: 0 0 var(--ecp-focus-split-w);
  width: var(--ecp-focus-split-w);
  max-width: var(--ecp-focus-split-w);
}

.workspace-shell--focus-split .workspace-context__tabs {
  display: none !important;
}

.workspace-shell--focus-split .workspace-context__inner {
  display: flex;
  flex-direction: row;
  gap: 1.25rem;
}

.workspace-context__col {
  flex: 1 1 0;
  min-width: 0;
}

.workspace-context__col:not(:last-child) {
  padding-right: 1.25rem;
  border-right: 1px solid #e3e7ea;
}

/* Stacked layout for small screens — also applies whenever the device is
   touch-first and short (coach-board-parity round: a landscape phone can
   be wider than 900px yet still only ~400-480px tall, previously falling
   through to the desktop side-by-side layout it doesn't have room for —
   reported live as "about 2 rows of plan visible... uses only part of
   the width"). Kept in lockstep with the phone card-layout breakpoint
   below (same combined condition) so the two never disagree. */
@media (max-width: 900px), (pointer: coarse) and (max-height: 500px) {
  .workspace-page {
    height: auto;
    /* Round AO: the desktop 20px flat gutter is slightly tighter than
       Bootstrap's own ~12px .container side padding this page used to
       render inside — pull it back down here so mobile ends up no
       worse off than before this round, on an already-dense grid. */
    padding: 0 10px;
  }
  .workspace-shell {
    flex-direction: column;
    height: auto;
    overflow: visible;
  }
  .workspace-planner {
    flex: none;
    max-width: 100%;
    max-height: 55vh;
    overflow: hidden;
    border-right: none;
    border-bottom: 1px solid #e3e7ea;
  }
  /* The fixed-width side panel doesn't fit a phone screen — stack it
     below the grid instead, same as the old pre-side-panel layout. */
  .board-body {
    flex-direction: column;
    overflow: visible;
  }
  .board-library-panel {
    flex: 0 0 auto;
    width: 100%;
    height: 200px;
    margin: 0.5rem 0 0;
  }
  .workspace-context {
    padding: 1rem;
  }
  /* A.3's fixed 225px is a desktop-only concept — stacked mobile layout
     needs the panel to span full width like everything else here. */
  #workspace-context {
    flex: 0 0 auto;
    width: 100%;
    max-width: 100%;
  }
  /* Collapsed rail becomes a slim horizontal strip (stacked layout),
   * not a vertical one — writing-mode: vertical-rl only makes sense
   * when the panel is narrow in WIDTH, which it no longer is here. */
  #workspace-context.workspace-context--collapsed {
    flex: 0 0 auto;
    min-width: 0;
    max-width: 100%;
    width: 100%;
    height: 32px;
    padding: 0.4rem 0;
  }
  #workspace-context.workspace-context--collapsed .workspace-context__rail {
    writing-mode: horizontal-tb;
    transform: none;
    height: auto;
  }
  .workspace-shell--focus-collapsed .workspace-planner {
    max-height: 55vh;
  }
  .workspace-shell--focus-collapsed .workspace-context {
    flex: 0 0 auto;
  }
}

/* ── Slot selectors ──────────────────────────────────────────────── */
.slot-selectors {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.22rem 0.75rem;
  background: #f8f9fa;
  border-bottom: 1px solid #e3e7ea;
  flex-shrink: 0;
}

.slot-selectors__label {
  font-size: 0.68rem;
  font-weight: 700;
  color: #9aa5b0;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  white-space: nowrap;
}

.slot-select {
  max-width: 140px;
  font-size: 0.78rem;
}

/* ── Inline slot group (in board header) ────────────────────────── */
.board-slot-group {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  flex-shrink: 0;
}

.board-slot-group .slot-select {
  max-width: 130px;
  font-size: 0.75rem;
  padding-top: 0.12rem;
  padding-bottom: 0.12rem;
}

.board-today-btn {
  font-size: 0.72rem;
  padding: 0.15rem 0.55rem;
  white-space: nowrap;
  flex-shrink: 0;
  line-height: 1.4;
}

/* ── Zoom slider control ─────────────────────────────────────────── */
.board-zoom-control {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin-left: 0.75rem;
  flex-shrink: 0;
}

.board-zoom-label {
  font-size: 0.65rem;
  color: #b0bac3;
  min-width: 1.8em;
  text-align: right;
  font-variant-numeric: tabular-nums;
  user-select: none;
}

.board-zoom-slider {
  width: 140px;
  accent-color: #457b9d;
  cursor: pointer;
  vertical-align: middle;
}

/* ── Inline planner editor ────────────────────────────────────────── */
/*
 * Singleton textarea overlay that appears over the clicked planner cell.
 * position: fixed keeps it in view even when the board scrolls.
 * A subtle blue focus ring signals "you are editing the planner."
 * Background/color are overridden by JS to match the cell's rendered colour.
 */
.planner-editor {
  position: fixed;
  z-index: 500;
  box-sizing: border-box;
  padding: calc(0.3rem * var(--planner-zoom)) 0.55rem;
  font-family: inherit;
  font-size: calc(0.78rem * var(--planner-zoom));
  line-height: 1.45;
  background: #fff;
  color: #1a2332;
  border: 2px solid #457b9d;
  border-radius: 2px;
  box-shadow: 0 2px 10px rgba(69, 123, 157, 0.22);
  resize: none;
  outline: none;
  overflow: hidden;
  white-space: pre-wrap;
  word-break: break-word;
  min-height: 28px;
}

/* ── Phone edit sheet (item 4) — wrapper + chrome ─────────────────────
 * #planner-sheet (board.html) wraps .planner-editor and
 * #planner-editor-phone-actions, plus a new header/footer either side
 * of them. display: contents here on desktop (and every width above
 * the phone breakpoint) removes it from the box tree entirely so its
 * children's own existing desktop positioning/styling is completely
 * unaffected — the phone media query below is what turns it into a
 * real flex column sheet and gives the header/footer/toast their
 * actual visible styling. */
#planner-sheet {
  display: contents;
}

#planner-sheet-header,
#planner-sheet-footer,
#planner-sheet-toast {
  display: none;
}

/* ── Predictive-text dropdown (Part 3) ────────────────────────────────
 * Anchored under .planner-editor by JS (left/top/width set inline to
 * match the editor's own fixed-position rect). z-index sits just above
 * the editor (500) so it's never hidden behind it or other board chrome. */
.planner-suggest {
  position: fixed;
  z-index: 550;
  box-sizing: border-box;
  max-height: 12rem;
  overflow-y: auto;
  background: #fff;
  border: 1px solid #457b9d;
  border-top: none;
  border-radius: 0 0 4px 4px;
  box-shadow: 0 4px 10px rgba(69, 123, 157, 0.22);
  font-size: 0.78rem;
}

.planner-suggest__item {
  padding: 0.3rem 0.55rem;
  cursor: pointer;
  color: #1a2332;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.planner-suggest__item--active,
.planner-suggest__item:hover {
  background: #457b9d;
  color: #fff;
}

/* Empty-cell affordance — subtle dashed border on hover shows the cell is
   writable, without cluttering the default planner view. */
td[data-athlete-name]:not([data-entry-id]):hover {
  outline: 1px dashed #b0bac3;
  outline-offset: -1px;
  cursor: cell;
}

/* ── Selectable cells ─────────────────────────────────────────────── */
.board-cell--selectable {
  cursor: cell;
}

/* "This is editable" cue on hover — a tint, not an underline. Underline
   is reserved app-wide as the one consistent "this is a genuine link"
   signal (see .entry-link below); an underline here used to look exactly
   like a broken/fake hyperlink once mobile Safari's tap-triggered
   :hover state got stuck after the tap. box-shadow (not background)
   because many cells already carry an explicit inline background colour
   (colour_hex) that a background-color rule here couldn't override. */
.board-cell--selectable:hover {
  box-shadow: inset 0 0 0 999px rgba(0, 0, 0, 0.055);
}

/* ── Draft entries (round G) ──────────────────────────────────────────
 * Coach-board-only content not yet shared with the athlete(s) it's
 * scoped to. Must be unmistakably different from a published cell at a
 * glance, without a separate legend. Round W: simplified to a plain
 * text corner label only — the old inset box-shadow border competed
 * visually with the new .board-cell__pill's own same-hue border (two
 * different borders on the same cell read as noise, not signal). The
 * "not yet published" concept itself is unchanged, just no longer
 * expressed as a second border.
 *
 * Round Z — anchor moved from the cell (<td>) to the pill
 * (.board-cell__pill) itself. Before Round W the coloured surface WAS
 * the whole cell, so anchoring "Draft" to the cell's own corner was
 * correct; Round W introduced the narrower inner pill without moving
 * this label, leaving it floating in the cell's corner past a short
 * pill's own edge. .board-cell__pill now carries position: relative
 * itself (see above), so the label is scoped to the pill via a
 * descendant selector rather than a template change.
 *
 * A pill that wraps to two lines (long session text) would otherwise
 * have "Draft" floating on top of the first line's wrapped text once
 * scoped to the pill's own (now much tighter) box — checked live via
 * screenshot. Reserved top padding on the pill itself, sized to the
 * label's own line-height, keeps the label in its own strip above the
 * text regardless of how many lines the pill wraps to.
 */
.board-cell--draft .board-cell__pill {
  padding-top: 0.85rem;
}

.board-cell--draft:not(.board-cell--adjusted) .board-cell__pill::after {
  content: 'Not shared';
  position: absolute;
  top: 0.15rem;
  right: 3px;
  font-size: 0.55rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  opacity: 0.6;
  pointer-events: none;
}

/* Stage B fix round, item (a); inherited-session styling fix round,
   item 19: a resolved (inherited sub-squad/squad) session shown in an
   athlete's own column — not that athlete's own PlannerEntry, so not
   directly editable/draggable here (no data-entry-id — see
   _board_rows.html and _build_board_day_rows()'s resolved_fallback
   docs). Full colour (no fading — a faded cell previously read as
   "something's wrong/incomplete here", not "this is simply inherited"),
   dashed border only, distinguishes "this is the squad's plan, shown
   for reference" from a true individual entry. A plain click still
   opens a blank editor to give this athlete their own variation, exactly
   like an empty cell. */
.board-cell--resolved {
  cursor: pointer;
}

.board-cell__pill--resolved {
  border-style: dashed !important;
}

/* Item 19: an athlete's own individual entry that genuinely differs
   from the day's group session (row.has_variation) gets a small
   "Adjusted" label — same corner-label pattern as .board-cell--draft's
   "Draft" above — so a bespoke override is visually distinct from both
   an inherited session and a plain individual entry that merely repeats
   the group plan. Solid border (the cell's own default, untouched by
   this rule) distinguishes it further from the dashed inherited cells.
   A cell can be both draft and adjusted at once (an unpublished
   bespoke override) — combined into one label rather than two
   overlapping ::after pseudo-elements fighting over the same corner. */
.board-cell--adjusted .board-cell__pill {
  padding-top: 0.85rem;
}

.board-cell--own .board-cell__pill {
  padding-top: 0.85rem;
}

.board-cell--adjusted:not(.board-cell--draft) .board-cell__pill::after {
  content: 'Adjusted';
  position: absolute;
  top: 0.15rem;
  right: 3px;
  font-size: 0.55rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  opacity: 0.6;
  pointer-events: none;
}

.board-cell--draft.board-cell--adjusted .board-cell__pill::after {
  content: 'Not shared \00b7 Adjusted';
  position: absolute;
  top: 0.15rem;
  right: 3px;
  font-size: 0.55rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  opacity: 0.6;
  pointer-events: none;
}


/* ── Bulk publish / unpublish panel (round G) ────────────────────────
 * Collapsible row below the board header — a deliberate date-range +
 * column bulk action, never a per-cell button (see board.html).       */
.board-publish-panel {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.75rem 1.25rem;
  padding: 0.5rem 0.75rem;
  margin: 0 0.75rem 0.5rem;
  background: #f5f7f8;
  border: 1px solid #e3e7ea;
  border-radius: 6px;
  font-size: 0.8rem;
}

.board-publish-panel__row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.board-publish-panel label {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  font-weight: 500;
  color: #4a5560;
}

.board-publish-panel__group-label {
  font-weight: 600;
  color: #2f3942;
}

.board-publish-panel__actions {
  gap: 0.5rem;
}

.board-publish-panel__result {
  font-size: 0.75rem;
  color: #6a7580;
}

/* ── Session library side panel (Part 4(b), round 2; A.4 compaction) ─
 * A fixed-width sidebar next to the plan (.board-body's second flex
 * child, sibling of .board-main) — not a block stacked above the grid.
 * Own height (100% of .board-body) and own internal scroll on the list
 * only, so opening it / saving more sessions never resizes or displaces
 * the plan. Toggled via the "Library" header button (that button's own
 * label stays "Library" — space is tight next to Today/4w/Share; this
 * panel gets its own "Session Library" heading once open instead).
 * One flat, shared list — no Mine/Squad split (single-coach
 * architecture, see models/session_library_entry.py).
 *
 * A.4: fixed 440px regardless of Current Focus panel state — the old
 * round-2 680px widen-when-focus-collapsed rule is gone; a single fixed
 * width is simpler and matches the strict 2-column card grid below
 * (.board-library-panel__list), which no longer needs to reflow into a
 * 3rd column. Colours use real ECP page-shell/divider/ink/accent tokens
 * — see ecp-colour-reference.md. */
.board-library-panel {
  display: flex;
  flex-direction: column;
  flex: 0 0 440px;
  width: 440px;
  height: 100%;
  gap: 0.5rem;
  padding: 0.6rem 0.75rem;
  margin: 0 0 0 0.75rem;
  background: #EAF0F8;
  border: 1px solid #e3e7ea;
  border-radius: 6px;
  font-size: 0.8rem;
  overflow: hidden;
}

.board-library-panel__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
}

/* ── A.6: "+ New session" inline compose ─────────────────────────── */
.board-library-panel__new-btn {
  font-size: 0.72rem;
  white-space: nowrap;
}

.board-library-panel__compose {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 0.4rem;
  background: #fff;
  border: 1px solid #dde3e8;
  border-radius: 6px;
}

.board-library-panel__compose-preview {
  padding: 0.2rem 0.4rem;
  border-radius: 4px;
  font-size: 0.72rem;
  line-height: 1.25;
  white-space: pre-wrap;
}

.board-library-panel__compose-actions {
  display: flex;
  gap: 0.4rem;
  justify-content: flex-end;
}

.board-library-panel__title {
  font-size: 0.85rem;
  font-weight: 600;
  color: #1a2332;
}

.board-library-panel__row {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.board-library-panel__filters select {
  max-width: 160px;
}

/* Card grid (A.4: strict 2 columns, was a responsive auto-fill flowing
 * up to 3 as the panel widened — the panel is now a single fixed width
 * so a strict column count is simpler and predictable). flex: 1 1 auto
 * so this is the only part of the panel that scrolls (header/search/
 * filters stay put). */
.board-library-panel__list {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 0.4rem;
  align-content: start;
  flex: 1 1 auto;
  overflow-y: auto;
  padding-top: 0.15rem;
}

.board-library-panel__empty {
  margin: 0.25rem 0;
  color: #5a6f88;
  font-size: 0.78rem;
  grid-column: 1 / -1;
}

/* A.4: more compact cards — tighter padding/gap/font-size than round 2,
 * needed now two cards always sit side by side in a narrower column
 * (grid-template-columns: repeat(2, 1fr) above) rather than one wide
 * card per row. */
.library-card {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  padding: 0.3rem 1.3rem 0.3rem 0.4rem;
  background: #fff;
  border: 1px solid #e3e7ea;
  border-radius: 6px;
  cursor: pointer;
  width: 100%;
}

.library-card:hover {
  border-color: #457b9d;
  box-shadow: 0 2px 6px rgba(69, 123, 157, 0.18);
}

/* Session text wraps onto two lines rather than truncating — was
 * single-line with an ellipsis when the card was a narrow horizontal
 * strip; a full-width card has room for the whole (or near-whole) text. */
.library-card__pill {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
  padding: 0.2rem 0.4rem;
  border-radius: 4px;
  font-size: 0.72rem;
  line-height: 1.25;
  white-space: normal;
}

/* Tags/usage/pin sit in a row below the (now wrapped) session text,
 * rather than squeezed onto the same line as it. */
.library-card__meta {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
}

.library-card__tags {
  display: flex;
  gap: 0.25rem;
  flex-wrap: wrap;
}

.library-card__tag {
  font-size: 0.65rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  color: #5a6f88;
}

.library-card__usage {
  font-size: 0.68rem;
  color: #5a6f88;
  margin-left: auto;
}

.library-card__pin,
.library-card__delete {
  border: none;
  background: none;
  cursor: pointer;
  font-size: 0.8rem;
  line-height: 1;
  padding: 0.1rem 0.2rem;
  color: #5a6f88;
}

.library-card__pin:hover,
.library-card__delete:hover {
  color: #457b9d;
}

.library-card__delete {
  position: absolute;
  top: 2px;
  right: 2px;
  font-size: 0.65rem;
}

/* ── Inline "remove from library?" confirmation (replaces window.confirm)
 * Swaps the card's normal content out for a compact confirm/cancel row
 * anchored at the card's own position — no browser confirm() dialog. */
.library-card--confirm-delete .library-card__pill,
.library-card--confirm-delete .library-card__meta {
  display: none;
}

.library-card__confirm-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.library-card__confirm-msg {
  font-size: 0.78rem;
  color: #1a2332;
}

.library-card__confirm-actions {
  display: flex;
  gap: 0.4rem;
  margin-left: auto;
}

/* Reuses the existing per-cell drag-and-drop drop-target highlight
 * (.board-cell--drop-target) — a library card dragged over a cell gets
 * the identical visual cue as moving/duplicating an existing session. */

/* ── "Save current pill to library" editor button (Part 4(b)) ───────
 * Positioned by JS just above the singleton .planner-editor overlay,
 * same fixed-position-anchored pattern as .planner-suggest. */
.planner-editor-save-lib-btn {
  position: fixed;
  z-index: 560;
  padding: 0.15rem 0.5rem;
  font-size: 0.7rem;
  font-weight: 600;
  background: #457b9d;
  color: #fff;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  white-space: nowrap;
}

.planner-editor-save-lib-btn:hover {
  background: #35617d;
}

/* ── Add-session target picker (brief 32 Stage B, Priority 2) ────────
 * Positioned by JS (--sq-left/--sq-top custom props) just under the
 * .planner-editor overlay, same fixed-position-anchored pattern as
 * .planner-editor-save-lib-btn. Shown only for the Group column of a
 * squad that has sub-squads. Phone override (below, same breakpoint as
 * #planner-sheet) drops the fixed position — it becomes a plain row in
 * the bottom sheet's flow instead. */
#planner-editor-squad-target {
  position: fixed;
  left: var(--sq-left, 0);
  top: var(--sq-top, 0);
  z-index: 560;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  background: #fff;
  border: 1px solid #dde3e8;
  border-radius: 6px;
  padding: 0.3rem 0.5rem;
  box-shadow: 0 2px 8px rgba(26, 35, 50, 0.15);
}

#planner-editor-squad-target[hidden] {
  display: none;
}

.planner-editor-squad-target__label {
  font-size: 0.72rem;
  font-weight: 600;
  color: #5a6f88;
  white-space: nowrap;
  margin: 0;
}

#planner-editor-squad-select {
  font-size: 0.8rem;
  padding: 0.1rem 1.5rem 0.1rem 0.4rem;
  width: auto;
}

/* Venue override (brief 38, Stage 1.2) — same fixed-position-anchored
 * pattern as the squad target picker above, just below it (--venue-top
 * is set higher by JS when the squad picker is also shown, so the two
 * never overlap). Shown for every cell, not just the Group column. */
#planner-editor-venue {
  position: fixed;
  left: var(--venue-left, 0);
  top: var(--venue-top, 0);
  z-index: 560;
  display: flex;
  align-items: center;
  gap: 0.4rem;
  background: #fff;
  border: 1px solid #dde3e8;
  border-radius: 6px;
  padding: 0.3rem 0.5rem;
  box-shadow: 0 2px 8px rgba(26, 35, 50, 0.15);
}

#planner-editor-venue[hidden] {
  display: none;
}

.planner-editor-venue__label {
  font-size: 0.72rem;
  font-weight: 600;
  color: #5a6f88;
  white-space: nowrap;
  margin: 0;
}

#planner-editor-venue-input {
  font-size: 16px;
  padding: 0.1rem 0.4rem;
  width: 9rem;
}

/* Genuine external link a coach has attached to an entry (link_url) —
   underline is the one consistent "this is a real link" signal app-wide.
   color:inherit so it reads correctly on any entry background colour. */
.entry-link {
  text-decoration: underline;
  text-underline-offset: 2px;
  color: inherit;
}

.board-cell--active {
  outline: 2px solid #457b9d;
  outline-offset: -2px;
}

/* Unlinked-column warning badge (no_athlete_link flag from upsert endpoint).
 * A small ⚠ sits at the top-right of the cell after a save on any column
 * that has no confirmed athlete_id in planner_entries.  Persistent (not
 * auto-dismissed) so the coach knows the entry won't reach the athlete's
 * own planner view.  Removed automatically when the column gains a link. */
.board-cell__unlinked-badge {
  position: absolute;
  top: 1px;
  right: 2px;
  font-size: 0.6rem;
  line-height: 1;
  color: #c77b00;
  pointer-events: none;
  cursor: help;
  /* keep above the session text */
  z-index: 1;
}

/* ── Session hover tooltip ───────────────────────────────────────── */
/*
 * Singleton element created by JS; positioned at the cursor.
 * Shows the complete session text for any populated planner cell,
 * consistently, without requiring the coach to discover the feature.
 */
.board-cell-tip {
  position: fixed;
  background: rgba(26, 35, 50, 0.88);
  color: #fff;
  font-size: 0.76rem;
  padding: 5px 9px;
  border-radius: 3px;
  pointer-events: none;
  white-space: pre-wrap;
  max-width: 300px;
  z-index: 600;
  line-height: 1.45;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
}

/* ── Touch devices: no native text selection / iOS callout on pills or
   day headers (coach-board-parity round) ───────────────────────────
   A long-press on a pill or date was selecting its text (the blue
   selection highlight/handles) and/or triggering iOS's native "Copy /
   Look Up" callout menu, both fighting with the long-press context
   menu (.board-context-menu) this app opens for the same gesture. A
   plain tap still opens the session's edit sheet — unaffected by
   user-select, which only governs text selection, not click/tap
   events. Scoped to touch (pointer: coarse) devices only — desktop
   keeps its normal text-selection affordance. */
@media (pointer: coarse) {
  .board-cell__pill,
  .board-date-cell {
    user-select: none;
    -webkit-user-select: none;
    -webkit-touch-callout: none;
  }
}

/* ── Current Focus panel — structured coaching context ───────────────────────
   Current Focus + Key Events + Recent Activity + Volume + Narrative
   ────────────────────────────────────────────────────────────────── */

/* Section container */
.ccs-section {
  margin-bottom: 1.1rem;
}

.ccs-section--focus {
  margin-bottom: 0.75rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid #e9ecef;
}

.ccs-section__label {
  font-size: 0.62rem;
  font-weight: 700;
  color: #9aa5b0;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin: 0 0 0.35rem;
}

/* ── Current Focus tabs (A.3) ────────────────────────────────────────
 * One tab per currently-open athlete (openOrder, at most two) — see
 * #workspace-context-tabs in board.html and its "CURRENT FOCUS PANEL"
 * script block. Hidden (empty) whenever no athlete is open. */
.workspace-context__tabs {
  display: flex;
  gap: 0.35rem;
  margin-bottom: 0.75rem;
}

.workspace-context__tab {
  flex: 1 1 0;
  min-width: 0;
  border: 1px solid #dde3e8;
  background: #fff;
  border-radius: 6px;
  padding: 0.3rem 0.4rem;
  font-size: 0.72rem;
  font-weight: 600;
  color: #5a6f88;
  cursor: pointer;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.workspace-context__tab--active {
  background: #2f6fed;
  border-color: #2f6fed;
  color: #fff;
}

/* Athlete identity header */
.ccs-athlete-hd {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.5rem;
  margin-bottom: 0.85rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid #e9ecef;
}

.ccs-athlete-hd__name {
  font-size: 0.95rem;
  font-weight: 700;
  color: #1a2332;
}

.ccs-athlete-hd__link {
  font-size: 0.75rem;
  color: #457b9d;
  text-decoration: none;
  white-space: nowrap;
}
.ccs-athlete-hd__link:hover { text-decoration: underline; }

/* Current Focus — ephemeral, updated by JS on cell click */
.ccs-focus-content {
  min-height: 2.5rem;
  font-size: 0.83rem;
  color: #2d3748;
}

.ccs-focus__placeholder {
  font-size: 0.82rem;
  color: #b0bac3;
  margin: 0;
  font-style: italic;
}

.ccs-focus__meta {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin-bottom: 0.3rem;
  flex-wrap: wrap;
}

.ccs-focus__date {
  font-size: 0.82rem;
  font-weight: 700;
  color: #1a2332;
}

.ccs-focus__athlete {
  font-size: 0.75rem;
  color: #6c757d;
}

.ccs-focus__session {
  display: flex;
  align-items: flex-start;
  gap: 0.4rem;
}

.ccs-focus__chip {
  display: inline-block;
  width: 12px;
  height: 12px;
  border-radius: 2px;
  border: 1px solid rgba(0,0,0,0.1);
  flex-shrink: 0;
  margin-top: 0.18em;
}

.ccs-focus__text {
  line-height: 1.5;
  word-break: break-word;
}

.ccs-focus__empty {
  font-size: 0.8rem;
  color: #9aa5b0;
  font-style: italic;
}

/* Key events list */
.ccs-events {
  list-style: none;
  margin: 0;
  padding: 0;
}

.ccs-event {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding: 0.22rem 0;
  font-size: 0.8rem;
  color: #2d3748;
  border-bottom: 1px solid #f4f5f6;
}
.ccs-event:last-child { border-bottom: none; }

.ccs-event__date {
  flex-shrink: 0;
  font-size: 0.72rem;
  color: #9aa5b0;
  min-width: 2.8rem;
  font-variant-numeric: tabular-nums;
}

.ccs-event__chip {
  display: inline-block;
  width: 10px;
  height: 10px;
  border-radius: 2px;
  border: 1px solid rgba(0,0,0,0.08);
  flex-shrink: 0;
}

.ccs-event__text {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Recent activity list */
.ccs-activities {
  list-style: none;
  margin: 0;
  padding: 0;
}

.ccs-activity-row {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.4rem;
  padding: 0.22rem 0;
  font-size: 0.8rem;
  border-bottom: 1px solid #f4f5f6;
}
.ccs-activity-row:last-child { border-bottom: none; }

.ccs-activity-row__date {
  flex-shrink: 0;
  font-size: 0.72rem;
  color: #9aa5b0;
  min-width: 2.8rem;
  font-variant-numeric: tabular-nums;
}

.ccs-activity-row__title {
  flex: 1 1 auto;
  color: #2d3748;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.ccs-activity-row__dist {
  flex-shrink: 0;
  font-size: 0.72rem;
  color: #6c757d;
  font-variant-numeric: tabular-nums;
}

/* Pace / avg HR — own line under date+title+dist (flex-basis: 100%
 * wraps it in the same flex row rather than needing a nested wrapper),
 * indented to roughly align under the title (date's own reserved
 * min-width + the row's gap). */
.ccs-activity-row__meta {
  flex-basis: 100%;
  font-size: 0.7rem;
  color: #9aa5b0;
  padding-left: calc(2.8rem + 0.4rem);
  font-variant-numeric: tabular-nums;
}

/* Item 3 — Key Events "+N more" progressive disclosure. Extra items
 * (beyond the first 3) stay in the DOM but hidden until the toggle
 * button (a sibling, not nested inside the <ul>) adds .is-expanded to
 * the list itself — delegated click handler lives in board.html's
 * "CURRENT FOCUS PANEL" script block. */
.ccs-list-item--extra {
  display: none;
}
.ccs-list--expandable.is-expanded .ccs-list-item--extra {
  display: flex;
}

.ccs-more-toggle {
  display: block;
  margin: 0.3rem 0 0;
  border: none;
  background: none;
  padding: 0;
  font-size: 0.72rem;
  font-weight: 600;
  color: #457b9d;
  cursor: pointer;
}
.ccs-more-toggle:hover {
  text-decoration: underline;
}

/* Recent Activity's "See all" → the athlete's own Activity History view
 * (item 3) — always shown (not a toggle), since only the latest 5 are
 * ever fetched here at all. */
.ccs-see-all {
  display: block;
  margin-top: 0.3rem;
  font-size: 0.72rem;
  font-weight: 600;
  color: #457b9d;
  text-decoration: none;
}
.ccs-see-all:hover {
  text-decoration: underline;
}

/* Volume SVG chart */
.ccs-volume {
  margin-top: 0.25rem;
  line-height: 0;
}
.ccs-volume svg {
  max-width: 100%;
  height: auto;
}

/* Narrative journal entries */
.ccs-narrative-entry {
  margin-bottom: 0.75rem;
  padding-bottom: 0.65rem;
  border-bottom: 1px solid #f4f5f6;
}
.ccs-narrative-entry:last-child { border-bottom: none; }

.ccs-narrative-entry__date {
  display: block;
  font-size: 0.68rem;
  color: #9aa5b0;
  margin-bottom: 0.2rem;
  font-variant-numeric: tabular-nums;
}

.ccs-narrative-entry__text {
  font-size: 0.82rem;
  color: #2d3748;
  margin: 0;
  line-height: 1.55;
  white-space: pre-wrap;
  word-break: break-word;
}

/* ── Virtual-scroll sentinels ────────────────────────────────────── */
.board-sentinel td {
  height: 2px;
  padding: 0;
  border: none;
  background: transparent !important;
}

/* Empty state for any panel section */
.ccs-empty {
  font-size: 0.78rem;
  color: #b0bac3;
  margin: 0.25rem 0;
  font-style: italic;
}

/* ── Legacy entry context panel (entry_context.html) ───────────── */
.ccs-panel {
  font-size: 0.85rem;
  color: #2d3748;
}

.ccs-entry-header {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 0.85rem;
}

.ccs-entry-header__meta {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  flex-wrap: wrap;
}

.ccs-entry-header__date {
  font-size: 0.92rem;
  font-weight: 700;
  color: #1a2332;
}

.ccs-entry-header__sep {
  color: #b0bac3;
}

.ccs-entry-header__athlete {
  font-size: 0.82rem;
  color: #4a5568;
  font-weight: 500;
}

/* Exact-colour chip matching the planner cell */
.ccs-colour-chip {
  display: inline-block;
  width: 14px;
  height: 14px;
  border-radius: 3px;
  border: 1px solid rgba(0,0,0,0.12);
  flex-shrink: 0;
}

/* Session text block */
.ccs-session-text {
  margin-bottom: 0.85rem;
}

.ccs-session-text__body {
  margin: 0.25rem 0 0;
  line-height: 1.6;
  white-space: pre-wrap;
  word-break: break-word;
  font-size: 0.88rem;
  color: #1a2332;
}

/* Athlete workspace link */
.ccs-athlete-link {
  margin-bottom: 0.85rem;
}

.ccs-athlete-link__anchor {
  font-size: 0.8rem;
  color: #457b9d;
  text-decoration: none;
}

.ccs-athlete-link__anchor:hover {
  text-decoration: underline;
}

/* Divider */
.ccs-divider {
  border: none;
  border-top: 1px solid #e9ecef;
  margin: 1rem 0;
}

/* Section labels */
.ccs-section-label {
  font-size: 0.62rem;
  font-weight: 700;
  color: #9aa5b0;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  margin-bottom: 0.3rem;
}

.ccs-section-label--heading {
  font-size: 0.68rem;
  margin-bottom: 0.75rem;
  color: #6c757d;
}

/* CCS stub sections — placeholders for future content */
.ccs-stub {
  padding: 0.65rem 0.75rem;
  background: #f8f9fa;
  border-left: 2px solid #dee2e6;
  border-radius: 0 4px 4px 0;
  margin-bottom: 0.65rem;
}

.ccs-stub__label {
  font-size: 0.62rem;
  font-weight: 700;
  color: #adb5bd;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  margin-bottom: 0.2rem;
}

.ccs-stub__body {
  font-size: 0.78rem;
  color: #adb5bd;
  margin: 0;
  line-height: 1.5;
  font-style: italic;
}



/* ================================================================== */
/* Athlete Planner View (spec 18)                                      */
/*                                                                     */
/* Read-only projection of the planner into the Athlete Workspace.    */
/* Colour system is shared with the Coach Workspace (_macros.html).   */
/* Mobile-first: vertical list, no horizontal scrolling required.     */
/* ================================================================== */

/* Squad Plan toggle (spec 39) */
.squad-plan__toggle {
  display: flex;
  border: 1px solid #e0e0e0;
  border-radius: 6px;
  overflow: hidden;
  width: fit-content;
  margin: 0 auto 1rem;
}

.squad-plan__toggle-btn {
  flex: 1;
  padding: 0.35rem 1.1rem;
  font-size: 0.82rem;
  font-weight: 500;
  border: none;
  background: transparent;
  color: #9aa0a8;
  cursor: pointer;
  transition: background 0.15s, color 0.15s;
  white-space: nowrap;
}

.squad-plan__toggle-btn.is-active {
  background: #1a2332;
  color: #fff;
}

.squad-plan__toggle-btn:not(.is-active):hover {
  background: #f5f5f5;
  color: #1a2332;
}

.squad-plan__return-btn {
  background: none;
  border: none;
  padding: 0;
  color: #9aa0a8;
  font-size: 0.78rem;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* Week navigation (spec 38 Part A) — nav label simplified for spec 41 */
.athlete-plan__nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  padding: 0.25rem 0 0.75rem;
}

.athlete-plan__nav-label {
  font-weight: 500;
  font-size: 0.85rem;
  color: #9aa0a8;
  min-width: 9rem;
  text-align: center;
}

.athlete-plan__nav-arrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  border-radius: 50%;
  border: 1px solid #e0e0e0;
  text-decoration: none;
  color: inherit;
  font-size: 1rem;
  flex-shrink: 0;
  transition: background 0.15s;
}

.athlete-plan__nav-arrow:hover {
  background: #f5f5f5;
}

.athlete-plan__nav-arrow--disabled {
  opacity: 0.25;
  pointer-events: none;
  border-color: transparent;
}

.athlete-plan__nav-return {
  text-align: center;
  margin-bottom: 0.5rem;
  font-size: 0.78rem;
}

.athlete-plan__nav-return a {
  color: #9aa0a8;
}

/* Loading indicator (spec 41) — three pulsing dots, calm by default */
.athlete-plan__loading {
  display: flex;
  justify-content: center;
  gap: 0.3rem;
  padding: 0.5rem 0;
}

.athlete-plan__loading-dot {
  width: 0.35rem;
  height: 0.35rem;
  border-radius: 50%;
  background: #c8cdd5;
  animation: ap-loading-pulse 1.2s ease-in-out infinite;
}

.athlete-plan__loading-dot:nth-child(2) { animation-delay: 0.2s; }
.athlete-plan__loading-dot:nth-child(3) { animation-delay: 0.4s; }

@keyframes ap-loading-pulse {
  0%, 80%, 100% { opacity: 0.3; transform: scale(0.85); }
  40%            { opacity: 1;   transform: scale(1); }
}

/* Per-week header (spec 41) — orients the athlete as new weeks load */
.athlete-plan-week {
  border-top: 1px solid #e8eaed;
}

.athlete-plan-week__header {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  padding: 0.55rem 0.5rem 0.3rem;
}

.athlete-plan-week__label {
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #9aa0a8;
}

.athlete-plan-week__label--current {
  color: #0d6efd;
}

.athlete-plan-week__dates {
  font-size: 0.72rem;
  color: #c0c5cc;
}

/* Sentinel elements — invisible triggers for IntersectionObserver */
.athlete-plan__sentinel {
  height: 1px;
  visibility: hidden;
}

/* Edge fades — visual affordance that more weeks exist above/below.
 * Controlled by JS toggling .athlete-plan--has-past / --has-future.
 * position:relative on the section is set below for the fades to anchor to. */
.athlete-plan {
  position: relative;
}

/* Edge fades anchor to the scrollable .athlete-plan__days container,
 * not the outer section.  The JS scroll handler adds/removes the class
 * on .athlete-plan based on container.scrollTop so the fades accurately
 * reflect what the user can see, not just how many weeks are loaded. */
.athlete-plan--has-past .athlete-plan__days::before,
.athlete-plan--has-future .athlete-plan__days::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  height: 2.5rem;
  pointer-events: none;
  z-index: 1;
}

.athlete-plan--has-past .athlete-plan__days::before {
  top: 0;
  background: linear-gradient(to bottom, #fff 0%, transparent 100%);
}

.athlete-plan--has-future .athlete-plan__days::after {
  bottom: 0;
  background: linear-gradient(to top, #fff 0%, transparent 100%);
}

.athlete-plan__heading {
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #9aa0a8;
  margin-bottom: 0.6rem;
}

.athlete-plan__days {
  border-top: 1px solid #e8eaed;
  /* Fixed scroll frame — planner scrolls internally; page height unaffected.
   * 70 vh shows ~1–1.5 weeks on a typical phone viewport, making the
   * My Training section reachable without scrolling past the entire history. */
  max-height: 70vh;
  overflow-y: auto;
  position: relative;   /* anchor for ::before / ::after edge-fade overlays */
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scrollbar-color: #dde3e8 transparent;
}

.athlete-plan-day {
  display: grid;
  grid-template-columns: 4.8rem 1fr;
  align-items: start;
  gap: 0.6rem;
  padding: 0.55rem 0 0.55rem 0.5rem;
  border-bottom: 1px solid #f2f3f5;
  border-left: 3px solid transparent;
  transition: background 0.1s;
}

.athlete-plan-day--today {
  border-left-color: #0d6efd;
  background: #f5f8ff;
}

.athlete-plan-day__date {
  padding-top: 0.25rem;
}

.athlete-plan-day__label {
  display: block;
  font-size: 0.8rem;
  color: #5f6878;
  white-space: nowrap;
  line-height: 1.3;
}

.athlete-plan-day--today .athlete-plan-day__label {
  font-weight: 700;
  color: #1a2332;
}

.athlete-plan-day__session {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  min-width: 0;   /* prevents grid blowout on long text */
}

.athlete-plan-session {
  font-size: 0.84rem;
  line-height: 1.45;
  padding: 0.3rem 0.55rem;
  border-radius: 4px;
  background: #f0f1f3;
  white-space: pre-wrap;   /* preserve coaching notation line breaks */
  word-break: break-word;
}

/* Group-as-context: shown smaller and slightly receded when an       */
/* individual entry also exists for the same day.                     */
.athlete-plan-session--group {
  font-size: 0.76rem;
  opacity: 0.72;
}

.athlete-plan-session__label {
  font-size: 0.66rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  opacity: 0.7;
  margin-right: 0.4rem;
}

.athlete-plan-rest {
  font-size: 0.8rem;
  color: #bcc2ca;
  font-style: italic;
  padding: 0.25rem 0;
}

/* ================================================================== */
/* Athlete Feedback (spec 19)                                          */
/*                                                                     */
/* "Tell your coach" — three lightweight disclosure forms.            */
/* Principles: interested not demanding, natural coaching language,   */
/* reveal rather than display.                                        */
/* ================================================================== */

/* ── Bar: label + three trigger buttons ─────────────────────────── */

.athlete-feedback {
  border-top: 1px solid #e8eaed;
  padding-top: 0.75rem;
}

.athlete-feedback__bar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.athlete-feedback__label {
  font-size: 0.78rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: #9aa0a8;
  white-space: nowrap;
}

.athlete-feedback__triggers {
  display: flex;
  gap: 0.4rem;
  flex-wrap: wrap;
}

.athlete-feedback__trigger {
  font-size: 0.8rem;
  padding: 0.2rem 0.65rem;
  border-radius: 20px;
  border: 1px solid #d0d4da;
  background: transparent;
  color: #495057;
  cursor: pointer;
  transition: border-color 0.1s, background 0.1s, color 0.1s;
  line-height: 1.5;
}

.athlete-feedback__trigger:hover {
  border-color: #0d6efd;
  color: #0d6efd;
}

.athlete-feedback__trigger.is-active {
  background: #0d6efd;
  border-color: #0d6efd;
  color: #fff;
}

/* ── Disclosure panel ───────────────────────────────────────────── */

.athlete-feedback__form {
  margin-top: 0.65rem;
  padding: 0.75rem 0.85rem;
  border: 1px solid #e0e3e8;
  border-radius: 6px;
  background: #fafbfc;
}

/* ── Form internals ─────────────────────────────────────────────── */

.af-form__textarea {
  width: 100%;
  resize: vertical;
  border: 1px solid #d0d4da;
  border-radius: 4px;
  padding: 0.4rem 0.55rem;
  font-size: 0.875rem;
  line-height: 1.5;
  font-family: inherit;
  background: #fff;
  color: #1a2332;
}

.af-form__textarea:focus {
  outline: none;
  border-color: #6ea8fe;
  box-shadow: 0 0 0 3px rgba(13, 110, 253, 0.12);
}

.af-form__secondary {
  margin-top: 0.55rem;
  display: flex;
  align-items: center;
  gap: 0.55rem;
  flex-wrap: wrap;
}

.af-form__secondary--row {
  gap: 1rem;
}

.af-form__field {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.af-form__field--check {
  justify-content: flex-end;
}

.af-form__field-label {
  font-size: 0.76rem;
  color: #6c757d;
  white-space: nowrap;
}

.af-form__check-label {
  font-size: 0.82rem;
  color: #495057;
  display: flex;
  align-items: center;
  gap: 0.3rem;
  cursor: pointer;
}

.af-form__select,
.af-form__date {
  font-size: 0.82rem;
  padding: 0.2rem 0.4rem;
  border: 1px solid #d0d4da;
  border-radius: 4px;
  background: #fff;
  color: #1a2332;
}

.af-form__actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.6rem;
}

/* ── Recent entries list ─────────────────────────────────────────── */

.athlete-feedback__recent {
  margin-top: 0.75rem;
  border-top: 1px solid #f0f2f4;
  padding-top: 0.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.af-entry {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  font-size: 0.8rem;
  color: #5f6878;
  line-height: 1.4;
}

.af-entry__type {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: #9aa0a8;
  flex-shrink: 0;
}

.af-entry__date {
  color: #adb5bd;
  flex-shrink: 0;
}

.af-entry__note {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  flex: 1 1 0;
  min-width: 0;
}

/* ================================================================== */
/* Athlete entries in the Coach Journal panel (spec 19)                */
/*                                                                     */
/* Athlete-submitted entries sit alongside coach entries in the        */
/* Recent Journal. Visual distinction: lighter background, source      */
/* badge (type label), slightly receded text.                         */
/* ================================================================== */

.journal-entry--athlete {
  background: #f5f7fa;
  border-left: 2px solid #6ea8fe;
  padding: 0.4rem 0.55rem 0.4rem 0.7rem;
  border-radius: 0 4px 4px 0;
}

.journal-entry__meta {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  margin-bottom: 0.2rem;
}

.journal-entry__source-badge {
  font-size: 0.66rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  background: #dce8ff;
  color: #1a56db;
  padding: 0.1rem 0.35rem;
  border-radius: 3px;
}

.journal-entry--athlete .journal-entry__date {
  font-size: 0.76rem;
  color: #9aa0a8;
}

.journal-entry--athlete .journal-entry__content {
  font-size: 0.82rem;
  color: #3d4554;
}

.journal-entry__detail {
  font-size: 0.74rem;
  color: #9aa0a8;
  margin-top: 0.15rem;
}

/* ================================================================== */
/* Consent & registration form (spec 20/21)                            */
/*                                                                     */
/* Design intent: calm and clear, not a legal wall.                    */
/* Spacious layout, readable prose, muted required markers.           */
/* ================================================================== */

.consent-page {
  max-width: 680px;
  margin: 2rem auto 4rem;
  padding: 0 1rem;
}

.consent-header {
  margin-bottom: 2.5rem;
}

.consent-header__title {
  font-size: 1.5rem;
  font-weight: 700;
  margin-bottom: 0.6rem;
}

.consent-header__intro {
  color: #555;
  line-height: 1.6;
  margin-bottom: 0.4rem;
}

.consent-header__athlete {
  font-size: 0.875rem;
  color: #777;
}

/* Section container */
.consent-section {
  margin-bottom: 2.5rem;
  padding-bottom: 2rem;
  border-bottom: 1px solid #e8e8e8;
}

.consent-section:last-of-type {
  border-bottom: none;
}

.consent-section__title {
  font-size: 1.1rem;
  font-weight: 700;
  margin-bottom: 0.5rem;
  color: #1a1a1a;
}

.consent-section__intro {
  font-size: 0.9rem;
  color: #555;
  line-height: 1.6;
  margin-bottom: 1rem;
}

/* Individual form fields */
.consent-field {
  margin-bottom: 1rem;
}

.consent-label {
  display: block;
  font-size: 0.875rem;
  font-weight: 600;
  color: #333;
  margin-bottom: 0.3rem;
}

.consent-input {
  max-width: 100%;
}

.consent-field__help {
  font-size: 0.78rem;
  color: #888;
  margin-top: 0.3rem;
  margin-bottom: 0;
}

/* Required / optional markers */
.consent-required {
  color: #b91c1c;
  font-weight: 700;
  margin-left: 0.1rem;
}

.consent-optional {
  font-size: 0.82rem;
  color: #888;
  font-weight: 400;
}

/* Consent prose block */
.consent-text {
  background: #f9f9f9;
  border: 1px solid #e3e3e3;
  border-radius: 6px;
  padding: 1rem 1.25rem;
  font-size: 0.875rem;
  line-height: 1.7;
  color: #333;
  margin-bottom: 1.25rem;
}

.consent-text ul {
  padding-left: 1.2rem;
  margin-bottom: 0;
}

.consent-text li {
  margin-bottom: 0.5rem;
}

/* Checkbox + full-text label rows */
.consent-check {
  margin-bottom: 1rem;
  padding: 0.85rem 1rem;
  border: 1px solid #ddd;
  border-radius: 6px;
  background: #fff;
}

.consent-check--optional {
  background: #fafaf8;
  border-style: dashed;
}

.consent-check__label {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  font-size: 0.875rem;
  line-height: 1.6;
  color: #222;
  cursor: pointer;
  margin-bottom: 0;
}

.consent-check__input {
  flex-shrink: 0;
  margin-top: 0.3rem;
  width: 1rem;
  height: 1rem;
  cursor: pointer;
}

/* Submit area */
.consent-submit {
  margin-top: 2rem;
  text-align: center;
}

.consent-submit__btn {
  padding-left: 2.5rem;
  padding-right: 2.5rem;
}

.consent-submit__note {
  max-width: 380px;
  margin: 0.5rem auto 0;
}

/* ── Connect Your Data (spec 28) ─────────────────────────────────── */

/* Dashboard persistent prompt */
.connect-data-prompt {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.75rem 1rem;
  margin-bottom: 1.5rem;
  background: #f8f9fa;
  border: 1px solid #dee2e6;
  border-radius: 6px;
}
.connect-data-prompt__text {
  font-size: 0.9rem;
  color: #495057;
}
.connect-data-prompt__text strong {
  display: block;
  color: #212529;
  margin-bottom: 0.1rem;
}

/* Connect Your Data page */
.connect-data-page {
  max-width: 580px;
  margin: 0 auto;
  padding: 2rem 1rem 3rem;
}
.connect-data-page__heading {
  font-size: 1.4rem;
  font-weight: 600;
  margin-bottom: 0.4rem;
}
.connect-data-page__lead {
  color: #6c757d;
  margin-bottom: 2rem;
}
.connect-data-page__skip {
  margin-top: 2.5rem;
  font-size: 0.85rem;
}

/* Data source cards */
.connect-source {
  border: 1px solid #dee2e6;
  border-radius: 8px;
  padding: 1.1rem 1.25rem;
  margin-bottom: 1rem;
  background: #fff;
}
.connect-source__header {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 0.75rem;
}
.connect-source--strava .connect-source__header {
  margin-bottom: 0.9rem;
}
.connect-source__name {
  font-weight: 600;
  font-size: 1rem;
}
.connect-source__action {
  margin-top: 0.5rem;
}
.connect-source__status-text {
  font-size: 0.88rem;
  color: #495057;
  margin-bottom: 0.5rem;
}

/* What to expect guide */
.connect-source__guide {
  background: #f8f9fa;
  border-radius: 6px;
  padding: 0.75rem 1rem;
  margin-bottom: 1rem;
}
.connect-source__guide-title {
  font-size: 0.82rem;
  font-weight: 600;
  color: #495057;
  margin-bottom: 0.4rem;
}
.connect-source__guide-list {
  font-size: 0.82rem;
  color: #6c757d;
  padding-left: 1.25rem;
  margin: 0;
}
.connect-source__guide-list li {
  margin-bottom: 0.35rem;
}

/* Future wearables */
.connect-future {
  margin-top: 2rem;
}
.connect-future__note {
  font-size: 0.82rem;
  color: #9aa5b0;
  margin-bottom: 0.75rem;
}
.connect-future__grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 0.6rem;
}
.connect-source--future {
  opacity: 0.45;
  margin-bottom: 0;
}
.connect-source--future .connect-source__header {
  margin-bottom: 0;
}
.connect-source__soon {
  font-size: 0.75rem;
  color: #9aa5b0;
  font-style: italic;
}

/* ── Image consent flag ──────────────────────────────────────────────────── */
/* Always rendered (deliberate exception to silence-when-good — see the macro
   comment): green = full consent, red = any restriction. The green icon is
   meant to become routine enough that a red one immediately draws the eye. */
.img-consent-flag {
  display: inline-flex;
  align-items: center;
  position: relative;   /* tooltip anchor; also sits above the card's stretched-link overlay */
  z-index: 2;
  font-size: 1.44rem;   /* 2x — needs enough presence to make its absence noticeable */
  cursor: default;
  opacity: 1;
  vertical-align: middle;
  margin-left: 0.25rem;
}
.img-consent-flag .bi {
  line-height: 1;
}
.img-consent-flag--restricted {
  color: #c0392b;
}
.img-consent-flag--full {
  color: #24B857;
}

/* Custom tooltip — replaces the native title attribute, which has an
   unavoidable ~500ms+ browser-controlled hover delay. This one shows
   almost immediately. */
.img-consent-flag::after {
  content: attr(data-tooltip);
  position: absolute;
  bottom: 130%;
  left: 50%;
  transform: translateX(-50%);
  background: rgba(26, 35, 50, 0.92);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 400;
  white-space: nowrap;
  padding: 4px 8px;
  border-radius: 4px;
  box-shadow: 0 2px 6px rgba(0, 0, 0, 0.18);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
  transition: opacity 0.08s ease;
  transition-delay: 0.05s;
  z-index: 20;
}
.img-consent-flag:hover::after,
.img-consent-flag:focus-visible::after {
  opacity: 1;
  visibility: visible;
}

/* ── Spec 25: My Consents page ───────────────────────────────────────────── */
.my-consents-page {
  max-width: 680px;
  margin: 0 auto 2rem;
}
.my-consents-page__heading {
  font-size: 1.4rem;
  font-weight: 600;
  margin-bottom: 0.35rem;
  /* Round F — layer 3, navbar-only offset (no tab bar on this page). Just
     the heading, not the lead paragraph below it — keeps the sticky
     footprint compact rather than pinning two lines of descriptive text. */
  position: sticky;
  top: var(--sticky-nav-height);
  z-index: 1000;
  background: #fff;
}
.my-consents-page__lead {
  color: #667;
  margin-bottom: 1.75rem;
}
.my-consents-section {
  margin-bottom: 2rem;
}
.my-consents-section__heading {
  font-size: 1rem;
  font-weight: 600;
  margin-bottom: 0.35rem;
  color: #2d3748;
}
.my-consents-section__intro,
.my-consents-section__body {
  font-size: 0.875rem;
  color: #667;
  margin-bottom: 0.75rem;
}
.my-consents-section--data {
  padding: 1rem 1.2rem;
  background: #f8f9fb;
  border-radius: 8px;
  border: 1px solid #e4e8ee;
}
.my-consents-minor-note {
  font-size: 0.82rem;
  color: #6b5c2e;
  background: #fdf7e3;
  border: 1px solid #e8d8a0;
  border-radius: 6px;
  padding: 0.5rem 0.75rem;
  margin-bottom: 1rem;
}
.my-consents-list {
  list-style: none;
  padding: 0;
  margin: 0;
}
.my-consents-item {
  display: flex;
  align-items: center;
  gap: 1rem;
  padding: 0.85rem 0;
  border-bottom: 1px solid #edf0f4;
}
.my-consents-item:last-child {
  border-bottom: none;
}
.my-consents-item__info {
  flex: 1;
  min-width: 0;
}
.my-consents-item__label {
  display: block;
  font-weight: 500;
  font-size: 0.9rem;
  color: #1a202c;
  margin-bottom: 0.15rem;
}
.my-consents-item__desc {
  display: block;
  font-size: 0.8rem;
  color: #718096;
}
.my-consents-item__form {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
}
.my-consents-switch {
  margin: 0;
}
.my-consents-switch__input {
  cursor: pointer;
  width: 2.5em;
  height: 1.35em;
}
.my-consents-item__state {
  font-size: 0.78rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  min-width: 2rem;
  text-align: right;
}
.my-consents-item__state--on  { color: #2e7d5e; }
.my-consents-item__state--off { color: #9aa5b0; }
.my-consents-deactivate-link {
  color: #c0392b;
  text-decoration: underline;
  text-underline-offset: 2px;
}
.my-consents-back {
  margin-top: 1.5rem;
}

/* ── Spec 25: Account deactivation confirmation page ──────────────────────── */
.deactivate-page {
  max-width: 560px;
  margin: 2rem auto;
}
.deactivate-page__heading {
  font-size: 1.3rem;
  font-weight: 600;
  margin-bottom: 1rem;
  color: #c0392b;
}
.deactivate-page__warning {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  background: #fff3f3;
  border: 1px solid #f5c6c6;
  border-radius: 7px;
  padding: 0.75rem 1rem;
  margin-bottom: 1.25rem;
  color: #c0392b;
  font-size: 0.92rem;
}
.deactivate-page__body {
  color: #2d3748;
  margin-bottom: 0.75rem;
  font-size: 0.92rem;
}
.deactivate-page__consequences {
  color: #2d3748;
  font-size: 0.9rem;
  margin-bottom: 1.25rem;
  padding-left: 1.35rem;
}
.deactivate-page__consequences li {
  margin-bottom: 0.3rem;
}
.deactivate-page__actions {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-top: 1.75rem;
}
.deactivate-page__confirm-btn {
  /* btn-danger already provides colour; just ensure sizing is clear */
  font-size: 0.9rem;
}

/* ======================================================================
   MY TRAINING CALENDAR GRID (athlete dashboard redesign)

   Mobile-first single-column day list. Today/Tomorrow rows are always
   expanded; every other row is a compact one-line summary that expands
   in place on tap, one row open at a time (no shared panel).

   Deliberately does NOT reuse .athlete-plan* — that block still belongs
   to the separate Squad Plan feature (routes/athlete.py: squad()/
   squad_week()), which must keep working unchanged.
   ====================================================================== */

.tp-grid__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.5rem;
  /* Round F — layer 3 sticky header for Training Plan / Training History,
     which use this component instead of the plain .sticky-page-header
     h1-row pattern. */
  position: sticky;
  top: calc(var(--sticky-nav-height) + var(--sticky-tabs-height));
  z-index: 1000;
  /* Round S — flat athlete tint (this component is athlete-only: Training
     Plan / Training History), reads as part of the tinted page rather
     than a separate coloured bar. Sticky only to occlude content
     scrolling underneath. */
  background: #DCE6F0;
}

.tp-grid__heading {
  display: flex;
  flex-direction: column;
}

.tp-grid__title {
  margin: 0;
}

.tp-grid__squad {
  font-size: 0.78rem;
  font-weight: 500;
  color: #6b7684;
}

/* Brief 32 "Groups" round, item 6: the squad label becomes a link to the
   read-only "Squad plan" view whenever the athlete belongs to a squad —
   same quiet sizing/colour as the plain label above, just underlined to
   signal it's tappable. */
.tp-grid__squad--link {
  text-decoration: underline;
  text-underline-offset: 2px;
}

.tp-grid__squad--link:hover {
  color: #1a2332;
}

/* Brief 32 "Groups" round, item 6 — "Squad plan" page (every group's own
   session, side by side, read-only). Deliberately plain/quiet, same
   general card language as the Training Plan grid, no new colour
   system introduced. */
.squad-plan__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1rem;
}

.squad-plan__title {
  margin: 0;
}

.squad-plan__empty {
  color: #6b7684;
  font-size: 0.9rem;
}

.squad-plan__days {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.squad-plan__day {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  padding: 0.5rem 0.6rem;
  border-radius: 8px;
}

.squad-plan__day--today {
  background: #f0f2f4;
}

.squad-plan__date {
  flex: 0 0 auto;
  width: 3.4rem;
  display: flex;
  flex-direction: column;
  line-height: 1.15;
}

.squad-plan__dow {
  font-size: 0.72rem;
  color: #5a6f88;
}

.squad-plan__dm {
  font-size: 0.85rem;
  font-weight: 600;
  color: #1a2332;
}

.squad-plan__groups {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  min-width: 0;
}

.squad-plan__group-pill {
  border-radius: 6px;
  padding: 0.22rem 0.5rem;
  font-size: 0.85rem;
  font-weight: 600;
  background: #f0f2f4;
  border: 1.5px solid transparent;
}

.squad-plan__group-name {
  font-weight: 700;
  opacity: 0.65;
  text-transform: uppercase;
  font-size: 0.72em;
  letter-spacing: 0.02em;
  margin-right: 0.4em;
}

.tp-grid__v2-link {
  /* Soft-launch nav entry for Training Plan v2 — only rendered at all
     when the training_plan_v2 feature flag is on for this athlete, so no
     CSS-level hiding is needed. Deliberately small/quiet: a discreet
     opt-in link, not a promoted feature. */
  font-size: 0.78rem;
  font-weight: 500;
  color: #475569;
  text-decoration: none;
}

.tp-grid__v2-link:hover {
  text-decoration: underline;
}

.tp-grid__days {
  position: relative;
  border: 1px solid #eef0f2;
  border-radius: 10px;
  overflow-y: auto;
  overscroll-behavior: contain;
  /* Round S — dropped the opaque white fill this box used to have: it was
     swallowing almost the whole athlete-tint body colour behind a solid
     white card on mobile (only Bootstrap's ~12px container gutter showed
     the tint through). Border/radius kept as a subtle outline signalling
     "this scrolls"; day rows now sit directly on the tinted body. */
  /* Static fallback only (~10 rows) — JS measures the actual rendered
     row height on load and overrides this with an exact pixel value so
     the frame reliably shows ~10 rows and never grows/shifts the page
     regardless of screen size or font settings. Deliberately does NOT
     use -webkit-overflow-scrolling: touch — that legacy property has
     been observed to let rubber-band overscroll chain into the page
     even with overscroll-behavior: contain set. */
  max-height: 30rem;
}

/* Round 8: replaces the old "See earlier"/"See more ahead" click-link
   rows, which ate their own dedicated vertical space above/below the
   fixed window on a phone — scroll-triggered loading (JS) already does
   the actual job, this just signals "this box scrolls" at a glance.
   Fixed to the scroll container's own box (not the scrolled content)
   because they're absolutely positioned against .tp-grid__days, which
   is their nearest positioned ancestor. Round S: recoloured to fade to
   the athlete tint instead of white, now that .tp-grid__days no longer
   has its own opaque white fill (see above) — fading to white here would
   paint a visible white smear against the tinted surface underneath. */
.tp-grid__days::before,
.tp-grid__days::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 1.35rem;
  pointer-events: none;
  z-index: 2;
}

.tp-grid__days::before {
  top: 0;
  background: linear-gradient(to bottom, #DCE6F0, rgba(220, 230, 240, 0));
}

.tp-grid__days::after {
  bottom: 0;
  background: linear-gradient(to top, #DCE6F0, rgba(220, 230, 240, 0));
}

.tp-row {
  position: relative;
  /* Round AG: every row shares the same background now — no row-level
     tint for Today/Tomorrow (see .tp-row--locked-open below, replaced
     by a pill-scoped lift instead). Same pale blue already established
     this session on the coach side (Round AD) for consistency. */
  background: #E3EDF9;
  border: 1px solid transparent;
  border-bottom-color: #eef0f2;
  transition: border-color 0.12s, box-shadow 0.12s, transform 0.12s;
}

.tp-row:last-child {
  border-bottom-color: transparent;
}

.tp-row--week-sep {
  border-top: 6px solid #f7f8f9;
}

/* Round N: scoped to the date/day-label element only, matching the
   board's weekend-tint scope — no other cell in a weekend row carries a
   background beyond this. */
.tp-row--weekend .tp-row__date {
  background: #65BFFF;
  border-radius: 4px;
}

/* Round AG: the row-level tint that used to distinguish Today
   (.tp-row--today { background: #f2f7fa; }) is gone — every row now
   shares .tp-row's background. Today's cue is the teal, bold DOW label
   below, plus the pill lift (see .tp-row--today .tp-row__summary-text--planned
   / .tp-planned--individual further down). */

/* Round S — teal-derived accent (from the new #2DD4BF banner hue) instead
   of the old disconnected steel-blue, so "Today" ties visually to the new
   brand colour. Darkened well past the raw banner teal for ~5:1 contrast
   against this row's pale background — a considered starting point, not
   a locked value; confirm visually. Round AG: bolded as an additional cue
   now that the row-level tint is gone. */
.tp-row--today .tp-row__dow {
  color: #0f766e;
  font-weight: 800;
}

/* Round AG: Tomorrow gets the same teal as Today but stays regular
   weight — a lighter echo, not equal prominence. */
.tp-row--tomorrow .tp-row__dow {
  color: #0f766e;
}

/* "Lift off the page" — same visual language as .athlete-card:hover
   (border tint + soft shadow + tiny upward lift). On tap-to-expand for
   any row; on hover for any row regardless of expanded state. Round AG:
   no longer applied to .tp-row--locked-open (Today/Tomorrow) — those
   get a pill-scoped lift instead (see .tp-row__summary-text--planned /
   .tp-planned--individual further down), not a whole-row card. Stacked
   above sibling rows so the shadow isn't clipped by them. */
.tp-row--expanded,
.tp-row:hover {
  position: relative;
  z-index: 1;
  border-color: #9fb3c0;
  box-shadow: 0 3px 10px rgba(0, 0, 0, 0.08);
  transform: translateY(-1px);
}

.tp-row__summary {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  padding: 0.65rem 0.85rem;
  background: none;
  border: none;
  text-align: left;
  cursor: pointer;
}

.tp-row__summary:disabled {
  cursor: default;
}

.tp-row__summary:not(:disabled):hover {
  background: rgba(0, 0, 0, 0.02);
}

.tp-row__date {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  min-width: 3.1rem;
  flex-shrink: 0;
  line-height: 1.15;
}

.tp-row__dow {
  font-size: 0.78rem;
  font-weight: 600;
  color: #1a2332;
}

.tp-row__dm {
  font-size: 0.72rem;
  color: #5a6f88;
}

.tp-row__key-chip {
  width: 9px;
  height: 9px;
  border-radius: 50%;
  flex-shrink: 0;
}

/* tp-row__summary-content: side-by-side container for the plan pill and
   the recorded-activity tag. Two distinct colour systems sit next to each
   other deliberately — planner colour (display_colour) for the plan,
   .wt--modality tint for the activity — so at a glance it's obvious these
   are two different kinds of fact (an instruction vs. a recorded outcome),
   never merged into one "completed" judgement the app can't actually
   verify. */
.tp-row__summary-content {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: 0.35rem;
  overflow: hidden;
}

.tp-row__summary-text {
  flex: 0 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.85rem;
  color: #4a5568;
}

.tp-row__summary-text--planned {
  border-radius: 6px;
  padding: 0.22rem 0.5rem;
  font-weight: 600;
  background: #f0f2f4;
  /* Round W — same-hue darker border via the border() Jinja global
     (services/board_presentation.py:border_colour_for()), set inline
     alongside background/color whenever a real semantic colour is
     resolved. transparent base so an entry with no resolved colour
     (bare grey fallback background above) doesn't grow a stray default
     border colour. */
  border: 1.5px solid transparent;
  flex-shrink: 1;
}

/* Brief 32 "Groups" round, item 6: a quiet sub-squad name tag (e.g.
   "TAC 1") prefixing the headline pill text — only ever present for a
   genuine sub-squad session (see sub_squad_label_for_entry()), never a
   whole-squad one, so it reads as "extra context" rather than noise on
   every single day. Deliberately understated — opacity on the pill's
   own text colour, not a separate background chip — so it doesn't
   compete with the pill's own colour-coded surface meaning. */
.tp-row__group-label {
  font-weight: 700;
  opacity: 0.65;
  margin-right: 0.3em;
  text-transform: uppercase;
  font-size: 0.72em;
  letter-spacing: 0.02em;
}


/* Round AG: Today/Tomorrow's "prominence" moved from a whole-row card
   (removed, see .tp-row--expanded/.tp-row:hover above) to a lift on the
   pill(s) themselves — the exact Bold/Light values Dave picked from the
   mockup's playground, not starting points to re-tune. Targets whichever
   pill(s) are actually present (group headline, individual entry, or
   both) — a blank locked day has no pill, so nothing to lift. */
.tp-row--today .tp-row__summary-text--planned,
.tp-row--today .tp-planned--individual {
  box-shadow: 0 6px 16px rgba(26, 35, 50, 0.30);
  transform: translateY(-2.4px);
}

.tp-row--tomorrow .tp-row__summary-text--planned,
.tp-row--tomorrow .tp-planned--individual {
  box-shadow: 0 2px 7px rgba(26, 35, 50, 0.12);
  transform: translateY(-0.8px);
}

/* Activity tag: same light-tint-chip shape as the plan pill, but coloured
   by activity type (.wt--run/cycling/swim/cross) rather than planner
   colour, so the two never look like the same kind of information. */
.tp-row__summary-text--activity {
  border-radius: 6px;
  padding: 0.22rem 0.5rem;
  font-weight: 500;
  flex-shrink: 2;
}

.tp-row__summary-text--activity.wt--run {
  background: #edf0f3;
  color: #4a5568;
}

.tp-row__summary-text--activity.wt--cycling {
  background: #e8f1f7;
  color: #457b9d;
}

.tp-row__summary-text--activity.wt--swim {
  background: #e3f5f2;
  color: #2a9d8f;
}

.tp-row__summary-text--activity.wt--cross {
  background: #fbf1de;
  color: #c9920a;
}

/* Multi-session days (2+ recorded sessions): the summary is a
   comma-joined list spanning several activity types, so a single
   .wt--* colour tint would misrepresent the other sessions. Neutral
   chip styling instead — same shape as the plan pill, no colour claim. */
.tp-row__summary-text--activity-multi {
  background: #f0f2f4;
  color: #4a5568;
}

/* Sibling of .tp-row__summary (the toggle button), not a descendant —
   a real <a> can't validly nest inside a <button>. Overlaid on the row
   corner rather than underlined like a text link: it's a standalone
   icon, not running text, so underlining it would just look like a
   rendering glitch. */
.tp-row__link-icon {
  position: absolute;
  top: 0.35rem;
  right: 2.1rem; /* shifted left of the always-present .tp-row__add-btn */
  z-index: 2;
  font-size: 0.85rem;
  line-height: 1;
  opacity: 0.7;
}

.tp-row__link-icon:hover {
  opacity: 1;
}

/* Round AB: reveal control for a locked (Today/Tomorrow) row's action
   buttons (race-adopt/key-toggle, edit-your-own) — hidden by default now
   instead of always dumped below the pills. Same sibling-of-.tp-row__summary,
   absolute-overlay pattern as .tp-row__link-icon/.tp-row__add-btn; sits
   to their left so all three can coexist on a busy row without
   overlapping. Only ever rendered when there's something behind it to
   reveal (see _training_day_row.html: has_locked_detail_content). */
.tp-row__reveal-btn {
  position: absolute;
  top: 0.28rem;
  right: 3.7rem;
  z-index: 3;
  width: 22px;
  height: 22px;
  padding: 0;
  line-height: 20px;
  text-align: center;
  border-radius: 50%;
  border: 1px solid #c9d2da;
  background: #fff;
  color: #4a5568;
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
  opacity: 0.65;
}

.tp-row__reveal-btn:hover,
.tp-row__reveal-btn:focus-visible,
.tp-row__reveal-btn[aria-expanded="true"] {
  opacity: 1;
}

/* Round D: dedicated "+" trigger for adding an athlete's own entry
   (session or competition) — separate from .tp-row__summary's expand
   tap so the two never collide, present on every row (blank or already
   planned) alike. Sibling of the summary button, same absolute-overlay
   pattern as .tp-row__link-icon above. */
.tp-row__add-btn {
  position: absolute;
  top: 0.28rem;
  right: 0.5rem;
  z-index: 3;
  width: 22px;
  height: 22px;
  padding: 0;
  line-height: 20px;
  text-align: center;
  border-radius: 50%;
  border: 1px solid #c9d2da;
  background: #fff;
  color: #4a5568;
  font-size: 0.95rem;
  font-weight: 600;
  cursor: pointer;
  opacity: 0.65;
}

.tp-row__add-btn:hover,
.tp-row__add-btn:focus-visible {
  opacity: 1;
}

.tp-row__add-menu {
  position: absolute;
  top: 1.85rem;
  right: 0.4rem;
  z-index: 4;
  display: flex;
  flex-direction: column;
  min-width: 150px;
  background: #fff;
  border: 1px solid #c9d2da;
  border-radius: 8px;
  box-shadow: 0 4px 14px rgba(0, 0, 0, 0.14);
  overflow: hidden;
}

.tp-row__add-menu-option {
  padding: 0.5rem 0.75rem;
  border: none;
  background: none;
  text-align: left;
  font-size: 0.85rem;
  color: #2d3748;
  cursor: pointer;
}

.tp-row__add-menu-option:hover {
  background: #f0f2f4;
}

.tp-row__add-menu-option + .tp-row__add-menu-option {
  border-top: 1px solid #eef0f2;
}

/* Round D: source-tagged stacked individual entries can now include
   more than one per day (coach-assigned + athlete-added coexisting) —
   each gets its own edit affordance, tucked into the same progressive-
   disclosure detail area as the race actions above rather than
   cluttering the always-visible summary line. */
.tp-row__edit-own-btn {
  align-self: flex-start;
}

/* Round D: Training session type picker (Track/Grass/Road/Hills) removed
   in round V — colour_semantic is now auto-detected from free text
   server-side, same mechanism the coach board's round U auto-detect
   uses. No manual swatch picker rendered any more; nothing left to
   style. */

/* ── Round AB: anchored entry-editor popover ──────────────────────────
 * Shared by _session_entry_form.html and _competition_entry_form.html —
 * near-identical twins (date + text + Save/Delete/Cancel), one class
 * between them rather than styling each separately. `position: fixed`
 * so it's anchored to the viewport, not affected by `.tp-grid__days`
 * being its own internally-scrolling container — each form's own script
 * sets `left`/`top` from the tapped row/pill/button's
 * getBoundingClientRect(), clamped against the viewport edges the same
 * way board.html's `.board-context-menu` already clamps (see
 * positionPopover() in each form's script). Neutral tint, never plain
 * white — no surface in this app should be (same fallback #f0f2f4
 * `.tp-planned`/`.board-cell__pill` already use). */
.entry-editor-popover {
  position: fixed;
  z-index: 1071;
  display: none;
  width: min(380px, calc(100vw - 1.5rem));
  padding: 0.9rem 1rem 0.75rem 1rem;
  background: #f0f2f4;
  border: 1px solid #c9d2da;
  border-radius: 10px;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.18);
}

.tp-row--expanded .tp-row__summary-text,
.tp-row--locked-open .tp-row__summary-text {
  overflow: visible;
  text-overflow: clip;
  white-space: normal;
}

/* Delight: tapping a row should visibly "grow" the planned text into a
   bigger, clearer presentation — not just unlock word-wrap — even when
   the text already fit on one line collapsed. */
.tp-row--expanded .tp-row__summary-text--planned,
.tp-row--locked-open .tp-row__summary-text--planned {
  display: block;
  font-size: 1.05rem;
  line-height: 1.45;
  padding: 0.65rem 0.8rem;
  margin-top: 0.1rem;
}

/* The plan pill grows substantially on expand (above); stack it above the
   activity tag rather than keeping them side by side, or the enlarged
   plan crowds the tag into an awkward sliver. */
.tp-row--expanded .tp-row__summary-content,
.tp-row--locked-open .tp-row__summary-content {
  flex-direction: column;
  align-items: flex-start;
  gap: 0.3rem;
}

.tp-row--expanded .tp-row__summary-text--activity,
.tp-row--locked-open .tp-row__summary-text--activity {
  font-size: 0.92rem;
  padding: 0.35rem 0.6rem;
}

.tp-row--expanded .tp-row__summary,
.tp-row--locked-open .tp-row__summary {
  align-items: flex-start;
}

.tp-row__detail {
  padding: 0 0.85rem 0.85rem 0.85rem;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

/* Round C: Race Calendar self-service actions surfaced on an expanded
   Training Plan row (see _training_day_row.html / _calendar_grid_script.html). */
.tp-row__race-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
}

.tp-planned {
  border-radius: 7px;
  padding: 0.5rem 0.65rem;
  font-size: 0.88rem;
  font-weight: 600;
  background: #f0f2f4;
  /* Round W — same-hue darker border, see .tp-row__summary-text--planned
     above for why the base is transparent, not a fixed grey. */
  border: 1.5px solid transparent;
}

.tp-planned--individual {
  font-weight: 500;
  font-size: 0.92rem;
  line-height: 1.4;
  padding: 0.5rem 0.7rem;
  /* Now a direct sibling of .tp-row__summary rather than a child of
     .tp-row__detail, so it no longer inherits that wrapper's horizontal
     padding/gap — replicate the same inset and breathing room below. */
  margin: 0 0.85rem 0.5rem 0.85rem;
}

/* Round Z — an athlete's own entry is now directly tappable to edit
   (see dashboard.html's delegated click handler); a subtle cursor +
   hover tint signals this without adding a visible button. Only applied
   when viewer_can_edit is true (a coach/squad-authored entry never gets
   this class), so this never implies an edit affordance that isn't
   real. */
.tp-planned--editable {
  cursor: pointer;
}

.tp-planned--editable:hover {
  box-shadow: inset 0 0 0 999px rgba(0, 0, 0, 0.055);
}

/* Collapsed by default to a single truncated line — same failure mode
   the group plan pill already avoids, since individual session text can
   run to ~200 characters (multi-part sessions with paces/reps). Full
   text only on expand/locked-open, via the same tap gesture that grows
   the group pill. */
.tp-planned--individual .tp-planned__text {
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tp-row--expanded .tp-planned--individual .tp-planned__text,
.tp-row--locked-open .tp-planned--individual .tp-planned__text {
  white-space: normal;
  overflow: visible;
  text-overflow: clip;
}

.tp-planned__label {
  font-size: 0.68rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  margin-right: 0.4rem;
  opacity: 0.75;
}

.tp-activity {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.3rem 0.1rem;
  font-size: 0.82rem;
}

.tp-activity--linked {
  font-weight: 600;
  font-size: 0.88rem;
}

.tp-activity__label {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.tp-activity__meta {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  flex-shrink: 0;
}

.tp-activity__metrics {
  color: inherit;
  opacity: 0.85;
  font-size: 0.8rem;
}

/* Secondary, deliberately understated — the primary tap target stays
   ECP's own activity detail page (.tp-activity__label); this is just an
   escape hatch straight to Strava for people who specifically want it.
   Branded (Strava orange, small-caps label) rather than reusing the
   generic 🔗 .tp-row__link-icon treatment, so the two links aren't
   confused with each other. */
.tp-activity__strava-link {
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: #fc4c02;
  text-decoration: none;
  opacity: 0.75;
}

.tp-activity__strava-link:hover {
  opacity: 1;
  text-decoration: underline;
}

@media (min-width: 576px) {
  .tp-row__summary {
    padding: 0.7rem 1.1rem;
  }
  .tp-row__detail {
    padding: 0 1.1rem 1rem 1.1rem;
  }
}

/* ======================================================================
   ROUND X — Training Today panel, drag-and-drop, right-click menu
   ====================================================================== */

/* ── Today-row expand trigger ──────────────────────────────────────────
 * Item (d), Stage B fix round: the WHOLE date cell is now the tap
 * target (the date text + cue live inside the <button> itself), not
 * just a small chevron — easier to hit on phone, and the cue text
 * ("N sessions · M variations") tells the coach what expanding will
 * show before they tap, instead of an unlabelled arrow. */
.board-today-expand-btn {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  width: 100%;
  margin: 0;
  padding: 0;
  border: none;
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.board-today-expand-btn:hover .board-date__dm {
  text-decoration: underline;
  text-decoration-color: #7ab8d4;
}

.board-today-expand-btn__cue {
  display: block;
  font-size: 0.6em;
  line-height: 1.2;
  color: #457b9d;
  opacity: 0.85;
  /* Item 20: .board-col--date is a fixed 52px-wide column — a longer
     cue ("2 groups · 1 variation") in a single nowrap line either
     overflowed past the column's edge or sat hidden behind the next
     (opaque) column's background, reading as if it had been truncated.
     Overrides .board-date-cell's own ancestor white-space: nowrap
     (date digits still need to stay on one line each; only the cue
     itself wraps) so it instead wraps onto a second line within the
     column's own width. */
  white-space: normal;
  overflow-wrap: break-word;
}

.board-today-expand-btn__chevron {
  display: block;
  margin-top: 0.1rem;
  font-size: 0.65em;
  line-height: 1;
  color: #457b9d;
  opacity: 0.75;
  transition: transform 0.12s ease, opacity 0.12s ease;
}

.board-today-expand-btn:hover .board-today-expand-btn__chevron {
  opacity: 1;
}

.board-today-expand-btn[aria-expanded="true"] .board-today-expand-btn__chevron {
  transform: rotate(180deg);
  opacity: 1;
}

/* ── Training Today panel ─────────────────────────────────────────────
 * Inserted directly below today's row; teal/accent continuity with
 * .board-row--today's own #457b9d accent so it reads as "belonging" to
 * that row rather than as a generic expanding table row. */
.today-panel-row td {
  padding: 0;
  border-right: none;
}

.today-panel-cell {
  background: #f0f6ff;
}

.today-panel {
  padding: 0.85rem 1.1rem 1rem;
}

.today-panel__section + .today-panel__section {
  margin-top: 0.9rem;
}

.today-panel__label {
  margin: 0 0 0.4rem;
  font-size: 0.68rem;
  font-weight: 700;
  color: #457b9d;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.today-panel__card {
  display: block;
  border-radius: 4px;
  border: 1.5px solid transparent;
  padding: 0.35rem 0.55rem;
  font-size: 0.85rem;
  line-height: 1.4;
}

.today-panel__card--group {
  max-width: 420px;
}

/* Item (d), Stage B fix round: one card per sub-squad on a split day —
 * wider than an individual-variation card so the athlete-name list has
 * room, and a muted "· squad session" tag when the card is showing the
 * inherited whole-squad text rather than the sub-squad's own. */
.today-panel__card--subsquad {
  flex: 1 1 260px;
}

.today-panel__inherited-tag {
  font-style: italic;
  font-weight: 400;
  opacity: 0.8;
}

.today-panel__card-athletes {
  display: block;
  margin-top: 0.25rem;
  font-size: 0.78rem;
  opacity: 0.85;
}

.today-panel__cards {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.today-panel__cards .today-panel__card {
  flex: 0 1 220px;
}

.today-panel__card-name {
  display: block;
  font-weight: 700;
  font-size: 0.72rem;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  opacity: 0.85;
  margin-bottom: 0.15rem;
}

.today-panel__card-text {
  display: block;
}

.today-panel__everyone-else,
.today-panel__empty {
  margin: 0;
  color: #6c757d;
  font-size: 0.85rem;
  font-style: italic;
}

.today-panel__error {
  margin: 0;
  color: #b3261e;
  font-size: 0.85rem;
}

.today-panel__loading {
  margin: 0;
  color: #9aa5b0;
  font-size: 0.85rem;
}

/* Allocation status line (item 7) — replaces the old always-visible
 * per-chip unallocated text/Allocate button; now surfaced only inside
 * the expanded reveal panel, for a split day only. */
.today-panel__status {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  font-size: 0.85rem;
  padding: 0.3rem 0;
}

.today-panel__status--ok {
  color: #2f7a4f;
}

.today-panel__status--warn {
  color: #c07a1e;
}

.today-panel__allocate-btn {
  display: inline-block;
  margin-top: 0.6rem;
  font-size: 0.8rem;
  font-weight: 600;
  color: #4a5568;
  background: none;
  border: 1px solid #dde3e8;
  border-radius: 999px;
  padding: 0.3rem 0.8rem;
}

.today-panel__allocate-btn:hover,
.today-panel__allocate-btn:active {
  border-color: #2f6fed;
  color: #2f6fed;
}

/* ── Drag-and-drop states ─────────────────────────────────────────────
 * Reuses the established inset box-shadow hover-tint pattern (see
 * .board-cell--selectable:hover above) rather than background-color,
 * since cells routinely carry an explicit inline background colour that
 * a background-color rule here couldn't reliably override. */
.board-cell--dragging {
  opacity: 0.45;
}

.board-cell--drop-target {
  box-shadow: inset 0 0 0 2px #457b9d, inset 0 0 0 999px rgba(69, 123, 157, 0.12);
}

.board-cell--pick-source {
  box-shadow: inset 0 0 0 2px #457b9d;
}

/* ── Drag-to-reorder athlete columns (round AO) ──────────────────────
 * Same visual language as the per-cell drag states above, on the
 * header cell instead — kept as its own rule pair since it's a fully
 * separate drag-and-drop system (see board.html's own comment). */
.board-col--athlete--dragging {
  opacity: 0.45;
}

.board-col--athlete--drop-target {
  box-shadow: inset 0 0 0 2px #457b9d, inset 0 0 0 999px rgba(69, 123, 157, 0.12);
}

/* ── Duplicate / Move destination-picker banner ───────────────────── */
.board-pick-banner {
  position: fixed;
  left: 50%;
  bottom: 24px;
  transform: translateX(-50%);
  z-index: 1060;
  background: #2d3748;
  color: #fff;
  padding: 0.5rem 1rem;
  border-radius: 6px;
  font-size: 0.85rem;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.25);
}

/* ── Right-click / long-press context menu ────────────────────────── */
.board-context-menu {
  position: fixed;
  z-index: 1070;
  min-width: 140px;
  background: #fff;
  border: 1px solid #dde3e8;
  border-radius: 6px;
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.18);
  padding: 0.3rem;
}

.board-context-menu__item {
  display: block;
  width: 100%;
  text-align: left;
  padding: 0.4rem 0.6rem;
  border: none;
  background: none;
  font-size: 0.85rem;
  color: #2d3748;
  border-radius: 4px;
  cursor: pointer;
}

.board-context-menu__item:hover:not(:disabled) {
  background: #f0f6ff;
}

.board-context-menu__item:disabled {
  color: #b0bac3;
  cursor: not-allowed;
}

/* ── Add-or-replace modal (occupied drop/move destination) ────────── */
.board-drop-modal {
  position: fixed;
  inset: 0;
  z-index: 1080;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.25);
}

.board-drop-modal__box {
  background: #fff;
  border-radius: 8px;
  padding: 1.1rem 1.25rem;
  max-width: 320px;
  box-shadow: 0 10px 32px rgba(0, 0, 0, 0.25);
}

.board-drop-modal__msg {
  margin: 0 0 0.9rem;
  font-size: 0.9rem;
  color: #2d3748;
}

.board-drop-modal__actions {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}

/* ── Section C (round <new>): phone single-column agenda view ────────
 * Replaces Round AQ's 50/50 Group+Athlete split (which squeezed two
 * already-narrow columns into half a phone screen each) with a true
 * full-width, one-segment-at-a-time day list — much closer to the
 * athlete's own Training Plan agenda view. The desktop multi-column
 * grid (Group + N athlete columns, each a fixed 260/190px even
 * collapsed) simply doesn't translate to a phone screen; below the
 * phone breakpoint the table degrades to a vertical stack of day
 * "cards", each showing its date plus whichever ONE segment (Group or
 * a single athlete) the new .board-phone-segments control has picked
 * — see "PHONE SEGMENTED CONTROL" in board.html's script for the JS
 * that toggles .board-col--group--phone-focus / .board-col--athlete--
 * phone-focus accordingly. The desktop pill strip's own open/close-two-
 * slots mechanism (.board-cell--in-slot) still runs in the background
 * (its state is independent, coach-wide, and shared with Squad
 * Overview) — explicitly neutralised below so it never fights the
 * phone segment that's actually showing. */
.board-phone-segments {
  display: none;
}

@media (max-width: 520px), (pointer: coarse) and (max-height: 500px) {
  /* One-bar phone header (mockup-matching round, item 5): the brand
     navbar + page tab bar used to BOTH render on Coach Workspace at
     phone width — the brand bar shrunk (see git history for the old
     padding/font-size rules) but never removed, so the slim ~44px
     .board-phone-topbar app-bar below it was actually a SECOND bar
     stacked underneath a first one, not the single bar the mockups
     show. The brand navbar carries account-level actions (Profile/
     Logout/Admin/role switch) that .board-phone-topbar's own ☰ menu
     didn't used to list — _workspace_nav_tabs.html now folds those in
     too, so hiding the brand bar here loses no functionality. Reached
     via :has() on <body> rather than a class on the nav element itself,
     since the navbar lives in templates/base.html outside this page's
     own markup — scoped to the Coach Workspace page specifically
     (body:has(.workspace-page)), not every page using that shared
     navbar. --sticky-nav-height (measured by base.html's own JS) drops
     to 0 automatically once the navbar has no rendered height, which is
     what lets .board-phone-topbar and .workspace-page's own height math
     below both still work unmodified. */
  body:has(.workspace-page) nav.navbar {
    display: flex;
  }

  .workspace-page .nav-tabs-bar__tab {
    padding: 0.3rem 0.6rem;
    font-size: 0.76rem;
  }

  .squad-tabs {
    display: none;
  }

  /* Claude's review round: Manage (coach.squads_manage) has no
     alternative squad-switcher on phone — #app-shell-squad-trigger
     (templates/base.html) only renders for app_coach_area (Squad
     Overview/Coach Workspace), so hiding .squad-tabs here would leave
     Manage with no way to switch squads at all on phone. Re-enabled
     specifically for Manage's own chips row (brief 39 item 3) via a
     more specific selector, rather than excluding Manage from the
     blanket rule above (which still correctly applies everywhere
     else this component renders). */
  .manage-page-container .squad-tabs {
    display: flex;
  }

  .squad-tab {
    padding: 0.25rem 0.6rem;
    font-size: 0.74rem;
  }

  .board-header {
    padding: 0.15rem 0.5rem;
  }

  /* #board-wrap (inside .planner-scroll-row, inside .board-main) is the
   * element the JS actually scrolls — virtual-scroll's boardWrap.scrollTop
   * adjustments, its IntersectionObserver root, and every
   * todayRow.scrollIntoView() call all target it specifically. For it to
   * be genuinely scrollable it needs a REAL bounded height from its
   * ancestor chain, not one sized to fit its own (huge, ~9000px virtual-
   * scroll) content. Two bugs combined to break that chain here, reported
   * live as "no scroll up and down options" / stuck showing dates months
   * in the past regardless of the unconditional "scroll today into view"
   * call:
   *   1. .board-main's base (desktop) flex is `flex: 0 0 auto` — fine
   *      when .board-body's main axis is horizontal (desktop), but once
   *      the ≤900px stacked layout flips .board-body to
   *      flex-direction: column, that SAME property now applies to the
   *      vertical axis instead, so .board-main (and everything inside
   *      it, including #board-wrap two levels down) stopped shrinking to
   *      available space and just grew to its full content height
   *      instead — undoing .planner-scroll-row's own correct
   *      `flex: 1 1 auto; min-height: 0` one level further down.
   *   2. .workspace-planner's `max-height: 55vh; overflow: hidden`
   *      (≤900px rule, predates this phone redesign, originally sized to
   *      leave room for a library panel that back then stacked BELOW the
   *      grid — it's a bottom sheet overlay now, not stacked, so that
   *      rationale no longer applies) was the only remaining clip
   *      boundary, silently freezing the view at whatever rendered
   *      first in the DOM (the oldest loaded rows, today − 90 days) with
   *      nothing to touch-scroll — #board-wrap's own overflow: auto
   *      never had a bounded box to make it a real scroll container in
   *      the first place.
   * Fixed by letting .workspace-planner claim the real remaining
   * viewport height (below the sticky navbar + tab bar, same pattern as
   * .workspace-shell's own desktop height) instead of an arbitrary 55vh,
   * and giving .board-main a real flex-shrink budget so that height
   * properly propagates all the way down to #board-wrap. */
  .workspace-planner {
    max-height: none;
    height: calc(100vh - var(--sticky-nav-height) - var(--sticky-tabs-height) - var(--workspace-phone-topbar-height));
    height: calc(100dvh - var(--sticky-nav-height) - var(--sticky-tabs-height) - var(--workspace-phone-topbar-height));
  }

  .board-main {
    flex: 1 1 auto;
    min-height: 0;
    overflow: visible;
    /* Item 1 (mobile-today-emphasis brief): .board-main's base (desktop)
       rule pins width/min-width to --board-content-w — a real-pixel sum
       of date + group + (open-slot-count × 190px) columns, computed by
       recomputeRowLayout() in board.html for the desktop multi-column
       grid. That JS runs unconditionally regardless of layout, and this
       phone block never reset width/min-width back — only flex/overflow
       above — so .board-main stayed pinned to that desktop figure (314px
       with zero slots open, +190px per athlete selected via the phone
       segmented control) even though the phone layout is a single-
       column card stack with no multi-column concept at all. On a real
       phone viewport (~390px) that's already close with nothing
       selected, and badly overflows once any athlete segment is picked
       — the actual cause of the reported right-edge clipping (day cards/
       today's outline missing their right edge, pills running to the
       screen edge, "DRAFT" truncated to "DRA"): .workspace-planner's
       overflow: hidden silently clips whatever real width this produced
       rather than letting the page scroll, so content was being
       generated wider than the viewport and cut off, not reflowed.
       100% here makes .board-main always match the phone layout's real
       available width, completely independent of the desktop column
       math. */
    width: 100%;
    min-width: 0;
  }

  #board-wrap {
    -webkit-overflow-scrolling: touch;
    /* Real-device retest: despite every non-selected segment column
       already being display:none (see .board-col--group/--athlete
       base rules + the --phone-focus override below), the board was
       still horizontally draggable, revealing a sliver of whatever sat
       past the 100vw edge. overflow-x: hidden is the hard guarantee —
       there is never a second axis to scroll on phone, vertical-only
       (touch-action: pan-y) so a diagonal swipe can't be captured as a
       horizontal pan by iOS Safari's gesture recognizer either. */
    overflow-x: hidden;
    touch-action: pan-y;
  }

  .board-table,
  #board-tbody,
  .board-row {
    max-width: 100vw;
    overflow-x: hidden;
  }

  /* Item 3: declutter the phone header — Zoom is a desktop-density
     control (the single-column phone layout has no row-density problem
     for it to solve) so it's hidden outright; Today stays since it's
     the one navigation action phone still needs; Library + Share…
     collapse into the "⋯" menu (.board-phone-menu, see board.html)
     instead of two more buttons, so .board-header__controls keeps
     fitting in a single row alongside the title/month line above it. */
  .board-focus-control {
    display: none;
  }

  #library-toggle-btn,
  #publish-toggle-btn {
    display: none;
  }

  .board-phone-menu {
    display: block;
  }

  .board-phone-segments {
    display: flex;
    position: sticky;
    top: 0;
    z-index: 60;
    align-items: center;
    gap: 0.4rem;
    padding: 0.4rem 0.5rem;
    background: #eef3f9;
    border-bottom: 1px solid #dde3e8;
  }

  .board-phone-segments__list {
    display: flex;
    gap: 0.35rem;
    overflow-x: auto;
    padding-left: 16px;
    scroll-padding-left: 16px;
    flex: 1 1 auto;
    min-width: 0;
    -webkit-overflow-scrolling: touch;
    /* At rest the first pill remains fully visible; the right edge alone
       signals overflow. JS enables the left fade only after scrolling. */
    mask-image: linear-gradient(to right, black 0, black calc(100% - 20px), transparent 100%);
    -webkit-mask-image: linear-gradient(to right, black 0, black calc(100% - 20px), transparent 100%);
  }

  .board-phone-segments__list.is-scrolled {
    mask-image: linear-gradient(to right, transparent, black 10px, black calc(100% - 20px), transparent);
    -webkit-mask-image: linear-gradient(to right, transparent, black 10px, black calc(100% - 20px), transparent);
  }

  input:not([type="checkbox"]):not([type="radio"]):not([type="hidden"]):not([type="submit"]):not([type="button"]),
  textarea,
  select {
    font-size: max(16px, 1em) !important;
  }

  .today-panel-row {
    display: block;
    margin: -0.5rem 0.5rem 0.5rem;
    background: #fff;
    border-radius: 0 0 10px 10px;
  }

  .today-panel-row td,
  .today-panel-cell {
    display: block;
    width: 100%;
    box-sizing: border-box;
    border: 0;
    background: transparent;
  }

  .today-panel {
    padding: 0.25rem 0.65rem 0.75rem;
  }

  .board-phone-segment {
    flex: 0 0 auto;
    border: 1px solid #c7d0d9;
    background: #fff;
    border-radius: 999px;
    padding: 0.3rem 0.8rem;
    font-size: 0.78rem;
    font-weight: 600;
    color: #33404d;
    white-space: nowrap;
  }

  .board-phone-segment[aria-selected="true"] {
    background: #2f6fed;
    border-color: #2f6fed;
    color: #fff;
  }

  .board-phone-segments__info-btn {
    flex: 0 0 auto;
    border: 1px solid #2f6fed;
    background: #fff;
    color: #2f6fed;
    border-radius: 6px;
    padding: 0.3rem 0.6rem;
    font-size: 0.74rem;
    font-weight: 600;
    white-space: nowrap;
  }

  /* Desktop's "Athletes" pill strip has different semantics (open up
     to 2 columns side by side, no Group option) that don't map onto
     the phone's single-segment model — the segmented control above
     replaces it entirely at this width. */
  .board-pill-strip {
    display: none;
  }

  .board-thead {
    display: none; /* each day card carries its own date — no shared header row needed */
  }

  .board-row--week-sep {
    display: none; /* card format already reads as one-row-per-day; the subtle week gap doesn't translate */
  }

  /* Item 2 (mobile-today-emphasis brief): both tab rows above the board
     ("Squad Overview / Coach Workspace / Race Calendar" via the shared
     components/_tab_bar.html, and "EKU / Glasgow / ..." via .squad-tabs)
     were wrapping 2-word labels onto a second line at phone widths
     (default flex-shrink + normal white-space), each nearly doubling
     its row height and contributing most of the reported ~340px header
     stack. Switched to the same nowrap + horizontal-scroll pattern
     already established for .board-phone-segments__list just above,
     rather than inventing a new one. Both classes are shared beyond
     this page (.nav-tabs-bar with athlete pages via _tab_bar.html;
     .squad-tabs with coach/home.html's Squad Overview) — scoped here to
     the existing phone media query only, so desktop/tablet is
     untouched, but the same fix applies on phone wherever these shared
     components render. Flagged to the user rather than changed
     silently, per the brief. */
  .nav-tabs-bar,
  .squad-tabs {
    flex-wrap: nowrap;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
    /* align-items + min-height: confirmed via live measurement that
       overflow-x: auto on a row-direction flex container with no
       explicit height (relying on default align-items: stretch +
       content-derived auto height) can collapse the container's OWN
       computed height to ~2px in this engine, even though its anchor
       children still render at their real (correct) height — a
       content-vs-container auto-height mismatch specific to this
       overflow + flex-wrap: nowrap combination. Explicit alignment +
       a height floor sized to the tab's own padding + line-height
       sidesteps it entirely rather than relying on auto-sizing. */
    align-items: center;
    min-height: 2.1rem;
  }

  .nav-tabs-bar__tab,
  .squad-tab {
    white-space: nowrap;
    flex-shrink: 0;
  }

  /* "Coach Workspace" already shows in .board-phone-topbar above — no
     need to repeat it in .board-header's own month/Today/⋯ row too. */
  .board-header__title {
    display: none;
  }

  /* Item 1 (mockup-matching round): .nav-tabs-bar itself (three text
   * tabs, "Squad Overview"/"Coach Workspace"/"Race Calendar") is
   * replaced on phone by the slim .board-phone-topbar app-bar below —
   * same three destinations, now behind its own ☰ menu instead of an
   * always-visible row. :has() scopes this to exactly the pages that
   * render both (every _workspace_nav_tabs.html include emits them as
   * adjacent siblings) — athlete-side nav bars using the same
   * .nav-tabs-bar class (no .board-phone-topbar sibling there) are
   * untouched. */
  .nav-tabs-bar:has(~ .board-phone-topbar) {
    display: none;
  }

  .board-phone-topbar {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 0.5rem;
    min-height: calc(44px + env(safe-area-inset-top));
    padding: env(safe-area-inset-top) 0.75rem 0;
    background: linear-gradient(90deg, #0f766e, #2f6fed);
    position: sticky;
    /* One-bar phone header (item 5): the brand navbar above is hidden
       entirely at this breakpoint (see body:has(.workspace-page)
       nav.navbar rule above) and --sticky-nav-height measures its real
       (now zero) height automatically, so this already lands at the
       true top of the viewport without a separate override. */
    top: var(--sticky-nav-height);
    z-index: 1010;
  }

  .board-phone-topbar__title {
    font-size: 0.95rem;
    font-weight: 700;
    color: #fff;
  }

  .board-phone-topbar__menu-btn {
    flex: 0 0 auto;
    width: 2rem;
    height: 2rem;
    line-height: 1;
    font-size: 1.2rem;
    border: none;
    background: none;
    color: #fff;
  }

  .board-phone-topbar__menu {
    position: fixed;
    top: calc(var(--sticky-nav-height) + var(--workspace-phone-topbar-height) + 0.25rem);
    right: 0.75rem;
    z-index: 1230;
    min-width: 10rem;
    background: #fff;
    border: 1px solid #c7d0d9;
    border-radius: 8px;
    box-shadow: 0 8px 24px rgba(26, 35, 50, 0.22);
    display: flex;
    flex-direction: column;
    padding: 0.3rem;
  }

  .board-phone-topbar__menu[hidden] {
    display: none;
  }

  .board-phone-topbar__item {
    display: block;
    padding: 0.5rem 0.65rem;
    border-radius: 5px;
    font-size: 0.88rem;
    font-weight: 500;
    color: #1a2332;
    text-decoration: none;
  }

  .board-phone-topbar__item:hover,
  .board-phone-topbar__item:active {
    background: #eef3f9;
    color: #1a2332;
    text-decoration: none;
  }

  .board-phone-topbar__item--active {
    font-weight: 700;
    color: #0f766e;
  }

  /* Item 5: account-level actions (Profile/Logout/Admin/role switch)
     folded into the same menu, below a divider, now that the brand
     navbar providing them is hidden entirely at this breakpoint. */
  .board-phone-topbar__divider {
    border-top: 1px solid #e2e7ee;
    margin: 0.3rem 0.2rem;
  }

  .board-phone-topbar__item--btn {
    width: 100%;
    text-align: left;
    border: none;
    background: none;
    font-family: inherit;
  }

  .board-table {
    /* Exit table layout entirely at this width — .board-row below is
       display:flex (not table-row), which a UA otherwise still tries to
       size via the table column-width algorithm (since .board-table/
       #board-tbody themselves were still table/table-row-group), making
       the row's rendered width track its children's intrinsic content
       width rather than the viewport — the direct cause of cards (and
       long, unwrapped pill text inside them) running off the right
       edge, reported live as "column wider than the screen". display:
       block on the table + its tbody removes that algorithm completely,
       so .board-row's own width: 100% (below) is what actually governs
       layout, same as any ordinary block/flex element. */
    display: block;
    table-layout: auto;
    width: 100%;
  }

  #board-tbody {
    display: block;
    width: 100%;
  }

  .board-row {
    display: flex;
    flex-direction: column;
    /* Real-device retest: cards had a left margin but no right margin,
       with the right border/radius and today's outline cut off —
       asymmetric despite margin: 0 0.5rem 0.5rem below being identical
       on both sides. Root cause: `width: 100%` computes against the
       PARENT's content width and is never reduced by this element's own
       margin (box-sizing: border-box only makes width include padding/
       border, not margin) — so each card rendered a full parent-width
       box pushed right by its own left margin, overshooting the
       parent's right edge by one right-margin's worth (confirmed via
       getBoundingClientRect(): row width exactly equalled #board-tbody's
       width, not width-minus-margins). #board-wrap's overflow-x: hidden
       (further up this file) then silently clipped that overshoot
       instead of reflowing it, hiding the bug from a plain scrollWidth
       check. Fix: no width rule at all — .board-row's parent
       (#board-tbody) is a plain block box, so a block-level child left
       at its default width: auto already fills "available width minus
       my own margin" per normal CSS block layout, which is exactly the
       symmetric sizing wanted here. */
    min-width: 0;
    box-sizing: border-box;
    background: #fff;
    border-radius: 10px;
    margin: 0 0.5rem 0.5rem;
    padding: 0.5rem 0.65rem;
    box-shadow: 0 1px 2px rgba(26, 35, 50, 0.1);
  }

  .board-row--today {
    box-shadow: 0 0 0 2px #0f766e, var(--lift-shadow);
    transform: translateY(var(--lift-y));
    /* Leaves room for the now-sticky .board-phone-segments bar so the
     * unconditional todayRow.scrollIntoView() call (further down this
     * file's board.html script) doesn't tuck today's row underneath it. */
    scroll-margin-top: 54px;
  }

  /* Item 4 (mobile-today-emphasis brief): the desktop left-accent border
     (.board-row--today .board-col--date, base rule further up this
     file) makes sense as a column divider in the table grid, but inside
     a free-floating phone card that already has its own whole-card teal
     outline (the box-shadow ring above), that same thin inner line sits
     right against the date text with nothing else around it to read as
     a "border" — reported live as "a stray text-cursor-like mark before
     'Mon 5'". Not a focus/edit state or a rendering bug — it's a real,
     working style rule that's simply redundant and confusing once a
     card already has its own outline. Removed on phone only; the
     outline ring above is sufficient on its own. */
  .board-row--today .board-col--date {
    border-left: none;
  }

  /* Item 4 (bugfix round, Oct 2026 — Bug 4): the today-row "show
     today's sessions" expand chevron (.board-today-expand-btn) kept
     being reported live as "a stray dot", across two earlier attempts
     (see the superseded comment this replaces, preserved in git
     history) that both focused on the chevron's OWN size/position.
     Root cause was neither of those — it's that .board-col--date on
     phone is no longer a narrow table column, it's the FULL WIDTH of
     the free-floating card (board-row is flex-direction: column, date
     cell stacked above the group cell — see .board-row--today rule
     above). `margin-left: auto` therefore pushed the chevron all the
     way to the card's right edge, tens of pixels from "Mon 5 Oct" with
     nothing in between — a fully transparent, borderless 44px hit
     target with only a tiny ▾ glyph floating alone in empty space
     reads exactly like a stray mark, regardless of how correctly
     sized/positioned it technically is. Fix: drop the auto-margin push
     entirely so it sits immediately after "Oct" (reads as part of the
     date, not a disconnected mark), and give it actual visible button
     chrome (soft circular background + border) so the tap target is
     self-evidently a control even against the card's plain background. */
  /* Item (d) update (Stage B fix round): the whole date cell is now
     wrapped in this <button> (date spans + cue text + chevron all
     nested inside it, see _board_rows.html), not just a small trailing
     icon — on phone that would otherwise stack them in a column (the
     desktop rule's flex-direction: column) and reintroduce exactly the
     bloated date row these comments already fixed once. Flatten back
     to the original inline "Mon 5 Oct [chevron]" reading: row layout,
     content-sized (not width: 100%), and hide the new cue text here —
     item (e)'s whole point is a SHORTER phone header, so the cue adds
     clutter on phone precisely where there's least room for it; it's
     a desktop/tablet affordance only (room's tight, nothing scrolls
     past these fixed columns there, so cue text helps more than it
     costs). Tapping anywhere on the date text or the circle still
     opens the panel either way — a strictly bigger tap target than
     before, no behaviour lost. */
  .board-today-expand-btn {
    display: inline-flex;
    flex-direction: row;
    align-items: baseline;
    gap: 0.35rem;
    width: auto;
    margin: 0;
    font-size: 0.85rem;
    opacity: 1;
  }

  .board-today-expand-btn__cue {
    display: none; /* desktop/tablet-only affordance — see comment above */
  }

  .board-today-expand-btn__chevron {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    margin: 0;
    width: 28px;
    height: 28px;
    min-width: 28px;
    min-height: 28px;
    border-radius: 50%;
    background: rgba(69, 123, 157, 0.12);
    border: 1px solid rgba(69, 123, 157, 0.35);
    font-size: 0.85rem;
    flex-shrink: 0;
    /* Expand the invisible tap target to 44px without growing the
       visible circle — keeps the touch target accessible while the
       on-screen affordance stays compact and close to the date text. */
    position: relative;
  }

  .board-today-expand-btn__chevron::before {
    content: "";
    position: absolute;
    top: 50%;
    left: 50%;
    width: 44px;
    height: 44px;
    transform: translate(-50%, -50%);
  }

  /* Month divider \u2014 a plain <tr>, not a .board-row, so it never picks
     up the card styling above; needs its own explicit block display
     now .board-table/#board-tbody have exited table layout (see the
     .board-table comment above), otherwise a bare <tr>/<td> left at
     their table-layout defaults render unpredictably once their table
     ancestor chain is broken. */
  .board-month-divider {
    display: block;
  }

  .board-month-divider td {
    display: block;
    width: 100%;
    box-sizing: border-box;
  }

  .board-row td {
    border: none;
  }

  /* Date becomes a small header line atop the card, not a sticky
   * left-hand column — !important needed on position/left to win
   * against .board-col--sticky and the .board-cell.board-col--sticky2
   * re-assertion rule (deliberately higher-specificity ties elsewhere
   * in this file for the desktop horizontal-scroll layout, which no
   * longer applies at all once the table is a single-column stack). */
  .board-col--date {
    position: static !important;
    left: auto !important;
    width: auto !important;
    min-width: 0 !important;
    max-width: none !important;
    display: flex !important;
    align-items: baseline;
    gap: 0.35rem;
    padding: 0 0 0.3rem !important;
    background: none !important;
  }

  .board-col--date .board-date__dm,
  .board-col--date .board-date__mon {
    display: inline;
  }

  /* Weekday abbreviation ("Sat 3 Oct" vs. bare "3 Oct") — real-device
     retest asked for it explicitly on phone. The desktop rule above
     ([data-zoom="large"] .board-date__dow { display: none; }) hides it
     at the two densest zoom presets so "Mon 24" still fits a 52px
     column; that column-width constraint doesn't exist here (the date
     line is now an inline header, not a fixed-width column), so always
     show it regardless of whatever zoom tier is currently active. */
  .board-col--date .board-date__dow {
    display: inline !important;
  }

  /* A.1/A.2's slot-based visibility (.board-cell--in-slot) is desktop-
   * only state — explicitly turned off here so it can never show a
   * second column alongside the phone-selected one. */
  .board-col--group,
  .board-col--athlete.board-cell--in-slot {
    display: none;
  }

  /* 3-class selectors (vs. the 2-class rules above/elsewhere) so these
   * always win regardless of source order — the one segment the
   * coach picked is the only content column ever shown on phone. */
  .board-col--group.board-cell.board-col--group--phone-focus,
  .board-col--athlete.board-cell.board-col--athlete--phone-focus {
    display: block;
    position: static !important;
    left: auto !important;
    width: 100% !important;
    min-width: 0 !important;
    max-width: none !important;
    border-left: none;
    background: none !important;
    padding: 0;
  }

  .board-cell__pill {
    font-size: 0.92rem;
    padding: 0.3rem 0.5rem;
    max-width: 100%;
  }

  /* Real-device retest: pill text was getting cut off instead of
     wrapping. .board-cell__text's base rule (desktop) intentionally
     clips beyond 3 lines (-webkit-line-clamp: 3) to protect the dense
     4-week grid's row height — that density trade-off doesn't apply to
     the phone single-column stack, where each row already has as much
     vertical room as its content needs. Drop the clamp entirely here so
     the full session text always wraps onto as many lines as it needs. */
  .board-cell__text {
    display: block;
    -webkit-line-clamp: unset;
    overflow: visible;
    max-width: 100%;
  }

  /* ── Current Focus / Athlete info — bottom sheet ──────────────────
   * The side-by-side rail/panel concept doesn't fit a phone screen —
   * expanded, it instead floats as a bottom sheet over the board,
   * opened via the "Athlete info" button in .board-phone-segments and
   * dismissed via its own × (item 7, both platforms), tap-outside
   * (.board-phone-backdrop, item 4) or Esc (item 7). Swipe-down is
   * deliberately not implemented — no simple, reliably cross-browser
   * iOS Safari approach was found without a gesture library, and a ×
   * plus tap-outside is sufficient per the brief. */
  /* !important on every conflicting property: the pre-existing tabbed-
   * overlay mode (.workspace-shell--focus-tabbed #workspace-context:not
   * (.workspace-context--collapsed), triggered by recomputeRowLayout()
   * whenever available width is tight — which it always is at phone
   * width) has HIGHER selector specificity (1 class + 1 ID + 1 :not
   * vs. this rule's 1 ID + 1 :not) and would otherwise win regardless
   * of source order, leaving the panel a narrow 360px right-edge
   * flyout instead of a full-width bottom sheet. Confirmed via a live
   * getComputedStyle() check (width was computed as 132px, not 100%,
   * before this fix was added).
   *
   * max-height was originally 70vh — reported live as "completely
   * obscures the rest of coach workspace, I cannot see the training
   * plan at all" (desktop, which has no such cap via the rail/overlay
   * modes, was unaffected). On a real phone viewport the sticky nav +
   * tab bar + segmented control already consume a large share of the
   * remaining 30vh above the sheet, leaving only a sliver (or nothing)
   * of the board visible and barely any backdrop left to tap-dismiss.
   * Capped lower so a real, usable strip of the board (and backdrop)
   * always stays visible above the sheet. */
  #workspace-context:not(.workspace-context--collapsed) {
    position: fixed !important;
    left: 0 !important;
    right: 0 !important;
    bottom: 0 !important;
    top: auto !important;
    width: 100% !important;
    max-width: 100% !important;
    max-height: 50vh;
    z-index: 1200 !important;
    border-radius: 14px 14px 0 0;
    box-shadow: 0 -8px 24px rgba(26, 35, 50, 0.25);
    overflow-y: auto;
  }

  #workspace-context.workspace-context--collapsed {
    display: none; /* the rail itself is redundant once the segmented control's "Athlete info" button is the opener */
  }

  .workspace-context__rail {
    display: none;
  }

  /* The × (item 7) sits directly over the full-width active tab
   * (.workspace-context__tab--active, solid blue) whenever exactly one
   * athlete is open — its default plain-gray-on-transparent styling
   * (main.css ~2915) all but disappears against that background. Give
   * it a solid chip here so it reads clearly regardless of what's
   * behind it; safe to apply unconditionally in this media query since
   * the button is already only ever rendered while the sheet itself is
   * open (collapsed hides #workspace-context entirely, above). */
  .workspace-context__close {
    display: flex;
    align-items: center;
    justify-content: center;
    top: 0.4rem;
    right: 0.5rem;
    width: 1.8rem;
    height: 1.8rem;
    background: rgba(255, 255, 255, 0.9);
    color: #1a2332;
    border-radius: 50%;
    box-shadow: 0 1px 3px rgba(26, 35, 50, 0.3);
  }

  /* ── Session Library — bottom sheet (consistent with Athlete info
   * above; a 200px-tall inline strip wouldn't leave useful room once
   * the board itself is now a full-width single column). #}
  .board-library-panel {
    flex: none;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    top: auto;
    width: 100%;
    height: auto;
    max-height: 70vh;
    margin: 0;
    border-radius: 14px 14px 0 0;
    box-shadow: 0 -8px 24px rgba(26, 35, 50, 0.25);
    z-index: 1200;
  }

  .board-phone-backdrop:not([hidden]) {
    position: fixed;
    inset: 0;
    background: rgba(26, 35, 50, 0.35);
    z-index: 1190;
  }

  /* ── Phone edit sheet (item 4, restructured mockup-matching round
   * item 8) ──────────────────────────────────────────────────────────
   * A proper bottom sheet: #planner-sheet becomes the single fixed-
   * position flex column (grab handle / two-tier header / scrollable
   * body / secondary actions / sticky Delete-Cancel-Save footer),
   * replacing the old arrangement of two independently-fixed-positioned
   * strips (.planner-editor floating above a separate
   * #planner-editor-phone-actions bar) with one cohesive sheet.
   * .planner-editor (the body) and #planner-editor-phone-actions
   * (secondary actions) both drop their own position: fixed — they're
   * plain flex children of #planner-sheet now, which is the only
   * element actually anchored to the viewport.
   * Item 8: height reduced from ~85vh to ~55vh — a session's text is
   * short, and the old height left a wall of empty space below a
   * ~120px-tall textarea; a growing-then-scrolling textarea plus a
   * shorter sheet both match the mockup and leave more of the board
   * visible/dimmed behind it. */
  #planner-sheet {
    display: flex;
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    max-height: 55vh;
    z-index: 1210;
    flex-direction: column;
    background: #fff;
    border-radius: 14px 14px 0 0;
    box-shadow: 0 -8px 24px rgba(26, 35, 50, 0.25);
    overflow: hidden;
    padding-bottom: env(safe-area-inset-bottom);
    /* Item (f), Stage B fix round: the session's colour reads as a thin
     * accent strip along the sheet's own top edge (set by board.html's
     * applyEditorColour() via --sheet-colour-strip) instead of filling
     * the whole textbox — a full-colour background worked fine for the
     * small single-cell floating overlay this editor started as, but
     * reads as overwhelming filling a tall sheet. Falls back to
     * transparent (no visible strip) before any colour has ever been
     * set. */
    border-top: 4px solid var(--sheet-colour-strip, transparent);
    /* Item 10 (motion polish): transform/opacity-only slide-up + fade,
     * driven by the same .is-open class board.html already toggles —
     * display stays flex at all times (can't transition display), with
     * pointer-events:none closed so the off-screen sheet never blocks
     * taps on the board underneath it. No visibility:hidden toggle here
     * deliberately — board.html calls editor.focus() BEFORE adding
     * .is-open (see openEditor()/openSubSquadChipEditor()), and a
     * visibility:hidden ancestor silently rejects that focus() call. */
    transform: translateY(100%);
    opacity: 0;
    pointer-events: none;
    transition: transform 260ms cubic-bezier(0.22, 0.9, 0.3, 1), opacity 200ms ease-out;
  }

  #planner-sheet.is-open {
    transform: translateY(0);
    opacity: 1;
    pointer-events: auto;
    transition: transform 220ms cubic-bezier(0.22, 0.9, 0.3, 1), opacity 160ms ease-out;
  }

  /* Grab handle — phone-only visual affordance, no drag behaviour
   * wired to it (Cancel/×/Escape/backdrop already cover dismissal). */
  .planner-sheet__handle {
    flex: 0 0 auto;
    width: 36px;
    height: 4px;
    margin: 0.5rem auto 0.1rem;
    border-radius: 999px;
    background: #d8dee5;
  }

  #planner-sheet-header {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 0.5rem;
    padding: 0.4rem 0.75rem 0.6rem;
    border-bottom: 1px solid #dde3e8;
    background: #f5f8fb;
    flex: 0 0 auto;
  }

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

  .planner-sheet__date {
    font-size: 0.72rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    color: #5a6f88;
    text-transform: uppercase;
  }

  .planner-sheet__name {
    display: flex;
    align-items: center;
    gap: 0.4rem;
    margin-top: 0.1rem;
    font-size: 1rem;
    font-weight: 700;
    color: #1a2332;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .planner-sheet__dot {
    flex: 0 0 auto;
    width: 0.6rem;
    height: 0.6rem;
    border-radius: 50%;
  }

  .planner-sheet__dot[hidden] {
    display: none;
  }

  .planner-sheet__close {
    flex: 0 0 auto;
    width: 2rem;
    height: 2rem;
    line-height: 1;
    font-size: 1.3rem;
    border: none;
    background: none;
    color: #5a6f88;
    border-radius: 50%;
  }

  .planner-sheet__close:active {
    background: #e3edf9;
  }

  /* Item 8: a fixed default height that grows with content up to a cap,
   * then scrolls internally — not "fill all remaining space" (the old
   * flex: 1 1 auto behaviour left a huge mostly-empty box for a one-
   * line session). */
  .planner-editor {
    position: static !important;
    flex: 0 1 auto;
    height: 120px;
    max-height: 32vh;
    min-height: 0 !important;
    width: 100% !important;
    box-sizing: border-box;
    overflow-y: auto !important;
    font-size: 1rem;
    border: none;
    border-radius: 0;
    box-shadow: none;
    /* Item (f): the colour now lives in #planner-sheet's own top-edge
     * strip (above) — the textbox itself stays plain/neutral here,
     * overriding applyEditorColour()'s inline background/color/border
     * (set for desktop's floating-overlay look, where filling the small
     * overlay with the session's colour is the whole point). */
    background: #fff !important;
    color: #1a2332 !important;
  }

  .planner-sheet__group-reference {
    flex: 0 0 auto;
    padding: 0.25rem 0.75rem 0.45rem;
    color: #6c757d;
    font-size: 0.78rem;
  }

  .planner-sheet__group-reference[hidden] {
    display: none;
  }

  .planner-editor-save-lib-btn {
    display: none; /* desktop-only affordance — removed entirely on phone (item 8) */
  }

  #planner-editor-squad-target {
    position: static;
    width: 100%;
    flex: 0 0 auto;
    box-shadow: none;
    border: none;
    border-radius: 0;
    padding: 0.4rem 0.75rem;
    background: #f5f8fb;
    border-top: 1px solid #dde3e8;
  }

  #planner-editor-squad-target[hidden] {
    display: none;
  }

  #planner-editor-squad-select {
    flex: 1 1 auto;
  }

  #planner-editor-venue {
    position: static;
    width: 100%;
    flex: 0 0 auto;
    box-shadow: none;
    border: none;
    border-radius: 0;
    padding: 0.4rem 0.75rem;
    background: #f5f8fb;
    border-top: 1px solid #dde3e8;
  }

  #planner-editor-venue[hidden] {
    display: none;
  }

  #planner-editor-venue-input {
    flex: 1 1 auto;
    width: auto;
  }

  #planner-editor-phone-actions {
    position: static;
    width: 100%;
    flex: 0 1 auto;
    min-height: 0;
    overflow-y: auto;
    display: none;
    flex-direction: column;
    gap: 0.4rem;
    padding: 0.5rem 0.75rem;
    background: #f5f8fb;
    border-top: 1px solid #dde3e8;
  }

  #planner-editor-phone-actions.is-open {
    display: flex;
  }

  .planner-editor-phone-actions__row {
    display: flex;
    gap: 0.5rem;
  }

  .planner-editor-phone-actions__row button {
    flex: 1 1 0;
  }

  #planner-editor-library-results {
    max-height: 18vh;
    overflow-y: auto;
    display: flex;
    flex-direction: column;
    gap: 0.3rem;
  }

  .planner-editor-library-result {
    text-align: left;
    border: 1px solid #dde3e8;
    border-radius: 6px;
    padding: 0.35rem 0.55rem;
    background: #fff;
    font-size: 0.82rem;
  }

  /* Item 8: Delete/Cancel/Save merged into ONE row — Delete hidden
   * unless editing an existing entry (toggled via the [hidden]
   * attribute, which removes it from this flex row entirely so Cancel/
   * Save share the full width for a brand-new entry). */
  #planner-sheet-footer {
    display: flex;
    gap: 0.5rem;
    padding: 0.5rem 0.75rem calc(0.5rem + env(safe-area-inset-bottom, 0px));
    border-top: 1px solid #dde3e8;
    background: #fff;
    flex: 0 0 auto;
  }

  #planner-sheet-footer button {
    flex: 1 1 0;
  }

  #planner-sheet-footer button[hidden] {
    display: none;
  }

  /* "Saved" confirmation — a small fading toast above the sheet (not
     inside it, so it's visible even right after the sheet closes on
     save). Shown by showSavedToast() in board.html's script. */
  #planner-sheet-toast {
    display: block;
    position: fixed;
    left: 50%;
    bottom: calc(55vh + 0.75rem);
    transform: translateX(-50%) translateY(6px);
    z-index: 1215;
    background: #1a2332;
    color: #fff;
    font-size: 0.85rem;
    font-weight: 600;
    padding: 0.4rem 0.9rem;
    border-radius: 999px;
    opacity: 0;
    pointer-events: none;
    transition: opacity 180ms ease, transform 180ms ease;
  }

  #planner-sheet-toast.is-visible {
    opacity: 1;
    transform: translateX(-50%) translateY(0);
  }


  /* Right-click (desktop) has no phone equivalent any more — long-press
   * still opens this menu for Duplicate/Share/Unshare (not folded into
   * the edit sheet, per the brief's scope), just restyled as a bottom
   * sheet list instead of a tiny anchored popup so it's reachable with
   * a thumb. */
  .board-context-menu {
    position: fixed !important;
    left: 0 !important;
    right: 0 !important;
    bottom: 0 !important;
    top: auto !important;
    width: 100%;
    min-width: 0;
    border-radius: 14px 14px 0 0;
    padding: 0.5rem;
  }

  .board-context-menu__item {
    padding: 0.6rem 0.8rem;
    font-size: 1rem;
  }
}

/* Landscape phone only (coach-board-parity round) — not narrow portrait,
 * which already has plenty of vertical headroom. The non-scrolling
 * chrome above #board-wrap (.board-header + .board-phone-segments)
 * must not crowd out the plan itself — "the header area takes no more
 * than about a quarter of the screen in landscape". #board-wrap's
 * container already claims all remaining height via .board-main's
 * flex: 1 1 auto (see above), so tightening these two is enough on its
 * own — no explicit height cap needed, just a smaller footprint. */
@media (pointer: coarse) and (max-height: 500px) {
  .board-header {
    padding: 0.15rem 0.6rem;
  }
  .board-header__title {
    font-size: 0.78rem;
  }
  .board-header__month {
    font-size: 11px;
  }
  .board-phone-segments {
    padding: 0.25rem 0.5rem;
  }
  .board-phone-segment {
    padding: 0.2rem 0.6rem;
  }
}

/* ═══════════════════════════════════════════════════════════════════
   Training Plan v2 (foundation round) — feature-flagged, athlete-only.
   New `.tpv2-` prefix, kept separate from the existing `.tp-` prefixed
   Training Plan / Training History grid so the two pages don't collide
   while this is built out behind its flag. Chrome tones match the rest
   of the athlete-facing app: #DCE6F0 page tint, #E3EDF9 card surfaces —
   never plain white (regression-check item, brief §14).
   ═══════════════════════════════════════════════════════════════════ */

.tpv2-page {
  margin-bottom: 2rem;
}

.tpv2-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  background: #DCE6F0;
  padding: 0.5rem 0;
  margin-bottom: 0.75rem;
  /* Stays visible while the agenda/calendar body scrolls beneath it,
     stacked BELOW the shared .nav-tabs-bar (~line 1437) rather than at
     the same top offset — this previously used `top: var(--sticky-nav-
     height)` alone (same as .nav-tabs-bar's own offset), which made the
     two sticky elements land at identical screen coordinates once both
     engaged; with equal z-index and this header coming later in the DOM,
     it painted directly over the tab bar, making Training History/Race
     Calendar permanently unreachable without scrolling all the way back
     to the top (reported bug, Oct 2026). `--sticky-tabs-height` (set by
     base.html's measurement script whenever .nav-tabs-bar is present) is
     exactly what the existing `.sticky-page-header` component (~line 55)
     already uses for this same stacked-sticky-header pattern — not a new
     technique, just missed here. z-index left at 1010 (still above the
     watermark's 5, below the tooltip/reveal sheet's 1200+). */
  position: sticky;
  top: calc(var(--sticky-nav-height) + var(--sticky-tabs-height));
  z-index: 1010;
}

.tpv2-header__titles {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.tpv2-header__title {
  margin: 0;
}

/* Live month/year label (month-header-and-dividers round) — replaces the
   old faint centred watermark. Teal accent matches the TODAY marker's
   colour (.tpv2-day--today/.tpv2-day__today-badge) so it reads as part
   of the same "today-aware" system rather than a new colour. Text-only
   updates (no crossfade) — see _tpv2_scripts.html. */
.tpv2-header__month {
  font-size: 14px;
  font-weight: 600;
  color: #0f766e;
  min-height: 1em;
}

.tpv2-view-toggle {
  display: flex;
  gap: 0.4rem;
}

/* ── Agenda view ────────────────────────────────────────────────── */

.tpv2-agenda {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}

.tpv2-day {
  background: #E3EDF9;
  border-radius: 10px;
  border: 1px solid transparent;
  padding: 0.6rem 0.75rem;
}

.tpv2-day--today {
  border-color: #0f766e;
  /* Month-header-and-dividers round: a subtle "lift" on top of the
     existing teal outline + TODAY badge — agenda (week) view only, the
     calendar view's own .tpv2-calendar__day--today is untouched. */
  box-shadow: var(--lift-shadow);
  transform: translateY(var(--lift-y));
}

/* Brief 38, Stage 1.1 (review round) — sits above earlier_days, itself
   above the Today card; quiet/outline style so it doesn't compete with
   the Today card's own prominence below it. */
.tpv2-show-earlier-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  width: 100%;
  background: transparent;
  border: 1px dashed #94a3b8;
  border-radius: 8px;
  padding: 0.5rem;
  font-size: 0.82rem;
  color: #475569;
}

.tpv2-show-earlier-btn:hover {
  background: rgba(15, 23, 42, 0.04);
}

/* ── Month divider row (month-header-and-dividers round) ─────────────
   A plain, non-interactive sibling row in the agenda list — never
   injected inside a day card — rendered before the first day of each
   new month, and above the very first loaded day regardless of its own
   date (see _tpv2_agenda.html). No background/border; left-aligned
   flush with the day cards' own left edge. */
.tpv2-month-divider {
  height: 40px;
  display: flex;
  align-items: center;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: #64748b;
  pointer-events: none;
  user-select: none;
}

.tpv2-day__date-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 0.4rem;
}

.tpv2-day__date {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
}

.tpv2-day__dow {
  font-weight: 700;
  color: #334155;
}

.tpv2-day--today .tpv2-day__dow {
  color: #0f766e;
}

.tpv2-day__num {
  color: #64748b;
  font-size: 0.85rem;
}

.tpv2-day__today-badge {
  font-size: 0.7rem;
  font-weight: 700;
  color: #0f766e;
  text-transform: uppercase;
}

.tpv2-day__add-btn {
  border: none;
  background: transparent;
  color: #475569;
  border-radius: 999px;
  width: 1.8rem;
  height: 1.8rem;
  line-height: 1;
}

.tpv2-day__add-btn:hover {
  background: rgba(15, 23, 42, 0.08);
}

.tpv2-day__slots {
  display: flex;
  gap: 0.6rem;
}

.tpv2-slot {
  flex: 1 1 0;
  min-width: 0;
}

.tpv2-slot__label {
  display: block;
  font-size: 0.72rem;
  font-weight: 400;
  font-style: italic;
  /* #475569 (not the lighter #94a3b8 used elsewhere as a subtle grey) —
     #94a3b8 on this view's #E3EDF9 card background is only ~2.2:1
     contrast, well under the WCAG AA 4.5:1 minimum for this text size,
     and was confirmed nearly illegible in a real browser screenshot.
     Only rendered when the slot is empty (Build Brief v2.0 §10) — a
     quiet "am"/"pm" orientation cue, never an auto-generated "Rest". */
  color: #475569;
  margin-bottom: 0.2rem;
}

.tpv2-slot__pills {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.tpv2-pill {
  background: #eef2f7;
  color: #1a2332;
  border-radius: 6px;
  padding: 0.3rem 0.5rem;
  font-size: 0.82rem;
  cursor: pointer;
  transition: transform 0.1s, box-shadow 0.1s;
  /* Round 4: flex row so an optional leading icon (see
     .tpv2-pill__icons below) sits vertically centred against the label
     rather than baseline-aligned inline content — the label itself
     (.tpv2-pill__label) carries the truncation rules that used to live
     directly on this element. */
  display: flex;
  align-items: center;
  min-width: 0;
}

.tpv2-pill__label {
  /* Pill face is a short label now (see resolve_planner_pill_label()) —
     kept to one line and clipped rather than wrapped, both as a safety
     net for the rare no-surface-detected fallback (raw session_text,
     e.g. a longer race name) and so the calendar view's week rows stay a
     predictable height. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  min-width: 0;
  flex: 1 1 auto;
}

/* Round 4 (v2.0 brief §8, final handoff pass): icon(s) shown AFTER the
   label, right-anchored — the label has flex:1 1 auto (see
   .tpv2-pill__label) so it grows to fill the row and pushes this icon
   group flush against the pill's own right-edge padding. See
   resolve_pill_icons()'s docstring in services/planner_entry_service.py
   for which icon(s) a given pill gets, and services/pill_icons.py for
   the actual SVG markup. Never shown for Running (the unmarked default
   activity) or for a race/unrecognised session. */
.tpv2-pill__icons {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  margin-left: 0.35rem;
  flex: 0 0 auto;
}

.tpv2-icon {
  height: 1em;
  width: auto;
  display: block;
  color: #1a2332;
}

/* Position tweaks called out explicitly in the handoff doc — everything
   else uses the shared centring above with no per-icon adjustment.
   Bike: "+2.5 unit downward nudge" against its own viewBox height
   (~2.5/15 of the icon's own rendered height, since .tpv2-icon scales
   to height:1em — viewBox height is 15, not the original 14, after
   fixing a wheel-clipping bug; see services/pill_icons.py). */
.tpv2-icon--cycling {
  transform: translateY(0.167em);
}

.tpv2-pill:hover,
.tpv2-pill:focus-visible {
  transform: translateY(-1px);
  box-shadow: 0 2px 6px rgba(15, 23, 42, 0.18);
  outline: none;
}

/* Round 3 (v2.0 brief §6/7): hatched-by-default tier for TPv2 activity
   pills (Running/Cycling/XT/Swimming/Rowing confirmed, plus Trail — the
   one surface with its own hatched identity, a named exception to
   surface colour being dropped on hatched sessions; see
   resolve_activity_pill_render()'s docstring in
   services/planner_entry_service.py. Strength is always solid, never
   hatched). The pale two-tone background comes from the inline
   background-color set by the Python resolver (the 'hatch_background'
   tint) — this class only layers a diagonal stripe overlay on top, one
   per activity's confirmed stripe colour, so the pair (background +
   stripe) always travels together with the resolved activity rather
   than being split across two independent sources.

   Deliberately NOT patternTransform="rotate()" — a vertical-stripe
   pattern on a square tile has a coincidental symmetry at exactly ±45°
   that makes both rotation directions render identically. The path's
   corner-to-corner coordinates (M-1,11 L11,-1) draw the "/" diagonal
   directly instead. */
.tpv2-pill--hatch-running,
.tpv2-pill--hatch-cycling,
.tpv2-pill--hatch-cross_training,
.tpv2-pill--hatch-swimming,
.tpv2-pill--hatch-rowing,
.tpv2-pill--hatch-trail {
  background-repeat: repeat;
  background-size: 10px 10px;
}

.tpv2-pill--hatch-running {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10'%3E%3Cpath d='M-1,11 L11,-1' stroke='%23a8d9c4' stroke-width='2.2' stroke-linecap='square'/%3E%3C/svg%3E");
}

.tpv2-pill--hatch-cycling {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10'%3E%3Cpath d='M-1,11 L11,-1' stroke='%23eee9a0' stroke-width='2.2' stroke-linecap='square'/%3E%3C/svg%3E");
}

.tpv2-pill--hatch-cross_training {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10'%3E%3Cpath d='M-1,11 L11,-1' stroke='%23d3bdec' stroke-width='2.2' stroke-linecap='square'/%3E%3C/svg%3E");
}

.tpv2-pill--hatch-swimming {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10'%3E%3Cpath d='M-1,11 L11,-1' stroke='%23a4d2ea' stroke-width='2.2' stroke-linecap='square'/%3E%3C/svg%3E");
}

.tpv2-pill--hatch-rowing {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10'%3E%3Cpath d='M-1,11 L11,-1' stroke='%23eac7a4' stroke-width='2.2' stroke-linecap='square'/%3E%3C/svg%3E");
}

.tpv2-pill--hatch-trail {
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='10'%3E%3Cpath d='M-1,11 L11,-1' stroke='%23dbeaa4' stroke-width='2.2' stroke-linecap='square'/%3E%3C/svg%3E");
}



/* ── Calendar view ──────────────────────────────────────────────── */

.tpv2-calendar {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.tpv2-calendar__week {
  display: grid;
  /* minmax(0, 1fr), not a bare 1fr: a bare 1fr track's automatic minimum
     size is its content's min-content width, so a long unbreakable pill
     label or day header can inflate a column past its fair 1/7 share and
     blow out the whole grid width — confirmed cause of a real bug where
     the entire page (not just this grid) could be swiped/shifted
     sideways on the calendar view specifically. minmax(0, 1fr) caps each
     track's automatic minimum at 0, so overflowing content clips/scrolls
     locally instead of expanding the track. */
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 0.4rem;
}

.tpv2-calendar__day {
  background: #E3EDF9;
  border-radius: 8px;
  border: 1px solid transparent;
  padding: 0.4rem;
  /* Fixed (not min-) height, uniform across every cell in the whole grid,
     not just within one week's row — a compact glanceable calendar needs
     a predictable row height regardless of how many sessions a given day
     has. Overflow is clipped (a day with more pills than fit still shows
     its first ones; full detail is always available via tap-to-reveal,
     never only visible here) rather than growing the cell. min-width: 0
     lets this grid item shrink below its content's min-content width
     instead of contributing to the grid-blowout bug described above. */
  height: 6.5rem;
  min-width: 0;
  overflow: hidden;
}

.tpv2-calendar__day--today {
  border-color: #0f766e;
}

.tpv2-calendar__day-header {
  display: flex;
  justify-content: space-between;
  font-size: 0.72rem;
  color: #64748b;
  margin-bottom: 0.25rem;
}

.tpv2-calendar__day--today .tpv2-calendar__dow {
  color: #0f766e;
  font-weight: 700;
}

.tpv2-calendar__slots {
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
}

.tpv2-calendar__slot {
  display: flex;
  align-items: baseline;
  gap: 0.25rem;
  min-width: 0;
}

.tpv2-calendar__slot-label {
  flex: 0 0 auto;
  font-size: 0.68rem;
  font-weight: 400;
  font-style: italic;
  /* See .tpv2-slot__label above: #94a3b8 measured ~2.2:1 contrast on the
     #E3EDF9 card background and was nearly invisible on screen. Only
     rendered when the slot is empty (Build Brief v2.0 §10). */
  color: #475569;
}

.tpv2-calendar__slot-pills {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
  flex: 1 1 auto;
}

.tpv2-calendar__pills .tpv2-pill,
.tpv2-calendar__slot-pills .tpv2-pill {
  font-size: 0.7rem;
  padding: 0.15rem 0.35rem;
}

.tpv2-calendar__slot-overflow {
  /* "+N more" cue for a day with more pills than the fixed-height cell
     (.tpv2-calendar__day, overflow: hidden) can show — confirms clipped
     content is visibly flagged rather than silently hidden. Cap of 1
     pill/slot (not 2) after a real-browser measurement showed 2 pills +
     this chip's own line together overflow the fixed cell height by
     ~20px — clipping the chip itself, silently defeating the whole
     point. 1 pill/slot fits with room to spare (the AM slot can never
     actually overflow under the current heuristic in
     _build_training_plan_v2_days() — a 3rd+ individual entry always
     stacks into PM — so only PM's chip is ever realistically shown).
     Revisit the cap if either the cell height or heuristic changes. */
  font-size: 0.62rem;
  font-style: italic;
  color: #64748b;
}

.tpv2-calendar-scroll-wrap {
  display: flex;
  /* This container is now the deliberate horizontal scroller (one
     4-week block per child, snap-scrolled — see .tpv2-calendar-block
     below), not a defensive overflow trap like it used to be when
     horizontal movement here was always a bug (grid-blowout, see
     .tpv2-calendar__week above). The outer html/body overflow-x: hidden
     (near the top of this file) still guards the *page* itself against
     ever panning sideways because of this — only this specific box
     scrolls, nothing further up the tree does. */
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
}

.tpv2-calendar-block {
  /* One 4-week block = one snap point, sized to the scroll-wrap's own
     visible width so a single swipe always lands on a whole block —
     never a partial week, never a calendar-month boundary. position:
     relative was previously also the containing block for this block's
     own month watermark mark(s) — those were removed (month-header-and-
     dividers round; see .tpv2-header__month) but position: relative is
     kept since nothing else currently needs changing here. */
  flex: 0 0 100%;
  min-width: 0;
  scroll-snap-align: start;
  position: relative;
}

/* ── Today card (brief 38, Stage 1.1) ──────────────────────────────── */

.tpv2-today {
  background: #E3EDF9;
  border: 1px solid #0f766e;
  border-radius: 10px;
  padding: 0.75rem 0.9rem;
  margin-bottom: 0.75rem;
  box-shadow: var(--lift-shadow);
}

.tpv2-today__date-row {
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  flex-wrap: wrap;
  margin-bottom: 0.35rem;
}

.tpv2-today__date {
  font-weight: 700;
  color: #334155;
}

.tpv2-today__badge {
  font-size: 0.7rem;
  font-weight: 700;
  color: #0f766e;
  text-transform: uppercase;
}

.tpv2-today__time {
  font-size: 0.82rem;
  color: #475569;
}

.tpv2-today__meta-row {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  flex-wrap: wrap;
  margin-bottom: 0.4rem;
  font-size: 0.78rem;
}

.tpv2-today__group-tag {
  background: #0f766e;
  color: #ffffff;
  border-radius: 999px;
  padding: 0.1rem 0.5rem;
  font-weight: 600;
}

.tpv2-today__your-session-label {
  color: #475569;
}

.tpv2-today__elsewhere-tag {
  background: #94a3b8;
  color: #ffffff;
  border-radius: 999px;
  padding: 0.1rem 0.5rem;
  font-weight: 600;
}

.tpv2-today__venue {
  color: #475569;
  min-width: 0;
  overflow-wrap: anywhere;
}

/* "Their session, large" (brief wording) — a bigger, bolder treatment
   than the agenda's own compact .tpv2-pill, same colour-resolution
   source. min-width: 0 + overflow-wrap so a long session_text can never
   push the card wider than its container (phone-overflow checks). */
.tpv2-today__session {
  background: #eef2f7;
  color: #1a2332;
  border-radius: 8px;
  padding: 0.6rem 0.75rem;
  font-size: 1.05rem;
  font-weight: 600;
  min-width: 0;
  overflow-wrap: anywhere;
}

.tpv2-today__your-version {
  display: block;
  font-size: 0.68rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  opacity: 0.85;
  margin-bottom: 0.15rem;
}

.tpv2-today__group-line {
  font-size: 0.75rem;
  color: #64748b;
  margin-top: 0.25rem;
}

/* Brief 48 Stage 2.2: the group's session, quietly, under an athlete's
   own session ("Group 1 tonight: Track 6 x 1000") — agenda row and the
   classic Training Plan row. */
.tpv2-day__group-beneath {
  margin-top: 0.35rem;
  font-size: 0.75rem;
  line-height: 1.35;
  color: #64748b;
  overflow-wrap: anywhere;
}
.tp-row__group-beneath {
  margin: -0.15rem 0.85rem 0.5rem 0.85rem;
  font-size: 0.78rem;
  line-height: 1.35;
  color: #64748b;
  overflow-wrap: anywhere;
}
.tp-row__group-beneath-label { font-weight: 600; }

.tpv2-today__with-row {
  font-size: 0.82rem;
  color: #475569;
  margin-top: 0.4rem;
  min-width: 0;
  overflow-wrap: anywhere;
}

.tpv2-today__log-slot {
  height: 0;
}

.tpv2-today__also {
  margin-top: 0.5rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.tpv2-today__also-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.4rem;
  width: 100%;
  background: #ffffff;
  border: 1px solid #cbd5e1;
  border-radius: 8px;
  padding: 0.4rem 0.6rem;
  font-size: 0.8rem;
  color: #334155;
  text-align: left;
  min-width: 0;
}

.tpv2-today__also-summary {
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  flex: 1 1 auto;
}

.tpv2-today__also-chevron {
  flex: 0 0 auto;
  transition: transform 0.15s;
}

.tpv2-today__also-row[aria-expanded="true"] .tpv2-today__also-chevron {
  transform: rotate(180deg);
}

.tpv2-today__also-detail {
  background: #f8fafc;
  border-radius: 8px;
  padding: 0.4rem 0.6rem;
  font-size: 0.78rem;
  color: #334155;
  min-width: 0;
  overflow-wrap: anywhere;
}

.tpv2-today__also-names {
  color: #64748b;
  margin-top: 0.2rem;
}

/* ── Reveal: tooltip (passive detail) ──────────────────────────────── */

.tpv2-tooltip {
  position: fixed;
  z-index: 1200;
  background: #ffffff;
  border-radius: 10px;
  box-shadow: 0 6px 24px rgba(15, 23, 42, 0.25);
  padding: 0.75rem 1rem;
  max-width: 18rem;
}

.tpv2-tooltip__close {
  position: absolute;
  top: 0.25rem;
  right: 0.5rem;
  border: none;
  background: transparent;
  font-size: 1.1rem;
  line-height: 1;
  color: #94a3b8;
}

.tpv2-tooltip__text {
  margin: 0.5rem 0 0.75rem;
  font-size: 0.9rem;
}

/* ── Reveal: bottom sheet (active logging) ─────────────────────────── */

.tpv2-sheet-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(15, 23, 42, 0.35);
  z-index: 1300;
}

.tpv2-sheet {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1301;
  background: #ffffff;
  border-radius: 16px 16px 0 0;
  padding: 1rem 1.25rem 1.5rem;
  max-height: 85vh;
  overflow-y: auto;
  box-shadow: 0 -8px 24px rgba(15, 23, 42, 0.25);
}

.tpv2-sheet__handle {
  width: 2.5rem;
  height: 0.3rem;
  border-radius: 999px;
  background: #cbd5e1;
  margin: 0 auto 0.75rem;
}

.tpv2-sheet__close {
  position: absolute;
  top: 0.5rem;
  right: 0.9rem;
  border: none;
  background: transparent;
  font-size: 1.3rem;
  line-height: 1;
  color: #94a3b8;
}

.tpv2-sheet__text {
  font-size: 0.95rem;
  font-weight: 600;
  margin-bottom: 0.75rem;
}

.tpv2-sheet__section {
  margin-bottom: 1rem;
}

.tpv2-sheet__section-title {
  font-size: 0.78rem;
  font-weight: 700;
  color: #64748b;
  display: block;
  margin-bottom: 0.35rem;
}

.tpv2-completion {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.tpv2-completion__option {
  text-align: left;
  border: 1px solid #cbd5e1;
  background: #f8fafc;
  border-radius: 8px;
  padding: 0.5rem 0.75rem;
  font-size: 0.88rem;
}

.tpv2-completion__option--selected {
  border-color: #0f766e;
  background: #ccfbf1;
  font-weight: 700;
}

.tpv2-sheet__actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* ── Pop and Zing ───────────────────────────────────────────────────
   Title centred above the scale, prompt centred below — deliberately
   two separate centred lines, not one boxed banner. Real native
   vertical range input (writing-mode-based — supported in current
   Chrome/Firefox; -webkit-appearance fallback for older WebKit), thumb
   hidden, custom handle positioned by JS (see
   _tpv2_pop_and_zing_script.html) so it can drive the squash/spark
   endpoint effects and the growing/brightening battery-style colour
   convention. */

.tpv2-popzing__title {
  text-align: center;
  font-weight: 700;
  color: #1a2332;
  margin-bottom: 0.5rem;
}

.tpv2-popzing__prompt {
  text-align: center;
  font-size: 0.85rem;
  color: #64748b;
  margin-top: 0.5rem;
}

.tpv2-popzing__track-wrap {
  position: relative;
  display: flex;
  justify-content: center;
  /* The handle (2.2rem circle) and its caption are centred ON the
     track's 0%/100% endpoints, so half the handle — and, at the top
     end, the caption text above it — would otherwise overlap the
     "Pop and Zing" title sitting directly above; the same happens at
     the bottom end against the prompt line below. Reserve clearance
     on both sides. */
  margin-top: 1.75rem;
  margin-bottom: 1.75rem;
}

.tpv2-popzing__track {
  position: relative;
  width: 10px;
  height: 15rem;
  background: #e2e8f0;
  border-radius: 999px;
}

.tpv2-popzing__fill {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  border-radius: 999px;
  transition: height 0.08s linear, background 0.08s linear;
}

.tpv2-popzing__input {
  -webkit-appearance: slider-vertical;
  writing-mode: vertical-lr;
  direction: rtl;
  position: absolute;
  left: 50%;
  top: 0;
  width: 2.2rem;
  height: 100%;
  margin: 0;
  transform: translateX(-50%);
  opacity: 0;
  cursor: pointer;
}

.tpv2-popzing__input::-webkit-slider-thumb {
  -webkit-appearance: none;
  width: 2.2rem;
  height: 2.2rem;
}

.tpv2-popzing__input::-moz-range-thumb {
  width: 2.2rem;
  height: 2.2rem;
  border: none;
}

.tpv2-popzing__handle {
  --pz-scale: 1;
  position: absolute;
  left: 50%;
  top: 50%;
  width: 2.2rem;
  height: 2.2rem;
  border-radius: 50%;
  transform: translate(-50%, -50%) scale(var(--pz-scale));
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 2px 6px rgba(15, 23, 42, 0.3);
  pointer-events: none;
  transition: top 0.08s linear, background 0.08s linear;
}

.tpv2-popzing__handle-value {
  font-size: 0.85rem;
  font-weight: 700;
  color: #ffffff;
  text-shadow: 0 1px 2px rgba(0, 0, 0, 0.35);
}

/* Sun-ray marks through the top half of the range (value >= 6). */
.tpv2-popzing__handle--rays::before,
.tpv2-popzing__handle--rays::after {
  content: "";
  position: absolute;
  background: rgba(255, 255, 255, 0.85);
  border-radius: 999px;
}

.tpv2-popzing__handle--rays::before {
  width: 0.5rem;
  height: 2px;
  top: -0.55rem;
  left: 50%;
  transform: translateX(-50%) rotate(0deg);
}

.tpv2-popzing__handle--rays::after {
  width: 2px;
  height: 0.5rem;
  top: -0.15rem;
  right: -0.15rem;
}

/* Value 1 — "dropped plasticine": a squash that doesn't spring back. */
.tpv2-popzing__handle--squash {
  animation: tpv2PopZingSquash 0.35s ease-out forwards;
}

@keyframes tpv2PopZingSquash {
  0%   { transform: translate(-50%, -50%) scale(var(--pz-scale)); }
  60%  { transform: translate(-50%, -35%) scale(1.3, 0.6); }
  100% { transform: translate(-50%, -50%) scale(1.15, 0.7); }
}

/* Value 10 — spring-zigzag burst outward. */
.tpv2-popzing__handle--spring {
  animation: tpv2PopZingSpring 0.5s ease-out;
}

@keyframes tpv2PopZingSpring {
  0%   { transform: translate(-50%, -50%) scale(var(--pz-scale)); }
  20%  { transform: translate(-65%, -50%) scale(var(--pz-scale)); }
  40%  { transform: translate(-35%, -50%) scale(var(--pz-scale)); }
  60%  { transform: translate(-58%, -50%) scale(var(--pz-scale)); }
  80%  { transform: translate(-42%, -50%) scale(var(--pz-scale)); }
  100% { transform: translate(-50%, -50%) scale(var(--pz-scale)); }
}

.tpv2-popzing__sparks {
  position: absolute;
  left: 50%;
  top: 0;
  width: 0;
  height: 0;
  pointer-events: none;
}

.tpv2-popzing__spark {
  --pz-spark-x: 0px;
  --pz-spark-y: 0px;
  position: absolute;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: #fbbf24;
  animation: tpv2PopZingSpark 0.5s ease-out forwards;
}

@keyframes tpv2PopZingSpark {
  0%   { transform: translate(0, 0); opacity: 1; }
  100% { transform: translate(var(--pz-spark-x), var(--pz-spark-y)); opacity: 0; }
}

.tpv2-popzing__captions {
  position: absolute;
  top: 50%;
  width: 9rem;
  font-size: 0.78rem;
  color: #334155;
  transform: translateY(-50%);
  pointer-events: none;
}

.tpv2-popzing__captions--left {
  right: calc(50% + 1.6rem);
  text-align: right;
}

.tpv2-popzing__captions--right {
  left: calc(50% + 1.6rem);
  text-align: left;
}

/* ── Session register (brief 33, Stage 1, section 5) ─────────────────
   "Tonight bar" at the top of Coach Workspace, and each training day's
   small "Register" button on its board card. The register page itself
   is a standalone document (static/css/register.css) — none of that
   stylesheet loads here, and none of main.css loads there. */

.register-tonight-bar {
  display: flex;
  align-items: center;
  gap: 12px;
  width: 100%;
  padding: 12px 14px;
  margin: 0 0 0.75rem;
  border-radius: 16px;
  border: 0;
  background: #2f6fed;
  color: #fff;
  text-align: left;
  text-decoration: none;
  box-shadow: var(--lift-shadow);
  transform: translateY(var(--lift-y));
}
.register-tonight-bar:hover,
.register-tonight-bar:focus-visible {
  color: #fff;
  text-decoration: none;
}
.register-tonight-bar__text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.register-tonight-bar__kicker {
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  opacity: 0.85;
}
.register-tonight-bar__title {
  font-size: 19px;
  font-weight: 800;
}
.register-tonight-bar__sub {
  font-size: 14px;
  opacity: 0.92;
}
.register-tonight-bar__go {
  flex: 0 0 auto;
  background: #fff;
  color: #2f6fed;
  font-weight: 800;
  border-radius: 12px;
  padding: 10px 14px;
  font-size: 16px;
  white-space: nowrap;
}
.register-tonight-bar--done .register-tonight-bar__go,
.register-tonight-bar--mine_done .register-tonight-bar__go {
  background: rgba(255, 255, 255, 0.2);
  color: #fff;
}
.register-tonight-bar--cancelled {
  background: #55657a;
}

.board-register-btn {
  margin-left: auto;
  display: inline-block;
  border: 1.5px solid #c9d5e4;
  background: #fff;
  border-radius: 10px;
  padding: 2px 8px;
  font-size: 12px;
  font-weight: 700;
  color: #1a2332;
  text-decoration: none;
  white-space: nowrap;
}
.board-register-btn:hover,
.board-register-btn:focus-visible {
  color: #1a2332;
  text-decoration: none;
  border-color: #2f6fed;
}
.board-register-btn[aria-disabled="true"] {
  opacity: 0.6;
  pointer-events: none;
}

/* ==================================================================== *
 * Brief 48 Stage 2 — the add-session sheet, the Share sheet and the    *
 * amber "N sessions not shared yet" line on the Coach Workspace.      *
 *                                                                      *
 * "The look" (brief 48): coach blue (#EAF0F8) sheet, see-through cards *
 * (rgba(255,255,255,.5) + a fine inset edge + blur), soft blue fields, *
 * light-blue unselected chips, teal only for the chosen chip, the main *
 * button and links, amber for "Not shared". Phone: a bottom sheet;     *
 * 768px and up: a centred sheet. Every tap target is at least 44px.    *
 * The coach board itself has no dark theme yet, so the sheets carry    *
 * their own self-contained dark variant (they paint their own          *
 * background) rather than half-darkening the page behind them.         *
 * ==================================================================== */
.ecp-sheet-scrim {
  position: fixed;
  inset: 0;
  z-index: 1300;
  background: rgba(15, 23, 42, 0.35);
  opacity: 0;
  transition: opacity 200ms ease-out;
}
.ecp-sheet-scrim.is-open { opacity: 1; }
.ecp-sheet-scrim[hidden] { display: none; }

.ecp-sheet {
  --sheet-text: #1a2332;
  --sheet-muted: #475569;
  --sheet-link: #0f766e;
  --sheet-danger: #b42318;
  --sheet-card: rgba(255, 255, 255, 0.5);
  --sheet-edge: rgba(47, 95, 158, 0.14);
  --sheet-field: rgba(47, 95, 158, 0.08);
  --sheet-chip: rgba(47, 95, 158, 0.10);
  --sheet-chip-text: #1e3a5f;
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1310;
  display: flex;
  flex-direction: column;
  max-height: 92vh;
  max-height: 92dvh;
  box-sizing: border-box;
  color: var(--sheet-text);
  background: rgba(234, 240, 248, 0.97);
  -webkit-backdrop-filter: blur(18px) saturate(1.2);
  backdrop-filter: blur(18px) saturate(1.2);
  border-radius: 16px 16px 0 0;
  box-shadow: 0 -8px 30px rgba(26, 35, 50, 0.22);
  transform: translateY(100%);
  transition: transform 260ms cubic-bezier(0.22, 0.9, 0.3, 1);
  overflow: hidden;
}
.ecp-sheet.is-open { transform: translateY(0); }
.ecp-sheet[hidden] { display: none; }

@media (min-width: 768px) {
  .ecp-sheet {
    left: 50%;
    right: auto;
    top: 50%;
    bottom: auto;
    width: min(520px, 92vw);
    max-height: 86vh;
    border-radius: 16px;
    box-shadow: 0 18px 50px rgba(26, 35, 50, 0.28);
    transform: translate(-50%, -46%);
    opacity: 0;
    transition: transform 220ms cubic-bezier(0.22, 0.9, 0.3, 1), opacity 180ms ease-out;
  }
  .ecp-sheet.is-open { transform: translate(-50%, -50%); opacity: 1; }
}

@media (prefers-reduced-motion: reduce) {
  .ecp-sheet, .ecp-sheet-scrim { transition: none; }
}

.ecp-sheet__header {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 8px;
  padding: 6px 8px 2px;
  flex: 0 0 auto;
}
.ecp-sheet__header::before {
  /* grab handle */
  content: '';
  position: absolute;
  top: 6px;
  left: 50%;
  width: 36px;
  height: 4px;
  margin-left: -18px;
  border-radius: 2px;
  background: rgba(47, 95, 158, 0.25);
}
.ecp-sheet__title {
  margin: 0;
  padding-top: 8px;
  font-size: 1.02rem;
  font-weight: 700;
  text-align: center;
  white-space: nowrap;
  color: var(--sheet-text);
}
.ecp-sheet__header > :first-child { justify-self: start; }
.ecp-sheet__header > :last-child { justify-self: end; }

.ecp-sheet__textbtn,
.ecp-btn-text,
.ecp-link {
  min-height: 44px;
  min-width: 44px;
  padding: 0 10px;
  border: 0;
  background: none;
  color: var(--sheet-link);
  font-size: 1rem;
  font-weight: 500;
  cursor: pointer;
}
.ecp-link {
  min-height: 0;
  min-width: 0;
  padding: 0;
  font-size: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
  position: relative;
}
/* Keep the small inline link easy to hit without changing the layout. */
.ecp-link::after {
  content: '';
  position: absolute;
  inset: -12px -8px;
}
.ecp-btn-text--danger { color: var(--sheet-danger); font-size: 0.92rem; }

.ecp-sheet__body {
  flex: 1 1 auto;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 4px 16px 8px;
}
.ecp-sheet__section-label {
  display: block;
  margin: 12px 2px 6px;
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--sheet-muted);
}
.ecp-sheet__who {
  margin: 8px 2px 0;
  font-size: 0.82rem;
  line-height: 1.4;
  color: var(--sheet-muted);
  min-height: 1.4em;
}
.ecp-sheet__error {
  margin: 10px 2px 0;
  font-size: 0.86rem;
  color: var(--sheet-danger);
}

.ecp-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
.ecp-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  max-width: 100%;
  padding: 0 14px;
  border: 0;
  border-radius: 22px;
  background: var(--sheet-chip);
  color: var(--sheet-chip-text);
  font-size: 0.92rem;
  font-weight: 500;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: pointer;
}
.ecp-chip[aria-checked="true"] {
  background: #2DD4BF;
  color: #0b3b36;
  font-weight: 600;
}
.ecp-chip:focus-visible { outline: 2px solid #0f766e; outline-offset: 2px; }
.ecp-chip__dot {
  flex: 0 0 auto;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12);
}
.ecp-chips__divider {
  flex-basis: 100%;
  height: 0;
}

.ecp-field {
  display: block;
  width: 100%;
  box-sizing: border-box;
  padding: 10px 12px;
  border: 0;
  border-radius: 10px;
  background: var(--sheet-field);
  color: var(--sheet-text);
  font-size: 16px; /* never smaller: iOS zooms into smaller fields */
  line-height: 1.4;
  resize: vertical;
}
.ecp-field::placeholder { color: #7a8aa0; }
.ecp-field:focus { outline: 2px solid #2DD4BF; outline-offset: 0; }

.ecp-card {
  margin-top: 12px;
  border-radius: 12px;
  background: var(--sheet-card);
  box-shadow: inset 0 0 0 0.5px var(--sheet-edge);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  overflow: hidden;
}
.ecp-row {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 48px;
  margin: 0;
  padding: 0 12px;
  box-sizing: border-box;
}
.ecp-row + .ecp-row { box-shadow: inset 0 0.5px 0 var(--sheet-edge); }
.ecp-row__label {
  flex: 0 0 auto;
  font-size: 0.95rem;
  color: var(--sheet-text);
}
.ecp-row__value {
  flex: 1 1 auto;
  min-width: 0;
  text-align: right;
  color: var(--sheet-muted);
  font-size: 0.95rem;
}
.ecp-row__input {
  flex: 1 1 auto;
  min-width: 0;
  min-height: 44px;
  border: 0;
  background: transparent;
  color: var(--sheet-text);
  font-size: 16px;
  text-align: right;
}
.ecp-row__input::placeholder { color: #7a8aa0; }
.ecp-row__input:focus { outline: none; }
.ecp-row:focus-within { background: var(--sheet-field); }

.ecp-sheet__footer {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 2px;
  padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
}
.ecp-btn-main {
  min-height: 48px;
  border: 0;
  border-radius: 12px;
  background: #2DD4BF;
  color: #0b3b36;
  font-size: 1rem;
  font-weight: 700;
  cursor: pointer;
}
.ecp-btn-main:disabled { opacity: 0.45; cursor: default; }
.ecp-btn-main--danger { background: #e5484d; color: #fff; }
.ecp-btn-main:focus-visible,
.ecp-btn-text:focus-visible,
.ecp-sheet__textbtn:focus-visible { outline: 2px solid #0f766e; outline-offset: 2px; }

/* ── Share sheet list ─────────────────────────────────────────────── */
.share-sheet__dates {
  margin: 10px 2px 0;
  font-size: 0.92rem;
  color: var(--sheet-text);
}
.share-sheet__picker {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
  margin-top: 10px;
}
.share-sheet__picker label {
  display: flex;
  flex-direction: column;
  gap: 4px;
  flex: 1 1 140px;
  font-size: 0.78rem;
  color: var(--sheet-muted);
}
.share-sheet__picker input { min-height: 44px; }
.share-sheet__note {
  margin: 10px 2px 0;
  font-size: 0.82rem;
  color: var(--sheet-muted);
}
.share-row {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 48px;
  margin: 0;
  padding: 0 12px;
  cursor: pointer;
}
.share-row + .share-row { box-shadow: inset 0 0.5px 0 var(--sheet-edge); }
.share-row input[type="checkbox"] {
  flex: 0 0 auto;
  width: 22px;
  height: 22px;
  margin: 0;
  accent-color: #2DD4BF;
}
.share-row__label {
  flex: 1 1 auto;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: 0.95rem;
}
.share-row__count {
  flex: 0 0 auto;
  font-size: 0.82rem;
  color: var(--sheet-muted);
}
.share-row__count--new { color: #8a4f09; font-weight: 600; }
.share-row--zero .share-row__label { color: var(--sheet-muted); }
.share-sheet__more { margin-top: 10px; }

/* ── The amber "N sessions not shared yet · Share ›" line ─────────── */
.board-unshared-line {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 6px;
  width: 100%;
  min-height: 44px;
  margin: 6px 0;
  padding: 8px 12px;
  box-sizing: border-box;
  border: 0;
  border-radius: 10px;
  background: rgba(245, 158, 11, 0.14);
  box-shadow: inset 0 0 0 0.5px rgba(180, 105, 14, 0.35);
  color: #8a4f09;
  font-size: 0.9rem;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}
.board-unshared-line[hidden] { display: none; }
.board-unshared-line__go { color: #0f766e; white-space: nowrap; }
.board-unshared-line:focus-visible { outline: 2px solid #0f766e; outline-offset: 2px; }

/* ── "+" on every day: small to look at, 44px to hit ──────────────── */
.board-day-add-btn { position: relative; }
.board-day-add-btn::after {
  content: '';
  position: absolute;
  inset: -11px;
}
@media (max-width: 520px), (pointer: coarse) and (max-height: 500px) {
  .board-col--date .board-day-add-btn {
    margin-left: auto;
    margin-right: 11px;
    align-self: center;
  }
}

/* ── After saving: the new session glows briefly ─────────────────── */
@keyframes ecp-just-saved {
  0%, 60% { box-shadow: 0 0 0 3px #2DD4BF, 0 0 0 7px rgba(45, 212, 191, 0.25); }
  100% { box-shadow: 0 0 0 0 rgba(45, 212, 191, 0); }
}
.board-just-saved .board-cell__pill,
.board-subsquad-chip.board-just-saved {
  animation: ecp-just-saved 2s ease-out;
}
@media (prefers-reduced-motion: reduce) {
  .board-just-saved .board-cell__pill,
  .board-subsquad-chip.board-just-saved { animation: none; box-shadow: 0 0 0 3px #2DD4BF; }
}

/* ── Self-contained dark variant of the sheets ───────────────────── */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .ecp-sheet {
    --sheet-text: #e5edf7;
    --sheet-muted: #a5b4c8;
    --sheet-link: #5eead4;
    --sheet-danger: #f87171;
    --sheet-card: rgba(255, 255, 255, 0.06);
    --sheet-edge: rgba(148, 180, 220, 0.22);
    --sheet-field: rgba(148, 180, 220, 0.12);
    --sheet-chip: rgba(148, 180, 220, 0.16);
    --sheet-chip-text: #dbe7f5;
    background: rgba(20, 28, 42, 0.97);
    color-scheme: dark;
  }
  :root:not([data-theme="light"]) .ecp-sheet .share-row__count--new { color: #fbbf24; }
  :root:not([data-theme="light"]) .ecp-sheet .ecp-field::placeholder,
  :root:not([data-theme="light"]) .ecp-sheet .ecp-row__input::placeholder { color: #8293a9; }
}
:root[data-theme="dark"] .ecp-sheet {
  --sheet-text: #e5edf7;
  --sheet-muted: #a5b4c8;
  --sheet-link: #5eead4;
  --sheet-danger: #f87171;
  --sheet-card: rgba(255, 255, 255, 0.06);
  --sheet-edge: rgba(148, 180, 220, 0.22);
  --sheet-field: rgba(148, 180, 220, 0.12);
  --sheet-chip: rgba(148, 180, 220, 0.16);
  --sheet-chip-text: #dbe7f5;
  background: rgba(20, 28, 42, 0.97);
  color-scheme: dark;
}
:root[data-theme="dark"] .ecp-sheet .share-row__count--new { color: #fbbf24; }

/* Brief 48 Stage 2: amber means "Not shared". The corner label on any
   unshared board pill, and the sub-squad bar's badge, read "Not shared"
   on a small amber chip — legible on every session-type colour. */
.board-cell--draft .board-cell__pill::after {
  padding: 0 0.25rem;
  border-radius: 3px;
  background: #FDE9C8;
  color: #8a4f09;
  opacity: 1 !important;
  line-height: 1.35;
}
.board-subsquad-bar__draft-badge {
  background: #FDE9C8;
  color: #8a4f09;
  border-color: rgba(180, 105, 14, 0.45);
  opacity: 1;
}

/* Brief 48 Stage 2 review: Save in the sheet header (top right, iOS
   pattern) so the phone keyboard can't cover it; the "Editing: …" line;
   and [hidden] always wins inside the sheets (several of their elements
   set their own display). */
.ecp-sheet [hidden] { display: none !important; }
.ecp-btn-head {
  min-height: 44px;
  min-width: 64px;
  padding: 0 16px;
  border: 0;
  border-radius: 22px;
  background: #2DD4BF;
  color: #0b3b36;
  font-size: 0.95rem;
  font-weight: 700;
  white-space: nowrap;
  cursor: pointer;
}
.ecp-btn-head:disabled { opacity: 0.45; cursor: default; }
.ecp-btn-head:focus-visible { outline: 2px solid #0f766e; outline-offset: 2px; }
.ecp-btn-text--block { display: block; width: 100%; margin-top: 4px; }
.ecp-btn-text:disabled { opacity: 0.45; cursor: default; }
.ecp-sheet__editing {
  margin: 0 2px 6px;
  font-size: 0.82rem;
  line-height: 1.4;
  color: var(--sheet-muted);
  overflow-wrap: anywhere;
}
.ecp-field:disabled { opacity: 0.6; }
.ecp-sheet .ecp-sheet__header { padding-right: 12px; }
/* Cancel | title | Save changes: let the two buttons take what they
   need and the title the rest, so "Save changes" never squeezes. */
#add-session-sheet .ecp-sheet__header { grid-template-columns: auto minmax(0, 1fr) auto; }
#add-session-sheet .ecp-sheet__title { overflow: hidden; text-overflow: ellipsis; }

/* Brief 48 Stage 1 review: the athlete tab bar has four tabs (Training
   Plan · Squad plan · Training History · Race Calendar). On a phone they
   share the row evenly and all fit at 375px with no sideways scroll, and
   each is a full 44px tap target. Last in the file so it wins over the
   phone block's flex-shrink: 0. */
@media (max-width: 430px) {
  .nav-tabs-bar {
    gap: 0;
  }
  .nav-tabs-bar__tab {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 44px;
    padding: 0.6rem 0.3rem;
    font-size: 0.8rem;
    text-align: center;
    display: flex;
    align-items: center;
    justify-content: center;
  }
}

/* Narrow phones (360px, e.g. many Androids): with Bootstrap's system font
   "Training History" was clipped. A touch smaller text and side padding
   fits all four on one line; if a wider system font still doesn't fit,
   a label wraps onto two lines inside its 44px tab rather than being cut. */
@media (max-width: 370px) {
  .nav-tabs-bar__tab {
    padding: 0.5rem 0.15rem;
    font-size: 0.75rem;
    line-height: 1.15;
    white-space: normal;
  }
}

/* ==================================================================== *
 * Brief 48 Stage 3 — athlete chips ("Where they train"), the Athlete   *
 * page link, the other coach's plan tag, Week | Month, and the Month   *
 * view (templates/coach/board_month.html). "The look": see-through    *
 * panels on the coach blue, light-blue segmented controls with a white *
 * selected slider, teal only for the chosen thing and links, amber for *
 * "Not shared". 44px tap targets; no sideways scroll at 375px.         *
 * ==================================================================== */

/* ── Chips: a thin divider, then quieter chips for elsewhere ───────── */
.board-chip-divider {
  flex: 0 0 auto;
  align-self: center;
  width: 1px;
  height: 22px;
  margin: 0 4px;
  background: rgba(47, 95, 158, 0.28);
}
.board-pill--elsewhere:not(.board-pill--open) {
  background: transparent;
  border-color: #b6c0ca;
  color: #6b7785;
  font-weight: 500;
}
.board-phone-segment.board-phone-segment--elsewhere:not([aria-selected="true"]) {
  background: transparent;
  border-color: #b6c0ca;
  color: #6b7785;
  font-weight: 500;
}

/* ── "<name> · Athlete page ›" under the chips ───────────────────── */
.board-athlete-link-row {
  display: flex;
  align-items: center;
  padding: 0 0.75rem;
}
.board-athlete-link-row[hidden] { display: none; }
.board-athlete-link {
  display: inline-block;
  line-height: 44px;
  color: #0f766e;
  font-size: 0.86rem;
  font-weight: 600;
  text-decoration: none;
  max-width: 100%;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.board-athlete-link:hover { text-decoration: underline; }
@media (max-width: 520px) {
  .board-athlete-link-row { padding: 0 0.5rem; }
}

/* ── A coached-elsewhere athlete's own entries: "Mia's plan" ─────── */
.board-cell__plan-tag {
  display: block;
  margin: 0 0 2px;
  font-size: 0.66rem;
  font-weight: 600;
  letter-spacing: 0.02em;
  color: #5b6b7c;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ── Segmented control (Week | Month) ─────────────────────────────── */
.board-header__month-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}
.ecp-seg {
  display: inline-flex;
  padding: 2px;
  border-radius: 10px;
  background: rgba(47, 95, 158, 0.10);
}
.ecp-seg__opt {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 64px;
  min-height: 44px;
  padding: 0 12px;
  border-radius: 8px;
  color: #1e3a5f;
  font-size: 0.86rem;
  font-weight: 500;
  text-decoration: none;
  position: relative;
}
a.ecp-seg__opt:hover { color: #0f766e; }
.ecp-seg__opt.is-selected {
  background: #fff;
  box-shadow: 0 1px 3px rgba(26, 35, 50, 0.15);
  font-weight: 600;
  color: #1a2332;
}
/* In the board's slim header: 44px to hit, slimmer to look at. */
.ecp-seg--small .ecp-seg__opt { min-height: 30px; min-width: 56px; font-size: 0.8rem; }
.ecp-seg--small .ecp-seg__opt::after { content: ''; position: absolute; left: 0; right: 0; }
.ecp-seg--small .ecp-seg__opt::after { top: -7px; bottom: -7px; }

/* ── Month view ───────────────────────────────────────────────────── */
.month-page {
  box-sizing: border-box;
  max-width: 1280px;
  margin: 0 auto;
  padding: 8px 16px 32px;
  color: #1a2332;
}
.month-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  flex-wrap: wrap;
  margin-bottom: 8px;
}
.month-head__nav { display: flex; align-items: center; gap: 2px; min-width: 0; }
.month-head__month {
  margin: 0;
  font-size: 1.15rem;
  font-weight: 700;
  color: #0f766e;
  white-space: nowrap;
}
.month-step {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  border-radius: 10px;
  color: #1e3a5f;
  font-size: 1.6rem;
  line-height: 1;
  text-decoration: none;
}
.month-step:hover { background: rgba(47, 95, 158, 0.10); color: #0f766e; }

.month-body { display: block; }
.month-side { display: none; }
.month-main { min-width: 0; }

.month-chips {
  display: flex;
  gap: 6px;
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  padding: 2px 0 8px;
  margin: 0 -16px 0 0;
}
.month-chips::-webkit-scrollbar { display: none; }
.month-chip {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  padding: 0 14px;
  border-radius: 22px;
  background: rgba(47, 95, 158, 0.10);
  color: #1e3a5f;
  font-size: 0.9rem;
  font-weight: 500;
  text-decoration: none;
  white-space: nowrap;
}
.month-chip.is-selected { background: #2DD4BF; color: #0b3b36; font-weight: 600; }
.month-chip--elsewhere:not(.is-selected) {
  background: transparent;
  box-shadow: inset 0 0 0 1px #b6c0ca;
  color: #6b7785;
}
.month-dot {
  display: inline-block;
  flex: 0 0 auto;
  width: 9px;
  height: 9px;
  border-radius: 50%;
  margin-right: 6px;
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.12);
}
.month-chip .month-dot { margin-right: 0; }
.month-who-note { margin: 0 0 8px; font-size: 0.84rem; color: #475569; }

.month-grid {
  display: grid;
  grid-template-columns: repeat(7, minmax(0, 1fr));
  gap: 3px;
}
.month-dow {
  padding: 2px 0 4px;
  text-align: center;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: #5b6b7c;
}
.month-day {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  min-height: 72px;
  padding: 3px;
  border-radius: 8px;
  background: rgba(255, 255, 255, 0.5);
  box-shadow: inset 0 0 0 0.5px rgba(47, 95, 158, 0.14);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  color: inherit;
  text-decoration: none;
  overflow: hidden;
}
.month-day:hover { box-shadow: inset 0 0 0 1px rgba(15, 118, 110, 0.45); }
.month-day:focus-visible { outline: 2px solid #0f766e; outline-offset: 1px; }
.month-day--weekend { background: rgba(214, 226, 242, 0.62); }
.month-day--empty { opacity: 0.55; }
.month-day--out { opacity: 0.4; }
.month-day--today { box-shadow: inset 0 0 0 2px #0f766e; opacity: 1; }
.month-day__head { display: flex; align-items: center; justify-content: space-between; }
.month-day__num { font-size: 0.78rem; font-weight: 600; color: #33404d; padding-left: 2px; }
.month-day--today .month-day__num { color: #0f766e; font-weight: 700; }
.month-day__draft {
  width: 7px;
  height: 7px;
  margin-right: 2px;
  border-radius: 50%;
  background: #f59e0b;
}
.month-tile {
  display: block;
  min-width: 0;
  padding: 1px 4px;
  border-radius: 4px;
  background: rgba(47, 95, 158, 0.12);
  color: #1e3a5f;
  font-size: 0.66rem;
  line-height: 1.35;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  box-shadow: inset 2px 0 0 var(--gc, transparent);
}
.month-tile__label { font-weight: 700; }
.month-tile--inherited { opacity: 0.6; }
.month-tile--own .month-tile__label,
.month-tile--their-coach .month-tile__label { font-style: italic; }
.month-more { font-size: 0.66rem; color: #5b6b7c; padding-left: 2px; }

/* A phone's day is ~45px wide: a labelled tile shows just its label
   ("G1", "Own"); the colour says the rest. */
@media (max-width: 599px) {
  .month-tile--labelled .month-tile__text { display: none; }
  .month-tile__label-long { display: none; }
}
@media (min-width: 600px) {
  .month-tile__label-short { display: none; }
}
@media (min-width: 600px) {
  .month-day { min-height: 92px; padding: 4px; }
  .month-tile { font-size: 0.72rem; padding: 2px 5px; }
}

@media (min-width: 900px) {
  .month-body {
    display: grid;
    grid-template-columns: 200px minmax(0, 1fr);
    gap: 16px;
    align-items: start;
  }
  .month-side {
    display: flex;
    flex-direction: column;
    gap: 2px;
    position: sticky;
    top: 8px;
    padding: 8px;
    border-radius: 12px;
    background: rgba(255, 255, 255, 0.5);
    box-shadow: inset 0 0 0 0.5px rgba(47, 95, 158, 0.14);
  }
  .month-side__sec {
    margin: 8px 6px 2px;
    font-size: 0.7rem;
    font-weight: 700;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: #5b6b7c;
  }
  .month-side__sec:first-child { margin-top: 2px; }
  .month-side__item {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    min-height: 44px;
    padding: 4px 10px;
    border-radius: 8px;
    color: #1e3a5f;
    font-size: 0.9rem;
    text-decoration: none;
  }
  .month-side__item:hover { background: rgba(47, 95, 158, 0.10); }
  .month-side__item.is-selected { background: #2DD4BF; color: #0b3b36; font-weight: 600; }
  .month-side__item--elsewhere:not(.is-selected) { color: #6b7785; }
  .month-side__note { flex-basis: 100%; font-size: 0.74rem; color: #6b7785; }
  .month-chips { display: none; }
  .month-day { min-height: 112px; }
}

/* ==================================================================== *
 * Brief 49 — what athletes can use.                                     *
 *                                                                      *
 * .coming-soon   — the calm card a feature shows while it's switched   *
 *                  off for an athlete (components/coming_soon.html).   *
 * .feature-card  — the admin card "What athletes can use" and the      *
 *                  per-athlete switches on the athlete page (Profile). *
 * .ecp-switch    — an on/off switch (a real submit button, role=switch)*
 *                                                                      *
 * Brief 48 "the look": see-through panels (rgba(255,255,255,.5), a fine *
 * inset edge, blur), light-blue tint for what isn't chosen, teal only  *
 * for what's on. Pages stay light in OS dark mode (only the sheets    *
 * carry their own dark variant), so these sit on a light page always. *
 * ==================================================================== */
.coming-soon {
  display: flex;
  align-items: flex-start;
  gap: 14px;
  margin: 12px 0 20px;
  padding: 18px 18px;
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.5);
  box-shadow: inset 0 0 0 0.5px rgba(47, 95, 158, 0.14);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  color: #1a2332;
}
.coming-soon__icon {
  flex: 0 0 40px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: rgba(47, 95, 158, 0.10);
  color: #1e3a5f;
  font-size: 1.15rem;
}
.coming-soon__text {
  margin: 0;
  font-size: 0.98rem;
  line-height: 1.45;
  color: #1a2332;
}

.feature-card {
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.5);
  box-shadow: inset 0 0 0 0.5px rgba(47, 95, 158, 0.14);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  overflow: hidden;
  max-width: 680px;
}
.feature-row {
  padding: 12px 16px;
  color: #1a2332;
}
.feature-row + .feature-row { box-shadow: inset 0 0.5px 0 rgba(47, 95, 158, 0.18); }
.feature-row__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  min-height: 44px;
}
.feature-row__name { font-weight: 600; font-size: 0.98rem; }
.feature-row__tag {
  display: inline-block;
  margin-left: 6px;
  padding: 1px 8px;
  border-radius: 999px;
  background: rgba(47, 95, 158, 0.10);
  color: #1e3a5f;
  font-size: 0.74rem;
  font-weight: 600;
  vertical-align: 1px;
}
.feature-row__always { color: #475569; font-size: 0.9rem; white-space: nowrap; }
.feature-row__state { font-size: 0.9rem; color: #1e3a5f; font-weight: 500; }
.feature-row__note { margin: 2px 0 0; font-size: 0.85rem; color: #475569; line-height: 1.4; }
.feature-row__form { display: flex; align-items: center; gap: 8px; margin: 0; flex-shrink: 0; }
.feature-row__switch-label { font-size: 0.85rem; color: #475569; }
.feature-row__readonly { font-size: 0.9rem; color: #475569; white-space: nowrap; }
.feature-row__warning { margin: 4px 0 0; font-size: 0.85rem; color: #8a4f09; line-height: 1.4; }
.feature-card__foot { margin: 8px 2px 0; font-size: 0.82rem; color: #475569; max-width: 680px; }

/* The switch: 51×31 like iOS, inside a 44px-high tap target. */
.ecp-switch {
  position: relative;
  flex: 0 0 auto;
  width: 51px;
  height: 44px;
  padding: 0;
  border: 0;
  background: transparent;
  cursor: pointer;
}
.ecp-switch::before {
  content: "";
  position: absolute;
  left: 0;
  top: 6.5px;
  width: 51px;
  height: 31px;
  border-radius: 999px;
  background: rgba(47, 95, 158, 0.18);
  transition: background-color 180ms ease-out;
}
.ecp-switch__thumb {
  position: absolute;
  top: 8.5px;
  left: 2px;
  width: 27px;
  height: 27px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 2px 4px rgba(15, 23, 42, 0.22);
  transition: transform 180ms ease-out;
}
.ecp-switch[aria-checked="true"]::before { background: #0f766e; }
.ecp-switch[aria-checked="true"] .ecp-switch__thumb { transform: translateX(20px); }
.ecp-switch:focus-visible { outline: none; }
.ecp-switch:focus-visible::before { box-shadow: 0 0 0 3px rgba(15, 118, 110, 0.35); }
.ecp-switch:disabled { cursor: default; opacity: 0.45; }
@media (prefers-reduced-motion: reduce) {
  .ecp-switch::before, .ecp-switch__thumb { transition: none; }
}

/* Brief 49: the watch line on today's Today card. */
.tpv2-today__watch-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  margin-top: 4px;
  color: #0f766e;
  font-weight: 600;
  text-decoration: none;
}

/* ==================================================================== *
 * Brief 42 — squad onboarding: the public join page, "Waiting to join" *
 * on Squad Overview, the Join link and Guests cards on Manage squads & *
 * groups, the first-login welcome, and the guest page.                 *
 *                                                                      *
 * Brief 48 "the look": coach blue (#EAF0F8) behind see-through panels  *
 * (rgba(255,255,255,.5), a fine inset edge, blur), soft blue fields,   *
 * light-blue tint for what isn't chosen, teal only for the main button,*
 * links and what's chosen. Every tap target ≥ 44px, every input 16px.  *
 * ==================================================================== */
.join-page,
.welcome-page,
.guest-page {
  min-height: calc(100vh - var(--sticky-nav-height));
  margin: 0 calc(var(--bs-gutter-x, 1.5rem) * -0.5);
  padding: 20px 16px 40px;
  background: #EAF0F8;
}
.join-card,
.onb-card {
  max-width: 560px;
  margin: 0 auto 14px;
  padding: 20px 18px;
  border-radius: 14px;
  background: rgba(255, 255, 255, 0.5);
  box-shadow: inset 0 0 0 0.5px rgba(47, 95, 158, 0.14);
  -webkit-backdrop-filter: blur(12px);
  backdrop-filter: blur(12px);
  color: #1a2332;
}
.join-title { font-size: 1.35rem; font-weight: 700; margin: 0 0 2px; }
.join-lead { margin: 0 0 18px; color: #475569; }
.join-message { margin: 0; font-size: 1.05rem; line-height: 1.5; }
.join-section { font-size: 0.8rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  color: #475569; margin: 22px 0 8px; }
.join-field { margin: 0 0 14px; border: 0; padding: 0; min-width: 0; }
.join-field__label { display: block; font-weight: 600; font-size: 0.95rem; margin: 0 0 6px; padding: 0; }
.join-field__hint { margin: 4px 0 0; font-size: 0.85rem; color: #475569; }
.join-field__error { margin: 6px 0 0; font-size: 0.9rem; color: #b42318; font-weight: 600; }
.join-input,
.onb-input {
  display: block;
  width: 100%;
  min-height: 44px;
  padding: 10px 12px;
  border: 0;
  border-radius: 10px;
  background: rgba(47, 95, 158, 0.08);
  font-size: 16px;
  color: #1a2332;
}
.join-input:focus,
.onb-input:focus { outline: 2px solid #0f766e; outline-offset: 1px; background: #fff; }
.join-choice { display: grid; grid-template-columns: 1fr; gap: 8px; }
.join-choice__option {
  display: flex; align-items: center; gap: 10px; min-height: 48px; padding: 10px 14px;
  border-radius: 12px; background: rgba(47, 95, 158, 0.10); color: #1e3a5f; font-weight: 600; cursor: pointer;
}
.join-choice__option input { width: 20px; height: 20px; accent-color: #0f766e; flex: 0 0 auto; }
.join-choice__option:has(input:checked) { background: #fff; box-shadow: inset 0 0 0 2px #0f766e; color: #0f766e; }
.join-dob { display: grid; grid-template-columns: 1fr 1fr 1.6fr; gap: 8px; }
.join-dob__label { display: block; font-size: 0.8rem; color: #475569; margin-bottom: 2px; }
.join-hp { position: absolute !important; left: -10000px; width: 1px; height: 1px; overflow: hidden; }
.join-privacy { margin: 18px 0 12px; font-size: 0.88rem; color: #475569; line-height: 1.45; }
.join-privacy a, .onb-link { color: #0f766e; font-weight: 600; }
.join-button,
.onb-button {
  display: inline-flex; align-items: center; justify-content: center;
  width: 100%; min-height: 48px; padding: 10px 18px; border: 0; border-radius: 12px;
  background: #0f766e; color: #fff; font-weight: 700; font-size: 1rem; text-decoration: none;
}
.join-button:hover, .onb-button:hover { background: #0b5f59; color: #fff; }
.onb-button--small { width: auto; min-height: 44px; font-size: 0.95rem; }
.onb-button--quiet {
  background: rgba(47, 95, 158, 0.10); color: #1e3a5f;
}
.onb-button--quiet:hover { background: rgba(47, 95, 158, 0.18); color: #1e3a5f; }
.onb-button--danger { background: transparent; color: #b42318; box-shadow: inset 0 0 0 1px rgba(180, 35, 24, 0.45); }
.onb-button--danger:hover { background: rgba(180, 35, 24, 0.08); color: #b42318; }
.onb-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 10px; }
.onb-actions > * { flex: 1 1 auto; }
.onb-muted { color: #475569; font-size: 0.9rem; }
.onb-heading { font-size: 1rem; font-weight: 700; margin: 0 0 6px; }

/* "Waiting to join" on Squad Overview */
.waiting-section { margin: 0 0 20px; }
.waiting-section__title { display: flex; align-items: center; gap: 8px; font-size: 0.85rem; font-weight: 700;
  letter-spacing: .04em; text-transform: uppercase; color: #475569; margin: 0 0 8px; }
.onb-badge {
  display: inline-flex; align-items: center; justify-content: center; min-width: 22px; height: 22px;
  padding: 0 7px; border-radius: 999px; background: #0f766e; color: #fff; font-size: 0.78rem; font-weight: 700;
}
.waiting-card { max-width: 680px; margin: 0 0 10px; }
.waiting-card__name { font-weight: 700; font-size: 1.05rem; }
.waiting-card__meta { font-size: 0.9rem; color: #475569; margin-top: 2px; }
.waiting-card__marker {
  display: inline-block; margin-left: 6px; padding: 1px 8px; border-radius: 999px;
  background: rgba(47, 95, 158, 0.10); color: #1e3a5f; font-size: 0.75rem; font-weight: 600;
}
.waiting-card__parent {
  margin-top: 10px; padding: 10px 12px; border-radius: 10px;
  background: rgba(255, 255, 255, 0.7); box-shadow: inset 3px 0 0 #0f766e;
}
.waiting-card__parent-name { font-weight: 700; }
.waiting-card__hint { margin: 4px 0 0; font-size: 0.88rem; color: #1e3a5f; font-weight: 600; }
.waiting-card__panel { margin-top: 10px; }
.waiting-card__panel[hidden] { display: none; }

/* Join link + Guests cards (Manage squads & groups) */
.join-link-box {
  display: flex; gap: 8px; align-items: stretch; margin: 8px 0 4px;
}
.join-link-box .onb-input { flex: 1 1 auto; min-width: 0; font-size: 16px; }
.join-link-box .onb-button { flex: 0 0 auto; width: auto; }
.guest-row { padding: 10px 0; }
.guest-row + .guest-row { box-shadow: inset 0 0.5px 0 rgba(47, 95, 158, 0.18); }
.guest-row__name { font-weight: 600; }
.guest-row__state { font-size: 0.88rem; color: #475569; }

/* First-login welcome */
.welcome-card { text-align: left; }
.welcome-card h1 { font-size: 1.45rem; font-weight: 700; margin: 0 0 10px; }
.welcome-card p { margin: 0 0 12px; line-height: 1.5; }
.welcome-card h2 { font-size: 0.95rem; font-weight: 700; margin: 16px 0 4px; }

/* Guest page */
.guest-day { max-width: 560px; margin: 0 auto 10px; }
.guest-day__date { font-size: 1.05rem; font-weight: 700; margin: 0 0 6px; }
.guest-session { padding: 8px 0 8px 12px; box-shadow: inset 4px 0 0 var(--gc, #2f5f9e); margin: 6px 0; }
.guest-session__group { font-size: 0.8rem; font-weight: 700; color: #1e3a5f; }
.guest-session__text { margin: 2px 0 0; }
.guest-session__when { font-size: 0.85rem; color: #475569; }
summary.onb-button { list-style: none; cursor: pointer; }
summary.onb-button::-webkit-details-marker { display: none; }
.waiting-card__panel-wrap[open] > summary { margin-bottom: 6px; }
.app-shell-bar__count {
  position: absolute; top: 4px; right: 2px; min-width: 18px; height: 18px; padding: 0 5px;
  border-radius: 999px; background: #0f766e; color: #fff; font-size: 0.7rem; font-weight: 700;
  line-height: 18px; text-align: center; box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.85);
}
.app-shell-bar__menu-trigger { position: relative; }
.app-shell-menu__count {
  display: inline-flex; align-items: center; justify-content: center; min-width: 20px; height: 20px;
  margin-left: 6px; padding: 0 6px; border-radius: 999px; background: #0f766e; color: #fff;
  font-size: 0.74rem; font-weight: 700;
}
/* Not amber: amber means "Not shared" (brief 48). A changed request is a
   caution, so the danger red marks it. */
.waiting-card__changed { margin-top: 10px; padding: 10px 12px; border-radius: 10px; background: rgba(255, 255, 255, 0.75);
  box-shadow: inset 3px 0 0 #b42318; }
.waiting-card__changed .waiting-card__hint { color: #b42318; margin: 0 0 6px; }
.waiting-card__versions { display: grid; grid-template-columns: 1fr 1fr; gap: 10px; font-size: 0.86rem; overflow-wrap: anywhere; }

/* ── Brief 58 (plan_v3): Training Plan and Squad plan updates ─────────
   Only reached when the switch is on — every class below is added by the
   templates under `plan_v3`, so with it off nothing here applies. */

/* Item 49: Week view pills show the session itself — wrapping, clamped
   to three lines (the full text is still in the tap-to-reveal). */
.tpv2-pill--text {
  align-items: flex-start;
  padding: 0.35rem 0.5rem;
  line-height: 1.3;
}

.tpv2-pill--text .tpv2-pill__body {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1 1 auto;
}

.tpv2-pill--text .tpv2-pill__label {
  white-space: normal;
  overflow: hidden;
  text-overflow: clip;
  display: -webkit-box;
  -webkit-box-orient: vertical;
  -webkit-line-clamp: 3;
  line-clamp: 3;
  overflow-wrap: anywhere;
}

.tpv2-pill--text .tpv2-pill__icons {
  margin-top: 0.1rem;
}

/* Item 50: a group's session for an athlete not yet in a group — a
   dashed edge and a quiet note, so it reads as "one of these", not
   "yours". */
.tpv2-pill--option {
  outline: 1px dashed rgba(26, 35, 50, 0.45);
  outline-offset: -1px;
}

.tpv2-pill__option {
  display: block;
  margin-top: 0.15rem;
  font-size: 0.72rem;
  opacity: 0.85;
}

.tpv2-pill__option-group {
  font-weight: 700;
}

/* Items 28 and 29: the same icons on the Squad plan. */
.squad-plan__days--v3 .squad-plan__group-pill {
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
  padding: 0.3rem 0.5rem;
}

.squad-plan__group-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.4rem;
}

.squad-plan__icons {
  display: inline-flex;
  align-items: center;
  gap: 0.15rem;
  flex: 0 0 auto;
}

/* Item 30: time and venue. */
.squad-plan__when {
  font-size: 0.75rem;
  font-weight: 500;
  opacity: 0.85;
}

/* Item 31: empty days stay (the week reads evenly) but recede. */
.squad-plan__days--v3 .squad-plan__day--empty {
  padding-top: 0.15rem;
  padding-bottom: 0.15rem;
  opacity: 0.55;
}

.squad-plan__days--v3 .squad-plan__day--empty .squad-plan__dm {
  font-size: 0.75rem;
  font-weight: 500;
}

.squad-plan__days--v3 .squad-plan__day--empty .squad-plan__dow {
  font-size: 0.68rem;
}

/* ----------------------------------------------------------------------
   Race Calendar, 7 Nov 2026 (items 33–47, on top of the redesign; all
   under .fx2r-* so the switch-off Race Calendar is untouched).
   Teal = go/done in ECP: #2DD4BF fill, #0b7a6d ink.
   ---------------------------------------------------------------------- */

/* Item 33: a small teal tick on races already in the athlete's plan. */
.fx2r-tick {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 50%;
  background: rgba(45, 212, 191, 0.28);
  box-shadow: inset 0 0 0 1.5px #2DD4BF;
  color: #0b7a6d;
  font-size: 0.85rem;
  font-weight: 700;
  line-height: 1;
}

.fx2r-tick--past {
  opacity: 0.55;
}

.fx2r-tick--legend {
  width: 1.1rem;
  height: 1.1rem;
  font-size: 0.65rem;
  vertical-align: -0.15rem;
}

.fx2r-legend {
  font-size: 0.8rem;
  color: #475569;
  margin: -0.35rem 0 0.6rem;
}

/* Items 34 and 35: the chip row sticks under the title bar (the same
   sticky offsets as .sticky-page-header, plus that bar's measured
   height), and the chips are compact and wrap rather than running off
   the right edge. */
.fx2-filter-chips.fx2r-chips {
  position: sticky;
  top: calc(var(--sticky-nav-height) + var(--sticky-tabs-height) + var(--fx2r-page-header-height, 3.25rem));
  z-index: 999;
  flex-wrap: wrap;
  overflow-x: visible;
  gap: 0.25rem;
  margin: -0.5rem 0 0.75rem;
  padding: 0.4rem 0 0.5rem;
}

body.role-coach   .fx2-filter-chips.fx2r-chips { background: #EAF0F8; }
body.role-athlete .fx2-filter-chips.fx2r-chips { background: #DCE6F0; }

.fx2r-chips .fx2-chip {
  font-size: 0.78rem;
  padding: 0.3rem 0.45rem;
  min-height: 2rem;
  border-width: 1px;
  border-radius: 999px;
}

/* Item 47: the own-race form sits in the panel, in the page — not a
   floating popover over the chips. */
.fx2r-add-panel .entry-editor-popover {
  position: static;
  width: auto;
  padding: 0;
  background: transparent;
  border: 0;
  box-shadow: none;
}

.fx2r-add-panel .form-control,
.fx2r-add-panel .form-select {
  min-height: 2.5rem;
  font-size: 1rem; /* 16px, so iOS doesn't zoom in on focus */
}

/* Item 39: "My key race" as a clear on/off switch. */
.fx2r-form-switch {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding-left: 0;
}

.fx2r-form-switch .form-check-input {
  float: none;
  margin: 0;
  width: 2.75rem;
  height: 1.6rem;
}

.fx2r-form-switch .form-check-input:checked {
  background-color: #0f766e;
  border-color: #0f766e;
}

.fx2r-keyrow {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  margin: 0.25rem 0 0.5rem;
}

.fx2r-keyrow__label {
  font-weight: 500;
  color: #1a2332;
}

.fx2r-keyrow__state {
  margin-left: auto;
  font-size: 0.85rem;
  color: #475569;
}

/* Item 41: "✓ In your plan" in ECP teal, not Bootstrap green. */
.fx2r-inplan {
  display: inline-block;
  padding: 0.15rem 0.6rem;
  border-radius: 999px;
  background: rgba(45, 212, 191, 0.22);
  box-shadow: inset 0 0 0 1px #2DD4BF;
  color: #0b7a6d;
  font-weight: 600;
}

/* Item 38: "More options" is quiet. */
.fx2r-quiet-link {
  color: #475569;
  font-size: 0.85rem;
  text-decoration: underline;
  text-underline-offset: 2px;
}

/* Item 47: "Save race" in the own-race panel is ECP teal (go/done), like
   "Add to my plan", not Bootstrap green. */
.fx2r-add-panel #race-add-submit {
  background: #0f766e;
  border-color: #0f766e;
  color: #fff;
}

/* 7 Nov: the selected race-type chip is ECP teal (go/done), not Bootstrap
   blue, on the redesigned Race Calendar. */
.fx2r-chips .fx2-chip.active,
.fx2r-chips .fx2-chip.active:hover,
.fx2r-chips .fx2-chip.active:focus { background: #2DD4BF; border-color: #2DD4BF; color: #04201d; }

/* ==================================================================== *
 * Phone test fixes (8 Nov, brief 58) — athlete tabs as pills, behind   *
 * plan_v3 (templates/athlete/_training_nav_tabs.html passes the        *
 * .nav-tabs-bar--pills modifier). Each tab is a rounded pill with a    *
 * light fill so it looks tappable; the current one is ECP teal. If     *
 * they don't fit, the inner row scrolls sideways on its own (never the *
 * page) and fades at the edge where there's more. Last in the file so  *
 * it wins over the phone rules above. Coach tab bars never get it.     *
 * ==================================================================== */
.nav-tabs-bar.nav-tabs-bar--pills {
  display: block;
  overflow: visible;
  min-height: 0;
  border-bottom: 0;
  padding: 0.45rem 0;
}

.nav-tabs-bar--pills .nav-tabs-bar__scroll {
  position: relative;
  display: flex;
  flex-wrap: nowrap;
  gap: 0.4rem;
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
  overscroll-behavior-x: contain;
}

.nav-tabs-bar--pills .nav-tabs-bar__scroll::-webkit-scrollbar { display: none; }

.nav-tabs-bar--pills .nav-tabs-bar__scroll.is-more-right {
  -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 2.5rem), transparent);
  mask-image: linear-gradient(to right, #000 calc(100% - 2.5rem), transparent);
}

.nav-tabs-bar--pills .nav-tabs-bar__scroll.is-more-left {
  -webkit-mask-image: linear-gradient(to left, #000 calc(100% - 2.5rem), transparent);
  mask-image: linear-gradient(to left, #000 calc(100% - 2.5rem), transparent);
}

.nav-tabs-bar--pills .nav-tabs-bar__scroll.is-more-left.is-more-right {
  -webkit-mask-image: linear-gradient(to right, transparent, #000 2.5rem, #000 calc(100% - 2.5rem), transparent);
  mask-image: linear-gradient(to right, transparent, #000 2.5rem, #000 calc(100% - 2.5rem), transparent);
}

.nav-tabs-bar--pills .nav-tabs-bar__tab {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 0;
  min-height: 40px;
  margin: 0;
  padding: 0 0.95rem;
  border: 1px solid rgba(47, 95, 158, 0.22);
  border-radius: 999px;
  background: rgba(47, 95, 158, 0.10);
  color: #1a2332;
  font-size: 0.85rem;
  font-weight: 600;
  line-height: 1.2;
  white-space: nowrap;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}

@media (hover: hover) {
  .nav-tabs-bar--pills .nav-tabs-bar__tab:hover {
    background: rgba(47, 95, 158, 0.18);
    color: #1a2332;
  }
}

.nav-tabs-bar--pills .nav-tabs-bar__tab:focus { outline: none; }
.nav-tabs-bar--pills .nav-tabs-bar__tab:focus-visible {
  outline: 2px solid #2F5F9E;
  outline-offset: 2px;
}

.nav-tabs-bar--pills .nav-tabs-bar__tab.nav-tabs-bar__tab--active,
.nav-tabs-bar--pills .nav-tabs-bar__tab.nav-tabs-bar__tab--active:hover {
  background: #2DD4BF;
  border-color: #2DD4BF;
  color: #04201d;
}

/* Keep a little room so the first/last pill's focus ring isn't clipped. */
.nav-tabs-bar--pills .nav-tabs-bar__scroll { padding: 2px 3px; }

/* ==================================================================== *
 * Phone test fixes (8 Nov, brief 59) — Race Calendar redesign chips.  *
 * On iPhone a deselected chip stayed grey: Bootstrap's                 *
 * .btn-outline-secondary :hover (sticky after a tap on touch screens), *
 * :focus and :active all paint it grey. Under the redesign an          *
 * unselected chip always looks unselected; keyboards still get a       *
 * visible ring via :focus-visible. Selected stays teal.                *
 * ==================================================================== */
.fx2r-chips .fx2-chip {
  -webkit-tap-highlight-color: transparent;
}

.fx2r-chips .fx2-chip:not(.active):focus,
.fx2r-chips .fx2-chip:not(.active):active,
.fx2r-chips .fx2-chip:not(.active):focus:active {
  background: transparent;
  border-color: var(--bs-secondary, #6c757d);
  color: var(--bs-secondary, #6c757d);
  box-shadow: none;
}

@media (hover: none) {
  .fx2r-chips .fx2-chip:not(.active):hover {
    background: transparent;
    border-color: var(--bs-secondary, #6c757d);
    color: var(--bs-secondary, #6c757d);
  }
}

.fx2r-chips .fx2-chip:focus { box-shadow: none; }
.fx2r-chips .fx2-chip:focus-visible {
  outline: 2px solid #2F5F9E;
  outline-offset: 2px;
  box-shadow: none;
}

.fx2r-chips .fx2-chip.active:focus,
.fx2r-chips .fx2-chip.active:active {
  background: #2DD4BF;
  border-color: #2DD4BF;
  color: #04201d;
}

/* Phone test fixes (8 Nov, brief 59): the today divider names the date
   ("Today, Sat 7 Nov"), in ordinary case so it reads as a date. */
.fx2-list--redesign .fx2-today-divider {
  text-transform: none;
  letter-spacing: 0;
  font-size: 0.82rem;
}
