/* Climbing scorer — one stylesheet, mobile-first.
   Entry happens on a phone, at a crag, in sun: big targets, high contrast.

   Palette rationale
   -----------------
   A scoreboard is a cheerful object, and this one is read outdoors, so the
   light theme is the designed one and the dark variant exists only because
   the OS may ask for it.

   The accent is a deepened jade-teal. Teal reads outdoor and athletic and is
   uncrowded in climbing software — indigo-violet is the current herd and
   reads SaaS rather than sport — but the usual teal-500 family only reaches
   about 3:1 on white, which fails AA for text. Darkening to #0A7C6B keeps the
   hue and gives 5.11:1 for white-on-accent.

   Amber is the highlight rather than a second brand colour: gold reads
   naturally as first place, so it carries ranks and "here now" badges.

   Layout follows the current convention of a *tinted* page ground with
   *white* cards sitting on it — that gives elevation without relying on
   shadows, which matters here because soft shadows disappear in glare while
   a 1px border does not. Shadows are kept for the sticky header alone.

   Contrast, computed against the backgrounds actually used (WCAG 2.1):
     ink on bg        16.7:1     ink-soft on bg    7.09:1
     white on accent   5.11:1    ink on highlight  9.3:1
   Body text clears AAA; everything clears AA. A screen at 50% brightness in
   direct sun loses roughly 40% of its effective contrast, which is why the
   soft ink is #4A5567 rather than a lighter grey that would merely pass. */

:root {
  /* Light palette. `--light-*` are the source values; the live tokens below
     point at them, so data-theme="light" can restore them inside a dark page
     and there is still one place to edit a colour. */
  --light-bg: #f7f8fa;
  --light-surface: #ffffff;
  --light-surface-2: #f0f2f5;
  --light-ink: #101828;
  --light-ink-soft: #4a5567;
  --light-line: #e3e6eb;

  --light-accent: #0a7c6b;
  --light-accent-ink: #ffffff;
  --light-accent-soft: #e6f4f1;

  /* Bright amber is a FILL colour: #f2b01e carries dark ink at 9.3:1, but as
     text on white it is only 1.9:1 — invisible in sun. `--hi-ink` is the
     darkened amber for anything that is actually read. */
  --light-hi: #f2b01e;
  --light-hi-soft: #fdf4e0;
  --light-hi-ink: #96600c;

  --light-warn-bg: #fdf4e0;
  --light-warn-line: #d99e12;
  --light-err-bg: #fdeeee;
  --light-err-line: #c8252b;
  --light-err-ink: #a81d22;
  --light-ok: #107a52;
  --light-shadow: 0 1px 2px 0 rgb(16 24 40 / .05);
  --light-shadow-lg: 0 10px 15px -3px rgb(16 24 40 / .10),
                     0 4px 6px -4px rgb(16 24 40 / .10);

  /* Dark palette source values. */
  --dark-bg: #0b0f14;
  --dark-surface: #151b23;
  --dark-surface-2: #1e2630;
  --dark-ink: #e8ecf2;
  --dark-ink-soft: #9aa6b8;
  --dark-line: #263040;

  --dark-accent: #2dd4b4;
  --dark-accent-ink: #06211c;
  --dark-accent-soft: #12302b;

  --dark-hi: #ffc94a;
  --dark-hi-soft: #332a14;
  --dark-hi-ink: #ffc94a;

  --dark-warn-bg: #332a14;
  --dark-warn-line: #fbbf24;
  --dark-err-bg: #3a1f1e;
  --dark-err-line: #ff6b6b;
  --dark-err-ink: #ffb4b4;
  --dark-ok: #34d399;
  --dark-shadow: 0 1px 2px 0 rgb(0 0 0 / .4);
  --dark-shadow-lg: 0 10px 20px -4px rgb(0 0 0 / .5);

  /* The live tokens every rule reads. */
  --bg: var(--light-bg);
  --surface: var(--light-surface);
  --surface-2: var(--light-surface-2);
  --ink: var(--light-ink);
  --ink-soft: var(--light-ink-soft);
  --line: var(--light-line);
  --accent: var(--light-accent);
  --accent-ink: var(--light-accent-ink);
  --accent-soft: var(--light-accent-soft);
  --hi: var(--light-hi);
  --hi-soft: var(--light-hi-soft);
  --hi-ink: var(--light-hi-ink);
  --warn-bg: var(--light-warn-bg);
  --warn-line: var(--light-warn-line);
  --err-bg: var(--light-err-bg);
  --err-line: var(--light-err-line);
  --err-ink: var(--light-err-ink);
  --ok: var(--light-ok);
  --shadow: var(--light-shadow);
  --shadow-lg: var(--light-shadow-lg);

  /* Cards 12px, controls 8px — the current convention, and a softer card
     than the 10px it replaces reads friendlier without looking novelty. */
  --radius: 12px;
  --radius-sm: 8px;
  --tap: 44px;
  color-scheme: light;
}

