/* ── Map view ── */

.map-view {
  width: 100%;
  /* Fill the viewport below the sticky chrome. */
  height: calc(100vh - var(--chrome-h));
  isolation: isolate;
}

/* ── Feed ── */

/* The shared page column. Every routed view except the full-bleed map and
   detail pages renders inside it. NOTE: this is a column flex container —
   children must use `width: 100%` (plus their own `max-width`) rather than
   bare `margin: 0 auto`, which would shrink them to fit-content. */
.feed {
  max-width: 640px;
  margin: 0 auto;
  padding: 20px 16px;
  display: flex;
  flex-direction: column;
  gap: 16px;
}

@media (min-width: 1024px) {
  .feed {
    max-width: 720px;
  }

  /* Dashboard-style views (You) get more room to spread out. */
  .feed.feed-wide {
    max-width: 960px;
  }
}

.activity-feed {
  display: flex;
  flex-direction: column;
  gap: 16px;
  width: 100%;
  /* Opt the wall in to browser scroll anchoring explicitly so that prepended
     cards (bulk uploads, live sync) don't push the reader's current card
     downward. A fallback JS effect in ActivityFeed handles the remaining gap
     when the browser suppresses anchoring. */
  overflow-anchor: auto;
}

/* Trailing target for the wall's infinite-scroll observer. It must take part
   in layout (a `display: none` element never intersects), but it should not
   draw anything. */
.feed-sentinel {
  height: 1px;
  /* Never let the sentinel become a scroll anchor: anchoring on it would
     fight the reveal of the next page. */
  overflow-anchor: none;
}

.empty-state {
  text-align: center;
  padding: 60px 20px;
  color: var(--text-subtle);
  line-height: 1.8;
}

.empty-state code {
  background: var(--border);
  padding: 1px 5px;
  border-radius: 4px;
  font-size: 0.9em;
}

/* ── Activity card ── */

.activity-card {
  background: var(--surface);
  border-radius: 14px;
  overflow: hidden;
  box-shadow: var(--shadow-sm);
  cursor: pointer;
  /* Skip layout and paint for cards outside the viewport so a wall of
     thousands stays cheap to render. `auto` remembers each card's real height
     once measured; the fallback estimate keeps the scrollbar stable before
     then. Pairs with the per-card map-bytes release in ActivityMapCard (which
     observes the article box directly — content-visibility hides the subtree,
     not the article's own box — so viewport tracking stays accurate). */
  content-visibility: auto;
  contain-intrinsic-size: auto 340px;
}

.activity-card:hover {
  box-shadow: var(--shadow-md);
  transition: box-shadow 0.15s;
}

.card-header {
  padding: 16px 16px 12px;
}

.activity-meta {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
}

.activity-type-badge {
  background: var(--brand-soft);
  color: var(--brand);
  padding: 3px 10px;
  border-radius: 20px;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.2px;
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

@media (prefers-color-scheme: dark) {
  .activity-type-badge {
    color: var(--brand);
  }

  .card-map-placeholder.no-gps {
    background: #2a2a2a;
  }

  .map-no-gps {
    background: #2a2a2a;
  }
}

.activity-date {
  color: var(--text-subtle);
  font-size: 0.8rem;
}

.s3-badge {
  margin-left: auto;
  display: inline-flex;
  align-items: center;
}

.s3-icon {
  width: 14px;
  height: 14px;
}

.s3-synced {
  color: var(--success);
  opacity: 0.75;
}

.s3-unsynced {
  color: var(--accent);
  opacity: 0.9;
}

.shared-card {
  border-left: 3px solid var(--accent);
}

.owner-badge {
  margin-left: 6px;
  font-size: 0.65rem;
  font-weight: 600;
  letter-spacing: 0.3px;
  color: var(--accent);
  background: var(--accent-soft);
  padding: 1px 6px;
  border-radius: 3px;
}

.shared-badge {
  margin-left: 6px;
  font-size: 0.65rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: #5c6bc0;
  background: rgba(92, 107, 192, 0.12);
  padding: 1px 5px;
  border-radius: 3px;
}

.activity-title {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--text);
  line-height: 1.3;
}

