/*
 * User Dashboard + Leaderboard redesign — gamification components.
 *
 * Layered on top of the shared page shell and components (components/page-shell.css, kpi.css, tables.css): this file
 * adds ONLY what those don't already provide (hero banner, streak heatmap, badge tracks, trophy case, podium, medals,
 * standing widget, mistake gallery cards). It styles everything from the main.css design-system tokens (--text-*,
 * --space-*, --color-* ramps) rather than introducing new literals, so the three surfaces stay visually unified.
 * Label-type colors come from the --color-label-* tokens (source of truth: /v3/api/labelTypes) — never hardcoded hex.
 *
 * Type scale policy (per design review): ALL content — table cells, badge/trophy detail, card text, stat labels,
 * podium — is at least --text-body-* (16px). The smaller steps (small/caption) are reserved for legends
 * (.ud-heatmap-legend), chart captions (.ud-acc-note), and small decorative pills/ribbons (.ud-trophy-tag,
 * .ud-standing-tag, .ud-card-verdict, .ud-public-tag). Never size real content below body — and avoid footnote-style
 * asides (design review: if a note matters it belongs in real copy). The sanctioned exceptions are captions that
 * explain the thing they sit under: the accuracy chart's min-validations rule (.ud-acc-note) and the global
 * leaderboard's daily distance refresh (.ud-lb-note) — both owner calls.
 */

/* The ranked boards are the page, not a data appendix, so their cells read at body size rather than .ps-table's
   small; the headers keep the primitive's small-bold so the data stays louder than its labels. Scoped to the boards
   so the compact admin tables on /admin/user/:username keep their dense treatment. */
.page-content .ud-lb-table, .page-content .ud-cities-table { font: var(--text-body-regular); }

/* Leaderboard tables are capped at ~10 rows, so show them in full — no inner scroll (admin tables keep theirs), and
   overflow visible so the Accuracy header tooltip can escape the wrap. The responsive rule restores mobile scroll. */
.page-content .ps-table-scroll-box {
  max-height: none;
  overflow: visible;
}

/* The admin tables right-align every non-first column (numeric); on the leaderboard the name column — and the
   all-time "Most active in" city column — are text, so left-align them. Scoped to the ranked boards: other tables in
   .page-content (the cities-you've-mapped one) carry a number in column 2 and want the right-aligned default. */
.page-content .ud-lb-table td:nth-child(2),
.page-content .ud-lb-table th:nth-child(2) { text-align: left; }

/* The global board's "Top city" is a name, so it reads left-aligned like Mapper rather than right like the numbers. */
.page-content .ud-lb-global td:nth-child(3),
.page-content .ud-lb-global th:nth-child(3) { text-align: left; }
.page-content .ps-table--sticky tbody tr { cursor: default; }

/* Bump only the top-3 medal emoji so they read clearly; plain rank numbers keep the table's font size. */
.ud-lb-rank .ud-lb-medal {
  /* px, not rem: Bootstrap sets a 10px root here, so a rem size silently renders at 62.5% of its intent. */
  font-size: 28px;
  line-height: 1;
}

/* Header tooltip (e.g. Accuracy): a CSS popover shown on hover/focus — more reliable than a native title. */
.ud-th-tip {
  position: relative;
  cursor: help;
}

.ud-th-tip:focus-visible {
  outline: 2px solid var(--color-link-200);
  outline-offset: 2px;
}

.ud-th-info {
  margin-left: 4px;
  color: var(--color-neutral-700);
  font-size: 0.8em;
  vertical-align: middle;
}

.ud-th-tip::after {
  content: attr(data-tooltip);
  position: absolute;
  top: calc(100% + 8px);
  right: 0;
  z-index: 5;
  width: max-content;
  max-width: 260px;
  padding: 8px 11px;
  border-radius: var(--border-radius);
  background: var(--color-asphalt-700);
  color: var(--color-neutral-white);
  font: var(--text-small-regular);
  line-height: 1.35;
  text-align: left;
  white-space: normal;
  text-transform: none;
  box-shadow: var(--box-shadow);
  opacity: 0;
  visibility: hidden;
  transition: opacity var(--transition-fast);
  pointer-events: none;
}

.ud-th-tip:hover::after,
.ud-th-tip:focus-visible::after {
  opacity: 1;
  visibility: visible;
}

.ud-lb-user,
.ud-lb-city {
  color: var(--color-link-200);
  text-decoration: none;
}

.ud-lb-user:hover,
.ud-lb-city:hover { text-decoration: underline; }

/* ---- Hero / "Your impact" -------------------------------------------------------------------------------------- */
.ud-hero {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  padding: var(--space-lg);
  margin: var(--space-md) 0 var(--space-lg);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--border-radius-lg);
  background: linear-gradient(120deg, var(--color-pine-100), var(--color-banana-100));
}

.ud-hero-avatar {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 72px;
  height: 72px;
  border-radius: 50%;
  background: var(--color-pine-600);
  color: var(--color-neutral-white);
  font: var(--text-h3-bold);
  letter-spacing: 0.02em;
}

.ud-hero-greeting {
  margin: 0 0 4px;
  font: var(--text-body-regular);
  color: var(--color-neutral-700);
}

.ud-hero-headline {
  margin: 0 0 4px;
  font: var(--text-h3-bold);
  line-height: 1.2;
  color: var(--color-neutral-900);
}

.ud-hero-sub {
  margin: 0;
  font: var(--text-body-regular);
  color: var(--color-neutral-700);
}

/* Friendly, supportive nudge/encouragement callout — sprinkled through both surfaces. */
.ud-nudge {
  margin: var(--space-md) 0 0;
  padding: var(--space-sm) var(--space-md);
  border-left: 4px solid var(--color-banana-500);
  border-radius: var(--border-radius);
  background: var(--color-banana-100);
  font: var(--text-small-regular);
  color: var(--color-neutral-900);
}

/* Link blue is 2.4:1 against the nudge's neutral-900 copy, under WCAG 1.4.1's 3:1 for a color-only link. */
.ud-nudge a {
  text-decoration: underline;
}

/* ---- Streak / activity heatmap --------------------------------------------------------------------------------- */
.ud-streak {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-lg);
  align-items: flex-start;
}

.ud-streak-stats {
  display: flex;
  flex-direction: column;
  gap: var(--space-sm);
  flex: 0 0 200px;
}

/* Stacked in a column, so .ps-kpi's row flex-basis would become a 140px minimum *height*; size to content. */
.ud-streak-kpi {
  flex: 0 0 auto;
  margin: 0;
}

.ud-streak-current {
  border-color: var(--color-orange-400);
  background: var(--color-orange-100);
}

/* Fixed-size cells (GitHub-style) so the grid is a compact block instead of stretching to fill the width. The cell
   size and gap are set once here; the month/day label tracks and the cells themselves all read them, so the grid and
   its labels can't drift apart. */
.ud-heatmap {
  --ud-heat-cell: 18px;
  --ud-heat-gap: 3px;

  flex: 0 1 auto;
  margin: 0;
  overflow-x: auto;
}

/* GitHub-style scaffold: month labels across the top, weekday labels down the left, cells bottom-right. */
.ud-heatmap-scaffold {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto;
  gap: 4px 6px;
  width: max-content;
}

.ud-heatmap-corner {
  grid-column: 1;
  grid-row: 1;
}

.ud-heatmap-months {
  grid-column: 2;
  grid-row: 1;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: var(--ud-heat-cell);
  gap: var(--ud-heat-gap);
  font: var(--text-caption-regular);
  color: var(--color-neutral-700);
}

