/* css/site.css — JuicyTrails Challenge, phase 1, step 2.
   Concept: a results sheet stapled to a trailhead kiosk board, not a
   product. System fonts only. No shadows, no gradients, no card grids.
   Phone width first, 16px gutter, no horizontal scroll. Kept under
   ~300 lines on purpose. */

/* Oct 3 2026 (Derrick): dark is the only theme, on every page. The
   palette is four colours — orange, white, blue, black — and nothing
   else is a brand colour:
     --orange #ff8000  headline blocks, wordmark, logo (defined below; the map's hike+bike+horse trail colour, Oct 5)
     --fg     #f4f4f2  white text
     --link   #8ab4f8  blue: links, the Upload button
     --bg     #16160f  black page
   The light theme and the prefers-color-scheme switch are gone. */
:root {
  color-scheme: dark;
  --bg: #16160f;
  --fg: #f4f4f2;
  --muted: #b9b9b4;
  --border: #f4f4f2;
  --border-soft: #4a4a45;
  --link: #8ab4f8;
  --blue: #8ab4f8;
  --warn: #f2a541;
  --fail: #ff6b6b;
  --ok: #3fb950;
  --green: #7fbf7f;
  --amber: #e0ac4c;
  --red: #e2786a;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
}

* { box-sizing: border-box; }

html, body {
  background: var(--bg);
  color: var(--fg);
  max-width: 100%;
  overflow-x: hidden;
}

body {
  font-family: var(--sans);
  font-size: 16px;
  line-height: 1.45;
  margin: 0;
  padding: 0 16px 48px;
  /* Oct 5 (Derrick's phone): iOS draws the status bar and home bar over the
     page. viewport-fit=cover + the safe-area insets keep the header and the
     last lines clear of them; zero everywhere else. */
  padding-bottom: calc(48px + env(safe-area-inset-bottom));
  padding-left: max(16px, env(safe-area-inset-left));
  padding-right: max(16px, env(safe-area-inset-right));
}

a { color: var(--link); text-decoration: underline; }
a:visited { color: var(--link); }
a:hover { text-decoration: underline; }

h1, h2, h3 {
  font-family: var(--mono);
  letter-spacing: 0.02em;
  text-transform: uppercase;
  margin: 1.1em 0 0.4em;
}
h1 { font-size: 1.3rem; border-bottom: 2px solid var(--border); padding-bottom: 0.3em; }
h2 { font-size: 1.05rem; }
h3 { font-size: 0.95rem; color: var(--muted); }

p { margin: 0.5em 0; }

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

.kiosk-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0 0.6em;
  font-family: var(--mono);
  font-size: 0.85rem;
  padding: 10px 0;
  border-bottom: 1px solid var(--border-soft);
  margin-bottom: 4px;
}
.kiosk-nav .brand {
  font-weight: 700;
  text-transform: uppercase;
  margin-right: 0.6em;
}
.kiosk-nav a { text-decoration: underline; }
.kiosk-nav a:hover { text-decoration: underline; }

/* ------------------------------------------------------------ counter */

.counter-strip {
  font-family: var(--mono);
  font-size: 0.9rem;
  border: 1px solid var(--border);
  padding: 8px 10px;
  margin: 0.6em 0 1em;
  display: flex;
  flex-wrap: wrap;
  gap: 4px 1.2em;
}
.counter-strip b { font-size: 1rem; }

/* -------------------------------------------------------------- table */

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.92rem;
  margin: 0.6em 0 1.2em;
}
caption {
  text-align: left;
  font-family: var(--mono);
  font-size: 0.8rem;
  color: var(--muted);
  margin-bottom: 0.3em;
}
th, td {
  text-align: left;
  padding: 5px 8px;
  border-bottom: 1px solid var(--border-soft);
  vertical-align: top;
}
th {
  font-family: var(--mono);
  font-size: 0.78rem;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--muted);
  border-bottom: 2px solid var(--border);
}
tr:first-child td { border-top: none; }
td.num, th.num { text-align: right; font-family: var(--mono); }

/* --------------------------------------------------------------- state */

.state-claimed { color: var(--green); font-weight: 600; }
.state-short { color: var(--amber); }
.state-ineligible { color: var(--muted); }
.state-unmatched { color: var(--red); }

.dots { font-family: var(--mono); letter-spacing: 1px; }

/* ------------------------------------------------------------- forms */

form.kiosk-form {
  border: 1px solid var(--border);
  padding: 14px;
  margin: 0.8em 0;
  max-width: 480px;
}
form.kiosk-form label {
  display: block;
  font-family: var(--mono);
  font-size: 0.8rem;
  text-transform: uppercase;
  margin: 0.8em 0 0.25em;
}
form.kiosk-form label:first-child { margin-top: 0; }
input[type="file"],
select,
textarea,
input[type="email"],
input[type="text"] {
  width: 100%;
  font: inherit;
  padding: 6px 8px;
  border: 1px solid var(--border);
  background: var(--bg);
  color: var(--fg);
}
textarea { min-height: 5em; resize: vertical; }
label.checkline {
  display: flex;
  align-items: flex-start;
  gap: 0.5em;
  font-family: var(--sans);
  text-transform: none;
  font-size: 0.9rem;
}
label.checkline input { margin-top: 0.2em; }

button, .kiosk-btn {
  font: inherit;
  font-family: var(--mono);
  text-transform: uppercase;
  letter-spacing: 0.03em;
  background: var(--fg);
  color: var(--bg);
  border: 1px solid var(--border);
  padding: 8px 16px;
  margin-top: 1em;
  cursor: pointer;
}
button[disabled] {
  background: var(--bg);
  color: var(--muted);
  cursor: not-allowed;
}
button:hover:not([disabled]) { opacity: 0.85; }

.notice {
  font-family: var(--mono);
  border: 1px dashed var(--border);
  padding: 10px;
  margin: 0.8em 0;
}
/* message kinds — the text always says what happened too (never colour alone) */
.notice.warn { color: var(--warn); border-color: var(--warn); }
.notice.fail { color: var(--fail); border-color: var(--fail); }
.notice.ok { color: var(--ok); border-color: var(--ok); }

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

