/* Revolutionary Mile — player app.
 *
 * Every color, font and radius here reads from a CSS custom property that
 * theme.json writes at boot. There are no literal brand colors in this file
 * on purpose: a rebrand should never require editing CSS.
 *
 * The palette is the Trenton Downtown Association style guide's, hex for hex
 * (RevMile_StyleGuide_1.pdf). The type is Rubik, the free Google face that
 * guide clears for "all applications", self-hosted. --font-label is the same
 * face doing the small tracked uppercase work; --font-mono is NOT a brand
 * font and appears in exactly five places, all of them data: the join-code
 * input, the join-code display, the marker number, the distance and the
 * clock.
 *
 * Ported from the original mockup, with the phone frame removed — this runs
 * on a real phone, so the viewport is the frame.
 */

*, *::before, *::after { box-sizing: border-box; }

/* `hidden` means hidden, whatever display a class gives the element. Without
   this the UA's weak [hidden] rule loses to any `display:` here — the bottom
   nav's `display:flex` kept it on screen over the check-in and question
   screens, where render() had set it hidden, covering the code-word toggle
   and the hint under Submit. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  height: 100%;
  overscroll-behavior-y: contain;   /* no pull-to-refresh mid-question */
}

body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  /* Team names and marker names come from people, not from us: one long
     unbroken one must wrap, not push a 360px screen sideways. */
  overflow-wrap: break-word;
}

#app {
  display: flex;
  flex-direction: column;
  min-height: 100dvh;              /* dvh so iOS toolbars don't crop the nav */
  max-width: 560px;
  margin: 0 auto;
}

.content {
  flex: 1;
  min-width: 0;
  /* The DOCUMENT scrolls, not this panel (see the bottom-nav note below), so
     it has no business being a scroll container. It used to be one —
     `overflow-y: auto` — which did nothing vertically but did let anything
     too wide scroll the whole screen sideways under a thumb. Clip instead:
     not a scroll container, no sideways wobble. */
  overflow-x: clip;
  padding-bottom: 16px;
}

/* ------------------------------------------------------------- typography */

h1.screen-title {
  font-family: var(--font-display);
  font-weight: 900;
  font-size: 26px;
  letter-spacing: 0.5px;
  line-height: 1.05;
  margin: 2px 0 0;
  color: var(--brand);
  text-wrap: balance;
}
/* The style guide draws no italic: Anguita Sans and Rubik are both shown
   upright only, Light through Bold. Weight carries the demotion instead —
   and Rubik Light at 12.5px is a cleaner read outdoors than a synthesised
   oblique would have been. */
.screen-sub {
  font-size: 12.5px;
  font-weight: 300;
  opacity: 0.7;
  margin-top: 4px;
}
.eyebrow {
  font-family: var(--font-label);
  font-weight: 500;
  font-size: 10px;
  letter-spacing: 2px;
  text-transform: uppercase;
  color: var(--brand);
}

/* ----------------------------------------------------------------- header */

.app-header {
  padding: 18px 22px 16px;
  border-bottom: 1px solid var(--hairline);
  flex-shrink: 0;
}
.crest-row {
  display: flex; align-items: center; gap: 6px; margin-bottom: 6px;
  /* Wrapping is the backstop, right-aligned so a pill that drops to a second
     line stays with the others. The first line of defence is `.crowded`. */
  flex-wrap: wrap; row-gap: 6px; justify-content: flex-end;
}
/* The eyebrow takes the slack, so the pills after it sit as an evenly spaced
   group on the right. Every pill used to carry `margin-left: auto` of its own,
   which meant two of them both tried to push and the spacing between them came
   out different from the spacing to the edge. */
/* A team name, not a fixed word, since the field test could not tell which team
   a phone was on. Team names run to 60 characters, so it gets a ceiling and an
   ellipsis rather than pushing the pills off a 360px screen. */
