/* Faro — mobile-first, no framework. Kept small for the 3G / <500KB budget.
   The visual system and every token below are documented in docs/DESIGN.md.
   Palette follows Google Material so the app reads as familiar and modern.
   Rule: components reference tokens; no raw hex outside the token blocks. */

/* ---- Tokens (light) --------------------------------------------------------------- */
:root {
  --surface: #ffffff;
  --surface-raised: #ffffff;
  --surface-sunken: #f1f3f4;
  --text: #202124;
  --muted: #5f6368;
  --border: #dadce0;
  --overlay: rgba(32, 33, 36, 0.5);

  --brand: #2563eb;
  --brand-fg: #ffffff;
  --official: #1967d2;
  --official-fg: #ffffff;

  /* Fixed marker/accent colours — deliberately NOT overridden in the dark blocks, so the FAB,
     the announcement chip and the map markers stay the SAME colour in light and dark (they sit
     on the map or carry their own fill, not the page background). */
  --report-red: #d93025;    /* report triangles + "Report a problem" FAB */
  --report-red-fg: #ffffff;
  --announce-blue: #2563eb;  /* announcement chip + announcement circle pins */
  --announce-blue-fg: #ffffff;
  --ngo-purple: #7c3aed;     /* an NGO's own notices — council stays blue, NGO is purple.
                                Deliberately distinct from --review-fg (the "contested" badge
                                colour) so an NGO post never reads as flagged for review.
                                Applies to resource pins (shelter/water/food) too — shape
                                (square vs circle) carries "kind", colour carries "poster",
                                the same rule as plain announcements. */
  --ngo-purple-fg: #ffffff;
  --pin-bg: #f4f5f6;         /* pin fill — a light grey in BOTH themes so glyphs stay legible */
  --pin-fg: #202124;         /* pin glyph colour (monochrome-emoji fallback), dark in both themes */

  /* Emergency banner uses --report-red (see .banner) so it matches the "Report a problem"
     button exactly — one red across the app. Fixed in both themes: it's a safety signal. */

  --danger: #d33a4a;
  --danger-fg: #ffffff;
  --danger-soft-bg: #fdecee;
  --danger-soft-fg: #b3243b;
  --danger-glow: rgba(211, 58, 74, 0.45);
  --warning-bg: #fef7e0;
  --warning-fg: #a05100;
  --success: #1e8e3e;
  --success-bg: #e6f4ea;
  --success-fg: #137333;
  --info-bg: #e8f0fe;
  --info-fg: #1967d2;
  --review-bg: #f3e8fd;
  --review-fg: #8430ce;
  --stale-gray: #bdc1c6;     /* the 30-day-no-activity tier — deliberately softer/lighter than
                                --muted (used for not_our_responsibility and disputed) so the
                                two grays read as distinct at a glance. Overridden in dark
                                mode below to stay the "softer" one there too. */

  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 24px;
  --space-6: 32px;

  --radius-sm: 10px;
  --radius-md: 18px;
  --radius-pill: 999px;

  --text-xs: 0.72rem;
  --text-sm: 0.82rem;
  --text-base: 0.95rem;
  --text-md: 1.05rem;
  --text-lg: 1.25rem;
  --text-xl: 1.9rem;

  /* Material-style elevation: soft, grey-tinted, layered. */
  --shadow-sm: 0 1px 2px rgba(60, 64, 67, 0.3), 0 1px 3px 1px rgba(60, 64, 67, 0.12);
  --shadow-md: 0 1px 3px rgba(60, 64, 67, 0.3), 0 4px 8px 3px rgba(60, 64, 67, 0.13);
  --shadow-lg: 0 -1px 3px rgba(60, 64, 67, 0.1), 0 -6px 20px rgba(60, 64, 67, 0.2);
}

