h1 { color: black }
p  { color: rgb(82, 82, 82); }
.tagline { color: crimson; }
.box { color: crimson; padding: 1px; }
.row {
  display: flex;
  gap: 1px;
}
.meta {
  color: gray;
  font-size: 14px;
}

.flag {
  display: inline-block;
  font-size: 11px;
  font-weight: 700;
  padding: 2px 5px;
  border-radius: 3px;
  margin-right: 3px;
  color: #fff;
}
.flag--pr  { background: #b8860b; }   /* PR  — gold-ish */
.flag--sr  { background: #4b7a34; }   /* SR  — green */
.flag--cpr { background: #2e53e6; }   /* course PR — blue */
.flag--rpr { background: #6b3fa0; }   /* rating PR — purple */

.star { color: #b8860b; margin-left: 4px; }
.star--rating { color: #6b3fa0; }
.flag--csr { background: #1f6f8b; }
.flag--rsr { background: #8b3f6b; }
/* warn — board-eligibility notes ("Unranked"). Amber: it is a caution about
   where the numbers appear, not about the numbers, so it must not read as
   an error red or blend in with the record flags. */
.flag--warn { background: #a05a00; }
/* Inside a season <h3>: the badge keeps its own 11px, it just needs air
   between the heading text and the rating that follows it. */
.season-flag { margin-left: 6px; vertical-align: middle; }

/* The board-note marker: a small amber asterisk beside the season's grade,
   replacing the "Unranked" pill that used to sit in the heading and read
   like an error. Amber, not red: the note is about where the numbers
   appear, never about the numbers. --tip colours its tooltip border, same
   machinery as the race flags. */
.season-note {
  --tip: #a05a00;
  background: none;
  color: #a05a00;
  padding: 0;
  margin: 0 0 0 3px;
  font-size: 1em;
  line-height: inherit;
  vertical-align: baseline;
}

.page-layout { display: flex; gap: 24px; align-items: flex-start; }
.main-col    { flex: 1; min-width: 0; }
.sidebar     { width: 280px; flex-shrink: 0; }

.chart-row {
  display: flex;
  flex-wrap: wrap;
  gap: 16px;
  margin: 16px 0 24px;
}

/* TF section: results and charts side by side */
.section-split {
    display: flex;
    gap: 1.5rem;
    align-items: flex-start;   /* charts sit at the top, don't stretch to
                                  match the height of a long results table */
}

.section-main {
    flex: 1;                   /* take all remaining width */
    min-width: 0;              /* CRITICAL -- see below */
}

.chart-col {
    flex: 0 0 260px;           /* don't grow, don't shrink, exactly 260px */
    display: flex;
    flex-direction: column;    /* charts stack vertically */
    gap: 0.75rem;
    position: sticky;          /* stay visible while scrolling the seasons */
    top: 1rem;
}

/* The sidebar now carries charts, not just a list, so it needs real width. */
.sidebar {
    flex: 0 0 340px;        /* was narrower -- charts need room to be readable */
    display: flex;
    flex-direction: column;
    gap: 1.5rem;            /* space between the PR panel and the charts */
}

.chart-panel {
    display: flex;
    flex-direction: column;
    gap: 1rem;
}

/* Bigger: taller slots, and full sidebar width. */
.chart-slot {
    min-height: 220px;      /* was however tall the placeholder text made it */
    display: flex;
    align-items: center;
    justify-content: center;
    border: 1px dashed #bbb;
    border-radius: 4px;
    color: #777;
    font-size: 0.85rem;
    text-align: center;
    padding: 0.5rem;
}

@media (max-width: 900px) {
    .sidebar { flex: 1 1 auto; }   /* stack below the main column on phones */
}

/* ---- Race tables ------------------------------------------------------ */

table {
    border-collapse: collapse;   /* adjacent cell borders merge into one line
                                    instead of doubling up */
    width: 100%;
}

th, td {
    /* vertical | horizontal. You said rows are fine, so the first number
       stays modest and the second does the work. */
    padding: 0.4rem 0.9rem;
    border: 1px solid #ddd;      /* the boxes */
    text-align: left;
    vertical-align: top;
}

thead th {
    background: #f5f5f5;
    font-weight: 600;
    white-space: nowrap;         /* stop headers wrapping to two lines */
}

/* Numbers right-aligned and tabular so digits line up in a column.
   tabular-nums forces equal-width digits -- without it, 1 is narrower than
   0 in most fonts and the decimal points drift. Matters now that times
   carry ragged precision (9:19 next to 9:19.42). */
/* ⚠ THIS WAS GLOBAL AND RIGHT-ALIGNED EVERY TABLE'S 4th, 5th AND 6th COLUMN
   ON THE SITE -- written for the athlete page's result table, applied to all
   of them. It is why a `table td { text-align: left }` rule further down did
   nothing: td:nth-child(4) outranks it on specificity.

   tabular-nums stays. That is the part actually worth having: fixed-width
   digits so a column of times lines up instead of jittering row to row.
   Alignment is handled once, for every table, at the bottom of this file. */
td:nth-child(4),   /* Result     */
td:nth-child(5),   /* Place      (athlete page; harmless elsewhere) */
td:nth-child(6),   /* Rating     */
td:nth-child(7) {  /* Difficulty -- shifted right when Place was added */
    font-variant-numeric: tabular-nums;
}

/* Dates and times shouldn't wrap mid-value */
td:nth-child(1) { white-space: nowrap; }

/* The race-flag cell keeps its badges on ONE line. Without this, any new
   column squeezes it and the badges stack vertically -- a row with a full
   flag set grew to six lines tall. */
td.flags { white-space: nowrap; }
/* The place cell may break BETWEEN the ordinal and the team suffix, but
   never inside either -- a nowrap on the whole cell painted over the
   rating column at half-screen widths. */
.place-cell .no-break { white-space: nowrap; }
/* Also as a cell class: a time and its PR/SR badge stay on one line. */
td.no-break { white-space: nowrap; }

/* ---- top bar --------------------------------------------------------- */
.topbar {
    display: flex;
    align-items: center;
    gap: 1.5rem;
    padding: 0.75rem 1.5rem;
    border-bottom: 1px solid #e5e5e5;
}
.brand { font-weight: 700; font-size: 1.2rem; text-decoration: none; }
.topnav { display: flex; gap: 1.25rem; }
.topnav a { text-decoration: none; color: #333; }
.topsearch { margin-left: auto; }          /* pushes search to the right */
.topsearch input { padding: 0.4rem 0.7rem; min-width: 220px; }
 
/* ---- hero ------------------------------------------------------------ */
/* ---- hero: nine-photo mosaic with a centred title -------------------- */
/*
 * A 4x4 grid, laid out with NAMED AREAS rather than line numbers:
 *
 *     m1   m2   m2   m3
 *     m4   TT   TT   m5
 *     m4   TT   TT   m5
 *     m6   m7   m8   m9
 *
 * ★ THE CELL SHAPES ARE THE POINT. Nine identical squares would crop the
 *   portraits to heads and the panoramas to slivers. So the grid offers three
 *   shapes and the photos are assigned to match:
 *     m2      2:1 wide   -> the widest source (1.78)
 *     m4, m5  1:2 tall   -> the two portraits (0.80, 0.81)
 *     rest    ~1:1       -> the mid-landscape and square sources
 *
 * ★ grid-template-areas, not grid-column/row numbers. Nine tiles plus a
 *   spanning title is where line numbers stop being readable -- the ASCII
 *   block below IS the layout, so rearranging is editing a picture rather
 *   than recomputing four numbers per tile.
 *
 * ★ aspect-ratio on the SECTION, not heights on tiles. One number sets the
 *   band's proportion and the rows divide it; per-tile heights is how a
 *   mosaic ends up with a ragged bottom edge.
 */
.hero-mosaic {
    display: grid;
    grid-template-columns: 1fr 1.3fr 1.3fr 1fr;
    /* ★ THE OUTER ROWS ARE THE TALL ONES. At 1 / 0.8 / 0.8 / 1 the top and
     *   bottom strips were shallow enough that cover-crop threw away most of
     *   each photo -- Bolt lost his head, Kipchoge lost the clock. 1.6 makes
     *   those rows twice the height of the middle pair, so the images that
     *   have to carry a moment actually show it. The title cell gets less of
     *   the band and more absolute height, because the section is taller. */
    grid-template-rows: 1.6fr 0.7fr 0.7fr 1.6fr;
    grid-template-areas:
        "m1 m2 m2 m3"
        "m4 tt tt m5"
        "m4 tt tt m5"
        "m6 m7 m8 m9";
    gap: 3px;                       /* the seam. 0 for butted panels */
    max-width: 1280px;
    margin: 0 auto;
    aspect-ratio: 16 / 11;          /* taller band -- outer rows need the room */
    background: #0a0c0e;            /* shows through the gaps */
    border-bottom: 1px solid #e5e5e5;
}

.m1 { grid-area: m1; } .m2 { grid-area: m2; } .m3 { grid-area: m3; }
.m4 { grid-area: m4; } .m5 { grid-area: m5; } .m6 { grid-area: m6; }
.m7 { grid-area: m7; } .m8 { grid-area: m8; } .m9 { grid-area: m9; }
.mtitle { grid-area: tt; }

/* ---- per-tile framing ------------------------------------------------ */
/*
 * background-position decides WHICH PART SURVIVES the cover-crop, and the
 * default `center` is wrong whenever the subject is not centred. These are
 * measured off each source, not guessed:
 *
 *   m2  Bolt's face sits ~28% across and ~13% down a 1296x729 frame. Centre
 *       crops straight through his chest; 30% 22% keeps the head and the
 *       backward glance, which is the entire reason for the photo.
 *   m8  Kipchoge's 1:59:40 clock is in the top ~15% of the frame. Anything
 *       but `top` loses it, and the clock is the point of the image.
 *   m1  the NXN tape break is upper-middle; `center top` keeps the arms.
 *   m9  the shout is upper-middle of a tight portrait.
 *   m6  Bannister is left of frame and breasting the tape at mid-height.
 *
 * ! SPECIFICITY TRAP. `.mtile` further down ALSO sets background-position, it
 *   is also a single class, and it comes LATER -- so a bare `.mN` loses the
 *   tie on source order and its framing silently never applies. Any tile rule
 *   here MUST be written as `.hero-mosaic .mN` (0,2,0) to win. `.m2` was the
 *   only one written that way, which is why it was the only one that worked.
 *   .m1, .m8 and .m9 are still bare and therefore still inert.
 */
.m1 { background-position: center 15%; }
.hero-mosaic .m2 { background-position: 30% 22%; }
.hero-mosaic .m6 { background-position: 25% center; }
.m8 { background-position: center top; }
.m9 { background-position: center 20%; }

/* Every tile: cover-crop, dimmed and desaturated so the centre cell stays
   dominant and nine competing photos read as one texture. */
.mtile {
    background-size: cover;
    background-position: center;
    background-repeat: no-repeat;
    filter: grayscale(0.2) brightness(0.78);
    transition: filter 0.25s;
}
.hero-mosaic:hover .mtile { filter: grayscale(0) brightness(0.92); }

/* The centre cell. Solid, not translucent: nine photos behind a see-through
   panel is noise, and the title has to win. */
.mtitle {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    text-align: center;
    padding: 1.25rem 1.5rem;
    background: #0a0c0e;
    z-index: 1;
}

.hero-mosaic .brand {
    display: block;
    color: #fff;
    font-size: 2.8rem;
    font-weight: 700;
    letter-spacing: -0.02em;
    line-height: 1.05;
    text-decoration: none;
}
.hero-mosaic .tagline {
    color: rgba(255,255,255,0.82);
    margin: 0.45rem 0 1rem;
    font-size: 1rem;
}
.hero-mosaic .facts {
    display: flex;
    gap: 1.5rem;
    justify-content: center;
    flex-wrap: wrap;
}
.hero-mosaic .fact { color: rgba(255,255,255,0.72); font-size: 0.85rem; }
.hero-mosaic .fact-num { font-weight: 700; color: #fff; }

/* Narrow: the ring collapses to two tiles above and two below. Nine slivers
   on a phone would make all nine illegible, so five are hidden rather than
   shrunk -- the ones kept are the two portraits, which suit a phone anyway. */
@media (max-width: 760px) {
    .hero-mosaic {
        grid-template-columns: 1fr 1fr;
        grid-template-rows: 110px auto 110px;
        grid-template-areas:
            "m4 m5"
            "tt tt"
            "m2 m9";
        aspect-ratio: auto;
    }
    .m1, .m3, .m6, .m7, .m8 { display: none; }
    .mtitle { padding: 1.5rem 1rem; }
    .hero-mosaic .brand { font-size: 1.9rem; }
}
}
 
/* ---- rankings controls ---------------------------------------------- */
.rankings { padding: 1.5rem; }
.rank-controls {
    display: flex;
    gap: 1rem;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;   /* add this line */
    margin-bottom: 1.5rem;
}
.toggle-group button, .chip {
    padding: 0.4rem 0.8rem; border: 1px solid #ccc; background: #fff;
    cursor: pointer; border-radius: 4px;
}
.toggle-group button.is-active { background: #222; color: #fff; border-color: #222; }
.chip { opacity: 0.45; }                    /* off state */
.chip.is-on { opacity: 1; }
 
/* ---- board visibility (the whole point of the toggles) --------------- */
/* Hide every board-pair by default; show only the one matching the current
   sport+scope on .rankings. Attribute selectors do the switching. */
.board-pair { display: none; }
.rankings[data-sport="XC"][data-scope="season"]  .board-pair[data-sport="XC"][data-scope="season"],
.rankings[data-sport="XC"][data-scope="alltime"] .board-pair[data-sport="XC"][data-scope="alltime"],
.rankings[data-sport="TF"][data-scope="season"]  .board-pair[data-sport="TF"][data-scope="season"],
.rankings[data-sport="TF"][data-scope="alltime"] .board-pair[data-sport="TF"][data-scope="alltime"] {
    display: block;
}

/* Latest results follows the sport toggle only -- a list of recent meets
   has no season/all-time split. */
.recent-block { display: none; margin-top: 2.2rem; }
.rankings[data-sport="XC"] .recent-block[data-sport="XC"],
.rankings[data-sport="TF"] .recent-block[data-sport="TF"] { display: block; }
.recent-all { margin-top: 0.6rem; font-size: 0.9rem; text-align: right; }

/* /meets: one table per month, the month as a quiet running head. */
.meets-page .month-head {
    margin: 1.6rem 0 0.4rem;
    font-size: 0.8rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #6b7280;
}
 
/* pool chips hide a pool everywhere at once */
body.hide-hs_m      .pool-block[data-pool="hs_m"],
body.hide-hs_f      .pool-block[data-pool="hs_f"],
body.hide-college_m .pool-block[data-pool="college_m"],
body.hide-college_f .pool-block[data-pool="college_f"],
body.hide-ms_m      .pool-block[data-pool="ms_m"],
body.hide-ms_f      .pool-block[data-pool="ms_f"] {
    display: none;
}

.topsearch { margin-left: auto; } /* stays hard right */
 
/* ---- grid-based boards --------------------------------------------- */
:root {
    --cols: 3rem minmax(0, 1fr) minmax(0, 1fr) 5rem minmax(0, 1fr);   /* rating 4rem -> 5rem */   /* # was 2rem, now 2.5 */  /* the 6 column widths, shared */
    --board-gap: 1rem;                    /* gutter between the two boards */
}

.board-pair-grid {
    display: flex;
    gap: var(--board-gap);
    align-items: flex-start;
}
.board-side { flex: 1 1 0; min-width: 0; }

.board-grid {
    min-width: 0;
    display: grid;
    grid-template-columns: var(--cols);
}
.cell {
    /* remove: display: flex; align-items: center; */
    padding: 0.4rem 0.9rem;
    border: 1px solid #ddd;
    border-top: none;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}

.c-school, .c-detail {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.grid-head {
    display: grid;
    grid-template-columns: var(--cols);
}

.head {
    padding: 0.4rem 0.9rem;
    background: #f5f5f5;
    border: 1px solid #ddd;
    border-top: 1px solid #ddd;
    font-weight: 600;
}
.cell.blank { border-bottom: 1px solid #eee; }   /* keeps the line, no content */

.c-year { text-align: right; font-variant-numeric: tabular-nums; justify-content: flex-end; }

.c-rank, .c-rating {
    text-align: center;
    overflow: visible;
    text-overflow: clip;
    font-variant-numeric: tabular-nums;
}
.board-title,
.pool-name {
    margin: 0 0 0.5rem 0;
    padding-left: 0;      /* start at the container's left edge */
}

.board-grid,
.grid-head {
    margin-left: 0;       /* make sure the table isn't indented */
}
.head-center { text-align: center; }

@media (max-width: 850px) {
    .board-pair-grid { flex-direction: column; }
}

.search-wrap {
    position: relative;
    margin-left: auto;      /* pushes the whole search block to the right */
}
.search-results {
    position: absolute;
    top: 100%; left: 0; right: 0;
    background: #fff;
    border: 1px solid #ccc;
    border-top: none;
    max-height: 400px;
    display: flex;              /* column: scrolling list + pinned footer */
    flex-direction: column;
    z-index: 100;
}
.sr-scroll {
    overflow-y: auto;          /* only THIS scrolls */
    flex: 1;
}
.sr-more {
    flex: none;                /* pinned, never scrolls away */
    border-top: 1px solid #ddd;
    padding: 0.5rem 0.7rem;
    background: #fafafa;
    text-align: center;
    font-weight: 500;
    text-decoration: none;
    color: #333;
}
.sr-item {
    display: flex;
    align-items: baseline;
    gap: 0.5rem;
    padding: 0.4rem 0.7rem;
    text-decoration: none;
    color: #222;
    border-bottom: 1px solid #eee;
}
/* .is-active is the keyboard highlight (arrow keys in topbar-search.js);
   same colour as hover, because it means the same thing. */
.sr-item:hover, .sr-item.is-active { background: #f5f5f5; }
.sr-more.is-active { background: #f0f0f0; }
.sr-kind {
    font-size: 0.7rem;
    text-transform: uppercase;
    color: #999;
    min-width: 3.5rem;
}
.sr-label { font-weight: 500; }
.sr-sub { color: #888; font-size: 0.85rem; margin-left: auto; }

.search-tabs { display: flex; gap: 0.5rem; flex-wrap: wrap; margin: 1rem 0; }
.search-tab {
    padding: 0.4rem 0.9rem;
    border: 1px solid #ccc;
    border-radius: 4px;
    background: #fff;
    text-decoration: none;
    color: #333;
    display: inline-flex;
    align-items: center;
    gap: 0.4rem;
}
.search-tab.is-active { background: #222; color: #fff; border-color: #222; }
.tab-count {
    font-size: 0.75rem;
    opacity: 0.6;
    background: rgba(0,0,0,0.08);
    padding: 0 0.35rem;
    border-radius: 8px;
}
.search-tab.is-active .tab-count { background: rgba(255,255,255,0.2); }

.results-table { width: 100%; border-collapse: collapse; }
.results-table th, .results-table td {
    padding: 0.5rem 0.9rem;
    border-bottom: 1px solid #eee;
    text-align: left;
}
.results-table thead th { background: #f5f5f5; font-weight: 600; }
.result-row { cursor: pointer; }
.result-row:hover { background: #f9f9f9; }

.year-filter {
    margin-bottom: 1rem;
}

/* The results page's own search bar. Wide enough to read a long meet name
   back, and it sits above the tabs because it changes what the tabs count. */
.search-page-form {
    display: flex;
    gap: 0.5rem;
    margin: 0.75rem 0 0.25rem;
    max-width: 640px;
}
.search-page-form input[type="search"] {
    flex: 1 1 auto;
    padding: 0.5rem 0.8rem;
    border: 1px solid #ccc;
    border-radius: 4px;
    font-size: 1rem;
}
.search-page-form button {
    padding: 0.5rem 1.1rem;
    border: 1px solid #222;
    border-radius: 4px;
    background: #222;
    color: #fff;
    font-size: 0.95rem;
    cursor: pointer;
}
.search-page-form button:hover { background: #000; }

@media (max-width: 640px) {
    .search-page-form { max-width: none; }
}

.conv-add { position: relative; }        /* anchor */
.conv-drop {
    position: absolute;
    top: 100%;                            /* directly below the input */
    left: 0; right: 0;
    background: #fff;
    border: 1px solid #ccc;
    border-top: none;
    z-index: 50;
    max-height: 240px;
    overflow-y: auto;
}
.conv-drop:empty { display: none; }      /* invisible when no results */
.drop-item { padding:0.4rem 0.7rem; cursor:pointer; }
.drop-item:hover { background:#f0f0f0; }
.drop-meta { color:#999; font-size:0.8rem; }

.src-field { position: relative; }

.conv-drop {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    background: #fff;
    border: 1px solid #ccc;
    z-index: 100;
    max-height: 240px;
    overflow-y: auto;
}
.conv-drop:empty { display: none; }

/* ===== CONVERSIONS ===== matches the site's tables + spacing ===== */

.conv { max-width: 1100px; margin: 0 auto; padding: 1.5rem; }
.conv h1 { margin: 0 0 0.25rem; }
.conv-intro { color: #666; margin: 0 0 1.5rem; font-size: 0.95rem; }

/* red-asterisk required marker + the key line */
.req { color: #c0392b; font-weight: 700; }
.req-key { color: #999; font-size: 0.8rem; margin-left: 0.5rem; }

/* ---- source panel: a bordered card, like a form ---- */
.conv-source {
    border: 1px solid #ddd;
    border-radius: 6px;
    padding: 1.25rem;
    margin-bottom: 1.5rem;
    background: #fafafa;
}
.conv-row {
    display: flex;
    gap: 1rem;
    flex-wrap: wrap;
    align-items: flex-end;
    margin-bottom: 0.75rem;
}
/* each field: label stacked over its input */
.conv-row > label,
.src-field {
    display: flex;
    flex-direction: column;
    gap: 0.25rem;
    font-size: 0.85rem;
    color: #444;
    position: relative;              /* anchors its dropdown */
}
.conv-row input,
.conv-row select,
.src-field input,
.src-field select {
    padding: 0.4rem 0.6rem;
    border: 1px solid #ccc;
    border-radius: 4px;
    font-size: 0.95rem;
    min-width: 160px;
}

/* the picked-athlete / picked-course confirmation line */
.chosen { color: #2a7d46; font-size: 0.8rem; min-height: 1em; }

/* ---- search dropdowns (all of them share .conv-drop) ---- */
.conv-drop {
    position: absolute;
    top: 100%; left: 0; right: 0;
    background: #fff;
    border: 1px solid #ccc;
    border-top: none;
    z-index: 100;
    max-height: 240px;
    overflow-y: auto;
}
.conv-drop:empty { display: none; }
.drop-item { padding: 0.4rem 0.7rem; cursor: pointer; font-size: 0.9rem; }
.drop-item:hover { background: #f0f0f0; }
.drop-meta { color: #999; font-size: 0.8rem; margin-left: 0.4rem; }

/* ---- optional weather, collapsed ---- */
.conv-weather { margin: 0.5rem 0; }
.conv-weather summary { cursor: pointer; color: #555; font-size: 0.85rem; }

/* ---- the Convert button: primary, matches the site's dark toggle ---- */
.conv-go {
    margin-top: 0.5rem;
    padding: 0.55rem 1.4rem;
    background: #222;
    color: #fff;
    border: none;
    border-radius: 4px;
    font-size: 0.95rem;
    cursor: pointer;
}
.conv-go:hover { background: #000; }

/* ---- the headline result ---- */
.conv-normalized {
    font-size: 1.1rem;
    font-weight: 600;
    margin: 0 0 1rem;
    min-height: 1.4em;
    color: #111;
}
.conv-normalized:empty { display: none; }

/* ---- two tables side by side, stack on narrow ---- */
.conv-tables {
    display: flex;
    gap: 1.5rem;
    align-items: flex-start;
}
.conv-table-wrap { flex: 1 1 0; min-width: 0; }
.conv-table-wrap h2 { font-size: 1.1rem; margin: 0 0 0.5rem; }

@media (max-width: 800px) {
    .conv-tables { flex-direction: column; }   /* stack on narrow */
}

/* ---- the result tables: same look as the leaderboard tables ---- */
.conv-table {
    width: 100%;
    border-collapse: collapse;
}
.conv-table th,
.conv-table td {
    padding: 0.45rem 0.7rem;
    border-bottom: 1px solid #eee;
    text-align: left;
    font-size: 0.9rem;
}
.conv-table thead th {
    background: #f5f5f5;
    border-bottom: 2px solid #ddd;
    font-weight: 600;
}
/* numeric columns right-aligned with tabular figures, like the boards */
.conv-table .cell-dist,
.conv-table .cell-time {
    font-variant-numeric: tabular-nums;
}

/* ---- the add-course / add-distance row under each table ---- */
.conv-add { position: relative; margin-top: 0.5rem; }
.conv-add input {
    width: 100%;
    padding: 0.45rem 0.6rem;
    border: 1px solid #ccc;
    border-radius: 4px;
    font-size: 0.9rem;
}

.field-label { display: block; }   /* text + asterisk stay on one line */

.conv-row { gap: 1.25rem; row-gap: 1rem; }
.src-field { margin-bottom: 0.25rem; }

.topbar { display: flex; align-items: center; gap: 1.5rem;
          padding: 0.75rem 1.5rem; border-bottom: 1px solid #e5e5e5; }
.brand { font-weight: 700; font-size: 1.2rem; text-decoration: none; color: #111; }
.topnav { display: flex; gap: 1.25rem; }
.topnav a { text-decoration: none; color: #333; }

.search-wrap { position: relative; margin-left: auto; width: 320px; }  /* right */
#search-input { width: 100%; padding: 0.45rem 0.7rem;
                border: 1px solid #ccc; border-radius: 4px; }

.search-wrap { position: relative; }
.search-results {
    position: absolute;
    top: 100%; left: 0; right: 0;
    background: #fff;
    border: 1px solid #ccc;
    z-index: 200;
    max-height: 400px;
    display: flex;
    flex-direction: column;
}
.search-results:empty { display: none; }
.sr-scroll { overflow-y: auto; flex: 1; }
.sr-item { display: flex; gap: 0.5rem; padding: 0.4rem 0.7rem;
           text-decoration: none; color: #222; border-bottom: 1px solid #eee; }
.sr-item:hover, .sr-item.is-active { background: #f5f5f5; }
.sr-more { padding: 0.5rem; text-align: center; background: #fafafa;
           border-top: 1px solid #ddd; text-decoration: none; color: #333; }

/*
 * rankings.css -- styles for the rankings page ONLY.
 *
 * Goes in racecast/static/. Loaded by templates/rankings.html AFTER style.css.
 *
 * ★ EVERY RULE IS SCOPED UNDER .rankings-page.
 *
 *   There is no bare `body`, `h1`, `table`, `th`, `td` or `a` selector in this
 *   file, and no `*` reset. An earlier version had all of those, was pasted
 *   into style.css, and immediately restyled every page on the site --
 *   its `table` and `a` rules outranked the real ones simply by coming later.
 *
 *   Scoping makes that impossible: nothing here can match an element outside
 *   the rankings wrapper, no matter where the file ends up.
 *
 * The board switch keys off .rankings-page[data-board], NOT body[data-board],
 * for the same reason -- the page owns its own state, not the document.
 */

.rankings-page {
  /* Locally scoped tokens. Declared on the wrapper rather than :root so they
     cannot collide with variables in style.css. */
  --rk-muted:   #6b7280;
  --rk-line:    #e5e7eb;
  /* ! ONE BLUE ON THE SITE. This was #1d4ed8 -- which is, precisely,
     Tailwind's default blue-700, and a palette lifted from a framework's
     defaults is a large part of what makes a page look generated. The
     buttons now sit in the same navy family as the links. */
  --rk-accent:  #14477d;
  --rk-warn-bg: #fef3c7;
  --rk-warn-fg: #78350f;
  --rk-error:   #b91c1c;
  --rk-hover:   #f9fafb;
}

.rankings-page * { box-sizing: border-box; }

.rankings-page .sub {
  color: var(--rk-muted);
  margin: 0 0 1.25rem;
  font-size: 0.875rem;
}


/* ---------------------------------------------------------------- *
 *  BOARD TABS
 * ---------------------------------------------------------------- */

.rankings-page .tabs {
  display: flex;
  gap: 0.25rem;
  margin-bottom: 1rem;
}

.rankings-page .tab {
  padding: 0.5rem 1.125rem;
  border: 1px solid var(--rk-line);
  background: #fff;
  border-radius: 6px;
  cursor: pointer;
  font: inherit;
  font-size: 0.875rem;
  font-weight: 500;
}

/* Styled off aria-selected rather than a separate .active class, so the
   accessible state and the visible state cannot drift apart. */
.rankings-page .tab[aria-selected="true"] {
  background: var(--rk-accent);
  border-color: var(--rk-accent);
  color: #fff;
}


/* ---------------------------------------------------------------- *
 *  FILTERS
 * ---------------------------------------------------------------- */

.rankings-page .filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.625rem;
  align-items: flex-end;
  padding: 0.875rem;
  border: 1px solid var(--rk-line);
  border-radius: 8px;
  margin-bottom: 1rem;
}

.rankings-page .field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}

.rankings-page .field label {
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--rk-muted);
}

.rankings-page .field input,
.rankings-page .field select {
  padding: 0.375rem 0.5rem;
  border: 1px solid var(--rk-line);
  border-radius: 5px;
  font: inherit;
  font-size: 0.875rem;
  min-width: 7rem;
}

.rankings-page .field input.narrow { min-width: 4.5rem; }
.rankings-page .field input.state  { text-transform: uppercase; }

/* WHICH FILTERS ARE VISIBLE depends on the board, driven entirely by
   [data-board] on the wrapper. rankings.js sets that one attribute and never
   touches individual fields.

   Date fields are PERFORMANCE-only: an "ability" measured over two weeks is
   one or two races, which is a performance. The API returns 400 for that
   combination rather than silently ignoring it, so hiding the inputs keeps the
   UI and the API telling the same story. */
.rankings-page .perf-only { display: none; }
.rankings-page[data-board="performance"] .perf-only    { display: flex; }
/* Shared by Performances and Best times: the distance select and the
   course specifier. */
.rankings-page .perfpr-only { display: none; }
.rankings-page[data-board="performance"] .perfpr-only,
.rankings-page[data-board="pr"] .perfpr-only { display: flex; }
.rankings-page[data-board="performance"] .ability-only { display: none; }

/* ★ THE PR BOARD HIDES BOTH OF THE OTHERS' CONTROLS AND SHOWS ITS OWN.
   min_races is an ability idea and dates are a performance one; the times
   board wants a distance and, being one row per athlete, has no race count to
   filter on. Driven off data-board like the rules above, so rankings.js never
   touches individual field visibility. */
/* ---- the teams board ---------------------------------------------- *
 * It ranks a precomputed meet, so most of the other boards' controls have
 * nothing to act on: min-races is per athlete, the dates and the distance
 * belong to single races, and sport=both cannot be one field. */
/* ★ COURSES SHARE THE TEAMS PATTERN: its own filters, and everything that
   belongs to an athlete board hidden. A course has no pool, no sport, no
   season and no school, so showing those controls would offer filters that
   silently do nothing. */
.rankings-page .courses-only { display: none; }
.rankings-page[data-board="courses"] .courses-only { display: flex; }
.rankings-page[data-board="courses"] p.board-note.courses-only { display: block; }
.rankings-page[data-board="courses"] .ability-only { display: none; }
.rankings-page[data-board="courses"] .perf-only    { display: none; }
.rankings-page[data-board="courses"] .teams-only   { display: none; }
/* ! STATE IS NOT IN HERE ON PURPOSE. A course is somewhere, so the state
   filter means exactly what it says on this board; pool, sport, scope,
   grade, year and school describe an athlete and would do nothing. */
.rankings-page[data-board="courses"] .not-courses  { display: none; }
/* Finding an ATHLETE on a board of ground is not a question with an answer --
   the same reason it is off on teams. And "Coming soon" lists nation,
   division and the rest: unbuilt filters for ATHLETE boards, which read on
   this one as promises about courses that nobody has made. */
.rankings-page[data-board="courses"] .findme     { display: none; }
.rankings-page[data-board="courses"] .wip        { display: none; }

.rankings-page .teams-only { display: none; }
.rankings-page[data-board="teams"] .teams-only   { display: flex; }
.rankings-page[data-board="teams"] .ability-only { display: none; }
.rankings-page[data-board="teams"] .perf-only    { display: none; }
.rankings-page[data-board="teams"] .pr-only      { display: none; }
/* Finding an ATHLETE on a board of teams is not a question with an answer. */
.rankings-page[data-board="teams"] .findme       { display: none; }
.rankings-page[data-board="teams"] p.board-note.teams-only { display: block; }

/* ★ THE COURSE SPECIFIER ON TEAMS. The Course combo is always offered on
   the Teams tab (teams-ok); once a course is picked, rankings.js stamps
   .teams-course on the page and the board becomes single races at that
   venue -- so Distance appears, and the season/geography machinery (Year,
   Min athletes, Scope, State) goes away, because none of it means anything
   for one race at one place. The note (p.teams-only) stays: it explains
   the mode. */
.rankings-page[data-board="teams"] .perfpr-only.teams-ok { display: flex; }
.rankings-page[data-board="teams"].teams-course .perfpr-only { display: flex; }
.rankings-page[data-board="teams"].teams-course .field.teams-only,
.rankings-page[data-board="teams"].teams-course .year-field,
.rankings-page[data-board="teams"].teams-course .scope-field,
.rankings-page[data-board="teams"].teams-course .state-field { display: none; }

.rankings-page .pr-only { display: none; }
.rankings-page[data-board="pr"] .pr-only      { display: flex; }
.rankings-page[data-board="pr"] .ability-only { display: none; }
.rankings-page[data-board="pr"] .perf-only    { display: flex; }

/* The caveat is a paragraph, not a field, so flex would stretch it. */
.rankings-page .board-note {
  flex-basis: 100%;
  margin: 0.35rem 0 0;
  font-size: 0.82rem;
  color: var(--muted, #666);
}
.rankings-page[data-board="pr"] .board-note { display: block; }

/* Times are compared down a column, so they line up and share a width. */
/* A DNF is not a slow time, so it should not read as one. */
.rankings-page .rk td.time.dnf a { color: var(--muted, #888); }

.rankings-page .rk td.time {
  font-variant-numeric: tabular-nums;
  text-align: right;
  white-space: nowrap;
}

.rankings-page button.go {
  padding: 0.4375rem 1.25rem;
  border: 0;
  border-radius: 5px;
  background: var(--rk-accent);
  color: #fff;
  font: inherit;
  font-size: 0.875rem;
  font-weight: 500;
  cursor: pointer;
}
.rankings-page button.go:disabled { opacity: 0.5; cursor: default; }


/* ---------------------------------------------------------------- *
 *  THE KNOWN-BIAS NOTICE
 * ---------------------------------------------------------------- */

/* Hidden by default; rankings.js adds .show when the API reports
   national_bias, i.e. whenever no state filter is applied. */
.rankings-page .notice {
  display: none;
  padding: 0.625rem 0.875rem;
  background: var(--rk-warn-bg);
  color: var(--rk-warn-fg);
  border-radius: 6px;
  font-size: 0.8125rem;
  margin-bottom: 0.875rem;
}
.rankings-page .notice.show { display: block; }


/* ---------------------------------------------------------------- *
 *  TABLE
 * ---------------------------------------------------------------- */
/*
 * These deliberately do NOT restyle tables generally -- style.css already has
 * `table`, `th, td` and `td:nth-child(...)` rules for the race and meet pages,
 * and those positional selectors would fight with this table's column order.
 * Everything here is under .rankings-page and stays there.
 */

.rankings-page table.rk {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.875rem;
}

.rankings-page table.rk th,
.rankings-page table.rk td {
  padding: 0.5rem 0.625rem;
  text-align: left;
  border-bottom: 1px solid var(--rk-line);
}

.rankings-page table.rk th {
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--rk-muted);
  font-weight: 600;
}

/* tabular-nums forces fixed-width digits, so a column of ratings lines up on
   the decimal point instead of jittering row to row. */
.rankings-page table.rk td.num,
.rankings-page table.rk th.num {
  font-variant-numeric: tabular-nums;
}

.rankings-page table.rk td.rank   { color: var(--rk-muted); width: 2.875rem; }
.rankings-page table.rk td.rating { font-weight: 600; }

.rankings-page table.rk tbody tr:hover { background: var(--rk-hover); }

.rankings-page .state { color: var(--rk-muted); font-size: 0.8125rem; }


/* ---------------------------------------------------------------- *
 *  STATUS + PAGING
 * ---------------------------------------------------------------- */

.rankings-page .status {
  padding: 1.75rem 0;
  color: var(--rk-muted);
  text-align: center;
}
.rankings-page .status.error { color: var(--rk-error); }

.rankings-page .pager {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  margin-top: 1rem;
}

.rankings-page .pager button {
  padding: 0.375rem 0.875rem;
  border: 1px solid var(--rk-line);
  background: #fff;
  border-radius: 5px;
  cursor: pointer;
  font: inherit;
  font-size: 0.875rem;
}
.rankings-page .pager button:disabled { opacity: 0.4; cursor: default; }

.rankings-page .pager .page { color: var(--rk-muted); font-size: 0.8125rem; }

/* Hidden until the first result set arrives; rankings.js toggles this class. */
.rankings-page .pager.hidden { display: none; }

/* ---- unit toggles: (°C, °F) as small inline chips ---------------------- */
/*
 * ★ WHY THESE NEED display:inline EXPLICITLY.
 *   .conv-row > label is `display:flex; flex-direction:column`, so EVERY
 *   child becomes its own row -- including bare text nodes. That put the
 *   "(", each button, the ",", the ")" and the input on six separate lines.
 *   Wrapping the parenthetical in .unit-toggle makes it ONE flex item, and
 *   inside that span normal inline flow resumes.
 *
 *   width:auto is load-bearing too: .conv-row input,select sets min-width
 *   160px, and a button left to inherit button defaults renders far wider
 *   than its text.
 */
/* The parenthetical is one WIDGET, so inline-flex: the two buttons share an
   edge and behave as a single segmented control rather than a list. */
.unit-toggle {
    display: inline-flex;
    align-items: baseline;
    color: #bbb;
    font-weight: 400;
}
.unit-btn {
    /* inline-block so padding and a border box lay out; still in line flow.
       ZERO vertical padding and line-height 1 keep the control the same
       height as the parentheses beside it -- any vertical padding makes it
       taller than the text it sits in, which is what made it look bolted on. */
    display: inline-block;
    width: auto;
    min-width: 0;
    padding: 0 0.35rem;
    margin: 0;
    border: 1px solid #ddd;
    border-radius: 0;
    background: #f4f4f4;
    font: inherit;
    font-size: 0.7rem;
    line-height: 1.15;
    color: #aaa;
    cursor: pointer;
    vertical-align: baseline;
    transition: background 0.12s, border-color 0.12s, color 0.12s;
}

/* Joined: round only the outer corners, and collapse the shared border so
   the seam is one line rather than two. */
.unit-btn:first-of-type  { border-radius: 4px 0 0 4px; }
.unit-btn:last-of-type   { border-radius: 0 4px 4px 0; border-left: none; }

/* ONE-OR-THE-OTHER: the chosen unit is filled dark, the other SOFTENS to a
   near-invisible grey. A pair where both are legible reads as two separate
   buttons; a pair where one recedes reads as a choice already made. */
.unit-btn.is-on {
    background: #1a1a1a;
    border-color: #1a1a1a;
    color: #fff;
    font-weight: 600;
}
.unit-btn:not(.is-on):hover { background: #e8e8e8; color: #666; }
.unit-btn.is-on:hover { background: #333; border-color: #333; }
.unit-btn:focus-visible { outline: 2px solid #4a90d9; outline-offset: 1px; }

/* the constituent-parts sub-panel inside the weather block */
.conv-wx-parts { margin: 0.5rem 0 0 0; }
.conv-wx-parts summary {
    cursor: pointer; color: #777; font-size: 0.8rem;
}
.conv-hint { color: #777; font-size: 0.8rem; margin: 0.4rem 0 0 0; }

/* ---- dropdown anchoring ------------------------------------------------ */
/*
 * .conv-drop is position:absolute; top:100%, so it hangs off its nearest
 * POSITIONED ancestor. .src-field is position:relative and its box includes
 * the label AND the "chosen" confirmation line -- so the menu opened a row
 * and a half below the input. .drop-anchor wraps ONLY the input, so the menu
 * sits flush against it.
 */
.drop-anchor { position: relative; display: block; }
.drop-anchor > input { width: 100%; box-sizing: border-box; }

/* The menu matches the input exactly: same left edge, same width. A native
   <datalist> could do neither, which is why distance now uses this instead.
 *
 * The input keeps its OWN complete border and the menu sits just beneath it
 * as a separate box. The earlier -1px overlap merged the two into one shape,
 * which hid the input's bottom edge and made the field look like it had grown
 * rather than opened. border-top is restored here for the same reason -- the
 * base .conv-drop rule removes it, which only makes sense for a merged shape.
 */
.drop-anchor > .conv-drop {
    top: 100%;
    left: 0;
    right: 0;
    margin-top: 2px;
    border: 1px solid #ccc;
    border-radius: 4px;
    box-shadow: 0 2px 6px rgba(0,0,0,0.08);
}

/* the masked time field reads as a clock, so give it the digits' spacing */
#in-time { font-variant-numeric: tabular-nums; letter-spacing: 0.02em; }

/* ---- rankings section heading ---------------------------------------- */
.rank-heading {
    margin: 2rem 0 0.15rem;
    text-align: center;
    font-size: 1.6rem;
    letter-spacing: -0.01em;
}
.rank-sub {
    margin: 0 0 1.5rem;
    text-align: center;
    color: #777;
    font-size: 0.9rem;
}

/* The per-pool heading now carries a real label, so it can afford to look
   like one rather than like a debug string. */
.pool-name {
    margin: 1.25rem 0 0.4rem;
    font-size: 0.95rem;
    font-weight: 600;
    color: #333;
    letter-spacing: 0.01em;
}

/* Deep link from a home-page pool table into the full /rankings board.
   Carries the pool, sport, board and season the user is currently looking at.

   Styled as a bordered pill rather than a text link: the site has no accent
   hue at all (#111/#222/#333 throughout), so a coloured link would be the only
   colour on the page. This borrows the vocabulary .chip and
   .toggle-group button.is-active already established -- outline at rest,
   fills dark on hover -- which reads as "control", not "footnote".

   inline-block inside a right-aligned wrapper: the pill must shrink to its
   text. A block-level element with text-align:right would put the words on the
   right but stretch the border across the whole table. */
.pool-head {
    display: flex;
    align-items: baseline;      /* two different font sizes, one visual line */
    justify-content: space-between;
    gap: 1rem;
    margin: 1.25rem 0 0.4rem;   /* inherited from .pool-name, which is now zeroed */
}
.pool-head .pool-name {
    margin: 0;                  /* the row owns the spacing now */
}
.pool-more {
    flex: none;                 /* never let the label squeeze the link */
    display: inline-block;
    padding: 0.2rem 0.6rem;
    border: 1px solid #ccc;
    border-radius: 999px;
    font-size: 0.75rem;
    font-weight: 600;
    color: #222;
    text-decoration: none;
    background: #fff;
    white-space: nowrap;
    transition: background 0.12s ease, border-color 0.12s ease, color 0.12s ease;
}
.pool-more:hover,
.pool-more:focus-visible {
    background: #222;
    border-color: #222;
    color: #fff;
}
/* The arrow nudges on hover. Separate span so only the glyph moves, and
   inline-block because transform does nothing to an inline element. */
.pool-more .arr {
    display: inline-block;
    transition: transform 0.12s ease;
}
.pool-more:hover .arr { transform: translateX(2px); }

/* "XC -- 2025 Season" / "TF -- All-Time". Sits above the two boards and names
   the view.

   LEFT-ALIGNED ON PURPOSE. Centring it was tried and looked wrong: the label
   is short, the pair below it is wide, and a centred line floating over two
   left-aligned tables reads as unmoored rather than as their heading. It also
   stopped lining up with .board-title below it. Do not re-centre.

   Had no rule at all before this and was rendering as a default <h2>; the
   size and weight here match .rank-heading's register one step down. */
.board-scope-label {
    margin: 1.6rem 0 0.9rem;
    text-align: left;
    font-size: 1.15rem;
    font-weight: 600;
    letter-spacing: -0.01em;
    color: #222;
}

/* The hero wordmark, as an image rather than text.

   .brand still carries the old font rules; they now apply to nothing, because
   the anchor's only child is an <img>. Left in place because the topbar's
   .brand is still text.

   min() rather than a fixed width: the mosaic's centre cell is a grid area
   that narrows on small screens, and a fixed-width logo would overflow it
   before the media query at 850px fires. The percentage wins on narrow
   screens, the px ceiling wins on wide ones.

   display: block on the img kills the inline-descender gap under it, which
   would otherwise push the tagline a few px lower than the design. */
.hero-mosaic .brand-logo {
    display: block;
    margin: 0 0 0.5rem;
    line-height: 0;
}
.hero-mosaic .brand-logo img {
    display: block;
    /* A block element with an explicit width and no auto margins sits hard
       left -- it does NOT inherit the parent's text-align: center, because
       that centres inline content, not the block's own box. */
    margin-inline: auto;
    width: min(420px, 78%);
    height: auto;
}

@media (max-width: 850px) {
    /* The mosaic collapses to a single column here and the centre cell gets
       most of the width, so the logo can take more of it. */
    .hero-mosaic .brand-logo img { width: min(340px, 88%); }
}

/* Topbar wordmark. Replaces the text .brand on every page via _topbar.html.

   HEIGHT, NOT WIDTH. The topbar is a flex row with align-items: center, so
   what matters is that the mark's cap height matches the nav links beside it.
   Setting a width would let the mark's height drift if the artwork's aspect
   ratio ever changes; setting the height pins the thing the eye compares.

   1.15rem against the nav's 1rem: the mark is all-caps, and caps set to the
   same em size as mixed-case text read visually smaller.

   The .brand rules above still apply (font-weight, color) but now govern
   nothing, since the anchor's only child is an image. Left alone rather than
   removed -- they are shared with markup this patch does not touch. */
.topbar .brand-mark {
    display: flex;
    align-items: center;
    line-height: 0;
    flex: none;
}
.topbar .brand-mark img {
    display: block;
    /* 3rem, because the LOCKUP is 2.75x the height of its own letters -- the
       tick scale and the plate sit above and below the caps. At the 1.15rem
       used for the flat cut the actual word would be ~7px tall. This is the
       one number to change if the bar feels too deep. */
    height: 3rem;
    width: auto;
}

/* The lockup is ~2.5x taller than the text it replaced, so the bar's own
   padding comes down to stop the header eating the fold. */
.topbar { padding-top: 0.4rem; padding-bottom: 0.4rem; }

@media (max-width: 700px) {
    /* The search field takes 320px of a narrow bar; shrink the mark before
       anything is forced to wrap. */
    .topbar .brand-mark img { height: 2.25rem; }
}

/* ---- prose pages: /about and /report -------------------------------- *
 * Scoped under .page so nothing here can reach the boards, the mosaic or
 * the rankings table -- the same containment .rankings-page uses, and for
 * the same reason: an earlier version put bare `h2` and `p` rules in this
 * file and restyled the whole site.
 *
 * max-width in ch, not px: the readable line length is a function of the
 * FONT, and 68 characters stays right if the type ever changes size. */
.page {
    max-width: 68ch;
    margin: 0 auto;
    padding: 2rem 1.5rem 4rem;
    line-height: 1.65;
    color: #222;
}
.page h1 { font-size: 1.9rem; margin: 0 0 0.5rem; letter-spacing: -0.01em; }
.page h2 { font-size: 1.2rem; margin: 2.2rem 0 0.6rem; }
.page h3 { font-size: 1rem; margin: 1.6rem 0 0.4rem; }
.page p  { margin: 0 0 0.9rem; }
.page a  { color: #111; }
.page-lead { font-size: 1.05rem; color: #444; margin-bottom: 1.5rem; }
.page-meta { color: #777; font-size: 0.85rem; }

.page-steps { padding-left: 1.2rem; margin: 0 0 1rem; }
.page-steps li { margin-bottom: 0.7rem; }

/* ---- the report form ------------------------------------------------ */
.form-row { margin: 0 0 1.1rem; }
.form-row label {
    display: block;
    font-size: 0.85rem;
    font-weight: 600;
    margin-bottom: 0.3rem;
}
.form-row label .opt { font-weight: 400; color: #888; }
.form-row input,
.form-row select,
.form-row textarea {
    width: 100%;
    padding: 0.5rem 0.65rem;
    border: 1px solid #ccc;
    border-radius: 4px;
    font: inherit;
    font-size: 0.95rem;
    /* Without this, a textarea inherits the browser's monospace default and
       looks like a different site from the paragraph above it. */
    font-family: inherit;
    box-sizing: border-box;
}
.form-row textarea { resize: vertical; }

/* Hidden until it has something to say, so the layout does not jump when a
   message appears. */
.form-status { display: none; margin-top: 0.8rem; font-size: 0.9rem; }
.form-status.show { display: block; }
.form-status.error { color: #a32d2d; }


/* ---- topbar: About / Report ----------------------------------------- */
/* .muted is gone: the report link reads the same as every other nav item.
   Greying it out was me deciding it mattered less than it does. */

@media (max-width: 1100px) {
    /* Five nav items plus a 320px search field overflows before the existing
       850px breakpoint fires. Let the search shrink first -- it is the only
       element here that degrades gracefully at a smaller width. */
    .search-wrap { width: 220px; }
    .topnav { gap: 0.9rem; }
}
@media (max-width: 820px) {
    .search-wrap { width: 180px; }
}

/* ---- flag tooltips --------------------------------------------------- *
 * A white speech bubble to the RIGHT of the badge, outlined in that badge's
 * own colour.
 *
 * Each flag class sets --tip; the bubble border and its tail both read it, so
 * a flag's colour is declared once and the tooltip follows. Adding a flag
 * type means adding one line here, not three.
 *
 * Built from data attributes, not a JS library: the text is already in the
 * DOM, so there is nothing to initialise and nothing to re-initialise when a
 * table re-renders. It works with JS disabled.
 *
 * Shown on :focus as well as :hover -- hover alone is invisible to keyboard
 * users and to every touch device, which is why the badges carry tabindex. */
.flag--pr  { --tip: #b8860b; }
.flag--sr  { --tip: #4b7a34; }
.flag--cpr { --tip: #2e53e6; }
.flag--rpr { --tip: #6b3fa0; }
.flag--csr { --tip: #1f6f8b; }
.flag--rsr { --tip: #8b3f6b; }
.flag--warn { --tip: #a05a00; }

.flag {
    position: relative;
    cursor: help;
}
.flag:focus { outline: none; }
.flag:focus-visible {
    outline: 2px solid var(--tip, #111);
    outline-offset: 1px;
}

.flag::after {
    /* \A with white-space: pre-line is what puts the title and the blurb on
       separate lines out of two attributes -- a pseudo-element cannot hold
       two differently-styled children. */
    content: attr(data-title) "\A" attr(data-blurb);
    white-space: pre-line;
    position: absolute;
    /* ABOVE the badge, opening to the RIGHT: its LEFT edge lines up with the
       badge, so the text runs away from the flag rather than back over it.
       Anchoring right instead would push the bubble leftward across the row
       it belongs to. */
    bottom: calc(100% + 10px);
    left: 0;
    z-index: 60;
    width: 250px;
    padding: 9px 12px;
    border: 2px solid var(--tip, #111);
    border-radius: 9px;
    background: #fff;
    color: #222;
    /* The badge is 11px/700 white with letter-spacing. None of that suits a
       paragraph, so every one of those is reset here. */
    font-size: 11.5px;
    font-weight: 400;
    line-height: 1.5;
    letter-spacing: 0;
    text-align: left;
    text-transform: none;
    box-shadow: 0 3px 12px rgba(0, 0, 0, 0.14);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.12s ease;
    pointer-events: none;
}

/* The tail, pointing DOWN at the badge. A white square with two coloured
   edges rotated 45deg -- that gives an OUTLINED arrow. A plain CSS triangle is
   made of borders so it cannot itself be outlined, and a solid one would cut
   through the bubble's border where they meet; the white fill covers that
   seam. Right + bottom edges are the two that meet at the DOWNWARD corner
   once rotated. */
.flag::before {
    content: "";
    position: absolute;
    bottom: calc(100% + 5px);
    left: 12px;
    z-index: 61;
    width: 10px;
    height: 10px;
    background: #fff;
    border-right: 2px solid var(--tip, #111);
    border-bottom: 2px solid var(--tip, #111);
    transform: rotate(45deg);
    opacity: 0;
    visibility: hidden;
    transition: opacity 0.12s ease;
    pointer-events: none;
}

.flag:hover::after, .flag:focus::after,
.flag:hover::before, .flag:focus::before {
    opacity: 1;
    visibility: visible;
}

@media (max-width: 900px) {
    /* Flags sit in the LAST table column. On a wide screen the bubble opens
       rightwards over the sidebar, which is fine; once the layout stacks
       there is nothing to the right but the edge of the page, so it anchors
       to the badge's right edge and opens leftwards instead. The tail moves
       with it -- otherwise it would point at empty space. */
    .flag::after {
        left: auto;
        right: 0;
        width: 210px;
    }
    .flag::before {
        left: auto;
        right: 12px;
    }
}

/* ---- rankings: filter menus, WIP, find-an-athlete ------------------- *
 * All scoped under .rankings-page, the same containment rankings.css uses --
 * an earlier version put bare `table` and `a` rules in this file and
 * restyled the whole site. */
.rankings-page .combo { position: relative; }

/* ★ FIXED SIZE, ALWAYS. The trigger shows a count, never the selections, so
   the toolbar cannot reflow as values are ticked and unticked. */
.rankings-page .combo-btn {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 6px;
    width: 150px;
    padding: 0.42rem 0.55rem;
    border: 1px solid #ccc;
    border-radius: 4px;
    background: #fff;
    font: inherit;
    font-size: 0.9rem;
    color: #999;                 /* "Any" is a placeholder, not a value */
    cursor: pointer;
    text-align: left;
}
.rankings-page .combo.has-values .combo-btn { color: #111; font-weight: 600; }
.rankings-page .combo-btn:hover { border-color: #888; }
.rankings-page .combo-btn-label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}
.rankings-page .combo-caret { color: #999; font-size: 0.7rem; }

/* The panel. Wide and scrollable rather than tall and pushy -- it floats over
   the toolbar instead of moving it. */
.rankings-page .combo-panel {
    position: absolute;
    z-index: 50;
    top: calc(100% + 4px);
    left: 0;
    /* width is set inline, per field: 56 states and 12 grades do not want the
       same box. See PANEL in rankings.js. */
    border: 1px solid #bbb;
    border-radius: 6px;
    background: #fff;
    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.16);
}
.rankings-page .combo-panel.hidden { display: none; }
/* ⚠ A 520px PANEL UNDER A RIGHT-HAND FILTER RUNS OFF THE PAGE. rankings.js
   measures on open and adds .flip, which anchors it to the trigger's right
   edge instead. Measured, not assumed from column order -- the filter row
   wraps at narrow widths and which control is rightmost changes. */
.rankings-page .combo-panel.flip { left: auto; right: 0; }

.rankings-page .combo-search { padding: 7px; border-bottom: 1px solid #eee; }
.rankings-page .combo-input {
    width: 100%;
    padding: 0.35rem 0.5rem;
    border: 1px solid #ccc;
    border-radius: 4px;
    font: inherit;
    font-size: 0.88rem;
    box-sizing: border-box;
}

/* ★ A GRID, AND NO SCROLLBAR FOR BOUNDED LISTS. All 56 states are visible at
   once, so after two uses you know where Texas is and go straight to it -- the
   thing a scroll bar takes away, because a row's position depends on where the
   scroll was left.
 *
 * ⚠ grid, NOT column-count. CSS columns were tried and they FRAGMENT a flex
 *   row at the column break -- the second column rendered half outside the
 *   panel. Grid places each row whole.
 *
 * max-height is a backstop for the searched field, whose result count is not
 * bounded; the bounded lists never reach it. */
.rankings-page .combo-opts {
    display: grid;
    grid-template-columns: repeat(var(--cols, 1), minmax(0, 1fr));
    gap: 1px 4px;
    max-height: 420px;
    overflow-y: auto;
    padding: 5px;
}
/* Column-major: fill DOWN the first column, then the second. Needs explicit
   ROWS and auto COLUMNS -- the inverse of the default -- so grid-template-
   columns is cleared or the two would fight and produce implicit columns. */
.rankings-page .combo-opts.flow-col {
    grid-auto-flow: column;
    grid-template-columns: none;
    grid-template-rows: repeat(var(--rows, 1), auto);
    grid-auto-columns: minmax(0, 1fr);
}
.rankings-page .combo-none { grid-column: 1 / -1; }
/* ⚠ NO CSS COLUMNS. Two columns were tried and each row is a flex box, which
   column-count splits and clips at the break -- the second column rendered
   half off the panel. A single scrolling list is what the taller panel is
   for, and it cannot break. */

/* ★ EVERY INHERITED LABEL STYLE IS RESET HERE, DELIBERATELY. These rows are
   <label> elements so the checkbox has a proper hit target and a screen
   reader gets the association -- but .rankings-page label is styled for the
   FILTER captions above the controls: uppercase, letter-spaced, bold, small.
   Inheriting that rendered the state list as "A L A B A M A". A <div> would
   dodge it and lose the accessibility, so the styles are overridden instead. */
/* ⚠ FOUR CLASSES PLUS THE ELEMENT, ON PURPOSE. rankings.css is loaded AFTER
   style.css, so its label rule beats anything here at equal specificity --
   `.rankings-page .combo-opt` (0,2,0) lost the tie and the states rendered as
   "A L A B A M A". This selector outranks it without !important, which would
   just move the fight somewhere harder to see. */
.rankings-page .combo-panel .combo-opts label.combo-opt {
    display: flex;
    align-items: center;
    gap: 6px;
    /* Tight rows: 14 of them have to fit without a scrollbar. */
    padding: 3px 5px;
    margin: 0;
    border-radius: 3px;
    font-size: 0.86rem;
    font-weight: 400;
    text-transform: none;
    letter-spacing: normal;
    color: #222;
    cursor: pointer;
}
/* The name takes the slack so the code sits flush right without margin
   tricks that fight the flex gap. */
/* nowrap + ellipsis: a wrapped label would make its entire grid row taller
   across every column. Truncating one long name beats adding a line to four. */
.rankings-page .combo-panel .combo-opts label.combo-opt > span:first-of-type {
    flex: 1;
    min-width: 0;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
.rankings-page .combo-panel .combo-opts label.combo-opt:hover { background: #f2f2ee; }
.rankings-page .combo-panel .combo-opts label.combo-opt.is-on {
    background: #eef2fd;
    font-weight: 600;
}
/* ⚠ AN EXPLICIT WIDTH, BECAUSE THE CHECKBOX INHERITS ONE. rankings.css sizes
   filter inputs to fill their field, and that rule reaches these checkboxes
   too -- each one rendered 112px wide, which left the flex row no space and
   collapsed every state NAME to zero width. The panel showed a column of bare
   codes with the names invisible.

   Sized here rather than by unsetting, since "whatever the browser default
   is" varies between engines and this has to line up in a grid. */
.rankings-page .combo-panel .combo-opts label.combo-opt input[type="checkbox"] {
    flex: none;
    width: 14px;
    min-width: 14px;
    height: 14px;
    margin: 0;
    padding: 0;
}
.rankings-page .combo-panel .combo-opts .combo-code {
    color: #999;
    font-size: 0.78rem;
    font-weight: 400;
    letter-spacing: normal;
    flex: none;
}
.rankings-page .combo-none { padding: 10px; color: #999; font-size: 0.86rem; }

.rankings-page .combo-add {
    display: block;
    width: 100%;
    padding: 6px;
    margin-bottom: 4px;
    border: 1px dashed #bbb;
    border-radius: 4px;
    background: #fafaf8;
    font: inherit;
    font-size: 0.86rem;
    cursor: pointer;
    text-align: left;
}
.rankings-page .combo-add:hover { background: #f0f0ec; }

.rankings-page .combo-foot {
    display: flex;
    justify-content: space-between;
    padding: 6px 7px;
    border-top: 1px solid #eee;
}
.rankings-page .combo-foot button {
    border: 0;
    background: none;
    font: inherit;
    font-size: 0.83rem;
    cursor: pointer;
    padding: 2px 4px;
}
.rankings-page .combo-clear { color: #888; }
.rankings-page .combo-clear:hover { color: #a32d2d; }
.rankings-page .combo-done { color: #2e53e6; font-weight: 600; }

.rankings-page .find-results {
    position: absolute;
    z-index: 40;
    left: 0;
    right: 0;
    max-height: 240px;
    overflow-y: auto;
    margin-top: 2px;
    border: 1px solid #ccc;
    border-radius: 5px;
    background: #fff;
    box-shadow: 0 4px 14px rgba(0, 0, 0, 0.12);
}
.rankings-page .find-results.hidden { display: none; }
.rankings-page .find-opt {
    display: block;
    width: 100%;
    padding: 0.35rem 0.6rem;
    border: 0;
    background: none;
    text-align: left;
    font: inherit;
    font-size: 0.88rem;
    cursor: pointer;
}
/* .is-active is the keyboard highlight (arrow keys); same colour as hover,
   because it means the same thing. */
.rankings-page .find-opt:hover,
.rankings-page .find-opt.is-active { background: #f0f0ec; }
.rankings-page .find-sub { display: block; color: #888; font-size: 0.78rem; }

/* ---- not built yet --------------------------------------------------- */
.rankings-page .wip { margin: 0 0 1rem; font-size: 0.85rem; color: #777; }
.rankings-page .wip summary { cursor: pointer; }
.rankings-page .wip-row {
    display: flex;
    flex-wrap: wrap;
    gap: 0.8rem;
    margin-top: 0.6rem;
}
.rankings-page .wip-row input {
    padding: 0.35rem 0.5rem;
    border: 1px dashed #ccc;
    border-radius: 4px;
    background: #fafaf8;
    color: #aaa;
    font: inherit;
    font-size: 0.85rem;
    width: 130px;
}

/* ---- find an athlete ------------------------------------------------- */
.rankings-page .findme { margin: 0 0 1rem; }
.rankings-page .findme label {
    display: block;
    font-size: 0.85rem;
    font-weight: 600;
    margin-bottom: 0.3rem;
}
.rankings-page .find-wrap { position: relative; max-width: 340px; }
.rankings-page .find-wrap input {
    width: 100%;
    padding: 0.45rem 0.6rem;
    border: 1px solid #ccc;
    border-radius: 4px;
    font: inherit;
    font-size: 0.9rem;
    box-sizing: border-box;
}
.rankings-page .find-status { display: none; margin-top: 0.4rem; font-size: 0.85rem; }
.rankings-page .find-status.show { display: block; }
.rankings-page .find-status.error { color: #a32d2d; }

.rankings-page tr.is-found td { background: #fdf6d8; }
.rankings-page tr.is-found td:first-child { box-shadow: inset 3px 0 0 #b8860b; }

/* ---- sortable headers ------------------------------------------------ *
 * The header IS the sort control -- there is no Sort/Order menu. */
.rankings-page table.rk th { text-align: left; }
.rankings-page table.rk td { text-align: left; }

.rankings-page th.sortable {
    cursor: pointer;
    user-select: none;
    white-space: nowrap;
}
.rankings-page th.sortable:hover { color: #111; text-decoration: underline; }
.rankings-page th.is-sorted { color: #111; font-weight: 700; }

/* ---- loading --------------------------------------------------------- *
 * Loud on purpose. A faint grey word where a table used to be reads as an
 * empty result, so people re-click Apply -- which the busy guard swallows,
 * and the page looks broken. */
.rankings-page .status.loading {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 0.7rem;
    min-height: 180px;
    font-size: 1rem;
    color: #444;
}
.rankings-page .spinner {
    width: 20px;
    height: 20px;
    border: 2.5px solid #ddd;
    border-top-color: #2e53e6;
    border-radius: 50%;
    animation: rk-spin 0.7s linear infinite;
}
@keyframes rk-spin { to { transform: rotate(360deg); } }

@media (prefers-reduced-motion: reduce) {
    /* A spinner is decoration; the message is the information. */
    .rankings-page .spinner { animation: none; border-top-color: #ddd; }
}


/* ---- predictions ----------------------------------------------------- *
 * Scoped under .predict2, the same containment the rankings page uses. */
.predict2 { max-width: 940px; margin: 0 auto; padding: 2rem 1.5rem 4rem; color: #222; }
.predict2 h1 { font-size: 1.9rem; margin: 0 0 0.4rem; }

/* Steps reveal themselves as the one above is answered. An empty form with
   every input visible asks the reader to work out the order. */
.predict2 .step { margin: 0 0 1.8rem; padding-top: 1.4rem;
                  border-top: 1px solid #eee; }
.predict2 .step.hidden { display: none; }
.predict2 .step-head { display: flex; align-items: center; gap: 8px;
                       font-size: 0.95rem; font-weight: 700; margin-bottom: 0.8rem; }
.predict2 .step-n {
    display: inline-flex; align-items: center; justify-content: center;
    width: 22px; height: 22px; border-radius: 50%;
    background: #111; color: #fff; font-size: 0.75rem;
}

/* Two big choices, not a dropdown: there are two, they are equally weighted,
   and each needs a line of explanation that a <select> cannot carry. */
.predict2 .cards { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 1rem; }
.predict2 .card {
    flex: 1 1 260px; text-align: left; padding: 0.7rem 0.85rem;
    border: 1px solid #ccc; border-radius: 6px; background: #fff;
    font: inherit; cursor: pointer;
}
.predict2 .card:hover { border-color: #888; }
.predict2 .card.is-on { border-color: #111; box-shadow: inset 0 0 0 1px #111; }
.predict2 .card-t { display: block; font-weight: 600; font-size: 0.92rem; }
.predict2 .card-d { display: block; color: #777; font-size: 0.8rem; margin-top: 2px; }
.predict2 .when-pane.hidden, .predict2 .who-pane.hidden { display: none; }

.predict2 .pick-wrap { position: relative; max-width: 380px; }
.predict2 .pick-wrap.wide { max-width: 520px; }
.predict2 .pick-wrap input {
    width: 100%; padding: 0.5rem 0.65rem; border: 1px solid #ccc;
    border-radius: 4px; font: inherit; font-size: 0.95rem; box-sizing: border-box;
}
.predict2 .pick-results {
    position: absolute; z-index: 40; left: 0; right: 0; max-height: 320px;
    overflow-y: auto; margin-top: 2px; border: 1px solid #ccc;
    border-radius: 5px; background: #fff; box-shadow: 0 4px 14px rgba(0,0,0,0.12);
}
.predict2 .pick-results.hidden { display: none; }
/* The year heading. A meet name matches thirty editions and the year is the
   only thing that separates them. */
.predict2 .pick-group {
    padding: 3px 8px; background: #f4f4f0; border-top: 1px solid #eee;
    font-size: 0.72rem; font-weight: 700; color: #666; letter-spacing: 0.04em;
}
.predict2 .pick-opt {
    display: block; width: 100%; padding: 0.4rem 0.65rem; border: 0;
    background: none; text-align: left; font: inherit; font-size: 0.88rem;
    cursor: pointer;
}
.predict2 .pick-opt:hover { background: #f0f0ec; }
.predict2 .pick-sub { display: block; color: #888; font-size: 0.78rem; }

/* The chosen athletes.
 *
 * ⚠ opacity: 1 IS THE LINE THAT MATTERS, AND COLOUR WAS NEVER THE PROBLEM.
 *   A global `.chip { opacity: 0.45 }` exists further up this file for the
 *   athlete page's OFF-state toggles, and it was fading these to 45% -- so
 *   darkening the text from #222 to #111 to #000 changed nothing visible,
 *   twice. A transparent black is still grey.
 */
.predict2 .chip {
    opacity: 1;
    display: inline-flex; align-items: center; gap: 6px;
    padding: 3px 6px 3px 10px; border: 1px solid #bbb; border-radius: 999px;
    background: #f1f1ec; color: #000; font-size: 0.88rem; font-weight: 600;
}
.predict2 .chip.big { font-size: 0.95rem; font-weight: 600; }
.predict2 .chip-x { border: 0; background: none; cursor: pointer; color: #444;
                    font-size: 1.05rem; line-height: 1; padding: 0 3px; }
.predict2 .chip-x:hover { color: #a32d2d; }
.predict2 .chip-note { margin-left: 8px; color: #888; font-size: 0.82rem; }

/* Step 1, answered. A filled card in the place the search box was, so the
   step still reads as step 1 rather than vanishing. */
.predict2 .meet-search.hidden, .predict2 .meet-chosen.hidden { display: none; }
.predict2 .meet-chosen {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 2px 12px;
    align-items: center;
    max-width: 520px;
    padding: 0.7rem 0.85rem;
    border: 1px solid #ccc;
    border-left: 3px solid #111;
    border-radius: 5px;
    background: #fafaf8;
}
.predict2 .mc-main { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.predict2 .mc-name { font-size: 1.05rem; font-weight: 700;
                     overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The year as its own token rather than part of the name -- thirty editions
   are one meet, not thirty names. */
.predict2 .mc-year {
    flex: none; padding: 1px 6px; border-radius: 3px;
    background: #eceae2; color: #555; font-size: 0.78rem; font-weight: 600;
}
.predict2 .mc-main { grid-column: 1; grid-row: 1; }
.predict2 .mc-sub  { grid-column: 1; grid-row: 2; color: #888; font-size: 0.82rem; }
/* ⚠ BOTH COORDINATES, NOT JUST THE ROW. `grid-row: 1 / span 2` alone puts the
   button in the first FREE cell, which is column 1 -- so Change rendered to
   the LEFT of the meet name and shoved it across. Auto-placement fills by
   row; spanning rows does not imply a column. */
.predict2 .mc-change {
    grid-column: 2; grid-row: 1 / span 2; align-self: center;
    border: 1px solid #ccc; border-radius: 4px; background: #fff;
    padding: 0.3rem 0.6rem; font: inherit; font-size: 0.82rem; cursor: pointer;
}
.predict2 .mc-change:hover { border-color: #888; }

/* A team already in the field: shown, with the action it offers named. */
.predict2 .pick-opt.is-in .pick-name { color: #888; }
.predict2 .pick-name { }
/* The action sits right-aligned so the eye can run down a column of Add /
   Remove rather than hunting for it after each name. */
.predict2 .pick-act {
    float: right; margin-left: 10px;
    font-size: 0.75rem; font-weight: 600; color: #2e53e6;
}
.predict2 .pick-opt.is-in .pick-act { color: #a32d2d; }
.predict2 .chosen.hidden, .predict2 .chosen-meet.hidden { display: none; }
.predict2 .chosen, .predict2 .chosen-meet { margin-top: 8px; }

.predict2 .hint { color: #888; font-size: 0.82rem; margin: 0.45rem 0 0; }
.predict2 label { display: block; font-size: 0.8rem; font-weight: 600;
                  margin-bottom: 0.25rem; }
.predict2 input[type=date] { padding: 0.4rem 0.55rem; border: 1px solid #ccc;
                             border-radius: 4px; font: inherit; }
.predict2 .check { display: flex; align-items: center; gap: 7px;
                   font-weight: 400; font-size: 0.85rem; margin: 0; }
.predict2 .check input { width: 14px; height: 14px; margin: 0; }

/* The field. A grid of team cards, each editable in place -- a roster is a
   thing you scan and adjust, not a list you read top to bottom. */
.predict2 .field-head { display: flex; flex-wrap: wrap; gap: 1rem;
                        align-items: center; justify-content: space-between;
                        margin-bottom: 0.8rem; font-size: 0.88rem; }
.predict2 .field-grid.hidden { display: none; }
.predict2 .field-grid { display: grid; gap: 8px; align-items: start;
                        grid-template-columns: repeat(auto-fill, minmax(215px, 1fr)); }
/* Breathing room before "Add a team" -- it is a different action from editing
   the field above it, and butting it against the grid read as part of it. */
.predict2 .field-grid + .pick-wrap { margin-top: 1.4rem; }
.predict2 .team-card { border: 1px solid #e2e2dc; border-radius: 6px;
                       background: #fff; }

/* The summary IS the closed card, so the padding lives here rather than on
   the container -- otherwise a collapsed team keeps the body's spacing and
   the grid rows come out uneven. */
.predict2 .team-name {
    display: flex; align-items: center; gap: 8px;
    padding: 0.5rem 0.6rem;
    font-weight: 600; font-size: 0.87rem;
    cursor: pointer; list-style: none;
}
/* The default disclosure triangle is replaced by our own, so it sits after
   the name instead of before it and does not shove the label around. */
.predict2 .team-name::-webkit-details-marker { display: none; }
.predict2 .team-name::before {
    content: "\25b8"; color: #bbb; font-size: 0.7rem;
    transition: transform 0.12s ease;
}
.predict2 .team-card[open] > .team-name::before { transform: rotate(90deg); }
.predict2 .t-label { flex: 1; min-width: 0; overflow: hidden;
                     text-overflow: ellipsis; white-space: nowrap; }
.predict2 .team-n { color: #999; font-weight: 400; }
.predict2 .team-x {
    flex: none; border: 0; background: none; color: #ccc; cursor: pointer;
    font-size: 1rem; line-height: 1; padding: 0 2px;
}
.predict2 .team-x:hover { color: #a32d2d; }

/* Show: nothing / teams / rosters. A segmented control rather than two
   toggles -- the states are mutually exclusive and the current one should be
   readable without pressing anything. */
.predict2 .viewsel {
    display: inline-flex; align-items: center; gap: 0;
    margin-left: 10px; font-size: 0.78rem; color: #888;
}
.predict2 .vbtn {
    border: 1px solid #ddd; background: #fff; margin-left: -1px;
    padding: 1px 7px; font: inherit; font-size: 0.78rem; color: #555;
    cursor: pointer;
}
.predict2 .vbtn:first-of-type { margin-left: 8px; border-radius: 3px 0 0 3px; }
.predict2 .vbtn:last-of-type  { border-radius: 0 3px 3px 0; }
.predict2 .vbtn.is-on { background: #111; border-color: #111; color: #fff; }

/* A button that reads as a link: quiet actions beside the field summary. */
.predict2 .linkish {
    border: 0; background: none; padding: 0; margin-left: 8px;
    font: inherit; font-size: 0.82rem; color: #2e53e6;
    text-decoration: underline; cursor: pointer;
}
.predict2 .linkish.undo { color: #a32d2d; }
.predict2 .team-body { padding: 0 0.6rem 0.55rem; }
/* Names are the content of this card, not chrome -- they were 0.82rem in the
   inherited grey and hard to read against the rating beside them. */
.predict2 .runner-row { display: flex; align-items: center; gap: 6px;
                        font-size: 0.86rem; padding: 2px 0; color: #222; }
.predict2 .r-name { flex: 1; min-width: 0; overflow: hidden;
                    text-overflow: ellipsis; white-space: nowrap; }
.predict2 .r-rating { color: #666; font-size: 0.8rem; font-variant-numeric: tabular-nums; }
.predict2 .r-x { border: 0; background: none; color: #bbb; cursor: pointer;
                 font-size: 0.95rem; line-height: 1; padding: 0 2px; }
.predict2 .r-x:hover { color: #a32d2d; }
.predict2 .runner-row.is-out { color: #aaa; }
.predict2 .runner-row.is-back { color: #222; }
.predict2 .r-add { border: 1px solid #ccc; background: #fff; border-radius: 3px;
                   font: inherit; font-size: 0.72rem; cursor: pointer;
                   padding: 0 5px; }
/* "Add from squad", and the list it opens. Quiet until used -- most cards
   never need it, and thirty of these should not read as thirty buttons. */
.predict2 .squad-btn {
    display: block; width: 100%; margin-top: 6px;
    border: 1px dashed #ddd; border-radius: 3px; background: #fff;
    padding: 2px 6px; font: inherit; font-size: 0.74rem; color: #666;
    cursor: pointer; text-align: left;
}
.predict2 .squad-btn:hover { border-color: #999; color: #222; }
.predict2 .squad-list { margin-top: 4px; max-height: 190px; overflow-y: auto; }
.predict2 .squad-list.hidden { display: none; }
.predict2 .squad-loading { color: #999; font-size: 0.76rem; padding: 3px 0; }

.predict2 .dropped { margin-top: 5px; }
.predict2 .dropped summary { color: #999; font-size: 0.76rem; cursor: pointer; }

.predict2 .actions { display: flex; align-items: center; gap: 12px;
                     margin-top: 1.4rem; }
.predict2 .actions.hidden { display: none; }
.predict-status { display: none; font-size: 0.88rem; }
.predict-status.show { display: inline; }
.predict-status.error { color: #a32d2d; }

/* The answer. Big, with the band under it -- a prediction without a range
   claims a precision the data does not have. */
.predict2 .result { margin-top: 1.6rem; }
.predict2 .result-big { font-size: 2.6rem; font-weight: 700; line-height: 1; }
.predict2 .result-band { margin-top: 0.35rem; color: #555; font-size: 0.95rem; }
.predict2 .scored { margin-top: 0.5rem; font-size: 0.95rem; }
.predict2 .scored em { font-style: normal; color: #a32d2d; font-weight: 600; }
.predict2 table.rk { margin-top: 1.2rem; width: 100%; }
.predict2 table.rk th, .predict2 table.rk td { text-align: left; }
.predict2 .runner { display: inline-block; margin-right: 0.7rem;
                    font-size: 0.8rem; color: #444; }
.predict2 .runner em { color: #888; font-style: normal; }


/* ---- predictions ----------------------------------------------------- *
 * Scoped under .predict2, the same containment the rankings page uses. */
.predict2 { max-width: 940px; margin: 0 auto; padding: 2rem 1.5rem 4rem; color: #222; }
.predict2 h1 { font-size: 1.9rem; margin: 0 0 0.4rem; }

/* Steps reveal themselves as the one above is answered. An empty form with
   every input visible asks the reader to work out the order. */
.predict2 .step { margin: 0 0 1.8rem; padding-top: 1.4rem;
                  border-top: 1px solid #eee; }
.predict2 .step.hidden { display: none; }
.predict2 .step-head { display: flex; align-items: center; gap: 8px;
                       font-size: 0.95rem; font-weight: 700; margin-bottom: 0.8rem; }
.predict2 .step-n {
    display: inline-flex; align-items: center; justify-content: center;
    width: 22px; height: 22px; border-radius: 50%;
    background: #111; color: #fff; font-size: 0.75rem;
}

/* Two big choices, not a dropdown: there are two, they are equally weighted,
   and each needs a line of explanation that a <select> cannot carry. */
.predict2 .cards { display: flex; flex-wrap: wrap; gap: 10px; margin-bottom: 1rem; }
.predict2 .card {
    flex: 1 1 260px; text-align: left; padding: 0.7rem 0.85rem;
    border: 1px solid #ccc; border-radius: 6px; background: #fff;
    font: inherit; cursor: pointer;
}
.predict2 .card:hover { border-color: #888; }
.predict2 .card.is-on { border-color: #111; box-shadow: inset 0 0 0 1px #111; }
.predict2 .card-t { display: block; font-weight: 600; font-size: 0.92rem; }
.predict2 .card-d { display: block; color: #777; font-size: 0.8rem; margin-top: 2px; }
.predict2 .when-pane.hidden, .predict2 .who-pane.hidden { display: none; }

.predict2 .pick-wrap { position: relative; max-width: 380px; }
.predict2 .pick-wrap.wide { max-width: 520px; }
.predict2 .pick-wrap input {
    width: 100%; padding: 0.5rem 0.65rem; border: 1px solid #ccc;
    border-radius: 4px; font: inherit; font-size: 0.95rem; box-sizing: border-box;
}
.predict2 .pick-results {
    position: absolute; z-index: 40; left: 0; right: 0; max-height: 320px;
    overflow-y: auto; margin-top: 2px; border: 1px solid #ccc;
    border-radius: 5px; background: #fff; box-shadow: 0 4px 14px rgba(0,0,0,0.12);
}
.predict2 .pick-results.hidden { display: none; }
/* The year heading. A meet name matches thirty editions and the year is the
   only thing that separates them. */
.predict2 .pick-group {
    padding: 3px 8px; background: #f4f4f0; border-top: 1px solid #eee;
    font-size: 0.72rem; font-weight: 700; color: #666; letter-spacing: 0.04em;
}
.predict2 .pick-opt {
    display: block; width: 100%; padding: 0.4rem 0.65rem; border: 0;
    background: none; text-align: left; font: inherit; font-size: 0.88rem;
    cursor: pointer;
}
.predict2 .pick-opt:hover { background: #f0f0ec; }
.predict2 .pick-sub { display: block; color: #888; font-size: 0.78rem; }

/* The chosen athletes.
 *
 * ⚠ opacity: 1 IS THE LINE THAT MATTERS, AND COLOUR WAS NEVER THE PROBLEM.
 *   A global `.chip { opacity: 0.45 }` exists further up this file for the
 *   athlete page's OFF-state toggles, and it was fading these to 45% -- so
 *   darkening the text from #222 to #111 to #000 changed nothing visible,
 *   twice. A transparent black is still grey.
 */
.predict2 .chip {
    opacity: 1;
    display: inline-flex; align-items: center; gap: 6px;
    padding: 3px 6px 3px 10px; border: 1px solid #bbb; border-radius: 999px;
    background: #f1f1ec; color: #000; font-size: 0.88rem; font-weight: 600;
}
.predict2 .chip.big { font-size: 0.95rem; font-weight: 600; }
.predict2 .chip-x { border: 0; background: none; cursor: pointer; color: #444;
                    font-size: 1.05rem; line-height: 1; padding: 0 3px; }
.predict2 .chip-x:hover { color: #a32d2d; }
.predict2 .chip-note { margin-left: 8px; color: #888; font-size: 0.82rem; }

/* Step 1, answered. A filled card in the place the search box was, so the
   step still reads as step 1 rather than vanishing. */
.predict2 .meet-search.hidden, .predict2 .meet-chosen.hidden { display: none; }
.predict2 .meet-chosen {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: 2px 12px;
    align-items: center;
    max-width: 520px;
    padding: 0.7rem 0.85rem;
    border: 1px solid #ccc;
    border-left: 3px solid #111;
    border-radius: 5px;
    background: #fafaf8;
}
.predict2 .mc-main { display: flex; align-items: baseline; gap: 8px; min-width: 0; }
.predict2 .mc-name { font-size: 1.05rem; font-weight: 700;
                     overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* The year as its own token rather than part of the name -- thirty editions
   are one meet, not thirty names. */
.predict2 .mc-year {
    flex: none; padding: 1px 6px; border-radius: 3px;
    background: #eceae2; color: #555; font-size: 0.78rem; font-weight: 600;
}
.predict2 .mc-main { grid-column: 1; grid-row: 1; }
.predict2 .mc-sub  { grid-column: 1; grid-row: 2; color: #888; font-size: 0.82rem; }
/* ⚠ BOTH COORDINATES, NOT JUST THE ROW. `grid-row: 1 / span 2` alone puts the
   button in the first FREE cell, which is column 1 -- so Change rendered to
   the LEFT of the meet name and shoved it across. Auto-placement fills by
   row; spanning rows does not imply a column. */
.predict2 .mc-change {
    grid-column: 2; grid-row: 1 / span 2; align-self: center;
    border: 1px solid #ccc; border-radius: 4px; background: #fff;
    padding: 0.3rem 0.6rem; font: inherit; font-size: 0.82rem; cursor: pointer;
}
.predict2 .mc-change:hover { border-color: #888; }

/* A team already in the field: shown, with the action it offers named. */
.predict2 .pick-opt.is-in .pick-name { color: #888; }
.predict2 .pick-name { }
/* The action sits right-aligned so the eye can run down a column of Add /
   Remove rather than hunting for it after each name. */
.predict2 .pick-act {
    float: right; margin-left: 10px;
    font-size: 0.75rem; font-weight: 600; color: #2e53e6;
}
.predict2 .pick-opt.is-in .pick-act { color: #a32d2d; }
.predict2 .chosen.hidden, .predict2 .chosen-meet.hidden { display: none; }
.predict2 .chosen, .predict2 .chosen-meet { margin-top: 8px; }

.predict2 .hint { color: #888; font-size: 0.82rem; margin: 0.45rem 0 0; }
.predict2 label { display: block; font-size: 0.8rem; font-weight: 600;
                  margin-bottom: 0.25rem; }
.predict2 input[type=date] { padding: 0.4rem 0.55rem; border: 1px solid #ccc;
                             border-radius: 4px; font: inherit; }
.predict2 .check { display: flex; align-items: center; gap: 7px;
                   font-weight: 400; font-size: 0.85rem; margin: 0; }
.predict2 .check input { width: 14px; height: 14px; margin: 0; }

/* The field. A grid of team cards, each editable in place -- a roster is a
   thing you scan and adjust, not a list you read top to bottom. */
.predict2 .field-head { display: flex; flex-wrap: wrap; gap: 1rem;
                        align-items: center; justify-content: space-between;
                        margin-bottom: 0.8rem; font-size: 0.88rem; }
.predict2 .field-grid.hidden { display: none; }
.predict2 .field-grid { display: grid; gap: 8px; align-items: start;
                        grid-template-columns: repeat(auto-fill, minmax(215px, 1fr)); }
/* Breathing room before "Add a team" -- it is a different action from editing
   the field above it, and butting it against the grid read as part of it. */
.predict2 .field-grid + .pick-wrap { margin-top: 1.4rem; }
.predict2 .team-card { border: 1px solid #e2e2dc; border-radius: 6px;
                       background: #fff; }

/* The summary IS the closed card, so the padding lives here rather than on
   the container -- otherwise a collapsed team keeps the body's spacing and
   the grid rows come out uneven. */
.predict2 .team-name {
    display: flex; align-items: center; gap: 8px;
    padding: 0.5rem 0.6rem;
    font-weight: 600; font-size: 0.87rem;
    cursor: pointer; list-style: none;
}
/* The default disclosure triangle is replaced by our own, so it sits after
   the name instead of before it and does not shove the label around. */
.predict2 .team-name::-webkit-details-marker { display: none; }
.predict2 .team-name::before {
    content: "\25b8"; color: #bbb; font-size: 0.7rem;
    transition: transform 0.12s ease;
}
.predict2 .team-card[open] > .team-name::before { transform: rotate(90deg); }
.predict2 .t-label { flex: 1; min-width: 0; overflow: hidden;
                     text-overflow: ellipsis; white-space: nowrap; }
.predict2 .team-n { color: #999; font-weight: 400; }
.predict2 .team-x {
    flex: none; border: 0; background: none; color: #ccc; cursor: pointer;
    font-size: 1rem; line-height: 1; padding: 0 2px;
}
.predict2 .team-x:hover { color: #a32d2d; }

/* Show: nothing / teams / rosters. A segmented control rather than two
   toggles -- the states are mutually exclusive and the current one should be
   readable without pressing anything. */
.predict2 .viewsel {
    display: inline-flex; align-items: center; gap: 0;
    margin-left: 10px; font-size: 0.78rem; color: #888;
}
.predict2 .vbtn {
    border: 1px solid #ddd; background: #fff; margin-left: -1px;
    padding: 1px 7px; font: inherit; font-size: 0.78rem; color: #555;
    cursor: pointer;
}
.predict2 .vbtn:first-of-type { margin-left: 8px; border-radius: 3px 0 0 3px; }
.predict2 .vbtn:last-of-type  { border-radius: 0 3px 3px 0; }
.predict2 .vbtn.is-on { background: #111; border-color: #111; color: #fff; }

/* A button that reads as a link: quiet actions beside the field summary. */
.predict2 .linkish {
    border: 0; background: none; padding: 0; margin-left: 8px;
    font: inherit; font-size: 0.82rem; color: #2e53e6;
    text-decoration: underline; cursor: pointer;
}
.predict2 .linkish.undo { color: #a32d2d; }
.predict2 .team-body { padding: 0 0.6rem 0.55rem; }
/* Names are the content of this card, not chrome -- they were 0.82rem in the
   inherited grey and hard to read against the rating beside them. */
.predict2 .runner-row { display: flex; align-items: center; gap: 6px;
                        font-size: 0.86rem; padding: 2px 0; color: #222; }
.predict2 .r-name { flex: 1; min-width: 0; overflow: hidden;
                    text-overflow: ellipsis; white-space: nowrap; }
.predict2 .r-rating { color: #666; font-size: 0.8rem; font-variant-numeric: tabular-nums; }
.predict2 .r-x { border: 0; background: none; color: #bbb; cursor: pointer;
                 font-size: 0.95rem; line-height: 1; padding: 0 2px; }
.predict2 .r-x:hover { color: #a32d2d; }
.predict2 .runner-row.is-out { color: #aaa; }
.predict2 .runner-row.is-back { color: #222; }
.predict2 .r-add { border: 1px solid #ccc; background: #fff; border-radius: 3px;
                   font: inherit; font-size: 0.72rem; cursor: pointer;
                   padding: 0 5px; }
/* "Add from squad", and the list it opens. Quiet until used -- most cards
   never need it, and thirty of these should not read as thirty buttons. */
.predict2 .squad-btn {
    display: block; width: 100%; margin-top: 6px;
    border: 1px dashed #ddd; border-radius: 3px; background: #fff;
    padding: 2px 6px; font: inherit; font-size: 0.74rem; color: #666;
    cursor: pointer; text-align: left;
}
.predict2 .squad-btn:hover { border-color: #999; color: #222; }
.predict2 .squad-list { margin-top: 4px; max-height: 190px; overflow-y: auto; }
.predict2 .squad-list.hidden { display: none; }
.predict2 .squad-loading { color: #999; font-size: 0.76rem; padding: 3px 0; }

.predict2 .dropped { margin-top: 5px; }
.predict2 .dropped summary { color: #999; font-size: 0.76rem; cursor: pointer; }

.predict2 .actions { display: flex; align-items: center; gap: 12px;
                     margin-top: 1.4rem; }
.predict2 .actions.hidden { display: none; }
.predict-status { display: none; font-size: 0.88rem; }
.predict-status.show { display: inline; }
.predict-status.error { color: #a32d2d; }

/* The answer. Big, with the band under it -- a prediction without a range
   claims a precision the data does not have. */
.predict2 .result { margin-top: 1.6rem; }
.predict2 .result-big { font-size: 2.6rem; font-weight: 700; line-height: 1; }
.predict2 .result-band { margin-top: 0.35rem; color: #555; font-size: 0.95rem; }
.predict2 .scored { margin-top: 0.5rem; font-size: 0.95rem; }
.predict2 .scored em { font-style: normal; color: #a32d2d; font-weight: 600; }
.predict2 table.rk { margin-top: 1.2rem; width: 100%; }
.predict2 table.rk th, .predict2 table.rk td { text-align: left; }
.predict2 .runner { display: inline-block; margin-right: 0.7rem;
                    font-size: 0.8rem; color: #444; }
.predict2 .runner em { color: #888; font-style: normal; }


/* ---- meet page ------------------------------------------------------- *
 * Scoped under .meet-page, the same containment the other pages use. */
.meet-page { max-width: 1100px; margin: 0 auto; padding: 1.5rem 1.5rem 4rem; }
.meet-page h1 { font-size: 1.7rem; margin: 0 0 0.3rem; }
.meet-page h2 { font-size: 1.15rem; margin: 2rem 0 0.5rem; }
.meet-page h3 { font-size: 1rem; margin: 1.6rem 0 0.5rem;
                display: flex; align-items: baseline; gap: 10px; }
.meet-page .meta { color: #777; font-size: 0.9rem; margin: 0 0 1rem; }
.meet-page .hint { color: #888; font-size: 0.84rem; margin: 0.3rem 0 0.8rem; }
.meet-page .muted { color: #999; }

/* The compiled table is long -- a full invitational is several hundred rows --
   so it is tighter than the division summary above it. */
.meet-page table.rk { width: 100%; }
.meet-page table.rk th, .meet-page table.rk td { text-align: left; }
.meet-page table.compact th, .meet-page table.compact td {
    padding: 2px 8px; font-size: 0.86rem;
}
.meet-page .rank { color: #888; width: 3rem; }

.meet-page .cg-note { color: #999; font-size: 0.82rem; font-weight: 400; }
.meet-page .crumb { font-size: 0.85rem; margin: 0 0 0.3rem; }
/* The "this never happened" notice. Bordered rather than grey italics: someone
   arriving from a link has no other way to know these runners were not on the
   line together, so it has to survive being skimmed. */
.meet-page .synthetic {
    margin: 0.8rem 0 1.4rem; padding: 0.6rem 0.8rem;
    border-left: 3px solid #b8860b; border-radius: 3px;
    background: #fdf9e8; color: #5a4a1a; font-size: 0.86rem; line-height: 1.5;
}
.meet-page .compiled-group { margin-bottom: 2rem; }

/* Scores fold away: the results are what most visits want, and a score table
   above them pushes the finishers below the fold. */
.meet-page .cg-scores { margin-bottom: 0.6rem; }
.meet-page .cg-scores summary { cursor: pointer; font-size: 0.9rem;
                                font-weight: 600; }
.meet-page .scorers { color: #666; font-size: 0.8rem; }
.meet-page .mini-team { display: inline-block; margin-right: 8px;
                        font-size: 0.82rem; }
.meet-page .mini-team em { color: #888; font-style: normal; }
/* Runners 6 and 7 do not score but do displace -- shown, and marked as not
   counting toward the total. */
.meet-page .mini-team.displacer { color: #bbb; }




/* ---- tables: everything left aligned --------------------------------- *
 * ⚠ GLOBAL, AND DELIBERATELY SO. Numbers default to left here too. Right
 *   alignment is the typographic convention for figures, but this site mixes
 *   times, ratings, places and names in the same rows, and a table where some
 *   columns hug one edge and some the other reads as two tables. One rule,
 *   everywhere, so no page has to remember. */
table th, table td { text-align: left; }

/* Runners 6 and 7 score nothing but displace everyone behind them -- shown,
   and marked as not counting. */
.displacer, .displacer a { color: #aaa; }

/* The "this race did not happen" notice on a compiled page. Bordered rather
   than italic grey: someone arriving from a shared link has no other way to
   know these runners were not on the line together, so it has to survive
   being skimmed. */
.synthetic {
    margin: 0.8rem 0 1.2rem;
    padding: 0.6rem 0.8rem;
    border-left: 3px solid #b8860b;
    border-radius: 3px;
    background: #fdf9e8;
    color: #5a4a1a;
    font-size: 0.9rem;
    line-height: 1.5;
    max-width: 70ch;
}


/* ---- school page: best athletes chart -------------------------------- *
 * CSS bars rather than a charting library: twelve horizontal bars need one
 * div each and no JavaScript, and the athlete pages already carry the only
 * charting code on the site. */
.school-chart { margin: 0 0 1.5rem; max-width: 720px; }
.sc-row {
    display: grid;
    grid-template-columns: 11rem 1fr 3rem 5rem;
    align-items: center;
    gap: 8px;
    padding: 2px 0;
    font-size: 0.88rem;
}
.sc-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.sc-bar { background: #eeeeea; border-radius: 2px; height: 13px; }
.sc-fill {
    display: block; height: 100%; border-radius: 2px; background: #2e53e6;
    /* A zero-width bar is invisible and reads as missing data, so the
       smallest one still gets a sliver. */
    min-width: 3px;
}
.sc-val { font-variant-numeric: tabular-nums; font-weight: 600; }
.sc-yrs { color: #999; font-size: 0.8rem; }

@media (max-width: 640px) {
    /* The year span is the first thing to go: it is context, not the point. */
    .sc-row { grid-template-columns: 8rem 1fr 3rem; }
    .sc-yrs { display: none; }
}


/* ---- school page ----------------------------------------------------- */

/* Sport picker. A joined pair with one filled: two links with a separator
   read as unrelated items, and bold alone does not say "this is selected". */
.seg { display: inline-flex; margin: 0.2rem 0 0.8rem; }
.seg-btn {
    padding: 0.35rem 0.8rem;
    border: 1px solid #ccc;
    background: #fff;
    color: #333;
    font-size: 0.88rem;
    text-decoration: none;
}
.seg-btn + .seg-btn { border-left: 0; }
.seg-btn:first-child { border-radius: 4px 0 0 4px; }
.seg-btn:last-child  { border-radius: 0 4px 4px 0; }
.seg-btn.is-on { background: #111; border-color: #111; color: #fff; }

/* Seasons. Wraps rather than scrolls: twenty years is four short lines and a
   horizontal scroller would hide half of them. */
.yearbar { display: flex; flex-wrap: wrap; gap: 4px; margin: 0 0 1.2rem; }
.yb {
    padding: 1px 7px;
    border: 1px solid #ddd;
    border-radius: 3px;
    background: #fff;
    color: #444;
    font-size: 0.8rem;
    text-decoration: none;
}
.yb:hover { border-color: #888; }
.yb.is-on { background: #111; border-color: #111; color: #fff; }

/* Staged reveal. The control is the table's last row, so it reads as part of
   the list it extends rather than as a second table underneath. */
tr.row-hidden { display: none; }
tr.more-row td { padding: 0; }
.more-btn {
    display: block;
    width: 100%;
    padding: 0.45rem;
    border: 0;
    background: #fafaf8;
    font: inherit;
    font-size: 0.85rem;
    color: #2e53e6;
    cursor: pointer;
    text-align: left;
}
.more-btn:hover { background: #f0f0ec; }
.more-left { color: #999; }
.h2-note { color: #999; font-weight: 400; font-size: 0.8em; }

/* ======================================================================= *
 *  SITE FOUNDATION
 *
 *  ★ THIS SECTION SUPERSEDES EARLIER RULES ON PURPOSE. It is last in the
 *    file (bar the phone block) so it wins on source order without needing
 *    !important or inflated selectors. Where it contradicts something
 *    above -- the full cell grid on tables, most obviously -- the rule
 *    above is the one being replaced, not a rule being fought.
 * ======================================================================= */

/* ---- tokens ----------------------------------------------------------- *
 * These already existed, declared on .rankings-page so they could not
 * collide with anything. Same values, at :root, because every page wants
 * them and a second palette per page is how two greys appear. */
:root {
  --ink:    #111418;
  --muted:  #6b7280;
  --line:   #e5e7eb;
  --line-2: #cbd0d6;
  --accent: #14477d;   /* the same navy as --link; see --rk-accent */
  --zebra:  #fafbfc;
  --hover:  #f3f6fa;
  --gold:   #a97a12;
  /* ! LINKS NEED A COLOUR OF THEIR OWN. Near-black links with a hairline
     underline were, correctly, reported as impossible to spot -- in a table
     they were indistinguishable from the text beside them. This is a deep
     navy: unmistakably a link, without the 1996 blue that made the board
     shout. */
  --link:   #14477d;
}

/* ---- the typeface ------------------------------------------------------ *
 * ⚠ THERE WAS NONE. This file declared font-family exactly once, on form
 *   controls, so every page rendered in the browser's default serif at
 *   default sizes -- which is most of why the site read as an unstyled
 *   document rather than a product.
 *
 * A system stack: nothing to fetch, no CDN to depend on, and it is the
 * face the reader's own OS already renders best. tabular figures are the
 * point for a results site -- without them a column of times jitters,
 * because 1 is narrower than 0 in most proportional faces. */
body, input, select, button, textarea {
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
               "Helvetica Neue", Arial, sans-serif;
}
body {
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
}
h1, h2, h3, h4, h5 { letter-spacing: -0.015em; color: var(--ink); }
h1 { font-size: 2rem;    font-weight: 700; }
h2 { font-size: 1.35rem; font-weight: 650; margin-top: 2rem; }
h3 { font-size: 1.05rem; font-weight: 650; }

/* ---- links ------------------------------------------------------------- *
 * ⚠ THERE WAS NO `a` RULE EITHER, so every link on the site outside .page
 *   was browser-default blue and underlined. On the rankings board that was
 *   three of nine columns shouting, and the rating -- the number the site
 *   exists to produce -- could not compete with it.
 *
 * ! PROSE KEEPS ITS UNDERLINE. Dark text plus no underline is an invisible
 *   link in a paragraph, so the underline only comes off where the ROW is
 *   the link and the context already says so: tables, the board grid, nav,
 *   the sidebar panels. Hover puts it back everywhere. */
a { color: var(--link);
    text-underline-offset: 2px;
    text-decoration-thickness: 1px;
    text-decoration-color: currentColor; }
a:hover { text-decoration-thickness: 2px; }

table a, .board-grid a, .topnav a, .brand,
.bests-panel a, .pr-panel a, .season-jump a, .sport-toggle a,
.pool-more, .seg-btn, .yb, .meet-link, .tabs a, .find-opt {
  text-decoration: none;
}
table a:hover, .board-grid a:hover, .topnav a:hover,
.bests-panel a:hover, .pr-panel a:hover, .season-jump a:hover {
  text-decoration: underline;
}
.topnav a { color: #374151; }
.topnav a:hover { color: var(--ink); }

/* ---- tables ------------------------------------------------------------ *
 * The outer frame stays, so a table reads as one object; the internal
 * verticals go, because a full cell grid makes a results list look like a
 * spreadsheet. Rules plus a zebra do the separating.
 *
 * ! SQUARE CORNERS, DELIBERATELY. A rounded results table reads as a
 *   dashboard widget rather than a results table -- the look every
 *   generated site arrives with. Every reference this site actually sits
 *   beside (TFRRS, Athletic.net, MileSplit) sets dense square tables, and
 *   that is the idiom of the thing being displayed. No radius also means
 *   border-collapse can stay `collapse`, which is the simpler mechanism. */
table {
  border-collapse: collapse;
  width: 100%;
  border: 1px solid var(--line-2);
}
th, td { border: 0; border-bottom: 1px solid var(--line); }
thead th {
  background: #f7f8fa;
  border-bottom: 1px solid var(--line-2);
  text-transform: uppercase;
  font-size: 0.7rem;
  letter-spacing: 0.05em;
  color: var(--muted);
  font-weight: 600;
  padding: 0.5rem 0.7rem;
}
tbody tr:last-child td { border-bottom: 0; }
tbody tr:nth-child(even) { background: var(--zebra); }
tbody tr:hover { background: var(--hover); }

/* In a table the colour carries it and the underline is noise -- but the
   colour has to be doing real work, which is what --link is for. The
   RATING stays ink-black: it is a number the row is ranked by, and weight
   says that better than colour. */
table a, .board-grid a { color: var(--link); }
table td.rating a, .board-grid .c-rating a { color: var(--ink); }

/* ---- focus ------------------------------------------------------------- *
 * ! REQUIRED BY THE LINK CHANGE ABOVE. The browser default focus ring is
 *   drawn against the link's own colour; now that links are near-black on
 *   white it is close to invisible, and keyboard users lose their place. */
a:focus-visible, button:focus-visible,
input:focus-visible, select:focus-visible, [tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 3px;
}


/* ======================================================================= *
 *  PER-PAGE POLISH  (rests on SITE FOUNDATION above)
 * ======================================================================= */

/* ---- result pages get a measure ---------------------------------------- *
 * race / meet / course / school / venue / compiled had no container at all,
 * so at 1280 the h1 sat 8px from the window edge and the results table
 * stretched the full width -- the eye travelling the whole monitor from a
 * runner's name to their time. .page-wide is the wrapper those templates
 * now use; .page (68ch) stays the prose measure for About. */
.page-wide {
    max-width: 1080px;
    margin: 0 auto;
    padding: 1.6rem 1.5rem 3rem;
}
.page-wide > .meta:first-of-type { font-size: 0.95rem; color: #374151; }
.page-wide > .meta a { font-weight: 600; }

/* "View full meet" was a bare link on its own line; it is an action. */
.meet-link {
    display: inline-block;
    padding: 0.45rem 0.8rem;
    border: 1px solid var(--line-2);
    border-radius: 3px;
    font-size: 0.875rem;
    font-weight: 600;
    background: #fff;
}
.meet-link:hover { background: var(--hover); }

/* ---- you-are-here ------------------------------------------------------ *
 * The topbar gave no indication which page you were on. _topbar.html sets
 * the class from request.path, so no route had to learn about it. */
.topnav a.is-here {
    color: var(--ink);
    font-weight: 650;
    box-shadow: inset 0 -2px 0 var(--accent);
}

/* ---- rankings ---------------------------------------------------------- */
/* The native selects and the custom combo buttons disagreed on border,
   height and radius, so the filter row read as two toolbars. */
.rankings-page .field select,
.rankings-page .field input {
    border-color: #ccc;
    padding: 0.42rem 0.55rem;
    border-radius: 3px;
    font-size: 0.9rem;
    /* ⚠ NO EXPLICIT HEIGHT. A fixed height PLUS vertical padding leaves a
       native <select> laying its text out against a content box smaller
       than the line it is drawing, and the label rides high in the box --
       visibly off-centre next to the flex-centred combo buttons. Padding
       alone sizes both to the same height and lets the control centre its
       own text. */
    min-width: 6.25rem;
}
/* ! ONE LINE-HEIGHT ACROSS ALL THREE CONTROL KINDS. Without it a <select>,
   a text input and the flex combo button each derive their own content
   height from `normal` -- the button's caret glyph makes it the tallest --
   and the row ends up 3-4px out of step. Same padding plus the same line
   box is the only way they come out identical without a fixed height. */
.rankings-page .field select,
.rankings-page .field input,
.rankings-page .combo-btn { line-height: 1.25; }
.rankings-page .field input.narrow { min-width: 4rem; }
.rankings-page .field label { font-size: 0.65rem; }
/* ⚠ THE SANS STACK IS WIDER THAN THE SERIF IT REPLACED, and the row was
   sized for the serif -- Apply wrapped onto a second line. Trimming the
   controls puts the whole bar back on one row at 1280. */
.rankings-page .combo-btn { width: 132px; border-radius: 3px; }

/* The rating is what the board ranks, so it leads the row. */
.rankings-page table.rk td.rating { font-weight: 700; font-size: 1.02rem; }
.rankings-page table.rk tbody tr:nth-child(-n+3) td.rank {
    color: var(--gold);
    font-weight: 700;
}

/* ⚠ THIS NOTICE IS PERMANENT ON A NATIONAL BOARD, which is the default, so
   it is the first thing above the table on essentially every visit -- the
   fate the international caveat was deliberately spared. A rule and small
   text says the same thing without being the loudest object on the page. */
.rankings-page .notice {
    background: transparent;
    border: 0;
    border-left: 3px solid #d9b168;
    border-radius: 0;
    color: #57534e;
    font-size: 0.8125rem;
    padding: 0.4rem 0 0.4rem 0.7rem;
}

/* ---- home -------------------------------------------------------------- */
/* ! THE MURAL'S OWN aspect-ratio: 16 / 11 STANDS, and is not to be overridden
   here. A 16/7 override was added to get the boards above the fold; it
   squashed the band the outer rows were sized for, which is the thing that
   block's comment exists to protect. Above-the-fold is not worth cropping
   the photographs for. */

/* ★ TWO KINDS OF CONTROL THAT LOOKED IDENTICAL. The sport and scope groups
   are PICK ONE; the pool chips are TOGGLE OFF -- and an "on" chip was
   drawn exactly like an unselected mode button, so nothing said which was
   which.
   ! SQUARE, NOT PILLS, AND NO TICK GLYPHS. The pill-with-a-checkmark is
     the generic component every template ships; a filter that is plainly
     on or plainly greyed says the same thing in this site's own idiom.
     The difference is now carried by weight and ink, not by decoration. */
.pool-chips .chip {
    opacity: 1;
    background: #fff;
    color: #9aa1a9;
    border: 1px solid var(--line);
    border-radius: 3px;
    text-decoration: line-through;
    text-decoration-color: #c9ced4;
}
.pool-chips .chip.is-on {
    background: #fff;
    border-color: #9aa1a9;
    color: var(--ink);
    font-weight: 600;
    text-decoration: none;
}

/* The board grid is divs, not a table, so every part of the table
   treatment has to be spelled out -- and half-doing it is what made this
   board hard to read: the cells lost their verticals while the header
   kept them, the header lost its background, and nothing striped, so ten
   rows of near-black text ran together.
   ⚠ ZEBRA ON A CSS GRID IS ARITHMETIC, NOT :nth-child(even). There are no
     row elements -- five header divs, then five cells per row -- so a row
     is a span of five children and the stripe is a modulo over ten. */
.board-grid .cell, .board-grid .head { border-left: 0; border-right: 0; }
.board-grid .head {
    background: #f2f4f6;
    border-bottom: 1px solid var(--line-2);
    text-transform: uppercase;
    font-size: 0.68rem;
    letter-spacing: 0.05em;
    color: #4b5563;
}
.board-grid .cell {
    padding: 0.3rem 0.7rem;          /* denser: ten rows should read as a list */
    border-bottom: 1px solid var(--line);
}
.board-grid .cell:nth-child(10n+6),
.board-grid .cell:nth-child(10n+7),
.board-grid .cell:nth-child(10n+8),
.board-grid .cell:nth-child(10n+9),
.board-grid .cell:nth-child(10n+10) { background: var(--zebra); }
.board-grid .c-rating { font-weight: 700; font-variant-numeric: tabular-nums; }
.board-grid .cell:nth-child(5n+1) { color: var(--muted); font-variant-numeric: tabular-nums; }
.board-scope-label { font-size: 1.05rem; }

/* ⚠ THE WIDER FONT CLIPPED SCHOOL NAMES ("Springdale Har-..."), so school
   took the slack, the rank column gave some back and a three-digit rating
   stopped needing 5rem.
   ! EVERY RATIO HERE WAS MEASURED, and the margins are ONE PIXEL wide.
     "Fort Smith Northside" overflows by exactly 1px at school 1.1fr and the
     browser draws an ellipsis for it; the athlete names start doing the
     same the moment school passes 1.15fr. 0.88 / 1.15 / 1.17 is the only
     swept combination where neither clips at all.
   ⚠ SO MEASURE STRICTLY IF YOU CHANGE THESE. An earlier pass allowed a 1px
     tolerance, reported "nothing clipped", and shipped a visibly truncated
     school column -- scrollWidth > clientWidth by ANY amount is a clip.
   ! THE RESULT COLUMN IS THE ONE THAT MAY RUN OUT, by design: it holds a
     time and a meet name, and about a quarter of meet names fit nowhere
     ("Chile Pepper Cross Country Festival"). That is why the time is
     written FIRST -- the fixed-width half always survives -- and why the
     cell carries the full string in a title attribute. */
:root {
    --cols: 2.5rem minmax(0, 0.88fr) minmax(0, 1.15fr) 4.25rem minmax(0, 1.17fr);
}

/* ---- athlete ----------------------------------------------------------- */
/* The XC/TF switcher was two bare links run together, which read as one
   string. school.html already had a segmented control; this is it. */
.sport-toggle {
    display: inline-flex;
    border: 1px solid var(--line-2);
    border-radius: 3px;
    overflow: hidden;
    margin: 0.6rem 0 1.2rem;
}
.sport-toggle a {
    padding: 0.4rem 0.9rem;
    font-size: 0.875rem;
    font-weight: 600;
    color: #374151;
    border-right: 1px solid var(--line-2);
}
.sport-toggle a:last-child { border-right: 0; }
.sport-toggle a:hover { background: var(--hover); text-decoration: none; }

/* ---- pool-view toggle --------------------------------------------------- *
 * Same segmented shape as .sport-toggle so the two controls read as
 * siblings; buttons rather than links because switching a scale is state,
 * not navigation. The active segment is filled -- an outline alone did not
 * say which view the page is showing.
 *
 * .toggle-row puts the sport nav and this on ONE line: nav left, toggle
 * flush right, centred on the same axis so the two boxes align (the nav's
 * own bottom margin moves up onto the row). Wraps on narrow screens
 * rather than crushing either control. */
.toggle-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 0.6rem 1rem;
    margin: 0.6rem 0 1.2rem;
}
.toggle-row .sport-toggle { margin: 0; }
.scale-toggle {
    display: inline-flex;
    align-items: center;
    gap: 0;
    margin-left: auto;
}
.scale-lab {
    font-size: 0.75rem;
    font-weight: 600;
    color: #6b7280;
    text-transform: uppercase;
    letter-spacing: 0.04em;
    margin-right: 0.55rem;
}
.scale-toggle button {
    padding: 0.4rem 0.9rem;
    font-size: 0.875rem;
    font-weight: 600;
    color: #374151;
    background: #fff;
    border: 1px solid var(--line-2);
    border-right: 0;
    cursor: pointer;
}
.scale-toggle button:first-of-type { border-radius: 3px 0 0 3px; }
.scale-toggle button:last-of-type {
    border-right: 1px solid var(--line-2);
    border-radius: 0 3px 3px 0;
}
.scale-toggle button:hover { background: var(--hover); }
.scale-toggle button.is-active {
    background: #374151;
    border-color: #374151;
    color: #fff;
}
/* ---- athlete season tables: keep them INSIDE the column ---------------- *
 * The real column sizing lives in athlete-charts.css (table-layout: fixed
 * with per-column widths -- and a COLUMN-ORDER comment that must track
 * athlete.html). These are only the backstops:
 *   1. the flags cell WRAPS -- six pills become two rows, not one strip.
 *
 * ⚠ NO overflow-x:auto HERE, though it looks like the obvious fence. The
 *   flag tooltips are hidden 250px absolute boxes anchored to the pills in
 *   the LAST column; they count toward scrollWidth even while invisible,
 *   so a scroll container here shows a permanent phantom scrollbar (and
 *   clips the tooltips when they open). The fixed table layout is what
 *   guarantees the table fits its column. */
.season td.flags { white-space: normal; }
.season td.flags .flag { margin-bottom: 2px; }

/* ---- course records ----------------------------------------------------- *
 * Boys and Girls side by side; the section heading carries the selected
 * distance, its own fitted difficulty and its result count as a quiet
 * suffix, so the numbers above the table say which course-at-a-distance
 * they describe.
 *
 * ★ UNIFORM ROWS, BY OWNER'S CALL. table-layout:fixed + per-column widths
 *   keep every column the same width whatever the content, and nowrap +
 *   ellipsis keeps every row ONE LINE tall -- a long school name truncates
 *   (full text in title=) instead of wrapping the row taller than its
 *   neighbours. Same fixed-layout discipline the athlete season tables
 *   learned, same box-sizing so the percentages mean what they say. */
.rec-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 1.2rem; }
.rec-pair table, .dist-table, .meets-table { table-layout: fixed; }
.rec-pair th, .rec-pair td,
.dist-table th, .dist-table td,
.meets-table th, .meets-table td {
    box-sizing: border-box;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
}
/* The paired tables live in a half-width column, so the sitewide 0.9rem
   side padding would eat the narrow number columns whole. Tighter here. */
.rec-pair th, .rec-pair td { padding: 0.4rem 0.5rem; }
/* Number-ish columns get em widths sized to their widest real value
   (padding included); the two name columns are left unset, so fixed
   layout splits whatever remains between them and THEY do the
   ellipsizing, never the numbers. */
/* rem, not em: th widths in em would compute against the 0.7rem header
   font and starve the column the DATA has to fit in. Each width covers
   the widest of its header and its widest real value, padding included. */
/* individual tables: #, Athlete, two number cols, Dist/Grade, School, Date.
   Columns 3 and 4 are rating and time IN EITHER ORDER between the course
   views, so both get the wider of the two. */
.rec-ind th:nth-child(1) { width: 2.4rem; }
.rec-ind th:nth-child(3) { width: 4.7rem; }
.rec-ind th:nth-child(4) { width: 4.7rem; }
.rec-ind th:nth-child(5) { width: 4.4rem; }
.rec-ind th:nth-child(7) { width: 6.5rem; }
/* team tables: #, School, two number cols, Meet, Date */
.rec-team th:nth-child(1) { width: 2.4rem; }
.rec-team th:nth-child(3) { width: 6.5rem; }
.rec-team th:nth-child(4) { width: 4.9rem; }
.rec-team th:nth-child(6) { width: 6.5rem; }
.dist-table th:nth-child(1) { width: 16%; }
.dist-table th:nth-child(2) { width: 16%; }
.dist-table th:nth-child(3) { width: 24%; }
.dist-table th:nth-child(4) { width: 22%; }
.dist-table th:nth-child(5) { width: 22%; }
.meets-table th:nth-child(1) { width: 50%; }
.meets-table th:nth-child(2) { width: 16%; }
.meets-table th:nth-child(3) { width: 18%; }
.meets-table th:nth-child(4) { width: 16%; }
/* the jump to the full board, right-aligned under a table */
.view-all { margin: 0.4rem 0 0; font-size: 0.85rem; text-align: right; }

/* ---- head to head (/compare) ------------------------------------------ *
 * Two fixed athlete colors used consistently in every section: A is the
 * site navy, B an amber that reads apart from it at small sizes. */
.h2h-pick { display: flex; gap: 0.6rem; align-items: flex-end;
            flex-wrap: wrap; margin-bottom: 1rem; }
.h2h-pick .field label { display: block; font-size: 0.7rem;
            text-transform: uppercase; letter-spacing: 0.05em;
            color: var(--muted, #6b7280); margin-bottom: 0.25rem; }
.h2h-search { position: relative; }
.h2h-search input { padding: 0.42rem 0.55rem; border: 1px solid #ccc;
            border-radius: 4px; font: inherit; width: 240px; }
.h2h-sug { position: absolute; z-index: 40; top: calc(100% + 2px); left: 0;
           min-width: 280px; background: #fff; border: 1px solid #bbb;
           border-radius: 6px; box-shadow: 0 6px 20px rgba(0,0,0,0.16); }
.h2h-sug.hidden { display: none; }
.h2h-sug-row { padding: 0.4rem 0.6rem; cursor: pointer; font-size: 0.9rem;
               white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.h2h-sug-row:hover { background: #f0f3f7; }
/* ! .more-btn is a full-width table strip; the Compare button borrows its
     font and colour but has to stand alone. */
.h2h-go { display: inline-block; width: auto; padding: 0.42rem 1.1rem;
          border: 1px solid #14477d; border-radius: 4px;
          background: #14477d; color: #fff; }
.h2h-go:hover { background: #103a68; }
.h2h-head { display: grid; grid-template-columns: 1fr auto 1fr; gap: 1rem;
            align-items: center; margin: 1.2rem 0; }
.h2h-card { border: 1px solid var(--line-2, #e2e4e8); border-radius: 8px;
            padding: 0.9rem 1.1rem; }
.h2h-card.a { border-top: 3px solid #14477d; }
.h2h-card.b { border-top: 3px solid #b45309; }
.h2h-card .nm { font-size: 1.15rem; font-weight: 700; }
.h2h-card .nm a { color: inherit; }
.h2h-card .sub { color: var(--muted, #6b7280); font-size: 0.85rem;
                 margin-top: 0.15rem; }
.h2h-card .big { font-size: 1.6rem; font-weight: 700; margin-top: 0.4rem; }
.h2h-card .big .lbl { font-size: 0.7rem; font-weight: 600;
            color: var(--muted, #6b7280); text-transform: uppercase;
            letter-spacing: 0.05em; display: block; }
.h2h-rec { text-align: center; }
.h2h-rec .wl { font-size: 2.4rem; font-weight: 800; letter-spacing: -0.02em; }
.h2h-rec .wl .a { color: #14477d; }
.h2h-rec .wl .b { color: #b45309; }
.h2h-rec .note { color: var(--muted, #6b7280); font-size: 0.8rem; }
.h2h-dot { display: inline-block; width: 0.6em; height: 0.6em;
           border-radius: 50%; margin-right: 0.35em; }
.dot-a { background: #14477d; }
.dot-b { background: #b45309; }
.h2h-meet td.win, .h2h-seasons td.win, .h2h-bests td.win { font-weight: 700; }
td.margin-a { color: #14477d; font-weight: 600; }
td.margin-b { color: #b45309; font-weight: 600; }
.h2h-chart { border: 1px solid var(--line-2, #e2e4e8); border-radius: 8px;
             padding: 0.8rem; margin: 0.6rem 0 1.4rem; }
.h2h-legend { font-size: 0.8rem; color: var(--muted, #6b7280);
              margin-bottom: 0.4rem; }
@media (max-width: 800px) { .h2h-head { grid-template-columns: 1fr; } }
.rec-gender { margin: 0 0 0.35rem; font-size: 0.8rem;
              text-transform: uppercase; letter-spacing: 0.05em;
              color: #6b7280; }
.dist-note { font-size: 0.8rem; color: #6b7280; font-weight: 500;
             margin-left: 0.6rem; letter-spacing: 0; }
.dist-note-quiet { color: #9ca3af; }
@media (max-width: 900px) { .rec-pair { grid-template-columns: 1fr; } }

/* ---- the season rank line (athlete header) ----------------------------- *
 * One quiet text line under the stat strip: scopes broadest to narrowest,
 * live ranks in full ink and bold, WIP scopes muted until their data
 * exists. Links drop the underline (the bold number IS the affordance)
 * and get it back on hover. */
.rank-line { margin: 0.6rem 0 0.2rem; font-size: 0.95rem;
             color: var(--ink); }
.rank-line .rl-lab {
    display: inline-block; min-width: 3.6rem; margin-right: 0.5rem;
    font-size: 0.68rem; font-weight: 600; text-transform: uppercase;
    letter-spacing: 0.05em; color: #6b7280; }
.rank-line .sep { color: #d1d5db; margin: 0 0.35rem; }
.rank-line .rk a { color: var(--ink); font-weight: 700;
                   font-variant-numeric: tabular-nums;
                   text-decoration: none; }
.rank-line .rk a:hover { text-decoration: underline; }
.rank-line .wip { color: #c2c7cf; }
/* the "?" at the end: same circled-hint dress as the scale toggle's, but
   its tooltip keeps the default rightward opening -- this one sits at the
   LEFT edge of the page. */
.rank-note {
    --tip: #374151;
    background: none; color: #6b7280;
    border: 1px solid var(--line-2); border-radius: 50%;
    width: 1.05rem; height: 1.05rem; line-height: 0.95rem;
    text-align: center; padding: 0; margin-left: 0.5rem;
    font-size: 10px; display: inline-block;
    /* middle, not a baseline nudge: the ring's perceived height depends on
       the viewer's font metrics (Segoe on Windows sat it LOW where the
       build's fallback font sat it high), and middle is the one value that
       centres the box against the text everywhere. */
    vertical-align: middle; }

/* ---- the 404 page ------------------------------------------------------ */
.nf-page { max-width: 560px; margin: 4.5rem auto 0; text-align: center;
           padding: 0 1.5rem; }
.nf-code { font-size: 0.8rem; font-weight: 700; letter-spacing: 0.12em;
           text-transform: uppercase; color: #9ca3af; }
.nf-page h1 { margin: 0.3rem 0 0.6rem; }
.nf-page p.meta { margin: 0 0 1.4rem; }
.nf-search { display: flex; gap: 0.5rem; justify-content: center;
             margin-bottom: 1.1rem; }
.nf-search input { flex: 1; max-width: 340px; padding: 0.5rem 0.8rem;
                   border: 1px solid #d1d5db; border-radius: 3px;
                   font-size: 0.95rem; }
.nf-search button { padding: 0.5rem 1rem; border: 1px solid #374151;
                    background: #374151; color: #fff; font-weight: 600;
                    border-radius: 3px; cursor: pointer; }
.nf-links { font-size: 0.9rem; }
.nf-report { margin-top: 2.2rem; font-size: 0.8rem; color: #6b7280; }

/* The "?" beside the toggle: tooltip machinery from .flag, styled as a
   quiet circled hint rather than a record pill. */
.scale-note {
    --tip: #374151;
    background: none;
    color: #6b7280;
    border: 1px solid var(--line-2);
    border-radius: 50%;
    width: 1.15rem;
    height: 1.15rem;
    line-height: 1.05rem;
    text-align: center;
    padding: 0;
    margin-left: 0.55rem;
    font-size: 11px;
}
/* The toggle sits flush against the page's RIGHT edge, and the flag
   tooltip opens rightward by default (left: 0) -- so this one ran off the
   page. Anchor it to the badge's right edge and open leftward, tail moved
   with it; same flip the <=900px rule applies to the table flags. */
.scale-note::after {
    left: auto;
    right: 0;
}
.scale-note::before {
    left: auto;
    right: 4px;
}

/* All-Time Bests sat in a card and Season Bests did not, so two equivalent
   panels read as different kinds of thing. */
/* ! SQUARE, like the tables. A stack of 8px-rounded white cards is the
   look this site is trying not to have. */
.bests-panel, .pr-panel, .chart-panel {
    border: 1px solid var(--line);
    padding: 0.9rem 1rem 1rem;
    margin-bottom: 1.1rem;
    background: #fff;
}
.bests-panel h3, .pr-panel h3, .chart-panel h3 {
    margin: 0 0 0.7rem;
    font-size: 0.8rem;
    text-transform: uppercase;
    letter-spacing: 0.06em;
    color: var(--muted);
    font-weight: 650;
}
/* ! NO BOX INSIDE A BOX. Now that the panel is a card, rating-head's own
   frame is a second border 8px inside the first -- and the width it stole
   wrapped "2026 TF season" onto two lines. */
.bests-panel .rating-head {
    border: 0;
    border-bottom: 1px solid var(--line);
    border-radius: 0;
    background: transparent;
    padding: 0 0 0.75rem;
    margin-bottom: 0.75rem;
}
.bests-panel .rating-head .note { white-space: nowrap; }
.pr-panel ul { list-style: none; padding-left: 0; margin: 0.2rem 0 1rem; }
.pr-panel li {
    display: flex;
    justify-content: space-between;
    padding: 0.15rem 0;
    font-size: 0.875rem;
}
.pr-panel .season-head-link {
    display: flex;
    justify-content: space-between;
    font-weight: 650;
    padding: 0.5rem 0 0.25rem;
    border-top: 1px solid var(--line);
    margin-top: 0.5rem;
}
.pr-panel .season-head-link:first-of-type { border-top: 0; margin-top: 0; }

/* The season and its rating are the two facts in the heading, so they sit
   at the two ends of one line. The rating was the faintest thing in it. */
.season-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 0.6rem;
    border-bottom: 1px solid var(--line-2);
    padding-bottom: 0.35rem;
    margin-bottom: 0.2rem;
}
.season-rating { font-weight: 700; font-variant-numeric: tabular-nums; }
.season-meta { color: var(--muted); font-size: 0.85rem; margin: 0.35rem 0 0.6rem; }

/* The header stat strip: the page's headline numbers, which used to live
   only in the sidebar. */
/* ! ONE RULE UNDER IT, NOT A BOX AROUND IT. Big numbers over tiny
   uppercase labels inside a bordered band is the dashboard-hero pattern;
   the numbers are worth having, the packaging is not. */
.stat-strip {
    display: flex;
    flex-wrap: wrap;
    gap: 2rem;
    align-items: baseline;
    padding: 0.5rem 0 0.8rem;
    border-bottom: 1px solid var(--line);
    margin: 0.4rem 0 0.2rem;
}
.stat-strip .s-lab {
    display: block;
    font-size: 0.68rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--muted);
}
.stat-strip .s-val {
    font-size: 1.3rem;
    font-weight: 700;
    font-variant-numeric: tabular-nums;
    line-height: 1.1;
}
.stat-strip .s-sub { font-size: 0.75rem; color: var(--muted); margin-left: 0.4rem; }

/* ---- phones, site-wide ------------------------------------------------ *
 * The result pages now carry a real viewport meta, so this is where their
 * phone layout is decided. Two calls made here:
 *
 *   TABLES KEEP FULL SIZE AND SCROLL SIDEWAYS WITHIN THEMSELVES. A results
 *   table is the content of these pages, so it stays at 1rem while the
 *   text around it drops a step -- and display:block + overflow-x keeps a
 *   wide table from dragging the whole page sideways: the page never
 *   scrolls horizontally, the table does.
 *
 *   THE TOPBAR WRAPS instead of overflowing: nav links flow onto a second
 *   line and the search box takes a row of its own. */
@media (max-width: 700px) {
  body { font-size: 0.9rem; }
  h1 { font-size: 1.4rem; }
  h2 { font-size: 1.15rem; }

  .topbar { flex-wrap: wrap; gap: 0.5rem 1rem; padding: 0.6rem 1rem; }
  .topnav { flex-wrap: wrap; row-gap: 0.25rem; }
  .search-wrap { flex: 1 1 100%; margin-left: 0; }

  table { display: block; overflow-x: auto; font-size: 1rem; }
  /* Tighter cells: the padding was sized for desktop whitespace, and on a
     phone it is the difference between four visible columns and six. */
  th, td { padding: 0.35rem 0.55rem; }
}


/* ---- courses board ------------------------------------------------------ */
/* ! THE SENSE WORD IS THE POINT, not decoration. A reader who sees only
   "+0.050" has to remember which way the sign runs; "harder" beside it means
   they never have to. It is small and muted so the number still leads. */
.rankings-page .rk td .sense {
    display: inline-block;
    margin-left: 0.35rem;
    font-size: 0.74em;
    font-weight: 400;
    color: var(--muted);
    text-transform: lowercase;
}
/* ⚠ COLOUR IS A SECOND CHANNEL, NEVER THE ONLY ONE. The word carries the
   meaning; these only reinforce it, so the board still reads correctly in
   greyscale and for anyone who cannot separate the two hues. */
.rankings-page .rk td.hard { color: #9a3412; }
.rankings-page .rk td.easy { color: #14532d; }


/* ---------------------------------------------------------------- */
/*  TRAINING PACES                                                    */
/*                                                                    */
/*  ★ THE PROVENANCE COLUMN IS THE POINT, not decoration. One row is  */
/*    this athlete's own two races with no constant in it; the others */
/*    are published relationships applied to that row. Rendering them */
/*    identically would flatten a real difference in confidence, so   */
/*    the derived row carries the accent and the basis column is      */
/*    always visible rather than a tooltip.                           */
/* ---------------------------------------------------------------- */
.paces { margin: 1.5rem 0 0.5rem; }
/* ! THE PANEL IS A CONVERSION TABLE, SO IT USES .conv-table AND ADDS ONLY
     WHAT IS ITS OWN. It had a private set of type sizes, paddings and a
     lowercase small-caps header row, which put a second table style on a
     page that already had one. Everything below is an addition to
     .conv-table, never a contradiction of it. */
.paces h2 { font-size: 1.1rem; margin: 0 0 0.5rem; }
.paces-src {
    font-size: 0.82rem;
    font-weight: 400;
    color: var(--muted);
    margin-left: 0.5rem;
}
.paces-tbl .zone { font-weight: 600; white-space: nowrap; }
/* ! TABULAR FIGURES so the pace column lines up digit over digit -- a
     column of times that does not align is harder to scan than one that
     does, and these are read as a ladder. */
.paces-tbl .pace {
    font-variant-numeric: tabular-nums;
    white-space: nowrap;
}
.paces-tbl .km { color: var(--muted); }
.paces-tbl .basis { color: var(--muted); }
.paces-tbl tr.derived .zone,
.paces-tbl tr.derived .pace { color: var(--accent, #14477d); }
.paces-tbl tr.derived .zone::after {
    content: "fitted";
    display: inline-block;
    margin-left: 0.45rem;
    font-size: 0.62rem;
    font-weight: 600;
    letter-spacing: 0.05em;
    text-transform: uppercase;
    color: var(--accent, #14477d);
    border: 1px solid currentColor;
    border-radius: 2px;
    padding: 0 0.25rem;
    vertical-align: 1px;
}
.paces-vdot {
    font-size: 0.92rem;
    margin-top: 0.6rem;
}
.paces-vdot strong { font-variant-numeric: tabular-nums; }
.paces-vdot-note { font-size: 0.78rem; color: var(--muted); margin-left: 0.3rem; }
.paces-foot {
    font-size: 0.78rem;
    color: var(--muted);
    margin-top: 0.55rem;
    line-height: 1.5;
}
@media (max-width: 640px) {
    .paces-tbl .km, .paces-tbl th:nth-child(3) { display: none; }
    .paces-tbl .basis, .paces-tbl th.basis { display: none; }
}

/* ---- TF team points + compiled results -------------------------------- */
/* Event section headings on the compiled page: the event name with its
   division/gender/round context riding the standard dist-note. */
.ev-head { margin: 1.6rem 0 0.4rem; }
/* A scoring cell: the number a team fight is read from. */
td.pts { font-weight: 700; }
/* The how-this-was-scored note stays a readable measure, not full-bleed. */
.tfp-note { max-width: 64rem; }
/* Team tables ride .rec-pair's fixed layout, so the number columns need
   widths (same rem discipline as .rec-ind): #, School, Points, Event wins.
   School is left unset and does the ellipsizing. */
/* wide enough for a tie label ("T35"), not just a digit */
.tfp-team th:nth-child(1) { width: 3.4rem; }
.tfp-team th:nth-child(3) { width: 4.5rem; }
.tfp-team th:nth-child(4) { width: 7.4rem; }
/* The "this table is the meet's own published score" badge */
.tfp-off {
    font-size: 0.62rem; font-weight: 700; letter-spacing: 0.05em;
    text-transform: uppercase; color: #166534; background: #dcfce7;
    border: 1px solid #bbf7d0; border-radius: 3px;
    padding: 0.08rem 0.35rem; vertical-align: 0.1rem; margin-left: 0.35rem;
}
/* Round/heat divider rows inside a race table */
.heat-row td {
    background: #f3f4f6;
    font-weight: 600;
    font-size: 0.72rem;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: #374151;
    padding: 0.35rem 0.6rem;
}

/* ---- school PRs page -------------------------------------------------- */
/* Side-by-side pair tables: #, Athlete, Time/Mark, (Rating), Grade, Date.
   Fixed layout, numbers sized, the name does the ellipsizing. */
.prs-tbl, .prs-course { table-layout: fixed; }
.prs-tbl th, .prs-tbl td, .prs-course th, .prs-course td {
    box-sizing: border-box;
    white-space: nowrap;
    overflow: hidden;
    text-overflow: ellipsis;
    /* the rec-pair padding: the widths below are sized to it */
    padding: 0.4rem 0.5rem;
}
.prs-tbl th:nth-child(1) { width: 2.4rem; }
.prs-tbl th:nth-child(3) { width: 4.9rem; }   /* time/mark */
.prs-tbl th:nth-child(4) { width: 4.7rem; }   /* rating */
.prs-tbl th:nth-child(5) { width: 4.4rem; }   /* grade */
.prs-tbl th:nth-child(6) { width: 6.5rem; }   /* date */
/* field tables have no Rating column: grade+date shift left one */
.prs-field.prs-tbl th:nth-child(4) { width: 4.4rem; }
.prs-field.prs-tbl th:nth-child(5) { width: 6.5rem; }
.prs-field.prs-tbl th:nth-child(6) { width: auto; }
/* full-width XC tables with the Course column: name and course flex */
.prs-course th:nth-child(1) { width: 2.4rem; }
.prs-course th:nth-child(3) { width: 4.9rem; }
.prs-course th:nth-child(4) { width: 4.7rem; }
.prs-course th:nth-child(5) { width: 4.4rem; }
.prs-course th:nth-child(7) { width: 6.5rem; }
/* folded course chips on the school PRs page */
.chip-hidden { display: none; }
button.yb-more { cursor: pointer; font: inherit; color: var(--accent, #14477d); }

/* The race picker inside the chosen-meet card: one chip per division. */
.mc-races { margin-top: 0.45rem; display: flex; flex-wrap: wrap;
            gap: 0.3rem; align-items: center; }
.mc-races-label { font-size: 0.78rem; color: #6b7280; margin-right: 0.15rem; }
.race-chip {
    font-size: 0.78rem; padding: 0.15rem 0.55rem; border-radius: 999px;
    border: 1px solid #d1d5db; background: #fff; cursor: pointer;
}
.race-chip.is-on { background: #111827; color: #fff; border-color: #111827; }

/* ---- race-page additions ---------------------------------------------- *
 * The corrected-distance star, the race-day weather line, the withheld-
 * ratings banner, and the per-page report link. All four are amber-family
 * where they caution: like .flag--warn, they are notes about where numbers
 * come from, never errors about the numbers. */

/* The star beside a corrected distance. A REAL-element tooltip, not the
 * .flag data-attribute machinery: this bubble carries a live "report it"
 * link, and a ::after pseudo-element cannot hold one. Same visual language
 * as the flag bubbles (white card, 2px colored border, opens up-right);
 * shown on :hover and :focus-within so keyboard and touch reach it too. */
.dist-note {
    position: relative;
    color: #a05a00;
    cursor: help;
    margin-left: 2px;
}
.dist-note:focus { outline: none; }
.dist-note:focus-visible { outline: 2px solid #a05a00; outline-offset: 1px; }
.dist-tip {
    display: none;
    position: absolute;
    bottom: 100%;
    left: -6px;
    z-index: 60;
    /* the hover bridge: padding, not a gap, so the pointer can travel from
       the star into the bubble without the tooltip closing under it */
    padding-bottom: 8px;
}
.dist-note:hover .dist-tip,
.dist-note:focus-within .dist-tip { display: block; }
.dist-tip-box {
    display: block;
    width: 270px;
    padding: 9px 12px;
    border: 2px solid #a05a00;
    border-radius: 9px;
    background: #fff;
    color: #222;
    font-size: 11.5px;
    font-weight: 400;
    line-height: 1.5;
    letter-spacing: 0;
    text-align: left;
}
.dist-tip-box b { color: #5c2a0c; }
.dist-tip-box a { display: inline-block; margin-top: 3px; }

/* Race-day weather: a quiet second meta line; bold only the numbers. */
.wx { color: #6b7280; }
.wx b { color: #374151; font-weight: 600; }

/* Ratings-withheld banner, between the Results heading and the table. */
.withheld {
    background: #fef3c7;
    border: 1px solid #e8d59a;
    border-left: 4px solid #a05a00;
    border-radius: 6px;
    padding: .7rem .95rem;
    margin: .8rem 0 1rem;
    font-size: .93rem;
    color: #78350f;
    max-width: 46rem;
}
.withheld b { color: #5c2a0c; }

/* The per-page report link, quiet, after the results table. */
.page-report {
    margin-top: 1.6rem;
    padding-top: .8rem;
    border-top: 1px solid #e5e7eb;
    font-size: .85rem;
    color: #6b7280;
}

/* The report page's context chip when arriving via ?about=. */
.about-chip {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    background: #e8eef6;
    color: #14477d;
    border-radius: 6px;
    padding: .3rem .6rem;
    font-size: .85rem;
    margin: 0 0 1rem;
}
.about-chip code { font-family: ui-monospace, Consolas, monospace; font-size: .82em; }

/* Arrived-from-a-profile row highlight: the meet/race page scrolls to the
   clicked result (?r=) and tints its row so the eye lands with it. */
tr.row-hi > td { background: #fdf3cd; }

/* ---- SCHOOL UNITS: league / section / division ------------------------
   OUTLINED, never filled. The filled .flag pills mean a RECORD was set;
   a league is a fact about where the school competes, not an
   achievement, so the two must not read as the same class of thing.
   Slate rather than any flag colour, for the same reason. */
.unit {
  display: inline-block;
  font-size: 11px;
  font-weight: 600;
  padding: 1px 6px;
  margin-right: 4px;
  border: 1px solid #3d566e;
  border-radius: 3px;
  color: #3d566e;
  background: transparent;
  vertical-align: middle;
}
/* The census found two corroborated units in the latest season and
   recorded the disagreement rather than resolving it. Amber, matching
   .flag--warn: a caution about confidence, never an error. */
.unit-q {
  color: #a05a00;
  font-weight: 700;
  margin-left: 3px;
  cursor: help;
}
