:root {
  /* Typography. The app ships no font file: the sans stack is whatever the reader's computer
     has (Avenir Next on a Mac, Segoe UI on Windows), the mono stack likewise. To adopt a
     webfont later, add its @font-face rules (or a Google Fonts @import) at the top of this
     file and change these two values -- nothing else reads a family name. The design-system
     package inherits the change on its next build (see .design-sync/NOTES.md, "Fonts"). */
  --font-sans: "Avenir Next", "Segoe UI", "Helvetica Neue", sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

:root,
[data-theme="graphite-amber"] {
  --navy: #12141a;
  --blue: #0f766e;
  --accent: #f5a524;
  --bg: #f4f5f7;
  --card: #ffffff;
  --ink: #15171c;
  --muted: #5c6570;
  --line: #e2e5ea;
  --surface-soft: #eef1f4;
  --evidence: #0d9488;
  --chip-bg: #eef1f4;
  --chip-ink: #1e4f4a;
  --score-bg: #fff6e5;
  --score-ink: #8a5a00;
  --hero-end: #1a2e2b;
  --bg-glow: rgba(245, 165, 36, 0.10);
  --bg-top: #f7f8fa;
  --bg-bottom: #eceff3;
  --drawer-brand: #1a2220;
  --col-header: #0f766e;
  /* Used with no fallback at 18 sites (C-2): the running text colour and the warning hue. */
  /* Foreground-only variants of --accent and --evidence: the hue kept, darkened until it reads
     at 4.5:1 on --card (C-3). Borders, fills and glows keep the originals. */
  --accent-ink: #9d6a17;
  --evidence-ink: #0c857a;
  --text: #15171c;
  --amber: #b45309;
  --shadow: rgba(18, 20, 26, 0.08);
}

[data-theme="cold-steel"] {
  --navy: #0b1220;
  --blue: #0284c7;
  --accent: #38bdf8;
  --bg: #f8fafc;
  --card: #ffffff;
  --ink: #0f172a;
  --muted: #64748b;
  --line: #e2e8f0;
  --surface-soft: #f1f5f9;
  --evidence: #0284c7;
  --chip-bg: #e2e8f0;
  --chip-ink: #0f172a;
  --score-bg: #e0f2fe;
  --score-ink: #075985;
  --hero-end: #1e293b;
  --bg-glow: rgba(56, 189, 248, 0.12);
  --bg-top: #f8fafc;
  --bg-bottom: #eef2f7;
  --drawer-brand: #0f172a;
  --col-header: #0369a1;
  /* Used with no fallback at 18 sites (C-2): the running text colour and the warning hue. */
  /* Foreground-only variants of --accent and --evidence: the hue kept, darkened until it reads
     at 4.5:1 on --card (C-3). Borders, fills and glows keep the originals. */
  --accent-ink: #257da4;
  --evidence-ink: #027cbb;
  --text: #0f172a;
  --amber: #b45309;
  --shadow: rgba(15, 23, 42, 0.08);
}

[data-theme="forest"] {
  --navy: #0f1f17;
  --blue: #166534;
  --accent: #84cc16;
  --bg: #f3f4f2;
  --card: #ffffff;
  --ink: #14201a;
  --muted: #5b6b61;
  --line: #dde3de;
  --surface-soft: #e9eee9;
  --evidence: #15803d;
  --chip-bg: #ecfdf3;
  --chip-ink: #14532d;
  --score-bg: #f7fee7;
  --score-ink: #3f6212;
  --hero-end: #1a3d2b;
  --bg-glow: rgba(132, 204, 22, 0.12);
  --bg-top: #f6f7f4;
  --bg-bottom: #e8ebe6;
  --drawer-brand: #143024;
  --col-header: #166534;
  /* Used with no fallback at 18 sites (C-2): the running text colour and the warning hue. */
  /* Foreground-only variants of --accent and --evidence: the hue kept, darkened until it reads
     at 4.5:1 on --card (C-3). Borders, fills and glows keep the originals. */
  --accent-ink: #54830e;
  --evidence-ink: #15803d;
  --text: #14201a;
  --amber: #b45309;
  --shadow: rgba(15, 31, 23, 0.08);
}

[data-theme="classic"] {
  --navy: #1a1a2e;
  --blue: #1e90ff;
  --accent: #5b9bd5;
  --bg: #f3f5f8;
  --card: #ffffff;
  --ink: #1c2434;
  --muted: #667085;
  --line: #e6eaf0;
  --surface-soft: #f7f9fc;
  --evidence: #3498db;
  --chip-bg: #f0f4ff;
  --chip-ink: #1a1a2e;
  --score-bg: #eef5ff;
  --score-ink: #1e4f9a;
  --hero-end: #2c3e6b;
  --bg-glow: rgba(91, 155, 213, 0.18);
  --bg-top: #eef3f9;
  --bg-bottom: #e8edf5;
  --drawer-brand: #2c3e6b;
  --col-header: #1e90ff;
  /* Used with no fallback at 18 sites (C-2): the running text colour and the warning hue. */
  /* Foreground-only variants of --accent and --evidence: the hue kept, darkened until it reads
     at 4.5:1 on --card (C-3). Borders, fills and glows keep the originals. */
  --accent-ink: #4779a6;
  --evidence-ink: #2a7aaf;
  --text: #1c2434;
  --amber: #b45309;
  --shadow: rgba(26, 26, 46, 0.08);
}

[data-theme="crimson-black"] {
  --navy: #0a0a0a;
  --blue: #b91c1c;
  --accent: #e11d48;
  --bg: #f6f4f4;
  --card: #ffffff;
  --ink: #141010;
  --muted: #6b5f5f;
  --line: #e8e0e0;
  --surface-soft: #f3ecec;
  --evidence: #be123c;
  --chip-bg: #ffe4e6;
  --chip-ink: #9f1239;
  --score-bg: #fff1f2;
  --score-ink: #9f1239;
  --hero-end: #3f0a14;
  --bg-glow: rgba(225, 29, 72, 0.12);
  --bg-top: #faf7f7;
  --bg-bottom: #eee8e8;
  --drawer-brand: #14080a;
  --col-header: #b91c1c;
  /* Used with no fallback at 18 sites (C-2): the running text colour and the warning hue. */
  /* Foreground-only variants of --accent and --evidence: the hue kept, darkened until it reads
     at 4.5:1 on --card (C-3). Borders, fills and glows keep the originals. */
  --accent-ink: #e11d48;
  --evidence-ink: #be123c;
  --text: #141010;
  --amber: #b45309;
  --shadow: rgba(10, 10, 10, 0.10);
}

* { box-sizing: border-box; margin: 0; padding: 0; }
body {
  font-family: var(--font-sans);
  background:
    radial-gradient(circle at 12% 0%, var(--bg-glow), transparent 42%),
    linear-gradient(180deg, var(--bg-top) 0%, var(--bg) 50%, var(--bg-bottom) 100%);
  color: var(--ink);
  min-height: 100vh;
}
.topnav {
  position: sticky;
  top: 0;
  z-index: 300;
  background: var(--navy);
  color: white;
  padding: 10px 24px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px 16px;
  flex-wrap: nowrap;
  border-top: 3px solid var(--accent);
  min-height: 56px;
}
.nav-start {
  display: flex;
  align-items: center;
  flex: 0 0 auto;
  min-width: 0;
}
.topnav a { color: #ccc; text-decoration: none; margin-left: 12px; font-size: 14px; white-space: nowrap; }
.topnav a:hover { color: white; }
.topnav a.nav-active {
  color: white;
  font-weight: 700;
  padding: 6px 12px;
  margin-left: 6px;
  border-radius: 8px;
  border: 1px solid color-mix(in srgb, var(--accent) 70%, white);
  background: color-mix(in srgb, var(--accent) 25%, transparent);
}
.topnav a.nav-active:hover {
  background: var(--accent);
  color: #111;
  border-color: var(--accent);
}
.brand { display: flex; align-items: center; gap: 10px; color: white !important; font-weight: 700; margin-left: 0 !important; }
.nav-links {
  display: flex;
  align-items: center;
  gap: 2px;
  flex-wrap: wrap;
  justify-content: flex-end;
  margin-left: auto;
  min-width: 0;
}

.nav-menu { position: relative; display: inline-block; }
.nav-menu summary { color: #ccc; cursor: pointer; font-size: 14px; margin-left: 12px; white-space: nowrap; list-style: none; }
.nav-menu summary::-webkit-details-marker { display: none; }
.nav-menu summary::after { content: " ▾"; font-size: 11px; }
.nav-menu[open] summary, .nav-menu summary:hover { color: white; }
/* Marks the group containing the current page without forcing its panel open. Highlighting the
   section and expanding the menu are separate concerns; conflating them is what left several
   menus visibly open at once on admin pages.

   Accent-coloured, matching the accent pill a current top-level link gets, so "current" reads as
   one idea in the bar rather than two unrelated effects. Deliberately NOT the full pill: a group
   summary carries a dropdown caret and is a control you open, so boxing it would make it look like
   a destination. Only one of the two ever appears at a time -- see MenuRegistry.BuildMenu, where a
   current top-level link suppresses the group marker. */
.nav-menu.nav-group-active > summary {
  color: var(--accent-ink);
  font-weight: 700;
}
/* The help button: last in the bar, drawn as a circle rather than a word. It is the only nav item
   that is not a section of the product, so it is deliberately not styled like one -- a named link
   beside Coach View and Admin read as a fifth area to explore. Sized to match .fs-help-button, the
   in-page help affordance, so the same idea looks the same wherever it appears. */
.nav-help {
  align-items: center;
  border: 1px solid rgba(255, 255, 255, .35);
  border-radius: 50%;
  color: #ccc;
  display: inline-flex;
  flex: 0 0 auto;
  font-size: 13px;
  font-weight: 700;
  height: 26px;
  justify-content: center;
  line-height: 1;
  margin-left: 12px;
  text-decoration: none;
  width: 26px;
}
.topnav a.nav-help:hover {
  background: rgba(255, 255, 255, .12);
  border-color: white;
  color: white;
}
.topnav a.nav-help-active {
  background: color-mix(in srgb, var(--accent) 25%, transparent);
  border-color: color-mix(in srgb, var(--accent) 70%, white);
  color: white;
}
/* Room around the panel's contents and around each link: a menu whose text sits against its own
   edge reads as cramped, and a link with little padding is a small target. */
.nav-menu-panel { background: #1d2433; border: 1px solid rgba(255,255,255,.18); border-radius: 8px; box-shadow: 0 10px 24px rgba(0,0,0,.3); min-width: 200px; padding: 14px 16px; position: absolute; right: 0; top: 30px; z-index: 20; }
.nav-menu-panel a { border-radius: 6px; box-sizing: border-box; color: #e8edf7; display: block; font: inherit; margin: 0; padding: 10px 14px; text-align: left; text-decoration: none; width: 100%; }
.nav-menu-panel a:hover { background: rgba(255,255,255,.12); color: white; }
/* Logout sits in the bar itself, after Settings. A button, because signing out is a POST, drawn
   to read exactly like the .topnav links around it. */
.nav-logout-form { display: inline; margin: 0; }
.nav-logout-form button { background: transparent; border: 0; color: #ccc; cursor: pointer; font: inherit; font-size: 14px; margin-left: 12px; padding: 0; white-space: nowrap; }
.nav-logout-form button:hover { color: white; }
/* A group that is a place rather than a panel (Configuration) renders as a plain link in the
   bar, styled like a group summary so the bar reads as one row of peers. */
.nav-group-link { color: #ccc; font-size: 14px; margin-left: 12px; text-decoration: none; white-space: nowrap; }
.nav-group-link:hover, .nav-group-link.nav-active { color: white; }

.container { max-width: 1200px; margin: 0 auto; padding: 24px; }

/* Shared page branding (login, dashboard, library) */
.page-brand-hero {
  margin: 0 0 12px;
  padding: 0;
  border-radius: 16px;
  overflow: hidden;
  background:
    linear-gradient(135deg, color-mix(in srgb, var(--drawer-brand) 88%, black) 0%, var(--drawer-brand) 55%, color-mix(in srgb, var(--drawer-brand) 82%, white) 100%);
  border: 1px solid color-mix(in srgb, white 12%, transparent);
  box-shadow: 0 4px 18px rgba(0, 0, 0, 0.18);
}
/* Two rows: logo and page title share the top row, the tagline sits right-aligned on
   its own row beneath, under the title. Grid rather than flex because the tagline needs
   a row of its own spanning the full width -- flex would have to wrap to get there, and
   wrapping depends on how long the page title happens to be. */
.page-brand {
  display: grid;
  grid-template-columns: auto 1fr auto;
  grid-template-areas:
    "logo gap  page"
    "text text text";
  align-items: center;
  row-gap: 6px;
  column-gap: 16px;
  min-width: 0;
  padding: 14px 18px;
}
.page-brand-logo {
  grid-area: logo;
  justify-self: start;
  height: 88px;
  width: auto;
  flex-shrink: 0;
  display: block;
  filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.35));
}
.page-brand-text {
  grid-area: text;
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
  justify-self: end;
  text-align: right;
  /* Pulled up into the row above. The logo is 88px tall and the title pill only ~30px,
     so the top row has dead space on the right-hand side -- this tucks the tagline into
     it, closer under the title, instead of leaving a visible gap. Negative margin rather
     than a smaller row-gap because the gap is already 6px and cannot go far enough. */
  margin-top: -20px;
  color: white;
  line-height: 1.15;
}
.page-brand-sub {
  font-size: clamp(0.95rem, 1.6vw, 1.2rem);
  /* Weight down from 700 as well as fading the colour. Dropping opacity alone on bold
     text tends to read as washed-out rather than quieter -- the strokes stay heavy, so
     it still pulls the eye. 600 with 62% white recedes properly.

     62% is a deliberate floor, not the lowest that looked calm: on the lightest brand
     theme (#2c3e6b) this is still comfortably above the 4.5:1 contrast ratio, and the
     tagline is real information rather than decoration. */
  font-weight: 600;
  letter-spacing: 0.01em;
  color: color-mix(in srgb, white 62%, transparent);
}
.page-brand-page {
  grid-area: page;
  justify-self: end;
  margin: 0;
  padding: 8px 14px;
  flex-shrink: 0;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: white;
  line-height: 1;
  border-radius: 999px;
  background: color-mix(in srgb, white 14%, transparent);
  border: 1px solid color-mix(in srgb, white 18%, transparent);
}

.login-page {
  width: 100%;
  max-width: 520px;
  margin: 32px auto 48px;
  position: relative;
  z-index: 1;
}
.login-brand-hero {
  margin-bottom: 16px;
}
.login-card {
  margin-bottom: 0;
  background: color-mix(in srgb, var(--card) 92%, transparent);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
  box-shadow: 0 12px 40px rgba(0, 0, 0, 0.28);
}
.login-card input[type="text"],
.login-card input[type="password"],
.login-card input[type="email"] {
  background: #eef0f3;
  border-color: color-mix(in srgb, var(--line) 70%, #c5cad3);
  color: var(--ink);
}
.login-card input[type="text"]:focus,
.login-card input[type="password"]:focus,
.login-card input[type="email"]:focus {
  background: #f5f6f8;
  border-color: color-mix(in srgb, var(--blue) 45%, var(--line));
  outline: none;
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 22%, transparent);
}
.login-lede {
  margin: 0 0 18px;
  color: var(--muted);
  font-size: 14px;
  line-height: 1.45;
}
.login-submit {
  width: 100%;
  margin-top: 8px;
  min-height: 44px;
}

/* Full-bleed court backdrop on login */
body.login-body {
  min-height: 100dvh;
  background-color: #1a1612;
  background-image:
    linear-gradient(
      180deg,
      color-mix(in srgb, var(--drawer-brand) 72%, transparent) 0%,
      color-mix(in srgb, #0a0a0a 55%, transparent) 45%,
      color-mix(in srgb, #0a0a0a 70%, transparent) 100%
    ),
    url("/img/login-court-bg.webp");
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
  background-attachment: fixed;
}
body.login-body .topnav {
  background: color-mix(in srgb, var(--navy) 82%, transparent);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
}
body.login-body .container {
  position: relative;
  z-index: 1;
}

@media (max-width: 720px) {
  .login-page {
    margin: 16px auto 32px;
  }
  .page-brand {
    /* Stack everything centred, including the tagline -- the right-alignment used on wide
       screens has nothing to align against once the columns collapse. */
    grid-template-columns: 1fr;
    grid-template-areas: "logo" "text" "page";
    justify-items: center;
    text-align: center;
    row-gap: 8px;
    padding: 12px 14px;
  }
  .page-brand-logo {
    justify-self: center;
  }
  .page-brand-page {
    justify-self: center;
  }
  .page-brand-text {
    justify-self: center;
    text-align: center;
    margin-top: 0;
  }
  .page-brand-logo {
    height: 64px;
  }
  .page-brand-page {
    margin-left: 0;
  }
  /* The page title is dropped on a phone. The brand bar stacks into three rows here -- logo,
     tagline, title -- and this is the row that earns its space least: the browser tab carries it,
     the menu says where you are, and the page under it says so again. On a laptop it sits beside
     the logo and costs nothing, so it stays there. */
  .page-brand-page {
    display: none;
  }

  /* Navigation dropdowns are anchored with `right: 0`, so each one extends LEFTWARD from its
     trigger. That is right for a menu near the right edge and wrong for the leftmost one: measured
     at 320px, the Coach View panel opened 52px off the left of the screen with its first item cut
     off mid-word.
     
     Fixed to the viewport rather than the trigger, so the panel spans the screen under the bar and
     cannot escape whichever menu opened it. `top: auto` keeps its static position -- directly below
     the summary it belongs to -- so it still reads as that menu's panel rather than a sheet that
     appeared from nowhere. */
  .nav-menu-panel {
    position: fixed;
    left: 8px;
    right: 8px;
    top: auto;
    min-width: 0;
    /* A tall panel (Configuration: six sections, fourteen links) scrolls inside itself rather than
       pushing the page. 104px is the two-row phone bar with the back button (~80px) plus the 8px
       edge above and 16px of room; dvh rather than vh because vh on a phone is the height with the
       address bar hidden, which is taller than what is visible. overscroll-behavior keeps a scroll
       that reaches the panel's end from carrying on into the page behind it. */
    max-height: calc(100dvh - 104px);
    overflow-y: auto;
    overscroll-behavior: contain;
  }

  body.login-body {
    background-attachment: scroll;
  }
}

h1, h2 { color: var(--navy); margin-bottom: 16px; }
.btn {
  display: inline-block; padding: 10px 18px; border-radius: 8px;
  text-decoration: none; font-size: 14px; cursor: pointer; border: none;
}
.btn-primary { background: var(--navy); color: white; }
.btn-secondary { background: var(--blue); color: white; }
.btn-ghost { background: transparent; border: 1px solid var(--line); color: var(--ink); }
.btn-wide { padding-left: 36px; padding-right: 36px; white-space: nowrap; }
/* A disabled button has to LOOK disabled, everywhere, not only where somebody remembered to say so.
   The "Add scouting evaluation" control on the Add athletes form was the case that showed this: it
   renders as a genuinely inert <button disabled> before the athlete is saved, but kept its full
   secondary colour, so it read as a live action that silently did nothing when clicked. Only
   .admin-athletes-bulk had a disabled rule, which is exactly the drift a shared control is meant to
   prevent -- so the rule belongs on .btn itself.

   Colour is flattened rather than dimmed alone: opacity by itself keeps the hue, and a pale green
   still reads as "go". The variant classes come earlier in the file and are equally specific, so
   the attribute selector here wins on order; both [disabled] and :disabled are matched because an
   <a> cannot carry the pseudo-class and a <button> is written with the attribute. */
.btn[disabled],
.btn:disabled {
  background: var(--surface-soft, #e9e9ec);
  color: var(--muted, #6b7280);
  border-color: transparent;
  box-shadow: none;
  opacity: 0.65;
  /* Deliberately no pointer-events:none. A disabled <button> already ignores clicks, and removing
     pointer events would also remove the hover tooltip that says WHY it is disabled -- which is the
     one piece of help somebody stuck on this screen actually needs. */
  cursor: not-allowed;
}
/* Every backward navigation action lives in the topnav's NavStart slot (see _Layout.cshtml and
   _BackButton.cshtml) as the same grey pill, so "back" reads as one consistent control across
   every page instead of a separate element competing with each page's own header. A solid grey
   fill (rather than matching the dark topnav background) is what keeps it visible against the
   navy bar; a fixed "Back" label keeps its width predictable next to the nav links beside it. */
.topnav a.back-link {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 32px;
  margin-left: 0;
  padding: 0 14px 0 10px;
  border-radius: 999px;
  background: color-mix(in srgb, white 16%, transparent);
  color: #f2f2f2;
  text-decoration: none;
  font-size: 13px;
  font-weight: 600;
  white-space: nowrap;
  transition: background 0.15s ease, color 0.15s ease;
}
.topnav a.back-link:hover, .topnav a.back-link:focus-visible {
  background: color-mix(in srgb, white 28%, transparent);
  color: #fff;
}
.back-icon { flex: 0 0 auto; display: block; height: 16px; width: 16px; }
.card {
  background: var(--card);
  padding: 24px;
  border-radius: 12px;
  box-shadow: 0 8px 24px var(--shadow);
  border: 1px solid var(--line);
  margin-bottom: 20px;
}
.flash { padding: 12px 16px; border-radius: 8px; margin-bottom: 16px; }
.flash-success { background: #d4edda; color: #155724; }
.flash-error { background: #f8d7da; color: #721c24; }
/* Between success and error: the possible-duplicate prompt is not a failure — nothing was refused,
   the person was asked a question only they can answer. */
.flash-warn { background: #fff3cd; color: #664d03; }
.flash-warn a { color: inherit; }

/* ---------------------------------------------------------------------------------------------
   Components the admin views have always used and that were never styled.
   `.muted`, `.alert`, `.fs-card`, `.fs-panel` and `.fs-row-current` appear across the admin
   pages; none of them had a rule, so explanatory text rendered at the same weight as the data
   it explains and a warning saying "AI calls will fail" looked like ordinary body copy. Colours
   come from the theme tokens, so these follow every theme rather than pinning one.
   --------------------------------------------------------------------------------------------- */

/* Secondary prose: present, subordinate, skippable. The admin pages carry a lot of rationale
   and this is what keeps it from competing with the numbers. */
.muted { color: var(--muted); font-size: 14px; }
td .muted, th .muted, .muted .muted { font-size: 13px; }

/* Standing notices, as opposed to .flash which reports what a submit just did. The left rule
   carries the severity so the box stays quiet when it is only informational. */
.alert {
  padding: 12px 16px;
  border-radius: 8px;
  margin-bottom: 16px;
  border: 1px solid var(--line);
  border-left: 4px solid var(--muted);
  background: var(--surface-soft);
  color: var(--ink);
}
.alert-success { background: #d4edda; color: #155724; border-color: #b7dfc2; border-left-color: #1e7e34; }
.alert-danger { background: #f8d7da; color: #721c24; border-color: #efc2c7; border-left-color: #a71d2a; }
.alert-warning { background: #fff3cd; color: #664d03; border-color: #efe0ae; border-left-color: #b8860b; }
.alert a { color: inherit; }

/* Card grid used by the landing pages to make sibling choices scannable. */
.fs-card-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 16px;
  margin-bottom: 20px;
}
.fs-card {
  display: flex;
  flex-direction: column;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 8px 24px var(--shadow);
  padding: 20px 22px;
}
.fs-card > h2, .fs-card > h3 { margin-top: 0; }
.fs-card > :last-child { margin-bottom: 0; }

details.fs-panel { padding: 0; }
details.fs-panel > summary {
  cursor: pointer;
  padding: 12px 20px;
  list-style-position: inside;
  border-radius: 10px;
}
details.fs-panel[open] > summary { border-bottom: 1px solid var(--line); }
details.fs-panel > :not(summary) { padding: 0 20px; }
details.fs-panel[open] > ul { padding: 12px 20px 16px 40px; }

/* A quieter box than a card: supporting notes that belong beside content, not above it. */
.fs-panel {
  background: var(--surface-soft);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 16px 20px;
  margin-bottom: 20px;
}
.fs-panel > :first-child { margin-top: 0; }
.fs-panel > :last-child { margin-bottom: 0; }

/* Tables wide enough to need scrolling on a phone rather than squeezing columns to nothing. */
.fs-table { display: table; }
.fs-table-wrap { overflow-x: auto; }
.fs-table thead th { background: var(--surface-soft); white-space: nowrap; }

/* The row describing the thing currently in effect. */
.fs-row-current > td { background: var(--score-bg); color: var(--score-ink); }
.fs-row-current > td:first-child { box-shadow: inset 3px 0 0 var(--accent); }

/* Lets one control or note run the full width of an .admin-create-grid. */
.fs-span-2 { grid-column: 1 / -1; }

/* A bare button is a grid item, so it stretches to its whole column and reads as a banner
   rather than a control. Scoped to cards so the other 25 create-grids keep their layout. */
.fs-card .admin-create-grid > button { justify-self: start; }

/* Primary and destructive side by side, with the quiet one clearly subordinate. */
.fs-form-actions { display: flex; gap: 10px; align-items: center; }
.btn-quiet {
  background: transparent;
  color: var(--muted);
  border: 1px solid var(--line);
}
.btn-quiet:hover { color: #a71d2a; border-color: #a71d2a; }

/* Money is compared down a column, so it aligns right on the decimal, in tabular figures so
   the digits line up between rows. Left-aligned currency is the single thing that made the
   cost table hard to read. */
.fs-table td.num, .fs-table th.num { text-align: right; font-variant-numeric: tabular-nums; }
.fs-table td.nowrap, .fs-table th.nowrap { white-space: nowrap; }

/* Meter for a consumed-against-a-ceiling figure, which a table row communicates poorly. */
.fs-meter {
  max-width: 320px;
  height: 10px;
  border-radius: 999px;
  background: var(--surface-soft);
  border: 1px solid var(--line);
  overflow: hidden;
  margin: 8px 0 4px;
}
.fs-meter > span { display: block; height: 100%; min-width: 3px; background: var(--evidence); border-radius: 999px; }
.fs-meter-warn > span { background: var(--accent); }
.fs-meter-over > span { background: #a71d2a; }
.fs-figure { font-size: 26px; font-weight: 700; letter-spacing: -0.01em; }
.fs-figure-sub { color: var(--muted); font-size: 13px; }

/* Dashboard-card furniture: an eyebrow label instead of a competing heading, a divider between
   the reading half and the acting half, and a footnote pinned to the bottom so sibling cards in
   a row keep their footers aligned regardless of content height. */
.fs-stat-label {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 8px;
}
.fs-card-divider { border: 0; border-top: 1px solid var(--line); margin: 16px 0 12px; width: 100%; }
.fs-card-foot { margin-top: auto; padding-top: 12px; color: var(--muted); font-size: 13px; }
.fs-card-head { display: flex; justify-content: space-between; align-items: center; gap: 10px; }

/* A model that was withdrawn rather than never offered. Collapsed by default so the reason is
   available without competing with the choice the administrator is actually making. */
.fs-withheld { margin-top: 12px; font-size: 13px; }
.fs-withheld > summary { color: var(--muted); cursor: pointer; }
.fs-withheld > summary:hover { color: inherit; }
.fs-withheld > p { margin: 8px 0 0; line-height: 1.5; }
.fs-card-head > h3 { margin: 0; }

/* A control and its submit on one line, the control taking the room. */
.fs-input-row { display: flex; gap: 10px; align-items: stretch; margin-top: 4px; }
.fs-input-row > input, .fs-input-row > select { flex: 1; min-width: 0; margin-bottom: 0; }
.fs-input-row > .btn { flex: 0 0 auto; white-space: nowrap; }

/* Status pills. Same palette as the flash messages so state and feedback speak one language. */
.fs-badge {
  display: inline-flex;
  align-items: center;
  font-size: 12px;
  font-weight: 600;
  padding: 3px 10px;
  border-radius: 999px;
  white-space: nowrap;
}
.fs-badge-ok { background: #d4edda; color: #155724; }
.fs-badge-warn { background: #fff3cd; color: #664d03; }
.fs-badge-muted { background: var(--chip-bg); color: var(--chip-ink); }

/* The key-status list in the overview card. */
.fs-key-rows { display: flex; flex-direction: column; gap: 8px; }
.fs-key-row { display: flex; justify-content: space-between; align-items: center; gap: 10px; }

/* Admin data tables. One shared shape for search terms, model selection, and API keys. */
.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 14px;
  margin: 10px 0 6px;
}
.data-table th,
.data-table td {
  text-align: left;
  padding: 8px 10px;
  border-bottom: 1px solid var(--line);
  vertical-align: middle;
}
.data-table th {
  font-size: 12px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--muted);
}
.data-table tr.is-muted td { opacity: 0.55; }
/* The row currently in effect: readable at a glance without scanning the buttons column. */
.data-table tr.is-current td,
.athlete-table tr.is-current > td { background: color-mix(in srgb, #155724 6%, transparent); }
.data-table td form { margin: 0; display: inline; }

/* Sortable headers: real buttons for keyboard use, with the active direction shown. */

/* A card that is mostly a table: the table runs edge to edge, with the card's padding carried
   by the first and last columns, and the footnotes live in a bordered footer. */
.fs-table-card { padding: 0; overflow: hidden; }
.fs-table-card th:first-child, .fs-table-card td:first-child { padding-left: 22px; }
.fs-table-card th:last-child, .fs-table-card td:last-child { padding-right: 22px; }
.fs-table-card-foot { padding: 12px 22px 16px; border-top: 1px solid var(--line); }
.fs-table-card-foot > p { margin-top: 0; }
.fs-table-card-foot details.fs-panel { margin: 8px 0 0; }
.fs-table tbody tr:hover > td { background: var(--surface-soft); }
.fs-table tbody tr.fs-row-current:hover > td { background: var(--score-bg); }

/* AI settings is a decision page for business administrators. Restore the vertical rhythm removed
   by the global reset, keep actions comfortably clickable, and separate status from detail without
   changing the shared admin components used by other screens. */
.ai-settings-page {
  line-height: 1.5;
  margin: 0 auto;
  max-width: 1180px;
}
.ai-settings-header { margin-bottom: 24px; }
.ai-settings-eyebrow {
  color: var(--blue);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .1em;
  margin-bottom: 6px;
  text-transform: uppercase;
}
.ai-settings-header h1 {
  font-size: clamp(1.8rem, 4vw, 2.35rem);
  letter-spacing: -.02em;
  line-height: 1.15;
  margin: 0 0 8px;
}
.ai-settings-header p { font-size: 15px; margin: 0; max-width: 720px; }
.ai-settings-page > h2 {
  font-size: clamp(1.3rem, 2.5vw, 1.65rem);
  line-height: 1.25;
  margin: 36px 0 8px;
}
.ai-settings-page > h2 + .muted {
  line-height: 1.55;
  margin: 0 0 18px;
  max-width: 860px;
}
.ai-settings-page .alert { line-height: 1.5; margin-bottom: 20px; }
.ai-settings-page .fs-card-grid { gap: 20px; margin-bottom: 24px; }
.ai-settings-page .fs-card { line-height: 1.45; padding: 24px; }
.ai-settings-page .fs-card-divider { margin: 20px 0 16px; }
.ai-settings-page .fs-card-foot { line-height: 1.5; padding-top: 16px; }
.ai-settings-page .fs-input-row {
  align-items: flex-start;
  flex-direction: column;
  gap: 12px;
  margin-top: 6px;
}
.ai-settings-page .fs-input-row > input,
.ai-settings-page .fs-input-row > select {
  width: 100%;
}
.ai-settings-page .fs-input-row > input,
.ai-settings-page .fs-input-row > select,
.ai-settings-page .fs-input-row > .btn {
  min-height: 44px;
}
.ai-settings-page #modelWhy { line-height: 1.5; margin: 12px 0 0; }
.ai-settings-page .fs-key-rows { gap: 0; }
.ai-settings-page .fs-key-row { border-bottom: 1px solid var(--line); padding: 10px 0; }
.ai-settings-page .fs-key-row:first-child { padding-top: 2px; }
.ai-settings-page .fs-key-row:last-child { border-bottom: 0; padding-bottom: 2px; }
.ai-settings-page .fs-panel { line-height: 1.55; }
.ai-settings-page details.fs-panel li + li { margin-top: 10px; }
.ai-shared-costs {
  align-items: center;
  display: grid;
  gap: 18px 28px;
  grid-template-columns: minmax(220px, 1fr) minmax(360px, 1.5fr);
}
.ai-shared-costs h3 { font-size: 16px; margin: 0 0 4px; }
.ai-shared-costs dl {
  display: grid;
  gap: 12px;
  grid-template-columns: repeat(3, minmax(100px, 1fr));
  margin: 0;
}
.ai-shared-costs dl > div { border-left: 1px solid var(--line); padding-left: 14px; }
.ai-shared-costs dt { color: var(--muted); font-size: 12px; }
.ai-shared-costs dd { font-size: 18px; font-variant-numeric: tabular-nums; font-weight: 700; margin: 3px 0 0; }
.ai-shared-costs .ai-shared-cost-total { border-left-color: var(--accent); }
.ai-settings-page .fs-card-head { margin-bottom: 14px; }
.ai-settings-page .fs-card-head + .muted { margin: -4px 0 18px; }
.ai-settings-page .admin-create-grid { gap: 16px; }
.ai-settings-page .admin-create-grid label { margin-bottom: 0; }
.ai-settings-page .admin-create-grid input { margin-bottom: 0; min-height: 44px; }
.ai-settings-page .fs-form-actions { flex-wrap: wrap; padding-top: 2px; }
.ai-settings-page .fs-form-actions .btn { min-height: 44px; }
.ai-settings-page .fs-table-card { padding: 0; }
.ai-settings-page .fs-table-card-foot { line-height: 1.55; padding-bottom: 20px; padding-top: 16px; }

@media (max-width: 640px) {
  .ai-settings-page .fs-card-grid { grid-template-columns: minmax(0, 1fr); }
  .ai-settings-page .fs-card { padding: 20px; }
  .ai-settings-page .fs-input-row > .btn,
  .ai-settings-page .fs-form-actions .btn { text-align: center; width: 100%; }
  .ai-settings-page .fs-key-row { align-items: flex-start; flex-direction: column; gap: 6px; }
  .ai-settings-page .fs-table-card th:first-child,
  .ai-settings-page .fs-table-card td:first-child { padding-left: 16px; }
  .ai-settings-page .fs-table-card th:last-child,
  .ai-settings-page .fs-table-card td:last-child { padding-right: 16px; }
  .ai-settings-page .fs-table-card-foot { padding-left: 16px; padding-right: 16px; }
  .ai-shared-costs { align-items: stretch; grid-template-columns: 1fr; }
  .ai-shared-costs dl { grid-template-columns: 1fr; }
  .ai-shared-costs dl > div { padding-bottom: 4px; }
}
label { display: block; font-weight: 600; margin-bottom: 4px; font-size: 14px; }
input, select, textarea {
  width: 100%; padding: 10px; border: 1px solid var(--line); border-radius: 8px;
  font-size: 14px; margin-bottom: 14px; font-family: inherit; background: var(--card); color: var(--ink);
}
/* A native <select>'s own OS chrome (its dropdown control, not just its arrow) carries its own
   intrinsic height that some browsers -- Safari in particular -- don't fully reconcile with the
   padding/border box above, so a <select> can render visibly shorter or taller than a same-row
   <input> even though both share this exact box model. Turning off native appearance and drawing
   the arrow ourselves removes that inconsistency at the source, in every browser, rather than
   chasing per-browser height differences. */
/* A date is ten characters and a calendar button; a date box the width of its column reads as a
   text field somebody forgot to fill (Ron, 2026-09-15). One width everywhere. */
input[type="date"] { width: 12.5rem; max-width: 100%; }
select {
  appearance: none; -webkit-appearance: none; -moz-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 8'%3E%3Cpath d='M1 1l5 5 5-5' fill='none' stroke='%23667085' stroke-width='1.5' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 12px center;
  background-size: 10px 7px;
  padding-right: 32px;
}
table { width: 100%; border-collapse: collapse; }
th, td { padding: 10px 14px; text-align: left; border-bottom: 1px solid var(--line); }
th { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.4px; }

.meta { color: var(--muted); font-size: 13px; margin-top: 4px; }

.profile-hero {
  display: grid; grid-template-columns: 1fr auto; gap: 16px;
  background: linear-gradient(135deg, var(--navy), var(--hero-end));
  color: white; border-radius: 16px; padding: 28px; margin-bottom: 20px;
}
.profile-hero-with-avatar { align-items: start; }
.profile-hero-main {
  display: flex;
  align-items: center;
  gap: 20px;
  min-width: 0;
}
.profile-avatar {
  width: 112px;
  height: 112px;
  font-size: 38px;
  flex-shrink: 0;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.25);
}
/* The context chip on the band's right (Ron, 2026-09-17): an outlined caption in the band's
   colours, the shape of the band's own buttons but not a control; the gold star sits before the
   word on "Committed". On a phone it drops under the identity. */
.profile-hero-context {
  display: inline-flex; align-items: center; gap: 8px; align-self: start;
  padding: 10px 16px; border: 1px solid rgba(255, 255, 255, 0.55); border-radius: 10px;
  color: rgba(255, 255, 255, 0.92); font-size: 15px; font-weight: 600; white-space: nowrap;
}
.profile-hero-context .fs-commit-star { font-size: 18px; }
@media (max-width: 720px) { .profile-hero-context { align-self: auto; justify-self: start; } }
.profile-identity { min-width: 0; }
.profile-identity h1 { color: white; margin-bottom: 2px; overflow-wrap: anywhere; }
/* The position, set like a watermark: tucked under the name, muted rather than white, and spaced
   out in small capitals. It reads as a label on the card rather than as another line of facts,
   which is what keeps it from competing with the name directly above it. */
.profile-position {
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: rgba(255, 255, 255, 0.58);
  margin-bottom: 10px;
}

/* One fact per span, so a line breaks only between facts.

   The separator is generated here rather than typed into the markup: a "·" that is part of the
   text can be pushed onto a line by itself, which looks like a typing mistake. */
.meta-part {
  display: inline-block;
  /* Holds a fact together, and holds its trailing separator with it. A single value longer than
     the line will extend rather than wrap, which is the trade: these are team names, schools and
     towns, and a broken one reads worse than a long one. */
  white-space: nowrap;
  max-width: 100%;
}
/* The separator TRAILS the fact before it, and there is none before the first.

   An earlier version put the marker in front of every fact including the first, so that a wrapped
   line and an unwrapped one looked alike. The cost was that every line began with a "·" -- which
   reads as a stray character, because a list does not start with a separator.

   Trailing solves both at once. The "·" sits inside the preceding span, which is nowrap, so it can
   never be carried onto the next line on its own: a break happens between facts, and the new line
   therefore always begins with a word. The line above simply ends with its separator, the way a
   continued list should. */
.meta-part:not(:last-child)::after {
  content: "·";
  padding-left: 0.4em;
  opacity: 0.7;
}
/* The gap after the separator, so the next fact does not sit against it. Trailing space costs
   nothing when a fact ends a line -- the browser discards it. */
.meta-part { margin-right: 0.4em; }
.meta-part:last-child { margin-right: 0; }

/* On a phone a run of facts cannot fit on one line at all, so each gets its own line and the
   separators go entirely: the line break does the separating, and a trailing "·" on every line
   would be noise.

   Only the detail lines change. The position keeps its place under the name. */
@media (max-width: 640px) {
  .profile-primary-meta .meta-part,
  .profile-secondary-meta .meta-part {
    display: block;
    white-space: normal;
    overflow-wrap: anywhere;
    margin-right: 0;
  }
  .profile-primary-meta .meta-part::after,
  .profile-secondary-meta .meta-part::after {
    content: none;
  }
}
.profile-secondary-meta { opacity: .85; }
.profile-primary-meta, .profile-secondary-meta { overflow-wrap: anywhere; }
.profile-secondary-meta { margin-top: 6px; }
.scouting-report-card { margin-bottom: 20px; }
.scouting-report-head { margin-bottom: 12px; }
.scouting-report-head h2 { margin-bottom: 4px; }
/* Sits above the paged report cards: title (with live "N of M" count) and the previous/next
   controls share one row, mirroring the footer nav so a reader never has to scroll to page. */
.scouting-report-reader-head {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  justify-content: space-between;
  margin-bottom: 16px;
}
.scouting-report-reader-head h2 { margin-bottom: 0; }
.scouting-report-head-nav {
  align-items: center;
  display: flex;
  flex-shrink: 0;
  gap: 8px;
}
.scouting-report-count {
  color: color-mix(in srgb, var(--muted) 86%, transparent);
  font-size: 0.72em;
  font-weight: 600;
  white-space: nowrap;
}
.scouting-report-navigation {
  align-items: center;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  justify-content: flex-start;
  margin: -4px 0 20px;
}
.scouting-report-navigation .meta { margin: 0 4px; }
.scouting-lede {
  font-size: 15px;
  line-height: 1.55;
  color: var(--ink);
  margin-bottom: 16px;
  padding-bottom: 12px;
  border-bottom: 1px solid var(--line);
}
.scouting-characteristics {
  background: var(--surface-soft);
  border: 1px solid var(--line);
  border-radius: 12px;
  margin: 16px 0 22px;
  padding: 16px;
}
.scouting-characteristics-heading {
  align-items: baseline;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 12px;
  margin-bottom: 14px;
}
.scouting-characteristics-heading h3 {
  color: var(--navy);
  font-size: 17px;
  margin: 0;
}
.scouting-characteristics-heading p {
  color: var(--muted);
  font-size: 13px;
  margin: 0;
}
.scouting-characteristic-groups {
  display: grid;
  gap: 18px;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
.scouting-characteristic-group h4 {
  color: var(--muted);
  font-size: 12px;
  letter-spacing: .07em;
  margin: 0 0 8px;
  text-transform: uppercase;
}
.scouting-characteristic-group ul { list-style: none; margin: 0; padding: 0; }
.scouting-characteristic-group li { margin: 0 0 10px; }
.scouting-characteristic-group li:last-child { margin-bottom: 0; }
.scouting-characteristic-label {
  align-items: baseline;
  display: flex;
  font-size: 13px;
  gap: 10px;
  justify-content: space-between;
  margin-bottom: 3px;
}
.scouting-characteristic-label strong { color: var(--ink); font-size: 14px; white-space: nowrap; }
.scouting-characteristic-label small { color: var(--muted); font-size: 11px; font-weight: 500; }
.scouting-characteristic-group progress {
  appearance: none;
  background: color-mix(in srgb, var(--line) 72%, transparent);
  border: 0;
  border-radius: 999px;
  display: block;
  height: 6px;
  overflow: hidden;
  width: 100%;
}
.scouting-characteristic-group progress::-webkit-progress-bar {
  background: color-mix(in srgb, var(--line) 72%, transparent);
  border-radius: 999px;
}
.scouting-characteristic-group progress::-webkit-progress-value {
  background: var(--evidence);
  border-radius: 999px;
}
.scouting-characteristic-group progress::-moz-progress-bar { background: var(--evidence); border-radius: 999px; }
.scouting-report-card .report-section {
  margin-top: 16px;
  padding-top: 4px;
}
/* Spacing only. The report body's type is defined once, on .report-section in search.css, because
   a report is read inside several different wrappers and a per-wrapper copy is how the same words
   ended up at two sizes. Do not restate font-size or colour here. */
.scouting-report-card .report-section p:last-child { margin-bottom: 0; }
.doc-link-list { list-style: none; padding: 0; margin: 0; }
.doc-link-list li { margin-bottom: 12px; padding-bottom: 12px; border-bottom: 1px solid var(--line); }
.doc-link-list li:last-child { border-bottom: none; margin-bottom: 0; padding-bottom: 0; }

.profile-actions {
  align-items: stretch;
  display: flex;
  flex-direction: column;
  gap: 8px;
  justify-content: flex-start;
  min-width: 240px;
}
.profile-actions .btn { text-align: center; width: 100%; }
.profile-actions a, .profile-actions button { color: white; }
.profile-actions #btnSaveAthlete { background: var(--card); color: var(--ink); }
.profile-actions .profile-contract-button { background: rgba(255, 255, 255, .08); border-color: rgba(255, 255, 255, .35); }
.contract-athlete-dialog { width: min(520px, calc(100vw - 24px)); }
.contract-dialog-fields { gap: 14px; padding-top: 6px; }
.contract-dialog-field label { color: var(--ink); display: block; font-size: 13px; font-weight: 700; margin-bottom: 6px; }
.contract-dialog-field input, .contract-dialog-field select { margin: 0; min-height: 44px; width: 100%; }
.contract-dialog-fields .flash { margin: 0; }

@media (max-width: 720px) {
  .profile-hero { grid-template-columns: 1fr; }
  /* The avatar keeps the name company; the lines under it run the full width of the card.

     As a flex row the avatar sat beside the whole identity block, so about a quarter of the width
     went to the picture and every line of text wrapped early -- while the buttons underneath used
     the full width. The two halves of the card looked unrelated.

     A grid with `display: contents` on the wrapper lets the name sit in the second column beside
     the avatar while the meta lines span both, without changing the markup: the heading still
     belongs to the identity block, it is only laid out differently. */
  .profile-hero-main {
    display: grid;
    grid-template-columns: auto 1fr;
    column-gap: 16px;
    row-gap: 6px;
    align-items: center;
  }
  .profile-identity { display: contents; }
  .profile-identity h1 { grid-column: 2; margin-bottom: 0; align-self: end; }
  /* The subtitle stays with the name beside the avatar; only the detail lines run full width.
     nowrap keeps it on one line: it is two or three short words, and a position broken across two
     lines under a name reads as a fault rather than as a label. */
  .profile-position {
    grid-column: 2;
    margin-bottom: 0;
    font-size: 0.66rem;
    white-space: nowrap;
    align-self: start;
  }
  .profile-primary-meta,
  .profile-secondary-meta { grid-column: 1 / -1; }
  /* A clear break under the name row, and a tighter one between the two lines that follow.
     Equal spacing would read as three unrelated lines; this reads as a heading and its detail. */
  .profile-primary-meta { margin-top: 8px; }
  .profile-secondary-meta { margin-top: 0; }
  /* The picture spans both rows of the identity block, so the name and the position stack against
     it as one unit. Left in a single row it made that row as tall as the picture, which pushed the
     position down past the bottom of the avatar -- under the name in source order, but a long way
     from it on screen. */
  .profile-avatar {
    width: 88px;
    height: 88px;
    font-size: 30px;
    grid-row: 1 / span 2;
    align-self: center;
  }
  .scouting-report-navigation .btn { flex: 1 1 150px; text-align: center; }
  .scouting-report-navigation .meta { flex: 1 0 100%; text-align: center; }
  .scouting-report-reader-head { flex-direction: column; align-items: stretch; }
  .scouting-report-head-nav .btn { flex: 1 1 auto; text-align: center; }
  .scouting-characteristic-groups { grid-template-columns: 1fr; }
  .profile-actions { min-width: 0; width: 100%; }
  .topnav {
    flex-wrap: wrap;
    padding: 10px 14px;
  }
  .nav-links {
    width: 100%;
    justify-content: flex-end;
  }
  .topnav a { margin-left: 10px; }
}

/* Settings theme picker */
.theme-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 14px;
  margin: 12px 0 20px;
}
.theme-option {
  border: 2px solid var(--line);
  border-radius: 12px;
  padding: 14px;
  cursor: pointer;
  background: var(--card);
  transition: border-color 0.15s, box-shadow 0.15s;
  display: block;
  /* The containing block for the hidden radio below. */
  position: relative;
}
.theme-option:hover { border-color: var(--accent); }
/* The radio is hidden but must stay in the document for the keyboard and the form. Sized
   explicitly: the global input rule makes it width:100%, and absolutely positioned with no
   positioned ancestor that was 100% of the viewport, starting at the card -- three invisible
   inputs a screen wide each, and the page scrolled sideways. */
.theme-option input { position: absolute; width: 1px; height: 1px; margin: 0; opacity: 0; pointer-events: none; }
.theme-option.selected,
.theme-option:has(input:checked) {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent) 25%, transparent);
}
.theme-swatches { display: flex; gap: 6px; margin: 10px 0 8px; }
.theme-swatches span {
  width: 28px; height: 28px; border-radius: 8px; border: 1px solid rgba(0,0,0,0.08);
}
.theme-option h3 { font-size: 15px; margin: 0; color: var(--ink); }
.theme-option p { font-size: 12px; color: var(--muted); margin: 0; line-height: 1.4; }

.admin-stats {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px;
  margin-bottom: 8px;
}
.admin-stat { margin-bottom: 0; padding: 16px 18px; }
.admin-stat-label { font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.4px; }
.admin-stat-value { font-size: 28px; font-weight: 700; color: var(--navy); margin-top: 4px; }
.admin-create-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 12px 16px;
  align-items: end;
}
/* The administrator's preview banner. Deliberately loud: the page under it is a faithful copy of
   the coach product, and the whole risk of the feature is somebody mistaking one for the other. */
.preview-banner {
  padding: 12px 16px;
  margin-bottom: 16px;
  border-radius: 8px;
  border: 1px solid var(--warning-text, #92400e);
  background: var(--warning-surface, #fef3c7);
  color: var(--warning-text, #92400e);
  font-size: 14px;
  line-height: 1.5;
}

/* One row of actions, wrapping rather than overflowing.

   Each action is its own <form> because each posts to a different address, so the flex items here
   are forms rather than buttons -- which is why the margin reset below is on the form and not on
   .btn. Without this rule the class styled nothing outside .admin-create-grid: the only other
   rule for it is the scoped one below, forms are block elements, and .btn is inline-block with no
   margin, so the media pages drew their actions stacked and touching. Measured before this rule
   landed, the closest pair on the media item page sat 4px apart -- the whitespace between two
   inline-block elements, not a decision anybody made. */
.admin-create-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 12px;
  /* Clearance above and below, not only between. The gap above handles the buttons' relationship
     to each other and does nothing for their relationship to the page: the row sat directly against
     the paragraph explaining it, and the next <h2> sat directly on the row, because headings here
     carry margin-bottom and no margin-top. A control touching the sentence that describes it reads
     as part of the sentence. */
  margin: 18px 0;
}
.admin-create-actions > form { margin: 0; }
/* The destructive pair gets a rule of its own. Delete keeps a recoverable copy for 90 days and
   Take down permanently does not; spaced evenly in one row they read as a matched pair, which is
   worse than having no spacing at all. A full-width break pushes the permanent one onto its own
   line under a hairline, so choosing it is a separate motion from choosing the ordinary delete. */
.admin-create-actions .admin-action-break {
  flex-basis: 100%;
  height: 0;
  margin: 2px 0 0;
  border: 0;
  border-top: 1px solid var(--line, #e2e5ea);
}

/* The scoped rule stays and still wins where it applies: inside the create grid these sit in a
   column of their own and need to align with the field above rather than wrap. The margin is reset
   with it -- inside the grid the neighbours are cells beside the row, not text above it, and a
   vertical margin there would push the buttons out of line with the field they sit next to. */
.admin-create-grid .admin-create-actions {
  display: flex;
  align-items: end;
  padding-bottom: 14px;
  margin: 0;
}
.admin-table-wrap { overflow-x: auto; }
/* Row actions sit on one line with a gap, rather than each inline form stacking. Save and Remove
   are separate forms -- Remove must not submit the edits beside it -- so without this they wrap
   onto two lines and the row grows to twice its height. */
.admin-row-actions {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}
.admin-row-actions .admin-inline-form { display: inline-flex; margin: 0; }
.ai-provider-picker .theme-option p { margin-top: 4px; }
.ai-provider-picker .theme-option .meta { margin-top: 2px; }
.admin-inline-form select,
.admin-inline-form input { margin-bottom: 0; min-width: 120px; }
.admin-kv-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: 14px 16px;
}
.admin-kv-label {
  font-size: 12px;
  font-weight: 600;
  color: var(--muted);
  text-transform: uppercase;
  letter-spacing: 0.4px;
  margin-bottom: 4px;
}
.admin-kv-value {
  font-size: 15px;
  font-weight: 600;
  color: var(--navy);
  word-break: break-word;
}
.admin-section-subhead {
  margin: 8px 0 12px;
  font-size: 1rem;
  font-weight: 700;
  color: var(--ink);
}
.admin-action-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-bottom: 16px;
}
.admin-action-row .btn {
  min-height: 44px;
}
/* The evaluation page's actions sit at the right, a step above the content they act on. */
.admin-action-row--end { justify-content: flex-end; margin: 4px 0 20px; }
.admin-agency-list {
  list-style: none;
  margin: 0 0 16px;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.admin-agency-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 12px;
  min-height: 44px;
  padding: 8px 0;
  border-bottom: 1px solid color-mix(in srgb, var(--muted) 28%, transparent);
}
.admin-agency-name {
  font-weight: 600;
  color: var(--navy);
}
.admin-agency-add {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: 12px 16px;
  align-items: end;
}
.admin-agency-add select,
.admin-agency-add input {
  width: 100%;
  min-height: 44px;
}
.admin-agency-add-actions {
  display: flex;
  align-items: end;
  min-height: 44px;
}
@media (max-width: 640px) {
  .admin-agency-add-actions .btn,
  .admin-action-row .btn {
    width: 100%;
  }
}
.admin-row-locked { opacity: 0.72; background: color-mix(in srgb, #b42318 6%, transparent); }
/* Highlights the one evaluation revision users and RAG search currently see. */
.admin-row-active { background: color-mix(in srgb, #067647 8%, transparent); }
/* Lets a long field (narrative, notes) span the whole responsive create grid. */
.admin-create-grid .admin-create-full { grid-column: 1 / -1; }
/* :not(.evaluation-entry-columns) keeps this generic section-header rule off the ratings/narrative
   panel titles below — they carry their own `.evaluation-entry-panel-title` styling and need to
   line up with each other exactly, which this rule's margin/border/padding would throw off. */
.admin-create-grid .admin-create-full:not(.evaluation-entry-columns) h3 { margin: 8px 0 0; font-size: 15px; color: var(--muted, #666); border-top: 1px solid var(--border, #e2e2e2); padding-top: 12px; }
.admin-create-grid .admin-create-full:not(.evaluation-entry-columns):first-child h3 { border-top: none; padding-top: 0; }
.admin-create-grid textarea { width: 100%; resize: vertical; }
/* <fs-field>/<fs-select> (TagHelpers/FieldTagHelper.cs): the red required marker and the inline
   validation message a rejected submission shows next to the specific field it failed on. */
/* The required-field standard. See "Required fields" in AGENTS.md.

   Two signals, and neither is the colour alone: the label carries a red asterisk, and an empty
   required control carries a red border. Only the border COLOUR changes -- the width stays the
   1px every input already has, so a field does not resize as it is filled in, and a column of
   inputs does not jitter as the reader works down it. */
.fs-required { color: #dc2626; }
/* A required control that is still missing, or holds a value the browser refuses for its type.
   Applied and removed by required-fields.js, never by hand; it clears the moment the control is
   answered, so the reader sees what is still outstanding. The asterisk says "required". */
.fs-required-outline,
.fs-required-outline:hover { border-color: #dc2626; }
/* The focus ring stays the ordinary one. A red ring on the field somebody has just clicked into
   reads as a complaint about what they are in the middle of typing. */
.fs-required-outline:focus,
.fs-required-outline:focus-visible { border-color: var(--blue); }
/* A required control that is not an input at all -- the school picker's button, which stands in
   for a hidden field the dialog fills. It has its own border to recolour. */
button.fs-required-outline { border-color: #dc2626; }
/* An "at least one of these" group: every chip in it carries the border, like any other required
   control. The chips have their own border, so only its colour changes and nothing moves. */
.fs-check.fs-required-outline { border-color: #dc2626; }
.fs-field-error { display: block; margin-top: 4px; font-size: 12.5px; color: #dc2626; }
/* <fs-field clearable="true"> (clearable-field.js): the "x" that empties a pre-filled default in
   one click. The wrapper is the positioning context; padding-right on the input reserves the
   button's own width so a long value scrolls under it rather than sitting behind it. The button
   is hidden by JS whenever the field is empty, so it never offers to clear nothing. */
.fs-field-clearable { position: relative; display: block; }
.fs-field-clearable input { width: 100%; padding-right: 32px; }
.fs-field-clear {
  position: absolute; top: 50%; right: 6px; transform: translateY(-50%);
  width: 22px; height: 22px; padding: 0; border: none; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 16px; line-height: 1; font-family: inherit;
  background: transparent; color: var(--muted); cursor: pointer;
}
.fs-field-clear:hover, .fs-field-clear:focus-visible {
  background: var(--surface-soft); color: var(--ink);
}
.fs-field-clear[hidden] { display: none; }
/* Required fields use the browser's native :invalid state (a required input/select is invalid
   while empty) so the red outline needs no JS and clears itself the moment a value is entered. */
.fs-field input:required:invalid,
.fs-field select:required:invalid,
.fs-field textarea:required:invalid { border-color: #dc2626; }
/* A grid/flex item's default min-width is its content's natural size, not 0 — so a <select>
   (whose "content" some browsers, notably Safari, size by its widest option) can stay
   narrower than the width:100% rule asks for, while a same-row <input> doesn't show the
   problem. Letting the item and the select itself shrink below that natural minimum is the
   standard fix. */
.admin-create-grid > .fs-field { min-width: 0; }
.fs-field select { min-width: 0; }
/* This field has no <label> above its button (the button's own text already says what it does),
   unlike its siblings, so it doesn't need .admin-create-grid's row-wide align-items:end (bottom
   alignment) -- that would leave it visibly offset against label+input siblings that are shorter
   overall. Centering it vertically within the row instead lines its middle up with theirs. */
/* A table cell naming a school: its logo beside its name. Was an inline style repeated in two
   copies of the same renderer; it is one control now (FiveStarSchoolLogo.cell). */
.school-cell { display: flex; align-items: center; gap: 8px; }
/* The Schools page's Mark / Unmark result line (admin-schools.js), placed under the table's ribbon:
   what the last press did, its Undo, and the schools it left alone. flex: none so the table's
   scrolling area below keeps the rest of the height. */
.school-sport-status {
  flex: 0 0 auto;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px 12px;
  padding: 8px 10px;
  border-bottom: 1px solid var(--line);
  background: color-mix(in srgb, var(--accent) 6%, var(--card));
}
.school-sport-status[hidden] { display: none; }
.school-sport-status-skipped { flex: 1 1 100%; margin: 0; padding-left: 18px; max-height: 8em; overflow-y: auto; }
.school-sport-status-skipped:empty { display: none; }
/* "High schools: all. Colleges: basketball only." with its Show all colleges button
   (school-catalog-table.js renderCollegeSportLine), under the Schools page's search box and the
   search window's. Wraps on a phone so the button drops under the words. */
.school-college-line {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
  margin-top: 6px;
}
.school-college-line[hidden] { display: none; }
.school-college-line .btn[hidden] { display: none; }
/* The type-ahead's last option, which switches the search rather than choosing a school. */
.fs-lookup-option--colleges { font-weight: 600; color: var(--accent-ink, inherit); }
/* One field on a form page: its label above it, the control full width beneath.

   The generic version of what media.css's .media-add-row does for the Add media page. A form laid
   out as a row of grid cells -- which is what the offers form used to be, wedged above its own
   table -- reads as a filter bar: the labels sit at different heights, a checkbox group wraps into
   the field beside it, and the primary button ends up level with an input rather than under the
   form. Stacked, every field starts at the same left edge and the eye goes straight down. */
/* An empty validation summary must not draw anything.

   `asp-validation-summary` always renders its div; it marks it `validation-summary-valid` when
   there is nothing to report and `validation-summary-errors` when there is. Five pages carried
   `class="flash flash-error"` on that div unconditionally, so every one of them opened with a bare
   pink bar under the heading -- a warning about nothing, which reads as a page that failed to
   finish loading. */
.validation-summary-valid { display: none; }

.fs-form-page { max-width: 720px; }
/* A form split into two columns, for the case where one field wants height and the rest want a
   line each. Stacked in one column, a note box tall enough to be useful pushes the buttons off the
   screen and leaves half the page empty beside it.

   auto-fit rather than a fixed 1fr 1fr, per .cursor/rules/responsive-ui.mdc: it collapses to a
   single column on its own once there is no room for two, with no breakpoint to keep in step.
   Stretch, not start, so the columns are equal height and the note can fill its own. */
.fs-form-split {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
  gap: 0 24px;
}
/* Without this a long unbroken value in either column widens the track instead of wrapping. */
.fs-form-split > * { min-width: 0; }
/* The tall column: its field fills whatever height the other column ends up being. */
.fs-form-fill,
.fs-form-fill > .fs-form-row { display: flex; flex-direction: column; height: 100%; }
.fs-form-fill textarea { flex: 1 1 auto; min-height: 180px; resize: vertical; }
.fs-form-row { margin-bottom: 18px; }
.fs-form-row > label,
.fs-form-row > .fs-form-label { display: block; font-weight: 600; margin-bottom: 6px; }
/* A fieldset used purely to name a group of controls: the browser's border, padding and min-width
   would draw a box inside the form's own box, so they are cleared and the legend reads as a label. */
.fs-fieldset-plain { border: 0; margin: 0 0 18px; padding: 0; min-inline-size: 0; }
.fs-fieldset-plain > legend { padding: 0; }
.fs-form-row input[type="text"],
.fs-form-row input[type="date"],
.fs-form-row input[type="url"],
.fs-form-row select,
.fs-form-row textarea { width: 100%; }
/* The hint under a field. Muted and small, because it explains the control rather than labelling
   it -- read only when the label was not enough. */
.fs-form-hint { margin: 6px 0 0; font-size: 13px; color: var(--muted); }
.school-picker-field { align-self: center; }
/* Separates the Save button from the settings panel above it, so it reads as the action for the
   whole form rather than as the last control of whichever section is open. */
.settings-save-row { margin-top: 20px; }
/* The "Search for school" trigger (school-picker-dialog.js) opens a dialog rather than accepting
   direct input, so it reads as a real button — height/padding still match the box model above
   (10px vertical padding, 8px radius) so it lines up with the sibling inputs/selects in the same
   .admin-create-grid row. */
/* Neutral grey rather than the solid green it used to be: this sits mid-form among plain inputs,
   and a saturated fill there reads as the page's primary action when it is only one field's
   picker. Themed tokens rather than fixed hexes so it stays neutral under every palette, the way
   the surrounding inputs already do. */
.school-picker-trigger {
  width: 100%; min-width: 0; padding: 10px; border: 1px solid var(--line); border-radius: 8px;
  font-size: 14px; font-weight: 600; font-family: inherit; text-align: center;
  background: var(--surface-soft); color: var(--ink); cursor: pointer;
}
.school-picker-trigger:hover, .school-picker-trigger:focus-visible {
  background: var(--line);
}
/* school-picker-dialog.js: the shared .msd width (min(480px, ...)) was sized for
   _DataFilterDialog's checkbox list, not a data table — a real table needs more room. */
#schoolPickerDialog { width: min(1100px, calc(100vw - 24px)); max-width: calc(100vw - 24px); }
/* The scrollbars are drawn, always. macOS overlay scrollbars appear only while scrolling, so a
   dialog whose other twenty columns sit to the right showed nothing that said so (Ron,
   2026-09-21). Styling the WebKit scrollbar switches Chrome, Safari and Edge to a classic one
   that is visible at rest; Firefox keeps its own. */
#schoolPickerDialogTable .athlete-table-wrap::-webkit-scrollbar { height: 12px; width: 12px; }
#schoolPickerDialogTable .athlete-table-wrap::-webkit-scrollbar-track { background: var(--surface-soft, var(--card)); border-radius: 6px; }
#schoolPickerDialogTable .athlete-table-wrap::-webkit-scrollbar-thumb { background: var(--muted); border-radius: 6px; border: 3px solid var(--surface-soft, var(--card)); }
#schoolPickerDialogTable .athlete-table-wrap { scrollbar-width: auto; scrollbar-color: var(--muted) transparent; }
/* sortable-table.js's table has its own internal scroll container (.athlete-table-wrap), already
   sized to a fixed max-height so it's independently scrollable (both directions — it's plain
   overflow:auto) regardless of its ancestors' own sizing. Giving .msd-list a matching fixed
   max-height instead of letting it scroll on its own (overflow:visible here) means there's exactly
   one scroll container, and it doesn't depend on a multi-level flex height chain resolving
   correctly (that turned out to not reliably give the table a scrollable area at all). */
#schoolPickerDialogTable .athlete-table-wrap { max-height: min(55vh, 480px); }
/* .msd-list's base rule (multi-select-dialog.css) sets overflow-x: hidden as its own longhand,
   separate from overflow-y: auto -- not a combined overflow:auto shorthand. That's an ancestor
   clip boundary on top of whatever .athlete-table-wrap does internally, so it's forced open here
   explicitly (not just via the shorthand override below, in case the longhand was what kept
   winning) rather than assumed away. */
#schoolPickerDialog [data-school-picker-list] {
  overflow: visible !important;
  overflow-x: visible !important;
  overflow-y: visible !important;
  padding: 12px 16px;
  max-height: min(58vh, 510px);
}
/* .athlete-table-wrap is a flex item (flex:1 1 auto) whose default min-width is its content's
   natural size -- the exact same Flexbox gotcha the <select> width fix above deals with, just on
   this axis. With ~27 columns, that natural size is far wider than the dialog, so without a fix
   the wrap just grows to fit the whole table instead of staying put and letting the table scroll
   inside it -- no horizontal scrollbar ever appears. Forced with !important because narrower
   attempts at this same fix (min-width:0, then width:0/min-width:100%/max-width:100% without
   !important) did not hold up in practice; every ancestor in the chain needs the same treatment,
   or the same refusal-to-shrink just moves up one level. */
#schoolPickerDialogTable,
#schoolPickerDialogTable .athlete-table-shell {
  min-width: 0 !important;
}
#schoolPickerDialogTable .athlete-table-wrap {
  width: 0 !important;
  min-width: 100% !important;
  max-width: 100% !important;
  overflow-x: auto !important;
  overflow-y: auto !important;
}
/* Forces the overflow condition itself to be unambiguous, independent of whether width:max-content
   (the table's own base rule) is correctly summing ~27 columns' intrinsic widths in every browser
   -- a fixed, generously-large value that's wider than any realistic viewport guarantees the wrap
   above always has something to scroll to, regardless of that computation. */
#schoolPickerDialogTable .athlete-table { min-width: 2400px !important; }
/* COLUMN WIDTHS, AND CELLS THAT WRAP.
   Under the default automatic table layout a cell's width is decided by its longest unbroken
   content, and max-width on a cell is ignored outright -- so one school with a long official name
   stretched its column and squeezed every other, and nothing could be done about it from CSS. Fixed
   layout is what makes a width declaration mean anything, the same fix and for the same reason as
   #mappingColumnsTable above. Widths are declared on the header cells because fixed layout takes
   the whole column's width from the first row. The total is deliberately wider than the dialog: the
   point is not to fit 27 columns into the panel, it is to give them honest widths and let the wrap
   scroll sideways. */
#schoolPickerDialogTable .athlete-table { table-layout: fixed !important; }
#schoolPickerDialogTable .athlete-table th,
#schoolPickerDialogTable .athlete-table td {
  white-space: normal;
  overflow-wrap: anywhere;
  vertical-align: top;
}
/* A default every column gets, then the few that carry real prose get more. Without a default,
   fixed layout divides the declared width equally and a two-letter State column is as wide as the
   school's name. */
#schoolPickerDialogTable .athlete-table thead th { width: 110px; }
#schoolPickerDialogTable .athlete-table thead th[data-col="name"],
#schoolPickerDialogTable .athlete-table thead th[data-col="officialName"] { width: 260px; }
/* Rows sized for reading down a list; the 28px logo below does not set their height. */
#schoolPickerDialogTable .athlete-table td { padding-top: 8px; padding-bottom: 8px; }
/* The Logo column is back (plan school_sports_and_logos §6b, R5), at the type-ahead's 28px rather
   than the Schools page's 40px, in a narrow first column, so it does not make the rows tall again
   or take the School column's width. */
#schoolPickerDialogTable .athlete-table thead th[data-col="logo"] { width: 48px; }
#schoolPickerDialogTable .athlete-table .athlete-table-photo,
#schoolPickerDialogTable .athlete-table .athlete-table-avatar { width: 28px; height: 28px; font-size: 11px; }
/* Whole, not cropped, as in the type-ahead's list, so a wide wordmark reads the same in both. */
#schoolPickerDialogTable .athlete-table .athlete-table-photo { object-fit: contain; object-position: center; }
#schoolPickerDialogTable .athlete-table thead th[data-col="address"] { width: 220px; }
#schoolPickerDialogTable .athlete-table thead th[data-col="city"],
#schoolPickerDialogTable .athlete-table thead th[data-col="conference"],
#schoolPickerDialogTable .athlete-table thead th[data-col="sports"],
#schoolPickerDialogTable .athlete-table thead th[data-col="website"] { width: 160px; }
#schoolPickerDialogTable .athlete-table thead th[data-col="pick"] { width: 48px; }
#schoolPickerDialogTable .athlete-table thead th[data-col="state"],
#schoolPickerDialogTable .athlete-table thead th[data-col="country"],
#schoolPickerDialogTable .athlete-table thead th[data-col="isJuco"] { width: 80px; }
/* col-find-input's max-width:120px (site.css, shared with the dense wide admin tables) assumes a
   narrow column; here several columns (School, Official name, ...) render much wider than that,
   leaving the find box looking short-changed inside its own cell. Filling the cell instead looks
   right regardless of how wide that particular column ends up being. */
#schoolPickerDialogTable .col-find-input { max-width: none; width: 100%; }
/* The School column's own Find box is the search box on top of the dialog, seen twice (Ron,
   2026-09-22). The column keeps `find: true` -- the top box drives it through setTextFind -- and
   only its in-header input is hidden; the other columns' filters stay. */
#schoolPickerDialogTable .col-find-input[data-find-col="name"] { display: none; }
/* Matches .athlete-table-shell's card look (border/radius/background) so the detail view reads as
   a panel the same way the list view's table does, instead of bare text floating in the dialog. */
.school-picker-detail-fields {
  display: grid; grid-template-columns: max-content 1fr; gap: 10px 20px; align-items: baseline;
  margin: 0; padding: 18px 20px; border: 1px solid var(--line); border-radius: 14px; background: var(--card);
}
.school-picker-detail-fields dt { font-weight: 600; font-size: 12px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.3px; }
.school-picker-detail-fields dd { margin: 0; }
.admin-athlete-header {
  display: flex;
  flex-wrap: wrap;
  gap: 12px 20px;
  align-items: flex-start;
  justify-content: space-between;
  margin-bottom: 16px;
}
.admin-athlete-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}
.admin-athletes-bulk {
  justify-content: flex-end;
}
.admin-athletes-bulk .ribbon-meta {
  margin-right: 4px;
  color: var(--muted);
  font-size: 0.92rem;
}
.admin-athletes-bulk .btn[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
}
.admin-athletes-bulk .btn.toolbar-action--ready:not([disabled]) {
  box-shadow: 0 0 0 2px color-mix(in srgb, var(--navy) 25%, transparent);
}
.admin-athletes-table .admin-col-check {
  width: 48px;
  text-align: center;
  vertical-align: middle;
}
.admin-athletes-table .admin-col-photo {
  width: 56px;
  text-align: center;
  vertical-align: middle;
}
.admin-check-label {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 44px;
  min-height: 44px;
  margin: 0;
  cursor: pointer;
}
.admin-check-label input {
  width: 18px;
  height: 18px;
  margin: 0;
}
.admin-athlete-photo,
.admin-athlete-photo-fallback {
  width: 40px;
  height: 40px;
  border-radius: 10px;
  object-fit: cover;
  display: inline-grid;
  place-items: center;
}
.admin-athlete-photo-fallback {
  background: linear-gradient(145deg, var(--navy), var(--blue));
  color: #fff;
  font-weight: 700;
  font-size: 0.95rem;
}
.admin-filter-row th {
  padding-top: 6px;
  padding-bottom: 10px;
  vertical-align: top;
  border-bottom: 1px solid var(--line);
}
.admin-filter-row .msd-trigger {
  width: 100%;
  min-height: 40px;
  max-width: 100%;
}
@media (max-width: 720px) {
  .admin-athletes-bulk {
    width: 100%;
    justify-content: stretch;
  }
  .admin-athletes-bulk .btn {
    flex: 1 1 auto;
    min-height: 44px;
  }
  .admin-athletes-bulk .ribbon-meta {
    width: 100%;
  }
}
.admin-athlete-section-head {
  display: flex;
  flex-wrap: wrap;
  gap: 8px 16px;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 12px;
}
.admin-athlete-section-head h2 { margin: 0; }
/* Preserves report line breaks without letting a long line widen the page. */
.admin-narrative {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  background: var(--surface-soft);
  border: 1px solid var(--line);
  border-radius: 10px;
  padding: 12px;
  font: inherit;
  margin: 0;
}
.admin-pager { display: flex; flex-wrap: wrap; align-items: center; gap: 12px; margin-top: 12px; }
/* Keeps a long quarantined import row from stretching the operations table. */
.metric-raw-row { display: inline-block; max-width: 40ch; overflow-wrap: anywhere; font-size: 13px; }

.evaluation-compare-grid { display: grid; grid-template-columns: 1fr; gap: 16px; }
.delta-up { color: #067647; font-weight: 600; }
.delta-down { color: #b42318; font-weight: 600; }
.delta-flat { color: var(--muted); }

@media (min-width: 720px) {
  .evaluation-compare-grid { grid-template-columns: 1fr 1fr; }
}

/* Athlete comparison report (Views/Athletes/Compare.cshtml): a fixed, Amazon-style attribute
   matrix for up to five athletes, opened from the Search dashboard's "Compare report" action. */
.compare-report-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 16px;
}
.compare-report-notice { border-color: var(--accent); }
.compare-report-header {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 12px;
  margin-bottom: 20px;
}
.compare-report-header-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 4px;
  padding: 16px 12px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--card);
  color: inherit;
  text-decoration: none;
}
.compare-report-header-card:hover, .compare-report-header-card:focus-visible {
  border-color: var(--accent);
}
.compare-report-photo {
  width: 64px;
  height: 64px;
  border-radius: 50%;
  object-fit: cover;
  display: flex;
  align-items: center;
  justify-content: center;
  font-weight: 700;
  font-size: 20px;
}
.compare-report-name { font-weight: 700; }
/* Keeps the row-label column readable while a wide comparison table scrolls horizontally on phones. */
.compare-report-table th:first-child,
.compare-report-table td:first-child {
  position: sticky;
  left: 0;
  background: var(--card);
  z-index: 1;
  min-width: 160px;
  text-align: left;
}
.compare-report-row-description { margin-top: 2px; font-size: 12px; }
.compare-report-evidence {
  margin: 6px 0 0;
  padding-left: 10px;
  border-left: 2px solid var(--line);
  font-size: 13px;
  font-style: italic;
  color: var(--muted);
}
/* Scout ratings rank banner. Gold/silver/bronze are real medal colors, so they are fixed hex
   values rather than theme tokens — a gold medal reads as gold in every color preference the same
   way it would on a physical podium. Rank 4+ uses the theme's own chip colors instead, so a ranked
   cell without a medal still reads as "ranked" without competing against the three fixed hues. */
.compare-rank-badge {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  padding: 4px 10px;
  border-radius: 999px;
  font-weight: 700;
  font-size: 13px;
  line-height: 1.4;
  white-space: nowrap;
}
.compare-rank-gold { background: #fdecc8; color: #7a4e00; }
.compare-rank-silver { background: #e6e8eb; color: #454c52; }
.compare-rank-bronze { background: #f3ddc8; color: #7a4419; }
.compare-rank-other { background: var(--chip-bg); color: var(--chip-ink); }

@media print {
  .topnav, .profile-actions, .no-print { display: none !important; }
  body { background: white; }
  .container { max-width: none; padding: 0; }
  .card, .profile-hero { box-shadow: none; border: 1px solid #ccc; }
  /* "More season averages" stays collapsed on screen but should always show up on a printed report. */
  details:not([open]) summary { display: none; }
  details > *:not(summary) { display: block !important; }
}

/* A row of toggle chips backed by real checkboxes — used for the school directory's school-type
   and sport filters, on the settings page and above the directory table. The input stays in the
   DOM (and focusable) rather than being replaced by a styled div, so the keyboard and screen
   readers get a plain checkbox and the label does the visual work. */
.fs-check-row {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}
/* The offer form's one stage row, as Ron drew it: "Visit ☐  Visit date [..]   Committed ☐
   Committed date [..]" on one line, each tick beside its own date; the two pairs wrap onto two
   lines on a phone. */
.fs-offer-stages { display: flex; flex-wrap: wrap; gap: 12px 32px; align-items: center; }
.fs-offer-stage { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; }
.fs-offer-stage label:not(.fs-check) { margin: 0; font-weight: 600; }
.fs-offer-stage input[type="date"] { width: auto; }
.fs-offer-stage input[type="date"]:disabled { opacity: 0.55; }
.fs-check {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  padding: 7px 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  font-size: 13px;
  font-weight: 600;
  color: var(--muted);
  cursor: pointer;
  user-select: none;
  transition: background 120ms ease, color 120ms ease, border-color 120ms ease;
}
.fs-check:hover { border-color: color-mix(in srgb, var(--accent) 55%, var(--line)); }
.fs-check:has(input:checked) {
  background: color-mix(in srgb, var(--accent) 16%, var(--card));
  border-color: var(--accent);
  color: var(--text);
}
/* Focus has to land on the label, because the checkbox itself is visually inside it. */
.fs-check:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.fs-check input[type="checkbox"] { accent-color: var(--accent); margin: 0; }
/* A chip's name stays on one line.

   The chip is a pill sized to its text, so a wrapped name ("Track and Field (Outdoor)" over three
   lines) made one chip three times the height of its neighbours and left the row of them looking
   like a broken grid rather than a set of equal choices. The row already wraps, so a longer name
   costs a wider chip and, at worst, one more row -- which is the right thing to spend. */
.fs-check > span { white-space: nowrap; }

/* The school directory's Sports cell. Filtering lives in the column header like every other
   column; this is just the display. */
.fs-sport-cellinfo { white-space: normal; }
/* "Not on file" is a coverage statement, not a value, so it reads as muted rather than as data —
   and it is the queue our coaching staff works through. */
.fs-sport-unknown { font-style: italic; }

/* One team on a school's detail page. Read-only, so it is a tag rather than the toggle chip
   .fs-check renders — same shape, no hover or checked state to imply it can be clicked. */
.fs-sport-tag {
  display: inline-flex;
  align-items: baseline;
  gap: 8px;
  padding: 7px 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--card);
  font-size: 13px;
}
.fs-sport-tag .meta { font-size: 12px; }

/* The school detail page's sport checklist. A table rather than a chip row because it is a grid:
   one row per sport, one column per team, and a coach reads down a column to check off a school. */
.fs-sport-grid {
  width: 100%;
  max-width: 560px;
  border-collapse: collapse;
}
.fs-sport-grid th,
.fs-sport-grid td {
  padding: 7px 10px;
  border-bottom: 1px solid var(--line);
  text-align: left;
  font-size: 13px;
}
.fs-sport-grid thead th {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
  border-bottom-width: 2px;
}
.fs-sport-grid thead th:not(:first-child),
.fs-sport-grid tbody td { text-align: center; }
.fs-sport-grid tbody td:last-child { text-align: left; }
.fs-sport-grid tbody th { font-weight: 600; }
.fs-sport-grid tbody tr:hover { background: color-mix(in srgb, var(--accent) 6%, transparent); }
/* A generous hit area, so checking off fifteen sports is not fifteen precise clicks. */
.fs-sport-cell {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  min-height: 28px;
  cursor: pointer;
}
.fs-sport-cell input[type="checkbox"] {
  width: 17px;
  height: 17px;
  accent-color: var(--accent);
  cursor: pointer;
}
/* Marks a sport the federal survey already reports, so a coach can tell a confirmed program from
   one they are establishing themselves. */
.fs-sport-evidence {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  background: color-mix(in srgb, var(--accent) 15%, transparent);
  font-size: 11px;
  font-weight: 600;
  color: var(--muted);
}
/* Reachable by a screen reader, invisible on screen — for a column header that would be noise as
   a visible label but that a table still needs so its columns are all named. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* A read-only facts list: label above value, wrapping into as many columns as fit. Used by the
   athlete profile's Profile and Contact tabs, which display what the admin pages edit. */
.fs-facts {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 12px 20px;
  margin: 0 0 18px;
}
/* For values that are sentences rather than fields — awards, injury history — where wrapping into
   narrow columns would make them unreadable. */
.fs-facts-wide { grid-template-columns: 1fr; }
.fs-fact dt {
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
.fs-fact dd {
  margin: 2px 0 0;
  font-size: 14px;
  overflow-wrap: anywhere;
}

/* A field nobody has filled in still shows, so the reader can see what could be known about an
   athlete rather than only what happens to be recorded. Muted and italic so it never reads as
   data — "Not on file" is a statement about the record, not about the athlete. */
.fs-fact-placeholder {
  color: var(--muted);
  font-style: italic;
  opacity: 0.75;
}
.fs-fact-empty dt { opacity: 0.75; }
/* A record locator: tabular figures so the digits line up when read aloud or transcribed, and
   select-all so one click grabs the whole value, since being copied is the point of it. */
.fs-fact-code {
  font-family: var(--font-mono);
  font-feature-settings: "tnum" 1;
  letter-spacing: 0.03em;
  user-select: all;
  cursor: text;
}

/* A heading with a record locator pushed to its far edge. Baseline alignment sits the number on
   the heading's baseline rather than floating it against the cap height; wrapping keeps it below
   the heading on a narrow screen instead of squeezing both. */
.fs-heading-with-id {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: 4px 12px;
}
/* Small and quiet on purpose: agents read a profile for the athlete, and admins and engineers are
   the ones who occasionally need to quote the number. Tabular figures so the digits line up when
   read aloud, and select-all so one click grabs it, because being copied is the point of it. */
.fs-heading-id {
  font-family: var(--font-mono);
  font-feature-settings: "tnum" 1;
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.03em;
  color: var(--muted);
  white-space: nowrap;
  user-select: all;
  cursor: text;
}
/* The label is not part of the value, so it stays out of the select-all copy target. */
.fs-heading-id-label {
  margin-right: 6px;
  font-family: inherit;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  opacity: 0.8;
  user-select: none;
}

/* Add-athletes page: the duplicate warning, the CSV box, and the per-row import report. */
.fs-duplicate-list { margin: 8px 0; padding-left: 18px; }
.fs-duplicate-list li { margin-bottom: 4px; }
.fs-import-textarea,
.fs-import-template {
  width: 100%;
  font-family: var(--font-mono);
  font-size: 12px;
}
.fs-import-template {
  margin: 0;
  padding: 10px;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: color-mix(in srgb, var(--card) 92%, var(--bg));
  overflow-x: auto;
  white-space: pre;
}
/* THE IMPORT FLOW'S STEP STRIP (TagHelpers/StepsTagHelper.cs).

   Four steps across three screens. Without a strip each screen is somewhere a person arrives with no
   sense of what came before or how much is left, which is the difference between a wizard and a
   sequence of unrelated pages.

   One segmented bar rather than four separate pills: the steps are a single sequence, and drawing
   them as one object with divisions says so more plainly than four shapes with gaps between them.
   The current segment is filled, so "you are here" survives being glanced at.

   State is carried by fill and by a tick AND by visually hidden text, because neither a colour nor a
   tick reaches everybody. */
.fs-steps {
  display: inline-flex;
  flex-wrap: wrap;
  align-items: stretch;
  list-style: none;
  margin: 0 0 18px;
  padding: 0;
  font-size: 14px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 12px;
  overflow: hidden;
  box-shadow: 0 1px 2px var(--shadow);
}
.fs-step {
  display: inline-flex;
  color: var(--muted);
  background: var(--card);
}
/* The padding lives on the inner element so that, when the step is a link, the whole segment is the
   target rather than just the words. */
.fs-step-inner {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 11px 20px;
  color: inherit;
  text-decoration: none;
  width: 100%;
}
a.fs-step-inner:hover { background: color-mix(in srgb, var(--blue) 7%, transparent); }
a.fs-step-inner:hover .fs-step-label { text-decoration: underline; }
a.fs-step-inner:focus-visible { outline: 2px solid var(--blue); outline-offset: -2px; }
.fs-step.is-current a.fs-step-inner:focus-visible { outline-color: #fff; }
/* A hairline between segments rather than a gap, so the bar reads as one control. */
.fs-step + .fs-step { border-left: 1px solid var(--line); }
.fs-step-number {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: var(--surface-soft);
  font-size: 13px;
  font-weight: 700;
  color: var(--muted);
}
.fs-step-label { color: inherit; }
.fs-step.is-done { color: var(--ink); }
.fs-step.is-done .fs-step-number {
  background: color-mix(in srgb, var(--blue) 14%, transparent);
  color: var(--blue);
}
.fs-step.is-current {
  background: var(--blue);
  color: #fff;
  font-weight: 700;
}
.fs-step.is-current .fs-step-number {
  background: color-mix(in srgb, #fff 22%, transparent);
  color: #fff;
}
.fs-step.is-current + .fs-step { border-left-color: transparent; }
@media (max-width: 640px) {
  /* Wraps to two rows on a phone; the divider would then sit at the start of a line, which reads as
     a stray mark rather than a division. */
  .fs-steps { display: flex; }
  .fs-step { flex: 1 1 auto; }
  .fs-step-inner { padding: 10px 14px; }
}
/* "START FROM THE TEMPLATE" ON THE IMPORT ATHLETES TAB.

   A picture of the file the button hands over, rather than a description of it. Its three rows are
   read from the generator, so the picture cannot drift from the file -- which is how the block this
   replaces ended up showing a 72-column heading above a 13-value example row.

   The table is wide by nature and gets its horizontal scroller from TableScrollTagHelper, which
   wraps every table in the product; nothing is needed here for that. */
.fs-template-card { margin-bottom: 14px; }
.fs-template-preview {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
  border: 1px solid var(--line);
  border-radius: 10px;
  overflow: hidden;
}
.fs-template-preview th,
.fs-template-preview td {
  padding: 8px 12px;
  border-bottom: 1px solid var(--line);
  text-align: left;
  vertical-align: middle;
  white-space: nowrap;
}
.fs-template-preview thead th {
  background: var(--surface-soft);
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
/* The row number column: narrow, quiet, and not something to read across. */
.fs-template-preview tbody th[scope="row"] {
  width: 1%;
  color: var(--muted);
  font-weight: 400;
}
.fs-template-preview tbody tr:last-child th,
.fs-template-preview tbody tr:last-child td { border-bottom: 0; }
/* The format row is tinted because it is the one row that is not data, and the tint is what makes
   that legible at a glance rather than only in the note underneath. */
.fs-template-preview tr.is-format-row { background: color-mix(in srgb, var(--warn, #e0a300) 10%, transparent); }
.fs-template-preview code {
  font-size: 12px;
  padding: 2px 6px;
  border-radius: 5px;
  background: color-mix(in srgb, var(--card) 80%, var(--bg));
}
/* The note carries the two things somebody has to know before uploading, so it is set apart rather
   than left as one more paragraph of prose. */
.fs-template-note {
  margin: 12px 0 0;
  padding: 2px 0 2px 14px;
  border-left: 3px solid color-mix(in srgb, var(--warn, #e0a300) 70%, transparent);
  color: var(--text);
  font-size: 13px;
  line-height: 1.55;
}
/* THE COLUMN MAPPING SCREEN.

   Its table is the shared sortable one, so most of the styling is inherited. What is here is the
   required-field checklist and the field picker inside a row -- a select in a table cell defaults to
   a size that makes every row twice as tall as it needs to be, which on a fifty-column file is the
   difference between a screen you can scan and one you scroll through twice. */
/* The list of fields this file does not carry. Two narrow columns in as many lanes as fit, because
   the reader is scanning for one name rather than reading it through -- a single tall column would
   make them scroll past sixty rows to find out that theirs is not among them. */
.fs-unsourced {
  width: 100%;
  border-collapse: collapse;
  font-size: 13px;
}
.fs-unsourced th,
.fs-unsourced td {
  padding: 5px 10px;
  border-bottom: 1px solid var(--line);
  text-align: left;
}
.fs-unsourced thead th {
  font-size: 11px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
.fs-unsourced code {
  font-size: 12px;
  padding: 1px 6px;
  border-radius: 5px;
  background: color-mix(in srgb, var(--card) 80%, var(--bg));
}

/* The mapping table's heading row: what the file contains on the left, what to do about it on the
   right. The action is up here because the table below is routinely taller than the window, and a
   button under it is one somebody has to scroll past everything to reach. */
/* A panel's title with its own action on the same line — the mapping screen's heading row, reused
   for the review screen's "Ready for import" so the Import button sits with the rows it acts on
   rather than in the page header beside Close import. */
.fs-panel-heading {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 16px;
  margin: 0 0 10px;
}
.fs-mapping-heading {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 10px 16px;
  margin: 0 0 4px;
}
.fs-mapping-actions {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  margin-left: auto;
}
@media (max-width: 640px) {
  /* Stacked on a phone, with the action full width so it is not a small target beside long text. */
  .fs-mapping-actions { width: 100%; justify-content: space-between; }
}

/* The six fields the import cannot run without, each as a card naming what satisfies it.

   Cards rather than a line of chips because each one carries two facts -- the field, and the column
   it came from -- and the second is the one somebody checks against their own spreadsheet. Green and
   red do the scanning; the tick, the exclamation mark and a visually hidden word do the rest, so the
   state does not rest on colour alone. */
.fs-required-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(210px, 1fr));
  gap: 10px;
  list-style: none;
  margin: 0;
  padding: 0;
}
.fs-required-card {
  display: flex;
  flex-direction: column;
  gap: 3px;
  padding: 11px 14px;
  border-radius: 10px;
  border: 1px solid var(--line);
  background: var(--card);
}
.fs-required-name { font-weight: 700; font-size: 14px; }
.fs-required-source { font-size: 13px; color: var(--muted); }
.fs-required-card.is-matched {
  border-color: color-mix(in srgb, var(--ok, #2f7d5d) 35%, var(--line));
  background: color-mix(in srgb, var(--ok, #2f7d5d) 8%, var(--card));
}
.fs-required-card.is-matched .fs-required-name { color: color-mix(in srgb, var(--ok, #2f7d5d) 80%, var(--text)); }
.fs-required-card.is-unmatched {
  border-color: color-mix(in srgb, var(--danger, #b3261e) 30%, var(--line));
  background: color-mix(in srgb, var(--danger, #b3261e) 6%, var(--card));
}
.fs-required-card.is-unmatched .fs-required-name,
.fs-required-card.is-unmatched .fs-required-source {
  color: color-mix(in srgb, var(--danger, #b3261e) 75%, var(--text));
}
/* Sized to its content rather than filling the cell, so the column stays as narrow as the longest
   field name instead of stretching to whatever space is going. */
.athlete-table select.fs-map-target,
.fs-import-report select {
  max-width: 100%;
  padding: 4px 8px;
  font-size: 13px;
  line-height: 1.4;
}
.athlete-table td:has(.fs-map-target) { padding-top: 4px; padding-bottom: 4px; }

/* WIDTHS ON THE MAPPING TABLE, AND WHY THEY NEED table-layout: fixed.

   A questionnaire heading is a whole question -- "Family Member(s) who Played Basketball in College"
   -- and a sample value can be an address or a list of awards. Left alone, those two columns push
   the table several screens wide and the drop-down somebody came to use ends up off the right-hand
   edge behind a horizontal scroll.

   Capping them with max-width did nothing, which is the mistake worth recording: under the
   automatic table layout a cell's max-width is advisory and the browser sizes columns from their
   content regardless. Fixed layout is what makes a declared width binding. It is safe here because
   this table has a known, small set of columns -- unlike the roster tables the same control renders
   elsewhere, where the column set is chosen by the reader. */
#mappingColumnsTable .athlete-table {
  table-layout: fixed;
  width: 100%;
  min-width: 0;
}
#mappingColumnsTable .athlete-table th,
#mappingColumnsTable .athlete-table td {
  white-space: normal;
  overflow-wrap: anywhere;
  vertical-align: middle;
}
#mappingColumnsTable .athlete-table th:nth-child(1),
#mappingColumnsTable .athlete-table td:nth-child(1) { width: 64px; }
#mappingColumnsTable .athlete-table th:nth-child(2),
#mappingColumnsTable .athlete-table td:nth-child(2) { width: 26%; }
#mappingColumnsTable .athlete-table th:nth-child(3),
#mappingColumnsTable .athlete-table td:nth-child(3) { width: 24%; }
#mappingColumnsTable .athlete-table th:nth-child(4),
#mappingColumnsTable .athlete-table td:nth-child(4) { width: 250px; }
#mappingColumnsTable .athlete-table th:nth-child(5),
#mappingColumnsTable .athlete-table td:nth-child(5) { width: 150px; }
#mappingColumnsTable .athlete-table select.fs-map-target { width: 100%; }

/* The position column: numeric, quiet, and narrow. It exists so file order is one click away after
   sorting by status. */
.fs-map-col,
#mappingColumnsTable .athlete-table td:first-child,
.athlete-table td[data-col="col"] {
  color: var(--muted);
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
  width: 1%;
}
.fs-import-report { width: 100%; border-collapse: collapse; font-size: 13px; }
/* A file preview can carry fifteen columns. Forced to the card's width they each get squeezed until
   a phone number wraps over three lines and the table becomes unreadable, so the wide variant is
   allowed its natural width and scrolls sideways inside this wrapper instead. The wrapper, not the
   table, is what must not exceed the card — otherwise the rows spill past its rounded edge. */
.fs-table-scroll {
  overflow-x: auto;
  max-width: 100%;
  /* Keeps a focused cell reachable by keyboard when the table is scrolled off-screen. */
  scrollbar-gutter: stable;
}
/* The same bar the roster tables show, so a wide table looks the same wherever it appears.
   Styling ::-webkit-scrollbar also opts Chrome and Safari out of overlay scrollbars, which is the
   point: an overlay bar fades away, and a reader who cannot see a bar does not know the columns
   continue past the edge. */
.fs-table-scroll::-webkit-scrollbar {
  height: 12px;
  width: 12px;
}
.fs-table-scroll::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--muted) 45%, transparent);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
.fs-table-scroll::-webkit-scrollbar-track {
  background: color-mix(in srgb, var(--surface-soft) 80%, white);
  border-radius: 999px;
}
/* Paper has no scrollbar to drag. Printing a scroll container clips whatever is off to the right,
   so on paper the wrapper stops constraining and the table prints in full. */
@media print {
  .fs-table-scroll {
    overflow-x: visible;
    max-width: none;
  }
}
.fs-import-report--wide { width: max-content; min-width: 100%; }
/* Values that are meaningless once broken across lines: a wrapped phone number reads as two
   numbers, and a wrapped ZIP or code reads as a different value entirely. */
.fs-import-report--wide td, .fs-import-report--wide th { white-space: nowrap; }
/* School and person names are the two columns worth letting breathe, since they are what a reader
   is actually scanning for; they wrap rather than pushing the table arbitrarily wide. */
.fs-import-report--wide td.fs-cell-wrap { white-space: normal; min-width: 14ch; max-width: 28ch; }
.fs-import-report th, .fs-import-report td {
  padding: 6px 10px;
  border-bottom: 1px solid var(--line);
  text-align: left;
  vertical-align: top;
}
/* The outcome is the column a reader scans, so it carries the colour rather than the whole row —
   a table of forty tinted rows is harder to read than one with forty tinted words. */
.fs-import-outcome {
  display: inline-block;
  padding: 2px 8px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 700;
  white-space: nowrap;
}
.fs-import-ok   { background: color-mix(in srgb, #16a34a 22%, transparent); color: var(--text); }
.fs-import-skip { background: color-mix(in srgb, var(--muted) 22%, transparent); color: var(--muted); }
.fs-import-warn { background: color-mix(in srgb, #f59e0b 26%, transparent); color: var(--text); }
.fs-import-bad  { background: color-mix(in srgb, #dc2626 24%, transparent); color: var(--text); }

/* The delete control on an athlete's Status tab. Visually separated because it is the one action on
   the page that cannot be undone — everything else on this screen is reversible. */
.fs-danger-zone {
  border: 1px solid color-mix(in srgb, #dc2626 45%, var(--line));
  background: color-mix(in srgb, #dc2626 5%, var(--card));
}
.fs-danger-zone h2 { color: #dc2626; }
.fs-danger-list { margin: 6px 0 14px; padding-left: 20px; font-size: 14px; }
.fs-danger-list li { margin-bottom: 3px; }
.btn-danger {
  background: #dc2626;
  border-color: #dc2626;
  color: white;
}
.btn-danger:hover { background: #b91c1c; border-color: #b91c1c; }

/* "Archive a graduating class" on the athletes list. Collapsed by default: it is a once-a-year
   action that would otherwise sit permanently next to the everyday controls. */
.fs-archive-class { margin: 0 0 14px; }
.fs-archive-class summary { cursor: pointer; font-size: 13px; font-weight: 600; color: var(--muted); }
.fs-archive-class form { margin-top: 10px; }

/* The one shared dialog wwwroot/js/dialog.js uses for every alert()/confirm()/prompt() the app
   needs, so nothing falls back to unstyleable browser chrome. Sizing/backdrop/animation mirror
   .filter-conflict-dialog; `margin: auto` re-centers the dialog against the global `* { margin:
   0 }` reset the same way that one does. */
.fs-dialog {
  margin: auto;
  width: min(420px, calc(100vw - 32px));
  max-height: calc(100dvh - 32px);
  padding: 0;
  border: 1px solid var(--line);
  border-radius: 18px;
  color: var(--ink);
  background: var(--card);
  box-shadow: 0 28px 70px rgba(10, 14, 22, .32);
  overflow: auto;
  overscroll-behavior: contain;
}
.fs-dialog[open] { animation: fsDialogIn .18s ease-out both; }
@keyframes fsDialogIn {
  from { opacity: 0; transform: translateY(10px) scale(.985); }
  to { opacity: 1; transform: none; }
}
.fs-dialog::backdrop {
  background: rgba(10, 14, 22, .52);
  backdrop-filter: blur(2px);
}
.fs-dialog-form { padding: clamp(18px, 4vw, 24px); }
.fs-dialog-message { margin: 0; line-height: 1.55; }
.fs-dialog-field { margin-top: 14px; }
.fs-dialog-field input {
  width: 100%;
  min-height: 44px;
  margin: 0;
}
.fs-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: 10px;
  margin-top: 20px;
}
.fs-dialog-actions button { min-height: 44px; min-width: 92px; }

/* Progress bar for a background import. Plain and unanimated on purpose: the number underneath is
   the real information, and a bar that animates while a job is stalled is actively misleading. */
.fs-progress {
  height: 10px;
  border-radius: 999px;
  background: var(--line);
  overflow: hidden;
}
.fs-progress-bar {
  height: 100%;
  background: var(--accent, #d9a441);
  transition: width .4s ease;
}

/* The shared help disclosure: a question-mark button whose panel opens over the content below it
   rather than pushing it down. Built on <details>, so the browser supplies keyboard operation and
   the open/closed announcement and no script is involved -- it still works on a page whose
   JavaScript failed, which is when somebody most wants the help. */
.fs-help {
  position: relative;
  display: inline-block;
}
.fs-help > .fs-help-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  border: 1px solid var(--line);
  border-radius: 50%;
  background: var(--surface-soft);
  cursor: pointer;
  font-weight: 700;
  line-height: 1;
  /* The default disclosure triangle would sit beside the "?" and read as a second glyph. */
  list-style: none;
}
.fs-help > .fs-help-button::-webkit-details-marker { display: none; }
.fs-help > .fs-help-button:hover,
.fs-help[open] > .fs-help-button {
  border-color: var(--accent, #2f6f62);
  color: var(--accent, #2f6f62);
}
.fs-help > .fs-help-button:focus-visible {
  outline: 2px solid var(--focus, #2563eb);
  outline-offset: 2px;
}
/* Absolute, so opening the panel never moves the table underneath. z-index clears the sticky table
   header, which would otherwise draw over the top of it. */
.fs-help > .fs-help-panel {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 20;
  width: min(560px, calc(100vw - 32px));
  padding: 14px 18px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: 0 8px 24px var(--shadow);
  font-size: 0.9rem;
  line-height: 1.5;
  text-align: left;
  /* A long help panel scrolls itself rather than running off the bottom of a short window. */
  max-height: min(60vh, 520px);
  overflow-y: auto;
}
.fs-help-left > .fs-help-panel {
  right: auto;
  left: 0;
}
.fs-help > .fs-help-panel > :first-child { margin-top: 0; }
.fs-help > .fs-help-panel > :last-child { margin-bottom: 0; }


/* SCROLLBARS ON TOUCH DEVICES

   A phone or tablet has no pointer to grab a scrollbar with, so the bar is decoration that costs
   width — and on a list that already scrolls inside a page that scrolls, two visible bars read as a
   fault. Scrolling itself is untouched: only the indicator is hidden.

   Keyed on the pointer being coarse rather than on width, because a narrow desktop window still has
   a mouse and still wants its scrollbar. */
@media (pointer: coarse) {
  html,
  body,
  .athlete-table-wrap,
  .catalog-table-scroll,
  .fs-table-scroll,
  .fs-table-pane {
    scrollbar-width: none;          /* Firefox */
    -ms-overflow-style: none;       /* older Edge */
  }

  html::-webkit-scrollbar,
  body::-webkit-scrollbar,
  .athlete-table-wrap::-webkit-scrollbar,
  .catalog-table-scroll::-webkit-scrollbar,
  .fs-table-scroll::-webkit-scrollbar,
  .fs-table-pane::-webkit-scrollbar {
    width: 0;
    height: 0;
  }
}

/* The About panel's key/value list on Settings.
   A two-column grid rather than a table: it is a description list, and it collapses to one column
   on a narrow screen without the horizontal scroll a table would force. */
.fs-about {
  display: grid;
  grid-template-columns: max-content 1fr;
  gap: 8px 24px;
  margin: 16px 0 0;
  align-items: baseline;
}
.fs-about dt {
  font-weight: 600;
  /* The site's own muted token, which every theme redefines -- a hard-coded grey here
     would stay grey when the palette changes. */
  color: var(--muted);
}
.fs-about dd {
  margin: 0;
  /* A commit hash has no spaces and must be allowed to wrap rather than widen the page. */
  overflow-wrap: anywhere;
}
@media (max-width: 520px) {
  .fs-about { grid-template-columns: 1fr; gap: 2px 0; }
  .fs-about dd { margin-bottom: 10px; }
}
/* Shared catalog lookup: an anchored popup that remains usable in narrow form grids. */
.fs-lookup-control { position: relative; }
.fs-lookup-control > input { width: 100%; }
.fs-lookup-results { position: absolute; z-index: 30; left: 0; right: 0; max-height: 18rem; overflow-y: auto; background: var(--surface, #fff); border: 1px solid var(--border, #cbd5e1); border-radius: 0 0 8px 8px; box-shadow: 0 8px 20px rgba(15, 23, 42, .14); }
.fs-lookup-results.fs-lookup-results--above { top: auto; bottom: 100%; border-radius: 8px 8px 0 0; box-shadow: 0 -8px 20px rgba(15, 23, 42, .14); }
.fs-lookup-option { display: flex; flex-direction: column; width: 100%; padding: .65rem .75rem; border: 0; border-bottom: 1px solid var(--border, #e2e8f0); background: transparent; color: inherit; text-align: left; }
.fs-lookup-option:hover, .fs-lookup-option[aria-selected="true"] { background: var(--surface-muted, #f1f5f9); }
.fs-lookup-option small { color: var(--muted, #64748b); }
/* A school option's picture (plan school_sports_and_logos §6b): the logo, or the initials when the
   school has none, before the name. Fixed size, so a late logo does not move the list; the selector
   is qualified so .athlete-avatar's 84px (search.css, on some pages) cannot win. Options without a
   picture keep the plain column layout above. */
.fs-lookup-option.fs-lookup-option--pictured { flex-direction: row; align-items: center; gap: .6rem; }
.fs-lookup-option-text { display: flex; flex-direction: column; min-width: 0; }
.fs-lookup-option .fs-lookup-thumb { flex: 0 0 28px; width: 28px; height: 28px; margin: 0; border-radius: 50%; }
.fs-lookup-option img.fs-lookup-thumb { display: block; object-fit: contain; border: 1px solid var(--line, #e2e8f0); background: var(--card, #fff); }
.fs-lookup-option .fs-lookup-thumb--initials { display: inline-flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 700; letter-spacing: .02em; color: #fff; background: #7f8c8d; box-shadow: none; }
.fs-lookup-browse { display: inline-block; margin-top: .35rem; font-size: .9rem; }
/* A lookup with an Add button: box and button share one row and one height (the input's usual
   bottom margin moves to the row so the two boxes line up), and the button is the input's twin
   in border and radius. The results list hangs under the box only, never over the +. */
.fs-lookup-control--with-add { display: flex; flex-wrap: nowrap; gap: .5rem; align-items: stretch; margin-bottom: 14px; }
.fs-lookup-control--with-add > input { flex: 1 1 8rem; width: auto; min-width: 0; margin-bottom: 0; }
.fs-lookup-control--with-add > .fs-lookup-results { top: 100%; right: calc(44px + .5rem); }
.fs-lookup-control--with-add > input:has(~ .fs-lookup-add ~ .fs-lookup-add) ~ .fs-lookup-results { right: calc(2 * (44px + .5rem)); }
.fs-lookup-control--with-add + .fs-lookup-browse { margin: -8px 0 14px; }
.fs-lookup-add { display: inline-flex; align-items: center; justify-content: center; flex: 0 0 auto; min-width: 44px; min-height: 44px; padding: 0; border: 1px solid var(--line, #cbd5e1); border-radius: 8px; background: var(--card, #fff); color: var(--ink); cursor: pointer; }
.fs-lookup-add:hover { background: var(--surface-muted, #f1f5f9); }
.fs-lookup-add:focus-visible { outline: 2px solid var(--focus, #2563eb); outline-offset: 2px; }
.fs-lookup-add-glyph { display: inline-flex; line-height: 0; }
.profile-affiliation-panel {
  min-width: 0;
  padding: 18px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: color-mix(in srgb, var(--surface-soft) 56%, var(--card));
  box-shadow: 0 5px 16px color-mix(in srgb, var(--shadow) 52%, transparent);
}
.profile-affiliation-panel-header {
  margin-bottom: 16px;
  padding-bottom: 13px;
  border-bottom: 1px solid color-mix(in srgb, var(--line) 82%, transparent);
}
.admin-create-grid .profile-affiliation-panel h3 {
  margin: 2px 0 4px;
  padding: 0;
  border: 0;
  color: var(--ink);
  font-size: 1.05rem;
}
.profile-affiliation-panel-header > div > p:last-child {
  max-width: 680px;
  margin: 0;
  color: var(--muted);
  font-size: .82rem;
  line-height: 1.45;
}
/* The School and Team panels are titled by a real heading -- the small grey capitals of an
   eyebrow did not read as a title (Ron, 2026-09-15). */
.profile-affiliation-title,
.admin-create-grid .profile-affiliation-panel h3.profile-affiliation-title {
  margin: 0 0 4px;
  padding: 0;
  border: 0;
  color: var(--ink);
  font-size: 1.15rem;
  font-weight: 700;
  letter-spacing: 0;
  text-transform: none;
}
.profile-affiliation-eyebrow {
  margin: 0;
  color: var(--muted);
  font-size: .68rem;
  font-weight: 750;
  letter-spacing: .09em;
  text-transform: uppercase;
}
.profile-affiliation-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 220px), 1fr));
  gap: 16px;
  align-items: start;
  min-width: 0;
}
.profile-affiliation-grid > *,
.profile-affiliation-grid .fs-field { min-width: 0; }
.profile-catalog-picker { min-width: 0; }
/* The school row: a wide search, a short code box, the button. */
/* The school row: a wide search, a code box no wider than six digits need, the button. */
.profile-school-add { grid-template-columns: minmax(220px, 1fr) auto auto; }
.profile-school-ets input[type="search"],
#schoolEtsCodeShown { width: calc(8ch + 24px); max-width: calc(8ch + 24px); }
/* The applied school's details: the read-only code needs no more room than the code itself. */
.profile-school-details { grid-template-columns: minmax(200px, 2fr) auto minmax(140px, 1fr) minmax(160px, 1fr); }
@media (max-width: 720px) { .profile-school-details { grid-template-columns: minmax(0, 1fr); } }
/* The team rows: the search on its own line, then name / type / jersey. */
.profile-team-search { grid-template-columns: minmax(220px, 1fr) auto; }
.profile-team-details { grid-template-columns: minmax(200px, 2fr) minmax(140px, 1fr) minmax(100px, .7fr); }
@media (max-width: 720px) { .profile-team-details { grid-template-columns: minmax(0, 1fr); } }
@media (max-width: 720px) { .profile-school-add { grid-template-columns: minmax(0, 1fr); } }

/* The team picker's two buttons sit on the grid's baseline rather than the top, so they line up
   with the lookup input beside them instead of with its label. */
.profile-team-add-actions {
  align-items: end;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  min-width: 0;
  padding-top: 22px;
}

/* Teams gathered but not yet submitted. They read as a list of things about to happen, which is
   why they sit between the picker and the table of what already has. */
.profile-team-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
}
.profile-team-chips:empty { margin: 0; }
.profile-team-chip {
  align-items: center;
  background: var(--surface-soft, #f4f6f8);
  border: 1px solid var(--line, #d8dee4);
  border-radius: 999px;
  display: inline-flex;
  gap: 4px;
  padding: 4px 6px 4px 12px;
}
.profile-team-chip .btn {
  border-radius: 999px;
  font-size: 1rem;
  line-height: 1;
  min-height: 0;
  padding: 4px 8px;
}
.profile-team-add + .profile-team-chips + .fs-table-pane { margin-top: 16px; }
/* Narrow, because it holds at most two digits and a wider box invites the reader to wonder what
   else belongs in it.

   Left-aligned against the numeric column it sits in. The table right-aligns numeric cells, which
   is right for numerals -- they line up for comparison -- but this cell holds a box rather than a
   numeral, and a right-aligned box under a left-aligned heading reads as belonging to the column
   next door. `display: block` with `margin-right: auto` overrides the cell's text-align without
   changing it for anything else. */
/* A read-only list on the coach's profile. Plain: it is something to read, not something to sort
   or select, so it carries none of the shared table's controls. */
.fs-readonly-table {
  border-collapse: collapse;
  margin-bottom: 18px;
  width: 100%;
}
.fs-readonly-table th,
.fs-readonly-table td {
  border-bottom: 1px solid var(--line, #e3e7eb);
  padding: 8px 12px 8px 0;
  text-align: left;
}
.fs-readonly-table th {
  color: var(--muted, #667);
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
}
.fs-readonly-table tbody tr:last-child th,
.fs-readonly-table tbody tr:last-child td { border-bottom: 0; }

/* Panels stack with the same gap the administration page uses, so the two sides look like one
   product rather than two that happen to show the same athlete. */
/* The coach page's Teams & school tab: a small heading over each fact grid, the same grid every
   other Profile tab uses (2026-09-16). */
.profile-facts-heading {
  margin: 0 0 8px;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
.fs-facts + .profile-facts-heading { margin-top: 6px; }

.profile-team-jersey {
  display: block;
  margin-right: auto;
  max-width: 72px;
  padding: 4px 8px;
  text-align: left;
}

/* Three rows, then scroll. Most athletes have one or two teams and the panel should not reserve
   height for the few who have eight -- this sits inside the profile form, above the school fields,
   and a long list would push them off the screen.

   The height itself is measured in the browser and set as an inline max-height, because a row
   carrying two buttons is a different height from one that does not, and a number written here
   would be a guess that goes stale the first time the buttons change. See admin-athlete-teams.js.
   The rule below only supplies the scrolling. */
/* The scrolling element is the wrap the table control generates, not this container -- and that
   wrap carries a 440px min-height meant for the search-results pane, which on a two-team athlete
   reserves a screenful of nothing. Both the floor and the ceiling are cleared here; the height that
   replaces them is measured in the browser (see admin-athlete-teams.js). */
#athleteTeamMembershipsTable .athlete-table-wrap {
  max-height: none;
  min-height: 0;
}

.profile-catalog-picker > .fs-lookup { min-width: 0; }
.profile-school-lookup {
  display: grid;
  grid-template-columns: minmax(0, 1fr) 44px;
  gap: 8px;
  align-items: end;
}
.profile-catalog-browse {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  min-height: 44px;
  margin: 0;
  padding: 0;
}
.profile-catalog-browse-icon {
  width: 21px;
  height: 21px;
}
@media (max-width: 640px) {
  .fs-lookup-results { max-height: 45vh; }
  .profile-affiliation-panel { padding: 15px; }
}

/* Team and club-program catalogs share one visual system. These records are managed together, so
   their directory, editor, status, and empty states should feel like one workspace. */
.catalog-page {
  width: 100%;
  max-width: 1280px;
  min-width: 0;
  margin: 0 auto;
}
.catalog-page > .fs-tabs,
.catalog-page .fs-tab-panel,
.catalog-table-card { min-width: 0; max-width: 100%; }
.catalog-page-header,
.catalog-detail-header {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 24px;
  margin-bottom: 24px;
}
.catalog-page-header h1,
.catalog-detail-header h1 {
  margin: 2px 0 8px;
  font-size: clamp(2rem, 4vw, 3.1rem);
  line-height: 1.05;
  letter-spacing: -.035em;
}
.catalog-page-header > div:first-child > p:last-child {
  max-width: 720px;
  margin: 0;
  color: var(--muted);
  font-size: 1rem;
  line-height: 1.55;
}
.catalog-eyebrow {
  margin: 0;
  color: var(--muted);
  font-size: .72rem;
  font-weight: 750;
  letter-spacing: .12em;
  text-transform: uppercase;
}
.catalog-header-stat {
  display: grid;
  min-width: 86px;
  padding: 11px 16px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: color-mix(in srgb, var(--card) 82%, transparent);
  text-align: center;
}
.catalog-header-stat strong { font-size: 1.35rem; line-height: 1; }
.catalog-header-stat span { margin-top: 4px; color: var(--muted); font-size: .75rem; }
.catalog-page > .fs-tabs { margin-bottom: 24px; }

.catalog-filter-card {
  margin-top: 20px;
  padding: 20px;
  box-shadow: 0 10px 30px color-mix(in srgb, var(--shadow) 72%, transparent);
}
.catalog-filter-bar {
  display: grid;
  grid-template-columns: minmax(220px, 1.6fr) minmax(180px, .8fr) minmax(230px, 1fr) auto;
  gap: 16px;
  align-items: end;
}
.catalog-filter-bar--compact { grid-template-columns: minmax(260px, 1fr) auto; }
.catalog-filter-card--compact { max-width: 620px; }
.catalog-live-search-note {
  max-width: 210px;
  margin: 0 0 7px;
  color: var(--muted);
  font-size: .78rem;
  line-height: 1.4;
}
.catalog-filter-bar .fs-field,
.catalog-filter-bar label,
.catalog-filter-bar input,
.catalog-filter-bar select { margin-bottom: 0; }
.catalog-checkbox {
  display: flex;
  align-items: center;
  gap: 12px;
  min-height: 44px;
  padding: 7px 10px;
  border: 1px solid var(--line);
  border-radius: 9px;
  cursor: pointer;
}
.catalog-checkbox:hover { background: var(--surface-soft); }
.catalog-checkbox:has(input:focus-visible) { outline: 2px solid var(--accent); outline-offset: 2px; }
.catalog-checkbox input { width: 18px; height: 18px; flex: 0 0 auto; accent-color: var(--accent); }
.catalog-checkbox span { display: grid; gap: 1px; line-height: 1.15; }
.catalog-checkbox strong { font-size: .88rem; }
.catalog-checkbox small { color: var(--muted); font-size: .7rem; }
.catalog-filter-actions { display: flex; gap: 8px; align-items: center; min-height: 44px; }
.catalog-filter-actions .btn { min-height: 44px; white-space: nowrap; }

.catalog-table-card { padding: 0; overflow: hidden; }
.catalog-card-heading {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 20px;
  padding: 22px 24px;
  border-bottom: 1px solid var(--line);
}
.catalog-card-heading h2 { margin: 0 0 4px; font-size: 1.2rem; }
.catalog-card-heading p { margin: 0; color: var(--muted); font-size: .88rem; line-height: 1.45; }
.catalog-count {
  flex: 0 0 auto;
  padding: 5px 10px;
  border-radius: 999px;
  background: var(--surface-soft);
  color: var(--muted);
  font-size: .75rem;
  font-weight: 650;
}
.catalog-table-scroll { width: 100%; max-width: 100%; overflow-x: auto; }
.catalog-table { width: 100%; min-width: 760px; border-collapse: collapse; font-size: .9rem; }
.catalog-table th {
  position: relative;
  padding: 12px 18px;
  background: color-mix(in srgb, var(--surface-soft) 72%, var(--card));
  color: var(--muted);
  font-size: .7rem;
  font-weight: 750;
  letter-spacing: .06em;
  text-align: left;
  text-transform: uppercase;
  white-space: nowrap;
}
.catalog-table td { padding: 16px 18px; border-top: 1px solid var(--line); vertical-align: middle; }
.catalog-table tbody tr:hover td { background: color-mix(in srgb, var(--surface-soft) 62%, transparent); }
.catalog-table td small { display: block; margin-top: 3px; color: var(--muted); font-size: .74rem; }
.catalog-primary-link { color: var(--ink); font-weight: 700; text-decoration: none; }
.catalog-primary-link:hover { color: var(--accent-ink); text-decoration: underline; text-underline-offset: 3px; }
#teamsTable .catalog-primary-link + small {
  display: block;
  margin-top: 3px;
  color: var(--muted);
  font-size: .74rem;
}
.catalog-row-action { display: inline-flex; align-items: center; gap: 6px; min-height: 44px; color: var(--ink); font-weight: 650; text-decoration: none; }
.catalog-row-action:hover { color: var(--accent-ink); }
.catalog-affiliation-type { display: block; margin-bottom: 2px; color: var(--muted); font-size: .7rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase; }
.catalog-capitalize { text-transform: capitalize; }

.catalog-empty-state { max-width: 520px; margin: 0 auto; padding: 70px 24px 76px; text-align: center; }
.catalog-empty-state--compact { padding-block: 44px 50px; }
.catalog-empty-icon {
  display: inline-grid;
  place-items: center;
  width: 46px;
  height: 46px;
  margin-bottom: 14px;
  border-radius: 14px;
  background: color-mix(in srgb, var(--accent) 18%, var(--card));
  color: var(--ink);
  font-size: 1rem;
  font-weight: 800;
}
.catalog-empty-state h3 { margin: 0 0 6px; font-size: 1.1rem; }
.catalog-empty-state p { margin: 0 auto 18px; color: var(--muted); line-height: 1.5; }

.catalog-editor-card { padding: 0; overflow: hidden; }
.catalog-card-heading--form { padding-block: 26px; }
.catalog-card-heading--form h2 { font-size: 1.4rem; }
.catalog-validation { margin: 18px 24px 0; }
.catalog-validation:empty { display: none; }
.catalog-form-section {
  display: grid;
  grid-template-columns: 210px minmax(0, 1fr);
  gap: 32px;
  padding: 26px 24px;
  border-bottom: 1px solid var(--line);
}
.catalog-form-section--last { border-bottom: 0; }
.catalog-form-section-head { display: flex; align-items: flex-start; gap: 12px; }
.catalog-form-section-head h3 { margin: 1px 0 4px; font-size: .98rem; }
.catalog-form-section-head p { margin: 0; color: var(--muted); font-size: .78rem; line-height: 1.45; }
.catalog-form-step {
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  flex: 0 0 auto;
  border-radius: 50%;
  background: color-mix(in srgb, var(--accent) 22%, var(--card));
  font-size: .75rem;
  font-weight: 800;
}
/* A game's two rosters side by side: home left, away right; stacked on narrow screens (Ron, 2026-10-08). */
.game-rosters { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; align-items: start; }
.game-rosters > .card { margin: 0; min-width: 0; }
@media (max-width: 1100px) { .game-rosters { grid-template-columns: minmax(0, 1fr); } }
/* Each roster scrolls inside its own column instead of being cut off (Ron, 2026-10-08). */
.game-rosters .fs-table-pane { overflow: auto; max-height: 70vh; }
.game-rosters .catalog-card-heading { padding-bottom: 12px; }
.game-rosters .catalog-card-heading h2 { margin: 2px 0 0; }
/* The game page's next-step box: what to do now, and links to both rosters (Ron, 2026-10-08). */
.catalog-next-step { padding: 18px 22px; }
.catalog-next-step p { margin: 0 0 10px; }
.catalog-next-step-links { display: flex; flex-wrap: wrap; gap: 10px; }
/* A hidden form row stays hidden: the grid's display would otherwise override the hidden attribute
   (Ron, 2026-10-08: the game form's Event name and Season showed while an event was chosen). */
.catalog-form-grid[hidden] { display: none; }
/* A card heading's action button keeps its words on one line (Ron, 2026-10-08: "+ Add a game" wrapped). */
.catalog-heading-action { white-space: nowrap; flex-shrink: 0; }
.catalog-form-grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 18px 20px; min-width: 0; }
.catalog-form-grid--three { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.catalog-form-grid > *,
.catalog-location-grid > * { min-width: 0; }
.catalog-form-grid label,
.catalog-form-grid input,
.catalog-form-grid select,
.catalog-form-grid textarea { margin-bottom: 0; }
.catalog-form-grid input,
.catalog-form-grid select { min-height: 44px; }
.catalog-form-grid textarea { min-height: 116px; resize: vertical; }
.catalog-form-grid > .admin-create-full { grid-column: 1 / -1; }
.catalog-location-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.catalog-form-actions { display: flex; align-items: center; gap: 10px; padding: 20px 24px 24px; border-top: 1px solid var(--line); background: color-mix(in srgb, var(--surface-soft) 52%, var(--card)); }
.catalog-form-actions .btn { min-height: 44px; }
.catalog-form-actions .required-why { order: 2; flex-basis: 100%; margin: 0; }

.catalog-detail-header { align-items: center; }
.catalog-detail-header > .fs-badge { padding: 6px 13px; }
/* More than one status pill in a header ("From the app" beside "Active"): side by side, same size. */
.catalog-detail-badges { display: flex; flex-wrap: wrap; gap: 8px; justify-content: flex-end; }
.catalog-detail-badges > .fs-badge { padding: 6px 13px; }
/* Paste names review (event watchlist): a line matching several athletes lists them one per row. */
.watchlist-paste-choice { border: 0; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 4px; }
.watchlist-paste-choice > legend { padding: 0; margin-bottom: 2px; }
.catalog-detail-meta { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; color: var(--muted); font-size: .9rem; }
.catalog-meta-dot { width: 4px; height: 4px; border-radius: 50%; background: var(--muted); opacity: .55; }
.catalog-detail-layout { display: grid; grid-template-columns: minmax(0, 1fr) 280px; gap: 20px; align-items: start; }
.catalog-detail-sidebar { position: sticky; top: 84px; }
.catalog-status-card,
.catalog-danger-card { box-shadow: 0 8px 24px color-mix(in srgb, var(--shadow) 72%, transparent); }
.catalog-status-card h2,
.catalog-danger-card h2 { margin: 6px 0 8px; font-size: 1.05rem; }
.catalog-status-card > p:not(.catalog-eyebrow),
.catalog-danger-card > p:not(.catalog-eyebrow) { margin: 0 0 18px; color: var(--muted); font-size: .84rem; line-height: 1.5; }
.catalog-status-card form,
.catalog-danger-card form { margin: 0; }
.catalog-status-card .btn,
.catalog-danger-card .btn { width: 100%; min-height: 44px; }
.catalog-danger-card { border-color: color-mix(in srgb, #dc2626 38%, var(--line)); }
.catalog-danger-card .catalog-eyebrow { color: #b91c1c; }

@media (max-width: 980px) {
  .catalog-filter-bar { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .catalog-detail-layout { grid-template-columns: 1fr; }
  .catalog-detail-sidebar { position: static; display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; }
}

@media (max-width: 720px) {
  .catalog-page-header,
  .catalog-detail-header { align-items: flex-start; gap: 14px; }
  .catalog-header-stat { min-width: 70px; padding-inline: 10px; }
  .catalog-filter-card { padding: 16px; }
  .catalog-filter-bar,
  .catalog-filter-bar--compact { grid-template-columns: 1fr; }
  .catalog-filter-card--compact { max-width: none; }
  .catalog-live-search-note { max-width: none; margin-bottom: 0; }
  .catalog-filter-actions { flex-wrap: wrap; }
  .catalog-filter-actions .btn { flex: 1 1 130px; }
  .catalog-card-heading { padding: 18px 16px; }
  .catalog-count { display: none; }
  .catalog-table th,
  .catalog-table td { padding-inline: 14px; }
  .catalog-form-section { grid-template-columns: 1fr; gap: 18px; padding: 22px 16px; }
  .catalog-form-grid,
  .catalog-form-grid--three { grid-template-columns: 1fr; gap: 16px; }
  .catalog-location-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .catalog-validation { margin-inline: 16px; }
  .catalog-form-actions { padding: 16px; }
  .catalog-form-actions .btn { flex: 1 1 140px; }
  .catalog-detail-sidebar { grid-template-columns: 1fr; }
}

@media (max-width: 430px) {
  .catalog-page-header h1,
  .catalog-detail-header h1 { font-size: 2rem; }
  .catalog-header-stat { display: none; }
  .catalog-detail-header { flex-direction: column; }
  .catalog-empty-state { padding: 48px 18px 54px; }
  .catalog-form-actions { flex-direction: column; align-items: stretch; }
  .catalog-form-actions .btn { flex-basis: auto; width: 100%; }
  .catalog-form-actions .required-why { order: 2; }
}

/* The Add athlete panel on Manage Athletes is opened by the ribbon's +, not a tab, so it carries
   its own way back. */
.admin-add-athlete-head { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; margin-bottom: 8px; }
.admin-add-athlete-head h2 { margin: 0; }

/* The evaluation form's header row: date at a date's width, the platform and event boxes (each
   with its magnifier and plus) sharing the rest, the season at a season's width. Stacks on a
   phone. */
.evaluation-header-row { display: grid; grid-template-columns: auto minmax(200px, 1fr) minmax(200px, 1fr) minmax(12rem, 16rem); gap: 18px 20px; align-items: start; }
.evaluation-header-row > * { min-width: 0; }
/* Inputs carry a 14px bottom margin by default, from the days when a form was a stack of fields.
   Inside a grid that already spaces its rows, that margin is doubled spacing doing nothing -- and
   on the last row it pushed the divider below a third of an inch clear of the boxes. The grid's
   own gap is the spacing here. */
.evaluation-header-row input,
.evaluation-header-row select,
.evaluation-header-row .fs-lookup-control { margin-bottom: 0; }
/* The evaluation form (Ron, 2026-09-24): row one is the date and the platform at the width their
   values need, then the event name taking the rest; row two is season and projection side by side. */
.evaluation-header-row--main { grid-template-columns: auto minmax(12rem, 22rem) minmax(12rem, 1fr); } /* fits from 721px up (final review B-N7) */
.evaluation-header-row--main input[type="date"] { inline-size: 11rem; }
.evaluation-header-row--second { grid-template-columns: minmax(12rem, 16rem) minmax(12rem, 18rem); }
@media (max-width: 720px) { .evaluation-header-row { grid-template-columns: minmax(0, 1fr); } }
/* The intel form's header row: the same shape, with room for an evaluation's date · location · evaluator. */
.intel-header-row { grid-template-columns: minmax(10rem, auto) minmax(220px, 1fr); }
/* The record's own page (read-only intel): the form's labels over plain text, the typed text
   shown as typed with its paragraphs and line breaks. */
.intel-value { margin: 4px 0 0; font-size: 15px; line-height: 1.5; overflow-wrap: anywhere; }
.intel-text { white-space: pre-wrap; }
@media (max-width: 720px) { .intel-header-row { grid-template-columns: minmax(0, 1fr); } }

/* Sign-in code screens (2026-09-20). One input drawn as six cells: the letter-spacing and the
   repeating underline are cosmetic, so paste, autofill, the required-fields standard and screen
   readers all see a single field. */
.fs-code-entry {
  font-family: var(--font-mono, ui-monospace, SFMono-Regular, Menlo, monospace);
  font-size: 1.75rem;
  letter-spacing: 0.7em;
  text-align: center;
  padding-left: 0.7em;
  max-width: 100%;
}
/* A plain checkbox line, not a chip: .fs-check is the pill used for filter chips and looks
   wrong under a form field. */
.login-keep { display: flex; align-items: center; gap: 10px; margin: 4px 0 14px; font-size: 14px; color: var(--ink); }
.login-keep input[type="checkbox"] { margin: 0; width: 18px; height: 18px; accent-color: var(--accent); flex: none; }
/* The quiet line under the sign-in form: "Other methods", "Not you?", "Forgot password?" — right
   aligned, one item per line on a phone. */
.login-aside { display: flex; flex-direction: column; align-items: flex-end; gap: 4px; margin: 12px 0 0; text-align: right; }
.login-resend { margin-top: 12px; }
.two-factor-qr { max-width: 240px; margin: 12px 0; }
.two-factor-qr svg { width: 100%; height: auto; }
.two-factor-key { font-size: 1.1rem; letter-spacing: 0.08em; word-break: break-all; }
.two-factor-codes { columns: 2; column-gap: 24px; font-size: 1.1rem; margin: 0 0 12px; padding-left: 1.5em; }
@media (max-width: 480px) { .two-factor-codes { columns: 1; } }

/* School page, School details card: the heading row with the logo action right-aligned (Ron,
   2026-10-07). On a narrow screen the button wraps under the heading rather than squeezing it. */
.school-details-heading {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
  flex-wrap: wrap;
  margin-bottom: 12px;
}
.school-details-heading h2 {
  margin: 0;
}
.school-details-heading-action {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: flex-end;
  margin-left: auto;
}