/* Light is the default, whatever the device prefers.
   This is read outdoors, in sun, where a light high-contrast screen beats a
   dark one, and a dark leaderboard on a projector at basecamp washes out. So
   `prefers-color-scheme` is deliberately NOT consulted: dark applies only
   when someone asks for it with the toggle, which sets data-theme. */

[data-theme='dark'] {
  --bg: var(--dark-bg);
  --surface: var(--dark-surface);
  --surface-2: var(--dark-surface-2);
  --ink: var(--dark-ink);
  --ink-soft: var(--dark-ink-soft);
  --line: var(--dark-line);
  --accent: var(--dark-accent);
  --accent-ink: var(--dark-accent-ink);
  --accent-soft: var(--dark-accent-soft);
  --hi: var(--dark-hi);
  --hi-soft: var(--dark-hi-soft);
  --hi-ink: var(--dark-hi-ink);
  --warn-bg: var(--dark-warn-bg);
  --warn-line: var(--dark-warn-line);
  --err-bg: var(--dark-err-bg);
  --err-line: var(--dark-err-line);
  --err-ink: var(--dark-err-ink);
  --ok: var(--dark-ok);
  --shadow: var(--dark-shadow);
  --shadow-lg: var(--dark-shadow-lg);
  color-scheme: dark;
}

/* Restores the light tokens, so ?theme=light works on a dark machine and a
   light frame can sit inside a dark page. */
[data-theme='light'] {
  --bg: var(--light-bg);
  --surface: var(--light-surface);
  --surface-2: var(--light-surface-2);
  --ink: var(--light-ink);
  --ink-soft: var(--light-ink-soft);
  --line: var(--light-line);
  --accent: var(--light-accent);
  --accent-ink: var(--light-accent-ink);
  --accent-soft: var(--light-accent-soft);
  --hi: var(--light-hi);
  --hi-soft: var(--light-hi-soft);
  --hi-ink: var(--light-hi-ink);
  --warn-bg: var(--light-warn-bg);
  --warn-line: var(--light-warn-line);
  --err-bg: var(--light-err-bg);
  --err-line: var(--light-err-line);
  --err-ink: var(--light-err-ink);
  --ok: var(--light-ok);
  --shadow: var(--light-shadow);
  --shadow-lg: var(--light-shadow-lg);
  color-scheme: light;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  padding-bottom: env(safe-area-inset-bottom, 0px);
  -webkit-text-size-adjust: 100%;
}

/* One visible focus style everywhere, in the accent rather than the
   browser's blue, so keyboard use looks intentional. */
:where(a, button, input, select, textarea, summary):focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

.topbar {
  display: flex; align-items: center; gap: 1rem;
  padding: .75rem 1rem;
  padding-top: calc(.75rem + env(safe-area-inset-top, 0px));
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  position: sticky; top: 0; z-index: 10;
  box-shadow: var(--shadow);
}
/* A stripe of accent under the bar: the one piece of colour every page
   shares, so the app reads as one thing. */
.topbar::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: -2px;
  height: 2px;
  background: linear-gradient(90deg, var(--accent), var(--hi));
}
.brand { font-weight: 700; text-decoration: none; color: var(--ink); letter-spacing: -.01em; }
.topbar nav { margin-left: auto; display: flex; gap: 1rem; }
.topbar a { color: var(--ink-soft); text-decoration: none; }
.topbar nav a:hover { color: var(--ink); }