/* Each label sits in one cell-column but overflows right over the (empty) following columns, like GitHub. */
.ud-heatmap-months > span {
  white-space: nowrap;
  overflow: visible;
}

.ud-heatmap-days {
  grid-column: 1;
  grid-row: 2;
  display: grid;
  grid-template-rows: repeat(7, var(--ud-heat-cell));
  gap: var(--ud-heat-gap);
  align-items: center;
  font: var(--text-caption-regular);
  color: var(--color-neutral-700);
}

.ud-heatmap-grid {
  grid-column: 2;
  grid-row: 2;
  display: grid;
  grid-template-rows: repeat(7, var(--ud-heat-cell));
  grid-auto-flow: column;
  grid-auto-columns: var(--ud-heat-cell);
  gap: var(--ud-heat-gap);
  width: max-content;
}

.ud-heat {
  width: var(--ud-heat-cell);
  height: var(--ud-heat-cell);
  border-radius: 3px;
  background: var(--color-neutral-100);

  /* Subtle inset ring (GitHub-style) so the lightest buckets stay distinguishable from the page background and each
   other (WCAG 1.4.11 non-text contrast; exact counts are in each cell's tooltip/aria-label). */
  box-shadow: inset 0 0 0 1px rgb(0 0 0 / 8%);
}

.ud-heat-0 { background: var(--color-neutral-200); }
.ud-heat-1 { background: var(--color-pine-200); }
.ud-heat-2 { background: var(--color-pine-400); }
.ud-heat-3 { background: var(--color-pine-600); }
.ud-heat-4 { background: var(--color-pine-800); }

/* Out-of-window padding cells (before the start / after today): keep the grid shape but invisible. */
.ud-heat-empty {
  background: transparent;
  box-shadow: none;
}

.ud-heat[tabindex]:focus-visible {
  outline: 2px solid var(--color-link-200);
  outline-offset: 1px;
}

/* The legend's swatches are a size down from the grid's cells so the key reads as a key, not a stray row. */
.ud-heatmap-legend {
  --ud-heat-cell: 12px;

  display: flex;
  align-items: center;
  gap: 4px;
  margin-top: var(--space-sm);
  font: var(--text-caption-regular);

  /* text-secondary, not tertiary: tertiary is ~2.7:1 on white, below the 4.5:1 AA floor for this small text. */
  color: var(--color-neutral-700);
}

/* ---- Badge tracks ---------------------------------------------------------------------------------------------- */
.ud-badge-tracks {
  display: grid;

  /* Cap at two tracks per row — at four across, the tier icons and their numbers get too small to read. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-md);
  margin: var(--space-md) 0;
}

.ud-badge-track {
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--border-radius-lg);
  padding: var(--space-md);
  background: var(--color-neutral-white);
}

.ud-badge-track-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-sm);
  margin-bottom: var(--space-sm);
}

.ud-badge-track-name {
  font: var(--text-title-bold);
  color: var(--color-neutral-900);
}

/* One-line explanation of what the track counts — especially useful on another mapper's public profile. */
.ud-badge-track-desc {
  margin: 0 0 var(--space-sm);
  font: var(--text-small-regular);
  color: var(--color-neutral-700);
}

/* The user's current tier name in this track (e.g. "IV: Barrier Buster") — a small pill whose color echoes the badge
   level's color ramp (white -> green -> yellow -> orange -> legendary), set via a .ud-tier-N modifier. */
.ud-badge-track-current {
  padding: 2px 10px;
  border-radius: 12px;
  font: var(--text-caption-semibold);
}

.ud-tier-0,
.ud-tier-1 {
  background: var(--color-neutral-200);
  color: var(--color-neutral-900);
}

.ud-tier-2 {
  background: var(--color-pine-600);
  color: var(--color-neutral-white);
}

.ud-tier-3 {
  background: var(--color-banana-500);
  color: var(--color-asphalt-900);
}

.ud-tier-4 {
  background: var(--color-orange-500);

  /* Dark text like tier-3: white on orange-500 is ~3:1, below the 4.5:1 AA floor. */
  color: var(--color-asphalt-900);
}

.ud-tier-5 {
  background: var(--color-asphalt-600);
  color: var(--color-neutral-white);
}

.ud-badge-track-next {
  display: block;
  margin-top: var(--space-sm);
  font: var(--text-small-regular);
  color: var(--color-neutral-700);
}

.ud-badge-levels {
  display: flex;
  justify-content: space-between; /* spread the five tiers across the full card width */
  gap: var(--space-xs);
  margin-bottom: var(--space-sm);
}

.ud-badge {
  width: 64px;
  height: 64px;
  max-width: 18%;
  object-fit: contain;
}

.ud-badge-locked {
  opacity: 0.18;
  filter: grayscale(1);
}

/* ---- Progress bar (shared) ------------------------------------------------------------------------------------- */
.ud-progress {
  height: 8px;
  border-radius: 4px;
  background: var(--color-neutral-200);
  overflow: hidden;
}

.ud-progress-lg {
  height: 14px;
  border-radius: 7px;
  margin: var(--space-sm) 0;
}

.ud-progress-fill {
  width: 0; /* JS (DashboardBadges) animates this to the real percent. */
  height: 100%;
  border-radius: inherit;
  background: linear-gradient(90deg, var(--color-pine-500), var(--color-pine-700));
}

/* ---- Trophy case ----------------------------------------------------------------------------------------------- */
.ud-trophy-case {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
  margin: var(--space-md) 0;
}

.ud-trophy {
  position: relative;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 4px;
  width: 184px;
  padding: var(--space-lg) var(--space-md);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--border-radius-lg);
  background: var(--color-neutral-white);
  border-top: 4px solid var(--color-neutral-300);
}

/* Region/pioneer trophies link to Explore that neighborhood: keep the card look, add a lift + reveal the affordance. */
.ud-trophy-link {
  text-decoration: none;
  color: inherit;
  cursor: pointer;
  transition: transform var(--transition-fast), box-shadow var(--transition-fast);
}

.ud-trophy-link:hover,
/* Keyboard focus keeps the hover lift but needs a real indicator — the soft shadow alone isn't visible enough. */
.ud-trophy-link:focus-visible {
  transform: translateY(-2px);
  box-shadow: var(--box-shadow);
  outline: 2px solid var(--color-link-200);
  outline-offset: 2px;
}

.ud-trophy-explore {
  margin-top: 4px;
  font: var(--text-tiny-semibold);
  color: var(--color-link-200);
  opacity: 0;
  transition: opacity var(--transition-fast);
}

.ud-trophy-link:hover .ud-trophy-explore,
.ud-trophy-link:focus-visible .ud-trophy-explore { opacity: 1; }

.ud-trophy-1 { border-top-color: var(--color-banana-500); }
.ud-trophy-2 { border-top-color: var(--color-neutral-400); }
.ud-trophy-3 { border-top-color: var(--color-orange-500); }

.ud-trophy-region {
  border-top-color: var(--color-pine-600);
  background: linear-gradient(160deg, var(--color-pine-100), var(--color-neutral-white));
}

.ud-trophy-pioneer {
  border-top-color: var(--color-banana-600);
  background: linear-gradient(160deg, var(--color-banana-100), var(--color-neutral-white));
}

.ud-trophy-free-explore {
  border-top-color: var(--color-blue-400);
  background: linear-gradient(160deg, var(--color-blue-100), var(--color-neutral-white));
}