/* Dark tokens live in one snippet applied two ways: via prefers-color-scheme (when the user
   hasn't overridden), and via [data-theme="dark"] (when they pick Dark). No build step means
   the block is duplicated rather than shared — keep the two copies in sync. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --surface: #292a2e;
    --surface-raised: #363739;
    --surface-sunken: #202124;
    --text: #e8eaed;
    --muted: #a6abb0;
    --border: #494c50;
    --overlay: rgba(0, 0, 0, 0.55);

    --brand: #8fabf7;
    --brand-fg: #202124;
    --official: #aecbfa;
    --official-fg: #202124;

    --danger: #f5959b;
    --danger-fg: #202124;
    --danger-soft-bg: #2d1a18;
    --danger-soft-fg: #f6aea9;
    --danger-glow: rgba(245, 149, 155, 0.45);
    --warning-bg: #3a2f14;
    --warning-fg: #fdd663;
    --success: #81c995;
    --success-bg: #1c2b1f;
    --success-fg: #a8dab5;
    --info-bg: #1a2a42;
    --info-fg: #8ab4f8;
    --review-bg: #2a1e37;
    --review-fg: #d7aefb;
    --stale-gray: #5f6368;

    --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.6), 0 1px 3px 1px rgba(0, 0, 0, 0.4);
    --shadow-md: 0 1px 3px rgba(0, 0, 0, 0.6), 0 4px 8px 3px rgba(0, 0, 0, 0.4);
    --shadow-lg: 0 -1px 3px rgba(0, 0, 0, 0.4), 0 -6px 20px rgba(0, 0, 0, 0.55);
  }
}
:root[data-theme="dark"] {
  --surface: #292a2e;
  --surface-raised: #363739;
  --surface-sunken: #202124;
  --text: #e8eaed;
  --muted: #a6abb0;
  --border: #494c50;
  --overlay: rgba(0, 0, 0, 0.55);

  --brand: #8fabf7;
  --brand-fg: #202124;
  --official: #aecbfa;
  --official-fg: #202124;

  --danger: #f5959b;
  --danger-fg: #202124;
  --danger-soft-bg: #2d1a18;
  --danger-soft-fg: #f6aea9;
  --danger-glow: rgba(245, 149, 155, 0.45);
  --warning-bg: #3a2f14;
  --warning-fg: #fdd663;
  --success: #81c995;
  --success-bg: #1c2b1f;
  --success-fg: #a8dab5;
  --info-bg: #1a2a42;
  --info-fg: #8ab4f8;
  --review-bg: #2a1e37;
  --review-fg: #d7aefb;
  --stale-gray: #5f6368;

  --shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.6), 0 1px 3px 1px rgba(0, 0, 0, 0.4);
  --shadow-md: 0 1px 3px rgba(0, 0, 0, 0.6), 0 4px 8px 3px rgba(0, 0, 0, 0.4);
  --shadow-lg: 0 -1px 3px rgba(0, 0, 0, 0.4), 0 -6px 20px rgba(0, 0, 0, 0.55);
}

/* ---- Base ------------------------------------------------------------------------- */
* { box-sizing: border-box; }
html, body { height: 100%; margin: 0; }
body {
  display: flex;
  flex-direction: column;
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  color: var(--text);
  background: var(--surface);
}
.hidden { display: none !important; }

/* ---- Permanent disclaimer banner -------------------------------------------------- */
.banner {
  background: var(--report-red);   /* same red as the "Report a problem" button, for consistency */
  color: var(--report-red-fg);
  padding: var(--space-2) var(--space-3);
  font-size: 0.86rem;
  font-weight: 500;
  line-height: 1.35;
  text-align: center;
  letter-spacing: 0.1px;
  z-index: 1000;
}

/* ---- Map -------------------------------------------------------------------------- */
.map {
  flex: 1 1 auto;
  min-height: 0;
  position: relative;
}

/* Dark mode: lift the basemap a touch so it reads as a lighter dark, not near-black.
   Only the tile pane is affected — pins, circles and controls live in other panes, so their
   colours stay true. Never applied in Light mode. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .leaflet-tile-pane { filter: brightness(1.18) contrast(0.97); }
}
:root[data-theme="dark"] .leaflet-tile-pane { filter: brightness(1.18) contrast(0.97); }
/* Light: Stadia's light style paints water/parks very pale and its streets/buildings very faint.
   saturate() only touches coloured pixels (deepening water to blue and parks to green while
   leaving the neutral greys/whites of the ground alone); the small contrast() bump lifts the grey
   street and building lines so they read against the near-white ground. Scoped to mirror the dark
   rules so the two never both apply. */
@media (prefers-color-scheme: light) {
  :root:not([data-theme="dark"]) .leaflet-tile-pane { filter: saturate(1.55) contrast(1.12); }
}
:root[data-theme="light"] .leaflet-tile-pane { filter: saturate(1.55) contrast(1.12); }

/* The map controls (Leaflet zoom) get a Material treatment to match. */
.leaflet-bar {
  border: none !important;
  box-shadow: var(--shadow-sm) !important;
  border-radius: var(--radius-sm) !important;
  overflow: hidden;
}
.leaflet-bar a {
  background: var(--surface-raised) !important;
  color: var(--text) !important;
  border-color: var(--border) !important;
}
.leaflet-control-attribution {
  background: color-mix(in srgb, var(--surface-raised) 82%, transparent) !important;
  color: var(--muted) !important;
}
.leaflet-control-attribution a { color: var(--brand) !important; }

/* ---- Pins: SHAPE = kind, COLOR = status (docs/DESIGN.md §5) ------------------------ */
/* Reports = triangle (red active / neutral disputed) · resources = pink square ·
   announcements = blue circle. Reports are the smallest; the other two are larger so
   official posts and places stand out over the many citizen reports. A white/raised fill
   with a coloured outline and a category glyph in the middle across all three. */

/* Report = triangle (inline SVG so the outline is themeable), red outline, category glyph in the
   middle. The smallest of the three, but sized so the glyph is legible. Light-grey fill in both
   themes. The SVG is an absolute backdrop (z 0); the glyph is grid-centred above it (z 1) and
   nudged down into the wider lower half of the triangle. */