/* ── Map (cards + detail shared) ── */

.maplibre-map {
  height: 400px;
  width: 100%;
}

.card-map-img {
  height: 200px;
  width: 100%;
  object-fit: cover;
  animation: card-map-fade-in 160ms ease-out;
}

@keyframes card-map-fade-in {
  from { opacity: 0; }
  to { opacity: 1; }
}

/* Shown while there's no pre-rendered static map PNG to display. Matches
   the img's dimensions so swapping in the real map doesn't shift layout.
   Two variants distinguish "will never have one" from "still coming" —
   see `.no-gps` and `.pending` below. */
.card-map-placeholder {
  height: 200px;
  width: 100%;
  background: var(--surface-alt);
}

/* No GPS trace (treadmill, manual entry, etc.) — nothing will ever render
   here, so show the wordmark instead of an empty box. Neutral gray rather
   than the moss-tinted --surface-alt, so it reads as "no data" and not as
   a colored status. */
.card-map-placeholder.no-gps {
  display: flex;
  align-items: center;
  justify-content: center;
  background: #f0f0f0;
}

.card-map-placeholder.no-gps img {
  width: 56px;
  height: 56px;
  border-radius: 12px;
}

/* A GPS trace exists but the static map PNG hasn't arrived yet (fresh
   device, cold IDB, S3 round-trip, first-time client render, or a failed
   render pending next-session retry). */
.card-map-placeholder.pending {
  background: linear-gradient(
    100deg,
    var(--surface-alt) 30%,
    var(--brand-soft) 50%,
    var(--surface-alt) 70%
  );
  background-size: 200% 100%;
  animation: card-map-pending-shimmer 1.8s ease-in-out infinite;
}

@keyframes card-map-pending-shimmer {
  0% { background-position: 200% 0; }
  100% { background-position: -200% 0; }
}

@media (prefers-reduced-motion: reduce) {
  .card-map-placeholder.pending {
    animation: none;
  }
}

/* Anchors the media thumbnail over the card's map preview. */
.card-media {
  position: relative;
}

/* Media deck in the map's corner: the first-photo thumbnail in front, one
   backing card peeking out per further attachment (five cards at most). */
.card-photo-deck {
  position: absolute;
  left: 10px;
  bottom: 10px;
  width: 56px;
  height: 56px;
}

/* First-photo thumbnail, the deck's front card. Doubles as a plain "has
   media" indicator (glyph on --surface-alt) while the bytes are in flight,
   when the fetch failed, or when the activity's media is video-only. */
.card-photo-thumb {
  position: absolute;
  inset: 0;
  border-radius: 10px;
  background: var(--surface-alt);
  outline: 2px solid var(--surface);
  box-shadow: var(--shadow-sm);
  display: flex;
  align-items: center;
  justify-content: center;
}

.card-photo-thumb img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: inherit;
  animation: card-map-fade-in 160ms ease-out;
}

.card-photo-glyph {
  display: inline-flex;
  color: var(--text-muted);
  opacity: 0.75;
}

.card-photo-glyph .at-icon {
  width: 20px;
  height: 20px;
  vertical-align: 0;
}

/* Backing cards of the deck, fanned up-right behind the thumbnail; --layer
   (1 = nearest the front) sets each card's offset and slight tilt. */
.card-photo-deck-card {
  position: absolute;
  inset: 0;
  border-radius: 10px;
  background: var(--surface-alt);
  outline: 2px solid var(--surface);
  box-shadow: var(--shadow-sm);
  transform: translate(calc(var(--layer) * 4px), calc(var(--layer) * -4px))
    rotate(calc(var(--layer) * 2deg));
}

.terrain-toggle {
  position: absolute;
  top: 10px;
  right: 10px;
  z-index: 1;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  padding: 10px 14px;
  min-height: 44px;
  min-width: 44px;
  cursor: pointer;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text);
}

.terrain-toggle:hover {
  background: var(--surface-alt);
}

/* Labelled slide switch overlaid on the map corner (show/hide virtual
   activities). A switch, deliberately unlike the 3D push-button beside it. */
