/* Basalt dashboard — themeable.
   The look is chosen by data-theme / data-mode on <body> (see themes.css and
   the theme/mode resolution in config.py / routes.py). Everything here reads
   from tokens, so a theme is a token swap, not a component rewrite. The :root
   values below are the original "basalt & tube-glow" dark palette, kept as the
   default and the ultimate fallback if themes.css somehow doesn't load;
   themes.css supplies every other theme/mode combination, including basalt
   light. Dark is the default: this runs on a projector in a dim hall. */

:root {
  --font-sans: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-mono: ui-monospace, "SF Mono", "Cascadia Code", "Roboto Mono", Menlo, Consolas, monospace;
  --font-display: var(--font-mono);   /* hero score + big numbers; themes give this a real face */

  --bg: #0b0e13;
  --surface: #141922;
  --surface-2: #1b222c;
  --border: #263242;
  --border-soft: #1b2430;
  --ink: #e8eef6;
  --ink-2: #9fb0c0;
  --ink-3: #62717f;
  --accent: #ffab3d;
  --accent-dim: #c9812c;
  --accent-ink: #1a1206;
  /* validated two-series chart colors (dataviz checks pass on dark surface) */
  --series-qso: #dc7414;
  --series-pts: #14ac90;
  /* the three normalized modes, one fixed hue each, never reassigned.
     Checked against #1b222c: lightness band, chroma floor, protan/deutan
     separation and contrast all pass. */
  --mode-cw: #dc7414;
  --mode-ssb: #14ac90;
  --mode-digi: #4e63c9;
  --axis: rgba(159, 176, 192, 0.16);
  --map-grat: rgba(159, 176, 192, 0.08);

  /* shape + elevation — themes reshape cards through these */
  --radius: 12px;
  --radius-sm: 6px;
  --card-border: 1px solid var(--border);
  --card-shadow: none;
  --track-bg: var(--border-soft);

  /* the rail carries two columns of cards, so it needs real width; the map
     keeps whatever is left, which is still the largest thing on screen */
  --rail: clamp(560px, 46vw, 960px);
  --header-h: 76px;
}

* { box-sizing: border-box; }

html, body { height: 100%; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-sans);
  -webkit-font-smoothing: antialiased;
}

.app { display: flex; flex-direction: column; height: 100vh; }

/* ---------- brand and clock, floating over the map ---------- */
/* There is no top bar: every row of chrome is a row the numbers don't get. */
.brand {
  position: absolute; top: 16px; left: 16px; z-index: 500;
  display: flex; align-items: center; gap: 12px; pointer-events: none;
}
.hexmark { flex: none; width: 34px; height: 42px; color: var(--accent); }
.brand-name {
  font-size: 1.15rem; font-weight: 600; letter-spacing: 0.26em;
  text-transform: lowercase; color: var(--ink-2);
}

.map-clock {
  position: absolute; top: 16px; right: 16px; z-index: 500;
  display: flex; align-items: flex-start; gap: 20px; pointer-events: none;
  font-family: var(--font-mono); text-align: right;
}
.clock { line-height: 1.15; }
.clock .utc { font-size: 1.4rem; font-weight: 600; letter-spacing: 0.03em; font-variant-numeric: tabular-nums; }
.clock .utc small { font-size: 0.62rem; color: var(--ink-3); margin-left: 5px; letter-spacing: 0.1em; }
.clock .local { font-size: 0.78rem; color: var(--ink-3); }