/* position:absolute is required — Leaflet transforms the marker to its lat/lng via translate3d,
   which only lands on the anchor point when the element is out of flow. With position:relative the
   translate stacked on top of the element's normal-flow offset, dropping the triangle ~14px below
   its circle (and by a per-marker amount), which is what made pins look off-centre when zooming. */
.report-pin { position: absolute; display: grid; place-items: center; width: 40px; height: 38px; filter: drop-shadow(var(--shadow-sm)); }
.report-pin .pin-shape { position: absolute; inset: 0; width: 100%; height: 100%; overflow: visible; z-index: 0; }
.report-pin .pin-shape path { fill: var(--pin-bg); stroke: var(--report-red); stroke-width: 2.5; stroke-linejoin: round; }
.report-pin .pin-glyph { position: relative; z-index: 1; margin-top: 5px; font-size: 16px; line-height: 1; color: var(--pin-fg); }
/* Disputed = the same light "inactive" grey as 30-day staleness (below), merged into one
   legend entry ("Inactive / disputed") — both mean "nobody's actively working this right
   now", just via different routes (community dispute vs. official quiet). Faded, never
   deleted. */
.report-pin.is-disputed .pin-shape path { stroke: var(--stale-gray); }
.report-pin.is-disputed { opacity: 0.65; }
/* An official is working it (in_progress, or the retired "acknowledged" alias) — no longer
   plain red-urgent, not yet resolved. Blue, matching the same colour officials/NGOs post
   under, since this is squarely "an official is on it". */
.report-pin.is-progress .pin-shape path { stroke: var(--info-fg); }
/* Resolved (or the retired "resolved_by_council" alias) — same green as a confirmed
   resource, reading as "done" at a glance. */
.report-pin.is-resolved .pin-shape path { stroke: var(--success); }
/* Closed without being fixed (not_our_responsibility) — its own dark/solid grey, distinct
   from the lighter --stale-gray (disputed/inactive) above. */
.report-pin.is-closed .pin-shape path { stroke: var(--muted); }
.report-pin.is-closed { opacity: 0.65; }
/* Queued offline: dashed neutral outline, visibly "not sent yet". */
.report-pin.is-pending .pin-shape path { stroke: var(--muted); stroke-dasharray: 3 3; }
.report-pin.is-pending { opacity: 0.7; }
/* Staleness (phase 11, simplified to one tier): 30 days with no activity at all, active
   reports only — every other status already has its own colour above and is exempt. Shares
   its grey with is-disputed above — see that comment for why. */
.report-pin.is-inactive .pin-shape path { stroke: var(--stale-gray); }
.report-pin.is-inactive { opacity: 0.65; }
/* Admin-hidden (phase 14): only ever rendered on the officials' own reports map (a citizen
   never receives a hidden row at all, per RLS) — greyscale + dimmed so an admin reviewing the
   map can tell at a glance what they've already moderated, independent of its status colour. */
.report-pin.is-hidden { opacity: 0.35; filter: grayscale(1); }

/* Resource = rounded square, larger than report triangles but a touch smaller than before —
   coloured by POSTER (blue council / purple NGO), same rule as announcements: shape alone
   carries "this is a place", colour carries "who posted it". */
.resource-pin {
  display: grid;
  place-items: center;
  width: 43px;
  height: 43px;
  font-size: 21px;
  color: var(--pin-fg);
  background: var(--pin-bg);
  border: 2.5px solid var(--announce-blue);
  border-radius: 11px;
  box-shadow: var(--shadow-sm);
}
.resource-pin.is-ngo { border-color: var(--ngo-purple); }
.resource-pin.is-stale { border-color: var(--muted); opacity: 0.55; }

/* Announcement = circle, blue outline, larger than report triangles (but a touch smaller
   than before). Purple for an NGO's own notice (phase 13) — council stays blue. */
.announcement-pin {
  display: grid;
  place-items: center;
  width: 43px;
  height: 43px;
  font-size: 21px;
  color: var(--pin-fg);
  background: var(--pin-bg);
  border: 2.5px solid var(--announce-blue);
  border-radius: 50%;
  box-shadow: var(--shadow-sm);
}
.announcement-pin.is-ngo { border-color: var(--ngo-purple); }

/* Approximate-location circle drawn around every report (reports are not GPS-exact).
   Red for active, neutral for disputed — matching the pin. Selected report's ring emphasised. */
.report-circle { stroke: var(--report-red); fill: var(--report-red); fill-opacity: 0.12; stroke-opacity: 0.6; stroke-width: 1.5; }
.report-circle.is-disputed { stroke: var(--stale-gray); fill: var(--stale-gray); }
.report-circle.is-progress { stroke: var(--info-fg); fill: var(--info-fg); }
.report-circle.is-resolved { stroke: var(--success); fill: var(--success); }
.report-circle.is-closed { stroke: var(--muted); fill: var(--muted); }
.report-circle.is-inactive { stroke: var(--stale-gray); fill: var(--stale-gray); }

/* The boundary-size preview shown while placing a report (submit.js) — dashed, to read as
   "not filed yet" against the solid circles of already-filed reports. */
