/* ---------- Inter, self-hosted ---------- */
/* Served from this origin rather than fonts.googleapis.com. Google Fonts meant every visitor's
   browser contacted Google and disclosed their IP address, which is the only third party this
   site had and the only thing stopping the privacy note saying "no third parties" outright.

   These are the same variable woff2 files Google serves, which is why there are four rather than
   ten: one file per subset/style spans the whole 400-800 weight axis, so `font-weight: 400 800`
   below is a RANGE, not a typo. Regenerate by fetching
   https://fonts.googleapis.com/css2?family=Inter:ital,wght@0,400;0,600;0,700;0,800;1,800
   with a modern browser User-Agent (an old one gets TTF instead of woff2) and downloading the
   latin and latin-ext urls.

   font-display: swap keeps text visible while the font loads, same as Google's CSS did.

   Inter is SIL OFL 1.1 -- see assets/fonts/NOTICE.md. */

@font-face {
  font-family: 'Inter';
  font-style: italic;
  font-weight: 400 800;
  font-display: swap;
  src: url('/fonts/inter-latin-ext-italic.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Inter';
  font-style: italic;
  font-weight: 400 800;
  font-display: swap;
  src: url('/fonts/inter-latin-italic.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('/fonts/inter-latin-ext-normal.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C4, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('/fonts/inter-latin-normal.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  --font-sans: "Inter", -apple-system, "Segoe UI", Roboto, Arial, sans-serif;

  --bg: #f5f6fa;
  --panel: #ffffff;
  --panel-alt: #f1f3f7;
  --border: #e2e6ed;
  --text: #12151c;
  --muted: #64748b;

  /* Primary colours, used sparingly as accents rather than as the base palette. */
  --blue: #0b5fd7;
  --blue-dim: #094aa8;
  --red: #dc2430;
  --red-dim: #b31b26;
  --yellow: #ffc60b;
  --header-bg: #7a1420;
  --header-bg-2: #55101a;
  --header-hover: rgba(255, 255, 255, 0.12);
  --header-active: rgba(255, 255, 255, 0.18);

  --good: #16a34a;
  --warn: #96590a;   /* readable amber text on white -- see --yellow for the bright badge fill */
  --bad: var(--red);

  /* Elevation + radius scale, applied consistently across every card-like surface (stat tiles,
     the rankings table, popovers, dropdowns) instead of one-off shadows per component. */
  --shadow-sm: 0 1px 2px rgba(18, 21, 28, 0.07);
  --shadow-md: 0 6px 18px rgba(18, 21, 28, 0.09);
  --shadow-lg: 0 18px 44px rgba(18, 21, 28, 0.16);
  --radius-sm: 7px;
  --radius-md: 11px;
  --radius-lg: 16px;
}

* { box-sizing: border-box; }

/* The `hidden` attribute's default display:none is a UA-stylesheet rule -- any author rule that
   sets display on the same element (e.g. .button-link's display:inline-flex, below) overrides it
   at equal specificity, since author styles always beat UA styles. Real bug hit in practice: the
   Export to Excel links stayed visibly flexed even while .hidden was correctly true in the DOM.
   This forces hidden to always win, for every element, regardless of what else sets display. */
[hidden] { display: none !important; }

body {
  margin: 0;
  font-family: var(--font-sans);
  line-height: 1.55;
  background: var(--bg);
  color: var(--text);
}

header {
  padding: 1.6rem 2rem 0;
  background:
    radial-gradient(circle at 92% -40%, rgba(255, 255, 255, 0.12) 0%, transparent 45%),
    linear-gradient(135deg, var(--header-bg) 0%, var(--header-bg-2) 100%);
  color: #fff;
  box-shadow: var(--shadow-lg);
}

.header-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  gap: 1rem;
  margin-bottom: 1.35rem;
}

.brand {
  display: flex;
  align-items: center;
  min-width: 0; /* lets the h1 truncate/wrap instead of forcing the flex row to overflow */
}


header h1 {
  margin: 0;
  font-size: 1.75rem;
  font-weight: 800;
  font-style: italic;
  text-transform: uppercase;
  letter-spacing: 0.015em;
  line-height: 1.15;
  color: #fff;
  text-shadow: 0 1px 4px rgba(0, 0, 0, 0.25);
}

/* Two-tone logotype: the category word picks up the accent colour already used for the review
   badge/HPS highlights elsewhere, so the wordmark doesn't introduce a new colour of its own. */
.wordmark-accent { color: var(--yellow); }

.profile-menu { position: relative; flex-shrink: 0; }

.profile-btn {
  background: rgba(255, 255, 255, 0.14);
  border: none;
  color: #fff;
  width: 36px;
  height: 36px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
  transition: background-color 0.15s ease, transform 0.15s ease;
}
.profile-btn:hover { background: rgba(255, 255, 255, 0.24); transform: scale(1.06); }
.profile-btn.logged-in { background: var(--good); }

.profile-popover {
  position: absolute;
  top: calc(100% + 0.6rem);
  right: 0;
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  padding: 1rem;
  min-width: 220px;
  z-index: 20;
}

.login-form { display: flex; flex-direction: column; gap: 0.5rem; }
.login-form label { display: flex; flex-direction: column; gap: 0.25rem; color: var(--muted); font-size: 0.85rem; }
.login-error { color: var(--bad); font-size: 0.85rem; margin: 0.25rem 0 0; min-height: 1em; }

.login-status { display: flex; flex-direction: column; gap: 0.6rem; align-items: flex-start; }
.login-status p { margin: 0; font-size: 0.9rem; }

nav {
  display: flex;
  gap: 0.35rem;
  overflow-x: auto;
  /* Explicit, not the default `visible` -- per spec, a non-visible overflow-x paired with a
     left-as-visible overflow-y silently computes the latter to `auto` too (the same coupling
     documented on .table-wrap above). Any stray sub-pixel vertical overflow from flex/font-metric
     rounding (or an absolutely-positioned child like .tab-btn::after) then makes `nav` a
     scrollable container in its own right and draws a real, visible scrollbar for it -- this was
     the cause of a genuinely confusing stray scrollbar-with-arrows sitting on the header. Setting
     this explicitly clips any such overflow instead of scrolling it. */
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  border-bottom: 1px solid rgba(255, 255, 255, 0.14);
}

.tab-btn {
  position: relative;
  background: none;
  border: none;
  color: rgba(255, 255, 255, 0.72);
  padding: 0.75rem 1.05rem;
  cursor: pointer;
  font-size: 0.92rem;
  font-weight: 600;
  letter-spacing: 0.01em;
  white-space: nowrap;
  flex-shrink: 0;
  transition: color 0.15s ease;
}

/* Underline-style active indicator (a thin brand-yellow bar riding the header's own bottom
   border) reads as a modern scoreboard tab strip rather than the flat filled-rectangle look a
   plain background-color swap would give. bottom: 0, not -1px -- `nav` has overflow-x: auto,
   and per spec a non-visible overflow-x forces overflow-y to compute as auto too (the exact
   same coupling documented on .table-wrap above), so a pseudo-element poking even 1px below
   nav's own box was enough to make nav a scrollable container and draw a real, visible
   scrollbar for that sub-pixel overflow -- the "arrow thing" bug. Staying inside the box avoids
   creating any overflow at all. */
.tab-btn::after {
  content: "";
  position: absolute;
  left: 1.05rem;
  right: 1.05rem;
  bottom: 0;
  height: 3px;
  border-radius: 3px 3px 0 0;
  background: var(--yellow);
  opacity: 0;
  transform: translateY(2px);
  transition: opacity 0.15s ease, transform 0.15s ease;
}

.tab-btn:hover { color: #fff; }

.tab-btn.active { color: #fff; }
.tab-btn.active::after { opacity: 1; transform: translateY(0); }

.badge {
  background: var(--yellow);
  color: #1a1200;
  border-radius: 999px;
  padding: 0.05rem 0.55rem;
  font-size: 0.75rem;
  font-weight: 700;
  margin-left: 0.35rem;
}

main { padding: 2rem 2rem 3.5rem; max-width: 1120px; margin: 0 auto; }

.tab-panel { display: none; }
.tab-panel.active { display: block; }

.panel-header { display: flex; flex-wrap: wrap; gap: 0.75rem; justify-content: space-between; align-items: center; margin-bottom: 0.5rem; }

.table-wrap {
  /* Not overflow-x: auto here -- per spec, overflow-x/y can't independently mix visible with a
     non-visible value; setting overflow-x to anything but visible silently forces overflow-y to
     compute as auto too, which makes THIS the sticky containing block for the <th> elements
     below instead of the page, breaking the sticky header entirely (found live: rectTop stayed
     negative/scrolled-away instead of clamping to 0). Desktop tables comfortably fit without
     horizontal scroll, so leave this visible by default and only re-enable overflow-x in the
     mobile breakpoint below, where shifting columns into view matters more than a sticky header. */
  overflow-x: visible;
  -webkit-overflow-scrolling: touch;
  margin-top: 0.75rem;
}
.table-wrap table { margin-top: 0; min-width: 560px; }

h2 {
  font-size: 1.4rem;
  font-weight: 700;
  letter-spacing: -0.005em;
  line-height: 1.25;
  margin: 0 0 0.5rem;
}
/* Inside a flex .panel-header (heading + action buttons on one row), let the header's own
   margin-bottom carry the spacing instead -- an asymmetric margin on just the h2 would skew its
   vertical centering against the buttons sitting next to it (align-items: center centers each
   flex item's full margin box, not just its content). */
.panel-header h2 { margin: 0; }

h3 {
  font-weight: 700;
  line-height: 1.3;
}

.hint { color: var(--muted); font-size: 0.9rem; margin-top: -0.15rem; }

table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 0.75rem;
  background: var(--panel);
  border: 1px solid var(--border);
  box-shadow: var(--shadow-sm);
  font-variant-numeric: tabular-nums; /* scores/ranks/points line up vertically column to column */
}
/* All four table corners are square, deliberately.

   The table carries a 1px border that is NOT rounded -- border-radius was only ever applied to
   the corner cells, never to <table> itself -- so rounding a cell's background fill left it
   visibly inset inside a square-cornered border: a chamfer floating within the corner rather
   than a rounded corner. The top corners were squared for that reason earlier; the bottom two
   kept the radius and showed the same artefact on the last row.
   
   Rounding the table properly would mean `overflow: hidden` on <table>, which is exactly what
   must not happen: a non-scrolling ancestor with non-visible overflow becomes the sticky
   containing block for the <th> elements, which breaks the sticky header outright. Square
   corners are the honest resolution. */

th, td {
  text-align: left;
  padding: 0.7rem 0.9rem;
  border-bottom: 1px solid var(--border);
  font-size: 0.9rem;
}

th {
  position: sticky;
  /* -1px, not 0, to kill a hairline seam above the stuck header. At a fractional
     devicePixelRatio (1.5 on a 150%-scaled Windows display) one CSS pixel isn't a whole device
     pixel, so the sticky row's own compositing layer snaps to the device grid while the rows
     scrolling behind it move continuously. When the rounding falls the wrong way, a sliver of
     the row underneath shows above the header and reads as a ~1px gap at the top of the
     viewport. Layout was never wrong -- getBoundingClientRect().top was exactly 0 -- so there
     is nothing to fix geometrically; parking the row 1px above the edge simply pushes the seam
     off-screen. The lost pixel is padding (0.7rem of it), so nothing is visibly clipped, and
     `top` has no effect at all until the row actually sticks. */
  top: -1px;
  z-index: 2;
  color: var(--muted);
  font-weight: 700;
  font-size: 0.78rem;
  letter-spacing: 0.03em;
  text-transform: uppercase;
  background: var(--panel-alt);
  border-bottom: 2px solid var(--border);
}

tbody tr { transition: background-color 0.12s ease; }
tbody tr:nth-child(even) td { background: var(--panel-alt); }
tbody tr:hover td { background: rgba(122, 20, 32, 0.045); }

tr:last-child td { border-bottom: none; }

button, input, select {
  font-family: inherit;
  font-size: 0.9rem;
}

/* Plain text, not a button -- explicitly zeroes out every visual property the shared `button`
   rule below sets (shadow, radius, gradient), since `.sort-header` is a real <button> element
   under the hood and a class selector alone doesn't reset properties the element rule sets but
   this rule doesn't mention. Without this, the column titles picked up a faint pill/box outline
   from `button`'s box-shadow showing through the (correctly) transparent background. */
.sort-header {
  background: none;
  border: none;
  box-shadow: none;
  border-radius: 0;
  padding: 0;
  margin: 0;
  color: inherit;
  font: inherit;
  font-weight: 700;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
}
.sort-header:hover, .sort-header:active { box-shadow: none; transform: none; background: none; }

.sort-header:hover { background: none; color: var(--text); }

.sort-arrows {
  display: inline-flex;
  flex-direction: column;
  gap: 0.17rem;
}

.sort-arrows .arrow {
  font-size: 0.6rem;
  line-height: 1;
  color: var(--muted);
  opacity: 0.5;
}

.sort-header:hover .arrow { opacity: 0.8; }
.sort-arrows .arrow.active { color: var(--blue); opacity: 1; }

button {
  background: var(--red);
  color: white;
  border: none;
  padding: 0.55rem 1.1rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
  font-weight: 600;
  transition: background-color 0.12s ease;
}

button:hover { background: var(--red-dim); }

button.secondary {
  background: var(--panel);
  border: 1px solid var(--border);
  color: var(--text);
}

button.secondary:hover { background: var(--panel-alt); }

button.small-btn {
  padding: 0.3rem 0.65rem;
  font-size: 0.85rem;
  border-radius: var(--radius-sm);
  margin-right: 0.3rem;
}

.button-link {
  display: inline-flex;
  align-items: center;
  background: var(--red);
  color: white;
  border: none;
  padding: 0.55rem 1.1rem;
  border-radius: var(--radius-sm);
  cursor: pointer;
  text-decoration: none;
  font-size: 0.9rem;
  font-weight: 600;
  transition: background-color 0.12s ease;
}
.button-link:hover { background: var(--red-dim); }
.button-link.secondary {
  background: var(--panel);
  border: 1px solid var(--border);
  color: var(--text);
}
.button-link.secondary:hover { background: var(--panel-alt); }

.panel-header-actions { display: flex; gap: 0.6rem; align-items: center; flex-wrap: wrap; }

form#ingest-form {
  display: grid;
  gap: 0.75rem;
  max-width: 480px;
  margin-top: 1rem;
}

form label {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  color: var(--muted);
  font-size: 0.85rem;
}

input, select {
  background: var(--panel);
  border: 1px solid var(--border);
  color: var(--text);
  padding: 0.5rem 0.7rem;
  border-radius: var(--radius-sm);
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}
input:focus, select:focus {
  outline: none;
  border-color: var(--blue);
  box-shadow: 0 0 0 3px rgba(11, 95, 215, 0.15);
}

.rankings-filters {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 0.75rem;
}
/* The search input and its clear button share a box so the button can sit inside the field. */
.search-field { position: relative; display: flex; flex: 1 1 260px; max-width: 320px; }
#rankings-search { flex: 1 1 auto; width: 100%; padding-right: 2rem; }
#rankings-country-filter { flex: 0 1 200px; }

/* Suppress every platform's own clear control. WebKit/Blink draw ::-webkit-search-cancel-button
   (iOS renders it as a system glyph that reads as an emoji against this flat UI); the other two
   are the legacy IE/Edge pseudo-elements, harmless to include and cheap insurance. */
#rankings-search::-webkit-search-cancel-button,
#rankings-search::-webkit-search-decoration { -webkit-appearance: none; appearance: none; }
#rankings-search::-ms-clear { display: none; width: 0; height: 0; }

.search-clear {
  position: absolute;
  right: 0.15rem;
  top: 50%;
  transform: translateY(-50%);
  display: flex;
  align-items: center;
  justify-content: center;
  width: 1.6rem;
  height: 1.6rem;
  padding: 0;
  background: none;
  border: none;
  border-radius: var(--radius-sm);
  color: var(--muted);
  cursor: pointer;
}
.search-clear:hover { color: var(--text); background: var(--panel-alt); }

.rankings-contact-link {
  display: flex;
  align-items: center;
  gap: 0.4rem;   /* the envelope is an inline SVG now, not a text glyph, so it needs real spacing */
  color: var(--muted);
  text-decoration: none;
  font-size: 0.9rem;
  border-bottom: 1px dashed transparent;
}
.rankings-contact-link:hover { color: var(--blue); border-bottom-color: var(--blue); }
.contact-icon { flex: none; }  /* stop the envelope squashing when the row gets tight */

/* Pagination under the rankings table. Flat and square to match the table itself -- these are
   navigation, not calls to action, so they deliberately don't inherit the red fill that bare
   `button` carries. */
.pagination {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  margin-top: 0.9rem;
}
.pagination-summary { color: var(--muted); font-size: 0.85rem; }
.pagination-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 0.25rem; }

.pagination-btn {
  background: var(--panel);
  color: var(--text);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.35rem 0.6rem;
  min-width: 2.15rem;
  font-size: 0.85rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums; /* page numbers keep an even width as the digits change */
}
.pagination-btn:hover:not(:disabled) { background: var(--panel-alt); border-color: var(--muted); }
.pagination-btn:disabled { opacity: 0.4; cursor: default; background: var(--panel); }

/* The current page reads as selected rather than clickable. */
.pagination-btn.current {
  background: var(--header-bg);
  border-color: var(--header-bg);
  color: #fff;
  cursor: default;
}
.pagination-btn.current:hover { background: var(--header-bg); border-color: var(--header-bg); }

.pagination-gap { color: var(--muted); padding: 0 0.15rem; user-select: none; }

.info-content { max-width: 680px; line-height: 1.55; }
.info-content h3 { margin-bottom: 0.35rem; }
/* About's method section nests one level down, so its sub-headings must read as subordinate to
   the h3s around them: same weight, body size, tighter spacing. */
.info-content h4 { font-size: 1rem; font-weight: 700; line-height: 1.3; margin: 1.1rem 0 0.25rem; }
/* Anchor targets (/#how-it-works, #recency) land with a little air rather than flush to the edge. */
.info-content [id] { scroll-margin-top: 1rem; }

.athlete-link {
  color: inherit;
  text-decoration: none;
  border-bottom: 1px dashed var(--border);
  cursor: pointer;
}
.athlete-link:hover { color: var(--blue); border-bottom-color: var(--blue); }

/* Rankings table's Athlete column lost its "(ITRA000038)" ID suffix -- widen the name itself
   (tracking + a touch more size) to use the freed-up space rather than leaving it empty. */
.rankings-athlete-cell { letter-spacing: 0.04em; font-size: 0.95rem; }

.back-link {
  background: none;
  border: none;
  color: var(--blue);
  padding: 0;
  margin-bottom: 1rem;
  font-size: 0.9rem;
  cursor: pointer;
}
.back-link:hover { background: none; text-decoration: underline; }

/* Admin-only "master edit" on the athlete profile -- sits between the meta line and the stat
   tiles, hidden until the Edit button is pressed. */
.athlete-edit-form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.75rem;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: 0.9rem 1.1rem;
  margin: 1rem 0 0;
  box-shadow: var(--shadow-sm);
}
.athlete-edit-form label { flex: 1 1 200px; }
.athlete-edit-form input { width: 100%; }
.athlete-edit-actions { display: flex; gap: 0.5rem; }
.athlete-edit-form .login-error { flex-basis: 100%; margin: 0; }