.crest-row .eyebrow {
  margin-right: auto;
  min-width: 0; max-width: 55%;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Too many pills for the app's name as well (staging, Test Mode, progress):
   the name gives way so the pills fit across a 360px phone on one line. It keeps its place
   as the spacer that pushes the pills right; the umbrella beside it still
   says whose app this is. */
.crest-row.crowded .eyebrow {
  flex: 1 1 0; min-width: 0;
  overflow: hidden; white-space: nowrap; visibility: hidden;
}
/* ...unless it is the team's name. The app's name is redundant beside the
   umbrella and can go; which team this phone is on cannot be read off the
   screen any other way, so it stays and shrinks — down to nothing if the row
   really is that tight — rather than disappearing. */
.crest-row.crowded .eyebrow.keep { visibility: visible; max-width: none; }
/* The supplied umbrella artwork, not a tinted icon: no `color`, no filter,
   nothing that could shift it off the guide's #FFCB04. Slightly wider than
   tall because the umbrella is, and squashing it to a square would be
   redrawing it. */
.crest { width: 26px; height: 20px; object-fit: contain; flex-shrink: 0; }
/* ---------------------------------------------------------------- pills --
 *
 * ONE definition for every pill and chip in the app. They had drifted into
 * five: 9px and 9.5px type, 3/4/6px of vertical padding, 8/9/12px horizontal,
 * three letter-spacings and no shared height — so the row of them in the
 * header came out visibly ragged. Anything pill-shaped gets this block and
 * then only its colours.
 *
 * `height` rather than vertical padding is the point: it is what makes a row
 * of pills line up whatever is written in them. */
.progress-pill,
.test-pill,
.set-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 22px;
  padding: 0 10px;
  border-radius: var(--radius-pill);
  font-family: var(--font-label);
  font-weight: 500;
  font-size: 9.5px;
  letter-spacing: 1.2px;
  line-height: 1;
  white-space: nowrap;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}

.progress-pill { background: var(--brand); color: var(--bg); }

/* --------------------------------------------------------------- controls */

.btn {
  display: block;
  width: 100%;
  text-align: center;
  /* Bold sans, not the typewriter face. Mono stays for DATA — join codes,
     code words, timers — where character alignment actually earns its keep. */
  font-family: var(--font-display);
  font-weight: 700;
  letter-spacing: 0.4px;
  font-size: 15px;
  /* Yellow with dark text, like the ribbon in the logo. White on this yellow
     is unreadable outdoors; ink is ~11:1. */
  background: var(--accent);
  color: var(--ink);
  border: none;
  border-radius: var(--radius-control);
  padding: 15px 13px;
  margin-top: 12px;
  cursor: pointer;
  /* 44px minimum target — this is used one-handed, outdoors, while walking. */
  min-height: 48px;
}
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
.btn.ghost { background: transparent; border: 1.5px solid var(--brand); color: var(--brand); }
.btn.gold  { background: var(--accent); color: var(--brand-deep); }

.field-group { width: 100%; margin-bottom: 14px; }
.field-label {
  font-family: var(--font-label);
  font-weight: 500;
  font-size: 9.5px;
  letter-spacing: 1.5px;
  text-transform: uppercase;
  color: var(--brand);
  opacity: 0.7;
  display: block;
  margin-bottom: 6px;
}
.field-input {
  width: 100%;
  border: 1.5px solid var(--hairline-strong);
  background: var(--bg);
  border-radius: var(--radius-control);
  padding: 13px;
  font-family: var(--font-body);
  /* 16px minimum or iOS Safari zooms the whole page on focus. */
  font-size: 16px;
  color: var(--ink);
}
.field-input.code {
  letter-spacing: 6px;
  text-align: center;
  text-transform: uppercase;
  font-family: var(--font-mono);
}

.swatch-row { display: flex; gap: 10px; }
.swatch {
  width: 38px; height: 38px; border-radius: 50%;
  cursor: pointer; flex-shrink: 0;
  border: 2.5px solid transparent;
  display: flex; align-items: center; justify-content: center;
  padding: 0;
}
.swatch[aria-pressed="true"] { border-color: var(--ink); }
.swatch[aria-pressed="true"]::after {
  content: ''; width: 8px; height: 8px; border-radius: 50%; background: var(--bg);
}

/* ------------------------------------------------------------------ auth */

.auth-wrap {
  padding: 34px 26px 24px;
  display: flex; flex-direction: column; align-items: center; text-align: center;
}
/* The crest, at a size where the ribbon still reads, with clear space around
   it — the guide asks for room on all sides and sufficient contrast, and this
   sits on white. Nothing is layered over it and nothing frames it: what was
   here before was a navy medallion of our own invention with a generic shield
   inside, which is the "added graphic elements" the guide rules out. */
.brand-logo {
  display: block;
  width: 148px;
  max-width: 62%;
  height: auto;
  margin: 0 auto 16px;
}
/* On the reward panel the crest stands in for a missing photograph, and it is
   the white-bordered cut — the one the guide supplies for colour backgrounds.
   width/height/object-fit are all restated because `.unlock-photo img` above
   is `height:100%; object-fit:cover`, which is right for a photograph and
   would stretch and crop the logo. Sized to leave clear space top and bottom
   rather than filling the panel edge to edge. */