/* The theme switch. A quiet control: it should be findable, not prominent —
   nobody comes to a leaderboard to change its colours. */
.theme-toggle {
  /* A full tap target like every other control: this gets pressed on a
     phone, outdoors, sometimes in gloves. */
  min-height: var(--tap); min-width: var(--tap);
  padding: 0 .4rem; margin: -.4rem 0 -.4rem .25rem;
  border: 1px solid transparent; background: transparent;
  color: var(--ink-soft); line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
}
.theme-toggle:hover { color: var(--ink); border-color: var(--line); }
.theme-icon { font-size: 1.05rem; }

main { max-width: 960px; margin: 0 auto; padding: 1rem; }

/* Body links take the accent. The default blue is what made the page read as
   unstyled, and it is the one colour that cannot be the brand. */
main a { color: var(--accent); text-decoration: none; font-weight: 500; }
main a:hover { text-decoration: underline; }

h1 { font-size: 1.5rem; margin: .25rem 0 1rem; letter-spacing: -.02em; }
h2 { font-size: 1.15rem; margin: 1.5rem 0 .5rem; letter-spacing: -.01em; }
.muted { color: var(--ink-soft); font-size: .9rem; }

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1rem;
  margin-bottom: 1rem;
}
/* A card wrapping only a table has the table's own padding already.
   `overflow-x: auto` rather than `hidden`: on a phone the table is given a
   min-width so its columns stay legible (see the max-width:560px block), and
   `hidden` meant the table could not scroll inside its card — so it pushed the
   whole page sideways instead, which is the one thing the min-width was
   supposed to avoid.

   Both axes are named: `overflow-x: auto` with the Y axis left at `visible`
   computes to `auto` on both, and a Y scrollbar appears over the rounded
   corner. `hidden` rather than `clip`, because `clip` on a scroll container
   propagated to the root and let the PAGE scroll 212px sideways. */
.card.table-wrap { padding: 0; overflow-x: auto; overflow-y: hidden; }

label { display: block; font-weight: 600; margin-bottom: .35rem; }

input[type=text], input[type=date], input[type=datetime-local],
input[type=search], select, textarea {
  width: 100%;
  min-height: var(--tap);
  padding: .5rem .65rem;
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
}
select { appearance: auto; }
textarea { min-height: 3rem; }

button {
  min-height: var(--tap);
  padding: .5rem 1rem;
  font: inherit; font-weight: 600;
  border-radius: var(--radius-sm);
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--ink);
  cursor: pointer;
}
button.primary {
  background: var(--accent); color: var(--accent-ink); border-color: transparent;
  box-shadow: var(--shadow);
}
button:not(:disabled):hover { border-color: var(--accent); }
button.primary:not(:disabled):hover { filter: brightness(1.06); }
button:disabled { opacity: .5; cursor: not-allowed; }

/* Irreversible actions look irreversible. The error tokens, because this is
   the same register as an error: something that cannot be taken back. */
button.danger {
  background: var(--err-bg); border-color: var(--err-line); color: var(--err-ink);
  font-weight: 600;
}
button.danger:not(:disabled):hover {
  background: var(--err-line); color: var(--surface); border-color: var(--err-line);
}
.danger-zone { border-color: var(--err-line); }
.danger-zone h2 { color: var(--err-ink); }

/* A join code is read off a screen and typed by someone in a helmet. */
.qr { display: block; width: 96px; height: 96px; }

/* Printing the join-code table is how the codes reach registration, so the
   page has to survive it: no navigation, no dark theme, nothing cut off. */
@media print {
  .topbar, .admin-bar, .btn-row, .danger-zone { display: none !important; }
  .card { border: 0; box-shadow: none; break-inside: avoid; }
  table { break-inside: auto; }
  tr { break-inside: avoid; }
}

.join-code {
  font-size: 1.05rem; font-weight: 700; letter-spacing: .12em;
  background: var(--accent-soft); color: var(--accent);
  padding: .15rem .45rem; border-radius: var(--radius-sm);
}
.btn-row { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: 1rem; }