.stat-grid {
  display: flex;
  flex-wrap: wrap;
  gap: 0.9rem;
  margin: 1.25rem 0 1.75rem;
}

.stat-tile {
  background: var(--panel);
  border: 1px solid var(--border);
  border-top: 3px solid var(--header-bg);
  border-radius: var(--radius-md);
  padding: 0.8rem 1.15rem;
  min-width: 105px;
  box-shadow: var(--shadow-sm);
}
.stat-tile .stat-label {
  font-size: 0.7rem;
  color: var(--muted);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}
.stat-tile .stat-value {
  font-variant-numeric: tabular-nums;
  font-size: 1.55rem;
  font-weight: 800;
  line-height: 1.35;
  margin-top: 0.1rem;
}
.stat-tile .stat-value small { font-size: 0.7rem; font-weight: 600; margin-left: 0.15rem; }

/* Results outside an athlete's best N. Greyed rather than hidden: a visitor should be able to see
   every result and why most of their ranking rests on the others. */
#profile-results-table tr.result-dropped td { color: var(--muted); }
#profile-results-table tr.result-dropped .hint { opacity: 0.75; }
.result-points { font-variant-numeric: tabular-nums; font-weight: 600; }
tr.result-dropped .result-points { font-weight: 400; }
#profile-results-note { margin: -0.25rem 0 0.75rem; }
/* The year rides inside the competition cell on phones only (see the mobile block). */
.comp-year { display: none; }
.info-content p { margin-top: 0.35rem; }