.unlock-photo .brand-logo {
  width: auto;
  height: 72%;
  max-width: 60%;
  object-fit: contain;
  margin: 0;
}
.auth-sub { font-size: 13px; opacity: 0.7; margin: 4px 0 22px; }

/* The crest already spells the event's name. The <h1> stays in the document
   for screen readers and for heading order; it just does not print it twice. */
.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}
.auth-tabs {
  display: flex; width: 100%; background: var(--surface);
  border-radius: var(--radius-card); padding: 3px; margin-bottom: 22px;
}
.auth-tab {
  flex: 1; text-align: center; padding: 11px 0;
  font-family: var(--font-label);
  font-weight: 500; font-size: 11px; letter-spacing: 1px;
  border-radius: var(--radius-card); color: var(--ink); opacity: 0.55;
  cursor: pointer; border: none; background: transparent;
}
.auth-tab[aria-selected="true"] { background: var(--brand); color: var(--bg); opacity: 1; }
.auth-form { width: 100%; text-align: left; }

/* ----------------------------------------------------------------- route */

.site-row {
  display: flex; align-items: center; gap: 14px;
  padding: 14px 20px;
  border-bottom: 1px solid var(--hairline);
  width: 100%; background: none; border-left: none; border-right: none; border-top: none;
  text-align: left; font: inherit; color: inherit; cursor: pointer;
}
.site-row:disabled { cursor: default; }
.site-marker {
  width: 30px; height: 30px; border-radius: 50%; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-mono); font-size: 11px;
  background: var(--bg); border: 1.5px solid var(--hairline-strong); color: var(--ink);
}
.site-marker.done { background: var(--good); border-color: var(--good); color: var(--bg); }
/* Answered, verdict held until the finish — the normal state of a done marker
   during the walk. Brand blue, deliberately neither green nor red: the whole
   point is that it says "done here" without saying how it went. */
.site-marker.answered { background: var(--brand); border-color: var(--brand); color: var(--bg); }
.site-marker.next {
  background: var(--brand); border-color: var(--brand); color: var(--bg);
  box-shadow: 0 0 0 4px var(--brand-ring);
}
.site-marker svg { width: 14px; height: 14px; }
.site-name { font-family: var(--font-display); font-size: 15px; color: var(--brand); }
/* In a route row the name and its status are two lines, not one run of text:
   inline, a long marker name pushed "Not yet" onto a line of its own, split
   mid-phrase, and the status read as part of the name. */
.site-row .site-name, .site-row .site-meta { display: block; }
.site-meta {
  font-family: var(--font-label);
  font-weight: 500; font-size: 9.5px; opacity: 0.55;
  margin-top: 2px; letter-spacing: 0.5px;
}
.site-row .dist {
  margin-left: auto; font-family: var(--font-mono); font-size: 11px;
  color: var(--brand); flex-shrink: 0; font-variant-numeric: tabular-nums;
}

/* -------------------------------------------------------------- question */

.q-box {
  margin: 18px 20px 0;
  background: var(--bg);
  border: 1px solid var(--hairline);
  border-radius: 6px;
  padding: 16px;
}
.q-eyebrow {
  font-family: var(--font-label);
  font-weight: 500; font-size: 10px; letter-spacing: 1.5px;
  /* Brand blue, not --accent: yellow on this white card is 1.5:1. The guide's
     own artwork only ever uses the yellow as a fill with ink on top of it. */
  color: var(--brand);
}
.q-text {
  font-family: var(--font-display); font-size: 16px; color: var(--brand);
  margin: 6px 0 14px; line-height: 1.35; text-wrap: balance;
}
.option {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 12px;
  border: 1.5px solid var(--hairline);
  border-radius: var(--radius-control);
  margin-bottom: 8px; font-size: 14px; cursor: pointer;
  width: 100%; text-align: left; background: var(--bg); font-family: inherit; color: inherit;
  min-height: 48px;
}
.option .dot {
  width: 15px; height: 15px; border-radius: 50%;
  border: 1.5px solid var(--brand); flex-shrink: 0;
}
.option[aria-pressed="true"] { border-color: var(--brand); background: var(--brand-tint); }
.option[aria-pressed="true"] .dot { background: var(--brand); border-color: var(--brand); }
.option.tried { opacity: 0.4; text-decoration: line-through; }

/* Shown when the team's scored answer is already in — a second person can
   still play for the story, but should know it will not change the score. */
.scored-note {
  font-size: 12.5px;
  line-height: 1.45;
  background: var(--surface);
  border-left: 3px solid var(--accent);
  border-radius: 3px;
  padding: 9px 11px;
  margin-bottom: 12px;
  opacity: 0.85;
}