.boundary-preview {
  stroke: var(--brand);
  fill: var(--brand);
  fill-opacity: 0.1;
  stroke-opacity: 0.8;
  stroke-dasharray: 5 4;
}
.report-circle.is-selected { fill-opacity: 0.2; stroke-opacity: 0.85; stroke-width: 2.5; }

/* ---- Map overlays: chip, Map button, legend, last-updated, empty ------------------ */
/* Announcement chip: top-centre pill, clearing the zoom control (left) and Map button (right). */
.announcement-chip {
  position: absolute;
  top: var(--space-2);
  left: 50%;
  transform: translateX(-50%);
  z-index: 600;
  max-width: calc(100% - 170px);
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-3);
  background: var(--announce-blue); /* same blue in light and dark */
  color: var(--announce-blue-fg);
  border: none;
  border-radius: var(--radius-pill);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1.2;
  box-shadow: var(--shadow-md);
  cursor: pointer;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
/* Nothing active: a quiet, non-interactive pill so it reads as status, not a call to action. */
.announcement-chip.is-empty {
  background: var(--surface-raised);
  color: var(--muted);
  font-weight: 500;
  box-shadow: var(--shadow-sm);
  cursor: default;
}

/* App version — tiny, muted, bottom-left corner. Sits above tiles, out of the way of the
   attribution (bottom-right) and the FAB (bottom-centre). Non-interactive. */
.app-version {
  position: absolute;
  left: var(--space-2);
  bottom: var(--space-1);
  z-index: 500;
  padding: 1px 6px;
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--surface-raised) 70%, transparent);
  color: var(--muted);
  font-size: 11px;
  line-height: 1.5;
  letter-spacing: 0.2px;
  pointer-events: none;
  user-select: none;
}

/* "Updated Nm ago" — small, muted, top-centre under the chip so the FAB never hides it. */
.last-updated {
  position: absolute;
  top: 46px;
  left: 50%;
  transform: translateX(-50%);
  z-index: 500;
  padding: 2px var(--space-2);
  font-size: var(--text-xs);
  line-height: 1.4;
  color: var(--muted);
  background: color-mix(in srgb, var(--surface-raised) 88%, transparent);
  border-radius: var(--radius-pill);
  box-shadow: var(--shadow-sm);
  pointer-events: none;
  white-space: nowrap;
}

/* Map button (was "Key"): opens the map key + theme control. Top-right, Material pill. */
.legend-toggle {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  z-index: 600;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 40px;
  padding: var(--space-2) var(--space-3);
  background: var(--surface-raised);
  color: var(--text);
  border: none;
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  font-weight: 600;
  box-shadow: var(--shadow-sm);
  cursor: pointer;
}
.legend-toggle svg { width: 16px; height: 16px; fill: currentColor; }

/* The Info board toggle (phase 12): right side, directly under the Map/legend toggle,
   mirroring its styling so the two floating buttons read as one family/stack. Always
   present (like the legend toggle) rather than only-when-nonempty (like the announcement
   chip) — the board is a place to check, not an alert, so it should not look inert if empty. */
.info-toggle {
  position: absolute;
  top: 60px;
  right: var(--space-2);
  z-index: 600;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 40px;
  padding: var(--space-2) var(--space-3);
  background: var(--surface-raised);
  color: var(--text);
  border: none;
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  font-weight: 600;
  box-shadow: var(--shadow-sm);
  cursor: pointer;
}

/* Language switcher: between Info and Feedback, same floating pill family. Shows the
   current language's code so it reads correctly no matter which language is active. */
.language-toggle {
  position: absolute;
  top: 112px;
  right: var(--space-2);
  z-index: 600;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 40px;
  padding: var(--space-2) var(--space-3);
  background: var(--surface-raised);
  color: var(--text);
  border: none;
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  font-weight: 600;
  box-shadow: var(--shadow-sm);
  cursor: pointer;
}

.language-list { display: flex; flex-direction: column; gap: var(--space-2); }
.language-option { justify-content: flex-start; text-align: left; }
.language-option.is-active { background: var(--brand); color: var(--brand-fg); border-color: var(--brand); }

/* Feedback (phase 22): directly under the language switcher, same floating pill family — a
   channel for bugs and suggestions about the app itself, not about an incident. */
.feedback-toggle {
  position: absolute;
  top: 164px;
  right: var(--space-2);
  z-index: 600;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 40px;
  padding: var(--space-2) var(--space-3);
  background: var(--surface-raised);
  color: var(--text);
  border: none;
  border-radius: var(--radius-pill);
  font-size: var(--text-sm);
  font-weight: 600;
  box-shadow: var(--shadow-sm);
  cursor: pointer;
}

.legend-panel {
  position: absolute;
  top: 52px;
  right: var(--space-2);
  z-index: 600;
  width: 210px;
  max-width: calc(100% - 16px);
  padding: var(--space-3);
  background: var(--surface-raised);
  color: var(--text);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-md);
  font-size: var(--text-sm);
}
.legend-panel h2 { margin: 0 0 var(--space-2); font-size: var(--text-sm); }