/* The corner switches, stacked under one another so each stays a 44px
   target; the 3D toggle sits to their right. */
.map-switches {
  position: absolute;
  top: 10px;
  right: 64px;
  z-index: 1;
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: stretch;
}

.map-switch {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 8px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 4px;
  padding: 0 12px;
  min-height: 44px;
  cursor: pointer;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text);
  user-select: none;
}

.map-switch:hover {
  background: var(--surface-alt);
}

/* Visually hidden but focusable; drives the track/thumb via :checked. */
.map-switch-input {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  margin: 0;
}

.map-switch-track {
  position: relative;
  flex: none;
  width: 36px;
  height: 20px;
  border-radius: 999px;
  background: var(--border-strong);
  transition: background 0.15s ease;
}

.map-switch-thumb {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: var(--shadow-sm);
  transition: transform 0.15s ease;
}

.map-switch-input:checked + .map-switch-track {
  background: var(--brand);
}

.map-switch-input:checked + .map-switch-track .map-switch-thumb {
  transform: translateX(16px);
}

.map-switch-input:focus-visible + .map-switch-track {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
}

.map-no-gps {
  height: 200px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #f0f0f0;
  color: var(--text-subtle);
  font-size: 0.82rem;
}

/* ── Search & Filters ── */

.search-bar {
  background: var(--surface);
  border-radius: 14px;
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}

.search-input-row {
  display: flex;
  align-items: center;
  padding: 10px 14px;
  gap: 10px;
}

.search-icon {
  width: 16px;
  height: 16px;
  flex-shrink: 0;
  color: var(--text-subtle);
}

.search-input-row input[type="text"] {
  flex: 1;
  border: none;
  outline: none;
  font-size: 0.95rem;
  font-family: inherit;
  background: transparent;
  color: var(--text);
  min-width: 0;
}

.search-clear-btn {
  background: none;
  border: none;
  color: var(--text-subtle);
  font-size: 0.9rem;
  cursor: pointer;
  padding: 2px 4px;
  line-height: 1;
}

.filter-toggle-btn {
  background: var(--accent-soft);
  border: 1px solid var(--border-strong);
  border-radius: 16px;
  padding: 4px 12px;
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--accent);
  cursor: pointer;
  white-space: nowrap;
  display: flex;
  align-items: center;
  gap: 5px;
  flex-shrink: 0;
  transition: background 0.15s;
}

.filter-toggle-btn:hover {
  background: var(--accent-soft);
  filter: brightness(0.95);
}

.filter-toggle-active {
  background: var(--accent);
  color: #fff;
  border-color: var(--accent);
}

.filter-toggle-active:hover {
  background: var(--accent-hover);
  filter: none;
}

.filter-badge {
  background: var(--brand);
  color: #fff;
  font-size: 0.7rem;
  font-weight: 700;
  border-radius: 50%;
  width: 18px;
  height: 18px;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
}

.filter-toggle-active .filter-badge {
  background: #fff;
  color: var(--accent);
}

.filter-panel {
  max-height: 0;
  overflow: hidden;
  transition: max-height 0.3s ease, padding 0.3s ease;
  padding: 0 14px;
}

.filter-panel.open {
  max-height: 500px;
  padding: 0 14px 14px;
}

.filter-section {
  margin-bottom: 12px;
}

.filter-label {
  font-size: 0.78rem;
  font-weight: 600;
  color: var(--text-muted);
  margin-bottom: 6px;
}

.type-chips,
.date-presets,
.virtual-row {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
}

.type-chip {
  padding: 5px 12px;
  border-radius: 20px;
  font-size: 0.78rem;
  font-weight: 600;
  cursor: pointer;
  border: 1.5px solid var(--border-strong);
  background: var(--surface);
  color: var(--text-muted);
  transition: all 0.15s;
  font-family: inherit;
}

.type-chip:hover {
  border-color: var(--accent);
  color: var(--accent);
}

.type-chip.active {
  background: var(--accent-soft);
  color: var(--accent);
  border-color: var(--accent);
}

.custom-date-row {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}