.set-badge { background: var(--accent); color: var(--brand-deep); }

/* ---------------------------------------------------------------- unlock */

.unlock-wrap { padding: 26px 24px; text-align: center; }
.stamp-big {
  font-family: var(--font-label);
  font-weight: 500; letter-spacing: 3px; font-size: 11px;
  color: var(--good-deep); border: 2px solid var(--good-deep); border-radius: 4px;
  padding: 5px 14px; display: inline-block; transform: rotate(-3deg); margin-bottom: 14px;
}
.unlock-wrap h2 {
  font-family: var(--font-display); font-weight: 900; font-size: 24px;
  color: var(--brand); margin: 0 0 20px; text-wrap: balance;
}
.unlock-photo {
  height: 170px; border-radius: 6px; overflow: hidden;
  background: linear-gradient(160deg, var(--brand), var(--brand-deep));
  border: 1px solid var(--hairline);
  display: flex; align-items: center; justify-content: center;
}
.unlock-photo img { width: 100%; height: 100%; object-fit: cover; }
.unlock-photo svg { width: 50px; height: 50px; color: var(--accent-soft); opacity: 0.45; }
/* Required by the toolkit's photo usage guidelines: the credit line has to
   appear wherever the photograph does. Small and quiet — it is a legal line,
   not part of the reward — but present and readable (--muted is 5.1:1). */
.photo-credit {
  font-family: var(--font-label);
  font-size: 9.5px;
  letter-spacing: 0.3px;
  color: var(--muted);
  text-align: left;
  margin-top: 6px;
}

.unlock-caption { text-align: left; margin-top: 12px; }
.unlock-caption p { font-size: 13px; opacity: 0.7; margin: 0; line-height: 1.55; }
audio { width: 100%; margin-top: 14px; }

/* ------------------------------------------------------------- dashboard */

.stat-row { display: flex; gap: 10px; padding: 16px 20px 0; }
.stat-tile {
  flex: 1; background: var(--bg); border: 1px solid var(--hairline);
  border-radius: 6px; padding: 12px; text-align: center;
}
.stat-tile .n {
  font-family: var(--font-display); font-weight: 700; font-size: 22px;
  color: var(--brand); font-variant-numeric: tabular-nums;
}
.stat-tile .l {
  font-family: var(--font-label);
  font-weight: 500; font-size: 8.5px; letter-spacing: 1px;
  opacity: 0.6; text-transform: uppercase;
}
.bar-outer {
  height: 8px; background: var(--line);
  border-radius: var(--radius-pill); overflow: hidden; margin: 12px 20px 0;
}
.bar-inner {
  height: 100%; background: linear-gradient(90deg, var(--brand), var(--accent));
  border-radius: var(--radius-pill); transition: width .4s ease;
}
.section-label {
  font-family: var(--font-display); font-size: 14px; color: var(--brand);
  margin: 22px 20px 6px;
}
.code-share {
  margin: 8px 20px 0; padding: 12px 14px;
  border: 1.5px dashed var(--accent); background: var(--bg);
  border-radius: var(--radius-control); text-align: center;
}
.code-share .code {
  font-family: var(--font-mono); font-size: 22px; letter-spacing: 6px; color: var(--brand);
}
.code-share .hint { font-size: 11px; opacity: 0.6; margin-top: 4px; }

/* ----------------------------------------------------------- leaderboard */

.lb-row {
  display: flex; align-items: center; gap: 12px; padding: 13px 20px;
  border-bottom: 1px solid var(--hairline);
}
.lb-rank {
  width: 26px; height: 26px; border-radius: 50%; background: var(--line);
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display); font-weight: 700; font-size: 12px;
  color: var(--brand); flex-shrink: 0;
}
/* Ink on yellow, like the ribbon in the logo. White on #FFCB04 is 1.5:1 and
   vanishes in daylight; ink is 10.7:1. Same rule as .btn. */
.lb-row.first .lb-rank { background: var(--accent); color: var(--ink); }
.lb-row.you { background: var(--brand-tint); }
.lb-row.you .lb-rank { background: var(--brand); color: var(--bg); }
.lb-name { font-family: var(--font-display); font-size: 14px; color: var(--brand); }
.lb-sub { font-family: var(--font-label);
  font-weight: 500; font-size: 9px; opacity: 0.5; }
.lb-time {
  margin-left: auto; font-family: var(--font-mono); font-size: 12px;
  color: var(--brand); font-variant-numeric: tabular-nums;
}

/* ------------------------------------------------------------ bottom nav */

