﻿/* ============================================================
   P4 — trust stack: unverified markers, share row, timeline
   ============================================================ */

/* Round-95k: the Dark basemap is OSM inverted in CSS — no keyed provider required. */
.tiles-darkened{ filter:invert(1) hue-rotate(180deg) brightness(.91) contrast(.9) saturate(.7); }
/* Round-95m: the Street base, calmed toward the old Voyager feel — desaturated, a touch
   brighter, contrast eased so the service pins carry the colour hierarchy again. */
.tiles-muted{ filter:saturate(.55) brightness(1.04) contrast(.94); }
/* Round-96l: street is real CARTO Voyager end-to-end — no filters. (.tiles-muted above
   stays for any future raw-OSM layer.) */

/* unverified community reports: hollow marker in the service color */
.inc-marker.unv{
  background:rgba(243,244,241,.9) !important;
  border:2.5px dashed currentColor;
}

/* resolved incidents: dimmed grace-period rendering */
.inc-marker.dim{ opacity:.45; filter:grayscale(.5); }

/* share row */
.share-row{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
/* round-70g: sized like .react-btn (comment/like/dislike) and THEME-colored only —
   emoji render as silhouettes (.ico-t) in the deep green, never full-colour */
.share-row button{
  width:30px; height:30px; border-radius:50%; border:1.5px solid var(--track);
  background:var(--paper); cursor:pointer; font-size:.8rem; font-weight:800;
  font-family:var(--font); color:var(--deep); transition:all .15s;
  display:inline-flex; align-items:center; justify-content:center;
}
.share-row button:hover{ border-color:var(--accent); background:var(--mintbg); }
.share-row .ico-t{ --ico: var(--deep, #4F8577); }
/* round-70f: the share row sits inline after the report content (no longer a bottom fold);
   the native-share pill fronts it where the Web Share API exists (i.e. most phones) */
.share-inline{ margin-top:12px; padding-top:10px; border-top:1px dashed var(--track); }
.share-row button.share-native{
  width:auto; border-radius:999px; padding:0 12px; font-size:.8rem;
  background:var(--deep); border-color:var(--deep); color:#fff;
}
.share-row button.share-native .ico-t{ --ico:#fff; }
.share-row button.share-native:hover{ background:var(--accent); border-color:var(--accent); }

/* public incident timeline */
.tl-row{
  border-left:3px solid var(--track); padding:6px 0 6px 12px; margin-left:4px;
  font-size:.85rem; color:var(--ink-soft);
}
.tl-row b{ color:var(--deep); }
.tl-row small{ color:var(--ink-soft); }

/* legal pages */
.legal{ max-width:820px; margin:0 auto; }
.legal h2{ color:var(--ink); font-size:1.3rem; margin:28px 0 10px; }
.legal p, .legal li{ color:var(--ink-soft); font-size:.98rem; line-height:1.65; }
.legal ul{ margin:8px 0 8px 22px; }
.legal .flag{
  background:#F3E9D8; border:1px solid #DECBA4; color:#7A5A22;
  padding:12px 16px; border-radius:10px; font-size:.9rem; margin-bottom:22px;
}
/* ---------------- omnibox search (P5) ---------------- */
.omni{ position:relative; margin-left:22px; flex:0 1 320px; }
.omni input{
  width:100%; padding:9px 14px; border-radius:999px; border:1.5px solid var(--track);
  background:#FBFBF8; font-family:var(--font); font-size:.92rem; color:var(--ink);
}
.omni input:focus{ outline:none; border-color:var(--accent); }
.omni-results{
  position:absolute; top:110%; left:0; right:0; z-index:1200;
  background:var(--paper); border:1px solid var(--track); border-radius:14px;
  box-shadow:0 14px 40px rgba(44,59,54,.25); overflow:hidden; max-height:70vh; overflow-y:auto;
}
.omni-group{
  padding:8px 14px 4px; font-size:.7rem; font-weight:800; letter-spacing:1.5px;
  text-transform:uppercase; color:var(--sage2);
}
.omni-hit{ display:block; padding:8px 14px; text-decoration:none; min-width:0; }
/* Round-91p: a wide dropdown with the groups as columns.
   Stacked vertically, four categories meant scrolling a narrow strip past three of them to see
   the fourth — on a screen with room to spare. The panel now widens beyond the input and lays the
   groups out side by side, anchored to the input's RIGHT edge so it grows leftwards into the page
   rather than off it. Below 900px nothing applies: .omni-col is a plain block and the groups
   stack exactly as before, which is what a phone wants anyway. */
@media (min-width: 900px){
  .omni-results{
    /* FIXED and centred, not anchored to the input. Right-anchoring a 1040px panel to an input
       that sits well left of centre put three of its four columns off the left edge of the screen
       — measured at x=-622. A centred band under the header cannot overflow either side whatever
       the header layout does, and reads as the full-width panel this is meant to be. */
    /* Stays ABSOLUTE, anchored to the search box as it always was — the vertical placement was
       never the problem. Only the width changes: right:0 made the panel match the input, and
       right-anchoring a wide one instead pushed three of its four columns off the left edge
       (measured at x=-622). Anchoring left and capping the width against the viewport keeps it
       on screen without needing to know where in the header the box sits.

       position:fixed was tried and is the wrong tool here: percentage offsets on a fixed element
       resolve against the VIEWPORT, so top:100% put the panel at the bottom of the window. */
    left:0; right:auto;
    width:min(1040px, calc(100vw - 28px));
    display:grid; grid-template-columns:repeat(auto-fit, minmax(230px, 1fr));
    align-items:start; gap:0 8px; padding:6px 4px;
  }
  .omni-col{ min-width:0; }
  /* Long headlines and OSM descriptions WRAP rather than run on.
     They were nowrap+ellipsis, which is the wrong trade in a column this narrow: a report headline
     and an address like "La, Accra, La-Dade-Kotopon Municipal District, Greater Accra Region" both
     lose the part that identifies them, and the last column lost its text past the panel edge.
     Two lines, wrapped, says which result this is; one clipped line does not.
     min-width:0 on the ANCHOR too — a grid item's automatic minimum is its content, so without it
     the longest address widens the whole column and pushes its neighbours out of the panel. */
  .omni-hit{ min-width:0; }
  .omni-hit b, .omni-hit small{
    white-space:normal; overflow-wrap:anywhere;
    display:-webkit-box; -webkit-box-orient:vertical; -webkit-line-clamp:2; overflow:hidden;
  }
}
.omni-hit:hover{ background:var(--mintbg); text-decoration:none; }
.omni-hit b{ display:block; color:var(--ink); font-size:.92rem; }
.omni-hit small{ color:var(--ink-soft); font-size:.78rem; }
.omni-empty{ padding:14px; color:var(--ink-soft); font-size:.9rem; }
/* The sheet-only controls never show on desktop. `.nav-icon.omni-open` rather than a bare
   `.omni-open`: the trigger also carries .nav-icon, whose display:inline-flex is declared later
   in this file at the same specificity and would otherwise win the cascade and leak the phone
   trigger onto desktop. */
.omni-close, .omni-clear, .nav-icon.omni-open{ display:none; }

/* ============ round-84: phone search sheet ============================================
   Two problems, one fix.

   1. iOS Safari auto-zooms the page whenever a focused input has font-size < 16px. The nav
      inputs were 15.2px, so tapping search zoomed the viewport and pushed the right-hand
      icon cluster off-screen — the grid button sat at x=365 of a 375px viewport, so it took
      almost nothing to lose it. That is the "nav extends and the grid menu goes slightly
      beyond screen view" report. 16px is the whole fix; it is a threshold, not a preference.

   2. Below 900px .omni was simply display:none, so on a phone there was NO site search on any
      page except the live map. The map's own box stayed, but shrunk to ~229px shared with the
      icons — enough to type into, not enough to read results in.

   So on a phone the box stops being a box: focusing it promotes the SAME element to a
   full-screen sheet, with the input at the top and results filling the rest. One input, one
   results container, same ids — the delegated search JS is untouched.
   ====================================================================================== */
@media (max-width: 900px){
  /* the inline pill disappears; the sheet and the nav trigger replace it */
  .omni:not(.omni-sheet){ display:none; }
  .nav-icon.omni-open{ display:inline-flex; }

  /* THE iOS ZOOM FIX. Targeted by id on purpose: the global form-control rule in style.css is
     `input:not([type="checkbox"]):not([type="radio"])...` — eight attribute selectors, so
     specificity 0,8,1. A sane `.omni-sheet input` (0,1,1) loses to it and the input silently
     stays at 15.2px, which is exactly the bug. One id (1,0,0) settles it for both the inline
     map box and the sheet. Do not "tidy" this into a class selector. */
  #omniInput{ font-size:16px; }
}

@media (max-width: 900px){
  html.search-open, html.search-open body{ overflow:hidden; }   /* no scrolling behind the sheet */

  /* Two separate things the header does to its own children, both of which broke the sheet.

     1. CONTAINING BLOCK. .site-header carries backdrop-filter:blur(8px), and a filtered element
        becomes the containing block for every position:fixed descendant. The sheet lives inside
        the header, so `inset:0` anchored to the header rather than the viewport and the sheet
        opened 93px down the page.

     2. STACKING CONTEXT. The header is position:fixed with z-index:1100, which makes it a
        stacking context — so the sheet's z-index:2000 only ranks it against its siblings INSIDE
        the header. Against the signed-out "be the first to know" bar (#signupBar, z-index:1200,
        a sibling of the header, not a child) the entire header lost, and the bar sat on top of
        the search UI. Raising the header itself is the fix; raising the child never could be.

     Both are scoped to while the sheet is open, and cost nothing visually because the sheet
     covers the header completely. */
  html.search-open .site-header, html.search-open .map-header{
    backdrop-filter:none;
    z-index:2100;          /* must exceed #signupBar's 1200 */
  }

  /* GRID, not flex-wrap. The first version used `display:flex; flex-wrap:wrap` with the results
     pane at `flex:1 1 100%`, which sizes a wrapped item to its CONTENT height rather than to the
     space left over. The pane grew to 1132px inside an 812px viewport, clientHeight equalled
     scrollHeight, so overflow-y:auto had nothing to do and the results simply ran off the bottom
     of the screen — unreachable, because the body is scroll-locked while the sheet is up.

     A grid row can be told to stop. `minmax(0, 1fr)` is the load-bearing part: a plain `1fr` has
     an automatic minimum of min-content and would grow to fit the list all over again. The 0
     minimum lets the row be capped at the leftover height, which is what finally gives the pane
     something to scroll. */
  .omni.omni-sheet, .msearch.omni-sheet{
    position:fixed; inset:0; z-index:2000;
    display:grid;
    grid-template-columns:auto minmax(0,1fr) auto;   /* back | input | clear */
    grid-template-rows:auto minmax(0,1fr);           /* bar  | scrollable body */
    align-items:center; gap:8px;
    width:auto; margin:0; padding:calc(env(safe-area-inset-top, 0px) + 10px) 12px 0;
    background:var(--paper); border:0; border-radius:0; box-shadow:none;
    /* dvh so the sheet tracks the on-screen keyboard instead of hiding behind it
       (vh first: pre-108 engines drop the dvh line and keep this one) */
    height:100vh;
    height:100dvh;
  }
  /* Placed explicitly so DOM order does not matter — the map header's box also contains a logo
     and a Go button, both hidden in sheet mode, and hidden items leave no grid track behind. */
  .omni-sheet .omni-close{ grid-column:1; grid-row:1; }
  .omni-sheet input      { grid-column:2; grid-row:1; }
  .omni-sheet .omni-clear{ grid-column:3; grid-row:1; }
  .omni-sheet .omni-results,
  .omni-sheet .omni-hint { grid-column:1 / -1; grid-row:2; align-self:stretch; min-height:0; }
  /* the map box's logo is redundant once the sheet fills the screen — the back arrow is clearer */
  .omni-sheet .msearch-logo, .omni-sheet .msearch-btn{ display:none; }

  .omni-sheet .omni-close{
    display:inline-flex; align-items:center; justify-content:center; flex:0 0 auto;
    width:40px; height:40px; padding:0; border:0; border-radius:50%;
    background:none; color:var(--ink); cursor:pointer;
  }
  .omni-sheet .omni-close:active{ background:var(--mintbg); }
  .omni-sheet .omni-close svg{ width:24px; height:24px; }

  .omni-sheet input{
    min-width:0; height:44px; margin:0; padding:0 12px;
    border:1.5px solid var(--track); border-radius:999px; background:#FBFBF8;
    font-size:16px;            /* THE iOS zoom threshold — do not lower this */
    color:var(--ink);
  }
  .omni-sheet input:focus{ outline:none; border-color:var(--accent); }
  /* the platform's own clear affordance would sit under ours */
  .omni-sheet input::-webkit-search-cancel-button{ -webkit-appearance:none; appearance:none; }

  .omni-sheet .omni-clear{
    display:inline-flex; align-items:center; justify-content:center; flex:0 0 auto;
    width:40px; height:40px; padding:0; border:0; border-radius:50%;
    background:none; color:var(--ink-soft); font-size:1.1rem; line-height:1; cursor:pointer;
  }
  .omni-sheet .omni-clear[hidden]{ display:none; }

  /* results stop being a floating dropdown and become the scrollable body of the sheet */
  .omni-sheet .omni-results{
    position:static; width:100%; max-height:none;
    margin:0; border:0; border-radius:0; box-shadow:none; background:none;
    overflow-y:auto; overscroll-behavior:contain; -webkit-overflow-scrolling:touch;
    padding-bottom:calc(env(safe-area-inset-bottom, 0px) + 16px);
  }
  .omni-sheet .omni-hit{
    padding:13px 8px;                       /* >=44px tall: a thumb target, not a mouse target */
    border-bottom:1px solid var(--track);
  }
  .omni-sheet .omni-hit b{ font-size:1rem; }
  .omni-sheet .omni-hit small{ font-size:.85rem; }
  .omni-sheet .omni-hit:active{ background:var(--mintbg); }
  .omni-sheet .omni-group{ padding:14px 8px 6px; font-size:.72rem; }
  .omni-sheet .omni-empty{ padding:20px 8px; }

  /* shown before anything is typed, so the sheet is never a blank white screen.
     display:block is load-bearing — the base `.omni-hint{display:none}` sits later in this file
     and would otherwise win by cascade order regardless of this rule's higher specificity. */
  .omni-sheet .omni-hint{
    display:block; flex:1 1 100%; padding:18px 8px; color:var(--ink-soft); font-size:.9rem;
  }
  .omni-sheet .omni-results:not([style*="none"]) ~ .omni-hint{ display:none; }
}
.omni-hint{ display:none; }

.foot-legal{ display:flex; flex-wrap:wrap; gap:4px 16px; margin-top:10px; }
.foot-legal a{ font-size:.82rem !important; color:#9fb8ab !important; display:inline !important; padding:0 !important; }
.foot-disclaimer{ font-size:.8rem; color:#9fb8ab; margin-top:12px; max-width:640px; }

/* ---- P7: locked service pills + partner-with-us panel ---- */
.pill-svc.locked{ opacity:.55; border-style:dashed; }
.pill-svc.locked:hover{ opacity:.85; border-color:var(--track); }
.partner-panel{
  position:fixed; top:calc(var(--header-h) + 60px); left:50%; transform:translateX(-50%);
  z-index:970; width:min(380px, calc(100vw - 28px)); padding:18px 20px;
}
.partner-panel h3{ margin-right:28px; }
.partner-panel p{ font-size:.9rem; margin-top:8px; }

/* ---- P18: "More" nav dropdown ---- */
.nav-more{ position:relative; display:inline-block; }
.nav-more-btn{
  background:none; border:none; cursor:pointer; font-family:var(--font);
  font-weight:600; font-size:inherit; color:inherit; padding:6px 2px;
}
.nav-more-menu{
  display:none; position:absolute; top:100%; right:0; min-width:180px; z-index:1200;
  background:var(--paper, #F1EBE4); border:1.5px solid var(--track); border-radius:12px;
  box-shadow:0 10px 28px rgba(44,59,54,.18); padding:8px 0;
}
.nav-more.open .nav-more-menu{ display:block; }
.nav-more-menu a{ display:block; padding:9px 18px; }
.nav-more-menu a:hover{ background:rgba(97,141,124,.1); }
@media (max-width: 900px){
  .nav-more{ display:block; }
  .nav-more-menu{ position:static; display:block; border:none; box-shadow:none; padding:0; }
  .nav-more-btn{ display:none; }
}

/* ---- P18: aggregation bubbles (zoom-banded map) ---- */
.agg-bubble{
  width:34px; height:34px; border-radius:50%; display:flex; align-items:center; justify-content:center;
  color:#fff; font-weight:800; font-size:.85rem; font-family:var(--font);
  border:2.5px solid rgba(255,255,255,.9); box-shadow:0 3px 10px rgba(44,59,54,.35);
}

/* ---- Brand refresh: theme-colored icons + themed scrollbars ---- */

/* Emoji rendered as single-color silhouettes (theme colors only):
   color:transparent + zero-blur text-shadow paints the glyph in one color. */
.inc-marker{ color:transparent; text-shadow:0 0 0 var(--ico, #F3F4F1); }
.ico-t{ color:transparent; text-shadow:0 0 0 var(--ico, var(--accent, #618D7C)); }
.chip.sel .ico-t, .chip.sel-amber .ico-t, .chip.sel-red .ico-t{ text-shadow:0 0 0 #F3F4F1; }
/* neutralize native emoji colors on admin/nav card headings */
.grid a.card > h3{ filter:grayscale(1); }

/* Themed scrollbars (Firefox + WebKit) */
html{ scrollbar-color:#618D7C transparent; scrollbar-width:thin; }

/* Round-17b: native checkboxes/radios/switches wear the brand accent everywhere
   (settings toggles, map pin filters, story filters, report form, admin pages) */
input[type="checkbox"], input[type="radio"], input[type="range"], progress{
  accent-color:var(--accent, #618D7C);
}

/* ---- Round-23: site-wide native control theme ----
   `.field input/select/textarea` (style.css) already themed form-card controls, but every
   control OUTSIDE a .field (admin tables, inline filters, popovers) fell back to browser
   chrome — which is why several pages hand-rolled the same inline select styling. These are
   deliberately LOW specificity (bare element selectors), so every existing rule that targets
   a control via a class (.field input, .msearch input, .compose textarea, .base-switch …)
   still wins, and inline styles still win over everything. */

/* Drives the native bits CSS can't reach: the select popup list, date/time picker panels,
   spinners and autofill. The whole site is the light "paper" theme. */
:root{ color-scheme:light; }

input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="submit"]):not([type="button"]):not([type="reset"]):not([type="image"]),
select,
textarea{
  font-family:var(--font); font-size:.95rem; color:var(--ink, #2C3B36);
  background:#FBFBF8; border:1.5px solid var(--track, #ADC3B9); border-radius:10px;
  padding:9px 12px; transition:border-color .15s, box-shadow .15s;
}
input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):focus,
select:focus, textarea:focus{
  outline:none; border-color:var(--accent, #618D7C); box-shadow:0 0 0 3px rgba(97,141,124,.18);
}
input:disabled, select:disabled, textarea:disabled{
  background:#F1F0EA; color:var(--ink-soft, #55635C); cursor:not-allowed; opacity:.75;
}
input::placeholder, textarea::placeholder{ color:var(--ink-soft, #55635C); opacity:.8; }

/* select: swap the OS arrow for a brand chevron (appearance:none kills the native one).
   `.field select` (style.css) MUST be listed explicitly: it is more specific (0,1,1) than a bare
   `select` (0,0,1), and its `background:` SHORTHAND resets background-image — so without this the
   native arrow would be suppressed while the chevron never painted, leaving no indicator at all.
   Same specificity + p4.css loads later ⇒ these win. */
select,
.field select{
  appearance:none; -webkit-appearance:none; -moz-appearance:none; cursor:pointer;
  padding-right:34px;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%234F8577' stroke-width='2.6' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpolyline points='6 9 12 15 18 9'/%3E%3C/svg%3E");
  background-repeat:no-repeat; background-position:right 11px center; background-size:14px;
}
select:disabled{ cursor:not-allowed; }
html[dir="rtl"] select,
html[dir="rtl"] .field select{ padding-right:12px; padding-left:34px; background-position:left 11px center; }
/* the popup list itself (honoured by Firefox/Chromium on some platforms) */
select option{ background:#FBFBF8; color:var(--ink, #2C3B36); }

/* date/time pickers: tint the OS indicator toward the brand instead of flat black */
input::-webkit-calendar-picker-indicator{ cursor:pointer; opacity:.65; }
input::-webkit-calendar-picker-indicator:hover{ opacity:1; }
/* file inputs (evidence upload, SafeNet photo): theme the button half */
input[type="file"]{ font-family:var(--font); font-size:.92rem; color:var(--ink, #2C3B36); }
input[type="file"]::file-selector-button{
  font-family:var(--font); font-weight:800; font-size:.85rem; cursor:pointer;
  margin-right:10px; padding:8px 14px; border-radius:999px; border:0;
  background:var(--accent, #618D7C); color:#fff;
}
input[type="file"]::file-selector-button:hover{ background:var(--deep, #4F8577); }
::-webkit-scrollbar{ width:11px; height:11px; }
::-webkit-scrollbar-track{ background:transparent; }
::-webkit-scrollbar-thumb{
  background:#618D7C; border-radius:6px; border:2.5px solid #E2DCD3;
}
::-webkit-scrollbar-thumb:hover{ background:#4F8577; }

/* ---- Simplified header: right cluster, app-launcher grid menu, avatar-only user ---- */
.nav-right{ margin-left:auto; display:flex; align-items:center; gap:12px; }
.nav-right > *{ flex:0 0 auto; } /* round-11: icons must never squash into ovals */
@media (max-width: 640px){
  /* round-11: small screens drop the brand text + slogan, keep the droplet */
  .site-header .brand > span{ display:none; }
  .nav-right{ gap:8px; }
}
/* Round-95l: at 360dp (LG G6 class — much of the Ghana install base) the standard header's
   row overflowed and the grid button was clipped off the right edge. Squeeze: tighter
   padding and gaps, slightly smaller circles, and the map shortcut goes — the brand logo
   already links to the live map. */
@media (max-width: 420px){
  .site-header{ padding:0 10px; }
  .nav-right{ gap:4px; }
  /* Round-96e/g: map + stories icons live on phones AND the brand pin stays (user asked
     for the app icon on every nav) — only the wordmark yields. Measured on the G6: the
     row has ~100px slack at 360dp with 36px icons, so the ~44px pin fits. */
  .site-header .brand span{ display:none; }
  .nav-icon{ width:36px; height:36px; }
  /* Round-96h: on every user page EXCEPT stories (fewer icons there — it reads fine
     packed right) the icon row spreads across the phone width instead of bunching
     right with dead space after the brand pin. The map header is separate markup. */
  .site-header:not(.on-stories) .nav-right{ flex:1 1 auto; justify-content:space-between; gap:0; margin-left:6px; }
  .nav-icon svg{ width:20px; height:20px; }
  .nav-right .bell, .nav-right .msg-btn{ padding:4px; }
  .nav-report{ transform:scale(.92); }
}

.bell{ position:relative; font-size:1.15rem; padding:6px; border-radius:8px; color:transparent; text-shadow:0 0 0 var(--deep, #4F8577); display:inline-flex; align-items:center; justify-content:center; }
.msg-btn{ color:var(--deep, #4F8577); text-shadow:none; }
/* Round-94c: .bell was styled for an EMOJI (transparent text + shadow silhouette). These two
   carry real SVGs now, so undo that and size them like the envelope always was. */
.nav-svg{ color:var(--deep, #4F8577); text-shadow:none; }
.nav-svg svg{ width:20px; height:20px; }
.nav-svg.on{ color:var(--accent, #618D7C); }
.msg-btn svg{ width:20px; height:20px; }
.bell:hover{ background:rgba(97,141,124,.12); text-decoration:none; }
.bell-badge{
  position:absolute; top:0; right:0; background:#C0392B; color:#fff;
  font-size:.6rem; font-weight:800; border-radius:999px; padding:1px 5px; line-height:1.3;
  text-shadow:none;
}

.avatar{ display:inline-flex; }
.avatar img, .avatar-fallback{
  width:34px; height:34px; border-radius:50%; object-fit:cover;
  border:2px solid var(--accent, #618D7C);
}
.avatar-fallback{
  display:inline-flex; align-items:center; justify-content:center;
  background:var(--accent, #618D7C); color:#F3F4F1; font-weight:800; font-size:1rem;
}
.avatar:hover img, .avatar:hover .avatar-fallback{ border-color:var(--deep, #4F8577); }

.nav-grid{ position:relative; }
.grid-btn{
  background:none; border:0; cursor:pointer; color:var(--deep, #4F8577);
  width:42px; height:42px; border-radius:10px;
  display:inline-flex; align-items:center; justify-content:center;
  transition:background .15s;
}
.grid-btn:hover, .nav-grid.open .grid-btn{ background:rgba(97,141,124,.14); }

.grid-menu{
  display:none; position:absolute; top:calc(100% + 8px); inset-inline-end:0; z-index:1200;
  width:min(340px, calc(100vw - 20px));
  background:#F3F4F1; border:1.5px solid var(--track, #ADC3B9); border-radius:16px;
  box-shadow:0 14px 36px rgba(44,59,54,.22); padding:12px;
  grid-template-columns:repeat(3, 1fr); gap:4px;
  /* Round-84: the menu grew (get-the-app tile, language rows) past a short phone's screen —
     cap it to the viewport and scroll inside, without the page scrolling behind it. */
  max-height:min(74vh, calc(100vh - 110px)); overflow-y:auto; overscroll-behavior:contain;
}
.nav-grid.open .grid-menu{ display:grid; }
/* Round-94: the grid menu lives inside the header's stacking context (z 1100), and the mobile
   bottom-sheet incident panel sits at 1250 — the open menu rendered UNDER the panel. Lift the
   whole header only while the menu is open; the sheet's close button stays on top otherwise. */
/* Round-94c: 1300 cleared the mobile incident sheet (1250) but NOT Ada's FAB (2500), so the
   menu still opened behind Ada on ordinary pages. Above everything the menu can meet. */
/* The ladder this has to clear, top rung last: Ada's FAB 2500 < the phone messenger shell
   2600 < the open grid menu. Equal z-index loses to whatever comes later in the document,
   which is how the menu ended up behind the messages pane. */
html.grid-open .site-header, html.grid-open .map-header{ z-index:2700; }   /* :has twin (pre-105) */
html:has(.nav-grid.open) .site-header, html:has(.nav-grid.open) .map-header{ z-index:2700; }
.grid-menu a{
  display:flex; flex-direction:column; align-items:center; gap:6px; text-align:center;
  padding:12px 6px; border-radius:12px; font-size:.78rem; font-weight:600;
  color:var(--ink, #2C3B36); line-height:1.2;
}
.grid-menu a:hover{ background:rgba(97,141,124,.12); text-decoration:none; }
/* outline tiles: accent stroke, transparent fill */
.grid-menu a svg{ width:24px; height:24px; color:var(--accent, #618D7C); flex:0 0 auto; }

/* ---- Live-map nav: transparent bar over the map (Google-Maps style) ---- */
:root{ --msearch-w:min(400px, 46vw); }
.map-header{
  position:fixed; top:var(--signup-h, 0px); left:0; right:0; z-index:1100;
  height:64px;   /* literal: --header-h is header + sign-up bar when the bar shows */
  display:flex; align-items:center; gap:12px; padding:0 14px;
  background:transparent; pointer-events:none; /* the map stays draggable between the islands */
}
.map-header .msearch, .map-header .nav-right{ pointer-events:auto; }
/* round-15f/16b: the icon cluster must never shrink below its content (the grid button
   was sliding off-screen on narrow phones when signed out) — the search box may SHRINK
   instead, but never GROW (flex-grow was stretching it over the service pills on desktop) */
.map-header .nav-right{ flex:0 0 auto; }
.map-header .msearch{ flex:0 1 auto; min-width:0; }

.msearch{
  position:relative; display:flex; align-items:center; gap:6px;
  width:var(--msearch-w); padding:5px 8px 5px 10px;
  background:rgba(243,244,241,.96); border:1px solid var(--track); border-radius:12px;
  box-shadow:0 4px 18px rgba(44,59,54,.22);
}
.msearch-logo{ display:inline-flex; flex:0 0 auto; }
.msearch-logo svg{ width:26px; height:26px; }
.msearch input{
  flex:1; min-width:0; border:0; background:none; padding:7px 4px;
  font-family:var(--font); font-size:.95rem; color:var(--ink);
}
.msearch input:focus{ outline:none; }
.msearch-btn{
  flex:0 0 auto; background:none; border:0; cursor:pointer; padding:6px;
  border-radius:8px; color:var(--accent); display:inline-flex;
}
.msearch-btn svg{ width:20px; height:20px; }
.msearch-btn:hover{ background:rgba(97,141,124,.12); }
.msearch .omni-results{ top:calc(100% + 8px); }

/* right-cluster buttons get a paper backing so they read over the map */
.map-header .grid-btn, .map-header .bell{
  background:rgba(243,244,241,.96); border:1px solid var(--track);
  border-radius:50%; box-shadow:0 4px 14px rgba(44,59,54,.18);
}
.map-header .bell{ width:42px; height:42px; display:inline-flex; align-items:center; justify-content:center; padding:0; }
.map-header .grid-btn:hover, .map-header .nav-grid.open .grid-btn{ background:#E9EFE9; }

@media (max-width: 720px){
  /* chips drop below the bar (see .svc-wrap); min-width:0 lets the box shrink past
     the input's intrinsic size so the right cluster never overflows */
  .map-header .msearch{ flex:1 1 auto; width:auto; min-width:0; }
  .map-header{ gap:8px; padding:0 10px; }
  /* Round-94: below ~560px the inline input is a sliver — icon-only reads better. The 🔍
     still opens the full-screen sheet (tap → input focus → round-84 sheet), where the real
     input lives. :not(.omni-sheet) keeps the sheet's own input visible. */
}
@media (max-width: 560px){
  .map-header .msearch:not(.omni-sheet){ flex:0 0 auto; gap:2px; padding:5px 6px; }
  .map-header .msearch:not(.omni-sheet) input,
  .map-header .msearch:not(.omni-sheet) .omni-clear{ display:none; }
}

/* ---- Nav quick actions: live map icon + report button (both navs) ---- */
.nav-icon{
  width:42px; height:42px; border-radius:50%; color:var(--deep, #4F8577);
  display:inline-flex; align-items:center; justify-content:center; flex:0 0 auto;
  position:relative;   /* round-96e: the stories icon carries a .bell-badge */
  /* Round-96d: the search trigger is a <button> (the other nav icons are <a>), and
     Android's WebView paints the UA button border/background on it — iOS doesn't,
     which is why only Android showed a dark ring. Neutralize explicitly. */
  border:0; background:none; padding:0; cursor:pointer;
}
.nav-icon svg{ width:22px; height:22px; }
.nav-icon:hover{ background:rgba(97,141,124,.12); }
.nav-report{
  display:inline-flex; align-items:center; gap:8px; flex:0 0 auto;
  background:var(--st-impact, #C0392B); color:#fff !important; border-radius:999px;
  padding:9px 16px; font-weight:700; font-size:.9rem;
}
.nav-report svg{ width:18px; height:18px; }
.nav-report:hover{ background:#d64536; text-decoration:none; }
@media (max-width: 900px){
  .nav-report .lbl{ display:none; }
  .nav-report{ width:42px; height:42px; padding:0; justify-content:center; border-radius:50%; }
}

/* ---- Time row (bottom) + mobile report shortcut ---- */
.time-row{
  position:fixed; bottom:18px; left:50%; transform:translateX(-50%); z-index:940;
  display:flex; align-items:center; gap:10px;
}
.time-row .base-switch{ position:static; transform:none; }
.map-report-sm{
  display:none; width:40px; height:40px; border-radius:50%; flex:0 0 auto;
  background:var(--st-impact, #C0392B); color:#fff;
  align-items:center; justify-content:center; box-shadow:0 6px 20px rgba(44,59,54,.3);
}
.map-report-sm svg{ width:19px; height:19px; }
.map-report-sm:hover{ background:#d64536; }
@media (max-width: 720px){
  .map-header .nav-report{ display:none; } /* report moves next to the timeline */
  /* Round-82: stories does the same, or the page carries two report buttons at once — the
     header pill and the one now sitting beside its time bands. */
  body:has(.stories-page) .site-header .nav-report{ display:none; }
  .map-actions{ display:none; }            /* join/deploy + zoom are desktop-only */
  .map-report-sm{ display:inline-flex; }
  .time-row{ left:10px; right:10px; bottom:14px; transform:none; }
  /* Round-88: was `flex:1 1 auto`, which STRETCHED the pill to the full width of the row.
     The pill carries the background, border and radius, so on any screen where the bands do
     not fill the row it painted a long empty strip to the right of the settings button —
     clearly visible on a tablet (~610 CSS px, bands end around 367px). Phones never showed it
     because at 360px the bands overflow and the pill is full anyway.

     `0 1 auto` lets it shrink and scroll exactly as before when the bands are too wide, but
     hug its content when they are not; the auto margins then centre it under the map the same
     way the desktop rule does with translateX(-50%). */
  .time-row .base-switch{ flex:0 1 auto; max-width:100%; margin-inline:auto;
                          overflow-x:auto; scrollbar-width:none; }
}

/* Round-88: centring the pill on the VIEWPORT (rather than in the space the report button
   leaves over) was tried and REVERTED — do not retry it this way. `position:absolute` on
   .base-switch takes it out of the flex row, and it then loses its content height: measured
   2px tall with 16px buttons inside, i.e. the bands vanish into a hairline. Anchoring with
   top/translateY fixed the vertical drift but not the collapse.

   The in-flow rule above is what ships: the pill hugs its content (gap gone, which was the
   actual bug) and sits on its original baseline. It rides ~48px right of the true centre at
   610px wide, because the report button is a flex sibling consuming space on one side only.
   Fixing that properly means giving the row a symmetric left-hand counterweight, or moving
   the report button out of the row — a layout change, not a tweak. */

/* Round-88: camera-capture buttons, sharing the .up-rec row with the audio recorder.
   The inputs are visually hidden inside their <label>s, so the label IS the button; keeping
   them in the DOM rather than display:none matters, because a hidden-but-present input is
   still clickable through its label and still opens the WebView file chooser.

   Two things here are load-bearing, both found by measuring the live page:

   1. display:contents on the wrapper. As a real flex ITEM it took a shrunken width and wrapped
      its two buttons into a column beside the audio button. contents dissolves the box so both
      labels become direct children of .up-rec and all three share one row, gap and wrap rule.
      The .up-rec in the selector is needed to out-specify `.in-app .app-only{display:block}`
      in style.css, which ties on specificity and loads later.

   2. The label reset. These sit inside .field, whose `label{display:block}` and larger form
      font were winning: the capture labels measured 42px tall at 15.2px text against the audio
      BUTTON's 37px at 14.08px — same padding, different type. Restating display and font here
      is what actually makes a <label> and a <button> the same control. */
.in-app .up-rec .up-capture{ display:contents; }
.up-rec .up-capture label{
  display:inline-flex; align-items:center; justify-content:center; gap:6px;
  width:auto; margin:0; cursor:pointer;
  font-size:.88rem; line-height:normal; font-weight:700;   /* == .btn-sm, measured */
}
.up-capture input.up-cap{ position:absolute; width:1px; height:1px; opacity:0;
                          overflow:hidden; clip:rect(0 0 0 0); white-space:nowrap; }

/* ── Round-88b: the phone composer ────────────────────────────────────────────────────────
   Describe-and-attach as ONE box, the way every messaging app the reporter already uses does it.
   Shown in the app and on any narrow viewport; the desktop keeps its drag-and-drop zone, because
   dragging is a desktop idiom and there is nothing to drag on a phone. */
/* Round-88h: the COMPOSER carries the frame now, not the textarea.
   Thumbnails sit inside that frame directly under the text, so the box holds the whole message —
   words and evidence together — instead of a bordered textarea with attachments floating below
   it. The textarea therefore goes borderless and transparent, and the composer reserves a strip
   at the bottom for the three buttons (which are absolutely positioned to it). */
.composer{
  position:relative;
  border:1px solid var(--track, #ADC3B9); border-radius:14px;
  background:var(--panel, #FBFBF8);
  padding-bottom:46px;                            /* the button strip */
}
.composer:focus-within{ border-color:var(--accent, #618D7C); }
/* Three classes to out-specify .field textarea in style.css, which loads after this file. */
.field .composer textarea{
  border:0; background:transparent; box-shadow:none; outline:none;
  width:100%; display:block; margin:0; border-radius:14px 14px 0 0;
  padding:12px 12px 4px; resize:vertical;
}
.composer-acts{
  /* Round-88f: shown on EVERY viewport. The drop zone it used to replace on mobile is gone —
     these three buttons are now the only way to attach a file, on phone, in-app and desktop. */
  display:flex; position:absolute; right:9px; bottom:9px;
  gap:6px; align-items:center;
  /* Explicit z-index: without it the buttons sat in the same stacking level as the textarea and
     taps fell through to it — the giveaway was the page scrolling (the textarea taking focus)
     instead of the menu opening.

     1250, not 3: this rule creates a STACKING CONTEXT, and the sheet inside it cannot escape its
     parent's level however high its own z-index is. At 3 the sheet opened but the location map
     (Leaflet panes 400–600) and the sticky send bar (1200) painted straight over it, hiding the
     third item. It has to sit above both for the sheet to be usable. */
  z-index:6;   /* just above .submit-bar (5); both far below the nav at 1100 */
}

/* Mobile gets the composer INSTEAD of the drop zone and the button row, not as well as.
   The evidence LABEL goes too — hiding only its contents left a heading with nothing beneath it.
   #upTiles stays: attached files must still be visible and removable. */

/* The drop zone is hidden VISUALLY, not with display:none — #upInput lives inside it, and the
   composer's buttons click that input to open the picker. A display:none file input cannot open
   a dialog (the same trap .up-file's own comment warns about), so hiding the zone that way made
   every composer upload button silently dead. sr-only keeps the input alive and clickable while
   taking no space. */

/* Two classes on the selector, not one. `.field label{display:block}` in style.css is (0,1,1)
   and loads AFTER this file, so a bare `.cbtn` lost the display battle on the camera button —
   which is a <label> — and it rendered as a squashed block instead of a centred circle. */
.composer-acts .cbtn{
  width:34px; height:34px; border-radius:50%; flex:0 0 34px;
  display:inline-flex; align-items:center; justify-content:center;
  border:1px solid var(--track, #ADC3B9); background:var(--panel, #FBFBF8);
  color:var(--accent, #618D7C); cursor:pointer; padding:0; margin:0;
  list-style:none;                         /* <summary> renders a marker without this */
  font-size:0;                             /* kills the inline-text baseline gap around the svg */
}
.composer-acts .cbtn::-webkit-details-marker{ display:none; }
.composer-acts .cbtn::marker{ content:""; }
.composer-acts .cbtn svg{ width:18px; height:18px; display:block; }
/* Round-89: no camera on this device. Left visible rather than hidden so the reporter can see
   the capability exists and why it is unavailable (the title carries the explanation) — a button
   that silently disappears reads as a bug, and they can still attach an existing file. */
.composer-acts .cbtn-off{ opacity:.42; cursor:not-allowed; }
/* position:relative so the .up-file input inside pins to THIS label, exactly as .up-zone does.
   Without it the input anchored to .composer-acts instead — outside its own label's box — and
   the file dialog never opened. The rule that used to override .up-file here is gone: the drop
   zone's sr-only pattern is the one proven to open a picker in this WebView, so the composer
   uses it unchanged rather than a second, subtly different copy. */
.composer-acts .cbtn{ position:relative; }
.composer-acts details[open] > .cbtn{ background:var(--accent, #618D7C); color:#FBFBF8; border-color:var(--accent, #618D7C); }
/* Held-to-talk reads as live: the button itself is the recording indicator. */
.composer-acts .cbtn.mic.rec{ background:#C0392B; border-color:#C0392B; color:#FBFBF8; transform:scale(1.08); }
@media (prefers-reduced-motion:reduce){ .composer-acts .cbtn.mic.rec{ transform:none; } }

.composer-acts .plus-wrap{ position:relative; display:inline-flex; }
/* Anchored to the button again (round-88c), now that it is two items rather than three: the
   full-width sheet was heavier than the choice deserved. The earlier off-screen problem is gone
   because .composer-acts sits at z-index 1250, so nothing paints over it, and a two-item menu is
   short enough to clear the top of the viewport from any realistic composer position. */
.composer-acts .plus-menu{
  position:absolute; bottom:42px; right:0; z-index:1300;   /* above the sticky send bar */
  min-width:210px; padding:8px; border-radius:16px;
  /* Clean sheet: plain white, no visible frame. Depth comes from a soft shadow and a hairline
     the same colour as the page, so the menu reads as a card floating over the form rather than
     a boxed-in panel. The bordered version fought every surface it opened over. */
  background:#FFFFFF; border:1px solid rgba(44,59,54,.06);
  box-shadow:0 12px 30px -10px rgba(44,59,54,.28), 0 2px 6px rgba(44,59,54,.06);
  display:flex; flex-direction:column; gap:2px;
}
/* Buttons now, not labels — but the .field label rules still need out-specifying, and these must
   shed the default button chrome (border, grey background, centred text) that would otherwise
   turn a clean menu row into a boxed control. */
.composer-acts .plus-item{
  display:flex; align-items:center; gap:10px; cursor:pointer;
  padding:10px 11px; border-radius:10px; font-size:.9rem; font-weight:600;
  color:var(--ink, #2C3B36); margin:0;
  width:100%; text-align:left; background:transparent; border:0; font-family:inherit;
}
.composer-acts .plus-item:hover{ background:rgba(97,141,124,.12); }
.composer-acts .plus-item svg{ width:18px; height:18px; color:var(--accent, #618D7C); flex:0 0 18px; }
/* Same fix as .cbtn above: anchor the .up-file input inside its own label instead of overriding
   the sr-only pattern with an unanchored copy. */
.composer-acts .plus-item{ position:relative; }

.composer-foot{ margin-top:6px; min-height:1.2em; }
.composer-limits{ font-size:.8rem; color:var(--ink-faint, #8A968F); }
.rec-live{ display:inline-flex; align-items:center; gap:7px; font-size:.85rem; font-weight:700; color:#C0392B; }

/* Round-88: the routing card on the report confirmation screen. Reads as a follow-up, not as a
   second form — no red, no required marks, and it is skippable simply by walking away, because
   the report is already filed by the time it appears. */
.routing-card{
  margin-top:16px; padding:14px 16px; border-radius:12px;
  border:1px solid var(--track, #ADC3B9); background:var(--panel, #FBFBF8);
}
.routing-row{ display:flex; gap:8px; align-items:center; flex-wrap:wrap; }
.routing-row select{ flex:1 1 180px; min-width:0; }

/* Round-88: "Add more details" — the folded optional fields on the report form.
   A native <details> rather than a Blazor toggle: it opens before interactivity has loaded,
   keeps the accessible disclosure semantics for free, and survives an enhanced navigation. */
.more-details{ border:1px solid var(--track, #ADC3B9); border-radius:12px;
               background:var(--panel, #FBFBF8); margin-bottom:16px; }
.more-details > summary{
  cursor:pointer; list-style:none; padding:13px 15px;
  font-weight:700; font-size:.94rem; color:var(--accent, #618D7C);
  display:flex; align-items:center; justify-content:space-between; gap:10px;
}
.more-details > summary::-webkit-details-marker{ display:none; }   /* Safari's default triangle */
.more-details > summary::after{ content:"+"; font-size:1.2rem; line-height:1; font-weight:400; }
.more-details[open] > summary::after{ content:"–"; }
.more-details > summary:focus-visible{ outline:2px solid var(--accent, #618D7C); outline-offset:2px; border-radius:12px; }
.more-details-body{ padding:0 15px 4px; }

/* Round-88: the always-visible send bar. Sticks to the bottom of the viewport so the exit is
   never a scroll away, and carries the line that names what is still missing. The fade lets the
   form scroll under it instead of ending in a hard band. */
/* Solid, not a gradient. The first version faded to transparent at the top, and content passing
   underneath ghosted through it — including the red "call 112 first" box, which is the single
   worst thing on this page to render as a half-legible smear. A hard edge reads as a bar; a
   translucent one reads as a bug. */
/* z-index 1200, not 5: Leaflet stacks its own panes at 400–600 and its controls at 800–1000, so
   the location map painted straight over the sticky bar — the submit button and the "still
   needed" line vanished behind it. Anything sticky on a page that embeds a map has to clear
   Leaflet's ceiling, not the page's. */
.submit-bar{
  position:sticky; bottom:0; z-index:5; margin-top:10px; padding:12px 0 10px;
  background:var(--panel, #FBFBF8);
  border-top:1px solid var(--track, #ADC3B9);
  box-shadow:0 -8px 18px -14px rgba(44,59,54,.5);
}
.submit-need, .submit-ready{ margin:0 0 9px; font-size:.86rem; font-weight:700; text-align:center; }
.submit-need{ color:#96661C; }          /* amber ink — a blocker, not an error */
.submit-ready{ color:#1B8F68; }

/* Round-88: the location preview under the report form's geo card. Fixed height because Leaflet
   measures its container on init — an auto-height box is 0px tall at that moment and the map
   renders as a grey sliver. Non-interactive by construction (see aquaGeoPreview), so it takes
   no pointer events and never steals a scroll on a touch screen. */
/* Round-89b: position+z-index CONTAINS Leaflet. Its panes (400-600) and controls (800-1000)
   otherwise compete in the page's stacking context, which is what forced the sticky bar up to
   1200 — above the nav at 1100, so the submit button rode over the header when scrolling. With
   the map boxed into its own context, everything around it can stay in normal page range. */
.geo-preview{ position:relative; z-index:0;
              height:200px; margin-top:10px; border-radius:12px; overflow:hidden;
              border:1px solid var(--track, #ADC3B9); background:#EDE7E0; }
.geo-preview .leaflet-container{ height:100%; width:100%; background:#EDE7E0; }
/* No touch-action override here on purpose. Leaflet sets its own (`pan-x pan-y` with dragging
   disabled), which is already what we want: one finger scrolls the form, and the two-finger
   pinch is handled by Leaflet's own touch handlers rather than the browser. An earlier attempt
   to set `pan-y pinch-zoom` here simply lost to leaflet.css and did nothing. */
/* The zoom buttons need a visible EDGE. An earlier `border:none` here left white controls on a
   near-white basemap: they were rendered and clickable but effectively invisible on the device,
   which is worse than not having them — the affordance is the whole point. */
.geo-preview .leaflet-control-zoom{ border:1px solid var(--track, #ADC3B9); border-radius:8px;
                                    overflow:hidden; box-shadow:0 1px 5px rgba(44,59,54,.28); }
.geo-preview .leaflet-control-zoom a{ width:30px; height:30px; line-height:30px;
                                      background:#FBFBF8; color:var(--ink, #2C3B36);
                                      font-size:18px; font-weight:700; }
.geo-preview .leaflet-control-zoom a + a{ border-top:1px solid var(--track, #ADC3B9); }

/* Round-88: sizes for the outline icons that replaced the emoji on the report and official-alert
   forms. OutlineIcons.Svg emits a bare <svg viewBox="0 0 24 24"> with no width/height, so without
   these it lays out at the SVG default of 300×150 — the icons must be sized wherever they appear.
   Colour needs no rule: the paths use stroke="currentColor", which is the whole point of the
   change — an emoji renders in the font's own palette (and differently per OEM), so it could
   never follow the theme. */
.geo-card .geo-ico svg{ width:20px; height:20px; display:block; }
.up-zone .up-ico svg{ width:26px; height:26px; display:block; margin:0 auto; }
.up-rec .btn svg,
.up-rec .up-capture label svg{ width:16px; height:16px; flex:0 0 auto; }
.up-tiles .up-typ svg{ width:18px; height:18px; }
/* inline with text (mode chips, and the location line on alert cards) */
.chip svg{ width:15px; height:15px; vertical-align:-2px; }
.oalert-loc svg{ width:14px; height:14px; vertical-align:-2px; }

/* ---- Service-chip overflow scroll arrows ---- */
.pill-arrow{
  /* Round-94: same height as the service pills (8px padding + text + 2px borders ≈ 38px), so
     the arrow sits flush with the row instead of floating above its centre line. */
  flex:0 0 auto; width:38px; height:38px; border-radius:50%; cursor:pointer;
  border:1px solid var(--track); background:rgba(243,244,241,.96); color:var(--deep, #4F8577);
  display:none; align-items:center; justify-content:center;
  box-shadow:0 3px 10px rgba(44,59,54,.2);
}
.pill-arrow svg{ width:16px; height:16px; }
.pill-arrow.right svg{ transform:rotate(180deg); }
.pill-arrow.show{ display:inline-flex; }
.pill-arrow.left{ margin-right:6px; }
.pill-arrow.right{ margin-left:6px; }

/* ---- Collapsible map sidebar cards (uniform width, minimize to icon) ---- */
.map-card.collapsible{ width:250px; }
.card-head{ display:flex; align-items:center; gap:8px; cursor:pointer; user-select:none; }
.card-head h2, .card-head h3{ margin:0 !important; flex:1; }
.card-ico{ display:inline-flex; flex:0 0 auto; }
.card-ico svg{ width:18px; height:18px; color:var(--tan-light, #E3C9A5); }
.card-caret{ display:inline-flex; flex:0 0 auto; margin-left:auto; }
.card-caret svg{ width:15px; height:15px; color:#DCE5DD; transition:transform .2s; }
.card-body{ margin-top:10px; }
/* keep the bottom-anchored Layers card from riding over the Sensor card */
.legend .card-body{ max-height:calc(100vh - 400px); max-height:calc(100dvh - 400px); overflow-y:auto; }
/* bottom-right stack: SafeNet watch card + weather card flow without overlapping */
/* round-15: right-hand column — assistant ✨ pinned top-right, the card chips
   vertically CENTERED on the right edge (recenters as cards expand/collapse) */
.right-stack{
  position:fixed; right:14px; top:50%; transform:translateY(-50%); bottom:auto; z-index:900;
  display:flex; flex-direction:column; gap:10px; align-items:flex-end;
  max-height:calc(100vh - var(--header-h) - 120px); overflow-y:auto;
  scrollbar-width:none; padding-left:6px;
}
.right-stack::-webkit-scrollbar{ display:none; }
/* round-73b: when the map cannot render, its controls do nothing — hide the sensor-network
   and layers cards rather than offer buttons that open empty panels. Stories and Ada stay:
   both work without a map. (#weatherCard / #safeNetCard also carry .net-stats and are
   JS-revealed, so they stay hidden here too, which is what we want.) */
html.legacy-browser .right-stack .net-stats,
html.legacy-browser .right-stack .legend,
html.legacy-browser .svc-wrap{ display:none !important; }
/* on the live map the assistant button heads the centered chip column.
   round-15d: the CSS default IS the final position (3 minimized chips below), so it
   never jumps on load; the script only fine-tunes (SafeNet chip, expanded cards) and
   the transition keeps that nudge soft. */
html.pg-map .asst-fab{ top:calc(50% - 140px); right:16px; transform:none; z-index:900;
  transition:top .3s ease, box-shadow .15s; }
body:has(#map) .asst-fab{
  top:calc(50% - 140px); right:16px; transform:none;
  transition:top .3s ease, box-shadow .15s;
  /* z-index:900 — the detail panel (#stationPanel, z-index:1000) must pass in front. The FAB
     sits mid-right, exactly where that panel opens, so at its default 2500 it floated over the
     report content and its ✕. 900 is the .right-stack value: the FAB heads that chip column and
     those chips already sit behind the panel, so the whole column now passes under it together.
     Scoped to the map — off-map the FAB keeps 2500, where it has no panel to yield to.
     The drawer stays at 2400 and still opens over the panel, which is what you want when the
     user deliberately summons Ada. */
  z-index:900;
}
html.pg-map .asst-fab:hover{ transform:scale(1.07); }
body:has(#map) .asst-fab:hover{ transform:scale(1.07); }
@media (max-width: 720px){
  .right-stack{ max-height:calc(100vh - var(--header-h) - 170px); }
  body:has(#map) .asst-fab{ width:44px; height:44px; font-size:1.15rem; top:calc(50% - 132px); }
}

/* ---- Round-17: simplified report panel — feed-card layout, folds, aliceblue pills ---- */
.inc-topline{ display:flex; align-items:center; gap:10px; justify-content:space-between; margin-bottom:10px; }
.inc-when{ color:var(--ink-soft, #55635C); font-size:.8rem; white-space:nowrap; }
.sp-fold{
  margin-top:12px; border:1px solid var(--track, #ADC3B9); border-radius:12px;
  background:rgba(255,255,255,.55); overflow:hidden;
}
.sp-fold summary{
  cursor:pointer; user-select:none; list-style:none; display:flex; align-items:center; gap:8px;
  font-weight:800; font-size:.82rem; letter-spacing:.6px; text-transform:uppercase;
  color:var(--deep, #4F8577); padding:10px 14px;
}
.sp-fold summary::-webkit-details-marker{ display:none; }
.sp-fold summary::before{ content:"▸"; font-size:.8rem; transition:transform .2s; }
.sp-fold[open] summary::before{ transform:rotate(90deg); }
.sp-fold .sp-fold-body{ padding:2px 14px 12px; }
/* round-19b: pills in the panel body AND the panel header (service + severity) wear white */
#spBody .pill, #spPill .pill{ background:#fff; }

/* round-17c: owner controls on the reporter's own report */
.own-box{
  margin-top:12px; border:1.5px dashed var(--accent, #618D7C); border-radius:12px;
  padding:10px 14px 12px; background:rgba(97,141,124,.06);
}
.own-row{ display:flex; align-items:center; gap:8px; }
.own-row b{ flex:1; font-size:.85rem; color:var(--deep, #4F8577); text-transform:uppercase; letter-spacing:.5px; }
.own-form{ margin-top:10px; }
.own-form .field{ margin-bottom:8px; }
.own-form input, .own-form select, .own-form textarea{
  width:100%; padding:8px 10px; border:1.5px solid var(--track, #ADC3B9); border-radius:10px;
  font-family:var(--font); font-size:.9rem;
}

/* ---- Round-17: user chip — avatar + name linking to the profile ---- */
.uchip{
  display:inline-flex; align-items:center; gap:8px; text-decoration:none; color:inherit;
  max-width:100%;
}
a.uchip:hover .uchip-name{ text-decoration:underline; }
.uchip img, .uchip-fb{
  width:26px; height:26px; border-radius:50%; object-fit:cover; flex:0 0 26px;
}
.uchip-fb{
  display:inline-flex; align-items:center; justify-content:center;
  background:var(--accent, #618D7C); color:#fff; font-weight:800; font-size:.75rem;
}
.uchip-fb.uchip-anon{ background:var(--sage2, #8FA99B); }
.uchip-name{ font-weight:700; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* ---- Round-17: settings as icon cards (grid on desktop, tap-to-expand bills on mobile) ---- */
.settings-grid{
  display:grid; grid-template-columns:repeat(2, 1fr); gap:16px; align-items:start;
  max-width:980px; margin:0 auto;
}
.scard{
  background:var(--paper, #FBFBF8); border:1.5px solid var(--track, #ADC3B9);
  border-radius:16px; overflow:hidden;
}
.scard-head{
  display:flex; align-items:center; gap:12px; padding:14px 18px;
  border-bottom:1px solid rgba(173,195,185,.35);
}
.scard-head h3{ margin:0; font-size:1rem; flex:1; }
.scard-ico{ display:inline-flex; flex:0 0 auto; }
.scard-ico svg{ width:20px; height:20px; color:var(--accent, #618D7C); }
.scard-caret{ display:none; }
.scard-body{ padding:16px 18px 18px; }
.scard-toggle{
  display:flex; align-items:flex-start; gap:10px; font-weight:600; font-size:.92rem;
  margin-bottom:10px; cursor:pointer;
}
.scard-toggle input{ width:auto !important; margin-top:3px; flex:0 0 auto; }
@media (max-width: 820px){
  .settings-grid{ grid-template-columns:1fr; gap:10px; }
  /* mobile: each card collapses to a tappable bill; only the open one shows its body */
  .scard-head{ cursor:pointer; user-select:none; border-bottom:0; }
  .scard-caret{ display:inline-flex; margin-left:auto; }
  .scard-caret svg{ width:15px; height:15px; color:var(--sage2, #8FA99B); transform:rotate(-90deg); transition:transform .2s; }
  .scard.open .scard-caret svg{ transform:rotate(90deg); }
  .scard:not(.open) .scard-body{ display:none; }
  .scard.open .scard-head{ border-bottom:1px solid rgba(173,195,185,.35); }
}

/* ---- Round-16: stories chip on the map + the stories feed page ---- */
.right-stack .stories-chip{
  position:relative; width:44px; height:44px; border-radius:50%; flex:0 0 auto;
  display:flex; align-items:center; justify-content:center; text-decoration:none;
}
.right-stack .stories-chip:hover{ text-decoration:none; filter:brightness(1.1); }
.right-stack .stories-chip .card-ico svg{ width:20px; height:20px; }
.story-badge{
  position:absolute; top:-4px; right:-4px; background:#C0392B; color:#fff;
  font-size:.62rem; font-weight:800; border-radius:999px; padding:1px 5px; line-height:1.4;
  border:2px solid #fff;
}

.stories-page{ background:#fff; min-height:100vh; }
.stories-filters{
  position:sticky; top:var(--header-h); z-index:500; background:#fff;
  border-bottom:1px solid #ECEAE4; padding:10px 14px 12px;
  display:flex; flex-direction:column; gap:10px; align-items:center;
}
.stories-svc-wrap{ display:flex; align-items:center; max-width:100%; min-width:0; }
.stories-svc{ display:flex; gap:8px; overflow-x:auto; max-width:100%; min-width:0; padding-bottom:2px; scrollbar-width:none; scroll-behavior:smooth; }
/* Round-94: the feed's pills are the MAP's pills (same classes, same on/off skin); soften the
   map's floating shadow — these sit on a white bar, not over tiles. */
.stories-svc .pill-svc{ box-shadow:none; backdrop-filter:none; background:#fff; }
.stories-svc .pill-svc.on{ background:var(--accent, #618D7C); }
.stories-svc::-webkit-scrollbar{ display:none; }
/* round-16b: scroll arrows appear when the chips overflow (like the map's service pills) */
.story-arrow{
  flex:0 0 auto; width:30px; height:30px; border-radius:50%; cursor:pointer;
  border:1.5px solid var(--track, #ADC3B9); background:#fff; color:var(--deep, #4F8577);
  display:none; align-items:center; justify-content:center;
}
.story-arrow svg{ width:16px; height:16px; }
.story-arrow.right svg{ transform:rotate(180deg); }
.story-arrow.show{ display:inline-flex; }
.story-arrow.left{ margin-right:6px; }
.story-arrow.right{ margin-left:6px; }
.story-arrow:hover{ border-color:var(--accent, #618D7C); background:var(--mintbg, #EAF1EC); }
.story-chip{
  display:inline-flex; align-items:center; gap:7px; white-space:nowrap; cursor:pointer;
  border:1.5px solid var(--track, #ADC3B9); border-radius:999px; background:#fff;
  font-family:var(--font); font-size:.85rem; font-weight:700; color:var(--ink, #2C3B36);
  padding:7px 14px; transition:all .15s;
}
.story-chip .dot{ width:10px; height:10px; border-radius:50%; }
.story-chip:hover{ border-color:var(--accent, #618D7C); }
.story-chip.on{ background:var(--accent, #618D7C); border-color:var(--accent, #618D7C); color:#fff; }
/* Round-94 (design 10's segment, on design 02's feed): a frosted pill with the active scope
   filled green — the same control the app shows under "Near you". */
.stories-scope{
  display:inline-flex; gap:2px; padding:3px; border-radius:999px;
  background:rgba(255,255,255,.55); border:1px solid rgba(255,255,255,.8);
  backdrop-filter:blur(14px); -webkit-backdrop-filter:blur(14px);
  box-shadow:0 6px 18px rgba(44,59,54,.08);
}
.stories-scope button{
  border:0; background:transparent; cursor:pointer; font-family:var(--font); font-size:.82rem;
  font-weight:700; color:var(--ink-soft, #55635C); padding:6px 16px; border-radius:999px;
  transition:background .15s, color .15s;
}
.stories-scope button.on{ background:var(--accent, #618D7C); color:#fff; }
.stories-filters{ background:rgba(255,255,255,.85); backdrop-filter:blur(10px); -webkit-backdrop-filter:blur(10px); }
/* Round-94: on desktop the scope segment and the service pills share ONE row — scope left,
   services beside it — instead of stacking. Below 900 px they stack again (the pills scroll). */
@media (min-width:901px){
  .stories-filters{ flex-direction:row; justify-content:center; align-items:center; gap:14px; }
  .stories-scope{ flex:0 0 auto; }
  .stories-svc-wrap{ flex:0 1 auto; min-width:0; }
  .scope-toggle{ display:none; }
}
/* ≤900: the segment folds into one button on the pill row — two rows of chrome, not three */
.scope-toggle{
  display:none; align-items:center; gap:6px; flex:0 0 auto; margin-right:8px;
  border:1.5px solid var(--track, #ADC3B9); border-radius:999px; background:#fff; cursor:pointer;
  font-family:var(--font); font-size:.85rem; font-weight:700; color:var(--ink, #2C3B36); padding:7px 12px;
}
.scope-toggle svg{ width:15px; height:15px; }
@media (max-width:900px){
  .stories-filters{ position:relative; }
  .scope-toggle{ display:inline-flex; }   /* small screens only — desktop shows the full segment */
  .stories-scope{ display:none; }
  .stories-filters.scope-open .stories-scope{
    display:inline-flex; position:absolute; top:calc(100% - 4px); left:12px; z-index:650;
    background:#fff; box-shadow:0 14px 36px rgba(44,59,54,.22); border:1px solid var(--track, #ADC3B9);
  }
}

.stories-wrap{ max-width:1100px; margin:0 auto; padding:18px 14px 96px; } /* clears the fixed time bands */
/* Round-82: the strip no longer positions itself — .time-row does, exactly as on the map, and
   `.time-row .base-switch{ position:static; transform:none }` makes this a flow child of it.
   The old fixed/centred declarations here were left inert by that change and are gone rather
   than kept as dead rules that contradict where the element actually sits. */
/* mobile: stay content-sized and centred (no empty trailing space), but cap to the viewport and
   scroll horizontally if the pills don't fit — matches the live map's scroll behaviour */
@media (max-width: 720px){
  .stories-time{ max-width:calc(100vw - 20px); overflow-x:auto; scrollbar-width:none; }
  .stories-time::-webkit-scrollbar{ display:none; }
}
/* Round-94: design 02 — masonry columns, the photo leads, text-only reports are solid tiles in
   their service colour. Columns collapse to one on phones; the community page shares the list
   and inherits the layout. */
.story-list{ columns:3 300px; column-gap:14px; }
.story-card{
  break-inside:avoid; margin:0 0 14px; overflow:hidden;
  background:#fff; border:1px solid #E7E4DC; border-radius:18px; padding:0;
  box-shadow:0 1px 3px rgba(44,59,54,.06);
}
.story-card > .media-gallery{ margin:0 !important; border-radius:18px 18px 0 0; overflow:hidden; gap:2px; }
.story-card > .media-gallery *{ border-radius:0 !important; }
.story-card .story-body{ padding:12px 14px 10px; }
.story-loc{ font-size:.82rem; color:var(--ink-soft, #55635C); margin:2px 0 6px; }
.story-official{
  display:inline-flex; align-items:center; font-size:.68rem; font-weight:800; letter-spacing:.5px;
  text-transform:uppercase; border-radius:999px; padding:2px 9px;
  background:var(--mintbg, #EAF1EC); color:var(--deep, #2C3B36);
}
@media (max-width:720px){ .story-list{ columns:1; } }
.story-head{ display:flex; align-items:center; gap:8px; flex-wrap:wrap; margin-bottom:8px; }
.story-svc{
  display:inline-flex; align-items:center; gap:6px; font-size:.78rem; font-weight:800;
  color:var(--svc, #618D7C); text-transform:uppercase; letter-spacing:.5px;
}
.story-svc .dot{ width:10px; height:10px; border-radius:50%; background:var(--svc, #618D7C); }
.story-sev{
  font-size:.68rem; font-weight:800; text-transform:uppercase; letter-spacing:.5px;
  border-radius:999px; padding:2px 9px;
}
   versions read as tags; these read as the WARNING/EMERGENCY they are. Same palette as the
   mockup: emergency red, warning amber, advisory the brand green. */
.sev-advisory{ background:#618D7C; color:#fff; }
.sev-warning{ background:#D9922B; color:#fff; }
.sev-emergency{ background:#B3261E; color:#fff; }
.sev-resolved{ background:#1FA97A; color:#fff; }
/* Round-94c: a community report nobody has confirmed yet. Hollow and dashed, the same
   language the map’s unverified pins speak — a solid chip would look like a verdict. */
.sev-unverified{ background:transparent; color:var(--ink-soft, #55635C); border:1.5px dashed currentColor; padding:1px 8px; }
.story-meta{ font-size:.78rem; color:var(--ink-soft, #55635C); margin-left:auto; }
.story-title{ font-size:1.06rem; color:var(--ink, #2C3B36); margin:0 0 6px; }
.story-desc{ font-size:.95rem; line-height:1.55; color:var(--ink-soft, #55635C); white-space:pre-line; }
.story-desc.clamp{ display:-webkit-box; -webkit-line-clamp:4; -webkit-box-orient:vertical; overflow:hidden; }

/* Round-25: YouTube-style "Show more/less" on long comment bodies (map panel, lightbox, stories) */
.js-cmt-body.cmt-clamp{ display:-webkit-box; -webkit-line-clamp:4; -webkit-box-orient:vertical; overflow:hidden; }
.cmt-showmore{ border:0; background:none; padding:2px 0; margin-top:2px; color:var(--deep, #4F8577);
  font-family:var(--font); font-weight:600; font-size:.82rem; cursor:pointer; }
.cmt-showmore:hover{ text-decoration:underline; }
.story-more{
  border:0; background:none; color:var(--deep, #4F8577); font-family:var(--font);
  font-weight:700; font-size:.85rem; cursor:pointer; padding:4px 0;
}
.story-card .story-body .media-gallery{ margin-top:12px; }
.story-actions{
  display:flex; align-items:center; gap:8px; margin-top:14px; padding-top:12px;
  border-top:1px solid #EFEDE7;
}
.story-act{
  border:1.5px solid var(--track, #ADC3B9); border-radius:999px; background:#fff; cursor:pointer;
  font-family:var(--font); font-size:.84rem; font-weight:700; color:var(--ink, #2C3B36);
  padding:6px 13px; transition:all .15s;
  /* round-71f: icon, label and count sit on ONE line — counts were wrapping underneath
     the icon on narrow phones */
  display:inline-flex; align-items:center; gap:5px; white-space:nowrap; flex:0 0 auto;
}
.story-act:hover{ border-color:var(--accent, #618D7C); background:var(--mintbg, #EAF1EC); }
.story-act.on{ background:var(--accent, #618D7C); border-color:var(--accent, #618D7C); color:#fff; }
/* round-71f: mobile = icons only (labels return on desktop); tighter padding keeps the
   row inside 375 px screens */
@media (max-width: 720px){
  .story-actions .act-lbl{ display:none; }
  .story-act{ padding:5px 10px; }
}
/* round-55: dislike uses the SAME theme colour as heart/love (round-53's separate tan removed) —
   the 💔 glyph already distinguishes it, so the selected state stays visually consistent. */
.story-map{ margin-left:auto; display:inline-flex; align-items:center; gap:5px; font-size:.84rem; font-weight:700; color:var(--deep, #4F8577); }
.story-map svg{ width:16px; height:16px; }
/* Round-94 (design 02): the action row is text — icon + count in the muted ink, no pills, the
   way the mockup reads; the selected state is the accent colour, and the map link keeps the
   far right. Works on white cards and on the solid service tiles alike. */
.story-list .story-actions, .inc-react.story-actions{ flex-wrap:wrap; gap:14px; margin-top:10px; padding-top:10px; border-top:1px solid #EFEDE7; }
.inc-react.story-actions{ border-top:0; padding-top:0; }
.story-list .story-actions .act-lbl{ display:none; }
.story-list .story-actions .act-lbl.always{ display:inline; }   /* the mockup writes "Share" out */
.story-list .story-act .ico-t svg{ width:15px; height:15px; display:block; }
.story-list .story-act{ padding:0; border:0; background:transparent; font-size:.85rem; font-weight:700; color:var(--ink-soft, #65756D); gap:5px; }
/* the global .ico-t is the emoji-silhouette trick (color:transparent + text-shadow); these are
   currentColor SVGs now, so undo it or the strokes inherit transparent and vanish */
.story-list .story-act .ico-t{ font-size:1rem; line-height:1; color:inherit; text-shadow:none; }
.story-list .story-act:hover{ background:transparent; color:var(--accent, #618D7C); }
.story-list .story-act.on{ background:transparent; border:0; color:var(--accent, #618D7C); }
.story-list .story-act svg{ width:16px; height:16px; }
.story-list .story-map{ flex:0 0 auto; }
.story-err{ color:#8e2a1f; font-size:.82rem; margin-top:8px; }
.story-comments{ margin-top:12px; border-top:1px solid #EFEDE7; padding-top:12px; }
.story-cmt{ display:flex; gap:10px; margin-bottom:10px; }
.story-cmt .cmt-av{ width:30px; height:30px; border-radius:50%; object-fit:cover; flex:0 0 30px; }
.story-cmt .cmt-av-fallback{
  display:inline-flex; align-items:center; justify-content:center;
  background:var(--accent, #618D7C); color:#fff; font-weight:800; font-size:.8rem;
}
.story-cmt b a{ color:var(--ink, #2C3B36); }
.story-cmt p{ font-size:.9rem; color:var(--ink-soft, #55635C); margin:2px 0; }
.story-cmt small{ color:var(--sage2, #8FA99B); font-size:.74rem; }
.story-cmt-empty{ color:var(--ink-soft, #55635C); font-size:.88rem; }
.story-compose{ display:flex; gap:8px; align-items:flex-end; margin-top:8px; }
.story-compose textarea{
  flex:1; min-height:44px; padding:9px 12px; border:1.5px solid var(--track, #ADC3B9);
  border-radius:12px; font-family:var(--font); font-size:.9rem; resize:vertical;
}
.story-compose textarea:focus{ outline:none; border-color:var(--accent, #618D7C); }
.story-sentinel{ text-align:center; padding:22px 0; }
.story-spinner{ color:var(--ink-soft, #55635C); font-size:.9rem; }
.story-end{ text-align:center; color:var(--sage2, #8FA99B); font-weight:700; padding:18px 0 0; }
/* Round-96i: empty Local feed offers the way out — a link-shaped button onto the All chip */
.story-scope-all{
  border:0; background:none; padding:0; cursor:pointer; font:inherit;
  color:var(--deep, #4F8577); text-decoration:underline;
}
@media (max-width: 720px){
  .stories-filters{ padding:8px 10px 10px; }
  .story-meta{ margin-left:0; width:100%; }
}

/* ---- Evidence gallery + lightbox ---- */
/* round-19: Facebook-style media grid — spans the panel width, arranges by count.
   Default (no n-class, e.g. the agency queue) = responsive thumbnail grid; the FB
   .n* layouts override it on the map panel + stories. */
.media-gallery{
  display:grid; gap:3px; margin-top:12px; width:100%; border-radius:12px; overflow:hidden;
  grid-template-columns:repeat(auto-fill, minmax(84px, 1fr));
}
.media-gallery .gal-audio{ grid-column:1 / -1; } /* audio rows span all columns */
.media-gallery.n1{ grid-template-columns:1fr; }
.media-gallery.n2{ grid-template-columns:1fr 1fr; }
.media-gallery.n3{ grid-template-columns:1fr 1fr; }
.media-gallery.n3 .gal-item:first-child{ grid-column:1 / -1; }
.media-gallery.n4{ grid-template-columns:1fr 1fr; }
.media-gallery.nmany{ grid-template-columns:1fr 1fr 1fr; }
.gal-item{
  position:relative; width:100%; aspect-ratio:1 / 1; border-radius:0; overflow:hidden; cursor:pointer;
  border:0; background:var(--mintbg, #EAF1EC); padding:0;
  display:flex; align-items:center; justify-content:center;
}
/* a lone image shows in full (16:9), not cropped square */
.media-gallery.n1 .gal-item{ aspect-ratio:16 / 10; }
.gal-item img{ width:100%; height:100%; object-fit:cover; display:block; }
.gal-item.video{ font-size:1.5rem; background:var(--panel-grad, #2C3B36); color:#F3F4F1; }
.gal-item:hover{ filter:brightness(.94); }
/* audio players stack below the visual grid */
.media-audio{ display:flex; flex-direction:column; gap:6px; margin-top:8px; }
/* "+N more" overlay on the last visible tile */
.gal-item .gal-more{
  position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  background:rgba(24,32,29,.55); color:#fff; font-size:1.4rem; font-weight:800;
}
/* round-18: video with a generated poster frame — image + centered play badge */
.gal-item.video.poster{ position:relative; background:#000; }
.gal-item .gal-play{
  position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  font-size:1.4rem; color:#fff; text-shadow:0 1px 6px rgba(0,0,0,.6);
  background:rgba(0,0,0,.18);
}

.lightbox{ position:fixed; inset:0; z-index:3000; display:none; background:rgba(24,32,29,.9); }
.lightbox.open{ display:flex; }
.lb-media{ flex:1; position:relative; display:flex; align-items:center; justify-content:center; min-width:0; }
.lb-stage{ max-width:calc(100% - 130px); max-height:92vh; display:flex; align-items:center; justify-content:center; }
.lb-stage img, .lb-stage video{ max-width:100%; max-height:92vh; border-radius:10px; }
.lb-close, .lb-nav{
  position:absolute; z-index:1; border:0; cursor:pointer; color:#F3F4F1;
  background:rgba(243,244,241,.14); width:44px; height:44px; border-radius:50%;
  font-size:1.3rem; display:inline-flex; align-items:center; justify-content:center;
}
.lb-close:hover, .lb-nav:hover{ background:rgba(243,244,241,.28); }
.lb-close{ top:14px; right:14px; }
.lb-nav{ top:50%; transform:translateY(-50%); font-size:2rem; }
.lb-prev{ left:14px; }
.lb-next{ right:14px; }
.lb-count{
  position:absolute; bottom:12px; left:50%; transform:translateX(-50%);
  color:#F3F4F1; font-size:.85rem; background:rgba(24,32,29,.55); padding:3px 12px; border-radius:999px;
}
/* Round-95e: design "10-airy-editorial" — paper rail, serif heading, white shadowed
   avatars, and a floating white composer card with a borderless textarea. Scoped to the
   lightbox so the map panel's comment styling is untouched. */
.lb-side{
  width:min(360px, 90vw); background:var(--paper, #F3F4F1);
  display:flex; flex-direction:column; gap:8px; padding:18px 0 14px;
}
/* Round-95c: comments-only mode — the comment icon reuses THIS modal. Desktop (a story with
   no media, or none passed): the rail alone as a centred card. Phones: the rail alone, full
   screen. The media pane is hidden entirely in this mode. */
.lightbox.comments-only .lb-media{ display:none; }
/* Round-95j: media-only full-screen viewer (phones) — no comments rail, an action bar over
   the bottom of the stage. Baseline styling; the design round may reskin it. */
.lightbox.media-only .lb-side{ display:none; }
.lightbox.media-only .lb-media{ padding-bottom:70px; }
.lightbox.media-only .lb-stage{ max-width:100%; }
.lb-acts{
  position:absolute; left:0; right:0; bottom:0; z-index:2;
  display:flex; justify-content:space-around; align-items:center;
  padding:10px 8px calc(12px + var(--safe-bottom, 0px) + env(safe-area-inset-bottom, 0px));
  background:linear-gradient(transparent, rgba(10,14,12,.78) 40%);
}
.lb-acts[hidden]{ display:none; }
.lb-act{
  border:0; background:none; color:#E7EDE8; cursor:pointer; font-family:var(--font);
  display:flex; align-items:center; gap:6px; font-size:.9rem; padding:6px 10px;
}
.lb-act .ico-t{ font-size:1.15rem; }
.lb-act.on{ color:#9FD3BC; font-weight:700; }
.lightbox.comments-only{ align-items:center; justify-content:center; }
.lightbox.comments-only .lb-side{
  width:min(560px, 94vw); max-height:82vh; height:min(82vh, 720px);
  border-radius:16px;
}
@media (max-width:900px){
  .lightbox.comments-only .lb-side{
    width:100%; height:100vh; height:100dvh; max-height:none; border-radius:0;
    padding-top:calc(18px + var(--safe-top, 0px) + env(safe-area-inset-top, 0px));
    padding-bottom:calc(14px + var(--safe-bottom, 0px) + env(safe-area-inset-bottom, 0px));
  }
  .lightbox.comments-only .lb-side h3{ font-size:1.05rem; }
}
.lb-side-head{ display:flex; align-items:flex-start; justify-content:space-between; gap:12px; padding:0 20px; }
.lb-side h3{
  font-family:Georgia, "Times New Roman", serif; font-weight:700;
  font-size:1.02rem; line-height:1.3; margin:0; color:var(--ink, #2C3B36);
}
/* the media pane's floating ✕ disappears with the pane in comments-only mode; the rail's own
   close is the one that must always exist. Hidden when the media pane (and its ✕) shows. */
.lb-side-close{
  width:32px; height:32px; border-radius:50%; border:0;
  background:#fff; color:var(--ink, #2C3B36); cursor:pointer; flex:0 0 auto;
  box-shadow:0 2px 8px rgba(0,0,0,.08);
}
.lightbox:not(.comments-only) .lb-side-close{ display:none; }
.lb-comments{ flex:1; overflow-y:auto; display:flex; flex-direction:column; gap:16px; min-height:0; padding:2px 20px 8px; }
.lightbox .cmt .cmt-av, .lightbox .cmt .cmt-av-fallback{
  width:36px; height:36px; flex:0 0 36px; border-radius:50%;
  box-shadow:0 2px 8px rgba(0,0,0,.08);
}
.lightbox .cmt .cmt-av-fallback{ background:#fff; color:var(--deep, #4F8577); font-weight:700; font-size:.8rem;
  display:flex; align-items:center; justify-content:center; }
.lightbox .cmt .cmt-author b, .lightbox .cmt .cmt-head b{ font-size:.88rem; }
.lightbox .cmt .js-cmt-body{ font-size:.92rem; line-height:1.55; color:#3E4A44; margin:3px 0; }
.lightbox .cmt .cmt-actions small{ color:var(--soft, #5C6E66); font-size:.75rem; }
/* the floating composer card — borderless textarea, tools row with emoji · counter · Post */
.lb-form{
  display:block; margin:0 14px; background:#fff; border-radius:16px;
  box-shadow:0 8px 26px rgba(44,59,54,.14); padding:12px 14px;
}
.lb-form textarea{
  width:100%; min-height:56px; border:0; padding:4px 2px; background:none;
  font-family:var(--font); resize:none;
}
.lb-form textarea:focus{ outline:none; }
/* three children + space-between = emoji left, counter CENTRED, Post far right */
.lb-tools{ display:flex; align-items:center; justify-content:space-between; gap:10px; margin-top:4px; }
.lb-tools .emoji-btn{ border:0; background:none; font-size:1.25rem; cursor:pointer; padding:0; }
.lb-count{ color:var(--soft, #5C6E66); font-size:.74rem; }
.lb-send{
  border:0; border-radius:999px; padding:9px 22px; background:var(--accent, #618D7C);
  color:#fff; font-weight:700; font-family:var(--font); cursor:pointer;
}
.lb-send:disabled{ opacity:.45; cursor:default; }
.lb-reply{ color:var(--accent, #618D7C); font-weight:700; cursor:pointer; }

.lightbox .cmt.lb-child{ margin-left:44px; }
.lb-reply-chip{
  display:flex; align-items:center; gap:8px; margin:0 20px 6px; padding:6px 12px;
  background:var(--mint, #EAF1EC); border-radius:10px; font-size:.85rem; color:var(--soft, #5C6E66);
}
.lb-reply-chip[hidden]{ display:none; }
.lb-chip-x{ margin-left:auto; border:0; background:none; cursor:pointer; color:inherit; }
.lb-stage audio{ width:min(80vw, 460px); }
@media (max-width: 900px){
  .lightbox.open{ flex-direction:column; }
  .lb-stage{ max-width:100%; max-height:55vh; }
  .lb-stage img, .lb-stage video{ max-height:55vh; }
  .lb-side{ width:auto; max-height:42vh; }
}

/* ---- Round-19: audio player tile in the media grid ---- */
.gal-audio{ display:flex; align-items:center; gap:10px; width:100%; padding:10px 12px; }
.gal-audio .gal-audio-ico{ font-size:1.4rem; flex:0 0 auto; }
.gal-audio audio{ flex:1; min-width:0; height:38px; }

/* ---- Round-19: composers — full-width textarea with the Post button inside bottom-right ---- */
.compose{ position:relative; margin-top:12px; }
.compose textarea{
  display:block; width:100%; box-sizing:border-box; min-height:84px; resize:vertical;
  padding:11px 13px 52px; border:1.5px solid var(--track, #ADC3B9); border-radius:14px;
  font-family:var(--font); font-size:.92rem; background:#fff; color:var(--ink, #2C3B36);
}
.compose textarea:focus{ outline:none; border-color:var(--accent, #618D7C); }
/* round-19b: keep the send button clear of the textarea border on all sides */
.compose-tools{
  position:absolute; right:11px; bottom:11px; display:flex; align-items:center; gap:6px;
}
.compose-emoji{
  border:0; background:none; cursor:pointer; font-size:1.15rem; line-height:1;
  padding:4px; border-radius:8px; color:var(--ink-soft, #55635C);
}
.compose-emoji:hover{ background:rgba(97,141,124,.12); }
.compose-send{
  border:0; cursor:pointer; border-radius:999px; padding:7px 16px;
  background:var(--accent, #618D7C); color:#fff; font-family:var(--font); font-weight:800; font-size:.85rem;
}
.compose-send:hover{ background:var(--deep, #4F8577); }
.compose-send:disabled{ opacity:.5; cursor:default; }

/* ---- Round-19b: custom tag input with suggestions ---- */
.tag-input{ display:flex; gap:8px; align-items:center; }
.tag-input input{
  flex:1; min-width:0; padding:9px 12px; border:1.5px solid var(--track, #ADC3B9); border-radius:10px;
  font-family:var(--font); font-size:.9rem; background:#fff; color:var(--ink, #2C3B36);
}
.tag-input input:focus{ outline:none; border-color:var(--accent, #618D7C); }
.tag-input .btn{ flex:0 0 auto; }

/* ---- Round-19: modern file uploader (report form) ---- */
/* visually-hidden but present + clickable via the label — display:none breaks the file
   dialog on many mobile browsers, so use the sr-only pattern instead */
.up-file{
  position:absolute; left:0; top:0; width:1px; height:1px; padding:0; margin:-1px; overflow:hidden;
  clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; border:0;
  /* left/top pin it inside .up-zone — unanchored, some engines kept the input's intrinsic
     ~270px width, poking past the right edge and giving the page a horizontal scrollbar */
}
.up-zone{
  position:relative; border:2px dashed var(--track, #ADC3B9); border-radius:14px; padding:20px; text-align:center;
  cursor:pointer; transition:border-color .15s, background .15s; background:var(--mintbg, #EAF1EC);
}
.up-zone:hover, .up-zone.drag{ border-color:var(--accent, #618D7C); background:#fff; }
.up-zone .up-ico{ font-size:1.7rem; }
.up-zone .up-hint{ font-size:.85rem; color:var(--ink-soft, #55635C); margin-top:4px; }
/* Round-88g: the attachment strip that sits under the composer. Smaller than the standalone
   grid and light-framed, so a row of thumbnails reads as part of the message box rather than a
   second gallery. Scrolls sideways instead of growing the form when several files are attached. */
.up-grid-compose{ grid-template-columns:repeat(auto-fill, minmax(72px, 1fr)); gap:8px;
                  margin:2px 12px 6px; }   /* inset to the frame, clear of the button strip */
.up-grid-compose .up-tile{ border:1px solid rgba(44,59,54,.10); background:#FFFFFF; }
.up-grid-compose .up-name{ font-size:.62rem; }
.up-grid{ display:grid; grid-template-columns:repeat(auto-fill, minmax(92px, 1fr)); gap:10px; margin-top:12px; }
.up-tile{
  position:relative; aspect-ratio:1 / 1; border-radius:12px; overflow:hidden;
  border:1.5px solid var(--track, #ADC3B9); background:var(--panel-grad, #2C3B36);
  display:flex; align-items:center; justify-content:center; color:#F3F4F1; font-size:1.5rem;
}
.up-tile img, .up-tile video{ width:100%; height:100%; object-fit:cover; }
.up-tile .up-typ{ font-size:1.7rem; }
.up-tile .up-play{
  position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  font-size:1.3rem; color:#fff; text-shadow:0 1px 6px rgba(0,0,0,.6); background:rgba(0,0,0,.18);
  pointer-events:none;
}
.up-tile .up-name{
  position:absolute; left:0; right:0; bottom:0; font-size:.66rem; color:#fff; padding:3px 5px;
  background:rgba(24,32,29,.6); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; z-index:2;
}
.up-tile .up-del{
  position:absolute; top:4px; right:4px; width:22px; height:22px; border-radius:50%; border:0;
  background:rgba(24,32,29,.65); color:#fff; cursor:pointer; font-size:.85rem; line-height:1;
  display:flex; align-items:center; justify-content:center; z-index:3;
}
.up-tile .up-del:hover{ background:#C0392B; }
/* per-file progress + status while uploading */
.up-tile .up-bar{ position:absolute; left:0; bottom:0; height:4px; background:var(--accent, #618D7C); transition:width .2s; z-index:2; }
.up-tile .up-pct{
  position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  background:rgba(24,32,29,.5); color:#fff; font-weight:800; font-size:.82rem; z-index:1;
}
.up-tile .up-badge{
  position:absolute; top:4px; left:4px; width:22px; height:22px; border-radius:50%;
  display:flex; align-items:center; justify-content:center; font-size:.8rem; font-weight:800; z-index:2;
}
.up-tile .up-badge.ok{ background:#1FA97A; color:#fff; }
.up-tile .up-badge.err{ background:#C0392B; color:#fff; }
.up-tile.error{ border-color:#C0392B; }
.up-tile.uploading{ opacity:.95; }
.up-toast{ margin-top:8px; font-size:.85rem; color:#8e2a1f; }
/* round-55: Facebook-style threaded comment replies */
.cmt-thread{ margin-bottom:2px; }
.cmt-replies{ margin-left:38px; border-left:2px solid var(--track,#E2DCD3); padding-left:10px; margin-top:2px; }
.cmt-reply-to{ color:var(--accent,#618D7C); font-weight:600; margin-right:3px; }
.cmt-more-replies{ background:none; border:none; color:var(--accent,#618D7C); cursor:pointer; font-size:.82rem; font-weight:600; padding:4px 0 6px; }
.cmt-more-replies:hover{ text-decoration:underline; }
.cmt-replying{ display:flex; align-items:center; justify-content:space-between; gap:8px; background:rgba(97,141,124,.12); border-radius:8px; padding:5px 10px; margin-bottom:6px; font-size:.82rem; color:var(--ink-soft,#6b7a72); }
.cmt-reply-x{ background:none; border:none; cursor:pointer; color:var(--ink-soft,#6b7a72); font-size:.95rem; line-height:1; padding:0 2px; }
.cmt-reply-x:hover{ color:var(--accent,#618D7C); }
@media (max-width:640px){ .cmt-replies{ margin-left:22px; padding-left:8px; } }
/* round-51: live-map point context menu (right-click / long-press) */
.mapctx{
  position:fixed; z-index:1200; min-width:230px; max-width:min(88vw,300px);
  background:var(--panel,#fff); border:1px solid var(--line,#E2DCD3); border-radius:14px;
  box-shadow:0 14px 40px rgba(44,59,54,.28); overflow:hidden; padding:5px;
  animation:mapctxIn .12s ease-out;
}
@keyframes mapctxIn{ from{ opacity:0; transform:scale(.96) } to{ opacity:1; transform:scale(1) } }
.mapctx-head{
  display:flex; align-items:center; gap:7px; padding:8px 10px 9px; margin:0 2px 4px;
  border-bottom:1px solid var(--line,#E2DCD3); font-size:.82rem; color:var(--ink-soft,#6b7a72);
}
.mapctx-label{ font-weight:700; color:var(--ink,#2C3B36); overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.mapctx-item{
  display:flex; align-items:center; gap:11px; width:100%; min-height:44px; padding:10px 12px;
  border:none; background:transparent; border-radius:9px; cursor:pointer; text-align:left;
  font-size:.92rem; color:var(--ink,#2C3B36); transition:background .1s;
}
.mapctx-item:hover, .mapctx-item:focus-visible{ background:rgba(97,141,124,.13); outline:none; }
.mapctx-item:active{ background:rgba(97,141,124,.22); }
.mapctx-ic{ width:22px; text-align:center; font-size:1.05rem; flex:0 0 22px; }
@media (max-width:640px){
  .mapctx{ min-width:0; width:min(92vw,320px); border-radius:16px; }
  .mapctx-item{ min-height:50px; font-size:1rem; }
}
/* round-52: enlarge Ada media (images, charts, tables) into a modal */
.ada-media{ position:relative; max-width:100%; }
.ada-zoom{
  position:absolute; top:6px; right:6px; z-index:3; width:28px; height:28px; padding:0;
  border:none; border-radius:7px; background:rgba(44,59,54,.6); color:#fff; cursor:pointer;
  font-size:1rem; line-height:1; display:flex; align-items:center; justify-content:center;
  opacity:0; transition:opacity .12s;
}
.ada-media:hover .ada-zoom, .ada-zoom:focus-visible{ opacity:1; }
@media (hover:none){ .ada-zoom{ opacity:.9; } } /* touch: always visible, no hover */
.ada-modal{
  position:fixed; inset:0; z-index:4000; background:rgba(20,26,24,.82);
  display:flex; align-items:center; justify-content:center; padding:24px; animation:mapctxIn .15s ease-out;
}
.ada-modal-inner{
  background:var(--panel,#fff); border-radius:14px; padding:16px; position:relative;
  max-width:min(96vw,1100px); max-height:92vh; overflow:auto; box-shadow:0 20px 60px rgba(0,0,0,.5);
}
.ada-modal-inner img, .ada-modal-inner svg{ max-width:100%; height:auto; display:block; }
/* round-53: the cloned table lands outside .msg-bubble, so restate its styling here (bigger
   than in-bubble since it's the enlarged view) */
.ada-modal-inner .tbl-scroll{ overflow-x:auto; max-width:100%; }
.ada-modal-inner table{ border-collapse:collapse; width:auto; min-width:100%; font-size:.95rem; }
.ada-modal-inner th, .ada-modal-inner td{ border:1px solid var(--track, #ADC3B9); padding:9px 14px; text-align:left; white-space:nowrap; }
.ada-modal-inner th{ background:var(--deep, #4F8577); color:#F3F4F1; font-weight:700; position:sticky; top:0; }
.ada-modal-inner tbody tr:nth-child(even){ background:rgba(97,141,124,.07); }
.ada-modal-close{
  position:sticky; top:0; float:right; width:34px; height:34px; border:none; border-radius:8px;
  background:var(--accent,#618D7C); color:#fff; cursor:pointer; font-size:1.05rem; z-index:5;
}
@media (max-width:640px){ .ada-modal{ padding:10px; } .ada-modal-inner{ padding:10px; } }
/* round-50: copy button on Ada assistant replies — subtle, appears on bubble hover */
.msg-bubble{ position:relative; }
/* round-58: sit the copy button BELOW the message content (not overlapping the last line) */
.asst-copy{
  display:block; margin-top:5px; width:26px; height:24px; padding:0;
  border:none; border-radius:7px; background:transparent; color:var(--sage2,#8FA99B);
  cursor:pointer; font-size:.95rem; line-height:1; opacity:0; transition:opacity .12s, background .12s;
}
.msg-bubble.mine .asst-copy{ margin-left:auto; }  /* right-aligned user bubble → keep it under the text */
/* round-60: quota usage meter (account Plan & Credits) */
.quota-bar{ height:8px; border-radius:6px; background:rgba(97,141,124,.18); overflow:hidden; }
.quota-bar span{ display:block; height:100%; background:var(--accent,#618D7C); border-radius:6px; transition:width .3s; }
/* round-56: Ada facility pins — a type emoji in a service-coloured circle */
.fac-pin{ width:26px; height:26px; border-radius:50%; display:flex; align-items:center;
  justify-content:center; font-size:14px; box-shadow:0 2px 5px rgba(44,59,54,.4); border:2px solid #F3F4F1; }
.msg-bubble:hover .asst-copy, .asst-copy:focus-visible{ opacity:1; }
.asst-copy:hover{ background:rgba(97,141,124,.14); color:var(--accent,#618D7C); }
.up-rec{ display:flex; align-items:center; gap:10px; margin-top:10px; flex-wrap:wrap; }
.up-rec .rec-dot{ width:10px; height:10px; border-radius:50%; background:#C0392B; animation:recpulse 1s infinite; }
@keyframes recpulse{ 0%,100%{ opacity:1 } 50%{ opacity:.3 } }

/* ---- Round-21: GPS location card + submit loader ---- */
.geo-card{
  display:flex; align-items:center; gap:12px;
  padding:14px 16px; border-radius:12px;
  border:1.5px solid var(--track); background:#f7fafc;
}
.geo-card.located{ border-color:#1E824C; background:#f2fbf5; }
.geo-card.error{ border-color:#C0392B; background:#fdf3f2; }
.geo-card > div{ flex:1 1 auto; min-width:0; }
.geo-card .hint{ margin-top:2px; }
.geo-ico{ font-size:1.4rem; line-height:1; }
.geo-spin, .btn-spin{
  display:inline-block; border-radius:50%;
  border:2.5px solid rgba(0,0,0,.15); border-top-color:currentColor;
  animation:geospin .7s linear infinite; flex:none;
}
.geo-spin{ width:20px; height:20px; color:#1E5AA8; border-width:3px; }
.btn-spin{ width:15px; height:15px; color:#fff; vertical-align:-2px; margin-right:8px; }
@keyframes geospin{ to{ transform:rotate(360deg) } }
.submit-status{
  margin:10px 2px 0; text-align:center; font-size:.9rem; font-weight:600;
  color:var(--ink-soft); animation:statuspulse 1.4s ease-in-out infinite;
}
@keyframes statuspulse{ 0%,100%{ opacity:1 } 50%{ opacity:.55 } }

/* ---- Round-74: admin console shell (sidebar + main) ----
   Admin pages render through AdminLayout, not MainLayout, so there is no marketing footer
   and the page shell is a two-column grid. 1000px is an existing breakpoint in this file
   (see the map chips rules below) — no new breakpoint is introduced. */
.adm-shell{
  display:grid; grid-template-columns:248px 1fr; align-items:start;
  padding-top:var(--header-h); min-height:100vh;
}
.adm-main{ min-width:0; }               /* lets wide tables scroll instead of blowing the grid */
.adm-main .page{ padding-top:0; }       /* the shell already offsets for the fixed header */
.adm-main .hero-inner{ padding:28px 0 24px; }
.adm-main .section{ padding:28px 0 48px; }
.adm-main .admin-crumbs{ padding:12px 20px 0; margin-top:0; }

.adm-sidebar{
  position:sticky; top:var(--header-h); align-self:start;
  /* round-80: a full viewport height (not max-height) so the right-hand divider runs to the
     bottom of the window instead of stopping under the last nav item, which left the rail
     looking like a floating box on short pages. */
  height:calc(100vh - var(--header-h)); overflow-y:auto;
  padding:14px 10px 24px; border-right:1px solid var(--track);
  background:var(--paper); scrollbar-width:thin;
}
/* groups reuse .scard so site.js's existing .scard-head handler gives tap-to-expand */
.adm-sidebar .adm-group{ border:0; border-radius:0; background:transparent; margin-bottom:2px; }
.adm-sidebar .scard-head{ padding:8px 10px; border-bottom:0; }
.adm-sidebar .scard-head h3{
  font-size:.72rem; text-transform:uppercase; letter-spacing:.06em;
  color:var(--ink-soft); font-weight:800;
}
.adm-sidebar .scard-body{ padding:0 0 6px; }
.adm-link{
  display:flex; align-items:center; gap:9px; padding:7px 12px; border-radius:9px;
  color:var(--ink); text-decoration:none; font-size:.88rem; font-weight:600;
}
.adm-link:hover{ background:var(--mintbg); text-decoration:none; }
.adm-link.on{ background:var(--accent); color:#fff; }
.adm-ico{ width:18px; text-align:center; flex:0 0 auto; }
.adm-menu-btn{ display:none; background:none; border:0; font-size:1.25rem; cursor:pointer; color:var(--ink); }
.adm-header .brand .tag{ background:#fff; color:var(--sage2); }

/* Round-83: the admin header is ONE non-wrapping flex row — brand, menu button, country
   scope picker, map link, profile, app grid — and nothing in it was allowed to shrink
   (.nav-right > * is flex:0 0 auto, .cpick-input is width:100%, .adm-scope had no rules at
   all). On a phone the row overflowed and the right-hand cluster was pushed past the edge:
   profile image, messages and the grid launcher were all in the DOM and unreachable.
   Everything below just gives the row somewhere to give. */
@media (max-width: 1000px){
  .adm-shell{ grid-template-columns:1fr; }
  .adm-menu-btn{ display:inline-block; order:-1; margin-right:6px; }
  /* Keep the mark, drop the wordmark — it is the widest thing in the row and the least useful
     on a screen where the page title is two lines below it. */
  .adm-header .brand span{ display:none; }
  .adm-header{ padding:0 12px; gap:6px; }
  /* The scope picker is the other wide element. It stays (country scope matters on every admin
     page) but is capped and allowed to shrink instead of setting the row's width. */
  .adm-header .adm-scope{ flex:1 1 auto; min-width:0; max-width:190px; }
  .adm-header .adm-scope .cpick-input{ min-width:0; }
  /* And the right-hand cluster is pinned: it is the last thing that should ever be squeezed out. */
  .adm-header .nav-right{ flex:0 0 auto; gap:10px; margin-left:auto; }
}

/* Phones proper. 190px of country picker still pushed the app-grid launcher 30px past the edge
   on a 375px screen, so the cap tightens again here — measured, not guessed. "Ghana" and every
   other country name still reads at 120px; the full list opens on tap regardless. */
@media (max-width: 560px){
  .adm-header{ padding:0 10px; gap:4px; }
  /* Measured on the real page at 375px, not a mock: .nav-right holds SIX items — scope 120,
     map 42, bell 37, messages 32, avatar 34, app-grid 42 — plus gaps, totalling 347 beside a
     78px brand. 425 > 375, so the app-grid launcher fell off the end. The map shortcut is the
     only duplicate in that row (it is also the first tile inside the grid menu it was pushing
     off-screen), so on a phone it is the one that goes. */
  .adm-header .nav-right > .nav-icon{ display:none; }
  .adm-header .adm-scope{ max-width:110px; }
  .adm-header .nav-right{ gap:6px; }
  /* The INPUT has to be narrow to fit the row; the open LIST does not — it is absolutely
     positioned, so it costs the header nothing.
     But it must be anchored to the HEADER, not to the input. .cpick is position:relative, so
     right:0 pinned the list's right edge to the 110px input's right edge (x=196) and a 320px
     list then ran to x=-124 — off the left of the screen. Dropping .cpick out of the positioning
     chain on mobile makes the fixed, full-width header the anchor instead, so left/right insets
     are effectively viewport insets and the list cannot leave the screen at any width. */
  .adm-header .adm-scope .cpick{ position:static; }
  .adm-header .adm-scope .cpick-list{
    left:10px; right:10px; width:auto; top:calc(100% + 4px);
  }
  .adm-sidebar{
    position:fixed; top:var(--header-h); left:0; bottom:0; width:264px; z-index:1150;
    transform:translateX(-100%); transition:transform .2s ease;
    box-shadow:6px 0 24px rgba(44,59,54,.18); max-height:none;
  }
  .adm-shell.nav-open .adm-sidebar{ transform:none; }
  /* the .scard mobile rules collapse groups by default; open ones stay expanded */
  .adm-sidebar .scard-head{ cursor:pointer; }
}

/* ---- Round-21: admin breadcrumb ---- */
.admin-crumbs{ margin-top:12px; font-size:.86rem; color:var(--ink-soft);
  display:flex; flex-wrap:wrap; gap:8px; align-items:center; }
.admin-crumbs a{ color:var(--navy, #1E5AA8); text-decoration:none; font-weight:600; }
.admin-crumbs a:hover{ text-decoration:underline; }
.admin-crumbs .sep{ opacity:.5; }
.admin-crumbs .cur{ color:var(--ink); font-weight:700; }

/* ---- Round-21: platform role popover (admin users) ---- */
.role-pop-wrap{ position:relative; display:inline-block; }
.role-pop{ position:absolute; top:calc(100% + 6px); left:0; z-index:60; width:270px;
  background:var(--paper, #fff); border:1.5px solid var(--track); border-radius:12px;
  box-shadow:0 14px 40px rgba(44,59,54,.22); padding:8px; text-align:left; }
.role-pop h4{ margin:4px 8px 6px; font-size:.7rem; text-transform:uppercase; letter-spacing:.04em; color:var(--ink-soft); }
.role-pop label{ display:flex; align-items:flex-start; gap:9px; padding:7px 8px; border-radius:8px;
  cursor:pointer; font-size:.86rem; font-weight:600; line-height:1.25; }
.role-pop label:hover{ background:#EEF3EE; }
.role-pop label.disabled{ opacity:.55; cursor:not-allowed; }
.role-pop label input{ width:auto; margin-top:2px; flex:none; }
.role-pop .rp-desc{ display:block; font-size:.74rem; color:var(--ink-soft); font-weight:400; }
.role-pop .rp-div{ height:1px; background:var(--track); margin:6px 4px; }
.role-tags{ display:flex; flex-wrap:wrap; gap:5px; align-items:center; }
.role-tag{ font-size:.72rem; padding:3px 9px; border-radius:999px; background:#E7EFE7;
  color:var(--deep, #1E824C); font-weight:700; }
.role-tag.admin{ background:#FBE3E0; color:#B23B2E; }
.role-none{ color:var(--ink-soft); font-size:.82rem; }

/* ---- Round-22: area autocomplete ---- */
.ac-wrap{ position:relative; }
.ac-input{ width:100%; }
.ac-menu{ position:absolute; top:calc(100% + 4px); left:0; right:0; z-index:40;
  background:var(--paper, #fff); border:1.5px solid var(--track); border-radius:12px;
  box-shadow:0 14px 40px rgba(44,59,54,.18); padding:6px; max-height:260px; overflow-y:auto; }
.ac-item{ display:flex; align-items:center; gap:9px; width:100%; text-align:left;
  padding:9px 10px; border:none; background:none; border-radius:8px; cursor:pointer;
  font:inherit; font-size:.92rem; color:var(--ink); }
.ac-item:hover{ background:#EEF3EE; }
.ac-ico{ display:inline-flex; width:16px; height:16px; color:var(--green, #3E7C63); flex:none; }
.ac-ico svg{ width:16px; height:16px; }

/* ---- Round-22: translate-a-comment affordance ---- */
.cmt-translate{ border:none; background:none; cursor:pointer; padding:0; margin-left:4px;
  font:inherit; font-size:.8rem; font-weight:600; color:var(--green, #3E7C63); }
.cmt-translate:hover{ text-decoration:underline; }
.cmt-translate:disabled{ opacity:.6; cursor:default; text-decoration:none; }
.story-cmt small + .cmt-translate{ margin-left:8px; }

/* ---- Round-10: floating assistant ---- */
.asst-fab{
  position:fixed; right:16px; top:50%; transform:translateY(-50%); z-index:2500;
  /* Round-89: a pill, not a circle — it holds the word "Ada" now. min-width keeps it from
     collapsing narrower than the old dot in languages that render the name shorter. */
  width:auto; min-width:52px; height:52px; padding:0 12px;
  border-radius:999px; border:0; cursor:pointer;
  font-size:.98rem; font-weight:400; font-style:italic; letter-spacing:.01em;
  font-family:var(--font, inherit);
  display:inline-flex; align-items:center; justify-content:center;
  background:linear-gradient(135deg, var(--deep, #4F8577), var(--accent, #618D7C)); color:#fff;
  box-shadow:0 8px 26px rgba(44,59,54,.4);
  transition:transform .15s, box-shadow .15s;
}
.asst-fab:hover{ transform:translateY(-50%) scale(1.07); box-shadow:0 10px 30px rgba(44,59,54,.5); }

/* Round-89: on the live map Ada joins the right-hand stack, so it takes that stack's exact
   geometry — 44px round, matching .right-stack .map-card.min — and the map palette's tan.
   A wider, greener, differently-shaped button sitting among the map cards reads as something
   bolted on rather than one of the map's own controls. The label shrinks to fit the circle;
   it stays the word rather than an icon, because "Ada" is the thing people are told to ask. */
.asst-fab.asst-fab-map{
  width:44px; min-width:44px; height:44px; padding:0; border-radius:50%;
  background:var(--tan-light, #E3C9A5); color:var(--ink, #2C3B36);
  font-size:.74rem; letter-spacing:0;
}
.asst-fab.asst-fab-map .asst-lock{ font-size:.6rem; }
@media (max-width:900px){
  /* Round-95k: at 360dp the mid-screen pill sat on top of every hero paragraph (measured on
     the G6). Content pages anchor it lower-right — above the stories ＋ FAB — while the map
     keeps its curated position (the html.pg-map / body:has(#map) overrides outrank this). */
  .asst-fab{ top:auto; bottom:calc(150px + var(--safe-bottom, 0px)); transform:none; }
  .asst-fab:hover{ transform:scale(1.07); }
}
.asst-fab.open{ display:none; } /* round-11: one close button only — the drawer's */
/* round-71: signed-out visitors see the FAB locked — it links to sign-in */
a.asst-fab-locked{ display:inline-flex; align-items:center; justify-content:center; text-decoration:none; }
.asst-lock{
  position:absolute; right:-3px; bottom:-3px; font-size:.72rem; line-height:1;
  background:#F3F4F1; border-radius:50%; padding:2.5px; box-shadow:0 2px 6px rgba(44,59,54,.3);
}
.asst-close{
  width:34px; height:34px; flex:0 0 34px; border-radius:50%; border:0; cursor:pointer;
  padding:0; line-height:1;
  background:rgba(243,244,241,.18); color:#F3F4F1; font-size:1rem;
  display:inline-flex; align-items:center; justify-content:center;
}
.asst-close:hover{ background:rgba(243,244,241,.32); }
.asst-drawer{
  position:fixed; top:0; right:0; bottom:0; z-index:2400;
  width:min(430px, 100vw); background:var(--paper, #FBFBF8);
  border-left:1.5px solid var(--track, #ADC3B9); box-shadow:-14px 0 44px rgba(44,59,54,.3);
  display:flex; flex-direction:column;
  transform:translateX(105%); transition:transform .28s cubic-bezier(.2,.8,.25,1);
}
.asst-drawer.open{ transform:translateX(0); }
/* Round-94m: no phone-specific drawer sizing any more. The keyboard shrinks the VIEWPORT
   itself (native WebView margin in-app, interactive-widget=resizes-content in Safari), so
   a plain top:0/bottom:0 panel is already right — the round-94d --vvh override raced the
   native resize and made the drawer "inconsistent, sometimes disappearing". */
.asst-drawer-head{
  display:flex; align-items:center; justify-content:space-between; gap:10px;
  padding:14px 16px; border-bottom:1px solid var(--track, #ADC3B9);
  background:var(--panel-grad, #2C3B36); color:#F3F4F1;
}
.asst-chat{ display:flex; flex-direction:column; flex:1; min-height:0; }
.asst-toolbar{ display:flex; align-items:center; gap:8px; padding:10px 14px; border-bottom:1px dashed var(--track, #ADC3B9); }
.asst-history{
  display:flex; flex-direction:column; max-height:220px; overflow-y:auto;
  border-bottom:1px dashed var(--track, #ADC3B9); background:var(--mintbg, #EAF1EC);
}
.asst-hist-row{
  display:flex; flex-direction:column; align-items:flex-start; gap:2px; text-align:left;
  border:0; background:none; cursor:pointer; padding:9px 16px; font-family:var(--font);
  border-bottom:1px dashed var(--track, #ADC3B9);
}
.asst-hist-row:hover{ background:rgba(97,141,124,.14); }
.asst-hist-row.sel{ background:rgba(97,141,124,.2); }
.asst-hist-row small{ color:var(--ink-soft, #55635C); font-size:.72rem; }

/* round-11: assistant tables — proper formatting + horizontal scroll */
.msg-bubble .tbl-scroll{ overflow-x:auto; max-width:100%; margin:8px 0; }
.msg-bubble table{ border-collapse:collapse; font-size:.82rem; white-space:nowrap; }
.msg-bubble th, .msg-bubble td{ border:1px solid var(--track, #ADC3B9); padding:5px 10px; text-align:left; }
.msg-bubble th{ background:var(--deep, #4F8577); color:#F3F4F1; font-weight:700; }
.msg-bubble.mine th{ background:rgba(243,244,241,.25); }
.msg-bubble tbody tr:nth-child(even){ background:rgba(97,141,124,.07); }
.asst-scroll{ flex:1; min-height:220px; }
.asst-drawer .asst-scroll{ max-height:none; min-height:0; }  /* same shrink rule as .msg-scroll — the 220px page-panel minimum would push the drawer’s composer out when the keyboard shrinks it */
.page .asst-scroll{ max-height:52vh; }

/* ============================================================
   Round-95: aquaComposerSheet — full-screen mobile composer.
   Facebook-compose layout: top bar (Cancel · title · Post), the
   textarea owns the middle, tools row at the bottom. Fixed inset:0
   — the viewport itself shrinks for the keyboard, so no height
   math, ever (round-94m). z: backdrop 3090 / sheet 3100 — above
   the lightbox (3000, the sheet opens FROM its comment rail),
   below the emoji panel (3200, which must overlay the sheet).
   ============================================================ */
/* ============ Round-96g: /alerts-setup as the 09-chat-ada wizard ======================
   Ada-led chat onboarding. Accent (not the mockup's amber) for the avatar + CTAs — the
   user's call. sensing.js owns [data-setup-state]'s class list ("card setup-state warn|ok"),
   so the readout is styled through those exact classes. */
/* Round-96h: mockup-faithful pacing — ONE screen at a time, viewport-fit, NO scrolling.
   The intro turn (the disclosure) shows until Get started adds .wz-started (sensing.js
   also adds it when the permission is already "always"); the state turns are gated with
   !important because sensing.js drives their inline display. Each turn is a column with
   its CTA pinned at the bottom, mockup-style; .wz-bubs may scroll internally as a safety
   valve on tiny screens, but the copy is sized not to need it. */
.wz-section{ padding-top:24px; padding-bottom:0; }
html.in-app .wz-section{ padding-top:calc(var(--safe-top, 0px) + 14px); }
/* Round-96n2 (G6: "Get started is below the screen"): in-app, the wizard page must NOT
   reserve header space. Round-93b's app-shell style sets :root{--header-h:0} but the
   round-84 rule `html.in-app:not(.signup-bar-on){--header-h:calc(64px + safe-top)}` is
   more specific and wins, leaving 88dp of clearance for a header that is not there —
   which pushed the viewport-fit wizard's CTA below the fold. Scoped, explicit zero. */
html.in-app .wz-page{ padding-top:0; }
.wz-chat{
  max-width:480px; margin:0 auto; display:flex; flex-direction:column;
  height:calc(100vh - var(--safe-top, 0px) - 46px);
  height:calc(100dvh - var(--safe-top, 0px) - 46px);
  overflow:hidden;
}
.wz-chat:not(.wz-started) [data-setup-when]{ display:none !important; }
.wz-chat.wz-started .wz-intro{ display:none; }
.wz-turn{ flex:1; min-height:0; display:flex; flex-direction:column; }
.wz-bubs{ flex:1; min-height:0; overflow-y:auto; display:grid; gap:10px; align-content:start; }
.wz-turn .wz-cta, .wz-turn .wz-acts{ margin-top:14px; }
.wz-chat-head{
  display:flex; gap:12px; align-items:center; padding:4px 4px 14px;
  border-bottom:1px solid var(--track, #ADC3B9); margin-bottom:14px;
}
.wz-ada{
  width:46px; height:46px; border-radius:50%; background:var(--accent, #618D7C); color:#fff;
  font-weight:700; font-size:.8rem; display:flex; align-items:center; justify-content:center;
  flex:0 0 46px;
}
.wz-who b{ display:block; color:var(--ink, #2C3B36); font-size:.98rem; }
.wz-who span{ font-size:.76rem; color:var(--sage2, #8FA99B); }
/* Round-96n (user pick, from the 09-chat-ada mockup): the status is the 03-step-tracker
   card — the grant chain as two steps with a progress bar. sensing.js still owns
   [data-setup-state] (now the card's caption line) and stamps wz-lv-* on .wz-chat, which
   is all the steps/bar/dots below read. Plain class chains only — no :has (G6). */
.st-root{ background:#fff; border:1px solid #E4E8E2; border-radius:16px; padding:13px 16px 9px; margin-bottom:14px; }
.st-steps{ display:flex; align-items:center; }
.st-step{ display:flex; flex-direction:column; align-items:center; gap:5px; flex:0 0 auto; width:96px; }
.st-n{
  width:26px; height:26px; border-radius:50%; background:#EDF2EE; color:#8FA99B;
  font-size:.72rem; font-weight:800; display:flex; align-items:center; justify-content:center;
  border:2px solid #E0E5DF;
}
.st-step > span{ font-size:.62rem; color:#8FA99B; text-align:center; line-height:1.25; }
/* step 1: "now" until a grant lands, then done */
.wz-chat .st-s1 .st-n{ border-color:#C89B54; color:#C89B54; background:#FBF3E2; }
.wz-chat .st-s1 > span{ color:#7A5B22; font-weight:700; }
.wz-chat.wz-lv-whileusing .st-s1 .st-n, .wz-chat.wz-lv-always .st-s1 .st-n{
  background:#4F8577; border-color:#4F8577; color:#fff; }
.wz-chat.wz-lv-whileusing .st-s1 > span, .wz-chat.wz-lv-always .st-s1 > span{ color:#1F5C44; font-weight:400; }
/* step 2: idle, then "now" at whileusing, done at always */
.wz-chat.wz-lv-whileusing .st-s2 .st-n{ border-color:#C89B54; color:#C89B54; background:#FBF3E2; }
.wz-chat.wz-lv-whileusing .st-s2 > span{ color:#7A5B22; font-weight:700; }
.wz-chat.wz-lv-always .st-s2 .st-n{ background:#4F8577; border-color:#4F8577; color:#fff; }
.wz-chat.wz-lv-always .st-s2 > span{ color:#1F5C44; }
.st-bar{ flex:1; height:3px; background:#E0E5DF; border-radius:99px; position:relative; top:-11px; overflow:hidden; }
.st-bar i{ display:block; height:100%; background:#4F8577; width:0; border-radius:99px; }
.wz-chat.wz-lv-whileusing .st-bar i{ width:50%; }
.wz-chat.wz-lv-always .st-bar i{ width:100%; }
/* the caption is the SAME [data-setup-state] element sensing.js has always written */
.wz-chat .setup-state{
  border:0; background:none; box-shadow:none; border-radius:0; padding:7px 0 0;
  margin:0; text-align:center; font-size:.76rem; color:var(--sage2, #8FA99B);
}
.wz-chat .setup-state.warn{ background:none; border:0; color:#A98F5E; }
.wz-chat .setup-state.ok{ background:none; border:0; color:#1F5C44; font-weight:700; }
/* pager dots (mockup): decorative progress on the same classes; active = accent-gradient pill */
.wz-dots{ display:flex; gap:7px; justify-content:center; padding:12px 0 2px; }
.wz-dots span{ width:7px; height:7px; border-radius:50%; background:#D7DED4; }
.wz-chat:not(.wz-started) .wz-dots span:nth-child(1),
.wz-chat.wz-started.wz-lv-none .wz-dots span:nth-child(2),
.wz-chat.wz-started.wz-lv-whileusing .wz-dots span:nth-child(3),
.wz-chat.wz-lv-always .wz-dots span:nth-child(4){
  background:linear-gradient(135deg, #4F8577, #8FB8A6); width:22px; border-radius:99px;
}
.wz-chat.wz-started .wz-dots span:nth-child(1),
.wz-chat.wz-lv-whileusing .wz-dots span:nth-child(2),
.wz-chat.wz-lv-always .wz-dots span:nth-child(2),
.wz-chat.wz-lv-always .wz-dots span:nth-child(3){
  background:linear-gradient(135deg, #7FA893, #B9CEC2);
}
.wz-chat.wz-lv-always .wz-dots span:nth-child(1){ background:linear-gradient(135deg, #7FA893, #B9CEC2); width:7px; border-radius:50%; }
@keyframes wzPop{ from{ opacity:0; transform:translateY(8px); } to{ opacity:1; } }
.wz-bubs .wz-b{ animation:wzPop .3s ease; }
.wz-b{
  max-width:88%; background:#fff; border:1px solid #E4E8E2; border-radius:4px 18px 18px 18px;
  padding:13px 15px; font-size:.92rem; line-height:1.55; color:#33403A;
  box-shadow:0 2px 8px rgba(44,59,54,.05); justify-self:start;
}
.wz-b b{ color:var(--ink, #2C3B36); }
.wz-hint{ font-size:.82rem; color:#7E8D85; font-style:italic; }
.wz-ol{ margin:0 0 0 18px; padding:0; line-height:1.7; }
.wz-cta{
  display:block; width:100%; border:0; background:var(--accent, #618D7C); color:#fff;
  font-family:var(--font); font-weight:800; font-size:.98rem; text-align:center;
  padding:15px; border-radius:999px; cursor:pointer; text-decoration:none;
  box-shadow:0 8px 20px rgba(97,141,124,.3);
}
.wz-cta:hover{ text-decoration:none; color:#fff; filter:brightness(1.05); }
.wz-acts{ display:grid; gap:10px; }
.wz-ghost{
  display:block; text-align:center; padding:12px; font-size:.9rem; color:var(--deep, #4F8577);
  border:1px solid var(--track, #ADC3B9); border-radius:999px; text-decoration:none; background:#fff;
}

/* ============ Round-96p: /report as design 06 — one question per screen ================
   Public create flow pages; edit + staff get .wz2-classic (every step visible at once).
   Steps hide by CLASS, never removal — the uploader's #upInput must stay in the DOM. */
.wz2-step{ display:none; }
.wz2-step.on, .wz2-classic .wz2-step{ display:block; animation:wzPop .25s ease; }
.wz2-classic .wz2-step{ animation:none; }
.wz2-dots{ display:flex; gap:6px; margin-bottom:18px; }
.wz2-dots span{ flex:1; height:5px; border-radius:999px; background:#E0E5DF; }
.wz2-dots span.on{ background:var(--accent, #618D7C); }
.wz2 .wz2-q{ font-size:1.25rem; font-weight:800; color:#1F2A24; line-height:1.3; }
.wz2-classic .wz2-q{ font-size:inherit; font-weight:600; }   /* classic keeps ordinary labels */
.wz2-hint{ margin:4px 0 12px; }
/* service picker as tiles (wizard only — classic keeps the chip row) */
.wz2:not(.wz2-classic) .wz2-svc{ display:grid; grid-template-columns:repeat(2,1fr); gap:10px; margin-top:10px; }
@media (min-width:700px){ .wz2:not(.wz2-classic) .wz2-svc{ grid-template-columns:repeat(3,1fr); } }
.wz2:not(.wz2-classic) .wz2-svc .chip{
  display:grid; gap:6px; justify-items:center; padding:16px 8px; border-radius:16px;
  font-size:.92rem; border:1.5px solid #E0E5DF; background:#fff;
}
.wz2:not(.wz2-classic) .wz2-svc .chip .ico-t{ font-size:1.5rem; }
.wz2:not(.wz2-classic) .wz2-svc .chip.sel{
  /* the classic .chip.sel paints accent-on-white-text — on a white tile that is white on
     white. Selected tile = coloured border + coloured label, body stays white. */
  border-color:var(--c, var(--accent)); box-shadow:0 8px 20px rgba(44,59,54,.08);
  background:#fff; color:var(--c, var(--accent)); font-weight:700;
}
/* issue types as tappable rows */
.wz2-iss{ display:grid; gap:8px; margin-top:10px; }
.wz2-issb{
  border:1.5px solid #E0E5DF; background:#fff; border-radius:14px; padding:13px 15px;
  text-align:left; cursor:pointer; font-size:.92rem; color:#2C3B36; font-family:var(--font);
}
.wz2-issb.on{ border-color:var(--accent, #618D7C); background:#EFF5F1; font-weight:700; }
.wz2-issb.skip{ color:#8FA99B; font-style:italic; }
/* danger + visibility chips stack full-width as cards in the wizard */
.wz2:not(.wz2-classic) .wz2-sev, .wz2:not(.wz2-classic) .wz2-vis{ display:grid; gap:8px; margin-top:10px; }
.wz2:not(.wz2-classic) .wz2-sev .chip, .wz2:not(.wz2-classic) .wz2-vis .chip{
  width:100%; text-align:left; padding:14px 16px; border-radius:14px; font-size:.95rem;
}
/* the range picker */
.wz2-cap{ font-size:.82rem; font-weight:700; color:#2C3B36; margin:12px 0 8px; }
.wz2-map{ height:230px; border-radius:14px; border:1.5px solid var(--track, #ADC3B9); overflow:hidden; }
.wz2-slider{ display:flex; align-items:center; gap:10px; margin-top:10px; }
.wz2-slider span{ font-size:.78rem; color:#8FA99B; }
.wz2-slider input[type=range]{ flex:1; accent-color:var(--accent, #618D7C); }
.wz2-slider b{ font-size:.84rem; color:#2C3B36; min-width:52px; text-align:right; }
/* the more-details fold opens flat inside its step */
.wz2:not(.wz2-classic) .more-details summary{ display:none; }
.wz2-step-title{ display:block; margin-bottom:6px; }
/* wizard footer */
.wz2-foot{ display:flex; gap:12px; align-items:center; margin-top:16px; }
.wz2-ghost{
  border:0; background:none; color:#93A198; font-size:.9rem; padding:14px 6px;
  cursor:pointer; font-family:var(--font);
}
.wz2-next{
  flex:1; border:0; background:var(--accent, #618D7C); color:#fff; font-weight:800;
  font-size:1rem; padding:15px; border-radius:16px; cursor:pointer; font-family:var(--font);
}

html.composer-open, html.composer-open body{ overflow:hidden; }
.cs-backdrop{ position:fixed; inset:0; z-index:3090; background:rgba(24,32,29,.45); }
.cs-sheet{
  position:fixed; inset:0; z-index:3100; height:100vh; height:100dvh;
  display:grid; grid-template-rows:auto auto minmax(0,1fr) auto;  /* head | reply chip | body | tools */
  background:var(--paper, #FBFBF8);
  padding:calc(var(--safe-top, 0px) + env(safe-area-inset-top, 0px)) 0 0;
  animation:csIn .22s cubic-bezier(.2,.8,.25,1);
}
@keyframes csIn{ from{ transform:translateY(100%); } to{ transform:translateY(0); } }
@media (prefers-reduced-motion:reduce){ .cs-sheet{ animation:none; } }
/* children are placed EXPLICITLY — the omni-sheet lesson: a [hidden] sibling (the reply
   chip) generates no grid item, and auto-placement then shifts every later row up, handing
   the 1fr body row to the tools strip. */
.cs-head{
  grid-row:1;
  display:flex; align-items:center; gap:10px; padding:10px 14px;
  border-bottom:1px solid var(--track, #ADC3B9);
}
.cs-cancel{
  width:40px; height:40px; border-radius:50%; border:1.5px solid #F0EEE8; background:#fff;
  font-size:1rem; color:var(--ink, #2C3B36); cursor:pointer; flex:0 0 auto;
}
.cs-title{ flex:1; text-align:center; font-weight:700; color:var(--ink, #2C3B36); font-size:1rem;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
.cs-send{
  flex:0 0 auto; border:0; border-radius:999px; padding:9px 18px; cursor:pointer;
  background:var(--accent, #618D7C); color:#fff; font-family:var(--font); font-weight:700;
}
.cs-send:disabled{ opacity:.45; cursor:default; }
.cs-reply-chip{
  grid-row:2;
  display:flex; align-items:center; gap:8px; margin:8px 14px 0; padding:6px 12px;
  background:var(--mintbg, #EAF1EC); border-radius:10px; font-size:.85rem; color:var(--ink-soft, #55635C);
}
.cs-reply-chip[hidden]{ display:none; }
.cs-reply-x{ margin-left:auto; border:0; background:none; cursor:pointer; color:inherit; }
/* the .compose base gives the textarea its border/padding; here it fills the row instead */
.cs-form{ grid-row:3; margin:0; padding:12px 14px; display:flex; min-height:0; }
.cs-form textarea{
  flex:1; min-height:0; height:auto; resize:none; border:1.5px solid var(--track, #ADC3B9);
  border-radius:14px; padding:12px; font-family:var(--font); background:#fff;
}
.cs-form textarea:focus{ outline:none; border-color:var(--accent, #618D7C); }
.cs-error{ position:absolute; left:22px; bottom:2px; margin:0; color:#8e2a1f; font-size:.82rem; }
.cs-form{ position:relative; }
.cs-tools{
  grid-row:4;
  display:flex; align-items:center; gap:10px; padding:8px 16px;
  padding-bottom:calc(10px + var(--safe-bottom, 0px) + env(safe-area-inset-bottom, 0px));
  border-top:1px solid #F0EEE8;
}
.cs-tools .emoji-btn{ border:0; background:none; font-size:1.3rem; cursor:pointer; padding:4px; }
.cs-count{ margin-left:auto; font-size:.8rem; color:var(--sage2, #8FA99B); }

/* ============================================================
   Round-95i: the EDITORIAL incident panel (design final-media-02-hero).
   #stationPanel.ed — hero carousel / serif head / service-bordered
   description / hairline actions band / Comments·Timeline·Details tabs
   / pinned composer card. Stations and other panels keep the classic
   shell (the .ed class is added only by _openIncidentPanel).
   ============================================================ */
.station-panel.ed .sp-head{ background:none; padding:0; height:0; overflow:visible; z-index:6; }
.station-panel.ed .sp-head #spPill, .station-panel.ed .sp-head h2, .station-panel.ed .sp-head .sub{ display:none; }
.station-panel.ed .sp-close{
  top:14px; right:14px; background:#fff; color:var(--ink, #2C3B36); border:0;
  box-shadow:0 2px 10px rgba(0,0,0,.18);
}
.station-panel.ed #spBody{
  padding:0; min-height:0; background:var(--paper, #F3F4F1);
  display:grid; grid-template-rows:auto auto auto auto auto minmax(0,1fr) auto;
  overflow:hidden;
}
.ed-hero{ grid-row:1; position:relative; height:150px; overflow:hidden; cursor:pointer; }
.station-panel.ed .ed-hero img{ width:100%; height:100%; object-fit:cover; display:block; }
.ed-hero[hidden]{ display:none; }
.ed-play{ position:absolute; inset:0; display:flex; align-items:center; justify-content:center;
  font-size:2rem; color:#fff; text-shadow:0 2px 10px rgba(0,0,0,.6); pointer-events:none; }
.ed-hero-chips{ position:absolute; top:10px; left:12px; display:flex; gap:8px; }
.ed-mnav{ position:absolute; top:50%; transform:translateY(-50%); left:8px; width:30px; height:30px;
  border-radius:50%; border:0; background:rgba(255,255,255,.85); cursor:pointer; font-size:1rem; }
.ed-mnav.r{ left:auto; right:8px; }
.ed-dots{ position:absolute; bottom:8px; left:0; right:0; display:flex; gap:5px; justify-content:center; }
.ed-dots i{ width:7px; height:7px; border-radius:50%; background:rgba(255,255,255,.5); }
.ed-dots i.on{ background:#fff; }
.ed-chip{ font-size:.66rem; font-weight:800; letter-spacing:1px; border-radius:999px; padding:3px 11px;
  color:#fff; text-transform:uppercase; }
.ed-head{ grid-row:2; position:relative; padding:14px 18px 10px;
  border-bottom:1px solid rgba(44,59,54,.08); }
.has-hero .ed-head .ed-chips{ display:none; }      /* the hero overlay carries them */
.ed-chips{ display:flex; gap:8px; margin-bottom:8px; }
.ed-hl{ font-family:Georgia, "Times New Roman", serif; font-size:1.08rem; line-height:1.35;
  margin:0; padding-right:34px; color:var(--ink, #2C3B36); text-transform:none;
  display:-webkit-box; -webkit-line-clamp:2; -webkit-box-orient:vertical; overflow:hidden; }
.ed-meta{ font-size:.78rem; color:var(--ink-soft, #55635C); margin-top:5px; font-style:italic; }
.ed-src{ font-size:.76rem; color:var(--ink-soft, #55635C); margin-top:4px; }
.ed-src b{ color:var(--deep, #4F8577); }
.ed-desc{ grid-row:3; margin:10px 18px; font-size:.9rem; line-height:1.55; color:#3E4A44;
  border-left:3px solid var(--accent, #618D7C); padding-left:12px; cursor:pointer; }
.ed-desc .js-cmt-body{ display:-webkit-box; -webkit-line-clamp:4; -webkit-box-orient:vertical; overflow:hidden; }
.ed-desc.open .js-cmt-body{ display:block; -webkit-line-clamp:unset; max-height:40vh; overflow-y:auto; }
.ed-acts{ grid-row:4; margin:0 18px; padding:2px 0;
  border-top:1px solid rgba(44,59,54,.08); border-bottom:1px solid rgba(44,59,54,.08); }
.ed-acts .story-actions{ margin:6px 0; border:0; padding:0; }
.ed-tabs{ grid-row:5; display:flex; gap:18px; padding:9px 18px 0; border-bottom:1px solid #E7EBE6; }
.ed-tabs span{ padding-bottom:8px; font-size:.82rem; font-weight:700; color:var(--ink-soft, #55635C); cursor:pointer; }
.ed-tabs span.on{ color:var(--deep, #4F8577); border-bottom:2.5px solid var(--deep, #4F8577); }
.ed-panes{ grid-row:6; overflow-y:auto; overscroll-behavior:contain; -webkit-overflow-scrolling:touch;
  padding:12px 18px; }
.ed-pane{ display:none; } .ed-pane.on{ display:block; }
/* comments in the editorial voice: white shadowed avatars on a thread rail */
.station-panel.ed .cmt-thread{ position:relative; }
.station-panel.ed .cmt-thread::before{ content:""; position:absolute; left:15px; top:38px; bottom:6px;
  width:2px; background:#E4EAE4; }
.station-panel.ed .cmt-thread:last-child::before{ display:none; }
.station-panel.ed .cmt-av, .station-panel.ed .cmt-av-fallback{
  width:32px; height:32px; flex:0 0 32px; border-radius:50%;
  box-shadow:0 2px 8px rgba(0,0,0,.1); position:relative; z-index:1;
}
.station-panel.ed .cmt-av-fallback{ background:#fff; color:var(--deep, #4F8577); }
/* timeline pane: the same rail language with dots */
.station-panel.ed .tl-row{ position:relative; padding:0 0 14px 30px; font-size:.86rem; }
.station-panel.ed .tl-row::before{ content:""; position:absolute; left:6px; top:5px; width:10px; height:10px;
  border-radius:50%; background:#fff; box-shadow:0 0 0 2px var(--accent, #618D7C); }
.station-panel.ed .tl-row::after{ content:""; position:absolute; left:10px; top:18px; bottom:0; width:2px; background:#E4EAE4; }
.station-panel.ed .tl-row:last-child::after{ display:none; }
/* the pinned composer: the floating white card */
.ed-compose{ grid-row:7; margin:0 12px 12px !important; padding:10px 12px !important;
  background:#fff; border-radius:16px; box-shadow:0 8px 26px rgba(44,59,54,.14); }
.ed-compose textarea{ width:100%; min-height:48px; border:0 !important; padding:4px 2px !important;
  background:none !important; font-family:var(--font); resize:none; }
.ed-compose textarea:focus{ outline:none; }
.ed-tools{ display:flex; align-items:center; justify-content:space-between; gap:10px; margin-top:2px; }
.ed-tools .emoji-btn{ border:0; background:none; font-size:1.2rem; cursor:pointer; padding:0; }
.ed-count{ color:var(--ink-soft, #55635C); font-size:.72rem; }
.ed-compose .compose-send{ position:static; border-radius:999px; padding:8px 20px; }
.ed-compose .cmt-replying{ margin-bottom:6px; }
html.in-app .station-panel.ed .ed-compose{ margin-bottom:calc(12px + var(--safe-bottom, 0px)) !important; }

/* ---- Round-10: emoji panel, role badges, comment edit ---- */
.emoji-panel{
  position:fixed; z-index:3200; display:grid; grid-template-columns:repeat(8, 1fr); gap:2px;
  width:264px; background:var(--paper, #FBFBF8); border:1.5px solid var(--track, #ADC3B9);
  border-radius:14px; padding:8px; box-shadow:0 14px 36px rgba(44,59,54,.28);
}
.emoji-panel button{
  border:0; background:none; cursor:pointer; font-size:1.15rem; padding:3px; border-radius:8px;
}
.emoji-panel button:hover{ background:var(--mintbg, #EAF1EC); }

.role-badge{ cursor:help; font-size:.85rem; }
.cmt-error{ color:#8e2a1f; font-size:.82rem; margin-top:6px; }

/* round-12b: custom from → to date range inside the pin-filters popover */
.pin-filters .tf-range{
  border-top:1px solid var(--track, #ADC3B9); margin-top:6px; padding-top:9px;
  display:flex; flex-direction:column; gap:7px;
}
.pin-filters .tf-lab{ display:flex; align-items:center; justify-content:space-between; gap:10px; font-size:.85rem; }
   icon and highlight color inside the native popup (color-scheme + accent-color) */
.pin-filters .tf-lab input{
  border:1.5px solid var(--track, #ADC3B9); border-radius:8px; padding:5px 7px;
  font-family:var(--font); font-size:.78rem; font-weight:600;
  background:var(--cream, #F3EFE7); color:var(--deep, #4F8577); cursor:pointer;
  color-scheme:light; accent-color:var(--accent, #618D7C);
}
.pin-filters .tf-lab input:focus{ outline:none; border-color:var(--accent, #618D7C); background:#fff; }
.pin-filters .tf-lab input::-webkit-calendar-picker-indicator{
  cursor:pointer; opacity:.9;
  /* tint the black glyph to the sage accent (#618D7C) */
  filter:invert(53%) sepia(11%) saturate(760%) hue-rotate(107deg) brightness(93%) contrast(87%);
}
.pin-filters .tf-lab input::-webkit-datetime-edit-fields-wrapper{ color:var(--deep, #4F8577); }
.pin-filters .tf-lab input::-webkit-datetime-edit-hour-field:focus,
.pin-filters .tf-lab input::-webkit-datetime-edit-minute-field:focus,
.pin-filters .tf-lab input::-webkit-datetime-edit-day-field:focus,
.pin-filters .tf-lab input::-webkit-datetime-edit-month-field:focus,
.pin-filters .tf-lab input::-webkit-datetime-edit-year-field:focus{
  background:var(--accent, #618D7C); color:#fff; border-radius:4px;
}
.pin-filters [data-range-clear]{
  align-self:flex-start; border:1.5px solid var(--track, #ADC3B9); border-radius:999px;
  background:none; color:var(--ink-soft, #55635C); font-family:var(--font); font-size:.74rem;
  font-weight:700; padding:4px 10px; cursor:pointer;
}
.pin-filters [data-range-clear]:hover{ border-color:var(--accent, #618D7C); color:var(--deep, #4F8577); }

/* round-11: pin-filter popover above the time bands */
.pin-filters{
  position:fixed; bottom:64px; left:50%; transform:translateX(-50%); z-index:960;
  background:var(--paper, #FBFBF8); border:1.5px solid var(--track, #ADC3B9); border-radius:14px;
  padding:12px 16px; box-shadow:0 12px 34px rgba(44,59,54,.28);
  display:flex; flex-direction:column; gap:7px; min-width:240px;
}
.pin-filters h4{ font-size:.72rem; letter-spacing:1.5px; text-transform:uppercase; color:var(--sage2, #8FA99B); }
.pin-filters label{ display:flex; align-items:center; gap:8px; font-size:.9rem; font-weight:600; cursor:pointer; }
.pin-filters label input{ width:auto; }
.pin-filters small{ color:var(--ink-soft, #55635C); font-size:.74rem; max-width:240px; }
@media (max-width: 720px){
  .pin-filters{ bottom:70px; left:50%; transform:translateX(-50%); max-width:calc(100vw - 20px); }
}
.cmt-edit{
  width:100%; margin:6px 0; padding:8px 10px; border:1.5px solid var(--accent, #618D7C);
  border-radius:10px; font-family:var(--font); font-size:.92rem; min-height:60px;
}

/* ---- Country tabs on About/Learn/Partners (round-9) ---- */
.country-tabs{
  display:flex; align-items:center; gap:8px; flex-wrap:wrap;
  background:var(--paper, #FBFBF8); border:1.5px solid var(--track, #ADC3B9);
  border-radius:14px; padding:10px 14px;
}
.ct-label{ font-size:.8rem; font-weight:800; letter-spacing:.6px; text-transform:uppercase; color:var(--sage2, #8FA99B); }
.ct-tab{
  padding:6px 14px; border-radius:999px; border:1.5px solid var(--track, #ADC3B9);
  font-size:.88rem; font-weight:700; color:var(--ink-soft, #55635C);
}
.ct-tab:hover{ border-color:var(--accent, #618D7C); text-decoration:none; }
.ct-tab.on{ background:var(--accent, #618D7C); border-color:var(--accent, #618D7C); color:#fff; }
.ct-note{ font-size:.78rem; color:var(--ink-soft, #55635C); flex-basis:100%; }

/* Round-83: pills on desktop, type-ahead on mobile.
   152 enabled countries render 152 pills. On a wide screen that is a useful glance at who is
   live; on a phone it is a wall of wrapped chips that pushes the actual page below several
   screens of navigation. The picker is the same control the admin scope switcher uses, so it
   is one thing to learn rather than two. */
.ct-pick{ display:none; }
@media (max-width: 720px){
  .ct-pick{ display:block; flex:1 1 100%; min-width:0; }
  .country-tabs .ct-tab{ display:none; }
  .country-tabs{ gap:6px; }
  /* Room to read a country name, and never wider than the screen. */
  .ct-pick .cpick-list{ width:100%; max-width:calc(100vw - 40px); }
}

/* rendered per-country markdown */
.md-body h1, .md-body h2, .md-body h3{ color:var(--ink, #2C3B36); margin:18px 0 8px; }
.md-body p{ margin:8px 0; color:#3d4c46; }
.md-body ul, .md-body ol{ margin:8px 0 8px 22px; }
.md-body a{ color:var(--deep, #4F8577); }
.md-body img{ border-radius:12px; }

/* ---- Virtualized-list loading skeleton (round-9) ---- */
.skeleton{
  background:linear-gradient(90deg, var(--mintbg, #EAF1EC) 25%, #f5f8f5 37%, var(--mintbg, #EAF1EC) 63%);
  background-size:400% 100%;
  animation:skeleton-shimmer 1.3s ease infinite;
  border-radius:12px; border:1px solid var(--track, #ADC3B9);
}
@keyframes skeleton-shimmer{ 0%{ background-position:100% 50%; } 100%{ background-position:0 50%; } }

/* ---- Private messaging (§60) ---- */
/* Round-94: design 02 "messenger warm" — WhatsApp-familiar messages. White list with large
   avatars and green time stamps; the thread sits on a patterned warm wallpaper with flat
   shadowed bubbles (white theirs, pale-green mine). Same markup and handlers as before. */
/* Full bleed below the nav: the page IS the messenger. --header-h already includes the
   sign-up bar when it shows; dvh keeps mobile URL-bar resizing honest. */
.msg-page{ padding:var(--header-h) 0 0; }   /* clear the fixed header, nothing else */
body:has(.msg-page) .site-footer, body:has(.msg-page) .cov-strip{ display:none; }
.msg-shell{
  display:grid; grid-template-columns:340px 1fr; gap:0; align-items:stretch;
  height:calc(100vh - var(--header-h)); height:calc(100dvh - var(--header-h)); border:0; border-radius:0; overflow:hidden;
  background:#fff;
  /* the mockup's frame: centred on wide screens, the page paper showing at the sides */
  max-width:1180px; margin:0 auto;
}
/* no side borders on the shell — the white page does the framing */
.msg-head{ padding:16px 16px 0; }
.msg-head h1{ margin:0; font-size:1.35rem; }
.msg-head .sub{ font-size:.8rem; color:var(--ink-soft, #55635C); margin-top:1px; }
.msg-search{ padding:12px 12px 8px; }
.msg-search input{
  width:100%; border:0; border-radius:10px; padding:9px 14px; font-family:var(--font);
  font-size:.9rem; background:var(--bg, #F3F4F1); color:var(--ink, #2C3B36);
}
.msg-search input:focus{ outline:2px solid var(--accent, #618D7C); }
.msg-list{
  background:#fff; border-right:1px solid #F0EEE8; border-radius:0;
  overflow-y:auto;
}
.msg-row{
  display:flex; align-items:center; gap:12px; padding:12px 14px;
  border-bottom:1px solid #F0EEE8; color:var(--ink, #2C3B36);
}
.msg-row .cmt-av, .msg-row .cmt-av-fallback{ width:50px; height:50px; flex:0 0 50px; font-size:1.05rem; }
.msg-row:hover{ background:#F7F8F5; text-decoration:none; }
.msg-row.sel{ background:#F2F6F2; }
.msg-meta{ flex:1; min-width:0; display:flex; flex-direction:column; gap:1px; }
.msg-meta small{ color:var(--ink-soft, #55635C); font-size:.84rem; overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.msg-row.unread .msg-meta b, .msg-row.unread .msg-meta small{ color:var(--ink, #2C3B36); font-weight:700; }
.msg-side{ display:flex; flex-direction:column; align-items:flex-end; gap:5px; flex:0 0 auto; }
.msg-when{ font-size:.72rem; font-weight:700; color:var(--accent, #618D7C); }
.msg-dot{ width:12px; height:12px; border-radius:50%; background:#C0392B; flex:0 0 auto; }

.msg-thread{
  border:0; border-radius:0; display:flex; flex-direction:column; min-height:0;
  background:var(--bg, #F3F4F1) url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="140" height="140" opacity="0.06"><path d="M20 20l14 0M60 50a10 10 0 1 0 20 0M100 100l12 12M30 110a8 8 0 1 1 16 0" stroke="%232C3B36" fill="none" stroke-width="3"/></svg>');
}
.msg-thread-head{
  display:flex; align-items:center; gap:10px; padding:10px 16px;
  border-bottom:1px solid #F0EEE8; background:#fff;
  position:sticky; top:0; z-index:3;   /* stays put however the page moves on phones */
}
/* a long counterpart name never wraps — one line, ellipsised */
.msg-thread-head > a{ flex:1 1 auto; min-width:0; }
.msg-head-id{ min-width:0; flex:1; }
.msg-head-id b{ white-space:nowrap; overflow:hidden; text-overflow:ellipsis; display:block; }
.msg-back{ display:none; padding:2px 8px 2px 0; color:var(--deep, #4F8577); }
.msg-back svg{ width:36px; height:36px; display:block; }  /* thin 1.6 stroke chevron */
/* the mockup's icobtn: 40px circle, hairline #E3E6E1 border, the ⋮ glyph in ink */
.msg-kebab{
  width:40px; height:40px; border-radius:50%; border:1.5px solid #E3E6E1; background:#fff;
  display:inline-flex; align-items:center; justify-content:center; color:var(--ink, #2C3B36); cursor:pointer;
  font-size:1.25rem; font-weight:700; line-height:1;
}
.msg-kebab:hover{ background:var(--mintbg, #EAF1EC); }
.msg-menu-wrap{ position:relative; display:inline-flex; }
.msg-menu-backdrop{ position:fixed; inset:0; background:transparent; border:0; z-index:1290; cursor:default; }
.msg-menu{
  position:absolute; top:calc(100% + 6px); right:0; z-index:1300; min-width:180px;
  background:#fff; border:1px solid var(--track, #ADC3B9); border-radius:12px;
  box-shadow:0 14px 36px rgba(44,59,54,.22); padding:6px; display:flex; flex-direction:column;
}
.msg-menu > *{
  display:block; text-align:left; border:0; background:transparent; padding:9px 12px;
  border-radius:8px; font-family:var(--font); font-size:.9rem; color:var(--ink, #2C3B36); cursor:pointer;
}
.msg-menu > *:hover{ background:var(--mintbg, #EAF1EC); text-decoration:none; }
.msg-tag{
  display:block; font-size:.72rem; font-weight:800; line-height:1.25;
  white-space:nowrap; overflow:hidden; text-overflow:ellipsis;
}
.tag-agency{ color:#C0392B; }
.tag-platform{ color:var(--deep, #4F8577); }
.tag-community{ color:#6B5B95; }
.msg-head-id{ display:flex; flex-direction:column; min-width:0; }
/* the chat header's avatar matches the sidebar rows */
.msg-thread-head .cmt-av, .msg-thread-head .cmt-av-fallback{ width:50px; height:50px; flex:0 0 50px; font-size:1.05rem; border-radius:50%; object-fit:cover; }
.msg-head-id .msg-tag{ font-size:.7rem; }
/* min-height:0 is load-bearing: a flex item's default minimum is its CONTENT height, so
   without it the message list refuses to shrink when the keyboard shrinks the shell and
   the composer is pushed out through overflow:hidden — "the textarea doesn't show". The
   bug was invisible until the keyboard bridge made the shell actually shrink in-app. */
.msg-scroll{ flex:1; min-height:0; overflow-y:auto; padding:18px; display:flex; flex-direction:column; gap:8px; }
.msg-bubble{
  max-width:75%; background:#fff; border:none; box-shadow:0 1px 1px rgba(0,0,0,.12);
  border-radius:4px 10px 10px 10px; padding:8px 12px; align-self:flex-start;
}
.msg-bubble.mine{
  background:#DFF0DC; color:var(--ink, #2C3B36); border:none;
  border-radius:10px 4px 10px 10px; align-self:flex-end;
}
.msg-bubble p{ font-size:.95rem; white-space:pre-wrap; word-break:break-word; }
.msg-bubble small{ display:block; margin-top:3px; font-size:.68rem; color:#5B7A5B; text-align:right; }
.msg-compose{ display:flex; gap:8px; padding:12px 14px; border-top:1px solid #F0EEE8; background:#fff; }
.msg-compose textarea{
  flex:1; min-width:0; resize:none; border:1.5px solid #F0EEE8; border-radius:14px;
  padding:9px 12px; font-family:var(--font); font-size:.95rem; background:var(--bg, #F3F4F1);
}
.msg-compose textarea:focus{ outline:none; border-color:var(--accent, #618D7C); background:#fff; }
.msg-thread .compose{ background:#fff; }
/* the messenger owns the viewport — the DOCUMENT never scrolls here, so the fixed nav and the
   thread head cannot be scrolled away on phones. Panes scroll internally. */
html.pg-msg body{ height:100vh; overflow:hidden; background:#fff; }   /* :has twin (pre-105) */
body:has(.msg-page){ height:100dvh; overflow:hidden; background:#fff; }
/* Round-94d: nothing ever scrolls under the header on the messenger (the shell starts below
   it), so its backdrop blur has nothing to blur — it was pure GPU cost, re-rendered on every
   glued frame while the keyboard moved. Solid paper instead. */
html.pg-msg .site-header{ backdrop-filter:none; -webkit-backdrop-filter:none; background:rgba(243,244,241,.98); }
body:has(.msg-page) .site-header{ backdrop-filter:none; -webkit-backdrop-filter:none; background:rgba(243,244,241,.98); }  /* the PAGE around the centred shell is white; the thread keeps its warm wallpaper */
html:has(.msg-page){ background:#fff; }
.msg-empty{ flex:1; display:flex; align-items:center; justify-content:center; }
@media (max-width: 820px){
  .msg-shell{ grid-template-columns:1fr; }
  /* Round-94d: THE iOS FOCUS-ZOOM RULE, SITE-WIDE. iOS zooms the page toward any focused
   field whose font is under 16px, and that zoom is also what drags the fixed nav off-screen.
   Round-94 fixed it for ONE composer (.msg-thread .compose) and the audit then found 13 more
   sub-16px controls — Ada's drawer (.compose at .92rem), comment boxes, story compose, every
   search input. Chasing them selector by selector loses to the next new control, so: on any
   touch/small screen, every text-entry control is at least 16px. Desktop keeps its compact
   forms. Checkboxes/radios/ranges/files never summon the keyboard and keep their looks. */
@media (max-width:900px), (pointer:coarse){
  textarea, select,
  input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]):not([type="color"]):not([type="button"]):not([type="submit"]){
    font-size:16px !important;
  }
}

/* iOS Safari zooms any focused field under 16px/* iOS Safari zooms any focused field under 16px — the one cause of "the page enlarges when
     I tap the message box". 16px exactly. The thread's composer is the GENERIC .compose (the
     first fix targeted .msg-compose, which this page never uses — hence the zoom survived). */
  .msg-thread .compose textarea, .msg-search input{ font-size:16px !important; }
  /* pin the shell itself so iOS keyboard panning has no document to drag around. Its height
     follows the VISUAL viewport (--vvh, set from window.visualViewport in site.js): when the
     keyboard opens the shell shrinks with it, keeping the composer above the keys. */
  .msg-page .msg-shell{ position:fixed; left:0; right:0; top:var(--header-h); bottom:auto; max-width:none;
    height:calc(var(--vvh, 100dvh) - var(--header-h)); z-index:2600; }
  /* the chat owns the phone screen — Ada's FAB (z 2500) stays behind it, never over the
     composer or send button */
  html.pg-msg .asst-fab{ z-index:2400; }
  body:has(.msg-page) .asst-fab{ z-index:2400; }
  /* Round-94d: THE SHAKE. site.js glues the FAB to the visual viewport by writing its
     transform every frame the keyboard moves — and the base rule transitions transform over
     .15s, so each write started an animation toward a target that had already moved. An
     element permanently chasing a moving target with a 150ms lag IS the wobble. No transform
     transition where the glue drives it. */
  body:has(.msg-page) .asst-fab{ transition:box-shadow .15s; }
  .msg-shell.thread-open .msg-list{ display:none; }
  .msg-shell:not(.thread-open) .msg-thread{ display:none; }  /* full-bleed: the empty pane is noise on a phone */
  .msg-back{ display:inline-flex; }
}
/* Round-94: the in-app shell must follow the VISUAL viewport too — this exact rule outranked
   the keyboard fix (html.in-app adds specificity) and kept the composer under the keys in the
   WebView. The home-bar clearance moves to the composer's padding, where an open keyboard
   simply renders it harmless. */
html.in-app .msg-shell{ height:calc(var(--vvh, 100dvh) - var(--header-h)); }
html.in-app .msg-thread .compose{ padding-bottom:calc(12px + var(--safe-bottom, 0px)); }

/* §58 "not live here yet" banner */
.geo-banner{
  position:fixed; top:calc(var(--header-h) + 8px); left:50%; transform:translateX(-50%);
  z-index:1150; display:flex; align-items:center; gap:12px; flex-wrap:wrap;
  max-width:min(640px, calc(100vw - 20px));
  background:rgba(243,244,241,.97); border:1.5px solid var(--track, #ADC3B9); border-radius:14px;
  padding:10px 14px; box-shadow:0 8px 26px rgba(44,59,54,.25); font-size:.92rem;
}
.geo-banner .geo-close{
  background:none; border:0; cursor:pointer; color:var(--ink-soft, #55635C);
  font-size:1rem; padding:4px 6px; border-radius:6px;
}
.geo-banner .geo-close:hover{ background:rgba(97,141,124,.12); }
@media (max-width: 1000px){
  .geo-banner{ top:calc(var(--header-h) + 52px); } /* below the chips row */
}

/* SafeNet subject pins: profile photo (or emoji) in a ringed circle */
.sn-pin{
  width:44px; height:44px; border-radius:50%; overflow:hidden;
  background:#F3F4F1; border:3px solid var(--deep, #4F8577);
  box-shadow:0 3px 12px rgba(44,59,54,.45);
  display:flex; align-items:center; justify-content:center; font-size:22px;
}
.sn-pin img{ width:100%; height:100%; object-fit:cover; }
.sn-pin.asset{ border-color:var(--tan, #BC8A52); }
.sn-pin.danger{ border-color:#C0392B; animation:sevpulse 1.6s ease-in-out infinite; }
/* Round-85: freshness lives in the RING, extending this established pin rather than a second
   competing .sn-pin block (which is what the first attempt did — p4.css loads later and simply
   overrode it). A glance at the map now answers the same question the list answers in words. */
.sn-pin[style*="--ring"]{ border-color:var(--ring); }
.sn-live{ animation:sn-pulse 2.2s ease-out infinite; }
@keyframes sn-pulse{
  0%   { box-shadow:0 3px 12px rgba(44,59,54,.45), 0 0 0 0 rgba(46,158,91,.5); }
  70%  { box-shadow:0 3px 12px rgba(44,59,54,.45), 0 0 0 14px rgba(46,158,91,0); }
  100% { box-shadow:0 3px 12px rgba(44,59,54,.45), 0 0 0 0 rgba(46,158,91,0); }
}
@media (prefers-reduced-motion: reduce){ .sn-live{ animation:none; } }
/* Paused says so in the pin itself, not only in a popup nobody opens. */
.sn-paused{ filter:grayscale(1); border-style:dashed !important; }
.map-card.min{ width:auto; right:auto; padding:10px 12px; } /* #weatherCard keeps its right anchor (id wins) */
.map-card.min .card-body{ display:none; }
.map-card.min .card-head h2, .map-card.min .card-head h3, .map-card.min .card-caret{ display:none; }
.map-card.min .card-head{ gap:0; }
/* round-15c/15e: minimized chips in the right column are 44px circles (like the ✨
   button), and expanding/collapsing MORPHS between chip and card — width, height and
   corner radius animate while the content fades in a beat later. */
.right-stack .map-card.collapsible{
  width:250px; max-height:480px; overflow:hidden; flex:0 0 auto;
  transition:width .32s cubic-bezier(.2,.8,.25,1), max-height .32s cubic-bezier(.2,.8,.25,1),
             border-radius .32s ease, padding .32s ease;
}
.right-stack .map-card.collapsible .card-body{
  display:block; opacity:1; transition:opacity .2s ease .12s;
}
.right-stack .map-card.collapsible .card-head h2,
.right-stack .map-card.collapsible .card-head h3,
.right-stack .map-card.collapsible .card-caret{
  opacity:1; white-space:nowrap; transition:opacity .18s ease .12s;
}
.right-stack .map-card.min{
  width:44px; max-height:44px; padding:12px; border-radius:50%;
}
.right-stack .map-card.min .card-body{ display:block; opacity:0; transition-delay:0s; }
.right-stack .map-card.min .card-head h2,
.right-stack .map-card.min .card-head h3,
.right-stack .map-card.min .card-caret{ display:inline-flex; opacity:0; transition-delay:0s; }
.right-stack .map-card.min .card-head{ gap:8px; }
.right-stack .map-card.min .card-ico svg{ width:20px; height:20px; }

/* ---- Map type inside the grid menu (populated by map-engine on the live map) ---- */
.grid-menu .grid-base{ display:none; grid-column:1/-1; border-top:1px solid var(--track, #ADC3B9); margin-top:6px; padding-top:10px; }
.grid-menu .grid-base.has-base{ display:block; }   /* :has twin (pre-105 engines) */
.grid-menu .grid-base:has(#baseSwitch:not(:empty)){ display:block; }
.grid-base-title{
  font-size:.68rem; font-weight:800; letter-spacing:1.5px; text-transform:uppercase;
  color:var(--sage2, #8FA99B); margin-bottom:6px;
}
.grid-menu .base-switch{
  position:static; transform:none; display:flex; box-shadow:none;
  border:1px solid var(--track, #ADC3B9); border-radius:999px; overflow:hidden;
}
.grid-menu .base-switch button{ flex:1; background:#FBFBF8; color:var(--ink-soft, #55635C); padding:8px 4px; font-size:.78rem; }
.grid-menu .base-switch button + button{ border-left:1px solid var(--track, #ADC3B9); }
.grid-menu .base-switch button.on{ background:var(--accent, #618D7C); color:#fff; }

/* chips row drops below the bar once the header gets crowded.

   Round-83: raised from 1000px to 1200px. The desktop row sits BETWEEN the search box and the
   right-hand card column — left:calc(400px + 42px), right:370px — so its width is whatever is
   left over. On an iPad Pro in portrait (1024px) that leftover is 1024 - 442 - 370 = 212px: the
   service chips were squeezed to almost nothing, and the "N layers hidden" button (which lives
   inside .svc-wrap) was pushed out of the row and up against "Report an issue" in the header.
   Both reported symptoms are the same 212px.

   1200 is where the leftover first exceeds ~390px, which is the point the row can show several
   chips and still breathe. Below that, the full-width row under the header is simply better. */
@media (max-width: 1200px){
  .svc-wrap{ top:calc(var(--header-h) + 6px); left:8px; right:8px; }
  .net-stats{ top:calc(var(--header-h) + 56px); }
  /* Round-94 (revised twice): zoom-only container lives bottom-left on every width now —
     see style.css .map-actions. No per-breakpoint override needed. */
  .hidden-layers{ margin-top:4px; }
}

/* ---- RTL (Arabic) corrections: flip physical positioning flexbox/logic can't ---- */
/* Leaflet's overlay geometry ASSUMES LTR: .leaflet-tooltip/.leaflet-popup are position:absolute
   with no left/top and are placed purely by transform, so their origin is the element's static
   position. Under dir=rtl that static position resolves from the RIGHT edge, and every tooltip
   lands horizontally offset from its pin. Leaflet's own CSS never sets `direction`, so it
   inherits rtl from <html>. Keep the map's internals LTR and flip only the text back. */
html[dir="rtl"] .leaflet-container{ direction:ltr; }
html[dir="rtl"] .leaflet-tooltip,
html[dir="rtl"] .leaflet-popup-content{ direction:rtl; text-align:right; }
/* nav: push the icon cluster to the visual start (which is the LEFT in RTL) */
html[dir="rtl"] .nav-right{ margin-left:0; margin-right:auto; }
html[dir="rtl"] .card-caret{ margin-left:0; margin-right:auto; }
/* live-map service-chip row sits to the LEFT of the (now right-aligned) search box */
html[dir="rtl"] .svc-wrap{
  left:370px; right:calc(var(--msearch-w, 400px) + 42px);
}
html[dir="rtl"] .svc-pills{ justify-content:flex-start; }
/* overflow arrows: mirror both the gaps and the glyphs */
html[dir="rtl"] .pill-arrow.left{ margin-right:0; margin-left:6px; }
html[dir="rtl"] .pill-arrow.right{ margin-left:0; margin-right:6px; }
html[dir="rtl"] .pill-arrow.left svg{ transform:rotate(180deg); }
html[dir="rtl"] .pill-arrow.right svg{ transform:none; }
/* right-hand map card stack + assistant FAB move to the LEFT edge */
html[dir="rtl"] .right-stack{ right:auto; left:14px; align-items:flex-start; padding-left:0; padding-right:6px; }
html[dir="rtl"] body:has(#map) .asst-fab{ right:auto; left:16px; }
html[dir="rtl"] .msearch{ padding:5px 10px 5px 8px; }
/* omnibox result dropdown + search text align to the right */
html[dir="rtl"] .omni-results{ text-align:right; }
@media (max-width: 720px){
  html[dir="rtl"] .svc-wrap{ left:8px; right:8px; }
}

/* build r14b-2 */

/* OAuth verification / branding: app name + privacy/terms, styled like a Leaflet/OSM/Carto
   attribution bar and flush to the bottom-left corner of the map (no gap to the edges). */
.map-legal{
  position:fixed; left:0; bottom:0; margin:0; z-index:850;
  display:flex; align-items:center; gap:7px;
  padding:2px 8px; background:rgba(251,251,248,.82);
  border-radius:0 5px 0 0; font-size:11px; line-height:1.5; color:#3a4a44;
}
.map-legal .ml-brand{ font-weight:700; color:var(--deep, #4F8577); }
.map-legal a{ color:#3a4a44; text-decoration:none; }
.map-legal a:hover{ text-decoration:underline; }

/* Service-coloured cluster pill: each per-service cluster shows its own colour + count, so an area
   with several services reads as a group of differently-coloured count pills. */
.svc-cluster{
  display:flex; align-items:center; justify-content:center;
  min-width:28px; height:28px; padding:0 7px; border-radius:999px;
  color:#F3F4F1; font-weight:800; font-size:.82rem; font-family:var(--font);
  box-shadow:0 3px 10px rgba(44,59,54,.4); border:2px solid rgba(255,255,255,.65);
}
.svc-cluster.unv{ background:#FBFBF8; border-style:dashed; box-shadow:0 3px 10px rgba(44,59,54,.25); }

/* Round-38: Ada map-layer chips — bottom-left above the legal strip; click toggles a layer */
#adaLayers{
  position:fixed; left:8px; bottom:32px; z-index:860;
  display:flex; flex-wrap:wrap; gap:6px; max-width:min(60vw, 420px);
}
.ada-chip{
  border:1.5px solid var(--accent, #618D7C); border-radius:999px; cursor:pointer;
  background:rgba(251,251,248,.94); color:var(--deep, #4F8577);
  font-family:var(--font); font-weight:700; font-size:.72rem; padding:4px 10px;
  box-shadow:0 3px 10px rgba(44,59,54,.18);
  max-width:220px; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
.ada-chip:not(.on){ opacity:.45; border-style:dashed; }

/* Round-38: tighter Ada toolbar chips + theme-coloured icons */
.asst-toolbar .chip{ padding:4px 10px; display:inline-flex; align-items:center; gap:6px; }
.asst-toolbar .chip svg{ width:14px; height:14px; stroke:var(--deep, #4F8577); flex:0 0 auto; }
.asst-toolbar .chip.sel svg{ stroke:#fff; }

/* Round-91: the community's own geofence IS the hero background.
   It used to sit in a card beside the intro, which made the page's most distinctive thing — the
   actual shape of the place — compete with the text for a column. Behind the words it does the
   job a hero image does, and it is the real boundary rather than a stock photograph.

   The map is inert: aquaBoundary builds it with every interaction handler off, and pointer-events
   are dropped so it cannot swallow a tap meant for a button underneath the cursor. */
.cpage-hero{ display:grid; gap:24px; align-items:center; position:relative; z-index:1; }
.cpage-boundary{
  position:absolute; inset:0; width:100%; height:100%; z-index:0;
  pointer-events:none; opacity:.55; filter:saturate(.75);
}
/* Text over a map needs a floor under it. The veil is the hero's own gradient at high alpha down
   the left, fading right, so the words keep their contrast while the shape stays legible. */
.cpage-veil{
  position:absolute; inset:0; z-index:0; pointer-events:none;
  /* Round-91b/c: lightened 30% twice over — .94/.86/.55/.42 → .66/.60/.39/.29 → .46/.42/.27/.20. The contrast
     the text lost is given back by a shadow on the text itself rather than by hiding the map —
     a veil dark enough to guarantee legibility everywhere had made the map decorative. */
  background:
    linear-gradient(100deg, rgba(44,59,54,.46) 0%, rgba(44,59,54,.42) 38%, rgba(44,59,54,.27) 70%, rgba(44,59,54,.20) 100%),
    radial-gradient(120% 80% at 15% 40%, rgba(44,59,54,.27), transparent 70%);
}
@media (max-width: 899px){
  /* On a phone the text fills the width, so the veil has to be flat rather than directional. */
  .cpage-veil{ background:linear-gradient(180deg, rgba(44,59,54,.43), rgba(44,59,54,.46)); }
  .cpage-boundary{ opacity:.45; }
}

/* The shadow does the work the veil used to. Two layers: a tight one for edge definition against
   a light tile, and a wide soft one that darkens the area immediately around the glyphs, which is
   what keeps text readable over a busy map rather than over a flat colour. */
/* .cset-hero is the settings header: the same layering and shadows, but NOT .cpage-hero's grid,
   which would space a stack of four short lines out like columns. */
.cset-hero{ position:relative; z-index:1; }
.cpage-hero h1, .cpage-hero .ctype, .cpage-hero .lead, .cpage-hero .kicker,
.cset-hero h1, .cset-hero .lead, .cset-hero .kicker{
  text-shadow:0 1px 2px rgba(20,28,25,.85), 0 2px 10px rgba(20,28,25,.75), 0 4px 22px rgba(20,28,25,.55);
}
/* Buttons carry their own background or border, so a shadow on their label only muddies it. */
.cpage-hero .btn, .cset-hero .btn{ text-shadow:none; }

/* Round-91: the type line under the name was var(--sage2) on the dark hero — near enough to the
   background to be unreadable. Cream is the hero's own light tone. */
.cpage-hero .ctype{ color:var(--cream, #F1EBE4) !important; opacity:.92; }

/* Round-91: the safety score, in a card of its own above the counts. It is the one number on the
   page that is a judgement rather than a tally, and the factors under it are what makes it
   actionable rather than a rating. */
.cpage-score{
  display:flex; flex-wrap:wrap; align-items:center; gap:14px 22px;
  background:var(--paper, #F3F4F1); border-radius:var(--radius, 14px);
  border-left:6px solid var(--score, #4F8577); padding:16px 20px; margin-bottom:16px;
  box-shadow:var(--shadow-sm, 0 6px 18px rgba(44,59,54,.08));
}
.cpage-score .n{ font-size:2.1rem; font-weight:800; line-height:1; color:var(--score, #4F8577); }
.cpage-score .band{ font-weight:800; color:var(--score, #4F8577); letter-spacing:.3px; }
.cpage-score .lbl{ font-size:.85rem; color:var(--ink-soft, #5A6B63); }
.cpage-score .why{ flex:1 1 260px; font-size:.85rem; color:var(--ink-soft, #5A6B63); margin:0; }
/* Round-39b: community-page badges read better on the hero gradient with a solid white base */
.cpage-hero .pill{ background:#fff; }

/* Round-40: inline share row on story cards (reuses the map panel's .share-row buttons) */
.story-share{ margin-top:10px; padding-top:10px; border-top:1px dashed var(--track, #ADC3B9); }
/* story action icons follow the theme (silhouette technique) instead of full-colour emoji */
.story-src{ display:block; font-size:.8rem; color:var(--ink-soft, #55635C); margin-top:6px; }
.story-act .ico-t{ text-shadow:0 0 0 var(--deep, #4F8577); }
/* round-71f: selected buttons have the ACCENT background — an accent silhouette vanished
   into it (invisible like/dislike icons); white matches the button's text (round-57 rule) */
.story-act.on .ico-t{ text-shadow:0 0 0 #fff; }
/* round-71d: outline SVG glyphs inside action/share buttons follow the button colour */
.ico-svg{ width:15px; height:15px; vertical-align:-2px; display:inline-block; }
.share-native .ico-svg{ width:14px; height:14px; }

/* ── round-80: country type-ahead picker ────────────────────────────────────
   Opening the platform worldwide made every country control a 142-item list.
   Reuses the form-field look so it sits naturally beside other inputs. */
.cpick { position: relative; }
.cpick-input { width: 100%; }
.cpick-list {
  position: absolute; z-index: 60; left: 0; right: 0; top: calc(100% + 4px);
  margin: 0; padding: 4px; list-style: none;
  max-height: 280px; overflow-y: auto;
  background: var(--surface, #fff); border: 1px solid var(--track);
  border-radius: 10px; box-shadow: 0 10px 28px rgba(0,0,0,.18);
}
.cpick-opt {
  display: flex; align-items: baseline; gap: 8px;
  padding: 7px 10px; border-radius: 7px; cursor: pointer;
}
.cpick-opt small { color: var(--ink-soft); font-size: .78rem; }
.cpick-opt:hover, .cpick-opt.on { background: var(--track); }
.cpick-off {
  margin-left: auto; font-size: .72rem; text-transform: uppercase;
  letter-spacing: .04em; color: var(--ink-soft);
}
.cpick-empty { padding: 10px; color: var(--ink-soft); font-size: .88rem; }

/* ── round-80: no focus ring on headings after navigation ────────────────────
   Blazor's enhanced navigation moves focus to the page heading so screen readers
   announce the new page. That is correct behaviour, but the browser then paints a
   :focus-visible ring on an element the user never tabbed to, so every page load
   appeared to start with an outlined H1. Suppress it ONLY for programmatically
   focused, non-interactive targets — real controls keep their focus ring. */
h1:focus, h1:focus-visible,
[tabindex="-1"]:focus, [tabindex="-1"]:focus-visible { outline: none; box-shadow: none; }

/* ── round-80: coverage strip under the header ──────────────────────────────
   One quiet line, not a shouting banner: it must be readable without competing
   with the emergency guidance that appears at the point of reporting. */
.cov-strip{
  display:flex; align-items:center; gap:10px; justify-content:center;
  padding:7px 14px; font-size:.86rem; line-height:1.35;
  background:var(--tan, #E8D9BE); color:#FFFFFF;
  border-bottom:1px solid rgba(0,0,0,.08);
}
.cov-strip a{ color:inherit; font-weight:700; text-decoration:underline; }
.cov-close{
  margin-left:4px; background:none; border:0; cursor:pointer;
  font-size:.95rem; line-height:1; color:inherit; opacity:.6; padding:2px 4px;
}
.cov-close:hover{ opacity:1; }
html.cov-strip-off .cov-strip{ display:none; }

/* Round-83: the strip was rendering BEHIND the fixed header.
   MainLayout puts it between </header> and @Body, so it is an ordinary in-flow block starting at
   y=0 — but the header is position:fixed and `.page` compensates for that with its own
   padding-top. The strip therefore sat underneath the header, invisible, while its height became
   dead space between the nav and the page heading. Measured: strip top 0, entirely covered,
   64px of blank below the header. It only showed signed IN because the strip resolves the
   country from the account — signed out, GeoIP usually cannot, so it never rendered.

   Fix: push the strip below the fixed header and let it provide the offset `.page` was providing,
   instead of both reserving the same space. Verified in both states — signed out (header 157)
   and signed in (header 64) — strip fully visible, zero gap, no margin collapse.

   :has() so the page only drops its own reservation on the pages that actually have a strip;
   where it is absent nothing changes, and a browser without :has() keeps today's behaviour
   rather than losing the header offset entirely. */
.cov-strip{ margin-top:var(--header-h); }
body:has(.cov-strip) main.page{ padding-top:0; }

/* ── round-80: admin rail + hub icons follow the theme ───────────────────────
   Emoji rendered in the OS's own palette, so the rail never matched the product
   and looked pasted in. Outline icons stroke with currentColor, so they inherit
   the link colour, the hover state and the active state with no extra rules. */
.adm-ico{ display:inline-flex; align-items:center; justify-content:center; width:18px; height:18px; }
.adm-ico svg{ width:18px; height:18px; display:block; }
.adm-sidebar .adm-link{ color:var(--ink-soft, #5A6B63); }
.adm-sidebar .adm-link .adm-ico{ color:var(--accent, #618D7C); transition:color .15s; }
.adm-sidebar .adm-link:hover{ color:var(--ink, #23312C); }
.adm-sidebar .adm-link:hover .adm-ico,
.adm-sidebar .adm-link.on .adm-ico{ color:var(--accent-strong, #4F8577); }
/* Round-82: the active rail item sits on a filled accent pill, so --ink (near-black) on it was
   the low-contrast pairing. White is the readable one, and the icon has to come with it —
   .adm-link.on .adm-ico above sets its own colour and would otherwise stay green on green. */
.adm-sidebar .adm-link.on{ color:#fff; font-weight:700; }
.adm-sidebar .adm-link.on .adm-ico{ color:#fff; }

.adm-card-ico{ display:inline-flex; vertical-align:-3px; color:var(--accent, #618D7C); }
.adm-card-ico svg{ width:20px; height:20px; }

/* Round-81: audit log row clamp. <Virtualize ItemSize="44"> sizes its spacers from that number,
   so a row that renders taller makes the measured and computed scroll heights disagree and the
   table flickers as it recalculates. Every cell is held to one line; the Details text keeps its
   full value in a title attribute rather than being lost. */
table.pro.audit-log tbody tr{ height:44px; }
table.pro.audit-log tbody td{ padding-top:0; padding-bottom:0; height:44px; overflow:hidden; white-space:nowrap; }
table.pro.audit-log tbody td .clip{
  display:block; max-width:42ch; overflow:hidden; text-overflow:ellipsis; white-space:nowrap;
}
/* the actor cell carries a 26px avatar — keep it from pushing the row past the promised height */
table.pro.audit-log tbody td img,
table.pro.audit-log tbody td span[style*="border-radius:50%"]{ vertical-align:middle; }
@media (max-width:900px){ table.pro.audit-log tbody td .clip{ max-width:24ch; } }

/* Round-81: tag / keyword search inside the pin-filters popover */
.pin-filters .tf-tag{ border-top:1px solid var(--track); margin-top:8px; padding-top:8px; }
.pin-filters .tf-tag input[type="search"]{
  width:100%; box-sizing:border-box; padding:7px 10px; border:1px solid var(--track);
  border-radius:9px; font-size:.9rem; background:var(--paper, #fff); color:var(--ink);
}
.pin-filters .tf-tag small{ display:block; margin-top:5px; color:var(--ink-soft); font-size:.76rem; line-height:1.35; }

/* Round-81: keyword box in the stories filter row */
.stories-q{ display:flex; margin:0 0 8px; }
.stories-q input[type="search"]{
  width:100%; max-width:420px; padding:7px 12px; border:1px solid var(--track);
  border-radius:999px; font-size:.9rem; background:var(--paper, #fff); color:var(--ink);
}
.stories-q input[type="search"]:focus{ outline:2px solid var(--accent, #618D7C); outline-offset:1px; }
@media (max-width:640px){ .stories-q input[type="search"]{ max-width:none; } }

/* Round-81: time bands the viewer's plan does not cover — visibly closed, still clickable so the
   click can explain why rather than doing nothing. */
#timeSwitch button.locked, .stories-time button.locked{ opacity:.55; cursor:pointer; }
#timeSwitch button.locked:hover, .stories-time button.locked:hover{ opacity:.8; }

/* Round-81 (a): the history upsell must clear the Ada FAB.
   .asst-fab is z-index:2500 and sits mid-right, directly over the panel's ✕ — so the panel
   opened but could not be closed. It outranks the FAB rather than the FAB being lowered.
   (Round-81 (i) has since dropped the FAB to 900 *on the map*, so that collision is gone there —
   but this rule still earns its place: off-map the FAB is still 2500, and 2600 is also what
   keeps the panel above its own .gate-backdrop at 2590.) */
.partner-panel{ z-index:2600; }

/* Round-81 (b): time bands must stay on ONE line on mobile. Adding " 🔒" to four of six labels
   pushed them to wrap, so the strip grew to two rows and the lock read as a separate item. */
#timeSwitch button, .stories-time button{ white-space:nowrap; }
@media (max-width:640px){
  #timeSwitch, .stories-time{
    max-width:calc(100vw - 16px); overflow-x:auto; flex-wrap:nowrap;
    scrollbar-width:none; -webkit-overflow-scrolling:touch;
  }
  #timeSwitch::-webkit-scrollbar, .stories-time::-webkit-scrollbar{ display:none; }
  /* :not(.time-arrow) — the arrows are buttons inside the same host, so this ID-specificity rule
     was beating .time-arrow's own padding:0 and giving the 28px circles 20px of side padding,
     which squashed the chevron off-centre. They size themselves below. */
  #timeSwitch button:not(.time-arrow), .stories-time button:not(.time-arrow){
    flex:0 0 auto; padding:8px 10px; font-size:.78rem;
  }
  /* the arrows' own mobile size is set with the rest of their styling further down — a media
     query adds no specificity, so setting it here would lose to the later .time-arrow block. */
}

/* Round-81 (c): the custom-range block when history is capped */
.pin-filters .tf-range.capped h4::after{ content:" 🔒"; }
.pin-filters .tf-range .tf-cap{
  display:block; margin-top:6px; color:var(--ink-soft); font-size:.76rem; line-height:1.35;
}

/* Round-81 (d): the upsell heading sits on the dark panel gradient, where the inherited heading
   colour renders too dark to read. The card body already uses --paper; the heading did not. */
.partner-panel h3{ color:#fff; }
.partner-panel .sp-close{ color:#fff; opacity:.85; }
.partner-panel .sp-close:hover{ opacity:1; }

/* Round-81 (e): the gate modal gets the reconnect modal's treatment — a dimmed, blurred backdrop
   behind it and an accent edge. Without it the panel floats over a live map with no visual
   separation, so the eye keeps reading the map underneath instead of the message. */
.gate-backdrop{
  position:fixed; inset:0; z-index:2590;
  background:rgba(44,59,54,.38); backdrop-filter:blur(2px);
  animation:gateFade .18s ease-out;
}
@keyframes gateFade{ from{ opacity:0 } to{ opacity:1 } }
@media (prefers-reduced-motion:reduce){ .gate-backdrop{ animation:none } }
.partner-panel{ border-left:3px solid var(--accent, #618D7C); }

/* Round-81 (f): scroll affordance on the time bands. Reuses the service row's arrow styling —
   same problem, same answer — and only appears when the strip actually overflows. */
/* Round-81: STICKY children of the strip, not fixed elements placed by JS. Fixed arrows had to be
   re-positioned from the strip's rect on every scroll and resize, and a mobile browser collapsing
   its URL bar fires resize continuously while you scroll — visible jitter. Sticky lets the
   browser hold them against the strip's own edges, and because a sticky box keeps its place in
   the flow they reserve their own room instead of covering the first and last bands. */
/* Round-81 (f2): white disc, accent chevron, glyph centred, label-sized type.
   - align-self:center — the band buttons are taller than 28px, so without this the discs sit on
     the strip's cross-axis start instead of level with the labels they scroll.
   - line-height:1 + padding:0 — "‹" carries its own leading and side bearing; both push the
     glyph off the disc's centre unless the box is collapsed onto it.
   - font-size matches .base-switch button (.82rem, .78rem on mobile) so the chevron reads as the
     same type as the band labels rather than as a separate piece of furniture. */
/* Selector is .base-switch button.time-arrow, not .time-arrow: the arrows are <button>s inside
   .base-switch, and style.css's `.base-switch button` (0,0,1,1) outranks a lone class (0,0,1,0).
   Every declaration below — padding, background, colour, font-size — was being overridden by it,
   so the discs rendered transparent with the bands' pale label colour and the bands' 14px side
   padding. Beating it needs equal-or-greater specificity, not just a later rule. */
.base-switch button.time-arrow{
  position:sticky; flex:0 0 auto; align-self:center; z-index:2;
  /* 30px disc / 1.5px border / 16px caret — the .story-arrow figures, so the two scroll
     affordances on a phone are the same control at the same size. Colour stays accent-on-white
     rather than the services row's --deep, because these sit on the dark band strip. */
  width:30px; height:30px; border-radius:50%; padding:0;
  border:1.5px solid var(--accent, #618D7C); background:#fff; color:var(--accent, #618D7C);
  display:none; align-items:center; justify-content:center; cursor:pointer;
  line-height:1;
  box-shadow:0 3px 10px rgba(44,59,54,.25);
}
.base-switch button.time-arrow svg{ width:16px; height:16px; display:block; }
.base-switch button.time-arrow.right svg{ transform:rotate(180deg); }
.base-switch button.time-arrow:hover{ background:var(--accent, #618D7C); color:#fff; }
.time-arrow.left{ left:2px; }
.time-arrow.right{ right:2px; }
.base-switch button.time-arrow.show{ display:inline-flex; }
/* no mobile font-size override any more: the caret is an SVG sized in px, not type. */
/* round-81: no desktop kill-switch — the arrows are driven by whether the strip actually
   overflows, which can happen in a narrow desktop window too. */

/* Round-81 (g): scroll arrows live INSIDE the strip on phones.
   The bottom row is [time bands][report ✚]. Placing the arrows just outside the strip put the
   right one on top of the report button on an iPhone SE — and shrinking the strip did not help,
   because the row is centred so the report button simply moved left with it. Overlaying the
   strip's own ends is immune to how the row lays out: the arrows can never reach a sibling
   control. The strip gives up a little width and gains end padding so the arrows do not sit on
   the first and last bands. */
@media (max-width:640px){
  /* Round-82: .stories-time joins the rule — the stories strip now shares the map's .time-row and
     therefore has the same report button beside it to leave room for. */
  #timeSwitch, .stories-time{ max-width:calc(100vw - 96px); margin-right:18px; transition:padding .12s ease; }
  /* the padding itself is applied inline by _wireTimeArrows, alongside the arrow that needs it */
}

/* Round-81 (h): lift the bottom controls clear of the map attribution on phones.
   .time-row sat at bottom:14px (spanning ~613-653 on an iPhone SE) while .map-legal occupies
   647-667 — they overlapped by 6px, so a thumb aimed at the band strip or the report button
   frequently landed on an OpenStreetMap link and navigated away from the map. */
@media (max-width:720px){
  .time-row{ bottom:calc(27px + env(safe-area-inset-bottom, 0px)); }

  /* The attribution TEXT stays — OpenStreetMap, Leaflet and CARTO all require visible credit —
     but its links stop accepting taps, so a near miss on the controls above does nothing instead
     of leaving the page. Desktop keeps them clickable.

     TWO separate footers sit down there and the first pass only caught one: .map-legal is ours
     (Privacy, Terms) while .leaflet-control-attribution is Leaflet's own, holding the
     Leaflet / OpenStreetMap / CARTO links. Those were the ones still stealing taps. */
  .map-legal a,
  .leaflet-control-attribution a{ pointer-events:none; text-decoration:none; }
}

/* Round-81 (i): reserved space for the band-strip scroll arrows. Padding on the flex +
   overflow-x:auto strip provably did not apply, so the arrows covered the first and last
   controls; these spacers are real flex children and cannot be collapsed away. */


/* Round-81 (j): the stories filter bar is FIXED under the nav, not sticky.
   Sticky meant it travelled with the document until it reached its offset, so on a phone it
   visibly slid as you scrolled back to the top. Fixed pins it under the header for good; the
   feed gains matching top padding so the first card is not hidden behind it. */
.stories-filters{ position:fixed; left:0; right:0; top:var(--header-h); }
.stories-wrap{ padding-top:var(--stories-filters-h, 24px); }   /* JS measures the exact clearance */
/* round-81: the 12px breathing gap under the fixed filter bar was four times more than the
   design wants on a phone, where vertical space is the scarce thing */

/* Round-81 (k): the stories feed has its own bottom furniture (time bands, gear) and an infinite
   scroll that never reaches a natural end — a site footer below it is unreachable in practice and
   competes with the fixed controls for the same thumb space. */
.stories-page ~ .site-footer,
body:has(.stories-page) .site-footer{ display:none; }


/* Round-82 P4: discreet mode.
   For someone hiding from an intruder, a phone screen is a lamp pointed at their hiding place.
   Dim hard, drop the whites, and remove the red the help panel otherwise uses to draw the eye —
   in this one situation being noticed is the danger, not being missed.
   filter on <html> so it covers the map canvas and every panel at once, including anything the
   map draws after this class is set. */
html.aqua-discreet{ filter:brightness(.32) saturate(.6); }
html.aqua-discreet #helpPanel{ border-color:var(--track, #ADC3B9) !important; }
html.aqua-discreet #helpSend{ background:var(--deep, #4F8577) !important; }
/* A dimmed screen must not also be an unreadable one — keep the text the person is typing legible
   inside the panel while everything around it recedes. */
html.aqua-discreet #helpPanel, html.aqua-discreet #helpPanel *{ filter:brightness(1.6); }
@media (prefers-reduced-motion:reduce){ html.aqua-discreet{ transition:none; } }


/* Round-82: low-power mode. Everything that costs battery and is not the beacon goes away —
   animation, shadows, blur, the assistant. A phone at 12% is a phone that has to last until a
   crew arrives, and being findable matters more than being able to browse. */
html.aqua-lowpower *,
html.aqua-lowpower *::before,
html.aqua-lowpower *::after{ animation:none !important; transition:none !important; }
html.aqua-lowpower .asst-fab, html.aqua-lowpower .right-stack,
html.aqua-lowpower .leaflet-tile-pane{ display:none !important; }
html.aqua-lowpower .card, html.aqua-lowpower .sp-alertbox{ box-shadow:none !important; backdrop-filter:none !important; }


/* ================= Round-82 (#12): metrics visuals =================
   Sequential single hue throughout, because every measure here is MAGNITUDE — how many, how
   often, what share. None of it is identity, so categorical colour would be decoration that
   buries the one number that matters.

   The ramp was re-stepped rather than taken from the brand scale directly: the natural
   --water/--sage/--accent/--deep steps land in a lightness band where neither ink nor paper
   reaches 4.5:1, and a retention grid carries a number in EVERY cell. So the whole ramp stays
   above that band — light to mid, ink labels throughout, verified 5.35:1 at the darkest step.
   The site has no prefers-color-scheme anywhere, so one mode is the honest answer here. */
:root{
  --viz-1:#F2F6F1; --viz-2:#DCE9E0; --viz-3:#C2D9CB; --viz-4:#A8C8B6; --viz-5:#8FB8A3;
  --viz-track:#EDF1EC; --viz-ink:#2C3B36;
}
.viz-fig{ margin:0 0 30px; }
.viz-fig figcaption{ font-size:.86rem; color:var(--ink-soft,#5C6E66); margin:0 0 10px; max-width:640px; }

/* --- horizontal bars: 4px rounded data-end, anchored to the baseline --- */
.viz-bars{ display:grid; grid-template-columns:minmax(90px,auto) 1fr auto; gap:8px 12px; align-items:center; }
.viz-bars .lbl{ font-size:.88rem; color:var(--ink,#2C3B36); }
.viz-bars .track{ background:var(--viz-track); border-radius:4px; height:22px; position:relative; overflow:hidden; }
.viz-bars .fill{ height:100%; border-radius:0 4px 4px 0; background:var(--viz-5); }
.viz-bars .val{ font-size:.88rem; font-variant-numeric:tabular-nums; color:var(--ink,#2C3B36); }

/* --- meter: a single ratio against its limit, on the same ramp --- */
.viz-meter{ background:var(--viz-track); border-radius:999px; height:14px; overflow:hidden; max-width:420px; }
.viz-meter > span{ display:block; height:100%; background:var(--viz-5); border-radius:999px; }

/* --- retention heatmap. 2px surface gap between cells, per the mark spec: adjacent fills must
       never touch, or the eye reads two cells as one block. --- */
.viz-heat{ border-collapse:separate; border-spacing:2px; font-size:.86rem; }
.viz-heat th{ font-weight:600; color:var(--ink-soft,#5C6E66); font-size:.8rem; padding:4px 8px; text-align:left; }
.viz-heat td{ padding:8px 10px; border-radius:4px; text-align:center; font-variant-numeric:tabular-nums;
              color:var(--viz-ink); min-width:56px; }
.viz-heat td.empty{ background:transparent; color:var(--ink-soft,#5C6E66); }
.viz-heat td.wk{ background:transparent; text-align:left; white-space:nowrap; color:var(--ink,#2C3B36); }
/* --- legend: identity is never colour-alone, so the scale is spelled out --- */
.viz-legend{ display:flex; align-items:center; gap:6px; font-size:.8rem; color:var(--ink-soft,#5C6E66); margin-top:10px; }
.viz-legend i{ width:26px; height:12px; border-radius:3px; display:inline-block; }
@media (max-width:640px){ .viz-bars{ grid-template-columns:minmax(72px,auto) 1fr auto; } }


/* Round-82 (#9): nested rail children.
   Indented under their parent and set a step quieter, so the eye reads "this belongs to the thing
   above it" without needing a connector line. No icon: the parent's icon already identifies the
   section, and a second one at child level competes with it for the same meaning. */
.adm-sidebar .adm-link.adm-sub{
  padding-left:34px; font-size:.88rem; color:var(--ink-soft,#5A6B63);
  position:relative;
}
.adm-sidebar .adm-link.adm-sub::before{
  content:""; position:absolute; left:20px; top:50%; width:6px; height:1px;
  background:var(--track,#ADC3B9);
}
/* Round-89: an active CHILD is bold accent TEXT — never the filled pill its parent gets.
   It was white-on-accent because children used to inherit the parent's solid background; with
   that removed, white text on a light rail is invisible. Two stacked solid blocks also read as
   two peers, which is exactly the hierarchy the indent and the tick exist to deny. */
.adm-sidebar .adm-link.adm-sub.on{ background:transparent; color:var(--accent, #618D7C); font-weight:700; }
.adm-sidebar .adm-link.adm-sub.on:hover{ background:var(--mintbg); }
.adm-sidebar .adm-link.adm-sub.on::before{ background:var(--accent, #618D7C); }


/* Round-82: attention badges on the admin rail.
   Two levels, because two things are not equally urgent. A backlog of reports to review is work;
   an open help request is a person waiting. The review badge is the platform accent, the help
   badge is the impact red and pulses — reserved for that one case so the colour keeps meaning
   something. Numbers are shown, never colour alone, and >99 collapses so the rail cannot widen. */
.adm-badge{
  margin-left:auto; min-width:20px; padding:1px 6px; border-radius:999px;
  background:var(--accent,#618D7C); color:#fff;
  font-size:.72rem; font-weight:700; line-height:1.5; text-align:center;
  font-variant-numeric:tabular-nums; flex:0 0 auto;
}
.adm-badge.urgent{ background:var(--st-impact,#C0392B); animation:admPulse 2.2s ease-in-out infinite; }
.adm-link.on .adm-badge{ background:#fff; color:var(--accent,#618D7C); }
.adm-link.on .adm-badge.urgent{ background:#fff; color:var(--st-impact,#C0392B); }
@keyframes admPulse{ 0%,100%{ opacity:1 } 50%{ opacity:.55 } }
@media (prefers-reduced-motion:reduce){ .adm-badge.urgent{ animation:none } }
/* the link is a flex row so the badge can sit hard right */
.adm-sidebar .adm-link{ display:flex; align-items:center; gap:8px; }


/* Round-82: account tab strip. Horizontal rather than a left rail, because these pages keep the
   public shell — someone changing a notification setting is still browsing the site. Scrolls
   sideways on a phone like the map's service chips: five tabs do not fit on an iPhone SE, and
   wrapping to two rows pushes the actual setting below the fold. */
/* Round-83: the wrapper carries the overflow arrows and the border, so the border no longer
   scrolls with the tabs — a border on the scroller itself shifted a hairline under the thumb on
   every drag, which is half of what "shaky" was. */
.acct-tabs-wrap{
  display:flex; align-items:center;
  border-bottom:1px solid var(--track,#ADC3B9); margin-bottom:22px;
}
.acct-tabs{
  display:flex; gap:6px; overflow-x:auto; scrollbar-width:none;
  flex:1 1 auto; min-width:0; padding-bottom:0;
  /* iOS: keep the horizontal rubber-band inside this strip. Without contain, an over-scroll at
     either end propagates to the page and to Safari's back-swipe, which is what made the row
     feel like it was fighting the thumb. */
  overscroll-behavior-x:contain;
  -webkit-overflow-scrolling:touch;
  touch-action:pan-x;
  scroll-behavior:smooth;
}
.acct-tabs::-webkit-scrollbar{ display:none; }
.acct-tab{
  position:relative; flex:0 0 auto; padding:9px 14px; font-size:.92rem; font-weight:600;
  white-space:nowrap; color:var(--ink-soft,#5C6E66); text-decoration:none;
  border-bottom:2px solid transparent; margin-bottom:-1px;
  /* iOS paints a grey flash on tap that reads as the row jumping */
  -webkit-tap-highlight-color:transparent;
}
/* The wiggle. The active tab goes from 600 to 700, which re-measures the text, which re-lays out
   every tab after it — so selecting a tab shifted the row under the finger. This reserves the
   BOLD width on every tab permanently: an invisible copy of the label at weight 700 sets the
   box, and the visible text sits on top of it. Geometry now never changes. */
.acct-tab::before{
  content:attr(data-label); display:block; height:0; overflow:hidden;
  font-weight:700; visibility:hidden; pointer-events:none;
}
.acct-tab:hover{ color:var(--ink,#2C3B36); text-decoration:none; }
/* The active tab is marked by an underline AND weight, never colour alone. */
.acct-tab.on{ color:var(--deep,#4F8577); border-bottom-color:var(--deep,#4F8577); font-weight:700; }
/* The arrows sit level with the tabs and clear the 1px rule the wrapper owns. */
.acct-tabs-wrap .pill-arrow{ margin-bottom:2px; }


/* Round-82: "N layers hidden" notice.
   Layer choices persist per BROWSER with no expiry, so a toggle made once keeps applying for
   ever — and four of five services switched off is visually identical to "there is no data".
   Amber rather than the platform green: this is a state the reader probably did not intend,
   and it should not blend into the furniture. */
.hidden-layers{
  /* Round-83: it shares .svc-wrap with the scrolling chips row, which is width:100%. Without a
     shrink floor of its own it got squeezed to nothing on a narrow row and its 8px margin
     collapsed against the neighbouring control. */
  flex:0 0 auto; margin-left:10px; margin-right:15px; border:0; cursor:pointer;
  background:#E8A33D; color:#2C3B36;
  font-family:var(--font); font-weight:700; font-size:.82rem;
  padding:7px 13px; border-radius:999px;
  box-shadow:0 4px 14px rgba(44,59,54,.22);
}
.hidden-layers:hover{ background:#D9922B; }
/* Round-94: on phones the pill row is the scarce thing — the "N layers hidden — show all" notice
   (live map + stories) is dropped there; the service pills themselves still show what is off. */
@media (max-width:720px){ .hidden-layers{ display:none; } }


/* Round-82: local-resource pins (defibrillators, extinguishers, hydrants, shelters).
   A white disc with a coloured ring, deliberately quieter than an incident pin: these are
   standing facts about a place, not things happening right now, and they must never be mistaken
   for an alert. The ring carries freshness — green confirmed recently, amber ageing, grey over a
   year old, red reported missing. */
.res-pin{ background:none; border:0; }
.res-dot{
  display:flex; align-items:center; justify-content:center;
  width:30px; height:30px; border-radius:50%;
  background:#FFF; border:2.5px solid #2F6F5E;
  font-size:15px; line-height:1;
  box-shadow:0 2px 6px rgba(44,59,54,.28);
}
.res-pop{ font-family:var(--font); font-size:.86rem; color:#2C3B36; }
.res-pop-h{ font-size:.95rem; margin-bottom:2px; }
.res-pop-k{ color:#6B726B; font-size:.78rem; text-transform:uppercase; letter-spacing:.04em; }
.res-pop-a{ margin-top:6px; }
.res-pop-n{ margin-top:4px; color:#4A524A; }
.res-pop-f{ margin-top:7px; font-weight:600; font-size:.8rem; }
.res-pop-dir{ display:inline-block; margin-top:7px; color:#4F8577; font-weight:600; }
.res-pop-btns{ display:flex; gap:6px; margin-top:10px; }
.res-pop-btns button{
  flex:1; cursor:pointer; font-family:var(--font); font-weight:600; font-size:.8rem;
  padding:7px 8px; border-radius:8px; border:1px solid #C9CEC7; background:#F7F8F5; color:#2C3B36;
}
.res-pop-btns button:hover:not(:disabled){ background:#EDF0EA; }
.res-pop-btns button:disabled{ opacity:.5; cursor:default; }
/* "Not there" is the destructive-sounding one but it is the MOST valuable report on this layer —
   it is the only thing that stops the next person making a wasted run. Never styled as a warning. */
.res-pop-msg:not(:empty){ margin-top:8px; font-size:.8rem; color:#4F8577; font-weight:600; }

/* /resources — add on the left, what is already mapped here on the right. Side by side rather
   than stacked, because seeing the existing entries is what stops the same AED being added five
   times, and a list below the fold is a list nobody reads. */
.res-grid{ display:grid; grid-template-columns:1.1fr .9fr; gap:20px; align-items:start; }
@media (max-width:900px){ .res-grid{ grid-template-columns:1fr; } }
.res-pickmap{ height:280px; border-radius:12px; overflow:hidden; border:1px solid #D9DDD6; }
.res-list{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:12px; }
.res-list li{ display:flex; gap:11px; align-items:flex-start; }
.res-li-ico{
  flex:0 0 auto; display:flex; align-items:center; justify-content:center;
  width:34px; height:34px; border-radius:50%;
  background:#FFF; border:2.5px solid #2F6F5E; font-size:16px; line-height:1;
}
.res-li-body{ min-width:0; }
.res-li-btns{ display:flex; gap:6px; margin-top:6px; }
.res-li-btns button{
  cursor:pointer; font-family:var(--font); font-weight:600; font-size:.76rem;
  padding:5px 10px; border-radius:8px; border:1px solid #C9CEC7; background:#F7F8F5; color:#2C3B36;
}
.res-li-btns button:hover{ background:#EDF0EA; }

/* Round-83: the SLA clock.
   Three bands, and only two of them are loud. "ok" is deliberately quiet — if every item on the
   queue carries a coloured badge, the coloured badge stops meaning anything, and the one that is
   genuinely late reads the same as the ninety that are fine. */
.pill.sla-ok{ background:#E4E7E1; color:#4A524A; }
.pill.sla-ok .dot{ background:#8A8F87; }
.pill.sla-warn{ background:#FBEEDA; color:#8A5A12; }
.pill.sla-warn .dot{ background:#D9822B; }
.pill.sla-breach{ background:#F7DED8; color:#8E1B12; font-weight:700; }
.pill.sla-breach .dot{ background:#C0392B; }
.sla-banner{
  display:flex; gap:10px; flex-wrap:wrap; align-items:baseline;
  background:#F7DED8; border-left:4px solid #C0392B; padding:12px 16px;
}
.sla-banner b{ color:#8E1B12; }
.sla-banner span{ font-size:.88rem; color:#5A4340; }

/* Round-83: the agency operations map. */
.ops-map{ height:420px; border-radius:12px; overflow:hidden; border:1px solid #D9DDD6; }
@media (max-width:720px){ .ops-map{ height:320px; } }
.ops-legend{
  display:flex; flex-wrap:wrap; gap:6px 16px; margin-top:10px;
  font-size:.78rem; color:#4A524A;
}
.ops-legend span{ display:inline-flex; align-items:center; gap:6px; }
.ops-k{ width:11px; height:11px; border-radius:50%; display:inline-block; }
/* Age is drawn as a ring rather than another colour: severity already owns colour, and a second
   colour scale on the same marks is how a map stops being readable at a glance. */
.ops-k.ring{ background:transparent; border:2.5px solid #8E1B12; width:13px; height:13px; }
.ops-stale{ animation:opsPulse 2.4s ease-in-out infinite; }
@keyframes opsPulse{ 0%,100%{ opacity:1 } 50%{ opacity:.45 } }
@media (prefers-reduced-motion: reduce){ .ops-stale{ animation:none; } }

.ops-unit{ background:none; border:0; }
.ops-unit-dot{
  display:flex; align-items:center; justify-content:center;
  width:28px; height:28px; border-radius:8px;
  border:2px solid #FFF; font-size:14px; line-height:1;
  box-shadow:0 2px 7px rgba(44,59,54,.35);
}
.ops-placing{
  margin-top:10px; padding:9px 12px; border-radius:10px;
  background:#FBEEDA; color:#8A5A12; font-size:.86rem; font-weight:600;
}
.ops-pop{ font-family:var(--font); font-size:.86rem; color:#2C3B36; }
.ops-pop-h{ font-size:.96rem; }
.ops-pop-k{ color:#6B726B; font-size:.78rem; text-transform:uppercase; letter-spacing:.04em; margin-top:2px; }
.ops-pop-age{ margin-top:6px; font-weight:600; }
.ops-pop-age.stale{ color:#8E1B12; }
.ops-pop-n{ margin-top:5px; }
.ops-pop-n.muted{ color:#8A8F87; }
.ops-pop-link{ display:inline-block; margin-top:7px; color:#4F8577; font-weight:600; }

/* Round-83: the push ask.
   Bottom of the screen, above the thumb's reach on a phone, and deliberately NOT a modal — it
   appears immediately after the person completed something, and blocking that moment to demand a
   permission is how a banner becomes a Block. It can be ignored entirely and the page still works. */
.push-ask{
  position:fixed; left:50%; transform:translateX(-50%); bottom:18px; z-index:9000;
  width:min(440px, calc(100vw - 24px));
  display:flex; gap:12px; align-items:center; flex-wrap:wrap;
  background:#2C3B36; color:#F3F4F1;
  font-family:var(--font); font-size:.88rem; line-height:1.35;
  padding:13px 16px; border-radius:14px;
  box-shadow:0 10px 30px rgba(44,59,54,.34);
}
.push-ask-body{ flex:1 1 200px; }
.push-ask-btns{ display:flex; gap:8px; flex:0 0 auto; }
.push-ask-btns button{
  cursor:pointer; font-family:var(--font); font-weight:700; font-size:.82rem;
  padding:7px 13px; border-radius:999px; border:0;
}
.push-ask-btns button[data-push-yes]{ background:#4F8577; color:#F3F4F1; }
.push-ask-btns button[data-push-yes]:hover:not(:disabled){ background:#3F6E62; }
/* "Not now" is a real, easy choice — not a greyed-out afterthought. Making the decline hard is
   what turns a decline into a Block, and a Block cannot be undone from inside the app. */
.push-ask-btns button[data-push-no]{ background:transparent; color:#C3CCC6; text-decoration:underline; }
.push-ask-btns button:disabled{ opacity:.55; cursor:default; }
@media (max-width:520px){ .push-ask{ bottom:12px; font-size:.84rem; } }

/* Round-82: volunteer network. */
.vol-duty{
  display:flex; align-items:center; justify-content:space-between; gap:14px;
  padding:12px 14px; border-radius:12px; margin-bottom:16px;
}
.vol-duty.on{ background:#EAF2EE; border:1px solid #BBD5CA; }
.vol-duty.off{ background:#F2F3F0; border:1px solid #D9DDD6; }
.vol-vouched{ color:#2F6F5E; font-weight:600; font-size:.85rem; margin:0 0 6px; }
.vol-callouts{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:12px; }
.vol-callouts li{ padding:13px 14px; border-radius:12px; background:#F7F8F5; border:1px solid #E2E5DF; }
/* A pending callout is the only red thing on this page. Everything else can wait; this cannot,
   and it must be findable by someone glancing at a phone they just pulled out of a pocket. */
.vol-callouts li.live{ background:#FCEFEC; border-color:#E8B4A8; }
.vol-c-head{ display:flex; align-items:baseline; justify-content:space-between; gap:10px; }
.vol-c-km{ font-size:.8rem; font-weight:700; color:#4F8577; white-space:nowrap; }
.vol-c-state{ font-size:.82rem; font-weight:600; color:#2F6F5E; align-self:center; }
.vol-c-state.muted{ color:#8A8F87; }

/* Round-82: community issues. Status is a small filled pill rather than coloured text, so the
   state of a list of twenty is readable at a glance without reading any of them. */
.iss-list{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:14px; }
.iss-list li{ padding-bottom:12px; border-bottom:1px dashed #DDE1DA; }
.iss-list li:last-child{ border-bottom:0; padding-bottom:0; }
.iss-head{ display:flex; align-items:baseline; justify-content:space-between; gap:10px; }
.iss-status{
  flex:0 0 auto; font-size:.7rem; font-weight:700; text-transform:uppercase; letter-spacing:.04em;
  padding:3px 8px; border-radius:999px; background:#E4E7E1; color:#4A524A; white-space:nowrap;
}
.iss-status.acknowledged{ background:#E8EEF6; color:#2D568F; }
.iss-status.in_progress{ background:#FBEEDA; color:#8A5A12; }
.iss-status.resolved{ background:#E3F0E9; color:#2F6F5E; }
/* Declined is grey, not red. It is an honest answer, and colouring it as a failure is how you
   teach an assembly to leave things open for ever instead of saying no. */
.iss-status.rejected{ background:#EAEAE7; color:#75796F; }
.iss-reply{
  margin:8px 0 0; padding:9px 12px; border-radius:10px;
  background:#EAF2EE; font-size:.88rem;
}

/* Round-82: content-request thread. The platform's replies are the half a branch is waiting to
.creq-msg{ margin-top:10px; padding:9px 12px; border-radius:10px; background:#F5F6F3; }
.creq-msg.platform{ background:#EAF2EE; border-left:3px solid #4F8577; }
.iss-status.submitted{ background:#E4E7E1; color:#4A524A; }
.iss-status.needs_info{ background:#FBEEDA; color:#8A5A12; }
.iss-status.accepted{ background:#E8EEF6; color:#2D568F; }
.iss-status.done{ background:#E3F0E9; color:#2F6F5E; }
.iss-status.declined{ background:#EAEAE7; color:#75796F; }

/* Round-82: emergency plan. Progress, never a score — this is not a test, and a household with
   one good line is enormously better off than one with none. */
.plan-progress{ margin-bottom:20px; }
.plan-bar{ height:6px; border-radius:999px; background:#E4E7E1; overflow:hidden; margin-bottom:5px; }
.plan-bar span{ display:block; height:100%; background:#4F8577; transition:width .25s ease; }

/* The print sheet IS the deliverable for this page: a plan that only exists on a phone is
   unreadable on the night the phone is dead, wet, or left behind. Everything but the plan goes. */
@media print{
  .site-nav, .site-footer, .nav-grid, .asst-fab, .btn, .plan-progress, .hint, .sp-alertbox{ display:none !important; }
  .page, .section, .container, .card{ padding:0 !important; margin:0 !important; box-shadow:none !important; border:0 !important; }
  .field{ page-break-inside:avoid; margin-bottom:14px; }
  .field label{ font-weight:700; }
  /* Inputs print as their value only — a page of empty form boxes helps nobody at 2am. */
  .field input, .field textarea{ border:0 !important; padding:0 !important; resize:none; font-size:1rem; }
}

/* Round-83: duty roster. "Who is answerable right now" is the whole feature, so it gets the
   card at the top and the only colour on the page. Empty is amber, not red — nobody rostered is
   a gap worth closing, not a failure: the dispatcher still pages everyone. */
.duty-now{ border-left:4px solid #2F6F5E; }
.duty-now.empty{ border-left-color:#D9822B; background:#FBEEDA; }

/* Round-83: scene context on a queue card. Quiet by default; only the facts that change what
   somebody does get the alarm treatment, because if everything is urgent nothing is. */
.scene{ margin-top:10px; padding:9px 12px; border-radius:10px; background:#F5F6F3; }
.scene-f{ font-size:.87rem; margin-top:3px; }
.scene-f b{ color:#4F8577; }
.scene-f.alarm{ color:#8E1B12; }
.scene-f.alarm b{ color:#8E1B12; }

/* Round-83: device diagnostics. */
.diag-list{ list-style:none; margin:0; padding:0; display:flex; flex-direction:column; gap:10px; }
.diag-list li{ display:flex; gap:11px; align-items:flex-start; }
.diag-ico{
  flex:0 0 auto; width:22px; height:22px; border-radius:50%; color:#FFF;
  display:flex; align-items:center; justify-content:center; font-size:.76rem; font-weight:700;
  background:#8A8F87;
}
.diag-ok .diag-ico{ background:#2F6F5E; }
.diag-warn .diag-ico{ background:#D9822B; }
.diag-fail .diag-ico{ background:#C0392B; }

/* Round-83: the volunteer week grid. 168 cells have to stay tappable on a phone, so the row
   scrolls sideways rather than shrinking the cells below a thumb. */
.vol-grid{ overflow-x:auto; padding-bottom:4px; }
.vol-grid-head, .vol-grid-row{ display:grid; grid-template-columns:34px repeat(24, 15px); gap:2px; align-items:center; }
.vol-grid-row{ margin-top:2px; }
.vol-h{ font-size:.62rem; color:#8A8F87; text-align:left; }
.vol-d{ font-size:.68rem; color:#6B726B; }
.vol-cell{
  width:15px; height:15px; padding:0; border:0; border-radius:3px;
  background:#E4E7E1; cursor:pointer;
}
.vol-cell.on{ background:#4F8577; }
.vol-cell:hover{ outline:2px solid #2F6F5E; }

/* Round-83: legacy page content, shown so it stops being invisible. Monospace and scrollable —
   it is raw markdown being read for migration, not prose being presented. */
.legacy-body{
  margin:8px 0 0; padding:9px 12px; border-radius:8px; background:#F5F6F3;
  font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace; color:#4A524A;
  white-space:pre-wrap; max-height:170px; overflow:auto;
}

/* Round-83: the share toggle in the reaction row.
   Icon only — it sits between dislike and the comment counter, where a count would imply a
   reaction rather than an action. It reuses .react-btn so it matches the buttons either side of
   it exactly; only the padding is tightened, because there is no label to make room for. */
.react-btn.share-toggle{ padding-left:9px; padding-right:9px; }
.react-btn.share-toggle .ico-svg{ width:15px; height:15px; vertical-align:-2px; }
/* Open state matches the other reaction buttons' .on, so "this is showing" reads the same way
   everywhere in the row. */
.react-btn.share-toggle.on .ico-svg{ stroke:#fff; }
/* The revealed panel keeps the dashed rule the block under the description had — it is the
   platform's existing separator for share options, on stories as well as here. */
#sharePanel{ margin-top:10px; }

/* ── Round-83: the horizontal-scroll fix ────────────────────────────────────
   Reported on /account/channels at 375px (iPhone SE), and it was never that page's fault.

   `table.pro` carries min-width:520px so its columns stay readable, and every such table sits in
   `.table-wrap{ overflow-x:auto }` — a scroller whose whole job is to absorb that. It never got
   the chance. Grid and flex children default to `min-width:auto`, meaning they refuse to shrink
   below their content's intrinsic minimum, so the wrapper was forced to 520px, which widened its
   grid track, which widened the grid, which gave the PAGE a horizontal scrollbar while the
   scroller sat there unused at full width.

   Measured before: wrapper 520px inside a 375px viewport. After: 281px, with the table scrolling
   inside it as designed.

   This is the root cause, not a mask. Deliberately NOT `body{ overflow-x:hidden }` — that hides
   the next instance of this bug instead of surfacing it, and on iOS it can break position:sticky.
   Anything that overflows after this is a real defect and should still be visible as one. */
.grid > *,
/* Every OTHER grid container in the product needs the same permission. Found the hard way:
   .res-grid was missed, so /volunteer's 442px week-hour grid could not shrink and the page
   scrolled sideways even though .vol-grid already had overflow-x:auto and min-width:0 —
   the scroller was willing, its grandparent was not. */
.res-grid > *, .cpage-hero > *, .up-grid > *, .stats-row > *, .net-grid > *,
.sp-metrics > *, .foot-grid > *, .impact-strip > *{ min-width:0; }
.table-wrap{ min-width:0; }
/* Same reasoning one level down: a flex row holding a scroller has to let it shrink. */
.form-card .table-wrap, .card .table-wrap{ min-width:0; }

/* ── Round-83: the hidden attribute must actually hide ──────────────────────
   The "You appear to be in the affected area / Mark yourself safe / I'm not safe" prompt was
   shown to everybody, everywhere, regardless of the location gate — and the gate was never the
   problem. #checkinBox carried `hidden` AND an inline `display:flex`. The hidden attribute is
   only `[hidden]{display:none}` in the UA stylesheet, and an inline style beats it every time,
   so the element announced itself while its own attribute said otherwise.

   Two rules: give the box its flex layout only when it is NOT hidden, and make the attribute
   authoritative everywhere so the next element that does this cannot fail the same way. This
   was a safety bug — the prompt asserts a person's location — so it gets the belt AND braces. */
#checkinBox:not([hidden]){ display:flex; }
[hidden]{ display:none !important; }

   list used to print — an emoji ignores the theme, renders differently on every platform, and
   sat next to whatever glyph the sender had already put at the front of the title. */
.notif-ico{ display:inline-flex; align-items:center; justify-content:center; width:22px; height:22px; }
.notif-ico svg{ width:22px; height:22px; stroke:currentColor; fill:none; }

/* Round-87: the wizard's state banner. Colour carries the meaning at a glance; the text says it
   precisely, because "amber" is not an instruction. */
.setup-state{ border-left:4px solid var(--track); }
.setup-state.ok{ border-left-color:#1FA97A; }
.setup-state.warn{ border-left-color:#E8A33D; }

/* Round-89: the cedi equivalent under a dollar price. Deliberately quiet — the dollar figure
   is the price, this line is the reassurance that nothing unexpected happens at checkout. */
.plan-card .price-alt{ display:block; margin-top:2px; font-size:.82rem; font-weight:600;
                       color:var(--ink-soft, #55635C); letter-spacing:0; }

/* Round-90: disclosure carets on rail entries that have sub-pages (admin AND org rails).
   The row, not the link, is now the active pill. Otherwise the caret sat outside the green
   block it belongs to, reading as a control for the rail rather than for that entry. */
.adm-sidebar .adm-branch{ display:flex; align-items:center; border-radius:9px; }
.adm-sidebar .adm-branch:hover{ background:var(--mintbg); }
.adm-sidebar .adm-branch.on{ background:var(--accent, #618D7C); }
.adm-sidebar .adm-branch .adm-link{ flex:1 1 auto; min-width:0; }
/* the link keeps its text colour and loses only its background, which the row now draws */
.adm-sidebar .adm-branch .adm-link,
.adm-sidebar .adm-branch .adm-link:hover,
.adm-sidebar .adm-branch .adm-link.on{ background:transparent; }

.adm-sidebar .adm-exp{
  flex:0 0 auto; display:inline-flex; align-items:center; justify-content:center;
  width:26px; height:26px; margin-inline-end:4px; padding:0;
  background:none; border:0; border-radius:7px; cursor:pointer;
  color:var(--sage2, #8FA99B);
}
.adm-sidebar .adm-exp:hover{ background:rgba(0,0,0,.07); color:var(--ink, #23312C); }
.adm-sidebar .adm-exp:focus-visible{ outline:2px solid var(--accent, #618D7C); outline-offset:1px; }
/* NO transition on this transform, deliberately. With `transition:transform .18s` the caret was
   right on first paint and then stuck: clicking one left it at the identity matrix — visually
   unturned — for as long as you cared to wait. Measured, not guessed: the same click with the
   transition removed from that one button lands on rotate(-90deg) correctly, so it is the
   transition being interrupted by Blazor's DOM patch mid-flight, not a stale style. A caret that
   sometimes fails to turn is worse than one that never animates, and a disclosure arrow snapping
   is the normal behaviour anyway.
   caretleft points ◀: 180° is ▶ (shut) and -90° is ▼ (open, aimed at the children below). */
.adm-sidebar .adm-exp svg{ width:13px; height:13px; transform:rotate(180deg); }
.adm-sidebar .adm-exp[aria-expanded="true"] svg{ transform:rotate(-90deg); }
/* Under RTL the shut caret points ◀, into the text it belongs to; open is ▼ either way. */
html[dir="rtl"] .adm-sidebar .adm-exp svg{ transform:none; }
html[dir="rtl"] .adm-sidebar .adm-exp[aria-expanded="true"] svg{ transform:rotate(-90deg); }
/* on the active row the caret sits on accent, so it borrows the row's white */
.adm-sidebar .adm-branch.on .adm-exp{ color:#fff; }
.adm-sidebar .adm-branch.on .adm-exp:hover{ background:rgba(255,255,255,.2); color:#fff; }

/* Round-90: the "find the real neighbourhood" results.
   A list under the box rather than a native <select>: the rows carry a size, a member count and
   an Open link, none of which fit in an <option>, and the whole point is that a taken area is
   visibly taken rather than quietly missing. */
.acom-hits{
  margin-top:6px; border:1px solid var(--track,#ADC3B9); border-radius:10px;
  background:var(--paper,#fff); overflow:hidden; max-height:280px; overflow-y:auto;
}
.acom-hit{
  display:flex; flex-wrap:wrap; align-items:center; gap:10px; width:100%; text-align:left;
  padding:9px 12px; border:0; border-bottom:1px solid var(--track,#ADC3B9);
  background:none; font:inherit; cursor:pointer;
}
.acom-hits .acom-hit:last-child{ border-bottom:0; }
.acom-hit:hover{ background:var(--mintbg,#EFF5F1); }
.acom-hit-name{ font-weight:700; color:var(--ink,#23312C); min-width:0; overflow-wrap:anywhere; }
.acom-hit-meta{ font-size:.8rem; color:var(--ink-soft,#5A6B63); min-width:0; overflow-wrap:anywhere; }
/* Round-91q: Open and Join belong together.
   The row is a wrapping flex line and the buttons were just two more items in it, so a long
   locality line pushed them apart — Open could end up on one row and Join on the next, reading as
   two unrelated controls rather than one pair. They are pushed to the end as a group, and given a
   shared height so neither sits proud of the other. */
/* A lone button in a row (admin's "taken" rows) still sits at the end on its own. Direct children
   only — inside .acom-hit-actions an auto margin would prise the pair apart again. */
.acom-hit > .btn{ margin-left:auto; }
.acom-hit-actions{
  margin-left:auto; display:flex; align-items:stretch; gap:6px; flex:0 0 auto;
}
.acom-hit-actions .btn{
  display:inline-flex; align-items:center; justify-content:center;
  min-height:32px; white-space:nowrap; margin-left:0;
}
/* On a phone the name and the locality need the whole width, and two buttons pinned to the end of
   the same line ran off the right edge — the row does not scroll, so Join was simply cut off. The
   pair takes its own line and stays right-aligned, which keeps them adjacent without competing with
   the text for width. */
@media (max-width: 560px){
  .acom-hit-actions{ flex-basis:100%; margin-left:0; justify-content:flex-end; }
}
/* Taken: not a button, visibly not clickable, and it keeps its own way in via the Open link. */
.acom-hit.taken{ cursor:default; opacity:.72; }
.acom-hit.taken:hover{ background:none; }
.acom-hit.taken .acom-hit-meta{ color:var(--st-impact,#C0392B); font-weight:700; }

/* Round-90: the type label above a community's name. It is either the country's own level name
   ("District") or, for an unlinked community, the generic type — and those are held lower-case
   because the same two strings appear mid-sentence elsewhere. Capitalising the first letter here
   rather than storing two cased copies keeps ten languages from needing a second translation. */
.ctype::first-letter{ text-transform:uppercase; }

/* Round-91: the settings button on a community page when the member has muted something.
   It sits in the dark hero alongside .btn-outline, so it borrows that shape and changes only the
   colour — a muted service silences its emergencies, and the button is the only place that now
   says so. */
.btn-outline-danger{ background:transparent; color:#F5B7B1; border:2px solid #E6A9A2; }
.btn-outline-danger:hover{ background:rgba(230,169,162,.15); border-color:#F5B7B1; }

/* Round-91f: the community page's jump strip IS the account/admin tab strip — .acct-tabs-wrap
   supplies the layout, the arrows and the scrolling. Only three things differ: it sticks under the
   fixed nav, it is phone-only (desktop already shows the feed and the posts as two columns, so
   there is nothing to jump between), and it sits on an opaque background because it scrolls over
   page content rather than sitting in a header. */
.cpage-tabs{ display:none; }
@media (max-width: 899px){
  .cpage-tabs{
    display:flex; position:sticky; top:var(--header-h); z-index:60;
    background:rgba(243,244,241,.96); backdrop-filter:blur(8px);
    border-bottom:1px solid var(--track, #ADC3B9); padding:6px 8px; margin:0;
  }
}
.cpage-anchor{ position:relative; top:calc(-1 * (var(--header-h) + 52px)); height:0; }

/* Round-91m: a loading placeholder for any map container.
   Keyed on :not(.leaflet-container) — Leaflet adds that class to the element the instant it takes
   ownership, so the skeleton removes ITSELF with no JS to run, nothing to forget to call, and no
   way to leave a shimmer stuck over a map that has already drawn. It also covers the failure the
   round-91 work kept hitting: a container waiting on a slow script now looks like it is loading
   rather than looking broken. */
.map-skel:not(.leaflet-container){
  position:relative; overflow:hidden;
  background:linear-gradient(180deg, var(--track, #ADC3B9) 0%, rgba(173,195,185,.55) 100%);
}
.map-skel:not(.leaflet-container)::after{
  content:""; position:absolute; inset:0;
  background:linear-gradient(100deg, transparent 20%, rgba(255,255,255,.55) 50%, transparent 80%);
  transform:translateX(-100%);
  animation:map-skel-sweep 1.4s ease-in-out infinite;
}
@keyframes map-skel-sweep{ to{ transform:translateX(100%); } }
/* Round-92: the map on a saved place card. Short enough that the card still reads as a settings
   row rather than a map with some text under it, tall enough to place a junction. */
.place-map{
  height:170px; margin-top:12px; border-radius:12px;
  border:1.5px solid var(--track,#ADC3B9); overflow:hidden;
}
@media (max-width: 560px){ .place-map{ height:150px; } }
/* Round-91: honours the reader's own setting. A sweeping highlight behind text is exactly the
   kind of motion people turn this off for. */
@media (prefers-reduced-motion: reduce){
  .map-skel:not(.leaflet-container)::after{ animation:none; opacity:.35; }
}

/* Round-94: the ⚙ filters/range button reads "on" when a custom range or pin filter is applied —
   white like the selected band, so the row tells the truth about what the map is filtered to. */
#timeSwitch [data-pin-filters].on, .base-switch [data-pin-filters].on{
  background:#fff; color:var(--deep, #2C3B36); box-shadow:inset 0 0 0 2px var(--green, #618D7C);
}

/* Round-94: the incident panel borrows the stories action styling verbatim */
.inc-react .story-act{ padding:0; border:0; background:transparent; font-size:.85rem; font-weight:700; color:var(--ink-soft, #65756D); gap:5px; display:inline-flex; align-items:center; cursor:pointer; }
.inc-react .story-act .ico-t{ font-size:1rem; line-height:1; color:inherit; text-shadow:none; }
.inc-react .story-act .ico-t svg{ width:15px; height:15px; display:block; }
.inc-react .story-act:hover, .inc-react .story-act.on{ background:transparent; border:0; color:var(--accent, #618D7C); }

/* ── Round-94: notifications (/activity) — the composed "final-timeline" design ────────────
   04's timeline rail + 03's header and filter chips + 02's sticky day titles + 10's action
   pills. The green hero is gone; "Activity Center" keeps the hero's own deep green so the
   page still announces itself without a slab of colour. */
/* Round-94b: NO padding above the sticky block — with it, the block travelled those pixels
   before pinning and the gap under the nav visibly shrank. The spacing lives inside .ntf-top
   instead, so its natural position IS its stuck position and nothing moves. */
.ntf-wrap{ max-width:760px; padding-top:0; padding-bottom:60px; }
/* Round-94b: the notifications page sits on white — the cards, not the page, carry the colour
   (white for read, mint for unread), exactly as the mockup has it. */
html.pg-ntf, html.pg-ntf body{ background:#fff; }   /* :has twin (pre-105) */
body:has(.ntf-wrap), html:has(.ntf-wrap){ background:#fff; }
.ntf-kicker{
  display:block; font-size:.72rem; font-weight:800; letter-spacing:2px; text-transform:uppercase;
  /* Round-94: the tan the kicker wore on the green hero. Its literal value there was
     --tan-light (#DEC29A), which is legible on dark green and nearly invisible on this light
     page — so the same token pair's darker sibling, --tan, carries the look at a readable
     weight. */
  color:var(--tan, #BC8A52); margin-bottom:4px;
}
.ntf-head{ display:flex; align-items:center; gap:12px; flex-wrap:wrap; margin:0 0 14px; }
.ntf-head h1{ margin:0; font-size:clamp(1.4rem, 3vw, 1.7rem); }
/* Round-96h: caret that folds the header tools + filter chips. caretleft rotated:
   open = up (points at what collapses), closed = down. Deliberately NO transition —
   the class is Blazor-toggled (round-93 stuck-transition trap). */
.ntf-caret{
  border:0; background:#EDF2EE; width:30px; height:30px; border-radius:50%; cursor:pointer;
  display:inline-flex; align-items:center; justify-content:center; color:var(--deep, #4F8577);
}
.ntf-caret svg{ width:16px; height:16px; transform:rotate(-90deg); }   /* down (closed) */
.ntf-caret.open svg{ transform:rotate(90deg); }                        /* up (open) */
.ntf-new{ background:#C0392B; color:#fff; border-radius:999px; font-size:.72rem; font-weight:800; padding:2px 9px; }
.ntf-head-actions{ margin-left:auto; display:flex; gap:8px; flex-wrap:wrap; }
.ntf-pill{
  display:inline-flex; align-items:center; gap:6px; border:1.5px solid var(--track, #ADC3B9);
  background:#fff; border-radius:999px; padding:7px 14px; font-family:var(--font);
  font-size:.82rem; font-weight:700; color:var(--ink, #2C3B36); cursor:pointer;
}
.ntf-pill svg{ width:15px; height:15px; }
.ntf-pill:hover{ border-color:var(--accent, #618D7C); color:var(--deep, #4F8577); text-decoration:none; }
.ntf-pill.pri{ background:var(--c, #618D7C); border-color:var(--c, #618D7C); color:#fff; }
.ntf-pill.pri:hover{ color:#fff; filter:brightness(1.06); }
.ntf-more{ display:block; margin:16px auto 0; }

/* Round-94b: the kicker/title/filters block stays under the nav; JS publishes its height as
   --ntf-top-h so the day titles can dock exactly beneath it. */
.ntf-top{
  position:sticky; top:var(--header-h); z-index:3;
  background:#fff; padding-top:22px; margin:0 -4px; padding-inline:4px;
}
.ntf-chips-wrap{ display:flex; align-items:center; gap:6px; min-width:0; padding-bottom:8px; }
.ntf-arrow{
  flex:0 0 auto; width:30px; height:30px; border-radius:50%; cursor:pointer;
  border:1.5px solid var(--track, #ADC3B9); background:#fff; color:var(--deep, #4F8577);
  display:none; align-items:center; justify-content:center;
}
.ntf-arrow svg{ width:15px; height:15px; }
.ntf-arrow.right svg{ transform:rotate(180deg); }
.ntf-arrow.show{ display:inline-flex; }
.ntf-arrow:hover{ border-color:var(--accent, #618D7C); background:var(--mintbg, #EAF1EC); }
/* NB: no `scroll-behavior:smooth` here — with it, programmatic scrolls (scrollBy, even a plain
   scrollLeft assignment) animated and were cancelled before moving, so the arrows did nothing.
   The JS passes behavior:"smooth" per call instead, which is not affected. */
.ntf-chips{ display:flex; gap:8px; overflow-x:auto; margin:0; scrollbar-width:none; padding-bottom:2px; }
.ntf-chips::-webkit-scrollbar{ display:none; }
.ntf-chips::-webkit-scrollbar{ display:none; }
.ntf-chip{
  flex:0 0 auto; border:1.5px solid var(--track, #ADC3B9); background:#fff; border-radius:999px;
  padding:6px 14px; font-family:var(--font); font-size:.85rem; font-weight:700;
  color:var(--ink, #2C3B36); white-space:nowrap; cursor:pointer;
}
.ntf-chip.on{ background:var(--deep, #4F8577); color:#fff; border-color:var(--deep, #4F8577); }
.ntf-chip .b{ background:#C0392B; color:#fff; border-radius:999px; font-size:.68rem; padding:0 6px; margin-left:5px; }

.ntf-day{
  font-size:.72rem; font-weight:800; letter-spacing:.1em; text-transform:uppercase;
  color:var(--ink-soft, #55635C); margin:18px 0 10px;
  position:sticky; top:calc(var(--header-h) + var(--ntf-top-h, 104px)); z-index:2;
  background:#fff; padding:6px 0;
  transition:box-shadow .18s ease;
}
/* Round-94b: the shadow appears only while the title is actually pinned (JS toggles .stuck by
   comparing the element's box to its own resolved sticky offset) — a permanent shadow would
   sit under a heading that is merely scrolling past. */
.ntf-day.stuck{ box-shadow:0 10px 12px -10px rgba(44,59,54,.35); }
.ntf-tl{ position:relative; padding-left:56px; }
.ntf-tl:before{ content:""; position:absolute; left:22px; top:0; bottom:0; width:2px; background:var(--track, #ADC3B9); opacity:.5; }
.ntf-ev{ position:relative; margin-bottom:14px; }
.ntf-ev .node{
  position:absolute; left:-56px; top:2px; width:44px; height:44px; border-radius:50%;
  display:flex; align-items:center; justify-content:center; color:#fff; background:var(--c, #618D7C);
  border:3px solid #fff;   /* the rail passes behind the node; the ring hides the seam */
}
.ntf-ev .node svg{ width:20px; height:20px; }
.ntf-ev .c{ background:#fff; border:1px solid var(--track, #ADC3B9); border-radius:14px; padding:12px 14px; box-shadow:0 1px 3px rgba(44,59,54,.06); }
.ntf-ev.unread .c{ background:var(--mintbg, #EAF1EC); border-color:#CBDCD2; }
/* Round-94b: each card wears a WASH of its own type colour — a fire alert reads red-ish, a
   flood blue-ish, at a glance, without shouting. Unread doubles the wash rather than switching
   to mint, so "new" and "what kind" stay two separate signals. Guarded: browsers without
   color-mix keep the plain white/mint above. */
@supports (background: color-mix(in srgb, red 10%, white)){
  .ntf-ev .c{
    background:#F1EEE7; background:color-mix(in srgb, var(--c) 7%, #fff);
    border-color:color-mix(in srgb, var(--c) 24%, #fff);
  }
  .ntf-ev.unread .c{
    background:#F1EEE7; background:color-mix(in srgb, var(--c) 15%, #fff);
    border-color:color-mix(in srgb, var(--c) 40%, #fff);
  }
}
.ntf-ev-top{ display:flex; align-items:center; gap:8px; }
.ntf-ev-top .kind{ font-size:.72rem; font-weight:800; letter-spacing:.04em; text-transform:uppercase; color:var(--c, #618D7C); }
.ntf-ev-top .when{ margin-left:auto; font-size:.75rem; color:var(--ink-soft, #55635C); white-space:nowrap; }
.ntf-ev-top .dot{ width:9px; height:9px; border-radius:50%; background:#C0392B; flex:0 0 auto; }
.ntf-ev b{ display:block; margin:2px 0; }
.ntf-ev p{ margin:2px 0 8px; color:var(--ink-soft, #55635C); font-size:.9rem; }
.ntf-acts{ display:flex; align-items:center; gap:10px; flex-wrap:wrap; }
@media (max-width:560px){
  .ntf-tl{ padding-left:46px; }
  .ntf-tl:before{ left:17px; }
  .ntf-ev .node{ left:-46px; width:36px; height:36px; }
  .ntf-ev .node svg{ width:17px; height:17px; }
  .ntf-head-actions{ margin-left:0; width:100%; }
}