.custom-date-row input[type="date"] {
  padding: 6px 8px;
  border: 1.5px solid var(--border-strong);
  border-radius: 8px;
  font-size: 0.85rem;
  font-family: inherit;
  outline: none;
  background: var(--surface);
  color: var(--text);
  flex: 1;
  min-width: 0;
}

.custom-date-row input[type="date"]:focus {
  border-color: var(--accent);
}

.range-row {
  display: flex;
  gap: 12px;
  flex-wrap: wrap;
}

.range-group {
  flex: 1;
  min-width: 120px;
}

.range-inputs {
  display: flex;
  align-items: center;
  gap: 6px;
}

.range-inputs input {
  width: 100%;
  min-width: 0;
  flex: 1;
  padding: 6px 8px;
  border: 1.5px solid var(--border-strong);
  border-radius: 8px;
  font-size: 0.85rem;
  font-family: inherit;
  outline: none;
  background: var(--surface);
  color: var(--text);
}

.range-inputs input:focus {
  border-color: var(--accent);
}

.range-sep {
  color: var(--text-subtle);
  font-size: 0.85rem;
  flex-shrink: 0;
}

.no-match-state {
  text-align: center;
  padding: 40px 20px;
  color: var(--text-subtle);
}

.no-match-state p {
  margin-bottom: 12px;
}

.clear-filters-btn {
  background: none;
  border: none;
  color: var(--accent);
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  text-decoration: underline;
  font-family: inherit;
}

/* ── Route tab (src/route/) ── */

.route-overlay {
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: 16px;
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  pointer-events: none;
}

.route-overlay > * {
  pointer-events: auto;
}