/* PINNED, not "at the bottom of the layout".
 *
 * It used to be the last flex child of #app with `.content` as `flex: 1;
 * overflow-y: auto`. That looks identical until the content is taller than the
 * viewport: a flex item's default `min-height: auto` stops `.content` shrinking
 * below its content, so #app grew past 100dvh, the PAGE scrolled instead of the
 * panel, and the nav walked off the bottom of the screen. Position it against
 * the viewport and the question stops arising. */
.bottom-nav {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 40;
  /* #app is centred and capped; the nav has to match it, not the window. */
  max-width: 560px;
  margin: 0 auto;
  display: flex;
  border-top: 1px solid var(--hairline);
  background: var(--surface);
  /* Clears the iOS home indicator. */
  padding-bottom: env(safe-area-inset-bottom, 0);
}
/* Room for it, only on the screens that have one. `render()` sets this class,
   rather than `:has()`, so the layout does not depend on selector support. */
#app.has-nav .content { padding-bottom: calc(76px + env(safe-area-inset-bottom, 0)); }
.nav-item {
  flex: 1;
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 11px 0 13px;
  font-family: var(--font-label);
  font-weight: 500; font-size: 9.5px; letter-spacing: 1px;
  color: var(--ink); opacity: 0.5; text-transform: uppercase;
  background: none; border: none; cursor: pointer;
}
.nav-item svg { width: 19px; height: 19px; }
.nav-item[aria-current="page"] { opacity: 1; color: var(--brand); }

/* -------------------------------------------------------------- feedback */

.toast {
  position: fixed; left: 50%; bottom: 84px; transform: translateX(-50%);
  background: var(--brand-deep); color: var(--bg);
  font-size: 13px; line-height: 1.4;
  padding: 12px 16px; border-radius: var(--radius-control);
  max-width: min(440px, calc(100vw - 32px));
  box-shadow: 0 8px 24px -8px rgba(0, 0, 0, 0.5);
  z-index: 50; text-align: center;
}
.toast.error { background: var(--bad-deep); }
.notice { padding: 26px 24px; text-align: center; opacity: 0.7; font-size: 14px; }

:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}


/* ---- added with the race rules (migration 0003) ---- */

/* Test Mode is a live-fire footgun: someone will leave it on. Make it loud —
   deliberately the error red and NOT a brand colour, so it reads as a warning
   rather than as decoration. */
/* The deep step, not the flat guide red: this is 9.5px white text, and white
   on #EE3B23 is 4.0:1. Same hue, 6.4:1. */
.test-pill { background: var(--bad-deep); color: var(--bg); }

/* Two live URLs now, so the staging one says so on every screen. Navy rather
   than crimson: it is a statement of which build you are on, not an alarm. */
.test-pill.env { background: var(--brand); }

/* The staging warning on the join screen. `.race-banner` is built for the
   route screen, where it sits alone with the page gutter either side; inside
   `.auth-wrap` that gutter is already applied and the missing bottom margin
   let it collide with the tab row underneath. */
.auth-wrap .race-banner {
  margin: 4px 0 20px;
  text-align: left;
  width: 100%;
}

.race-banner {
  margin: 14px 20px 0;
  padding: 13px 15px;
  background: var(--surface);
  border-left: 3px solid var(--brand);
  border-radius: var(--radius-card);
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.race-banner strong {
  font-family: var(--font-display);
  font-size: 15px;
  color: var(--brand);
}
.race-banner span { font-size: 12.5px; opacity: 0.7; line-height: 1.45; }

.seal-wait {
  width: 56px; height: 56px; margin: 0 auto 16px;
  border-radius: 50%;
  background: var(--surface);
  border: 1.5px dashed var(--accent);
  display: flex; align-items: center; justify-content: center;
  color: var(--accent);
}
.seal-wait svg { width: 26px; height: 26px; }

.lede {
  font-size: 14px;
  line-height: 1.55;
  opacity: 0.72;
  margin: 0 0 20px;
  text-wrap: pretty;
}

/* A wrong answer still completes the site, so the stamp reports rather than
   scolds — the story unlocks either way. */
.stamp-big.wrong { color: var(--bad); border-color: var(--bad); }
/* The assignment reveal borrows the stamp, but it is not a result — green
   there reads as "you got it right" for something nobody answered. */
.stamp-big.assignment { color: var(--brand); border-color: var(--brand); }
/* "Answer recorded" — the result stamp while verdicts are held. Brand blue,
   for the same reason the list glyph is: green would read as a tick. */
.stamp-big.held { color: var(--brand); border-color: var(--brand); }

.site-marker.wrong { background: var(--bad); border-color: var(--bad); color: var(--bg); }

.roster-row {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 20px;
  border-bottom: 1px solid var(--hairline);
}
/* A chip is a pill you can tap, so it keeps the type scale and gains a real
   target: 34px, not the 22px a static pill gets away with. */
.roster-row .chip {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-height: 34px;
  padding: 0 14px;
  border-radius: var(--radius-pill);
  font-family: var(--font-label);
  font-weight: 500;
  font-size: 9.5px;
  letter-spacing: 1.2px;
  line-height: 1;
  white-space: nowrap;
  text-transform: uppercase;
  border: 1.5px solid var(--brand);
  color: var(--brand);
  background: transparent;
  cursor: pointer;
}
/* NOT `color: var(--accent)`. The guide's yellow is 1.5:1 on white — this is
   the exact rule CLAUDE.md sets out and this chip was breaking it: a CAPTAIN
   badge nobody can read outdoors. Yellow stays as the fill, ink goes on top,
   same as the logo's own ribbon and the same as .btn. */
.roster-row .chip.captain {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--ink);
  cursor: default;
}