.ud-trophy-tag {
  position: absolute;
  top: 8px;
  right: 8px;
  padding: 1px 7px;
  border-radius: 9px;
  font: var(--text-tiny-semibold);
  letter-spacing: 0.03em;
  text-transform: uppercase;
  color: var(--color-pine-800);
  background: var(--color-pine-200);
}

.ud-trophy-medal {
  font-size: 40px; /* px, not rem, for the reason given on .ud-lb-medal. */
  line-height: 1;
}

.ud-trophy-title {
  font: var(--text-subtitle-bold);
  color: var(--color-neutral-900);
}

.ud-trophy-sub {
  font: var(--text-caption-regular);
  color: var(--color-neutral-700);
}

/* ---- Your standing --------------------------------------------------------------------------------------------- */
.ud-standing-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: var(--space-md);
  margin: var(--space-md) 0;
}

.ud-standing-card {
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--border-radius-lg);
  padding: var(--space-lg);
  background: var(--color-neutral-white);
}

.ud-standing-community {
  background: linear-gradient(120deg, var(--color-pine-100), var(--color-neutral-white));
}

.ud-standing-card-wide { max-width: 560px; }

.ud-standing-tag {
  font: var(--text-caption-regular);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-neutral-700);
  margin-bottom: var(--space-xs);
}

/* Primary font, not the accent one: this prints a rank and a percentage, and Raleway's old-style figures drop
   3/4/5/7/9 below the baseline. */
.ud-standing-rank {
  font: var(--text-title-bold);
  font-variant-numeric: tabular-nums;
  color: var(--color-neutral-900);
}

.ud-standing-detail {
  margin-top: var(--space-xs);
  font: var(--text-small-regular);
  color: var(--color-neutral-700);
}

.ud-standing-of {
  font-weight: 400;
  color: var(--color-neutral-700);
}

/* Week-over-week rank movement: climbed (green) / dropped (red). Deliberate hexes: the design-system success/error
 tokens are too light to pass 4.5:1 on white, and the movement is also conveyed in words ("Up"/"Down"), not color
 alone. Both hexes pass AA (~5.4:1). */
.ud-delta-up {
  color: #1f7a44;
  font-weight: 700;
}

.ud-delta-down {
  color: #c0392b;
  font-weight: 700;
}

.ud-standing-slice {
  list-style: none;
  margin: var(--space-md) 0 0;
  padding: 0;
}

.ud-standing-slice li {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: 8px 10px;
  border-radius: var(--border-radius);
  font: var(--text-small-regular);
}

.ud-rank-you {
  background: var(--color-banana-100);
  font-weight: 700;
}

.ud-rank {
  flex: 0 0 28px;
  color: var(--color-neutral-700);
  font-variant-numeric: tabular-nums;
}

.ud-rank-user {
  flex: 1 1 auto;
  color: var(--color-neutral-900);
}

.ud-rank-val {
  flex: 0 0 auto;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
  color: var(--color-neutral-700);
}

/* ---- Accuracy-by-type bars ------------------------------------------------------------------------------------- */
.ud-accuracy-bars {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin: var(--space-md) 0;
}

.ud-acc-row {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: 4px 6px;
  border-radius: var(--border-radius);
}

.ud-acc-weakest {
  background: var(--color-orange-100);
  outline: 1px solid var(--color-orange-300);
}

.ud-acc-label {
  flex: 0 0 170px;
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  font: var(--text-small-regular);
  color: var(--color-neutral-900);
}

.ud-acc-swatch {
  width: 12px;
  height: 12px;
  border-radius: 3px;
  flex: 0 0 auto;
  background: var(--label-color, var(--color-neutral-400));
}

.ud-acc-bar {
  flex: 1 1 auto;
  height: 10px;
  border-radius: 5px;
  background: var(--color-neutral-200);
  overflow: hidden;
}

.ud-acc-fill {
  display: block;
  height: 100%;
  border-radius: inherit;
  background: var(--label-color, var(--color-pine-500));
}

/* Each row sets --label-color from its label type's canonical token (source of truth: /v3/api/labelTypes); the
   swatch and fill both read it, so the per-type color lives in CSS rather than an inline style. */
.ud-lt-curb-ramp { --label-color: var(--color-label-curb-ramp); }
.ud-lt-no-curb-ramp { --label-color: var(--color-label-no-curb-ramp); }
.ud-lt-obstacle { --label-color: var(--color-label-obstacle); }
.ud-lt-surface-problem { --label-color: var(--color-label-surface-problem); }
.ud-lt-no-sidewalk { --label-color: var(--color-label-no-sidewalk); }
.ud-lt-crosswalk { --label-color: var(--color-label-crosswalk); }
.ud-lt-signal { --label-color: var(--color-label-signal); }

.ud-acc-pct {
  flex: 0 0 48px;
  text-align: right;
  font: var(--text-small-bold);
  font-variant-numeric: tabular-nums;
  color: var(--color-neutral-700);
}

/* The percentage's sample size, e.g. "(32 validated)" — fixed basis so the column lines up across rows. */
.ud-acc-n {
  flex: 0 0 130px;
  font: var(--text-small-regular);
  font-variant-numeric: tabular-nums;
  color: var(--color-neutral-700);
}

/* The two sanctioned captions: the accuracy chart's min-validations rule, and the global board's note that its
   Distance column comes from a nightly precompute and so can trail the live per-city boards above. */
.ud-acc-note,
.ud-lb-note {
  margin: var(--space-xs) 0 0;
  font: var(--text-caption-regular);
  color: var(--color-neutral-700);
}

/* ---- Mistake gallery cards (#4388) ----------------------------------------------------------------------------- */
.ud-mistake-cards {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
  gap: var(--space-md);
  margin: var(--space-md) 0;
}

.ud-card {
  margin: 0;
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--border-radius-lg);
  overflow: hidden;
  background: var(--color-neutral-white);
}

.ud-card-img {
  position: relative;
  aspect-ratio: 3 / 2; /* Match the Explore canvas (720x480) so the label marker lines up, like the Gallery. */
  overflow: hidden;
  background: linear-gradient(135deg, var(--color-neutral-200), var(--color-neutral-400));
  background-size: cover;
  background-position: center;
}

/* The label's own icon, placed over the pano at the label's canvas position (left/top set inline) to mark exactly
   which feature was labeled. Small, like the Gallery's marker. */
.ud-card-label-marker {
  position: absolute;
  width: clamp(20px, 7%, 30px);
  height: auto;
  transform: translate(-50%, -50%);
  filter: drop-shadow(0 1px 4px rgb(0 0 0 / 55%));
  pointer-events: none;
}

.ud-card-verdict {
  position: absolute;
  bottom: 8px;
  left: 8px;
  padding: 2px 8px;
  border-radius: 10px;
  font: var(--text-caption-semibold);
  color: var(--color-neutral-white);
  background: var(--color-error-200);
}

.ud-card-body {
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: var(--space-sm);
}

.ud-card-title {
  font: var(--text-body-bold);
  color: var(--color-neutral-900);
}

.ud-card-note {
  font: var(--text-caption-regular);
  line-height: 1.4;
  color: var(--color-neutral-700);
}

/* Applied when there's no validator comment, so the placeholder text reads as an aside. */
.ud-card-note-muted { font-style: italic; }

.ud-card-prompt {
  margin: var(--space-sm) 0 4px;
  font: var(--text-small-bold);
  color: var(--color-neutral-900);
}

.ud-card-actions {
  display: flex;
  gap: var(--space-xs);
  margin-top: var(--space-xs);
}