.map-box {
  width: 100%;
  height: 54vh;
  min-height: 280px;
  border: 1px solid var(--border);
  margin: 0.6em 0 1em;
}
.legend {
  font-family: var(--mono);
  font-size: 0.8rem;
  display: flex;
  flex-wrap: wrap;
  gap: 0.4em 1.2em;
  margin-bottom: 0.6em;
}
.legend span::before {
  content: "\25A0";
  margin-right: 0.35em;
}
.legend .l-claimed::before { color: var(--green); }
.legend .l-short::before,
.legend .l-ineligible::before { color: var(--amber); }
.legend .l-unmatched::before { color: var(--red); }

/* ------------------------------------------------------------- thread */

.thread-post {
  border-top: 1px solid var(--border-soft);
  padding: 8px 0;
}
.thread-post.claim {
  color: var(--muted);
  font-size: 0.85rem;
  font-style: italic;
}
.thread-post .who {
  font-family: var(--mono);
  font-weight: 700;
  margin-right: 0.5em;
}

/* -------------------------------------------------------------- misc */

.muted { color: var(--muted); }
.stub { color: var(--muted); font-size: 0.85rem; }
footer.kiosk-foot {
  margin-top: 2.5em;
  padding-top: 0.6em;
  border-top: 1px solid var(--border-soft);
  font-family: var(--mono);
  font-size: 0.75rem;
  color: var(--muted);
}

/* upload drop zone (photos from Photos.app / Finder, or a GPX) */
.dropzone {
  margin-top: 0.5rem; padding: 1.25rem; text-align: center;
  border: 2px dashed var(--muted); color: var(--muted);
}
.dropzone.over { border-color: var(--fg); color: var(--fg); }
.photo-list { list-style: none; padding: 0; margin: 0.5rem 0 0; }
.photo-list li { display: flex; align-items: center; gap: 0.5rem; padding: 0.15rem 0; }
.photo-list img { width: 48px; height: 48px; object-fit: cover; }
.photo-list button { font: inherit; padding: 0 0.4rem; }
.photo-list .warn { color: var(--warn); font-size: 0.8rem; }

/* upload progress */
#upload-progress { width: 100%; margin-top: 0.8rem; accent-color: var(--ok); }
.upload-box .upload-progress { margin: 0; }
.upload-box #upload-progress { margin-top: 0; }
#upload-progress-text { font-family: var(--mono); font-size: 0.85rem; color: var(--muted); margin: 0.3rem 0 0; }

/* photos management page */
.photo-grid {
  display: flex; flex-wrap: wrap; gap: 0.6rem;
  margin: 0.5rem 0 1rem;
}
.photo-tile {
  width: 120px;
}
.photo-tile .thumb {
  width: 120px; height: 120px; object-fit: cover;
  border: 1px solid var(--border-soft); display: block; background: var(--border-soft);
}
.photo-tile .thumb-missing {
  width: 120px; height: 120px; border: 1px dashed var(--border-soft);
  color: var(--muted); font-size: 0.7rem; display: flex; align-items: center;
  justify-content: center; text-align: center; padding: 4px; overflow: hidden;
}
.photo-tile button { font-size: 0.75rem; padding: 3px 8px; margin-top: 0.3rem; width: 100%; }
/* photo caption (js/photos.js's photoCaption()) — color alone never
   carries the meaning, the text always says it too. */
.photo-caption {
  font-size: 0.7rem; line-height: 1.2; margin: 0.3rem 0 0; word-break: break-word;
}
.photo-caption.ok { color: var(--ok); }
.photo-caption.warn { color: var(--warn); }
.photo-caption.fail { color: var(--fail); }
.photo-caption.muted { color: var(--muted); }
.photo-group {
  border-top: 1px solid var(--border-soft); padding-top: 0.6rem; margin-top: 1.2rem;
}
.photo-group.just-uploaded { border: 1px dashed var(--ok); padding: 0.6rem; }
.photo-group h3 { margin-top: 0; }

/* photo map (camera-location + heading-wedge markers, the user's own
   photos — same blue as the track line in ride.js) */
#photo-map {
  width: 100%; height: 360px;
  border: 1px solid var(--border);
  margin: 0.6em 0 0.4em;
}
#photo-map-noloc { display: none; }

/* photo viewer OVERLAY (Sept 29 round 2 — Derrick, after the signed-in
   test: a fixed lightbox over everything, not an in-page panel; big
   arrows beside the image, never a rebuild-on-step glitch — see
   js/photos.js's ensureViewer()/step()). display:none until .open is
   added; body.viewer-open (below) kills background scroll while it's
   up. The tokens redefined here pin the overlay to the DARK palette's
   values regardless of the page's own light/dark theme, so a filled
   button (background var(--fg)) always has real contrast against the
   near-black backdrop instead of nearly vanishing in light mode. */
#photo-viewer {
  display: none;
  position: fixed;
  inset: 0;
  z-index: 500;
  background: rgba(0, 0, 0, 0.92);
  align-items: center;
  justify-content: center;
  flex-direction: column;
  padding: 16px;
  box-sizing: border-box;
  --bg: #16160f;
  --fg: #f4f4f2;
  --muted: #b9b9b4;
  --border: #f4f4f2;
  --border-soft: #4a4a45;
  --link: #8ab4f8;
  --warn: #f2a541;
  --fail: #ff6b6b;
  --ok: #3fb950;
}
#photo-viewer.open { display: flex; }
body.viewer-open { overflow: hidden; }

/* Round 3 (Sept 29, later) — Derrick, on his phone: pinching in the
   overlay zoomed the whole PAGE, not the sign in the photo. The wrap
   keeps the exact footprint .viewer-image used to have (max-width/
   max-height) and clips (overflow: hidden) whatever the image's own
   pinch/pan transform does inside it; js/photozoom.js's pure math +
   js/photoviewer.js's gesture handlers drive that transform. */
#photo-viewer .viewer-image-wrap {
  max-width: min(96vw, 1600px);
  max-height: 78vh;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}