.hint-text {
  font-size: 12px;
  font-weight: 300;      /* see .screen-sub — the guide has no italic */
  opacity: 0.65;
  margin: 12px 20px 0;
  line-height: 1.5;
}


/* ============================================================================
   Added with the route map, the help sheets and real back navigation.

   Same rule as everything above: no literal colour. `grep -n
   '#[0-9a-fA-F]\{3,6\}' public/assets/app.css` must still return only
   comments and the one neutral shadow.
   ========================================================================= */

/* ------------------------------------------------- header: back and help -- */

.title-row { display: flex; align-items: flex-start; gap: 8px; }
.title-row .screen-title { flex: 1; min-width: 0; }

/* 44px of target around a 20px glyph. These are tapped while walking, by a
   thumb, at the very top of the screen where the hand is least steady. */
.icon-btn {
  width: 40px; height: 40px; flex-shrink: 0;
  margin: -9px -8px -9px -10px;
  display: flex; align-items: center; justify-content: center;
  background: none; border: none; padding: 0; cursor: pointer;
  color: var(--brand);
  border-radius: 50%;
}
.icon-btn svg { width: 22px; height: 22px; }
.icon-btn.help { margin: -6px -8px 0 0; opacity: 0.75; }
.icon-btn:active { background: var(--brand-tint); }

/* A text button that is honestly a link: used where a full-width .btn would
   claim more importance than "explain this to me" deserves. */
.link-btn {
  background: none; border: none; cursor: pointer;
  font-family: var(--font-label); font-weight: 500;
  font-size: 11px; letter-spacing: 1px; text-transform: uppercase;
  color: var(--brand); padding: 12px; margin-top: 4px;
  text-decoration: underline; text-underline-offset: 3px;
}

/* ------------------------------------------------------------------- map -- */

.map-block { border-bottom: 1px solid var(--hairline); padding-bottom: 12px; }
.map-bar {
  display: flex; align-items: center; gap: 10px;
  padding: 14px 20px 8px;
}
.map-title {
  font-family: var(--font-label); font-weight: 500;
  font-size: 9.5px; letter-spacing: 1.5px; text-transform: uppercase;
  color: var(--brand); opacity: 0.7;
}
.map-toggle {
  margin-left: auto; background: none; border: none; cursor: pointer;
  font-family: var(--font-label); font-weight: 500;
  font-size: 10px; letter-spacing: 1px; text-transform: uppercase;
  color: var(--brand); padding: 8px 4px; min-height: 36px;
}
.map-block.folded { padding-bottom: 0; }