/* Segmented theme control. */
.seg {
  display: flex;
  gap: 2px;
  padding: 3px;
  margin-bottom: var(--space-3);
  background: var(--surface-sunken);
  border-radius: var(--radius-pill);
}
.seg-btn {
  flex: 1 1 0;
  min-height: 32px;
  padding: 4px 6px;
  border: none;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--muted);
  font: inherit;
  font-size: var(--text-xs);
  font-weight: 600;
  cursor: pointer;
}
.seg-btn.is-on { background: var(--surface-raised); color: var(--brand); box-shadow: var(--shadow-sm); }

.legend-row {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: 6px;
  line-height: 1.3;
}
/* Legend swatches: filled mini-shapes that mirror the pins (shape = kind, colour = status). */
.legend-swatch {
  flex: 0 0 auto;
  width: 16px;
  height: 16px;
  border: none;
}
.legend-swatch.tri { background: var(--report-red); clip-path: polygon(50% 0, 100% 100%, 0 100%); }
.legend-swatch.tri.progress { background: var(--info-fg); }
.legend-swatch.tri.resolved { background: var(--success); }
.legend-swatch.tri.closed { background: var(--muted); }
.legend-swatch.tri.stale { background: var(--stale-gray); }
.legend-swatch.circle { background: var(--announce-blue); border-radius: 50%; }
.legend-swatch.circle.ngo { background: var(--ngo-purple); }
.legend-foot {
  margin: var(--space-2) 0 0;
  padding-top: var(--space-2);
  border-top: 1px solid var(--border);
  font-size: var(--text-xs);
  color: var(--muted);
}
.legend-foot a { color: var(--brand); }
.legend-stats-link {
  display: block;
  margin-top: var(--space-3);
  padding-top: var(--space-2);
  border-top: 1px solid var(--border);
  font-size: var(--text-sm);
  color: var(--brand);
  text-decoration: none;
}

.map-empty {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 550;
  max-width: 80%;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-3) var(--space-3) var(--space-4);
  background: var(--surface-raised);
  color: var(--muted);
  border-radius: var(--radius-sm);
  box-shadow: var(--shadow-sm);
  font-size: var(--text-base);
  text-align: center;
  pointer-events: auto;
}
.map-empty-text { flex: 1; }
.map-empty-close {
  flex: none;
  display: grid;
  place-items: center;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  color: var(--muted);
  font-size: 13px;
  line-height: 1;
  cursor: pointer;
}
.map-empty-close:hover { background: var(--border); color: var(--text); }
/* The shimmer placeholder has no children and shouldn't reserve the flex gap. */
.map-empty.is-loading { display: block; }
.map-empty.is-loading {
  color: transparent;
  background:
    linear-gradient(90deg, var(--surface-sunken) 25%, var(--border) 37%, var(--surface-sunken) 63%);
  background-size: 400% 100%;
  animation: faro-shimmer 1.4s ease infinite;
  min-width: 180px;
  min-height: 44px;
}
@keyframes faro-shimmer {
  0% { background-position: 100% 0; }
  100% { background-position: 0 0; }
}

/* ---- Floating action button ------------------------------------------------------- */
.fab {
  position: fixed;
  left: 50%;
  bottom: var(--space-4);
  transform: translateX(-50%);
  z-index: 1000;
  min-height: 52px;
  background: var(--report-red); /* red, matching the report triangles on the map */
  color: var(--report-red-fg);
  border: none;
  border-radius: var(--radius-pill);
  padding: var(--space-3) var(--space-5);
  font-size: 1rem;
  font-weight: 600;
  box-shadow: var(--shadow-md);
  cursor: pointer;
}

/* ---- Bottom-sheet panel + scrim --------------------------------------------------- */
.scrim {
  position: fixed;
  inset: 0;
  z-index: 1000;
  background: var(--overlay);
}
.panel {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 1001;
  background: var(--surface-raised);
  color: var(--text);
  border-top-left-radius: var(--radius-md);
  border-top-right-radius: var(--radius-md);
  box-shadow: var(--shadow-lg);
  max-height: 80vh;
  overflow-y: auto;
}
.sheet-handle {
  width: 36px;
  height: 4px;
  margin: var(--space-2) auto 0;
  border-radius: var(--radius-pill);
  background: var(--border);
}
.sheet-close {
  position: absolute;
  top: var(--space-2);
  right: var(--space-2);
  width: 44px;
  height: 44px;
  display: grid;
  place-items: center;
  background: transparent;
  border: none;
  border-radius: 50%;
  color: var(--muted);
  font-size: 1.1rem;
  cursor: pointer;
}
.panel-body { padding: var(--space-3) var(--space-4) calc(var(--space-4) + env(safe-area-inset-bottom)); }

.panel-title { margin: 0 0 var(--space-3); padding-right: var(--space-6); font-size: var(--text-md); font-weight: 600; }
.panel-title.danger { color: var(--danger); }

.prompt { margin: 0 0 var(--space-4); font-size: var(--text-base); line-height: 1.4; }