/* Two equal buttons side-by-side, each one line (nowrap). Short labels + tight padding keep them on one line. */
.ud-card-actions .ud-chip {
  flex: 1 1 0;
  text-align: center;
  white-space: nowrap;
  padding: 6px 8px;
}

.ud-chip {
  padding: 4px 10px;
  border: 1px solid var(--color-neutral-300);
  border-radius: 14px;
  background: var(--color-neutral-white);
  font: var(--text-small-regular);
  color: var(--color-neutral-900);
  cursor: pointer;
  transition: background var(--transition-fast), border-color var(--transition-fast);
}

.ud-chip-agree:hover {
  background: var(--color-pine-100);
  border-color: var(--color-pine-400);
}

.ud-chip-disagree:hover {
  background: var(--color-orange-100);
  border-color: var(--color-orange-300);
}

/* Inert on an admin's view of someone else's dashboard: the vote acts as the signed-in user, so it can't be cast. */
.ud-chip:disabled,
.ud-chip:disabled:hover {
  background: var(--color-neutral-100);
  border-color: var(--color-neutral-200);
  color: var(--color-neutral-700);
  opacity: 0.6;
  cursor: not-allowed;
}

.ud-card-note-link {
  display: inline-block;
  margin-top: 6px;
  font: var(--text-caption-regular);
  color: var(--color-link-200);
  text-decoration: none;
  cursor: pointer;
}

.ud-card-note-link:hover { text-decoration: underline; }

.ud-card-note-link[aria-disabled="true"],
.ud-card-note-link[aria-disabled="true"]:hover {
  color: var(--color-neutral-700);
  opacity: 0.6;
  text-decoration: none;
  cursor: not-allowed;
}

.ud-card-comment-input {
  width: 100%;
  margin-top: var(--space-xs);
}

.ud-card-note-wrap {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin-top: var(--space-xs);
}

/* The `hidden` attribute must win over display:flex, or the note stays open before "Add a note" is clicked. */
.ud-card-note-wrap[hidden] { display: none; }

.ud-card-note-submit {
  align-self: flex-start;
}

.ud-card-vote {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
}

/* Voted state tints just the vote section (works on the card and inside the popup panel). */
.ud-card-vote--done {
  padding: var(--space-xs);
  border-radius: var(--border-radius);
  background: var(--color-pine-100);
}

/* Clickable card image opens the interactive label popup. */
.ud-card-img-clickable { cursor: pointer; }

.ud-card-img-clickable:focus-visible {
  outline: 2px solid var(--color-link-200);
  outline-offset: 2px;
}

.ud-card-expand-hint {
  position: absolute;
  top: 8px;
  right: 8px;
  padding: 2px 8px;
  border-radius: 10px;
  font: var(--text-caption-semibold);
  color: var(--color-neutral-white);
  background: rgb(0 0 0 / 55%);
  opacity: 0;
  transition: opacity var(--transition-fast);
  pointer-events: none;
}

.ud-card-img-clickable:hover .ud-card-expand-hint,
.ud-card-img-clickable:focus-visible .ud-card-expand-hint { opacity: 1; }

/* The mistake vote/note panel injected into the label popup dialog so you can respond from the expanded view. It sits
   outside .page-content, so set a readable base font here (children inherit it) and size the heading up. Full-width
   (no side margins), a small gap from the content above, and tight internal spacing. */
.ud-mistake-response {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  margin: var(--space-xs) 0 0;
  padding: var(--space-md);
  border: 1px solid var(--color-pine-200);
  border-radius: var(--border-radius-lg);
  background: var(--color-neutral-white);
  font: var(--text-body-regular);
}

.ud-mistake-response-heading {
  margin: 0;
  font: var(--text-subtitle-bold);
  color: var(--color-neutral-900);
}

/* The panel heading already asks "Do you agree…", so drop the vote section's own prompt inside the popup. */
.ud-mistake-response .ud-card-vote .ud-card-prompt { display: none; }

.ud-mistake-response .ud-card-vote,
.ud-mistake-response .ud-card-note-section { margin-top: 0; }

/* On the dashboard the label popup shows the viewer's OWN labels (mistake cards + their map), so the
   validate-on-hover overlay (Agree/Disagree/Unsure) and the add-a-comment row are never applicable and clash with the
   mistake-response panel below — hide both. The public profile shares this rule deliberately: its popup is a
   view-only showcase of someone's work, not a validation surface (that's LabelMap's job). */
#label-modal .label-detail__pano-overlay,
#label-modal .label-detail__comment-row { display: none; }

/* Tighten the Comments heading so its empty "None" doesn't tower over the Description row, and give it a colon so it
   reads like "Description :". The colon goes on the inner heading-text span so it lands before the count badge. */
#label-modal .label-detail__comments-section .label-detail__col-title { margin: 0 0 2px; }
#label-modal .label-detail__comments-section .label-detail__col-title > span:first-child::after { content: ":"; }

.ud-card-voted-msg {
  margin: 0;
  font: var(--text-small-bold);
  color: var(--color-pine-800);
}

.ud-card-change {
  align-self: flex-start;
  padding: 6px 14px;
}

/* The user's own note, independent of the vote. */
.ud-card-note-section {
  display: flex;
  flex-direction: column;
  margin-top: var(--space-xs);
}

.ud-card-your-note {
  margin: 0 0 4px;
  font: var(--text-caption-regular);
  font-style: italic;
  color: var(--color-neutral-700);
}

.ud-see-all {
  display: inline-block;
  margin-top: var(--space-xs);
  font: var(--text-body-bold);
  color: var(--color-link-200);
  text-decoration: none;
}

.ud-see-all:hover { text-decoration: underline; }

/* Beat the inline-block display so the `hidden` attribute actually hides the "See all" link until cards load. */
.ud-see-all[hidden] { display: none; }

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

/* Override choropleth.css's very tall fixed heights so the map is width-responsive and fits without page scroll.
   ID selector matches choropleth.css specificity and wins by load order (this file loads after). */
.ud-map-caption {
  margin: var(--space-md) 0 6px;
  font: var(--text-small-regular);
  color: var(--color-neutral-700);
}

#user-dashboard-choropleth-holder {
  position: relative;
  width: 100%;
  height: 480px;
  margin: 0 0 var(--space-md);
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--border-radius-lg);
  overflow: hidden;
}

#user-dashboard-choropleth-holder .choropleth {
  width: 100%;
  height: 100%;
}

@media (width <= 768px) {
  #user-dashboard-choropleth-holder { height: 360px; }
}

/* Placeholder still used on the public profile (its map isn't wired yet). */
.ud-map-placeholder {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 4px;
  height: 320px;
  border: 1px dashed var(--color-neutral-300);
  border-radius: var(--border-radius-lg);
  background: var(--color-neutral-100);
  color: var(--color-neutral-700);
  font: var(--text-body-regular);
}

.ud-map-placeholder small { font: var(--text-caption-regular); }

/* ---- Team / class ---------------------------------------------------------------------------------------------- */
.ud-team {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--space-md);
  margin: var(--space-md) 0;
}

.ud-team-card,
.ud-team-board {
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--border-radius-lg);
  padding: var(--space-md);
  background: var(--color-neutral-white);
}

.ud-team-card {
  display: flex;
  flex-direction: column;
  gap: var(--space-xs);
  align-items: flex-start;
}

.ud-team-name {
  font: var(--text-title-bold);
  color: var(--color-neutral-900);
}

.ud-team-meta {
  font: var(--text-small-regular);
  color: var(--color-neutral-700);
}