.map-holder { position: relative; margin: 0 20px; }
#route-map {
  /* Tall enough that ten markers across a mile of downtown are distinguishable
     without zooming, short enough that the next stop in the list is still on
     screen underneath it on a small phone. */
  height: 280px;
  border-radius: var(--radius-card);
  border: 1px solid var(--hairline);
  /* Mapbox paints over this; it shows only while the style and tiles land. */
  background: var(--surface);
  z-index: 0;   /* keeps tiles under the toast and the sheet */
}
@media (min-height: 780px) { #route-map { height: 340px; } }

.map-fallback {
  padding: 22px 18px; text-align: center;
  font-size: 13px; line-height: 1.5; opacity: 0.75;
  background: var(--surface); border-radius: var(--radius-card);
}

.map-legend {
  display: flex; flex-wrap: wrap; gap: 14px;
  padding: 10px 20px 0;
  font-size: 11.5px; opacity: 0.75;
}
.map-legend span { display: flex; align-items: center; gap: 6px; }
.map-legend .key {
  width: 11px; height: 11px; border-radius: 50%;
  border: 1.5px solid var(--hairline-strong); flex-shrink: 0;
}
.map-legend .key.todo    { background: var(--accent); }
.map-legend .key.correct { background: var(--good); }
.map-legend .key.wrong   { background: var(--bad); }
.map-legend .key.answered { background: var(--brand); }

.map-actions { display: flex; gap: 8px; padding: 10px 20px 0; }
.pill-btn {
  flex: 1;
  font-family: var(--font-label); font-weight: 500;
  font-size: 10.5px; letter-spacing: 0.8px; text-transform: uppercase;
  background: transparent; color: var(--brand);
  border: 1.5px solid var(--brand); border-radius: var(--radius-pill);
  padding: 10px 8px; min-height: 40px; cursor: pointer;
}
.pill-btn:active { background: var(--brand-tint); }
.map-note { margin-top: 8px; }

/* Mapbox GL's own chrome, brought into the app's palette. */
.mapboxgl-map { font-family: var(--font-body); }
.mapboxgl-popup-content {
  border-radius: var(--radius-control);
  padding: 12px 14px;
  font-family: var(--font-body);
}
.mapboxgl-popup-close-button { color: var(--muted); font-size: 18px; padding: 2px 7px; }
/* Attribution and the Mapbox wordmark are conditions of the Mapbox terms.
   Keep them legible and on screen — do not hide either. */
.mapboxgl-ctrl-attrib { font-size: 10px; }

/* The marker element itself: a bare SVG, no chrome. Mapbox does not impose a
   background of its own, but the pointer and the tap
   target both need saying. */
.rm-pin { cursor: pointer; line-height: 0; }
.rm-pin svg { display: block; }
.rm-pin:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; border-radius: 4px; }

/* Where the umbrella actually stands, said in the marker's popup rather than
   drawn as a second dot beside it. Thirty pins with loose dots next to them
   read as clutter, and this is only wanted once a marker has been chosen. */
.rm-pop-note {
  font-size: 11.5px;
  line-height: 1.4;
  color: var(--ink);
  background: var(--surface);
  border-radius: 4px;
  padding: 6px 8px;
  margin-top: 5px;
}

/* "You are here". White ring so it reads on both the pale streets and the
   parks in the Vibemap style. */
.rm-you {
  width: 15px; height: 15px; border-radius: 50%;
  background: var(--brand); border: 3px solid var(--bg);
  box-sizing: border-box;
}

/* Mapbox's two-finger hint, when a single finger tries to pan. Its default is
   a dark scrim with its own type; this keeps it in the app's voice. */
.mapboxgl-scroll-zoom-blocker, .mapboxgl-touch-pan-blocker {
  font-family: var(--font-body);
  font-size: 14px;
  border-radius: var(--radius-card);
}

.rm-pop { display: flex; flex-direction: column; gap: 3px; min-width: 150px; }
.rm-pop-ord {
  font-family: var(--font-label); font-weight: 500;
  font-size: 9px; letter-spacing: 1.2px; text-transform: uppercase;
  color: var(--muted);
}
.rm-pop strong { font-family: var(--font-display); font-size: 14px; color: var(--brand); }
.rm-pop-state { font-size: 11.5px; color: var(--muted); }
.rm-pop-go {
  margin-top: 7px; width: 100%;
  font-family: var(--font-display); font-weight: 700; font-size: 13px;
  background: var(--accent); color: var(--ink);
  border: none; border-radius: var(--radius-control);
  padding: 10px; min-height: 40px; cursor: pointer;
}

/* ----------------------------------------------------------- the check-in -- */

.checkin-wrap { padding: 18px 20px; }

/* The three ways in, numbered. A team standing at a marker with a phone that
   will not get a fix needs to know what to try next without asking anybody,
   and "there is a fallback" is not discoverable from a single button. */
.how-to { list-style: none; margin: 0 0 18px; padding: 0; }
.how-to li {
  display: flex; gap: 12px; align-items: flex-start;
  padding: 11px 0; border-bottom: 1px solid var(--hairline);
}
.how-to li:last-child { border-bottom: none; }
.how-to li > span:last-child { display: flex; flex-direction: column; gap: 2px; }
.how-to strong { font-family: var(--font-display); font-size: 14px; color: var(--brand); }
.how-to span span { font-size: 12.5px; line-height: 1.45; opacity: 0.72; }
.step-n {
  width: 24px; height: 24px; border-radius: 50%; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: var(--brand); color: var(--bg);
  font-family: var(--font-mono); font-size: 11px;
}