/* Route picker ---------------------------------------------------------- */
.picker { position: relative; }
.picker-list {
  max-height: 16rem; overflow-y: auto; margin-top: .4rem;
  border: 1px solid var(--line); border-radius: var(--radius-sm);
  background: var(--surface); box-shadow: var(--shadow-lg);
}
.picker-list[hidden] { display: none !important; }
.picker-group-label {
  padding: .35rem .65rem; font-size: .78rem; font-weight: 700;
  text-transform: uppercase; letter-spacing: .04em;
  color: var(--ink-soft); background: var(--bg);
  position: sticky; top: 0;
}
.picker-option {
  padding: .6rem .65rem; min-height: var(--tap);
  border-top: 1px solid var(--line); cursor: pointer;
  display: flex; justify-content: space-between; gap: .75rem;
}
.picker-option:hover, .picker-option[aria-selected='true'] {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.picker-option .meta { color: var(--ink-soft); font-size: .85rem; white-space: nowrap; }

/* Route facts ----------------------------------------------------------- */
.facts { display: flex; flex-wrap: wrap; gap: .5rem 1.25rem; margin: .75rem 0; }
.facts div { font-size: .92rem; }
.facts b { font-variant-numeric: tabular-nums; }
.pitch-list { margin: .5rem 0 0; padding-left: 1.1rem; font-size: .9rem; color: var(--ink-soft); }

/* Member rows ----------------------------------------------------------- */
.members { display: grid; gap: .5rem; }
.member-row {
  display: grid;
  grid-template-columns: 2rem minmax(5rem, 1fr) minmax(9rem, 1.4fr) 4.5rem;
  gap: .5rem; align-items: center;
}
.member-row .slot { color: var(--ink-soft); font-size: .85rem; text-align: center; }
.member-row .pts { text-align: right; font-variant-numeric: tabular-nums; font-weight: 600; }
@media (max-width: 460px) {
  .member-row { grid-template-columns: 1.6rem 1fr 4.2rem; grid-template-areas: 'slot name pts' '. type type'; }
  .member-row .slot { grid-area: slot; }
  .member-row .name { grid-area: name; }
  .member-row select { grid-area: type; }
  .member-row .pts { grid-area: pts; }
}

/* Totals ---------------------------------------------------------------- */
.totals {
  display: flex; flex-wrap: wrap; gap: 1.25rem;
  padding: .75rem 0; border-top: 1px solid var(--line); margin-top: .75rem;
}
.totals .big { font-size: 1.3rem; font-weight: 700; font-variant-numeric: tabular-nums; }

/* Messages -------------------------------------------------------------- */
.err, .warn {
  padding: .6rem .75rem; border-radius: var(--radius-sm);
  margin: .6rem 0; font-size: .92rem;
}
.err  { background: var(--err-bg);  border: 1px solid var(--err-line);  color: var(--err-ink); }
.warn { background: var(--warn-bg); border: 1px solid var(--warn-line); }
.err[hidden], .warn[hidden] { display: none !important; }

/* Tables ---------------------------------------------------------------- */
.table-wrap { overflow-x: auto; }
table { width: 100%; border-collapse: collapse; }
th, td { padding: .55rem .5rem; text-align: left; border-bottom: 1px solid var(--line); }
th { font-size: .8rem; text-transform: uppercase; letter-spacing: .03em; color: var(--ink-soft); }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
/* Scores are read by comparing them down a column, so the digits must not
   shift width between rows. Negative tracking on the large ones only. */
.rank, .pts, .big { font-variant-numeric: tabular-nums; letter-spacing: -.01em; }
tbody tr:hover { background: color-mix(in srgb, var(--accent) 6%, transparent); }
tfoot td { border-top: 2px solid var(--line); border-bottom: 0; font-weight: 600; }
thead th { background: var(--surface-2); }
thead th:first-child { border-top-left-radius: var(--radius); }
thead th:last-child { border-top-right-radius: var(--radius); }

.rank { font-weight: 700; font-variant-numeric: tabular-nums; }
/* First place gets the amber, because gold already means what we would
   otherwise have to explain. */
tbody tr:first-child .rank { color: var(--hi-ink); font-size: 1.1rem; }
tbody tr:first-child td { background: var(--hi-soft); }
.member-breakdown { font-size: .85rem; color: var(--ink-soft); }

.grid-teams { display: grid; gap: .75rem; grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr)); }
.team-card {
  display: block; padding: .85rem; text-decoration: none; color: inherit;
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
}
.team-card:hover { border-color: var(--accent); }
.team-card .pts { font-size: 1.2rem; font-weight: 700; font-variant-numeric: tabular-nums; }