.ud-team-board-head {
  font: var(--text-small-bold);
  color: var(--color-neutral-700);
  margin-bottom: var(--space-xs);
}

.ud-btn-secondary {
  padding: 6px 14px;
  border: 1px solid var(--color-neutral-300);
  border-radius: var(--border-radius);
  background: var(--color-neutral-white);
  color: var(--color-neutral-900);
  font: var(--text-small-regular);
  cursor: pointer;
  transition: background var(--transition-fast);
}

.ud-btn-secondary:hover { background: var(--color-neutral-100); }

.ud-btn-primary {
  display: inline-block;
  padding: 8px 18px;
  border: 1px solid transparent;
  border-radius: var(--border-radius);
  background: var(--color-pine-700);
  color: var(--color-neutral-white);
  font: var(--text-small-bold);
  text-decoration: none;
  cursor: pointer;
  transition: background var(--transition-fast);
}

.ud-btn-primary:hover {
  background: var(--color-pine-800);
  text-decoration: none;
}

/* Inert controls on an admin's view of someone else's dashboard (route/team actions, an Owner's settings). */
.ud-btn-secondary:disabled,
.ud-btn-secondary:disabled:hover {
  background: var(--color-neutral-100);
  border-color: var(--color-neutral-200);
  color: var(--color-neutral-700);
  opacity: 0.6;
  cursor: not-allowed;
}

.ud-btn-primary:disabled,
.ud-btn-primary:disabled:hover {
  background: var(--color-neutral-200);
  color: var(--color-neutral-700);
  opacity: 0.8;
  cursor: not-allowed;
}

/* "Create a team" sits below the teams table / empty-state banner, so give it breathing room. */
.ud-btn-primary.ud-create-team-btn { margin-top: var(--space-md); }

/* Plain "+" affordance that inherits the button's white text (a "➕" emoji renders blue and clashes with green). */
.ud-btn-plus { font-weight: 700; }

/* Public sign-up call-to-action shown to anonymous leaderboard visitors. */
.ud-cta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  margin: var(--space-md) 0;
  padding: var(--space-lg);
  border: 1px solid var(--color-pine-300);
  border-radius: var(--border-radius-lg);
  background: linear-gradient(120deg, var(--color-pine-100), var(--color-banana-100));
}

/* Warning variant of the CTA banner (e.g. the "sample data" notice on the public profile). */
.ud-cta-warning { border-color: var(--color-warning-200); }

.ud-cta-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.ud-cta-text strong {
  font: var(--text-subtitle-bold);
  color: var(--color-neutral-900);
}

.ud-cta-text span {
  font: var(--text-body-regular);
  color: var(--color-neutral-700);
}

.ud-cta-actions {
  display: flex;
  gap: var(--space-sm);
}

/* Both actions are anchors styled as buttons; the flex row stretches them to equal heights, so without this the
 shorter "Sign in" link's text sits on the baseline instead of vertically centering in the stretched box. */
.ud-cta-actions .ud-btn-primary,
.ud-cta-actions .ud-btn-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

/* ---- Leaderboard: this city's hero impact band (#4687) ---------------------------------------------------------- */

/* Small-caps lead-in naming the city, standing in for the h2 this band deliberately doesn't have (it would otherwise
   claim a row in the right-hand contents list, which starts at "Top of the board"). */
.ud-impact-caption {
  margin: var(--space-lg) 0 var(--space-xs);
  font: var(--text-small-bold);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-neutral-700);
}

/* Quieter than the cross-city band below: no icons, tiles left-aligned, so the page still opens on the podium rather
   than on a second full-height card. */
.ud-impact-band {
  display: grid;

  /* Two up by default, four only once the content column is genuinely wide enough (see the media query below).
     Fixed counts rather than auto-fit: with exactly four tiles, an intermediate three-column track would orphan the
     fourth on a row of its own. minmax(0, 1fr) so a long localized label can't push a track past its share. */
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: var(--space-md) var(--space-lg);
  margin: 0;
  padding: var(--space-lg);
  border: 1px solid var(--color-pine-200);
  border-radius: var(--border-radius-lg);
  background: linear-gradient(120deg, var(--color-pine-100), var(--color-banana-100));
  color: var(--color-neutral-900);
}

/* Reversed so the value paints above its label while the markup keeps dt-then-dd order, which is what lets a screen
   reader announce the pair as "Labels placed: 262,453". */
.ud-impact-stat {
  display: flex;
  flex-direction: column-reverse;
  gap: 2px;
  min-width: 0;
}

.ud-impact-value {
  margin: 0;
  font-family: var(--font-primary);

  /* Sized in px, not rem: Bootstrap sets a 10px root here, so a rem clamp silently renders at 62.5% of its intent.
     nowrap means an over-long value overflows its tile rather than breaking mid-number, so the floor is set low
     enough for a five-digit distance to fit the ~108px each tile gets at 320px. */
  font-size: clamp(22px, 5vw, 30px);
  white-space: nowrap;
  font-weight: 800;
  font-variant-numeric: tabular-nums;
  line-height: 1.05;
  letter-spacing: -0.02em;
}

/* Darker than the secondary text elsewhere on the page: the band's tint leaves that color at 4.2:1 here, under the
   4.5:1 AA needs at this size. Asphalt keeps the slate cast. */
.ud-impact-label {
  font: var(--text-small-medium);
  color: var(--color-asphalt-300);
}

/* Four across only above 1400px. The content column is not a simple function of viewport width — the right-hand
   contents rail is present above 992px and squeezes it to ~500px, so a four-track band is narrower at 1200px than it
   is at 992px, and "2,432 km" wraps mid-value. Below this the 2x2 arrangement has room to spare at every width. */