#photo-viewer .viewer-image {
  max-width: 100%;
  max-height: 100%;
  object-fit: contain;
  display: block;
  touch-action: none; /* the overlay's own handlers do the zooming */
  will-change: transform;
  transform-origin: center center;
}
#photo-viewer .viewer-image.zoomed { cursor: grab; }
#photo-viewer .viewer-image.panning { cursor: grabbing; }
#photo-viewer .viewer-no-image {
  color: var(--muted);
  font-family: var(--mono);
}

#photo-viewer .viewer-close {
  position: absolute;
  top: 12px; right: 16px;
  font-size: 1.3rem; line-height: 1;
  padding: 6px 14px; margin-top: 0;
}

/* Prev/next: big buttons at mid-height, left/right edges (Derrick's
   first finding — he expected these BESIDE the photo, not a row above
   it); at least 48x48px hit area; dimmed + inert at the ends. */
#photo-viewer .viewer-arrow {
  position: absolute;
  top: 50%; transform: translateY(-50%);
  width: 48px; height: 48px;
  font-size: 1.6rem; line-height: 1;
  display: flex; align-items: center; justify-content: center;
  padding: 0; margin-top: 0;
}
#photo-viewer .viewer-prev { left: 12px; }
#photo-viewer .viewer-next { right: 12px; }
#photo-viewer .viewer-arrow[disabled] { opacity: 0.3; }
/* The zoomable image has a transform layer (Sept 29 round 3), which
   paints ABOVE later-in-flow positioned siblings by DOM order — on a
   phone the photo spans the full width and hid the arrows (Derrick,
   7:58 PM). Arrows + close sit on their own layer above the image. */
#photo-viewer .viewer-arrow, #photo-viewer .viewer-close { z-index: 2; }
#photo-viewer .viewer-image-wrap { position: relative; z-index: 1; }

#photo-viewer .viewer-bar {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.5rem 0.8rem;
  max-width: min(96vw, 1600px);
  margin-top: 0.7rem;
  font-size: 0.8rem;
}
#photo-viewer .viewer-bar button { font-size: 0.8rem; padding: 4px 10px; width: auto; margin-top: 0; }
#photo-viewer .viewer-counter { font-family: var(--mono); color: var(--muted); }
#photo-viewer .viewer-caption,
#photo-viewer .viewer-heading { margin: 0; color: var(--muted); }
/* .viewer-reading's own class is overwritten by renderCaption()
   (js/photos.js) with "photo-caption <kind>" — that rule (above,
   .photo-caption) already sets its margin/size/colour, including
   picking up this scope's redefined --ok/--warn/--fail/--muted. */

/* The photo last opened in the viewer (js/photoviewer.js highlightTile()):
   the map markers' blue (js/photomap.js PHOTO_BLUE #1a73e8) with a glow
   that fades out like the bearing cones — Derrick, Oct 3: the thin dark
   line was "hard to see". */
.photo-tile.selected .thumb {
  outline: 3px solid #1a73e8; outline-offset: -1px;
  box-shadow: 0 0 0 4px rgba(26, 115, 232, 0.45), 0 0 28px 10px rgba(26, 115, 232, 0.55);
}

@media (max-width: 640px) {
  #photo-viewer .viewer-arrow { width: 40px; height: 40px; font-size: 1.3rem; }
  #photo-viewer .viewer-bar { justify-content: center; }
}

/* trail/ + ride/ ways tables: small OSM way link + "Edit in OSM" after
   the label (Sept 24/25); the row is a selector (js/waypanel.js). */
.osm-way, .osm-edit { font-family: var(--mono); font-size: 0.75rem; margin-left: 0.6em; white-space: nowrap; }
tr.way-row { cursor: pointer; }
tr.way-row:hover td { background: color-mix(in srgb, var(--border-soft) 40%, transparent); }
tr.way-row.selected td { background: var(--border-soft); box-shadow: inset 4px 0 0 var(--fg); }
tr.way-row:focus-visible { outline: 2px solid var(--link); outline-offset: -2px; }

/* the way tag panel (Sept 25): OSM tags from today's trail tiles for
   the selected way. Phone: under the map. Desktop (Derrick, Sept 25: a
   laptop is where mapping happens; map + tags are the focus): beside a
   taller map, its own scroll, so the map and the tags are read together. */
.map-stage { margin: 0.6em 0 0.4em; }
.map-stage .map-box { margin: 0 0 0.6em; }
.way-panel { border: 1px solid var(--border); padding: 10px 12px; margin: 0 0 1em; }
.way-panel-empty { margin: 0; }
@media (min-width: 1000px) {
  .map-stage { display: grid; grid-template-columns: minmax(0, 1fr) 380px; gap: 12px; align-items: stretch; }
  .map-stage .map-box { height: 72vh; min-height: 420px; margin: 0; }
  .map-stage .way-panel { height: 72vh; min-height: 420px; overflow-y: auto; margin: 0; }
}
.table-lead { margin: 0.8em 0 0.2em; }
.way-panel h3 { margin: 0 0 0.3em; color: var(--fg); text-transform: none; font-size: 1rem; }
.way-panel h3 .way-id { font-weight: 400; color: var(--muted); font-size: 0.8rem; margin-left: 0.6em; }
.way-panel p { margin: 0.3em 0; }
.way-panel table.tags { width: auto; font-size: 0.85rem; margin: 0.4em 0 0.6em; }
.way-panel table.tags td { padding: 2px 10px 2px 0; border-bottom: 1px dotted var(--border-soft); }
.way-panel table.tags td:first-child { font-family: var(--mono); color: var(--muted); white-space: nowrap; }
.way-panel h4 { font-family: var(--mono); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.03em; color: var(--muted); margin: 0.8em 0 0.3em; }