.checkbox-label {
  flex-direction: row;
  align-items: center;
  gap: 0.5rem;
}
.checkbox-label input[type="checkbox"] { width: auto; flex-shrink: 0; }

/* Plain cards, all three alike. They used gold/silver/bronze left edges for tiers 1-3, which
   Jonny removed on 2026-10-09 along with the rest of the medal styling: the tier number and its
   points already say which is which. */
.tier-card {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 0.65rem 0.9rem;
  margin-bottom: 0.5rem;
  box-shadow: var(--shadow-sm);
}

code {
  background: var(--panel-alt);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 0.1rem 0.35rem;
  font-size: 0.85em;
}

.flag {
  height: 1.5em;
  width: auto;
  vertical-align: middle;
  margin-right: 0.35em;
  margin-top: -0.15em;
  border-radius: 2px;
  box-shadow: 0 0 0 0.5px rgba(0,0,0,0.18);
}

.rank-num {
  display: inline-block;
  min-width: 2em; /* fixed so 1/2/3-digit ranks don't shift where the arrow starts */
  text-align: right;
}

.move-up, .move-down, .move-none, .move-new {
  display: inline-block;
  font-size: 0.9em;
  margin-left: 0.35em;
  white-space: nowrap;
}
.move-up { color: var(--good); }
.move-down { color: var(--bad); }
.move-none { color: var(--muted); }
.move-new { color: var(--blue); font-weight: 600; }