@media (width >= 1400px) {
  .ud-impact-band {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* The one place the cross-city total appears above the fold. Kept to a single sentence so the closing community band
   stays the section that actually presents those numbers. */
.ud-impact-bridge {
  margin: var(--space-sm) 0 0;
  font: var(--text-small-regular);
  color: var(--color-neutral-700);
}

/* Link blue reaches only 1.2:1 against this muted copy, so the hue can't be the only thing marking the link (1.4.1). */
.ud-impact-bridge a {
  text-decoration: underline;
}

/* ---- Leaderboard: community band + podium ---------------------------------------------------------------------- */

/* Every tile on one line, always (#5175). Each basis is an equal share of what's left once the gaps are paid for, so
   they exactly fill the row, and `min-width: 0` stops a long value from growing its own tile and pushing a lone
   widow onto a second row. Two things keep the values inside their share, in that order of preference: each swaps to
   the short form it also renders (1,662,874 → 1.7M) once full figures stop fitting, and only then does the type give
   ground. The band is its own container so both rules can measure it directly — its width depends on which of the
   page shell's three regimes is in play, so the viewport is a poor proxy.

   `--band-cols` is how many tiles the band holds, and every share below is computed from it. The leaderboard's has
   five and the dashboard's cross-city copy has four, so a hardcoded fifth strands the fourth tile alone on a second
   row at the narrow step — which is the failure this whole rule exists to prevent (#5186). A band that sets neither
   the variable nor a matching tile count is the one thing that can still go wrong here. */
.ud-community-band {
  --band-cols: 5;
  --band-cols-narrow: 3;

  display: flex;
  flex-wrap: wrap;
  container-type: inline-size;
  gap: var(--space-lg);
  padding: var(--space-lg);
  margin: var(--space-md) 0;
  border: 1px solid var(--color-pine-200);
  border-radius: var(--border-radius-lg);

  /* Light, on-brand card so the black line-art stat icons read clearly (they were invisible on the old dark band). */
  background: linear-gradient(120deg, var(--color-pine-100), var(--color-banana-100));
  color: var(--color-neutral-900);
}

/* The dashboard's cross-city copy of the band carries one tile fewer than the leaderboard's. */
.ud-cities-band {
  --band-cols: 4;
  --band-cols-narrow: 2;
}

.ud-community-stat {
  flex: 1 1 calc((100% - (var(--band-cols) - 1) * var(--space-lg)) / var(--band-cols));
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  text-align: center;
}

/* Stat icons: the same line-art PNGs the classic dashboard uses. The emoji placeholder matches their height. */
.ud-community-icon {
  height: 56px;
  width: auto;

  /* The tiles carry no intrinsic floor, so an icon wider than its share would paint over the next tile rather than
     widen its own. The widest today clears the narrowest tile by about 4px; this keeps the next one safe too. */
  max-width: 100%;
  object-fit: contain;
}

.ud-community-emoji {
  font-size: 48px;
  line-height: 56px;
}

/* Loud, celebratory community totals. Primary font, not the accent one: Raleway's old-style figures let 3/4/5/7/9
   drop below the baseline, so numbers set in it look uneven. */
.ud-community-value {
  display: block;
  font-family: var(--font-primary);

  /* px, not rem, for the reason given on .ud-impact-value above, and sized off the band (cqi) rather than the
     viewport so it tracks the width the tiles actually get. One expression covers every column count: a value may
     occupy 24% of its own tile, which is what the widest short form the tiles can hold needs. The 5px absorbs the
     gap the share calculation already paid for. Too large and a figure spills; too small and the numbers are
     needlessly meek, and only the first of those is something stat-bands.spec.js can fail on. */
  font-size: clamp(18px, calc(24cqi / var(--band-cols) - 5px), 30px);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;

  /* A grouped total is one unbreakable token: it wraps nowhere and so is never split across two lines. */
  white-space: nowrap;
}

/* Values that have a shorter form render both, and exactly one of them is shown. The full figure is the default;
   it is also the one that stays in the accessibility tree at every width, so a screen reader always reads the exact
   number while the short form is decorative (aria-hidden in the markup). */
.ud-value-short {
  display: none;
}

/* Where a row of full figures outgrows the shares it has to sit in, show the short forms instead of shrinking the
   type to suit the longest number anyone might one day reach. The full figure is only hidden visually. The threshold
   is the five-tile band's; a four-tile one gets wider shares and so abbreviates a little earlier than it must. */
@container (width < 1000px) {
  .ud-community-value .ud-value-full {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  .ud-community-value .ud-value-short {
    display: inline;
  }
}

/* Narrower than this even the short forms are illegible across a full row, so the band drops to `--band-cols-narrow`
   per line: five tiles go 3 + 2 and four go 2 + 2. Neither leaves the lone tile that reads as a mistake, which is why
   this is a per-band number rather than one divisor — three columns would strand the fourth tile of a four-tile band.
   The count is overridden on the tiles rather than the band because a container cannot restyle itself; the value's
   type inherits it from there. */
@container (width < 470px) {
  .ud-community-stat {
    --band-cols: var(--band-cols-narrow);
  }
}

/* On the narrowest phones a third of the band is smaller than the figures it holds, so each tile takes a line of its
   own. One per line is the other shape with no odd tile out, and it buys back the full type size. */
@container (width < 265px) {
  .ud-community-stat {
    --band-cols: 1;
  }
}

.ud-community-label {
  display: block;
  font: var(--text-body-regular);
  color: var(--color-asphalt-300);

  /* A tile is a fixed fraction of the band, and German runs its captions into single words longer than that
     (Beschriftungen, Validierungen). `anywhere` rather than `break-word` because only it also lets the caption's
     minimum width fall below that word — the tiles are centred, so a caption too wide for its own tile would
     otherwise hang out either side and lie across its neighbour's. */
  overflow-wrap: anywhere;
}

.ud-podium {
  display: flex;
  justify-content: center;
  align-items: flex-end;
  gap: var(--space-md);
  margin: var(--space-lg) 0;
}

.ud-podium-spot {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xs);
  width: 150px;
}

.ud-podium-name {
  font: var(--text-title-bold);
  color: var(--color-neutral-900);
}

.ud-podium-score {
  font: var(--text-body-medium);
  color: var(--color-neutral-700);
}

.ud-podium-block {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  width: 100%;
  padding-top: var(--space-sm);
  border-radius: var(--border-radius) var(--border-radius) 0 0;
  font-size: 44px; /* px, not rem, for the reason given on .ud-lb-medal. */
  color: var(--color-neutral-white);
}

.ud-podium-1 .ud-podium-block {
  height: 128px;
  background: var(--color-banana-500);
}

.ud-podium-2 .ud-podium-block {
  height: 98px;
  background: var(--color-neutral-400);
}

.ud-podium-3 .ud-podium-block {
  height: 74px;
  background: var(--color-orange-500);
}
.ud-podium-1 { order: 2; }
.ud-podium-2 { order: 1; }
.ud-podium-3 { order: 3; }

/* ---- Public profile -------------------------------------------------------------------------------------------- */
.ud-public-tag {
  display: inline-block;
  padding: 2px 10px;
  border-radius: 12px;
  background: var(--color-pine-200);
  color: var(--color-pine-800);
  font: var(--text-caption-semibold);
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

/* ---- Settings form --------------------------------------------------------------------------------------------- */
.ud-form {
  display: flex;
  flex-direction: column;
  gap: var(--space-lg);
  max-width: 620px;
  margin: var(--space-md) 0;
}

.ud-field {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.ud-label {
  font: var(--text-body-bold);
  color: var(--color-neutral-900);
}

.ud-check input:enabled { cursor: pointer; }

.ud-field-inline {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-sm);
  align-items: center;
}

.ud-field-inline .ps-input,
.ud-field-inline .ps-select { flex: 1 1 240px; }

.ud-field-help {
  font: var(--text-small-regular);
  color: var(--color-neutral-700);
}

/* The help line's muted grey is 1.2:1 against link blue, so color alone cannot separate the two (WCAG 1.4.1). */
.ud-field-help a {
  text-decoration: underline;
}

.ud-radio-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-md);
}

.ud-radio,
.ud-check {
  display: flex;
  gap: var(--space-xs);
  font: var(--text-body-regular);
  color: var(--color-neutral-900);
  cursor: pointer;
}

.ud-radio { align-items: center; }
.ud-check { align-items: flex-start; }
.ud-check input { margin-top: 3px; }

.ud-check-text {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

/* Inline "Saved ✓" / error message next to the Save button. */
.ud-save-status {
  margin-left: var(--space-sm);
  font: var(--text-body-regular);
  color: var(--color-neutral-700);
}
.ud-save-status.ud-save-ok { color: var(--color-success-200); }
.ud-save-status.ud-save-err { color: var(--color-error-200); }

/* ---- Admin page beside a user's dashboard (#4964) ----------------------------------------------------------- */
.ud-admin-flags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-xl);
}

.ud-admin-flag {
  flex: 1 1 260px;
  max-width: 360px;
  gap: var(--space-sm);
}

.ud-admin-flag .ud-save-status { margin-left: 0; }

.ud-admin-details { margin-top: var(--space-md); }

.ud-admin-details summary {
  cursor: pointer;
  font: var(--text-body-bold);
  color: var(--color-neutral-900);
}