.toast {
  position: fixed; left: 50%; transform: translateX(-50%);
  bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
  background: var(--ink); color: var(--bg);
  padding: .7rem 1rem; border-radius: 999px;
  display: flex; align-items: center; gap: .75rem; z-index: 50;
}
.toast[hidden] { display: none !important; }
.toast button { min-height: 2rem; padding: .2rem .7rem; background: transparent; color: var(--bg); border-color: currentColor; }

/* On a phone the logbook's route names wrap to five lines and the table
   becomes unreadable. Let it scroll sideways instead, with the route column
   wide enough to stay legible. */
@media (max-width: 560px) {
  .table-wrap table { min-width: 34rem; }
  .table-wrap td:nth-child(2) { min-width: 14rem; }
}

/* Location row: text field with the GPS button beside it. The button keeps a
   full tap target but does not steal width from the input on a phone. */
.loc-row { display: flex; gap: .5rem; align-items: stretch; }
.loc-row input { flex: 1 1 auto; min-width: 0; }
.loc-row button { flex: 0 0 auto; white-space: nowrap; }
.err-text { color: var(--err-ink); }

/* Admin console ---------------------------------------------------------- */
.admin-bar {
  display: flex; justify-content: space-between; align-items: center;
  gap: 1rem; flex-wrap: wrap;
  padding: .5rem .75rem; margin-bottom: 1rem;
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); font-size: .92rem;
}
.admin-bar a { color: var(--ink); }
form.inline { display: inline; }

/* A row of per-user controls — promote, disable, set a password. Wraps rather
   than forcing the table wider, because this is the widest row in the app and
   it is still read on a laptop at a crag. */
.row-actions {
  display: flex; flex-wrap: wrap; gap: .4rem; align-items: center;
}
.row-actions form.inline { display: flex; gap: .3rem; align-items: center; }
.row-actions input[type="password"] { width: 11rem; margin: 0; }
.row-actions .small { flex-basis: 100%; }
.small { font-size: .82rem; line-height: 1.35; }
form.inline button { min-height: 2rem; padding: .2rem .6rem; font-size: .85rem; }
.card.narrow { max-width: 26rem; }
.checkline {
  display: flex; align-items: center; gap: .5rem;
  font-weight: 400; margin: .75rem 0;
}
.checkline input { width: auto; min-height: auto; }
.card h2:first-child { margin-top: 0; }
/* Inputs inside admin forms get the same full-width treatment as elsewhere;
   an email box half an inch wide invites typos. */
.admin-bar + * input[type=email],
.card form input[type=email],
.card form input[type=text],
.card form input[type=password] { width: 100%; }

/* Export: two formats, two scopes — laid out so the choice is obvious. */
.export-grid {
  display: grid; gap: 1rem;
  grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr));
  margin: .75rem 0;
}
.export-grid .btn-row { margin-top: .35rem; }
input[type=file] {
  width: 100%; padding: .5rem; font: inherit;
  border: 1px dashed var(--line); border-radius: var(--radius-sm); background: var(--surface);
}
hr { border: 0; border-top: 1px solid var(--line); margin: 1.25rem 0; }

/* Scoring explainer ------------------------------------------------------ */
.formula {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: .75rem .9rem;
  margin: .75rem 0;
  font: .92rem/1.6 ui-monospace, SFMono-Regular, Menlo, monospace;
  overflow-x: auto;
  white-space: pre;
}
.card ul { margin: .5rem 0; padding-left: 1.2rem; }
.card li { margin: .35rem 0; }
.card p { margin: .6rem 0; }