.status-pill {
  display: inline-block;
  font-size: 0.82rem;
  font-weight: 600;
  padding: 0.15em 0.55em;
  border-radius: 999px;
}
.status-no_match { color: var(--bad); background: rgba(220, 36, 48, 0.1); }
.status-ambiguous { color: var(--warn); background: rgba(255, 198, 11, 0.18); }

#ingest-status { margin-top: 1rem; color: var(--muted); }

.resolution-row select, .resolution-row input { width: 100%; }

.trid-autocomplete { position: relative; margin-bottom: 0.3rem; }
.trid-suggestions {
  position: absolute;
  z-index: 10;
  left: 0;
  right: 0;
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  margin-top: 0.2rem;
  max-height: 220px;
  overflow-y: auto;
  box-shadow: var(--shadow-lg);
}
.trid-suggestion { padding: 0.4rem 0.6rem; cursor: pointer; border-bottom: 1px solid var(--border); }
.trid-suggestion:last-child { border-bottom: none; }
.trid-suggestion:hover { background: var(--panel-alt); }
.trid-suggestion-name { display: block; }
.trid-suggestion-empty { padding: 0.4rem 0.6rem; }

@media (max-width: 640px) {
  header { padding: 1.1rem 1rem 0; }
  header h1 { font-size: 1.1rem; }
  main { padding: 1rem 1rem 2rem; }

  /* Larger touch targets -- the desktop sizing below this breakpoint runs a bit small for
     fingers rather than a mouse cursor. */
  button, input, select { font-size: 1rem; }
  button { padding: 0.65rem 1.1rem; }
  input, select { padding: 0.6rem 0.7rem; }
  th, td { padding: 0.5rem 0.6rem; }

  /* Re-enable horizontal scroll here, narrow viewports need it to shift columns into view --
     this does mean the sticky header (see .table-wrap above) stops sticking below this
     breakpoint, since that requires overflow-x to stay visible. Reasonable trade-off: shifting
     columns into view matters more on a phone than a sticky header does. */
  .table-wrap { overflow-x: auto; }

  #rankings-search, #rankings-country-filter { max-width: none; }
  /* flex-basis follows the main axis -- #rankings-search/#rankings-country-filter's desktop
     `flex: 1 1 260px` sets that 260px as a WIDTH hint under the default row layout, but once this
     rule flips the container to a column, the same flex-basis becomes a HEIGHT hint instead,
     stretching both controls to ~260px tall. Reset flex-basis (and flex-grow, so they don't also
     fight over the column's leftover vertical space) explicitly for column layout. Targeted by
     the same ID selectors as the desktop rule (not a class/descendant selector) so this actually
     wins the cascade -- an ID beats a class regardless of source order. */
  .rankings-filters { flex-direction: column; }
  /* .search-field is in this list because it now carries the flex-basis that #rankings-search
     used to: wrapping the input to position the clear button inside it moved the sizing onto
     the wrapper, and the wrapper was not reset here. The result was a ~500px tall search box on
     phones -- flex-basis follows the MAIN axis, so 260px stops being a width hint and becomes a
     height one the moment this rule flips the container to a column. Any new element added to
     .rankings-filters with a flex-basis needs resetting here too. */
  .search-field, #rankings-search, #rankings-country-filter { flex: none; }
  .search-field { max-width: none; }

  /* Stack the summary above the controls rather than letting space-between strand them at
     opposite edges of a narrow screen. */
  .pagination { justify-content: flex-start; }
  .pagination-summary { flex: 1 0 100%; }

  /* Columns a phone drops so the ones that matter fit without sideways scrolling. On the
     rankings that is Total Pts and Events: at 375px wide the score -- the number the list is
     ranked by -- used to sit off-screen to the right. Both still appear on each profile. On a
     profile it is Field Size (Position already reads "501 / 593") and Tier (Points subsumes it). */
  .col-extra { display: none; }
  /* Flag only in the rankings' country column. Profiles keep the name beside the flag. */
  #rankings-table .country-name { display: none; }
  /* ...and lift the blanket 560px table minimum for these two, or the hidden columns' space is
     simply handed to the remaining ones and the table stays exactly as wide (found live: Score
     ended up further off-screen than before). The admin tables keep the minimum. */
  #rankings-table, #profile-results-table { min-width: 0; }
  /* The profile's Year column goes; the year sits small above each competition name instead,
     which frees the width for Points. "501 / 593" stays on one line. */
  .comp-year { display: block; font-size: 0.75rem; color: var(--muted); line-height: 1.2; }
  #profile-results-table .result-position-cell { white-space: nowrap; }
  /* Rank and movement arrow stay on one line; narrowed, the arrow wrapped under the number. */
  #rankings-table td:first-child { white-space: nowrap; }
  #rankings-table th, #rankings-table td,
  #profile-results-table th, #profile-results-table td { padding: 0.5rem 0.45rem; }

  /* Profile figures: a tight 3-column block instead of five tall cards stacking 2-2-1, which
     filled the whole first screen before any results appeared. */
  .stat-grid {
    display: grid;
    grid-template-columns: repeat(3, minmax(0, 1fr));
    gap: 0.5rem;
    margin: 1rem 0 1.25rem;
  }
  .stat-tile {
    min-width: 0;
    padding: 0.5rem 0.6rem;
    display: flex;
    flex-direction: column;
    justify-content: space-between;
  }
  .stat-tile .stat-label { font-size: 0.6rem; letter-spacing: 0.03em; }
  .stat-tile .stat-value { font-size: 1.15rem; line-height: 1.25; }
  .stat-tile .stat-value small { display: block; margin-left: 0; font-size: 0.65rem; }
}