.ud-admin-details .ps-table-scroll { margin-top: var(--space-sm); }

/* A number split across two lines reads as two numbers. */
.ud-admin-table td.num, .ud-admin-table th.num { white-space: nowrap; }

/* Two narrow columns, so the hours sit beside their city instead of a screen-width away (#4986). */
.ud-admin-hours-cities {
  max-width: 420px;
  margin-bottom: var(--space-md);
}

/* "Kept private" state on a public profile whose owner turned off public_profile. */
.ud-empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-xs);
  text-align: center;
  padding: var(--space-xl) var(--space-md);
  color: var(--color-neutral-900);
}
.ud-empty-emoji { font-size: 36px; } /* px, not rem, for the reason given on .ud-lb-medal. */

/* ---- "How is this ranked?" explainer --------------------------------------------------------------------------- */
.ud-explainer {
  margin: 0 0 var(--space-md);
  font: var(--text-small-regular);
  color: var(--color-neutral-700);
}

.ud-explainer > summary {
  cursor: pointer;
  color: var(--color-link-200);
  width: fit-content;
}

.ud-explainer > div {
  margin-top: var(--space-xs);
  line-height: 1.5;
  max-width: 720px;
}

/* ---- Create-team dialog ---------------------------------------------------------------------------------------- */
.ud-dialog {
  width: min(460px, calc(100vw - 32px));
  padding: 0;
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--border-radius-lg);
  box-shadow: var(--box-shadow-lg, 0 4px 12px rgb(0 0 0 / 15%));
}

.ud-dialog::backdrop { background: rgb(0 0 0 / 35%); }

.ud-dialog-form {
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: var(--space-lg);
}

.ud-dialog-title {
  margin: 0;
  font: var(--text-title-bold);
  color: var(--color-neutral-900);
}

.ud-dialog-sub {
  margin: 0 0 var(--space-sm);
  font: var(--text-body-regular);
  color: var(--color-neutral-700);
}

.ud-dialog-form .ud-label { margin-top: var(--space-sm); }

.ud-optional {
  font-weight: 400;
  color: var(--color-neutral-700);
}

.ud-dialog-error {
  margin-top: var(--space-sm);
  padding: 8px 12px;
  border-radius: var(--border-radius);
  background: rgb(from var(--color-error-200) r g b / 10%);
  color: var(--color-error-200);
  font: var(--text-body-regular);
}

.ud-dialog-note {
  margin: var(--space-sm) 0 0;
  font: var(--text-small-regular);
  color: var(--color-neutral-700);
}

.ud-dialog-actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--space-sm);
  margin-top: var(--space-lg);
}

.ud-team-join-label {
  display: block;
  margin-top: var(--space-xxs);
  font: var(--text-small-regular);
  color: var(--color-neutral-700);
}

/* ---- Responsive ------------------------------------------------------------------------------------------------ */

/* 768px matches the shell breakpoint (components/page-shell.css), where the left nav collapses behind a disclosure
   and the right TOC is already hidden — so here we just make the content itself stack and stay reachable on a phone. */
@media (width <= 768px) {
  .ud-hero {
    flex-direction: column;
    text-align: center;
  }

  /* One badge track per row on phones (two would squeeze the tier icons). */
  .ud-badge-tracks { grid-template-columns: 1fr; }
  .ud-hero-text { text-align: center; }

  .ud-streak { flex-direction: column; }

  .ud-streak-stats {
    flex-flow: row wrap;
    flex: 1 1 100%;
  }
  .ud-streak-kpi { flex: 1 1 140px; }

  .ud-heatmap {
    flex: 1 1 100%;
    width: 100%;
  }

  .ud-acc-label { flex-basis: 120px; }

  /* Wide leaderboard tables scroll horizontally instead of overflowing off the screen. */
  .page-content .ps-table-scroll-box {
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }
  .page-content .ps-table--sticky { min-width: 520px; }

  /* The global board carries an extra "Top city" column, so it needs more room before scrolling horizontally. */
  .page-content .ud-lb-global { min-width: 620px; }

  /* Podium shrinks to fit a phone: equal-width columns instead of fixed 150px spots. */
  .ud-podium { gap: var(--space-xs); }

  .ud-podium-spot {
    width: auto;
    flex: 1 1 0;
    min-width: 92px;
  }

  /* CTA and inline form controls stack full width and stay tappable. */
  .ud-cta {
    flex-direction: column;
    align-items: stretch;
  }
  .ud-cta-actions { flex-wrap: wrap; }

  .ud-cta-actions .ud-btn-primary,
  .ud-cta-actions .ud-btn-secondary {
    flex: 1 1 auto;
    text-align: center;
  }

  .ud-field-inline {
    flex-direction: column;
    align-items: stretch;
  }

  .ud-field-inline .ps-input,
  .ud-field-inline .ps-select { flex: 1 1 auto; }

  /* Mobile nav: this surface's section nav is two or three links, so it fits inline above the content as a chip row
       and needs no disclosure to open it — unlike the API docs' 18 links and the admin dashboard's 12, which the
       shared shell collapses behind .page-sidebar-toggle. */
  .page-sidebar-toggle { display: none !important; }

  .page-container .page-sidebar {
    display: block;
    position: static;
    width: 100%;
    height: auto;
    overflow: visible;
    border-right: none;
    border-bottom: 1px solid var(--color-neutral-200);
    background: var(--color-neutral-white);
  }

  .page-container .page-sidebar .page-nav {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-xs);
    padding: var(--space-sm) var(--space-md);
  }

  .page-container .page-sidebar .page-nav-header { display: none; }

  .page-container .page-sidebar .page-nav-item {
    padding: 6px 14px;
    border: 1px solid var(--color-neutral-200);
    border-radius: 16px;
    background: var(--color-neutral-white);
    font: var(--text-body-regular);
    color: var(--color-neutral-900);
  }

  .page-container .page-sidebar .page-nav-item.active {
    background: var(--color-pine-600);
    border-color: var(--color-pine-600);
    color: var(--color-neutral-white);
  }

  .page-container .page-sidebar .page-nav-item.disabled {
    background: var(--color-neutral-100);
    color: var(--color-neutral-700);
  }
}

@media (width <= 480px) {
  .ud-acc-label { flex-basis: 100px; }

  /* Sample-size column shrinks to fit on phones instead of holding its aligned desktop width. */
  .ud-acc-n { flex: 0 1 auto; }
  .ud-trophy { width: 100%; }

  .ud-badge {
    width: 38px;
    height: 38px;
  }
  .ud-badge-track-head { flex-wrap: wrap; }
}

/* ---- Accessibility utilities ------------------------------------------------------------------------------------ */

/* Visually-hidden text for screen readers (e.g. the rank number behind a medal emoji, podium places). */
.ud-sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* The only movement on these pages is the trophy-card hover lift; drop it (and its transition) for users who
 prefer reduced motion. Color/opacity transitions stay — they don't move anything. */
@media (prefers-reduced-motion: reduce) {
  .ud-trophy-link {
    transition: box-shadow var(--transition-fast);
  }

  .ud-trophy-link:hover,
  .ud-trophy-link:focus-visible {
    transform: none;
  }
}

/* ─── Your stories (#4054) ──────────────────────────────────────────────── */
.ud-story-row {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 12px 0;
  border-top: 1px solid var(--color-neutral-200);
}

.ud-story-row:first-child { border-top: none; }

.ud-story-row--hidden { opacity: 0.8; }