.anon-note {
  margin: 0 0 var(--space-4);
  padding: var(--space-2) var(--space-3);
  background: var(--surface-sunken);
  border-radius: var(--radius-sm);
  color: var(--muted);
  font-size: var(--text-sm);
  line-height: 1.4;
}

.row { display: flex; gap: var(--space-2); flex-wrap: wrap; }

/* ---- Buttons (Material: pill, filled primary, tonal secondary) -------------------- */
.btn {
  flex: 1 1 auto;
  min-width: 8rem;
  min-height: 44px;
  padding: var(--space-3) var(--space-4);
  font-size: 1rem;
  font-weight: 500;
  border: none;
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  color: var(--text);
  text-align: center;
  text-decoration: none;
  cursor: pointer;
}
.btn-primary { background: var(--brand); color: var(--brand-fg); }
.btn-danger { background: var(--danger); color: var(--danger-fg); }
.btn:disabled { opacity: 0.5; cursor: default; }
.btn-share { flex: 0 0 auto; min-width: 0; }

/* ---- Category chooser grid -------------------------------------------------------- */
.cat-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}
.cat {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: 48px;
  padding: var(--space-3);
  font-size: var(--text-base);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--surface-raised);
  color: var(--text);
  cursor: pointer;
  text-align: left;
}
.cat-symbol { font-size: 1.2rem; line-height: 1; }

/* ---- Inputs ----------------------------------------------------------------------- */
.desc-label { display: block; margin-bottom: var(--space-1); font-size: var(--text-base); }
.desc {
  width: 100%;
  padding: var(--space-3);
  font: inherit;
  color: var(--text);
  background: var(--surface-sunken);
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  margin-bottom: var(--space-4);
  resize: vertical;
}
.desc:focus { outline: none; border-color: var(--brand); background: var(--surface-raised); }
.desc::placeholder { color: var(--muted); }

/* ---- Toasts ----------------------------------------------------------------------- */
.toast {
  position: fixed;
  left: 50%;
  bottom: 5rem;
  transform: translateX(-50%);
  z-index: 1002;
  max-width: 90vw;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  color: #fff;
  font-size: var(--text-base);
  text-align: center;
  box-shadow: var(--shadow-md);
}
.toast-ok { background: var(--success); }
.toast-error { background: var(--danger); }