/* Accepted validation issues -- collapsed by default when anything is still outstanding, so the
   list leads with what actually wants attention. */
.accepted-issues { margin-top: 1rem; }
.accepted-issues > summary {
  cursor: pointer;
  color: var(--muted);
  font-size: 0.85rem;
  font-weight: 600;
  padding: 0.35rem 0;
}
.accepted-issues > summary:hover { color: var(--text); }
.accepted-issues[open] > summary { margin-bottom: 0.25rem; }


/* ---------- 404 ---------- */
/* The header on this page has no nav, so give the block room and centre it rather than letting
   it sit tight under the bar like the rankings table does. */
.error-page {
  max-width: 560px;
  margin: 4rem auto;
  text-align: center;
}
.error-pun {
  font-size: clamp(1.6rem, 5vw, 2.4rem);
  font-weight: 800;
  font-style: italic;      /* matches the header wordmark and the favicon */
  letter-spacing: -0.01em;
  margin: 0 0 0.4rem;
}
.error-code {
  font-size: clamp(3.5rem, 14vw, 6rem);
  font-weight: 800;
  line-height: 1;
  margin: 0 0 1.2rem;
  color: var(--header-bg);
  font-variant-numeric: tabular-nums;
}
.error-note {
  color: var(--muted);
  margin: 0 0 1.8rem;
  line-height: 1.5;
}
/* The brand is a link on the error page but plain text elsewhere, so it needs its own reset
   rather than inheriting the default anchor styling. */
.brand-link { display: flex; align-items: center; gap: 0.6rem; text-decoration: none; color: inherit; }