.ud-story-thumb {
  flex: none;
  width: 96px;
  height: 72px;
  object-fit: cover;
  border-radius: 6px;
  border: 1px solid var(--color-neutral-300);
}

/* Imageless rows (no photo, no label preview) keep the thumbnail's box so their text stays column-aligned. */
.ud-story-thumb--none {
  background: var(--color-neutral-100);
  border-color: var(--color-neutral-200);
}

.ud-story-body {
  flex: 1 1 auto;
  min-width: 0;
}

.ud-story-text {
  position: relative;
  z-index: 0; /* Own stacking context so the quote-mark pseudo can sit behind the text. */
  margin: 0 0 4px;
  padding-left: 18px;
  font-style: italic;
  white-space: pre-wrap;
  overflow-wrap: break-word;
}

/* Smaller cousin of the /stories cards' ghosted quote mark (community-list.css): together with the italics it
   marks this line as the author's own words. Serif deliberately — no design-system token has a serif face, and
   the classic testimonial glyph needs one (same rationale as the cards). */
.ud-story-text::before {
  content: "\201c";
  position: absolute;
  top: -2px;
  left: 0;
  z-index: -1;
  font: 700 30px/1 Georgia, serif;
  color: var(--color-neutral-300);
}

.ud-story-meta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 8px;
  font: var(--text-caption-regular);
  color: var(--color-neutral-700);
}

.ud-story-chip {
  display: inline-flex;
  align-items: center;
  padding: 3px 8px;
  border: 1px solid var(--color-banana-700);
  border-radius: 999px;
  background: var(--color-banana-100);
  color: var(--color-banana-900);
  font: var(--text-caption-semibold);
  line-height: 1;
}

.ud-story-edit,
.ud-story-delete {
  padding: 2px;
  border: none;
  background: none;
  cursor: pointer;
  font: var(--text-caption-semibold);
  text-decoration: underline;
}

.ud-story-edit { color: var(--color-link-200); }

.ud-story-delete { color: var(--color-orange-700); }

.ud-story-edit:focus-visible,
.ud-story-delete:focus-visible {
  outline: 2px solid var(--color-link-100);
  outline-offset: 2px;
  border-radius: 4px;
}

/* ---- My Routes (#3343): server-rendered rows of the user's RouteBuilder routes with inline actions. ---- */
.ud-routes {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

/* The list's display: flex would otherwise override the hidden attribute's UA display: none. */
.ud-routes[hidden] {
  display: none;
}

.ud-route-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: 12px 0;
  border-bottom: 1px solid var(--color-neutral-200);
}

.ud-route-info {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1 1 220px;
}

/* Static-map thumbnail of the route; clicking it opens the route preview in RouteBuilder. */
.ud-route-view {
  flex-shrink: 0;
  display: block;
  line-height: 0;
}

.ud-route-thumb {
  width: 180px;
  height: 90px;
  object-fit: cover;
  border: 1px solid var(--color-neutral-200);
  border-radius: 8px;
}

.ud-route-view:hover .ud-route-thumb,
.ud-route-view:focus-visible .ud-route-thumb {
  border-color: var(--color-link-100);
  box-shadow: 0 0 0 1px var(--color-link-100);
}

.ud-route-usage {
  font: var(--text-caption-regular);
  color: var(--color-neutral-700);
  cursor: help;
}

.ud-route-name {
  font-weight: 600;
  color: var(--color-neutral-900);
  overflow-wrap: anywhere;
}

/* The creator's "why this route matters" blurb, clamped so long stories keep the row scannable. */
.ud-route-desc {
  font: var(--text-small-regular);
  color: var(--color-neutral-700);
  overflow-wrap: anywhere;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.ud-route-meta {
  font: var(--text-small-regular);
  color: var(--color-neutral-700);
}

.ud-route-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs);
}

.ud-route-actions .ud-btn-secondary {
  padding: 4px 10px;
  text-decoration: none;
}

.ud-route-rename-form {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
}

.ud-route-rename-form .ps-input { padding: 4px 10px; }

.ud-route-rename-form .ud-btn-primary {
  padding: 4px 10px;
  font: var(--text-small-bold);
}

/* ---- Cities you've mapped (#4496) ------------------------------------------------------------------------------
 * The roll-up band reuses .ud-community-band so "across every city" looks the same here as on the leaderboard; only
 * the table, map, and "you're here" marker are new.
 */

.ud-cities-table-wrap { margin: var(--space-md) 0; }

/* The table isn't sortable, so drop the admin header's click affordance. */
.page-content .ud-cities-table thead th { cursor: default; }

/* The shell right-aligns every column past the first, which is what the counts and distances want. The city cell is a
   row header, and a <th> defaults to centered, so it's the one that needs saying. */
.page-content .ud-cities-table tbody th { text-align: left; }

.ud-cities-current { background: var(--color-pine-100); }

.ud-cities-popup-name {
  font-weight: 700;
  color: var(--color-neutral-900);
  margin-bottom: 4px;
}

/* Marks the deployment the mapper is reading this on, so the row they can verify against the tiles above is obvious. */
.ud-cities-here {
  display: inline-block;
  margin-left: var(--space-xs);
  padding: 1px 8px;
  border-radius: 999px;
  background: var(--color-pine-200);
  color: var(--color-neutral-900);
  font: var(--text-caption-semibold);
  white-space: nowrap;
}

.ud-cities-map {
  height: 340px;
  margin: var(--space-md) 0;
  border: 1px solid var(--color-neutral-200);
  border-radius: var(--border-radius-lg);
  overflow: hidden;
}

.ud-cities-footnote {
  color: var(--color-neutral-700);
  font: var(--text-caption-regular);
}

/* Names the deployment the KPI tiles belong to; the cross-city section below is the only part that isn't scoped. */
.ud-kpi-scope {
  margin-bottom: var(--space-xs);
  color: var(--color-neutral-700);
  font: var(--text-small-regular);
}

/* `.ud-community-band` sets a `display` that outranks the UA's `[hidden] { display: none }` — so a band the
 * script hasn't filled in yet would render as a row of empty stat labels. */
.ud-community-band[hidden] { display: none; }

/* ---- Streets with newer imagery (#4896): the mapper's own streets whose audits the imagery has outrun. ---- */
.ud-reaudit {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
}

.ud-reaudit-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-sm);
  padding: 12px 0;
  border-bottom: 1px solid var(--color-neutral-200);
}

/* The row's own `display` beats the UA stylesheet's `[hidden] { display: none }`, so the rows waiting behind
   "show more" need this to actually stay collapsed. */
.ud-reaudit-row[hidden] {
  display: none;
}

/* Rows carry no thumbnail, so the hover tint is the only feedback that a row is the one brushing the map above. */
.ud-reaudit-row:hover,
.ud-reaudit-row:focus-within {
  background: var(--color-neutral-100);
}

.ud-reaudit-info {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1 1 220px;
}

.ud-reaudit-place {
  font-weight: 600;
  color: var(--color-neutral-900);
  overflow-wrap: anywhere;
}

.ud-reaudit-meta {
  font: var(--text-small-regular);
  color: var(--color-neutral-700);
}

.ud-reaudit-audited {
  font: var(--text-small-regular);
  color: var(--color-neutral-700);
}

.ud-reaudit-actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-xs);
}

.ud-reaudit-actions .ud-btn-secondary {
  padding: 4px 10px;
  text-decoration: none;
}

.ud-reaudit-more {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-sm);
  font: var(--text-small-regular);
  color: var(--color-neutral-700);
}

.ud-reaudit-more .ud-btn-secondary {
  padding: 4px 10px;
}