/* ---- Boot fallback (colours pinned so it survives even if tokens never resolve) --- */
.boot-fallback {
  display: flex;
  flex-direction: column;
  justify-content: center;
  height: 100%;
  padding: var(--space-5);
  text-align: center;
  background: #ffffff;
  color: #1a1a1a;
  font-size: var(--text-base);
  line-height: 1.5;
}
.boot-fallback p { margin: 0 0 var(--space-2); }
.boot-fallback-title { font-size: var(--text-md); font-weight: 600; }
.boot-fallback-hint { font-size: var(--text-sm); color: #666; }
.boot-fallback a { color: #d93025; }

/* ---- Contact + address lines ------------------------------------------------------ */
.contact-line { margin: var(--space-1) 0 0; font-size: 0.88rem; line-height: 1.4; color: var(--text); }
.contact-line a { color: var(--brand); text-decoration: none; }

/* ---- Announcement list items ------------------------------------------------------ */
.announcement {
  padding: var(--space-3);
  margin-bottom: var(--space-2);
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.announcement.is-expired { opacity: 0.55; }
.announcement.is-resource { border-left: 3px solid var(--success); }
/* A pending (unverified) official/NGO sign-up in the admin dashboard's roster (phase 19). */
.announcement.is-pending-official { border-left: 3px solid var(--warning-fg); }
/* An NGO's own card (phase 13) — a plain notice or board post, never a resource (which keeps
   its green border regardless of poster; see the pin comment above for why). */
.announcement.is-ngo { border-left: 3px solid var(--ngo-purple); }
.announcement-title { margin: 0 0 var(--space-1); font-size: 1rem; }
.announcement-body { margin: 0 0 var(--space-1); font-size: 0.92rem; line-height: 1.4; }

/* ---- Report detail sheet ---------------------------------------------------------- */
.detail-head { margin-bottom: var(--space-2); }
.detail-meta {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  flex-wrap: wrap;
  margin-top: calc(-1 * var(--space-1));
}
.detail-time { font-size: 0.8rem; color: var(--muted); }
.detail-desc { margin: var(--space-2) 0 var(--space-4); font-size: var(--text-base); line-height: 1.4; }

.badge {
  display: inline-block;
  padding: 0.15rem var(--space-2);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: 600;
}
/* Report status badges: active=red, in_progress=blue, resolved=green,
   not_our_responsibility=dark/solid grey. disputed and stale both render as --muted text
   here regardless of which grey their pin/legend swatch uses (they say "Disputed"/"Stale"
   in words, so the badge doesn't need the visual distinction the pin does).
   'acknowledged'/'resolved_by_council' are retired from the official's dropdown but kept
   here so any historical row still renders correctly. */
.badge-active { background: var(--danger-soft-bg); color: var(--danger-soft-fg); }
.badge-disputed { background: var(--surface-sunken); color: var(--muted); }
.badge-resolved { background: var(--success-bg); color: var(--success-fg); }
.badge-acknowledged { background: var(--info-bg); color: var(--info-fg); }
.badge-in_progress { background: var(--info-bg); color: var(--info-fg); }
.badge-resolved_by_council { background: var(--success-bg); color: var(--success-fg); }
.badge-not_our_responsibility { background: var(--surface-sunken); color: var(--muted); }
/* Staleness (phase 11, simplified to one tier): 30 days with no activity at all. Text stays
   --muted (not the lighter --stale-gray) for contrast — the badge already says "Stale" in
   words, so only the map pin/legend need the two greys to differ for an at-a-glance read. */
.badge-stale { background: var(--surface-sunken); color: var(--muted); }
.badge-flagged { background: var(--review-bg); color: var(--review-fg); }

.vote-btn { font-size: var(--text-base); }
.hint { margin: var(--space-2) 0 0; font-size: var(--text-sm); color: var(--muted); }

/* ---- Public stats page (phase 15) -------------------------------------------------- */
.stats-page { background: var(--surface-sunken); min-height: 100%; }
.stats-main { max-width: 480px; margin: 0 auto; padding: var(--space-5) var(--space-4) var(--space-6); }
.stats-h1 { font-size: var(--text-lg); margin: 0 0 var(--space-1); }
.stats-sub { font-size: var(--text-sm); color: var(--muted); margin: 0 0 var(--space-5); }
.stats-card {
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
}
.stats-row { display: flex; justify-content: space-between; align-items: baseline; padding: 6px 0; }
.stats-label { font-size: var(--text-base); }
.stats-value { font-size: var(--text-lg); font-weight: 700; }
.stats-value.is-resolved { color: var(--success); }
.stats-value.is-open { color: var(--report-red); }
.stats-value.is-muted { color: var(--muted); }
.stats-value.is-stale { color: var(--muted); }
.stats-bar-track {
  height: 8px;
  border-radius: var(--radius-pill);
  background: var(--surface-sunken);
  overflow: hidden;
  margin: 0 0 var(--space-3);
}
.stats-bar-fill { height: 100%; border-radius: var(--radius-pill); }
.stats-bar-fill.is-resolved { background: var(--success); }
.stats-bar-fill.is-open { background: var(--report-red); }

.section-title { margin: var(--space-5) 0 var(--space-2); font-size: var(--text-base); }
.comment-list { list-style: none; margin: 0 0 var(--space-3); padding: 0; }
.comment { padding: var(--space-2); margin-bottom: var(--space-1); background: var(--surface-sunken); border-radius: var(--radius-sm); }
.comment-body { margin: 0 0 var(--space-1); font-size: 0.9rem; line-height: 1.35; word-break: break-word; }
.comment-time { font-size: var(--text-xs); color: var(--muted); }

/* Per-pin timeline (phase 9/10): a compact list, one row per status change, oldest first. */
.timeline-list { list-style: none; margin: 0; padding: 0; }
.timeline-entry {
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-2);
  padding: 6px 0; border-bottom: 1px solid var(--border);
  font-size: 0.9rem;
}
.timeline-entry:last-child { border-bottom: none; }
.timeline-label { font-weight: 500; }
.timeline-when { font-size: var(--text-xs); color: var(--muted); white-space: nowrap; }

.error-text { margin: var(--space-2) 0 0; font-size: var(--text-sm); color: var(--danger); }
.error-box {
  margin: var(--space-3) 0 0;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--danger);
  border-left-width: 4px;
  border-radius: 6px;
  background: var(--danger-soft-bg);
  color: var(--danger-soft-fg);
  font-size: 0.88rem;
  line-height: 1.4;
}

/* ---- Landing page (mifaro.net apex) ----------------------------------------------- */
.landing {
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-6) var(--space-5);
  max-width: 34rem;
  margin: 0 auto;
  text-align: center;
  overflow-y: auto;
}
.landing-title { margin: var(--space-2) 0 0; font-size: var(--text-xl); color: var(--official); }
.landing-tagline { margin: 0; font-size: 1rem; line-height: 1.5; color: var(--muted); }
.landing-choose { margin: var(--space-5) 0 var(--space-1); font-size: 1rem; color: var(--muted); font-weight: 600; }
.landing-cities { display: flex; flex-direction: column; gap: var(--space-2); width: 100%; max-width: 22rem; }
.landing .landing-city {
  display: block;
  min-height: 44px;
  padding: var(--space-3) var(--space-4);
  background: var(--brand);
  color: var(--brand-fg);
  border-radius: var(--radius-sm);
  font-size: var(--text-md);
  font-weight: 600;
  text-decoration: none;
  box-shadow: var(--shadow-sm);
}

/* ---- Officials console (login.html only) ------------------------------------------- */
.official-page { background: var(--surface-sunken); }
.banner-official { background: var(--official); color: var(--official-fg); }