/* The nav grows on this page; let it wrap rather than overflow on a phone. */
.topbar nav { flex-wrap: wrap; row-gap: .25rem; }
@media (max-width: 560px) {
  .topbar { flex-wrap: wrap; }
  .topbar nav { margin-left: 0; width: 100%; gap: .85rem; font-size: .92rem; }
}

/* Route list ------------------------------------------------------------- */
.area-group h2 { display: flex; align-items: center; gap: .6rem; margin-top: 0; }
.badge {
  font-size: .78rem; font-weight: 600;
  padding: .1rem .5rem; border-radius: 999px;
  background: var(--accent-soft);
  color: var(--accent);
}
.area-group[hidden] { display: none !important; }
/* Linked to from the scoring page by #areaKey. The topbar is sticky, so an
   anchored heading would land underneath it without this. */
.area-group[id] { scroll-margin-top: 4.5rem; }
tr[data-search][hidden] { display: none !important; }

/* Area bonus reasoning --------------------------------------------------- */
/* "Why 35%?" under an area heading on the routes page. Collapsed, because the
   facts are the answer to a question most readers are not asking. */
.area-why { margin: -.2rem 0 .8rem; }
.area-why > summary {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .35rem 0; cursor: pointer; list-style: none;
  font-size: .9rem; color: var(--ink-soft);
}
.area-why > summary::-webkit-details-marker { display: none; }
.area-why > summary::before {
  content: '▸'; font-size: .8rem; transition: transform .15s;
}
.area-why[open] > summary::before { transform: rotate(90deg); }
.area-why > summary:hover { color: var(--ink); }
.area-name { font-weight: 600; flex: 1 1 auto; }
.area-facts {
  display: grid; gap: .3rem 1.5rem;
  grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr));
  padding: .25rem .25rem .9rem 1.4rem;
  margin: 0;
}
.area-facts .muted { display: inline-block; min-width: 10rem; }

/* Shown only while the table is sorted by something other than its own order,
   so the rank column reading 9, 6, 11 has an explanation beside it. */
.sort-note { display: none; margin: -.4rem 0 .6rem; }
body:has(table.is-resorted) .sort-note { display: block; }

/* Charts -----------------------------------------------------------------
   Server-rendered SVG, so there is no library to load and nothing to lay out
   twice. Colours come from the theme tokens, so both palettes work without a
   second set of charts. */
.charts {
  display: grid; gap: 1.6rem 2rem;
  grid-template-columns: repeat(auto-fit, minmax(19rem, 1fr));
}
.chart { margin: 0; min-width: 0; }
/* The grade spread reads as a scale, so it gets the full width when there is
   room rather than being squeezed beside another chart. */
.chart-wide { grid-column: 1 / -1; }
.chart figcaption {
  font-weight: 600; font-size: .95rem; margin-bottom: .5rem;
}
.chart svg { width: 100%; height: auto; overflow: visible; }
.chart-note { font-size: .82rem; margin: .4rem 0 0; }

.c-bar { fill: var(--accent); }
/* The first bar is the one being compared against, so it carries the
   highlight the leader row uses in the table. */
.chart .c-bar:first-of-type,
.chart g:first-child .c-bar { fill: var(--accent); }
.c-label { font-size: 11px; fill: var(--ink-soft); }
.c-value {
  font-size: 11px; fill: var(--ink); font-variant-numeric: tabular-nums;
}

.race-summary {
  margin: -.2rem 0 .9rem; font-size: 1rem;
}

/* Sortable tables ---------------------------------------------------------
   The heading becomes a button so it is reachable by keyboard; it keeps the
   heading's own look rather than turning into a control, because a row of
   buttons across the top of a table reads as a toolbar. */
th:has(.th-sort) { padding: 0; }

/* The button replaces the cell's padding with its own, identical, so a heading
   still lines up with the column beneath it.

   Not a flex container: the arrow is an inline element in normal text flow,
   which is what keeps a right-aligned heading's right edge exactly on the
   figures' right edge. Every attempt to place it with flex — padding, absolute
   positioning, row-reverse with a negative margin — moved the label by the
   arrow's width in one state or another. */