.route-status {
  margin: 0;
  padding: 6px 14px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  color: var(--text-muted);
  font-size: 0.85rem;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

.route-totals {
  display: flex;
  gap: 16px;
  padding: 8px 18px;
  border-radius: 12px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  color: var(--text);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

.route-actions {
  display: flex;
  gap: 8px;
}

/* Unsaved edits live in the tab and nowhere else, so the editor says so
   plainly rather than leaving it to be inferred from the Save button. */
.route-unsaved {
  margin: 0;
  padding: 6px 14px;
  border-radius: 999px;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  color: var(--text-muted);
  font-size: 0.85rem;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

/* The one action on this surface that writes: given the weight of a primary
   button so it reads as the way out of the unsaved state. */
.route-save-btn {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
  font-weight: 600;
}

.route-save-btn:hover:not(:disabled) {
  background: var(--brand);
  filter: brightness(1.08);
}

.route-save-btn:disabled {
  opacity: 0.55;
  cursor: default;
}

.route-btn,
.route-mode-btn {
  padding: 6px 16px;
  border-radius: 999px;
  border: 1px solid var(--border-strong);
  background: var(--surface);
  color: var(--text);
  font-size: 0.85rem;
  cursor: pointer;
}

.route-btn:hover,
.route-mode-btn:hover {
  background: var(--brand-soft);
}

.route-mode {
  display: flex;
  gap: 4px;
}

.route-mode-btn.active {
  background: var(--brand);
  border-color: var(--brand);
  color: #fff;
}

.route-marker {
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: var(--brand);
  border: 2px solid #fff;
  color: #fff;
  font-size: 0.7rem;
  font-weight: 700;
  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.3);
}

/* Fills the .feed page column (which provides the outer padding). */
/* ── Map tab panels (routes above, activities below) ── */

/* The stack the two panels live in, laid over the map's left edge under
   MapLibre's own compass control; a pair of bottom sheets on narrow screens.
   It is only as tall as the panels it holds, and passes clicks through
   everywhere they are not — the gap between the two is map, not chrome. */
.map-panels {
  position: absolute;
  top: 64px;
  left: 10px;
  z-index: 1;
  width: min(340px, calc(100% - 20px));
  max-height: calc(100% - 84px);
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 10px;
  pointer-events: none;
}

/* One panel of the stack: as tall as its content and no taller, shrinking
   when the two together overrun the stack. `min-height: 0` is what lets it
   shrink at all — without it a long list would push the other panel off the
   map instead of scrolling inside its own. */
.map-panel {
  flex: 0 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  box-shadow: var(--shadow-sm);
  overflow: hidden;
  pointer-events: auto;
}

/* A row rather than one control: while a route is open the routes panel's
   header is the editor's, and carries the way back beside the name that folds
   the list. */
.map-panel-head {
  display: flex;
  align-items: center;
  gap: 8px;
  border-bottom: 1px solid var(--border);
}

/* The back control brings its own left padding with it; without one the
   toggle is the whole row and provides its own. */
.map-panel-head:has(.route-back-btn) {
  padding-left: 8px;
}

.map-panel-toggle {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 10px 14px;
  min-height: 44px;
  background: none;
  border: none;
  color: var(--text);
  font: inherit;
  font-weight: 600;
  cursor: pointer;
  text-align: left;
}

/* The same header row with nothing behind it to fold: the routes switch is
   off and this panel is an open route's name and the way back (see
   activity.allium's LayersAreDrawnApart). Text, so it says so — no pointer,
   no hover, nothing offering a library that is not there. */
span.map-panel-toggle-static {
  cursor: default;
}

.map-panel.collapsed .map-panel-head {
  border-bottom: none;
}

.map-panel-count {
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.map-panel .route-library,
.map-panel .map-activity-list {
  padding: 12px;
  overflow-y: auto;
  min-height: 0;
}

/* Folding is the `hidden` attribute on the list, and the browser's own
   [hidden] rule loses to any `display` an author sets — which .route-library
   does. Say it here, where the fold is, or a folded panel keeps its list and
   the two of them cover the map they are laid over. */
.map-panel [hidden] {
  display: none;
}

/* The activities panel's rows: one line of sport, title, date and distance,
   with no thumbnail — the map behind the panel is the thumbnail. */
.map-activity-rows {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.map-activity-open {
  width: 100%;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 6px 4px;
  background: none;
  border: none;
  border-radius: 8px;
  color: inherit;
  font: inherit;
  cursor: pointer;
  text-align: left;
}

.map-activity-open:hover {
  background: var(--brand-soft);
}

/* The row half of the row/line link (#944): pointing at either a row or its
   line on the map lights both, so the two name each other where a dozen lines
   in view otherwise name nothing. Said on the row rather than on the button
   inside it, because the map lights rows too and it has no pointer to put
   anywhere — a :hover rule could only ever answer one of the two ends.

   The tokens echo the traces' own colour on the map, so a lit row and the
   line it lit read as one object — they follow the theme where the layer's
   paint does not, which is why they are tokens rather than the layer's hex.
   An inset ring rather than a border, which would move the row by a pixel
   every time the pointer crossed it. */
.map-activity-card.hovered {
  border-radius: 8px;
  background: var(--map-trace-tint);
  box-shadow: inset 0 0 0 1px var(--map-trace-edge);
}

/* The row's tint stands in for the button's plain hover rather than sitting
   under it: the button fills the row, so its own background would cover both
   the tint and the ring — and they are the same pointer saying the same thing,
   in the colour that names the line. */
.map-activity-card.hovered .map-activity-open {
  background: none;
}

.map-activity-type {
  display: inline-flex;
  color: var(--text-muted);
}

.map-activity-type .at-icon {
  width: 20px;
  height: 20px;
  vertical-align: 0;
}

/* `flex: 1` with `min-width: 0` so the title below has a width to ellipsise
   against: without the pair it would size to its own text and overflow the
   panel instead. */
.map-activity-text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* One row per activity whatever it is called: the title is ellipsised rather
   than wrapped, so a long one never pushes the list twice as deep. */
.map-activity-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-weight: 600;
}

.map-activity-stats {
  color: var(--text-muted);
  font-size: 0.8rem;
}

/* A view with nothing in it. Its own class rather than the library's
   .route-library-empty, so the two panels' empty states never answer to one
   selector. */
.map-panel-empty {
  margin: 0;
  color: var(--text-muted);
  text-align: center;
}

/* What the row cap left out. Muted and unclickable: it is a statement about
   the list, not another row of it. */
.map-activity-more {
  margin: 8px 0 0;
  color: var(--text-muted);
  font-size: 0.8rem;
  text-align: center;
}

@media (max-width: 640px) {
  /* Two bottom sheets rather than a panel over the map's whole left side.
     Each takes at most a quarter of the screen, so the pair still leaves
     half the map visible above them. */
  .map-panels {
    top: auto;
    left: 10px;
    right: 10px;
    bottom: 10px;
    width: auto;
    max-height: 60vh;
  }

  .map-panel {
    max-height: 25vh;
  }
}

.route-library {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

/* Empty library: centre the call-to-action instead of leaving a lone
   button floating at the top of an empty page. */
.route-library:has(.route-library-empty) {
  align-items: center;
  padding-top: 48px;
  gap: 16px;
}

.route-library:has(.route-library-empty) .route-create-btn {
  /* Read as an empty-state: explanation first, call-to-action beneath. */
  order: 2;
  align-self: center;
}

.route-create-btn {
  align-self: flex-start;
  padding: 8px 18px;
  border-radius: 999px;
  border: none;
  background: var(--brand);
  color: #fff;
  font-weight: 600;
  cursor: pointer;
}

.route-create-btn:hover {
  background: var(--brand-hover);
}

.route-library-empty {
  color: var(--text-muted);
  text-align: center;
}

.route-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.route-card {
  display: flex;
  align-items: center;
  gap: 10px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px 14px;
}

/* The library's half of the same link, echoing the routes layer's blue: the
   row and the line it lights are the same thing said twice. The card already
   carries a border, so this one only changes its colour — nothing moves. */
.route-card.hovered {
  background: var(--map-route-tint);
  border-color: var(--map-route-edge);
}

.route-card-open {
  flex: 1;
  display: flex;
  align-items: center;
  gap: 10px;
  background: none;
  border: none;
  cursor: pointer;
  text-align: left;
}

/* The route's sport, as the icon the feed marks that sport's activities
   with — sized up a little, since here it stands on its own. */
.route-card-mode {
  display: inline-flex;
  color: var(--text-muted);
}

.route-card-mode .at-icon {
  width: 20px;
  height: 20px;
  vertical-align: 0;
}

.route-card-text {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-width: 0;
}

.route-card-name {
  font-weight: 600;
  color: var(--text);
}

.route-card-stats {
  font-size: 0.85rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.route-card-actions {
  display: flex;
  gap: 6px;
}

.route-btn-danger {
  border-color: var(--danger);
  color: var(--danger);
}

.route-rename-input {
  flex: 1;
  padding: 6px 10px;
  border: 1px solid var(--border-strong);
  border-radius: 8px;
  font: inherit;
}

/* The profile toggle, over the map's top edge and clear of MapLibre's own
   compass control in the corner — the map underneath is the Map tab's, and
   its controls stay usable while a route is open on it. The route's name and
   the way back are not here: they are the routes panel's header. */
.route-editor-head {
  position: absolute;
  top: 10px;
  left: 52px;
  z-index: 3;
  display: flex;
  align-items: center;
  gap: 10px;
  /* Four profile buttons overflow a phone's width on one line; wrap rather
     than push the last of them off screen. */
  flex-wrap: wrap;
  max-width: calc(100% - 64px);
}

/* The open route's name, in the panel header the editor borrows: plain text
   beside the back control, not a pill of its own. Ellipsised rather than
   wrapped — the header is one row high whatever the route is called. */
.route-editor-name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.route-elevation {
  width: min(640px, calc(100vw - 24px));
  background: var(--surface);
  border: 1px solid var(--border-strong);
  border-radius: 12px;
  padding: 6px 10px 2px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
}

/* The surface band: what the route is made of, on the elevation chart's x
   axis (the SVG shares its viewBox width and padding, so the two line up). */
.route-surface-band {
  display: block;
  width: 100%;
  height: auto;
  border-radius: 3px;
  overflow: hidden;
}

/* Each run carries its class as a --way custom property; the rect and the
   legend swatch both read it, so a colour lives in exactly one place. */
.route-surface-trail { --way: var(--way-trail); }
.route-surface-track { --way: var(--way-track); }
.route-surface-cycleway { --way: var(--way-cycleway); }
.route-surface-road { --way: var(--way-road); }
.route-surface-water { --way: var(--way-water); }
.route-surface-other { --way: var(--way-other); }

.route-surface-band rect {
  fill: var(--way);
}

.route-surface-legend {
  display: flex;
  flex-wrap: wrap;
  gap: 1px 10px;
  margin: 4px 0 3px;
  padding: 0;
  list-style: none;
  font-size: 0.72rem;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.route-surface-legend li {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}

.route-surface-swatch {
  width: 9px;
  height: 9px;
  border-radius: 2px;
  background: var(--way);
}

/* ── Plans (/plan, the upcoming strip, announcements) ─────────────────── */

.plan-list-view,
.plan-editor {
  width: 100%;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.plan-section-title {
  font-size: 0.95rem;
  color: var(--text-muted);
  margin: 8px 0 0;
}

.plan-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--shadow-sm);
  display: flex;
  align-items: center;
}

.plan-card-accepted {
  padding: 12px 16px;
}

.plan-card-badges {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-top: 4px;
}

.plan-announced-badge {
  display: inline-block;
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--brand-soft);
  color: var(--brand);
  font-size: 0.75rem;
  font-weight: 600;
}

.plan-going-count {
  color: var(--text-muted);
  font-size: 0.8rem;
}

.plan-editor-head {
  display: flex;
  align-items: center;
  gap: 12px;
}

.plan-name-input {
  font-size: 1.2rem;
  font-weight: 600;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
}

.plan-description-input {
  min-height: 64px;
  padding: 8px 12px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  font: inherit;
  resize: vertical;
}

.plan-stage-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

.plan-stage {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 12px 16px;
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.plan-stage-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
}

.plan-stage input[type='datetime-local'],
.plan-stage-note {
  padding: 6px 10px;
  border: 1px solid var(--border);
  border-radius: 8px;
  background: var(--surface);
  color: var(--text);
  font: inherit;
}

.plan-stage-route {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
}

.plan-stage-route-line {
  color: var(--text-muted);
  font-size: 0.85rem;
}

.plan-reask-hint,
.plan-stale-note {
  color: var(--text-muted);
  font-size: 0.85rem;
  margin: 4px 0 0;
}

.plan-share-block,
.plan-danger-block {
  display: flex;
  flex-direction: column;
  gap: 8px;
  align-items: flex-start;
}

.plan-signup-summary {
  display: flex;
  flex-direction: column;
  gap: 6px;
  align-items: flex-start;
}

.plan-signup-names {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: 6px 12px;
  font-size: 0.85rem;
}

.plan-signup-stale {
  color: var(--text-subtle);
  font-style: italic;
}

.plan-route-picker {
  position: fixed;
  inset: auto 0 0 0;
  max-height: 70vh;
  overflow-y: auto;
  background: var(--surface);
  border-top: 1px solid var(--border-strong);
  box-shadow: 0 -6px 24px rgba(0, 0, 0, 0.18);
  padding: 16px;
  z-index: 40;
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.plan-route-picker-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-weight: 600;
}

/* The strip above the wall: horizontal, bounded, gone when empty. */
.upcoming-strip {
  display: flex;
  gap: 10px;
  overflow-x: auto;
  padding-bottom: 4px;
}

.upcoming-card {
  flex: 0 0 auto;
  max-width: 260px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--shadow-sm);
  padding: 10px 14px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.upcoming-card-name {
  font-weight: 600;
}

.upcoming-card-meta {
  color: var(--text-muted);
  font-size: 0.8rem;
}

.upcoming-card-desc {
  font-size: 0.85rem;
  overflow: hidden;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
}

.upcoming-going-badge {
  color: var(--brand);
  font-size: 0.85rem;
  font-weight: 600;
}

.upcoming-signup-btn {
  align-self: flex-start;
}

.plan-announcement-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: 12px;
  box-shadow: var(--shadow-sm);
  padding: 12px 16px;
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.plan-announcement-card p {
  margin: 0;
}

.plan-announcement-meta {
  color: var(--text-muted);
  font-size: 0.85rem;
}

.plan-announcement-stages {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
  color: var(--text-muted);
  font-size: 0.85rem;
}