.countdown { line-height: 1.15; }
.countdown .cd-label {
  display: block; font-size: 0.62rem; letter-spacing: 0.14em;
  text-transform: uppercase; color: var(--ink-3);
}
.countdown .cd-value { font-size: 1.15rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.countdown.before .cd-value { color: var(--ink-2); }
.countdown.over .cd-value { color: var(--ink-3); }

.warning-banner {
  padding: 8px 22px; font-size: 0.82rem; font-weight: 600;
  background: color-mix(in srgb, var(--accent) 14%, var(--surface)); color: var(--accent);
  border-bottom: 1px solid color-mix(in srgb, var(--accent) 34%, var(--surface));
}
.warning-banner span + span { margin-left: 22px; }

/* ---------- main split ---------- */
.main { display: grid; grid-template-columns: 1fr auto var(--rail); flex: 1; min-height: 0; }

/* drag to trade map width for rail width; double-click restores the default */
.rail-resizer {
  width: 7px; cursor: col-resize; background: var(--border);
  border-left: 1px solid var(--bg); border-right: 1px solid var(--bg);
  transition: background 0.15s;
}
.rail-resizer:hover, .rail-resizer.dragging { background: var(--accent-dim); }
body.resizing { cursor: col-resize; user-select: none; }
/* Leaflet would otherwise swallow the drag once the pointer crosses the map */
body.resizing #map { pointer-events: none; }

.map {
  position: relative; min-height: 0;
  /* the grid here used to be a fixed 60px screen grid, which stayed put while
     the map moved under it; it's a real graticule in map.js now */
  background:
    radial-gradient(130% 100% at 32% 8%, rgba(255,255,255,0.025), transparent 62%),
    var(--bg);
  overflow: hidden;
}
#map { position: absolute; inset: 0; background: transparent; }
#map .leaflet-tile-pane { display: none; } /* no online tiles, ever */
.leaflet-container { background: transparent; font-family: var(--font-mono); }
.leaflet-control-zoom a {
  background: var(--surface); color: var(--ink-2); border-color: var(--border) !important;
}
.leaflet-control-zoom a:hover { background: var(--surface-2); color: var(--ink); }
.leaflet-control-attribution {
  background: rgba(0,0,0,0.35); color: var(--ink-3); font-size: 0.68rem;
}
.leaflet-control-attribution a { color: var(--ink-2); }
/* Country names. Sized down and letterspaced so they read as map furniture
   rather than data; a worked country's name steps up to full ink, since the
   worked ones are what the map is for. No pointer events - the QSO markers
   underneath own every tooltip. */
.country-label { width: 0 !important; height: 0 !important; pointer-events: none; }
/* Leaflet owns the marker div's own transform for positioning, so the centring
   happens on an inner span instead of fighting it. */
.country-label span {
  position: absolute;
  transform: translate(-50%, -50%);
  color: var(--ink-3);
  font-family: var(--font-sans);
  font-size: 0.62rem;
  font-weight: 500;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  white-space: nowrap;
  /* the outline keeps a name legible where it crosses a coast or a QSO dot */
  text-shadow:
    -1px -1px 0 var(--bg), 1px -1px 0 var(--bg),
    -1px 1px 0 var(--bg), 1px 1px 0 var(--bg);
}
.country-label.worked span { color: var(--ink); font-weight: 600; }

.station-marker {
  width: 14px; height: 14px; border-radius: 50%;
  background: var(--series-pts); border: 2px solid var(--bg);
  box-shadow: 0 0 0 3px rgba(255,255,255,0.15);
}

/* ---------- stats rail ---------- */
/* Everything has to fit without scrolling: this sits unattended on a big
   screen, and nobody is there to reach for a scrollbar. The last row takes
   the slack so the cards grow into whatever height is left. */
.rail {
  background: var(--surface); padding: 16px;
  display: grid; gap: 14px;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  /* min-content, not 0: the chart row is the only one that flexes, and a
     0 floor let it collapse under the cards' own minimum while the cards
     kept their size and painted over the bonus card below. Held at
     min-content, a window too short to hold everything overflows the rail
     -- visibly, and scrollable -- instead of stacking cards on top of
     each other. The @media (max-height) steps below buy back the height
     that makes overflow unnecessary in the first place. */
  grid-template-rows: auto auto minmax(min-content, 1fr);
  overflow-x: hidden; overflow-y: auto;
}
/* the hero, the tile strip and the footer run the full width; the four
   chart cards pair up two to a row */
.rail-wide { grid-column: 1 / -1; }

.card {
  border: var(--card-border); border-radius: var(--radius);
  background: var(--surface-2); padding: 16px 18px; box-shadow: var(--card-shadow);
}
.card-label {
  font-size: 0.74rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink-2); font-weight: 600;
}

/* hero score + progress toward last year */
/* the card is a size container so the big number can be sized against its own
   width -- a proportional theme face and a six-figure score would otherwise
   overrun a narrow rail */