.th-sort {
  display: block; width: 100%; min-height: var(--tap);
  padding: .55rem .5rem;
  background: none; border: 0; cursor: pointer;
  font: inherit; color: inherit;
  font-size: .8rem; text-transform: uppercase; letter-spacing: .03em;
  text-align: left;
}
.num .th-sort { text-align: right; padding-left: 1.2rem; }
.th-sort:hover { color: var(--ink); background: var(--surface-2); }

/* The arrow is drawn beside the label without taking space in the line, so the
   label does not shift as the sort moves between columns and an inactive
   column's faint arrow costs no width.
   The BUTTON is the positioning context: anchoring to the label instead left
   the arrow with no positioned ancestor — it was laid out against the page and
   three of them ended up floating 200px to the right of the table, which is
   what made the page scrollable sideways on a phone. */
.th-sort { position: relative; }
.th-arrow {
  position: absolute; top: 50%; transform: translateY(-50%);
  font-size: .9em; opacity: .35;
}
/* Pinned inside the button, on the edge the column is NOT aligned to, so it
   never sits between the label and the figures it heads. A right-aligned
   heading gets extra padding on that side so a long label cannot run into it. */
.num .th-sort .th-arrow { left: .4rem; }
th:not(.num) .th-sort .th-arrow { right: .4rem; }
.th-arrow::before { content: '\2195'; }
th.sorted-asc .th-arrow::before { content: '\2191'; }
th.sorted-desc .th-arrow::before { content: '\2193'; }

/* Event hero -------------------------------------------------------------
   The title sits ON the photo rather than above it, so the page opens with the
   place rather than with a heading and a picture of one.

   Two things make that safe to read. The gradient is a scrim under the text
   only — a full-image overlay would mute the photo for no benefit — and it is
   dark enough that white type clears AA against the brightest part of this
   image, which is the hazy sky on the left where the title sits.

   object-position favours the right: the climbers are on the right-hand edge,
   and a centred crop loses them first at phone width. */
.event-hero {
  position: relative;
  margin: 0 0 1rem;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface-2);
  box-shadow: var(--shadow);
}
.event-hero img {
  display: block;
  width: 100%;
  height: clamp(13rem, 34vw, 22rem);
  object-fit: cover;
  object-position: 68% 50%;
}
.event-hero h1 {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  margin: 0;
  padding: 2.5rem 1rem .9rem;
  color: #fff;
  font-size: clamp(1.4rem, 1rem + 2.2vw, 2.2rem);
  line-height: 1.15;
  letter-spacing: -.02em;
  /* Not a token: this scrim is over a photograph, not over the page ground,
     so it must stay the same in both themes. */
  background: linear-gradient(to top,
    rgb(0 0 0 / .72), rgb(0 0 0 / .45) 45%, rgb(0 0 0 / 0));
  text-shadow: 0 1px 3px rgb(0 0 0 / .5);
}

/* The current photo on the admin console, at the shape the page crops it to. */
.hero-preview {
  display: block; width: 100%; max-width: 32rem;
  height: 9rem; object-fit: cover; object-position: 68% 50%;
  border-radius: var(--radius-sm); border: 1px solid var(--line);
  margin: .3rem 0 .8rem;
}

/* Team check-in ---------------------------------------------------------- */
.whereabouts {
  display: flex; align-items: center; gap: .5rem; flex-wrap: wrap;
  padding: .55rem .7rem; margin-bottom: .9rem;
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--surface-2);
}
/* "Here now" is a positive, present-tense state, so it takes the amber that
   first place uses rather than another shade of the accent. */
.badge.ok {
  background: var(--hi-soft);
  color: var(--hi-ink);
}

/* The leaderboard's "Last seen" column: place above, time below, so the
   column stays narrow enough not to push the numbers off a phone screen. */
.seen { display: block; white-space: nowrap; }
.seen-when { display: block; font-size: .78rem; white-space: nowrap; }
/* The date is only interesting on a multi-day event; the time is what a
   coordinator reads, so keep the column from growing a third line. */
td[data-testid^="lb-seen"] { min-width: 8.5rem; }

/* Guidebook links. A quiet secondary action beside a heading: useful, but
   never competing with the area name itself. */
.guide-link {
  font-size: .8rem; font-weight: 500; white-space: nowrap;
  vertical-align: middle;
}