/* An NGO account gets the same console, but purple throughout (role.is-ngo, set once
   fetchMyOfficial resolves — see official.js) so the colour code is consistent with the
   map's own council-blue/NGO-purple rule (docs/DESIGN.md §5), not just pin colours. */
.official-page.is-ngo .banner-official { background: var(--ngo-purple); color: var(--ngo-purple-fg); }
.official-page.is-ngo .btn-primary { background: var(--ngo-purple); color: var(--ngo-purple-fg); }
.official-page.is-ngo .console-tabs .btn.is-active {
  background: var(--ngo-purple); color: var(--ngo-purple-fg); border-color: var(--ngo-purple);
}
.official-page.is-ngo .desc:focus { border-color: var(--ngo-purple); }

/* ---- Admin console (admin.html only) — reuses the console layout above, but red
   throughout (the highest-privilege role gets the app's one red, same token as the
   citizen-facing report/danger colour) so it reads as distinct from both the blue
   council console and the purple NGO one. ------------------------------------------- */
.admin-page { background: var(--surface-sunken); }
.banner-admin { background: var(--report-red); color: var(--report-red-fg); }
.admin-page .btn-primary { background: var(--report-red); color: var(--report-red-fg); }
.admin-page .console-tabs .btn.is-active {
  background: var(--report-red); color: var(--report-red-fg); border-color: var(--report-red);
}
.admin-page .desc:focus { border-color: var(--report-red); }
.console {
  flex: 1 1 auto;
  width: 100%;
  max-width: 42rem;
  margin: 0 auto;
  padding: var(--space-4) var(--space-4) var(--space-6);
  overflow-y: auto;
}
/* Top bar: title + who's signed in on the left, sign-out on the right. */
.console-topbar {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}
.console-topbar .btn { flex: 0 0 auto; min-width: 0; }
.console-topbar .row { align-items: center; }
.lang-select { width: auto; margin-bottom: 0; flex: 0 0 auto; }
.console-h1 { margin: 0; font-size: var(--text-lg); font-weight: 600; }
.console-sub { margin: 2px 0 0; font-size: var(--text-sm); color: var(--muted); }
.console-foot { margin: var(--space-2) 0 0; font-size: var(--text-sm); color: var(--muted); text-align: center; }

/* Each area of the console is a card, so sections read as distinct at a glance. */
.card {
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  padding: var(--space-4);
  margin-bottom: var(--space-4);
  box-shadow: var(--shadow-sm);
}
.card-title { margin: 0 0 var(--space-1); font-size: var(--text-md); font-weight: 600; }
.card-hint { margin: 0 0 var(--space-3); font-size: var(--text-sm); color: var(--muted); line-height: 1.4; }
.card .desc { margin-bottom: 0; }

/* A labelled field: label above its input, consistent spacing. */
.field { margin-bottom: var(--space-3); }
.field:last-of-type { margin-bottom: 0; }
.field-label { display: block; margin-bottom: 6px; font-size: var(--text-sm); font-weight: 600; color: var(--text); }

/* The report-boundary radius slider (submit.js) — a plain 0-200m range, styled to match the
   app's rounded/pill language rather than the browser's default track+thumb. */
.boundary-label-row { display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 6px; }
.boundary-value { font-size: var(--text-sm); color: var(--muted); font-variant-numeric: tabular-nums; }
.boundary-slider {
  -webkit-appearance: none;
  appearance: none;
  width: 100%;
  height: 6px;
  border-radius: var(--radius-pill);
  background: var(--border);
  outline: none;
}
.boundary-slider::-webkit-slider-thumb {
  -webkit-appearance: none;
  appearance: none;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--brand);
  border: 2px solid var(--surface-raised);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
}
.boundary-slider::-moz-range-thumb {
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--brand);
  border: 2px solid var(--surface-raised);
  box-shadow: var(--shadow-sm);
  cursor: pointer;
}

.list-title { margin: var(--space-2) 0 var(--space-3); font-size: var(--text-md); font-weight: 600; }
.anchor-label { display: flex; align-items: center; gap: var(--space-2); margin: 0 0 var(--space-2); font-size: var(--text-sm); }
.picker-map { height: 220px; border: 1px solid var(--border); border-radius: var(--radius-sm); margin-bottom: var(--space-1); }
/* The officials' reports map (phase 9): bigger than the composer's location picker since it
   is the primary view of this tab, but a fixed height (not flex-fill like the citizen map)
   because the console scrolls as a normal document, not a locked-viewport app shell. */
.official-reports-map { height: 60vh; min-height: 360px; border: 1px solid var(--border); border-radius: var(--radius-sm); margin-bottom: var(--space-2); }
.console-tabs { display: flex; gap: var(--space-2); margin-bottom: var(--space-4); }
.console-tabs .btn.is-active { background: var(--brand); color: var(--brand-fg); border-color: var(--brand); }

/* ---- Motion preference ------------------------------------------------------------ */
@media (prefers-reduced-motion: reduce) {
  .map-empty.is-loading { animation: none; }
}