/* The code word is the third thing to try, so it is folded away rather than
   sitting open beside the button most people will use. */
.fallback { margin-top: 22px; border-top: 1px solid var(--hairline); padding-top: 14px; }
.fallback summary {
  font-family: var(--font-label); font-weight: 500;
  font-size: 10.5px; letter-spacing: 1px; text-transform: uppercase;
  color: var(--brand); cursor: pointer; padding: 10px 0; min-height: 40px;
  display: flex; align-items: center;
}
.fallback summary::marker { color: var(--brand); }

/* ----------------------------------------------------------------- sheet -- */

.sheet-bg {
  position: fixed; inset: 0; z-index: 60;
  background: var(--brand-deep);
  background: color-mix(in srgb, var(--ink) 62%, transparent);
  display: flex; align-items: flex-end; justify-content: center;
}
@media (min-width: 560px) { .sheet-bg { align-items: center; } }

/* The page behind a modal must not scroll under a thumb that misses it — the
   classic iOS overlay complaint, and the reason this class exists at all. */
body.sheet-open { overflow: hidden; }

.sheet {
  background: var(--bg);
  width: 100%; max-width: 480px;
  border-radius: var(--radius-card) var(--radius-card) 0 0;
  padding: 18px 20px calc(18px + env(safe-area-inset-bottom, 0));
  max-height: 86vh; overflow-y: auto;
  -webkit-overflow-scrolling: touch;
}
@media (min-width: 560px) { .sheet { border-radius: var(--radius-card); } }

.sheet-head { display: flex; align-items: flex-start; gap: 10px; margin-bottom: 14px; }
.sheet-head h2 {
  flex: 1; margin: 0;
  font-family: var(--font-display); font-weight: 900; font-size: 20px;
  color: var(--brand); text-wrap: balance;
}
.sheet-x {
  width: 40px; height: 40px; margin: -8px -10px 0 0; flex-shrink: 0;
  display: flex; align-items: center; justify-content: center;
  background: none; border: none; cursor: pointer;
  color: var(--muted); border-radius: 50%;
}
.sheet-x svg { width: 20px; height: 20px; }
.sheet-done { margin-top: 18px; }

.help-list { list-style: none; margin: 0; padding: 0; }
.help-list li {
  font-size: 13.5px; line-height: 1.55;
  padding: 0 0 13px 22px; position: relative;
}
.help-list li::before {
  content: ''; position: absolute; left: 2px; top: 8px;
  width: 8px; height: 8px; border-radius: 50%; background: var(--accent);
}
.help-list li:last-child { padding-bottom: 0; }

/* ------------------------------------------------------ start and finish -- */

/* A race has a start line and a finish. Neither is necessarily the first or
   last marker anybody answers a question at, so they are their own markers on
   the map rather than being inferred from the stops. */
.rm-end {
  width: 26px; height: 26px; border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display);
  font-weight: 700; font-size: 12px; line-height: 1;
  border: 2px solid var(--bg);
  cursor: pointer; box-sizing: border-box;
}
.rm-end--start  { background: var(--brand); color: var(--bg); }
.rm-end--finish { background: var(--ink); color: var(--bg); }

/* The map's own banner for "this is not the real route yet". Amber-adjacent
   rather than the brand blue the other banners use: it is a caveat about what
   is on screen, not a piece of the event's furniture. */
.race-banner.provisional {
  margin: 10px 20px 0;
  border-left-color: var(--accent-deep);
}

/* ------------------------------------------------------------- the captain -- */

/* Only the captain can submit, which is the rule most likely to strand a team
   — so the Progress screen says who holds it rather than leaving it to a chip
   in a list. Yellow rule because it is the team's own state, not a warning. */
.race-banner.captain-banner { border-left-color: var(--accent-deep); }

.you-tag {
  font-family: var(--font-label);
  font-weight: 500;
  font-size: 9px;
  letter-spacing: 1px;
  text-transform: uppercase;
  color: var(--muted);
  margin-left: 4px;
}

/* Arrived from a team's join QR. Yellow rule: it is the one thing on this
   screen that says which team the phone is about to be put on. */
.auth-wrap .race-banner.join-banner { border-left-color: var(--accent-deep); }

/* ------------------------------------------------------------ the finish -- */

/* Every stop answered. Green rule: the one banner on the route that is
   good news rather than a condition. */
.race-banner.done-banner { border-left-color: var(--good); }

/* "That was your last stop", on the result that completed the route. Sits
   between the story and the buttons, so it needs room above, not below. */
.unlock-wrap .finish-note { margin: 20px 0 0; }