.score-card { display: flex; flex-direction: column; gap: 16px; padding: 20px 20px 18px; container-type: inline-size; }
.score-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; }
.score-value {
  font-family: var(--font-display); font-weight: 700; color: var(--accent);
  font-size: clamp(2.4rem, 13cqi, 5rem); line-height: 0.9; letter-spacing: -0.01em; font-variant-numeric: tabular-nums;
}
.score-delta {
  font-family: var(--font-mono); font-size: 0.95rem; color: var(--ink-3);
  display: flex; align-items: center; gap: 6px; white-space: nowrap;
}
.score-delta .up { color: var(--accent); }
.target { display: flex; flex-direction: column; gap: 9px; }
.target[hidden] { display: none; }
.target-bar {
  position: relative; height: 12px; border-radius: 7px;
  background: var(--bg); border: 1px solid var(--border); overflow: hidden;
}
.target-fill { height: 100%; background: var(--accent); border-radius: 6px 0 0 6px; transition: width 0.6s ease; }
.target-row { display: flex; align-items: baseline; justify-content: space-between; font-size: 0.9rem; }
.to-beat { color: var(--ink-2); }
.to-beat strong {
  font-family: var(--font-mono); color: var(--ink); font-size: 1.15rem; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.to-beat.ahead strong { color: var(--series-pts); }
.last-year {
  font-family: var(--font-mono); color: var(--ink-3); font-size: 0.9rem;
  letter-spacing: 0.02em; font-variant-numeric: tabular-nums;
}

/* four numbers across, then the rate and the by-mode breakdown taking two
   columns each on the row below -- any narrower and five-figure totals clip */
.tiles { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 14px; }
.tile {
  border: var(--card-border); border-radius: var(--radius); background: var(--surface-2);
  padding: 14px 16px; display: flex; flex-direction: column; gap: 6px; box-shadow: var(--card-shadow);
  container-type: inline-size;   /* so .t-value can scale to the tile's width */
}
.tile .t-label {
  font-size: 0.72rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-3); font-weight: 600;
}
.tile .t-value {
  font-family: var(--font-display); font-weight: 600; color: var(--ink);
  font-size: clamp(1.15rem, 22cqi, 2.4rem); line-height: 1; font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.dxcc-modes { display: flex; flex-wrap: wrap; gap: 6px 14px; margin-top: auto; }
.dxcc-modes .dm { display: flex; align-items: baseline; gap: 5px; }
.dxcc-modes .dm-label { font-size: 0.72rem; color: var(--ink-3); letter-spacing: 0.06em; }
.dxcc-modes .dm-value {
  font-family: var(--font-mono); font-weight: 600; color: var(--ink-2);
  font-size: 1.3rem; font-variant-numeric: tabular-nums;
}
.tile.rate .t-value { color: var(--accent); }
.tile .t-unit { font-size: 0.9rem; color: var(--ink-3); }
.tile .t-sub { color: var(--ink-3); font-weight: 500; letter-spacing: 0.06em; }
.tile .t-foot {
  font-family: var(--font-mono); font-size: 0.78rem; color: var(--ink-3);
  font-variant-numeric: tabular-nums; margin-top: auto;
}
.tile.span2 { grid-column: span 2; }
.tile.rate { position: relative; }
/* the 12-hour sparkline rides under the 60-minute number rather than taking
   a card of its own */
.tile.rate .spark { margin-top: auto; }
.tile.rate .spark svg { max-height: 46px; }

/* legend row shared by chart cards */
.graph-card { display: flex; flex-direction: column; }
/* The chart card, and only it, gets a floor and a clip. It stretches to fill
   whatever height it is handed, and squeezed below the floor it used to draw
   its gridlines and axis straight across the card underneath -- overflow
   hidden makes it stop at its own edge instead. The bars card is a list:
   clipping it would cut bands off the bottom, and zeroing its contribution
   to the row would let it overlap what follows, so it keeps its own height
   and the rail scrolls if the window really is too short. */
.graph-card:has(.chart) { min-height: 190px; overflow: hidden; }
.graph-card .card-label { margin-bottom: 12px; display: flex; flex-wrap: wrap; gap: 4px 14px; justify-content: space-between; align-items: center; }
.legend { display: flex; flex-wrap: wrap; gap: 6px 16px; }
.legend span { display: flex; align-items: center; gap: 7px; font-size: 0.82rem; letter-spacing: 0.02em; color: var(--ink-2); text-transform: none; font-weight: 500; font-variant-numeric: tabular-nums; }
.swatch { width: 14px; height: 3px; border-radius: 2px; flex: none; }
.legend b { font-family: var(--font-mono); color: var(--ink); font-weight: 600; }

/* rate sparkline */
.spark { width: 100%; margin-top: 10px; }
.spark svg { display: block; width: 100%; height: auto; overflow: visible; }
.spark .s-area { fill: var(--series-qso); fill-opacity: 0.14; }
.spark .s-line { fill: none; stroke: var(--series-qso); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; vector-effect: non-scaling-stroke; }
.spark .s-end { fill: var(--accent); }

/* over-time chart -- fills whatever height the card has, with the drawing
   code reading that size so the chart isn't stretched to fit */
.chart { position: relative; width: 100%; flex: 1; min-height: 140px; }
.chart svg { display: block; width: 100%; height: 100%; overflow: visible; }
.chart .grid line { stroke: var(--axis); stroke-width: 1; }
.chart .axis-text { fill: var(--ink-3); font-family: var(--font-mono); font-size: 11px; font-variant-numeric: tabular-nums; }
.chart .area-q { fill: var(--series-qso); fill-opacity: 0.12; }
.chart .line-q { fill: none; stroke: var(--series-qso); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.chart .line-p { fill: none; stroke: var(--series-pts); stroke-width: 2; stroke-linejoin: round; stroke-linecap: round; }
.chart .end-q { fill: var(--accent); }
.chart .end-p { fill: var(--series-pts); }
.chart .end-ring { fill: var(--surface-2); }
.chart .score-area { fill: var(--accent); fill-opacity: 0.12; }
.chart .score-line { fill: none; stroke: var(--accent); stroke-width: 2.2; stroke-linejoin: round; stroke-linecap: round; }
.chart .score-end { fill: var(--accent); }
.chart .target-line { stroke: var(--ink-3); stroke-width: 1; stroke-dasharray: 4 3; }
.chart .target-label { fill: var(--ink-3); letter-spacing: 0.06em; }
.chart .crosshair { stroke: var(--ink-3); stroke-width: 1; stroke-dasharray: 3 3; opacity: 0; }
.chart .hit { fill: transparent; }
.chart .hover-dot { opacity: 0; }
.tooltip {
  position: absolute; pointer-events: none; opacity: 0; transform: translate(-50%, -50%);
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 8px 10px; font-family: var(--font-mono); font-size: 0.78rem; color: var(--ink);
  white-space: nowrap; box-shadow: 0 6px 20px rgba(0,0,0,0.35); z-index: 5; line-height: 1.5;
}
.tooltip .tt-time { color: var(--ink-3); font-size: 0.72rem; margin-bottom: 3px; }
.tooltip .tt-row { display: flex; align-items: center; gap: 7px; }
.tooltip .tt-row .dot { width: 8px; height: 8px; border-radius: 50%; flex: none; }
.tooltip .tt-row b { margin-left: auto; padding-left: 12px; }

/* bands / modes bars */
.bd-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; margin-top: 4px; align-items: start; }
/* both columns start at the top; the mode rows line up with the band rows
   rather than spreading down the card */
.bd-col { display: flex; flex-direction: column; gap: 11px; }
.bd-col > div:last-child { display: flex; flex-direction: column; gap: 11px; }
.bd-sub {
  font-size: 0.7rem; letter-spacing: 0.15em; text-transform: uppercase;
  color: var(--ink-3); font-weight: 600; margin-bottom: 1px;
}
.bar-row { display: grid; grid-template-columns: 40px 1fr auto; align-items: center; gap: 10px; }
.bar-label { font-family: var(--font-mono); font-size: 0.92rem; color: var(--ink-2); }
.bar-track { height: 13px; background: var(--track-bg); border-radius: 7px; overflow: hidden; }
.bar-fill { display: block; height: 100%; background: var(--series-qso); border-radius: 7px; min-width: 3px; transition: width 0.5s ease; }
/* a band's bar split by mode; the 2px surface gaps keep the segments
   readable as separate quantities rather than one blended bar */
.bar-track.stacked { display: flex; gap: 2px; background: transparent; }
.bar-seg { height: 100%; min-width: 3px; transition: width 0.5s ease; }
.bar-seg:first-child { border-radius: 7px 0 0 7px; }
.bar-seg:last-child { border-radius: 0 7px 7px 0; }
.bar-seg:only-child { border-radius: 7px; }
/* mode legend on the bands card */
.mode-legend { display: flex; flex-wrap: wrap; gap: 5px 14px; }
.mode-legend span { display: flex; align-items: center; gap: 6px; font-size: 0.75rem; color: var(--ink-2); letter-spacing: 0.04em; text-transform: none; }
.mode-legend i { width: 10px; height: 10px; border-radius: 3px; display: block; }
.bar-val {
  font-family: var(--font-mono); font-size: 0.95rem; color: var(--ink);
  font-variant-numeric: tabular-nums; text-align: right; white-space: nowrap;
}
.bar-val .pct { color: var(--ink-3); font-size: 0.8rem; margin-left: 5px; }

/* recent QSOs */
.qso-list { display: flex; flex-direction: column; margin-top: 2px; }
.qso-row {
  display: grid; grid-template-columns: 52px 1fr 48px 46px 18px;
  align-items: center; gap: 10px; padding: 9px 2px;
  border-top: 1px solid var(--border-soft);
}
.qso-row:first-child { border-top: none; }
.qso-time { font-family: var(--font-mono); font-size: 0.85rem; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.qso-call { font-family: var(--font-mono); font-size: 1.1rem; font-weight: 600; color: var(--ink); letter-spacing: 0.02em; }
.qso-band { font-family: var(--font-mono); font-size: 0.88rem; color: var(--ink-2); text-align: right; font-variant-numeric: tabular-nums; }
.qso-mode { font-size: 0.72rem; letter-spacing: 0.06em; color: var(--ink-3); text-transform: uppercase; font-weight: 600; text-align: center; }
.qso-mult { color: var(--accent); font-size: 0.95rem; text-align: center; line-height: 1; }
.qso-mult[data-mult="0"] { visibility: hidden; }
@keyframes newflash {
  from { background: color-mix(in srgb, var(--accent) 16%, transparent); }
  to   { background: transparent; }
}
.qso-row.fresh { animation: newflash 2.6s ease-out 1; }
@media (prefers-reduced-motion: reduce) { .qso-row.fresh { animation: none; } }

.rail-foot {
  margin-top: auto; font-size: 0.72rem; color: var(--ink-3);
  letter-spacing: 0.08em; text-transform: uppercase;
  display: flex; align-items: center; gap: 9px; padding: 4px 2px;
  /* the theme switcher floats over the bottom-right corner; keep the resync
     button clear of it */
  padding-right: 52px;
}
.rail-foot .updot { width: 7px; height: 7px; border-radius: 50%; background: var(--accent-dim); }
.rail-foot.stale .updot { background: #d9534f; }
.rail-foot.stale { color: #d9534f; }
.rail-foot .feed { letter-spacing: 0.08em; }
.rail-foot .feed.ok { color: var(--series-pts); }
.rail-foot .feed.idle { color: var(--ink-3); }
.rail-foot .feed.bad { color: #d9534f; }
.rail-foot .stations { letter-spacing: 0.08em; }
.rail-foot .stations.ok { color: var(--ink-3); }
.rail-foot .stations.bad { color: #d9534f; }
.rail-foot .relaystate { letter-spacing: 0.08em; }
.rail-foot .relaystate.ok { color: var(--ink-3); }
.rail-foot .relaystate.idle { color: var(--ink-3); }
.rail-foot .relaystate.bad { color: #d9534f; }
.rail-foot .resync {
  margin-left: auto; font: inherit; letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-2); background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--radius-sm); padding: 3px 9px; cursor: pointer;
}
.rail-foot .resync:hover:not(:disabled) { color: var(--ink); border-color: var(--accent-dim); }
.rail-foot .resync:disabled { opacity: 0.5; cursor: default; }

/* laptop-sized screens can't carry two columns of cards in the rail */
@media (max-width: 1500px) {
  .rail { grid-template-columns: minmax(0, 1fr); }
  .graph-card { grid-column: 1 / -1; }
  .tiles { grid-template-columns: repeat(4, minmax(0, 1fr)); }
  .tile.span2 { grid-column: span 2; }
}

@media (max-width: 900px) {
  .app { height: auto; min-height: 100vh; }
  .main { grid-template-columns: minmax(0, 1fr); }
  .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .map { order: 2; border-right: none; border-top: 1px solid var(--border); min-height: 55vh; }
  .rail { order: 1; }
  .contest { display: none; }
  .score-value { font-size: 3.6rem; }

  /* the rail stacks above the map, so the handle has nothing to drag */
  .rail-resizer { display: none; }
  .brand, .map-clock { top: 10px; }
  .brand { left: 10px; }
  .map-clock { right: 10px; gap: 14px; }
  .clock .utc { font-size: 1.05rem; }
}

@media (max-width: 520px) {
  /* phone in a pocket at the far end of the field */
  .clock .local, .brand-name { display: none; }
}

/* claimed total, with the HF figure and the bonus points beside it */
.score-split { display: flex; align-items: baseline; gap: 26px; flex-wrap: wrap; }
.score-parts { display: flex; gap: 22px; }
.score-parts span { display: flex; align-items: baseline; gap: 7px; }
.sp-label { font-size: 0.68rem; letter-spacing: 0.16em; text-transform: uppercase; color: var(--ink-3); font-weight: 600; }
.sp-value { font-family: var(--font-mono); font-size: 1.5rem; font-weight: 600; color: var(--ink-2); font-variant-numeric: tabular-nums; }

/* bonus points strip */
.bonus-card { padding: 12px 18px 14px; }
.bonus-list { display: flex; flex-wrap: wrap; gap: 10px 26px; margin-top: 8px; }
.bonus-item { display: flex; align-items: baseline; gap: 8px; }
.bi-label { font-size: 0.82rem; color: var(--ink-2); }
.bi-value { font-family: var(--font-mono); font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }

/* admin page -- its own header, since the dashboard's brand is positioned
   over the map and has nowhere to sit here */
/* ---------- relay login ---------- */
/* The one page on the public relay a visitor sees before the password. Built
   from the same tokens as everything else, so it looks like the dashboard it
   is guarding rather than a bolted-on gate. */
body.login-page {
  display: flex; align-items: center; justify-content: center;
  min-height: 100vh; padding: 20px;
}
.login-card { width: min(380px, 100%); display: grid; gap: 18px; padding: 26px 26px 28px; }
.login-brand { display: flex; align-items: center; gap: 12px; }
.login-brand .hexmark { width: 30px; height: 37px; }
.login-lead { margin: 0; color: var(--ink-2); font-size: 0.92rem; }
.login-lead span { color: var(--ink-3); }
.login-form { display: grid; gap: 9px; }
.login-label {
  font-size: 0.72rem; letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--ink-2); font-weight: 600;
}
.login-input {
  background: var(--bg); color: var(--ink); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 10px 12px; font: inherit;
  font-family: var(--font-mono); letter-spacing: 0.08em;
}
.login-input:focus { outline: none; border-color: var(--accent); }
.login-error { margin: 0; color: var(--accent); font-size: 0.85rem; }
.login-button {
  margin-top: 4px; font: inherit; font-weight: 600;
  letter-spacing: 0.1em; text-transform: uppercase; font-size: 0.82rem;
  background: var(--accent); color: var(--accent-ink); border: none;
  border-radius: var(--radius-sm); padding: 11px 14px; cursor: pointer;
}
.login-button:hover { background: var(--accent-dim); }

body.admin { display: flex; flex-direction: column; min-height: 100vh; }
.admin-head {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 20px; border-bottom: 1px solid var(--border); background: var(--surface);
}
.admin-head .hexmark { width: 26px; height: 32px; color: var(--accent); }
.admin-head .brand-name {
  font-size: 1.05rem; font-weight: 600; letter-spacing: 0.26em;
  text-transform: lowercase; color: var(--ink-2);
}
.admin-title {
  font-size: 0.95rem; color: var(--ink-2);
  border-left: 1px solid var(--border); padding-left: 14px;
}
.admin-head .resync { margin-left: auto; }
.admin-page { padding: 20px; display: grid; gap: 16px; max-width: 860px; }
.admin-table { width: 100%; border-collapse: collapse; margin-top: 6px; }
.admin-table td { padding: 7px 8px; border-top: 1px solid var(--border-soft); font-size: 0.92rem; }
.admin-table tr:first-child td { border-top: none; }
.admin-table .num { font-family: var(--font-mono); text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap; }
.admin-table .right { text-align: right; }
.admin-table .total td { border-top: 1px solid var(--border); font-weight: 700; }
.admin-table .muted, .admin-page .muted { color: var(--ink-3); }
.admin-add { display: flex; gap: 10px; margin-top: 12px; flex-wrap: wrap; }
.admin-page input[type="text"], .admin-page input[type="number"], .admin-page select {
  background: var(--bg); color: var(--ink); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 6px 9px; font: inherit;
}
.admin-page input[type="number"] { width: 90px; text-align: right; font-family: var(--font-mono); }
.admin-page label { display: flex; align-items: center; gap: 9px; cursor: pointer; }
.admin-page input[type="checkbox"] { width: 16px; height: 16px; accent-color: var(--accent); cursor: pointer; }
.admin-page .resync, .admin-head .resync {
  font: inherit; font-size: 0.82rem; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-2); background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 6px 12px; cursor: pointer; text-decoration: none;
}
.admin-page .resync:hover, .header-right .resync:hover { color: var(--ink); border-color: var(--accent-dim); }
.header-right .resync {
  font-size: 0.78rem; letter-spacing: 0.06em; text-transform: uppercase; text-decoration: none;
  color: var(--ink-2); background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 6px 12px;
}

/* ---------- start-of-contest takeover ---------- */
/* The last minute before the start covers everything with one big countdown.
   It's the ceremony before the noise, and it clears itself the moment the
   contest opens. */
.takeover {
  position: fixed; inset: 0; z-index: 2000;
  display: flex; align-items: center; justify-content: center;
  background: radial-gradient(120% 90% at 50% 40%, var(--surface-2), var(--bg) 70%);
}
.takeover[hidden] { display: none; }
.takeover-inner { text-align: center; }
.takeover-brand { display: flex; align-items: center; justify-content: center; gap: 14px; margin-bottom: 40px; }
.takeover-brand .hexmark { width: 46px; height: 58px; color: var(--accent); }
.takeover-brand .brand-name {
  font-size: 1.6rem; font-weight: 600; letter-spacing: 0.3em;
  text-transform: lowercase; color: var(--ink-2);
}
.takeover-label {
  font-size: 1.1rem; letter-spacing: 0.28em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 10px;
}
.takeover-clock {
  font-family: var(--font-display); font-weight: 700; color: var(--accent);
  font-size: clamp(9rem, 28vw, 22rem); line-height: 0.9; font-variant-numeric: tabular-nums;
  text-shadow: 0 0 60px color-mix(in srgb, var(--accent) 35%, transparent);
}
.takeover-contest {
  font-size: 1.15rem; color: var(--ink-2); margin-top: 24px; letter-spacing: 0.04em;
}
.takeover.go .takeover-clock {
  color: var(--series-pts); text-shadow: 0 0 80px color-mix(in srgb, var(--series-pts) 50%, transparent);
  animation: takeover-go 1.5s ease-out 1;
}
@keyframes takeover-go {
  0% { transform: scale(0.6); opacity: 0; }
  25% { transform: scale(1.1); opacity: 1; }
  100% { transform: scale(1); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) { .takeover.go .takeover-clock { animation: none; } }

/* ---------- theme switcher ---------- */
/* a quiet floating control; the panel opens upward from the button so it never
   covers the map's bottom edge. Themed from the same tokens as everything else. */
.theme-switch {
  position: fixed; right: 16px; bottom: 16px; z-index: 1200;
  display: flex; flex-direction: column; align-items: flex-end; gap: 10px;
}
.ts-toggle {
  width: 40px; height: 40px; border-radius: 50%; display: grid; place-items: center;
  cursor: pointer; color: var(--ink-2); background: var(--surface);
  border: 1px solid var(--border); box-shadow: 0 4px 14px rgba(0, 0, 0, 0.28);
}
.ts-toggle:hover { color: var(--ink); border-color: var(--accent-dim); }
.ts-panel {
  background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: 0 12px 34px rgba(0, 0, 0, 0.35); padding: 12px; width: 200px;
}
.ts-panel[hidden] { display: none; }
.ts-section {
  font-size: 0.68rem; letter-spacing: 0.14em; text-transform: uppercase;
  color: var(--ink-3); font-weight: 600; margin: 2px 2px 8px;
}
.ts-section:not(:first-child) { margin-top: 14px; }
.ts-themes { display: grid; gap: 3px; }
.ts-themes button {
  display: flex; align-items: center; gap: 10px; width: 100%; text-align: left;
  font: inherit; font-family: var(--font-sans); font-size: 0.9rem; color: var(--ink-2);
  cursor: pointer; background: transparent; border: 1px solid transparent;
  border-radius: var(--radius-sm); padding: 7px 9px;
}
.ts-themes button i { width: 12px; height: 12px; border-radius: 50%; flex: none; box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.18); }
.ts-themes button:hover { background: var(--surface-2); color: var(--ink); }
.ts-themes button.active { background: var(--surface-2); color: var(--ink); border-color: var(--accent-dim); }
.ts-modes { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; }
.ts-modes button {
  font: inherit; font-family: var(--font-sans); font-size: 0.85rem; color: var(--ink-2);
  cursor: pointer; background: transparent; border: 1px solid var(--border);
  border-radius: var(--radius-sm); padding: 6px;
}
.ts-modes button:hover { color: var(--ink); border-color: var(--accent-dim); }
.ts-modes button.active { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }

/* A window with less height than a fullscreen 1080p one -- a browser with a
   tab strip and an address bar, or a 768-tall laptop -- has to find the
   difference somewhere. Take it from padding, gaps and display type, in that
   order, because the chart is the thing that breaks first and worst when the
   rail runs short. */
@media (max-height: 1000px) {
  .rail { padding: 12px; gap: 10px; }
  .card { padding: 12px 15px; }
  .score-card { gap: 11px; padding: 14px 16px 12px; }
  .score-value { font-size: clamp(2rem, 9.5cqi, 3.4rem); }
  .sp-value { font-size: 1.25rem; }
  .target { gap: 7px; }
  .tiles { gap: 10px; }
  .tile { padding: 10px 13px; gap: 4px; }
  .tile .t-value { font-size: clamp(1.05rem, 18cqi, 1.9rem); }
  .spark { margin-top: 6px; }
  .tile.rate .spark svg { max-height: 34px; }
  .graph-card .card-label { margin-bottom: 8px; }
  .graph-card:has(.chart) { min-height: 160px; }
  .chart { min-height: 112px; }
  .bd-col { gap: 8px; }
  .bonus-card { padding: 10px 15px 11px; }
  .bonus-list { gap: 6px 22px; margin-top: 6px; }
}

@media (max-height: 860px) {
  .rail { padding: 10px; gap: 8px; }
  .card { padding: 10px 13px; }
  .score-card { gap: 8px; padding: 11px 14px 10px; }
  .score-value { font-size: clamp(1.8rem, 8cqi, 2.7rem); }
  .sp-value { font-size: 1.1rem; }
  .tiles { gap: 8px; }
  .tile { padding: 8px 11px; }
  .tile .t-value { font-size: clamp(1rem, 15cqi, 1.6rem); }
  .tile.rate .spark svg { max-height: 26px; }
  .graph-card .card-label { margin-bottom: 6px; }
  .graph-card:has(.chart) { min-height: 140px; }
  .chart { min-height: 92px; }
  .bd-col { gap: 6px; }
  .bar-track { height: 11px; }
  .bonus-card { padding: 8px 13px 9px; }
  .bonus-list { gap: 4px 18px; margin-top: 4px; }
}