/* ride/: the owner's photos beside the tags (Sept 25) */
.legend .l-photo::before { color: #1a73e8; content: "\25CF"; }
.panel-photo-strip { display: flex; flex-wrap: wrap; gap: 6px; }
.panel-photo { width: 96px; padding: 0; border: 1px solid var(--border-soft); background: none; cursor: pointer; text-align: left; color: var(--fg); font: inherit; }
.panel-photo img { width: 96px; height: 96px; object-fit: cover; display: block; background: var(--border-soft); }
.panel-photo span { display: block; font-size: 0.65rem; line-height: 1.2; padding: 2px 3px; word-break: break-word; }
.panel-viewer { border-bottom: 1px solid var(--border-soft); margin: 0 0 0.6em; padding: 0 0 0.6em; }
.panel-viewer img { max-width: 100%; max-height: 50vh; display: block; }
.panel-viewer p { margin: 0.3em 0; }
.panel-viewer button { font-size: 0.75rem; padding: 2px 8px; }

/* trail/ thread: delete on your own posts (Sept 24) */
.thread-post .post-delete { font-size: 0.75rem; padding: 2px 8px; margin-left: 0.6rem; }

/* me/ season summary (Sept 24): the one block that answers "how many
   points" — total first, parts after. */
.season-summary { border: 1px solid var(--rule, #ccc); padding: 0.6rem 0.9rem; margin: 0.8rem 0 1.2rem; }
.season-summary h2 { margin: 0 0 0.3rem; }
.season-summary p { margin: 0; }

/* me/ (Sept 25, 0010): the two sharing switches. The per-track one sits
   in its table cell; "Show my name" is its own line under the profile. */
.switch-line { margin: 1em 0 0.2em; font-weight: 600; }
td.share-cell { white-space: nowrap; }
td.share-cell .share-line { display: inline-flex; align-items: center; gap: 0.35em; font-size: 0.85rem; }
td.share-cell .share-line input { margin: 0; }
td.share-cell .shared-link { font-family: var(--mono); font-size: 0.75rem; margin-left: 0.5em; }

/* ride/: the owner's "shared for mapping" badge (0010) */
.shared-line { font-family: var(--mono); font-size: 0.85rem; margin: 0.4em 0 0.6em; }

/* photos/ (0021): a photos-only group's own "shared for mapping" switch,
   appended inline after its <h3> heading text (js/photos.js shareSwitchEl()). */
.photo-group h3 .share-line { display: inline-flex; align-items: center; gap: 0.35em; font-size: 0.8rem; margin-left: 0.6em; vertical-align: middle; }
.photo-group h3 .share-line input { margin: 0; }
.shared-badge { display: inline-block; border: 1px solid var(--fg); padding: 1px 6px; text-transform: uppercase; letter-spacing: 0.03em; font-size: 0.75rem; }
/* shared/: legend entries */
.legend .l-track::before { color: #1a73e8; }
.legend .l-way::before { color: #9a9a9a; }

/* Map attribution pill: MapLibre's plain text inherits the page's light
   fg and vanishes on its white pill in dark mode ("Data from", and since
   Sept 26 "JuicyTrails ©"). Match MapLibre's own link colour. */
.maplibregl-ctrl-attrib, .maplibregl-ctrl-attrib a { color: rgba(0, 0, 0, 0.75); }

/* /me/ tracks table: the generic button rule (1em top margin, 8/16 px
   padding) made each Delete a 39 px block sitting low in a 66 px row,
   so it read as wrapped onto its own line (Derrick's sweep, Sept 26).
   Small in-row button, like the thread and photo-tile deletes. */
#rides-body td { vertical-align: middle; }
#rides-body .del-ride { margin: 0; padding: 3px 8px; font-size: 0.75rem; }

/* ---- front door (S2) */
body.door { padding: 0; height: 100dvh; display: flex; flex-direction: column; overflow: hidden; }
.masthead {
  padding: 8px 16px;
  border-bottom: 2px solid var(--border);
  font-family: var(--mono);
  font-size: 0.85rem;
  line-height: 1.35;
  flex: 0 0 auto;
}
.masthead p { margin: 2px 0; }
.mast-brand b {
  text-transform: uppercase;
  font-size: 1.05rem;
  letter-spacing: 0.04em;
  margin-right: 0.6em;
}
.mast-tag2 { color: var(--muted); }
.mast-doors a { text-transform: uppercase; font-weight: 700; }
/* the middot between doors is an inline-block so the link's underline
   does not run through it and the spaces around it survive collapsing */
.mast-doors a:not(:first-child)::before { content: "\00b7"; display: inline-block; margin: 0 0.45em 0 0.15em; color: var(--muted); font-weight: 400; }
.mast-util { float: right; color: var(--muted); }
@media (max-width: 640px) {
  .mast-util { float: none; display: block; }
}
.door-map { flex: 1 1 auto; min-height: 0; }

/* S8.3: retroactive credit + "mapped since this track" (shared/, ride/) */
.legend .l-mapped::before { color: var(--green); }
.unmatched-list { margin: 0.3em 0 0.6em; padding-left: 1.2em; font-family: var(--mono); font-size: 0.85rem; }

/* S7.2: "Open in iD with this trace" — one line per open off-network
   stretch, under the shared page's legend. */
.trace-links { margin: 0.3em 0 0.6em; padding-left: 1.2em; font-family: var(--mono); font-size: 0.85rem; }

/* S7.4: /region/'s "Open work in this region" queue — three short lists
   under the size warning, above the map. */
.queue-list { margin: 0.2em 0 0.8em; padding-left: 1.2em; font-size: 0.9rem; }
.queue-list li { margin: 0.15em 0; }

/* S5: /card/'s share card — deliberately dark regardless of the page's
   own light/dark theme (S5-CARD.md "Rendering": "the kiosk register:
   dark ground, near-white text, mono headings"), so a screenshot always
   reads the same. Local --card-* tokens (not the page's --bg/--fg/
   --muted, which follow prefers-color-scheme) hold those fixed values —
   js/card.js's drawCard() reads them back via getComputedStyle() so the
   downloaded PNG matches this block exactly. */
.card {
  --card-bg: #16160f;
  --card-fg: #f4f4f2;
  --card-muted: #b9b9b4;
  --card-border: #4a4a45;
  background: var(--card-bg);
  color: var(--card-fg);
  border: 1px solid var(--card-border);
  max-width: 600px;
  padding: 28px;
  margin: 0.8em 0;
}
.card p { margin: 0.35em 0; }
.card .row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  font-family: var(--mono);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.02em;
  margin-bottom: 0.6em;
}
.card .muted { color: var(--card-muted); }

.card-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.6em;
  max-width: 600px;
}
.card-actions button { margin-top: 0.4em; }

/* Processing feedback (Sept 27): "scoring…" / "reading…" / "with track
   (scoring)" text on /me/ and /photos/ while the 5-minute job hasn't
   caught up yet — see js/me.js's pendingRowHtml() and js/photos.js's
   renderCaption()/groupHeadingHtml(). */
.pulse { animation: jt-pulse 1.6s ease-in-out infinite; }
@keyframes jt-pulse { 0%, 100% { opacity: 1 } 50% { opacity: .35 } }
@media (prefers-reduced-motion: reduce) {
  .pulse { animation: none }
}

/* FIX-BATCH "Upload → landing" (Sept 29): the ?track=<id> row on /me/
   scrolls into view and outlines briefly (js/me.js highlightTrackRow()),
   fading out on its own rather than being removed by a timer alone —
   matches .photo-group.just-uploaded's dashed treatment on /photos/. */
tr.just-landed td:first-child { box-shadow: inset 3px 0 0 var(--ok); }
tr.just-landed { animation: jt-just-landed 2.5s ease-out; }
@keyframes jt-just-landed {
  0% { background: color-mix(in srgb, var(--ok) 18%, transparent); }
  100% { background: transparent; }
}
@media (prefers-reduced-motion: reduce) {
  tr.just-landed { animation: none; background: color-mix(in srgb, var(--ok) 18%, transparent); }
}
.photos-note { font-size: 0.85rem; }

/* ---- the standard sign-in block (Oct 3, the Peyton build, step 1):
   js/signin.js signInBlockHtml(), on /signin/, /me/ and /upload/'s
   gate. Google is one big button at the top; the email form is the
   quieter second way; one sentence why at the bottom. Nothing in it
   should push the Google button below a phone's first screen. */
.signin-block { max-width: 420px; margin: 0.6em 0 1.2em; }
.signin-block h2 { margin-top: 0.4em; }
.signin-block .si-provider {
  display: block; width: 100%; margin: 0 0 0.6em;
  padding: 13px 16px; font-size: 1rem;
}
.signin-block .si-or { text-align: center; margin: 0.4em 0; font-family: var(--mono); font-size: 0.8rem; text-transform: uppercase; }
.signin-block .si-email-form { display: flex; flex-wrap: wrap; gap: 0.5em; align-items: stretch; }
.signin-block .si-email-form label { flex: 0 0 100%; font-family: var(--mono); font-size: 0.8rem; text-transform: uppercase; margin: 0; }
.signin-block .si-email-form input[type="email"] { flex: 1 1 180px; min-width: 0; }
.signin-block .si-email-form .si-email-btn { margin: 0; flex: 0 0 auto; padding: 8px 12px; font-size: 0.85rem; }
.signin-block .si-why { font-size: 0.9rem; margin-top: 0.9em; }

/* /upload/ on a phone (Oct 3, Peyton build step 2): the Mac dropzone and
   Photos.app advice go away; one help line under each file input. */
body.phone .desk-only { display: none !important; }
.phone-help { font-size: 0.9rem; margin: 0.35em 0 0.6em; }
.phone-help p { margin: 0.3em 0; }
.phone-help details { margin: 0.3em 0; }
.phone-help summary { cursor: pointer; font-family: var(--mono); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.02em; }
.phone-help ol { padding-left: 1.3em; margin: 0.4em 0; }
.phone-help li { margin: 0.25em 0; }
.phone-help code { font-family: var(--mono); font-size: 0.85em; }

/* The one line nobody can miss (Oct 3, Peyton build step 3): "Uploaded:
   1 track, 8 photos." on /upload/ and again at the top of /me/. */
.notice.big { font-size: 1.05rem; padding: 14px 12px; border-style: solid; border-width: 2px; }

/* Tables on phones (Oct 3, Peyton build step 5 — Derrick: "the table
   does not seem to scroll sideways on the phone. Does not."): html/body
   clip overflow-x, so a wide table was simply cut off at POINTS. The
   wrapper scrolls sideways on its own. */
.table-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; margin: 0.6em 0 1.2em; }
.table-scroll > table { margin: 0; min-width: 100%; }
@media (max-width: 640px) {
  .table-scroll > table { font-size: 0.85rem; white-space: nowrap; }
  .table-scroll th, .table-scroll td { padding: 5px 6px; }
}

/* ---- landing (Oct 3, Peyton build step 4 — D1 revised: hero first).
   Preview at /door/ until Derrick has looked. One brand colour, the
   ~2016 icon's orange (s9/refs), as a token so it can move. The map is
   the image; the headline sits in solid colour blocks so it reads over
   anything (the browserbase reference's one technique). */
:root { --orange: #ff8000; --orange-ink: #fff; }   /* Oct 5 (Derrick): the map's Bicycle + Horse trail orange — the 2016 meaning restored; was #e8461e */
body.landing { padding: 0 0 48px; padding-bottom: calc(48px + env(safe-area-inset-bottom)); }
.land-head {
  display: flex; align-items: center; justify-content: space-between; gap: 1em;
  padding: 10px 16px; border-bottom: 2px solid var(--border);
  padding-top: calc(10px + env(safe-area-inset-top));
  padding-left: max(16px, env(safe-area-inset-left)); padding-right: max(16px, env(safe-area-inset-right));
}
/* Oct 3: the Blender logo (juicytrails-logo-assets/juicytrails-blender-logo)
   + the wordmark set exactly like the hero block (mono bold, white on
   --orange). Dark space between the two and to the left of the logo. */
.land-brand { display: inline-flex; align-items: center; gap: 0.7em; text-decoration: none; color: var(--fg); flex-shrink: 0; }
/* Oct 4 eve (Derrick, phone screenshot): the wordmark broke "Jui / cyTrails" at
   iPhone width. The brand never shrinks or wraps; the nav links take the fold. */
.land-brand .word { white-space: nowrap; }
.land-nav { flex-wrap: wrap; justify-content: flex-end; row-gap: 0.2em; }
.land-brand .word {
  font-family: var(--mono); font-weight: 700; text-transform: none; letter-spacing: 0;
  font-size: 1.05rem; line-height: 1.3; padding: 0.08em 0.3em;
  background: var(--orange); color: var(--orange-ink);
}
.land-brand .mark { width: auto; height: 34px; display: block; }
/* Oct 5 (Derrick + Payton's pick, logo poll G): the J sign replaces the
   blender, and the cut wordmark (img/j-wordmark.svg, Plex Mono Bold outlines,
   both i's facing the same way) replaces the CSS-set one. The fingerpost
   points at the name. Blender files stay in img/ untouched. */
.land-brand { gap: 0.45em; }
.land-brand .wordmark { width: auto; height: 26px; display: block; }
/* Oct 4: the wordmark's two i's read as eyes; the first is mirrored so
   its serif faces the second — "two people". Inline-block keeps the mono
   advance; the glyph flips inside its own cell. */
.word .eye { display: inline-block; transform: scaleX(-1); }
.land-nav { display: flex; gap: 1em; font-family: var(--mono); font-size: 0.85rem; text-transform: uppercase; }
.land-nav a { text-decoration: underline; }
/* Oct 4 eve (Derrick, next change): every inner page wears the landing's
   header — logo + wordmark, MAP · BOARDS · MAPPING · UPLOAD, then the
   who-slot (name · handle ⚙, or Sign in). Photos/Rules/Privacy moved to the
   footer. The body keeps its 16px gutter, so the bar bleeds out to the edges. */
.land-head.inner { margin: 0 -16px 14px; }
.land-head .nav-who { margin-left: 0; font-family: var(--mono); font-size: 0.85rem; white-space: nowrap; text-transform: uppercase; }
.land-head .nav-who .muted, .land-head .nav-who > a { text-transform: none; }
.land-nav { min-width: 0; }
/* Phone: brand + name on the top row, the four links on their own row
   underneath (the signed-in bar had stacked to three ragged lines). The
   handle shows from tablet up; the name and gear are enough in the bar. */
@media (max-width: 640px) {
  .land-head { flex-wrap: wrap; row-gap: 8px; }   /* landing too (Oct 5, Derrick's yes) */
  .land-head .nav-who { order: 2; margin-left: auto; }
  .land-head .land-nav { order: 3; flex-basis: 100%; justify-content: flex-start; gap: 1.1em; font-size: 0.85rem; }
  .land-head .nav-who .muted { display: none; }
}

.hero { position: relative; min-height: 72vh; min-height: 72svh; overflow: hidden; border-bottom: 2px solid var(--border); }
/* Oct 5 (Derrick, phone): `vh` on iOS Safari is the height WITH the toolbar
   hidden, so the hero overran the visible screen and the numbers strip
   started under the bottom bar. `svh` is the height with the bar showing —
   the hero now ends above it, with the strip in view on first paint. */
/* Oct 4: MapLibre's own stylesheet sets .maplibregl-map { position: relative },
   which beat this rule while it loaded after site.css (the map rendered at
   0 px tall — the hero had no image). Load order fixed in index.html and
   the position is pinned here so no later stylesheet can take it again. */
.hero-map, .hero-map.maplibregl-map { position: absolute; inset: 0; }
.hero-map.failed { background: var(--border-soft); }
/* Oct 3: the column is wide enough for "Trail Mapping Community" on one
   line at the largest type size; the prose under it keeps the 720 measure. */
.hero-text { position: relative; z-index: 2; padding: 10vh 16px 7vh; max-width: 960px; }
.hero .sub { max-width: 688px; }
.hero h1 {
  font-family: var(--mono); text-transform: none; letter-spacing: 0;
  font-size: clamp(1.7rem, 5.2vw, 3.2rem); line-height: 1.3; margin: 0 0 0.5em; border: 0; padding: 0;
}
.hero h1 .block {
  display: table; background: var(--orange); color: var(--orange-ink);
  padding: 0.08em 0.3em; margin: 0 0 0.12em;   /* one orange box per line (Oct 3) */
}
.hero .sub {
  display: table; background: var(--fg); color: var(--bg);   /* own line, shrink-to-fit (Oct 3) */
  font-size: clamp(1rem, 2.6vw, 1.25rem); padding: 0.2em 0.5em; margin: 0 0 1.1em;
}
.hero-doors { display: flex; flex-wrap: wrap; gap: 0.6em; margin: 0; }
.hero-doors .btn {
  font-family: var(--mono); text-transform: uppercase; letter-spacing: 0.04em; text-decoration: none;
  padding: 12px 20px; border: 2px solid var(--fg); font-size: 1rem;
}
/* Oct 4: the first door is the action (Upload, blue — "go do the thing"
   is blue, palette rule); the second is the look (See the boards / Me),
   black with a white rule. */
.hero-doors .btn.primary { background: var(--link); color: #16160f; border-color: var(--link); }
/* Oct 4 eve (Derrick, from the phone): the headline says "Fresh Trail Map",
   so the map needs the most prominent door. It leads the row and takes the
   whole row, in the headline's orange. Trial: ?mapdoor=white|black|off on the
   URL tries the other two palette colours or hides it (he decides by seeing). */
.hero-doors .btn.map { flex-basis: 100%; text-align: center; background: var(--orange); color: var(--orange-ink); border-color: var(--orange); font-weight: 700; }
.hero-doors .btn.map.white { background: #fff; color: #16160f; border-color: #fff; }
.hero-doors .btn.map.black { background: var(--bg); color: var(--fg); border-color: var(--fg); }
.hero-doors .btn.map.off { display: none; }
.hero-doors .btn.second { background: var(--bg); color: var(--fg); }
.hero .maplibregl-ctrl { display: none; }

.land-numbers {
  display: flex; flex-wrap: wrap; gap: 6px 1.4em; align-items: baseline;
  font-family: var(--mono); font-size: 0.9rem; padding: 12px 16px; border-bottom: 1px solid var(--border-soft);
}
.land-numbers b { font-size: 1.15rem; color: var(--orange); }
.land-about { padding: 14px 16px 0; max-width: 720px; }
/* Oct 4 eve (Derrick): the "What this is" label went — the hero says what
   it is; the first sentence leads the section. Photos first in the steps
   (photos are the evidence, the phone door says Upload photos). */
.land-about .lede { margin-top: 0.4em; }
.land-about .verbs { padding-left: 1.3em; margin: 0.6em 0 1em; }
.land-about .verbs li { margin: 0.45em 0; }
.land-about .small { font-size: 0.85rem; }
body.landing footer.kiosk-foot { margin: 2em 16px 0; }
@media (max-width: 640px) {
  .hero { min-height: 74vh; min-height: 74svh; }
  .hero-text { padding: 7vh 16px 6vh; }
  .land-nav { gap: 0.7em; font-size: 0.8rem; }
}

/* /upload/ when signed in (Oct 3): who is uploading, in one line. */
.signed-in-line { font-family: var(--mono); font-size: 0.85rem; margin: 0.4em 0 0.8em; }
.signed-in-line .linkish { background: none; border: 0; padding: 0; margin: 0; color: var(--link); text-decoration: underline; font: inherit; text-transform: none; letter-spacing: 0; cursor: pointer; }

/* /me/ Phone shortcut (Oct 3): the token line */
.shortcut-steps { padding-left: 1.3em; }
.shortcut-steps li { margin: 0.4em 0; }
.shortcut-steps button { margin-top: 0; padding: 6px 12px; font-size: 0.8rem; }
#upload-token { width: 60%; max-width: 360px; font-family: var(--mono); font-size: 0.8rem; display: inline-block; }

.gpx-how { margin: 0.35em 0 0.6em; }

/* "In development" notice (Derrick, Oct 3: say it plainly — tracks from a
   computer, photos from the phone) on the old front door (/map/). Oct 4:
   gone from the hero — /upload/ already says it where it is useful. */
.mast-note { color: var(--muted); }

/* ------------------------------------------------- Boards (Oct 3 2026)
   Four leaderboards that look like leaderboards, not tables: rank, name,
   a blue bar as a share of the leader, the number big. Top three ranks
   sit in orange. body.no-bars / body.no-top3 are the LOOK switches. */
body.boards h1 .muted { font-size: 0.85rem; font-weight: 400; margin-left: 0.6em; }
.look-strip { font-family: var(--mono); font-size: 0.8rem; color: var(--muted); display: flex; flex-wrap: wrap; gap: 0.4em 1.2em; margin: 0 0 0.4em; }
.look-strip label { display: inline-flex; align-items: center; gap: 0.35em; cursor: pointer; }
.look-strip input { margin: 0; }
.board { margin: 1.6em 0 0; }
.board h2 { margin: 0 0 0.15em; }
.board > .stub { margin: 0 0 0.5em; }
.lb { list-style: none; margin: 0.3em 0 0.5em; padding: 0; border-top: 2px solid var(--border); }
.lb-caption { font-family: var(--mono); font-size: 0.8rem; color: var(--muted); margin: 0.9em 0 0; }
.lb-row {
  display: grid;
  grid-template-columns: 2.6em minmax(9em, 1fr) 2fr auto;
  grid-template-areas: "rank name bar val" "rank sub bar val";
  align-items: center; column-gap: 0.9em;
  padding: 9px 4px; border-bottom: 1px solid var(--border-soft);
}
.lb-rank {
  grid-area: rank; align-self: stretch; display: flex; align-items: center; justify-content: center;
  font-family: var(--mono); font-size: 1.1rem; color: var(--muted);
}
.lb-name { grid-area: name; font-weight: 700; }
.lb-name .lb-osm { font-family: var(--mono); font-weight: 400; font-size: 0.8rem; margin-left: 0.5em; }
.lb-sub { grid-area: sub; color: var(--muted); font-size: 0.78rem; font-family: var(--mono); }
.lb-bar { grid-area: bar; height: 10px; background: color-mix(in srgb, var(--border-soft) 45%, transparent); }
.lb-bar i { display: block; height: 100%; background: var(--link); }
.lb-val { grid-area: val; font-family: var(--mono); font-size: 1.35rem; font-weight: 700; text-align: right; white-space: nowrap; min-width: 4.5em; }
.lb-val small { font-size: 0.65rem; font-weight: 400; color: var(--muted); letter-spacing: 0.03em; }
.lb-empty { padding: 10px 4px; font-size: 0.9rem; }
.rank-1 .lb-rank, .rank-2 .lb-rank, .rank-3 .lb-rank { background: var(--orange); color: #fff; font-weight: 700; }
.rank-1 .lb-val { color: var(--orange); }
body.no-top3 .lb-rank { background: none; color: var(--muted); font-weight: 400; }
body.no-top3 .rank-1 .lb-val { color: inherit; }
body.no-bars .lb-bar { display: none; }
body.no-bars .lb-row { grid-template-columns: 2.6em 1fr auto; grid-template-areas: "rank name val" "rank sub val"; }
.mode-switch { display: flex; flex-wrap: wrap; gap: 0.4em; margin: 0.2em 0 0.4em; }
.mode-switch button {
  font: inherit; font-family: var(--mono); font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.03em;
  background: none; color: var(--fg); border: 1px solid var(--border-soft); padding: 4px 10px; margin: 0; cursor: pointer;
}
.mode-switch button[aria-pressed="true"] { background: var(--fg); color: var(--bg); border-color: var(--fg); }
@media (max-width: 640px) {
  .lb-row { grid-template-columns: 2.2em 1fr auto; grid-template-areas: "rank name val" "rank sub val" "rank bar bar"; row-gap: 2px; }
  body.no-bars .lb-row { grid-template-columns: 2.2em 1fr auto; }
  .lb-val { font-size: 1.15rem; min-width: 0; }
}

/* ------------------------------------ kiosk nav brand + who (Oct 3 2026)
   Every inner page: the Blender logo + wordmark as the brand link, and
   the signed-in name · handle at the right end of the same row. */
.kiosk-nav { align-items: center; }
.kiosk-nav .brand { display: inline-flex; align-items: center; gap: 0.5em; text-decoration: none; text-transform: none; margin-right: 0.8em; }
.kiosk-nav .brand .mark { height: 30px; width: auto; display: block; }
.kiosk-nav .brand .word { font-weight: 700; background: var(--orange); color: var(--orange-ink); padding: 0.08em 0.3em; line-height: 1.3; }
.kiosk-nav .brand:hover .mark { opacity: 0.85; }
.nav-who { margin-left: auto; white-space: nowrap; }

/* ------------------------------------------- /upload/ two boxes (Oct 3)
   Tracks and photos are independent (Sept 29): one box each, a blue
   file button, a fold-out with the help, its own Upload button. */
.upload-boxes { display: grid; grid-template-columns: 1fr 1fr; gap: 16px; margin: 0.8em 0; }
@media (max-width: 760px) { .upload-boxes { grid-template-columns: 1fr; } }
.upload-box { border: 1px solid var(--border); padding: 14px 16px 16px; display: flex; flex-direction: column; gap: 0.6em; }
.upload-box h2 { margin: 0; }
.upload-box .lead { margin: 0; color: var(--muted); font-size: 0.9rem; }
.upload-box.over { border-color: var(--link); box-shadow: inset 0 0 0 1px var(--link); }
.file-pick { display: flex; flex-wrap: wrap; align-items: center; gap: 0.6em; }
.file-pick input[type="file"] { position: absolute; width: 1px; height: 1px; opacity: 0; overflow: hidden; }
.file-name { font-family: var(--mono); font-size: 0.85rem; color: var(--muted); word-break: break-all; }
.file-name.busy { color: var(--warn); word-break: normal; }
.btn-blue, .upload-box button[type="submit"] {
  font: inherit; font-family: var(--mono); text-transform: uppercase; letter-spacing: 0.03em;
  background: var(--link); color: #16160f; border: 1px solid var(--link); padding: 8px 16px; margin: 0; cursor: pointer;
  display: inline-block;
}
.btn-blue:hover, .upload-box button[type="submit"]:hover:not([disabled]) { opacity: 0.85; }
.upload-box button[type="submit"][disabled] { opacity: 0.5; cursor: default; }
.upload-box .mode-line { display: flex; align-items: center; gap: 0.6em; }
.upload-box .mode-line label { margin: 0; font-family: var(--mono); font-size: 0.78rem; text-transform: uppercase; color: var(--muted); }
.upload-box .mode-line select { width: auto; margin: 0; }
.upload-box details { font-size: 0.9rem; }
.upload-box summary { cursor: pointer; font-family: var(--mono); font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.02em; color: var(--link); text-decoration: underline; }
.upload-box details p, .upload-box details ol { margin: 0.4em 0; }
.upload-box details ol { padding-left: 1.3em; }
.upload-box .drop-hint { color: var(--muted); font-size: 0.85rem; border: 2px dashed var(--border-soft); padding: 0.8em; text-align: center; margin: 0; }
.upload-box.over .drop-hint { border-color: var(--link); color: var(--fg); }
.upload-box label.checkline { margin: 0.2em 0 0; font-size: 0.85rem; }
.upload-box .photo-list { margin: 0; }
.upload-progress { margin: 0.6em 0; }

/* ------------------------------------------ settings gear + panel (Oct 3)
   The gear sits beside the who-slot; on /me/ it opens the Settings panel
   (display name, boards switches, mapping switch + unlink, sign out). */
.nav-who { display: inline-flex; align-items: center; gap: 0.5em; }
.nav-gear { display: inline-flex; align-items: center; color: var(--orange); text-decoration: none; opacity: 0.9; }
.nav-gear:hover { opacity: 1; text-decoration: none; }
.nav-gear svg { display: block; }
.settings-panel { border: 1px solid var(--border); padding: 12px 16px 16px; margin: 8px 0 16px; max-width: 560px; }
.settings-panel h2 { margin: 0 0 0.4em; }
.settings-panel h3 { margin: 1.2em 0 0.3em; font-size: 0.9rem; }
.settings-panel form.kiosk-form { margin: 0; }
.settings-panel button { margin-top: 0.6em; }
#milestones-fold summary { cursor: pointer; font-family: var(--mono); font-weight: 700; text-transform: uppercase; font-size: 1.05rem; }
#milestones-fold[open] summary { margin-bottom: 0.4em; }

/* ------------------------------------------------ usernames are orange (Oct 3)
   Derrick: a person's name reads in the brand orange — the Me heading,
   the nav's who-slot, names on the boards and the profile heading.
   Links keep their underline so a name that goes somewhere still says so. */
#me-name, #profile-name { color: var(--orange); }
.nav-who > a:first-child { color: var(--orange); }
.lb-name, .lb-name > a { color: var(--orange); }

/* ------------------------------------------- phone: photos first (Oct 3)
   On a phone the Sign photos box comes first and the Track box says
   tracks are a computer job; the landing's door says Upload photos. */
.phone-note { display: none; }
body.phone .phone-note { display: block; margin: 0; color: var(--warn); font-size: 0.9rem; }
body.phone .upload-boxes { display: flex; flex-direction: column; }
body.phone #photos-form { order: -1; }

/* ------------------------------------------ tracks table flags (Oct 3)
   A row that needs a word (rejected, duplicate) gets a ⚠ next to its
   points; tapping it opens one note row underneath. The table itself
   stays essentials-only and a consistent width. */
.row-flag { font: inherit; background: none; border: 0; padding: 0 0.2em; margin: 0; color: var(--warn); cursor: pointer; line-height: 1; }
.row-flag[aria-expanded="true"] { color: var(--fg); }
tr.note-row td { color: var(--muted); font-size: 0.85rem; white-space: normal; font-family: var(--sans); padding-top: 2px; }
