/* ==========================================================================
   StreetScamRadar — single stylesheet.
   Order: tokens -> base -> chrome -> map -> panels -> sections -> dialogs
          -> responsive. The responsive block is LAST and overrides the rest.

   Colour rule: colour on the map always means SEVERITY, never category.
   Twelve categories cannot be told apart by hue; three severities can.
   Category is carried by its glyph in lists and popups instead.
   ========================================================================== */

/* --- Tokens -------------------------------------------------------------- */
:root {
  color-scheme: light;

  --ink:#101d26; --ink-2:#2b4553; --ink-soft:#5d7481; --muted:#8a9aa2;
  --paper:#f6f4ef; --surface:#ffffff; --sand:#ece8df;
  --line:#e2ded4; --line-strong:#cec9bc;

  /* --brand is overwhelmingly a TEXT and border colour — 31 uses against 7 —
     so in the dark theme it has to lift far enough to read on a dark page.
     --brand-fill is the other seven: a teal that white sits on, which is a
     different requirement and now a different token. */
  --brand:#0e5f5a; --brand-fill:#0e5f5a;
  --brand-lift:#12847b; --brand-deep:#0a3b3b; --glow:#7fe0cc;
  --on-accent:#ffffff;              /* text on a brand, pin or danger fill */

  /* One signal colour. Every report pin uses it — see js/config.js. */
  --pin:#e0713c; --pin-deep:#b84f22;
  --safe:#1f9d63; --danger:#c5382c;   /* errors and destructive actions only */

  /* The header is dark in BOTH themes, so it has tokens of its own rather
     than borrowing --ink. Flipping --ink for the dark theme would otherwise
     have turned the one already-dark bar on the page white. */
  --top-bg:#101d26; --top-ink:#ffffff; --top-ink-2:#cfe0e3; --top-ink-3:#8fb0b4;
  --top-line:#2f4855; --top-chip:#14252f;

  --map-bg:#dfe5e4;                 /* under the tiles, before they arrive */

  /* The three panels that float ON the map are frosted rather than solid, so
     the streets keep showing through them. They cannot use --surface: a solid
     fill would blank the map behind the intro card. */
  --frost:rgba(252,251,249,0.62); --frost-line:rgba(255,255,255,0.55);
  --frost-solid:rgba(255,255,255,0.94);

  /* Pale tints. Each is a wash of the colour it belongs to, and each needs a
     dark counterpart — a 4% white wash reads as the same idea where a 97%
     cream would glare. */
  --tint-warm:#fbf1ea;     --tint-warm-line:#f0d7c6;
  --tint-row:#fdf6f1;      --tint-brand:#f2f8f7;
  --tint-money:#fdf7ec;    --tint-money-line:#e6cfa8;
  --tint-harm:#fdf1ef;     --tint-harm-line:#eec3bd;
  --tint-threats:#faf2fc;  --tint-threats-line:#e3cbe9;
  --tint-hospital:#fbeceb; --tint-hospital-line:#f0c6c2;

  --gutter:4.5vw;
  /* A strip of PAGE down each side of the map, so the page can be scrolled
     past it. The wheel over the map zooms it and a finger on the map drags
     it, and the map used to run edge to edge — which left a reader no square
     inch to put the pointer on that was not the map. These rails are it. */
  --map-rail:30px;
  --radius:6px;
  --shadow:0 10px 30px rgba(13,27,38,.10);
  --shadow-sm:0 2px 8px rgba(13,27,38,.08);
}

/* --- The dark theme ------------------------------------------------------
   Set as an attribute rather than by media query alone, because the choice
   has to be a choice: js/theme.js writes data-theme from what you last picked,
   falling back to what the system asks for. index.html sets it before the
   first paint, so the page never flashes light on the way to dark.

   Not grey and white. The page is a deep blue-green ink, the surfaces sit a
   step above it rather than being black, and the two colours that carry
   meaning — the teal and the report orange — are LIFTED rather than dropped,
   because a colour that reads as an accent on cream reads as a stain on ink.
   -------------------------------------------------------------------------- */
:root[data-theme="dark"] {
  color-scheme: dark;

  --ink:#e9f1f3; --ink-2:#c3d5db; --ink-soft:#96aeb7; --muted:#7f959e;
  --paper:#0c161c; --surface:#142229; --sand:#1b2b33;
  --line:#22343d; --line-strong:#334b57;

  /* Lifted to be read as text on ink. The fill keeps white on it. */
  --brand:#49c3b1; --brand-fill:#13756c;
  --brand-lift:#6fdcc9; --brand-deep:#09312f; --glow:#7fe0cc;
  --on-accent:#ffffff;

  --pin:#f0854c; --pin-deep:#ffab7a;
  --safe:#35c281; --danger:#e8695c;

  /* A shade darker than the page, so the bar reads as chrome rather than as
     another panel floating on it. */
  --top-bg:#081116; --top-ink:#ffffff; --top-ink-2:#b7ccd3; --top-ink-3:#7f9ba3;
  --top-line:#27414d; --top-chip:#11202a;

  --map-bg:#101b21;

  /* Frost over dark tiles is a darkening, not a lightening. Same idea, and
     the blur still does most of the legibility. */
  --frost:rgba(11,20,26,0.72); --frost-line:rgba(255,255,255,0.10);
  --frost-solid:rgba(13,23,29,0.94);

  /* Washes, not creams: the same idea at 6–10% over the surface. */
  --tint-warm:#2a2119;     --tint-warm-line:#4a3726;
  --tint-row:#221b16;      --tint-brand:#122c2c;
  --tint-money:#2a2418;    --tint-money-line:#4c4026;
  --tint-harm:#2b1d1b;     --tint-harm-line:#4d3029;
  --tint-threats:#241d2b;  --tint-threats-line:#3f3149;
  --tint-hospital:#2b1c1c; --tint-hospital-line:#4d2e2e;

  /* On ink a shadow is a hole, not a lift. Deeper and tighter, and the panels
     lean on their borders instead. */
  --shadow:0 12px 34px rgba(0,0,0,.55);
  --shadow-sm:0 2px 10px rgba(0,0,0,.45);
}

/* --- Base ---------------------------------------------------------------- */
* { box-sizing:border-box; }
html { scroll-behavior:smooth; -webkit-text-size-adjust:100%; }
body { margin:0; overflow-x:hidden; background:var(--paper); color:var(--ink);
       font-family:Manrope,system-ui,Arial,sans-serif; line-height:1.55;
       font-size:15px; -webkit-font-smoothing:antialiased; }
button,input,select,textarea { font:inherit; color:inherit; }
button { cursor:pointer; }
a { color:var(--brand); }
h1,h2,h3 { line-height:1.1; letter-spacing:-.028em; font-weight:800; }
em { font-style:italic; }
:focus-visible { outline:2px solid var(--brand-lift); outline-offset:2px; border-radius:2px; }
.visually-hidden { position:absolute; width:1px; height:1px; overflow:hidden;
                   clip:rect(0 0 0 0); clip-path:inset(50%); white-space:nowrap; }
.eyebrow { margin:0 0 8px; font:500 10px/1.2 'DM Mono',monospace;
           letter-spacing:.14em; text-transform:uppercase; color:var(--muted); }
/* German says "letzte 7 Tage · öffentliche Ansicht" where English says "last 7
   days · public view", and at the tracking above that took two lines and
   pushed the heading down. Smaller and tighter here only. */
.reports-head .eyebrow { display:block; margin-bottom:4px; font-size:9px;
  letter-spacing:.09em; }
.muted-note { margin:0; color:var(--muted); font-size:12px; }

/* --- Buttons ------------------------------------------------------------- */
.primary-button { border:0; border-radius:var(--radius); background:var(--brand-fill);
  color:var(--on-accent); padding:11px 16px; font-size:13px; font-weight:800; letter-spacing:.01em; }
.primary-button:hover { background:var(--brand-lift); }
.primary-button.wide { width:100%; margin-top:18px; padding:14px; font-size:14px; }
.ghost-button { border:1px solid var(--line-strong); border-radius:var(--radius);
  background:transparent; color:inherit; padding:10px 14px; font-size:13px; font-weight:700; }
.ghost-button:hover { border-color:var(--brand); color:var(--brand); }
.ghost-button.small { padding:8px 12px; font-size:12px; }
.link-button { border:0; background:none; padding:6px 0; color:var(--brand);
  font-size:12px; font-weight:800; text-align:left; }
.link-button:hover { color:var(--brand-lift); text-decoration:underline; }

/* --- Setup banner -------------------------------------------------------- */
.setup-banner[hidden] { display:none; }
.setup-banner { display:flex; flex-wrap:wrap; gap:6px 10px; align-items:baseline;
  padding:10px var(--gutter); background:#3a2a10; color:#ffe9c2; font-size:12px; }
.setup-banner code { background:#00000038; padding:1px 5px; border-radius:3px;
  font:11px 'DM Mono',monospace; }

/* --- Top bar ------------------------------------------------------------- */
.topbar { display:flex; align-items:center; gap:22px; height:72px; padding:0 var(--gutter);
  background:var(--top-bg); color:var(--top-ink); position:relative; z-index:20; }
.brand { display:flex; align-items:center; gap:11px; color:inherit; text-decoration:none; }
.brand-text { display:flex; flex-direction:column; gap:1px; }
.brand-word { font-size:18px; font-weight:800; letter-spacing:-.035em; line-height:1.1; }
.brand-tagline { font:500 9.5px/1.2 'DM Mono',monospace; letter-spacing:.09em;
  text-transform:uppercase; color:var(--top-ink-3); }
.brand-mark { position:relative; width:26px; height:26px; border-radius:50%;
  border:2px solid var(--glow); flex:none; }
.brand-mark::after { content:""; position:absolute; inset:5px; border-radius:50%;
  background:var(--glow); opacity:.85; }
.brand .accent { color:var(--glow); }

.place-chip { display:flex; align-items:center; gap:8px; margin-right:auto;
  border:1px solid var(--top-line); border-radius:100px; background:var(--top-chip);
  color:var(--top-ink-2); padding:7px 14px; font-size:12px; font-weight:600; max-width:280px; }
.place-chip:hover { border-color:var(--brand-lift); color:var(--top-ink); }
#place-label { overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.place-ping { flex:none; width:7px; height:7px; border-radius:50%;
  background:var(--glow); box-shadow:0 0 0 3px rgba(127,227,208,.22); }

.topbar nav { display:flex; gap:22px; }
.topbar nav a { color:var(--top-ink-2); text-decoration:none; font-size:13px; font-weight:700; }
.topbar nav a:hover, .topbar nav a.active { color:var(--top-ink); }
.top-actions { display:flex; align-items:center; gap:10px; }
.topbar .ghost-button { border-color:var(--top-line); color:var(--top-ink-2); }
.topbar .ghost-button:hover { border-color:var(--glow); color:var(--top-ink); }

/* --- Map area (desktop: panels float over a full-bleed map) --------------- */
/* Page-coloured, not map-coloured: the rails either side have to LOOK like
   somewhere you can scroll, or nobody will try it. */
.map-area { position:relative; height:calc(100vh - 72px); min-height:600px; max-height:860px;
  overflow:hidden; background:var(--paper); }
/* Inset by a rail on each side, and edged, so the map reads as a panel on the
   page rather than as the page itself. */
.map-frame { position:absolute; inset:0 var(--map-rail);
  border-left:1px solid var(--line-strong); border-right:1px solid var(--line-strong); }
.city-map { position:absolute; inset:0; background:var(--map-bg); cursor:grab; }
.maplibregl-map { width:100%; height:100%; font-family:Manrope,Arial,sans-serif; }
.city-map.is-picking, .city-map.is-picking .maplibregl-canvas { cursor:crosshair; }

/* On a wide screen this floats over the map, and the heading and blurb were
   sitting straight on the tiles: streets ran through the words, and the words
   ran through the streets. A translucent panel separates the two without
   hiding the map behind it. Mobile puts this in normal flow below the header,
   where there is nothing to read through, and resets all of it. */
/* Above the filter panel, not merely equal to it. Both used to sit at z-index
   3, and the filter panel comes later in the document, so it painted over the
   search results — which only became reachable once the panel's padding pushed
   the dropdown far enough down to touch it. */
.map-intro { position:absolute; z-index:4; top:30px;
  left:max(var(--gutter), calc(var(--map-rail) + 16px)); width:336px;
  padding:12px 14px 13px; border:1px solid var(--frost-line);
  border-radius:var(--radius); background:var(--frost);
  /* Lighter than it looks: most of the legibility comes from the blur, not
     from the fill, so the map keeps showing through. */
  backdrop-filter:blur(16px) saturate(1.05);
  -webkit-backdrop-filter:blur(16px) saturate(1.05);
  box-shadow:0 6px 28px rgba(20,30,28,0.10); }
/* The heading, the two paragraphs and the hover fold that used to be here are
   gone with the markup — see the comment in index.html. What is left is a
   search box, so the card is sized by the box rather than by the prose it no
   longer holds, and the top margin on .place-search goes with the heading it
   used to clear. */
.place-search { display:flex; align-items:center; gap:8px;
  padding:6px 6px 6px 12px; background:var(--surface);
  border:1px solid var(--line-strong); border-radius:var(--radius); box-shadow:var(--shadow); }
.search-icon { font-size:20px; color:var(--ink-soft); transform:rotate(-20deg); }
.place-search input { flex:1; min-width:0; border:0; outline:0; background:none; font-size:13px; }
.search-go { border:0; border-radius:4px; background:var(--ink); color:var(--paper);
  padding:9px 14px; font-size:12px; font-weight:800; }
.search-go:hover { background:var(--brand-fill); }

.search-results { position:relative; z-index:6; margin-top:6px; background:var(--surface);
  border:1px solid var(--line-strong); border-radius:var(--radius);
  box-shadow:var(--shadow); max-height:270px; overflow:auto; }
.search-results[hidden] { display:none; }
.search-hit { display:block; width:100%; border:0; background:none; text-align:left;
  padding:10px 13px; border-bottom:1px solid var(--line); }
.search-hit:last-child { border-bottom:0; }
.search-hit:hover, .search-hit:focus-visible, .search-hit.is-active { background:var(--sand); }
.search-hit b { display:block; font-size:13px; }
.search-hit span { display:block; margin-top:2px; color:var(--muted); font-size:11px;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }

/* --- Floating map controls ----------------------------------------------- */
.floating-controls { position:absolute; z-index:3; top:34px;
  right:max(var(--gutter), calc(var(--map-rail) + 16px));
  display:flex; flex-direction:column; }
.map-frame .floating-controls { top:14px; right:14px; }
/* Travel advisory, floating over the map: top right on a desktop, top left on
   a phone where the zoom controls own the right.
   Two tiers of colour, not four: scanning a map, you need "is this serious"
   before you need the exact gradation, and the words carry the gradation. */
/* Above the zoom controls rather than beside them: the hazard panel opens
   from a chip in this stack now, and on a phone it is wide enough to reach
   across the map to where those buttons are. */
.map-chips { position:absolute; z-index:4; top:14px; right:66px;
  display:flex; flex-direction:column; align-items:flex-end; gap:8px;
  max-width:230px; }
.advisory-chip { display:flex; align-items:center; gap:9px; max-width:100%;
  padding:8px 12px; border:1px solid var(--line-strong); border-radius:var(--radius);
  background:var(--surface); box-shadow:var(--shadow-sm);
  font:inherit; text-align:left; cursor:pointer; }
.advisory-chip:hover { border-color:var(--brand); }
.advisory-chip:focus-visible { outline:2px solid var(--brand-lift); outline-offset:2px; }
.advisory-chip[hidden] { display:none; }
.ac-dot { flex:none; width:9px; height:9px; border-radius:50%; background:var(--muted); }
.ac-text { display:flex; flex-direction:column; gap:1px; min-width:0; }
.ac-kicker { overflow:hidden; color:var(--muted); font:500 9.5px/1.2 'DM Mono',monospace;
  letter-spacing:.08em; text-transform:uppercase; text-overflow:ellipsis; white-space:nowrap; }
.ac-level { overflow:hidden; color:var(--ink); font-size:12.5px; font-weight:700;
  line-height:1.25; text-overflow:ellipsis; white-space:nowrap; }
/* What this chip IS, above what it currently says. Same type as .ac-kicker it
   replaces — the difference is that it never changes, so a reader learns the
   two chips once and then only reads the line underneath. */
.ac-name { overflow:hidden; color:var(--muted); font:500 9.5px/1.2 'DM Mono',monospace;
  letter-spacing:.08em; text-transform:uppercase; text-overflow:ellipsis; white-space:nowrap; }
/* The weather chip carries a drawing where the advisory chip carries a dot: the
   advisory has four levels and a dot can hold that, while the weather has
   twelve kinds and a sun, which a dot cannot. Bigger than the dot because it
   has to be legible as a picture rather than noticeable as a colour. */
.ac-sign { flex:none; width:22px; height:22px; display:flex; align-items:center;
  justify-content:center; }
.ac-sign svg { width:100%; height:100%; display:block; }
.advisory-chip.is-severe .ac-dot { background:var(--danger); }
.advisory-chip.is-severe { border-color:rgba(197,56,44,.45); }
.advisory-chip.is-notice .ac-dot { background:var(--pin); }
.advisory-chip.is-clear  .ac-dot { background:var(--safe); }
/* Nothing resolved yet: the chip is just its own name. The name line is
   normally a small caption ABOVE the level, so with the level empty it would
   sit there as a caption for nothing — it takes the level's size and weight
   instead, and the empty line goes rather than leaving a gap below it.
   .is-empty is only ever set on the advisory chip; the weather chip shares the
   class .advisory-chip but always has a level to show. */
.advisory-chip.is-empty .ac-level { display:none; }
.advisory-chip.is-empty .ac-name { color:var(--ink); font-family:inherit;
  font-size:12.5px; font-weight:700; line-height:1.25; letter-spacing:0;
  text-transform:none; }

/* Inside the dialog. */
.advisory-panel { margin:4px 0 14px; padding:14px 15px; border:1px solid var(--line);
  border-left:4px solid var(--muted); border-radius:var(--radius); background:var(--sand); }
.advisory-panel.is-severe { border-left-color:var(--danger); background:rgba(197,56,44,.07); }
.advisory-panel.is-notice { border-left-color:var(--pin);    background:rgba(224,113,60,.08); }
.advisory-panel.is-clear  { border-left-color:var(--safe);   background:rgba(31,157,99,.07); }
.advisory-level { color:var(--ink); font-size:19px; font-weight:800; line-height:1.25; }
.advisory-explain { margin-top:6px; color:var(--ink-2); font-size:13px; line-height:1.6; }

/* The facts a traveller reading a warning actually wants: the number to dial
   and the two dates. A row each, key left, value right — the prose this
   replaced said the same thing at ten times the length. */
.advisory-facts { margin-bottom:12px; border-top:1px solid var(--line); }
.advisory-facts div { display:flex; align-items:baseline; justify-content:space-between;
  gap:14px; padding:8px 1px; border-bottom:1px solid var(--line); }
.advisory-facts dt { flex:none; color:var(--ink-soft); font-size:12px; }
.advisory-facts dd { color:var(--ink); font:600 12.5px/1.4 'DM Mono',monospace;
  text-align:right; }
.advisory-context { margin-bottom:14px; color:var(--muted); font-size:11.5px;
  line-height:1.5; }
.advisory-link { display:block; margin-bottom:10px; text-align:center; text-decoration:none; }

/* ---------------------------------------------------------------------------
   The hazard layers panel: a legend and a set of switches in one.

   On the map rather than in the filter panel, because switching a layer while
   watching what it draws is the whole point, and because these are somebody
   else's official data rather than a filter on our reports.
   --------------------------------------------------------------------------- */
/* A drawer handle under the chips, not a chip of its own: the panel it opens
   is named in its label and its tooltip, and a second heading over a stack
   that already carries two would say less than the caret does. The negative
   margin closes the stack's gap, so it reads as hanging off the chip above
   rather than floating under it. */
.hazard-menu { position:relative; max-width:100%; margin-top:-8px; }
/* The weather chip and its legend move as one, so the legend opens against the
   chip rather than against the stack. Unlike .hazard-menu this one is NOT
   pulled up into what is above it: the chip is the thing, the caret hangs off
   the chip, and there is nothing above either of them to join. */
.weather-menu { position:relative; display:flex; flex-direction:column;
  align-items:flex-end; max-width:100%; }
.weather-tab { width:38px; height:19px; margin-top:-1px; }
.weather-legend { width:258px; }
.hazard-tab { display:flex; align-items:center; justify-content:center;
  width:46px; height:21px; padding:0;
  border:1px solid var(--line-strong); border-top:0;
  border-radius:0 0 var(--radius) var(--radius);
  background:var(--surface); box-shadow:var(--shadow-sm); cursor:pointer; }
/* Nothing above it to hang from — no advisory and no weather warning here —
   so it closes up into a pill of its own rather than a tab cut off at the top. */
.map-chips > .hazard-menu:first-child { margin-top:0; }
.map-chips > .hazard-menu:first-child .hazard-tab { border-top:1px solid var(--line-strong);
  border-radius:var(--radius); }
.hazard-tab:hover { border-color:var(--brand); }
.hazard-tab:focus-visible { outline:2px solid var(--brand-lift); outline-offset:2px; }
.hazard-tab[aria-expanded="true"] { background:var(--brand-fill); border-color:var(--brand-fill); }
.hazard-caret { color:var(--muted); font-size:10px; line-height:1;
  transition:transform .15s ease; }
.hazard-tab:hover .hazard-caret { color:var(--brand); }
.hazard-tab[aria-expanded="true"] .hazard-caret { color:var(--on-accent);
  transform:rotate(180deg); }
@media (prefers-reduced-motion: reduce) { .hazard-caret { transition:none; } }

/* Six rows now, one per kind, so every millimetre of it is tighter than when
   it held three. It still has to sit over the map without covering it. */
.hazard-panel { position:absolute; z-index:4; top:calc(100% + 6px); right:0;
  width:272px; padding:11px 12px 9px;
  border:1px solid var(--line-strong); border-radius:var(--radius);
  background:var(--surface); box-shadow:var(--shadow); }
.hazard-panel[hidden] { display:none; }
/* The title stands alone now — the eyebrow above it said "Official sources"
   and the footer names them outright — so it centres against the close button
   instead of hanging at the top of it. */
.hp-head { display:flex; align-items:center; justify-content:space-between; gap:10px;
  margin-bottom:4px; }
.hp-head h2 { margin:1px 0 0; font-size:14px; line-height:1.2; }

.hazard-row { display:flex; align-items:center; gap:8px; padding:5px 0;
  border-top:1px solid var(--line); cursor:pointer; }
.hazard-row input { flex:none; width:15px; height:15px; accent-color:var(--brand); }
.hz-mark { flex:none; width:16px; height:16px; display:flex; align-items:center;
  justify-content:center; font-size:13px; line-height:1; }
/* Warning signs are drawn, not typed — js/hazard-signs.js. */
.hazard-sign { display:block; }
/* The same ring the map draws, so the panel is a real key to it. */
.hz-quake { border:2.2px solid #5c2d91; border-radius:50%;
  background:rgba(92,45,145,.09); }
/* The weather row is a chip rather than a map mark, so its swatch is the
   shape of the chip it appears in. */
.hz-weather { border-radius:3px; background:rgba(224,113,60,.18);
  border:1.5px solid var(--pin); }

.hz-text { display:flex; flex-direction:column; min-width:0; flex:1; }
.hz-name { overflow:hidden; color:var(--ink); font-size:12.5px; font-weight:600;
  line-height:1.25; text-overflow:ellipsis; white-space:nowrap; }
.hz-note { overflow:hidden; color:var(--muted); font-size:10.5px; line-height:1.3;
  text-overflow:ellipsis; white-space:nowrap; }
.hazard-row:has(input:not(:checked)) .hz-name,
.hazard-row:has(input:not(:checked)) .hz-mark { opacity:.45; }

/* The GDACS switches, in the filter column under Hospitals.
   A disclosure, closed by default: five switches most readers set once should
   not cost five rows of the page on every visit. The summary carries a count so
   it is worth glancing at while shut. */
.disaster-section { padding:0; }
.disaster-summary { display:flex; align-items:center; gap:8px; padding:9px 0;
  cursor:pointer; list-style:none; }
.disaster-summary::-webkit-details-marker { display:none; }
/* The caret is drawn rather than borrowed from the browser, so it turns with
   the disclosure and matches the one on the map chips. */
.disaster-summary::after { content:'\25BE'; margin-left:auto; color:var(--muted);
  font-size:10px; line-height:1; transition:transform .15s ease; }
.disaster-section[open] > .disaster-summary::after { transform:rotate(180deg); }
@media (prefers-reduced-motion: reduce) {
  .disaster-summary::after { transition:none; }
}
.disaster-summary:hover .ds-name { color:var(--brand); }
.disaster-summary:focus-visible { outline:2px solid var(--brand-lift); outline-offset:2px; }
.ds-name { color:var(--ink); font-size:13px; font-weight:700; }
.ds-note { overflow:hidden; color:var(--muted); font:11px/1.4 'DM Mono',monospace;
  text-overflow:ellipsis; white-space:nowrap; }
/* No panel chrome in here: it is already inside the filter column, so the
   border, background and shadow .hazard-panel carried over the map would be a
   box inside a box. */
.hazard-rows { padding:0 0 4px; }
.hazard-rows .hazard-row:first-of-type { border-top:1px solid var(--line); }

.hp-foot { margin:7px 0 0; padding-top:7px; border-top:1px solid var(--line);
  color:var(--muted); font-size:10px; line-height:1.45; }
.hp-foot + .hp-foot { margin-top:3px; padding-top:0; border-top:0; }

/* Weather warnings are listed under their own heading, because a national met
   service and GDACS are different people saying different things. */
.dialog-subhead { margin:14px 0 2px; color:var(--ink-soft);
  font:500 10px/1.3 'DM Mono',monospace; letter-spacing:.08em; text-transform:uppercase; }

/* One row per ongoing hazard inside the dialog. */
/* Sign on the left, what it is and where on the right. The sign spans both
   lines, so a row reads as one thing rather than a list of three. */
.disaster-row { display:grid; grid-template-columns:26px 1fr; gap:2px 11px;
  align-items:start; padding:11px 0; border-bottom:1px solid var(--line); }
.disaster-row > :not(.disaster-sign) { grid-column:2; }
.disaster-sign { grid-row:1 / span 4; margin-top:1px; }
.disaster-row:first-child { padding-top:2px; }
.disaster-kind { color:var(--ink-soft); font:500 10px/1.3 'DM Mono',monospace;
  letter-spacing:.08em; text-transform:uppercase; }
.disaster-row.is-severe .disaster-kind { color:var(--danger); }
.disaster-name { margin-top:3px; color:var(--ink); font-size:14.5px; font-weight:700;
  line-height:1.35; }
.disaster-when { margin-top:3px; color:var(--muted); font:11px/1.5 'DM Mono',monospace; }
.disaster-link { display:inline-block; margin-top:6px; color:var(--brand);
  font-size:12px; font-weight:700; }
/* The second link in a row — the issuing service's own page. Quieter than the
   first because MeteoAlarm is the one that always works and always speaks the
   reader's language; this one is the authority behind it. */
.disaster-link.is-quiet { margin-left:12px; color:var(--muted); font-weight:600; }
.disaster-link.is-quiet:hover { color:var(--brand); }
/* A weather warning that has not started yet. Held back rather than greyed:
   grey means "this is over" everywhere else here, and a red wind warning for
   Friday morning is the opposite of over — it is the one you can still act on.
   The row says "From Fri 06:00" in words, so this only has to be quieter. */
.disaster-row.is-upcoming { opacity:.72; }
.disaster-row.is-upcoming .disaster-when { color:var(--brand); font-weight:700; }

/* Hazards. The ring on the map is drawn by MapLibre; these are the popup. */
/* A warning sign is already a badge; a tinted disc around it was a badge on a
   badge, and at 34px it cropped the corners off the triangle. */
.popup-glyph.is-hazard { width:30px; height:30px; background:none; border:0; }
/* The agency's own grading, said on every hazard. Quiet for a green one and
   loud for a red one, because that IS the information. */
.popup-grade { margin-top:6px; font-size:11.5px; font-weight:700; letter-spacing:.01em; }
.popup-grade.is-severe  { color:var(--danger); }
.popup-grade.is-notice  { color:var(--pin-deep); }
.popup-grade.is-routine { color:var(--ink-soft); font-weight:600; }

.hazard-tsunami { margin-top:7px; padding:6px 9px; border-radius:4px;
  background:rgba(197,56,44,.1); color:var(--danger); font-size:12px; font-weight:700; }
/* "Not yet in force." The same shape as the tsunami line because it does the
   same job — correcting what the marker would otherwise imply — but in the
   brand colour rather than the danger one: this is lead time, which is good
   news, not a second warning on top of the first. */
.hazard-upcoming { margin-top:7px; padding:6px 9px; border-radius:4px;
  background:var(--tint-brand); color:var(--brand); font-size:12px; font-weight:700; }
/* When the warning lifts, or when it begins — the thing a weather popup is
   really for, and for a long time the smallest line on it. It sat in grey
   monospace under a divider while "See it on MeteoAlarm" was a filled green
   button, which made leaving the site louder than the answer. The band is
   warm rather than graded: the line above it already says orange or red, and
   two coloured blocks arguing about severity is one too many. */
.hazard-when { display:flex; align-items:baseline; gap:9px; margin:9px 0 0;
  padding:9px 12px; border-radius:var(--radius);
  background:var(--tint-warm); border:1px solid var(--tint-warm-line); }
.hw-label { flex:none; color:var(--ink-soft); font:500 9.5px/1.2 'DM Mono',monospace;
  letter-spacing:.1em; text-transform:uppercase; }
.hazard-when strong { color:var(--ink); font-size:15px; font-weight:800;
  letter-spacing:-.01em; }
/* Not yet in force. Lead time is good news rather than a second warning, so
   the brand colour — the same reasoning as .hazard-upcoming above. */
.hazard-when.is-ahead { background:var(--tint-brand); border-color:var(--brand-fill); }
.hazard-when.is-ahead .hw-label, .hazard-when.is-ahead strong { color:var(--brand); }
/* Said on every hazard, not buried in a page nobody opens: this is a signal
   for planning, and somebody in trouble needs the official source. */
.popup-fine { margin-top:7px; color:var(--muted); font-size:10.5px; line-height:1.45; }

.icon-button { width:38px; height:34px; border:1px solid var(--line-strong);
  background:var(--surface); color:var(--ink-2); font-size:19px; box-shadow:var(--shadow-sm); }
.icon-button:first-child { border-radius:var(--radius) var(--radius) 0 0; }
.icon-button:last-child { border-radius:0 0 var(--radius) var(--radius); border-top:0; }
.icon-button:hover { color:var(--brand); }

/* --- Filter panel -------------------------------------------------------- */
.filter-panel { position:absolute; z-index:3;
  left:max(var(--gutter), calc(var(--map-rail) + 16px)); bottom:26px; width:230px;
  background:var(--surface); border:1px solid var(--line-strong);
  border-radius:var(--radius); box-shadow:var(--shadow); overflow:hidden; }
.panel-heading { display:flex; justify-content:space-between; align-items:center;
  padding:10px 13px; border-bottom:1px solid var(--line);
  font-size:11.5px; font-weight:800; cursor:pointer; list-style:none; }
.panel-heading:hover { color:var(--brand); }
.panel-heading::-webkit-details-marker { display:none; }
/* The panel opens and closes at every width now, so the chevron is always
   there to say so — it used to appear only on a phone, where it was the only
   place the panel could be closed. */
.panel-chevron { font-size:14px; color:var(--muted); transition:transform .18s ease; }
.filter-panel[open] .panel-chevron { transform:rotate(180deg); }
.filter-panel:not([open]) .panel-heading { border-bottom:0; }
.filter-title button { border:0; background:none; color:var(--muted);
  font:inherit; font-size:10px; text-decoration:underline; padding:0;
  text-transform:none; letter-spacing:0; cursor:pointer; }
.filter-title button:hover { color:var(--brand); }
.filter-title { display:flex; justify-content:space-between; align-items:baseline; gap:8px; }
.filter-section { padding:12px 13px; }
.filter-title { margin:0 0 8px; font:500 9px 'DM Mono',monospace;
  letter-spacing:.1em; text-transform:uppercase; color:var(--muted); }
.filter-row { display:flex; align-items:center; gap:8px; min-height:26px;
  color:var(--ink-2); font-size:12px; cursor:pointer; }
.fr-note { color:var(--muted); font-size:10.5px; line-height:1.35; }
.filter-row input { accent-color:var(--brand); width:14px; height:14px; flex:none; }
.filter-row b { margin-left:auto; color:var(--muted); font:500 10px 'DM Mono',monospace; }
/* The scroll belongs to the panel, not to the scam chips. Its height is what
   is left of the map once the header, the reports card and a margin have had
   theirs, which is why it is a viewport sum rather than a number: on a laptop
   the panel used to run off the top of the screen the moment Disaster update
   was opened. */
.panel-scroll { max-height:min(420px, calc(100vh - 330px)); overflow-y:auto;
  overscroll-behavior:contain; }
.category-section { border-top:1px solid var(--line); }
/* One line, no heading, and as little padding as a row can have and still be
   a comfortable tap target. It used to be a section title plus a row plus a
   status line underneath — three lines and 24px of padding for one switch,
   all of it taken off the map. */
.safety-filter { border-top:1px solid var(--line); padding:6px 13px; }
.safety-filter .filter-row { gap:7px; min-height:24px; }
/* The status never gives way and the label does. They compete for one row, and
   the status is the half that changes as you move the map — "3 nearby" cut to
   "3 ne…" is the one thing here that would be useless, where "Hospitals near…"
   still reads. Neither wraps: a status that reflows the panel under the
   pointer is worse than one that clips. */
.fr-name { flex:0 1 auto; min-width:0; overflow:hidden;
  text-overflow:ellipsis; white-space:nowrap; }
.safety-filter .fr-note { flex:none; margin-left:auto; padding-left:6px;
  text-align:right; white-space:nowrap; }
#safety-status.is-warning { color:var(--pin-deep); font-weight:600; }
.category-filters { display:flex; flex-wrap:wrap; gap:5px; }
.cat-chip { display:inline-flex; align-items:center; gap:5px; border:1px solid var(--line-strong);
  border-radius:100px; background:var(--surface); padding:4px 9px 4px 7px;
  font-size:10.5px; color:var(--ink-2); }
.cat-chip:hover { border-color:var(--brand); }
.cat-chip[aria-pressed="false"] { opacity:.42; }
.cat-chip .glyph { font-size:12px; line-height:1; }

/* --- Reports card -------------------------------------------------------- */
.reports-card { position:absolute; z-index:3;
  right:max(var(--gutter), calc(var(--map-rail) + 16px)); bottom:26px; width:314px;
  display:flex; flex-direction:column; max-height:min(430px, calc(100% - 140px));
  background:var(--surface); border:1px solid var(--line-strong);
  border-radius:var(--radius); box-shadow:var(--shadow); overflow:hidden; }
/* The card is a <details>, and two engines disagree about what its children
   are: Chromium 131 and up wrap everything below the summary in
   ::details-content, which becomes the flex item, while older ones make the
   children themselves the items. .reports-body is the one child either way,
   and the rule below passes the column through the wrapper where there is
   one. An engine that does not know ::details-content drops that rule as
   invalid and the body is already the flex item, which is the same answer.
   Without this the list stops shrinking and scrolls the card off the map. */
.reports-card::details-content { display:flex; flex-direction:column; min-height:0; }
.reports-body { display:flex; flex-direction:column; flex:1; min-height:0; }
.reports-head { display:flex; align-items:center; justify-content:space-between;
  gap:10px; padding:12px 14px 9px; cursor:pointer; list-style:none; }
.reports-head::marker, .reports-head::-webkit-details-marker { display:none; content:''; }
.reports-head:hover .reports-head-text h2 { color:var(--brand); }
.reports-head:focus-visible { outline:2px solid var(--brand-lift); outline-offset:-2px; }
.reports-head-text { min-width:0; flex:1; }
.reports-head h2 { margin:0; font-size:16px; line-height:1.2; }
.reports-chevron { flex:none; }
.reports-card[open] .reports-chevron { transform:rotate(180deg); }
/* Closed, the head is the whole card, so the padding that was there to sit
   above the slider is not wanted. */
.reports-card:not([open]) .reports-head { padding-bottom:12px; }
.count-pill { flex:none; min-width:30px; padding:5px 10px; border-radius:100px;
  background:var(--pin); color:var(--on-accent); text-align:center;
  font:600 12px 'DM Mono',monospace; }
/* How far back.
   The filled part of the track is painted by a gradient on the input itself,
   stopping at --filled, which app.js moves. A range input will style its thumb
   and its track but gives no hook at all for "the part behind the thumb", and
   that filled span is the whole reason this reads as a length of time rather
   than as a dial. */
.age-slider { padding:2px 14px 9px; }
#age-range { -webkit-appearance:none; appearance:none; width:100%; height:18px;
  background:none; cursor:pointer; display:block; }
#age-range::-webkit-slider-runnable-track { height:6px; border-radius:100px;
  background:linear-gradient(to right,
    var(--brand-fill) 0 var(--filled, 100%), var(--line) var(--filled, 100%) 100%); }
#age-range::-moz-range-track { height:6px; border-radius:100px; background:var(--line); }
#age-range::-moz-range-progress { height:6px; border-radius:100px; background:var(--brand-fill); }
#age-range::-webkit-slider-thumb { -webkit-appearance:none; appearance:none;
  width:16px; height:16px; margin-top:-5px; border-radius:50%;
  background:var(--surface); border:2px solid var(--brand-fill);
  box-shadow:0 1px 3px rgba(0,0,0,.22); }
#age-range::-moz-range-thumb { width:16px; height:16px; border-radius:50%;
  background:var(--surface); border:2px solid var(--brand-fill);
  box-shadow:0 1px 3px rgba(0,0,0,.22); }
#age-range:focus-visible { outline:2px solid var(--brand); outline-offset:4px;
  border-radius:100px; }

/* The scale. The CURRENT value is not here — it is the line above the title,
   which already reads "Last 3 days · every report in view" and updates as the
   thumb moves. Repeating it beside the slider would be the same sentence twice
   in a 314px card. */
.age-ends { display:flex; justify-content:space-between; gap:10px; margin-top:1px;
  color:var(--muted); font-size:10px; letter-spacing:.02em; }

.report-list { overflow:auto; }
.report-entry { display:flex; gap:10px; padding:11px 14px; border-top:1px solid var(--line);
  cursor:pointer; }
.report-entry:hover { background:var(--paper); }
.report-glyph { flex:none; width:28px; height:28px; border-radius:50%; display:grid;
  place-items:center; font-size:14px; background:var(--tint-warm); border:1px solid var(--tint-warm-line); }
.report-copy { min-width:0; flex:1; }
.report-copy b { display:block; font-size:12.5px; line-height:1.35; text-wrap:pretty; }
.report-meta { display:block; margin-top:3px; color:var(--muted); font-size:11px; }

/* Confirmations are what raise a report now, so they are what the entry
   shows: a left edge that deepens, and the count spelled out. */
.report-entry.is-confirmed { border-left:3px solid var(--pin); padding-left:11px; }
.report-entry.is-confirmed-many { border-left:3px solid var(--pin-deep);
  padding-left:11px; background:var(--tint-row); }
.confirm-badge { display:inline-block; margin-top:6px; padding:2px 8px;
  border-radius:100px; background:var(--tint-warm); border:1px solid var(--tint-warm-line);
  color:var(--pin-deep); font-size:10.5px; font-weight:700; }

/* What happened, as small factual tags on a report. */
.impact-tags { display:flex; flex-wrap:wrap; gap:5px; margin-top:6px; }
.impact-tag { display:inline-flex; align-items:center; gap:4px; padding:2px 7px;
  border-radius:100px; border:1px solid var(--line-strong);
  color:var(--ink-2); font-size:10.5px; font-weight:700; white-space:nowrap; }
.impact-tag.is-money   { border-color:var(--tint-money-line); background:var(--tint-money); }
.impact-tag.is-harm    { border-color:var(--tint-harm-line); background:var(--tint-harm); }
.impact-tag.is-threats { border-color:var(--tint-threats-line); background:var(--tint-threats); }
.report-actions { display:flex; align-items:center; gap:10px; margin-top:7px; }
.chip-action { border:1px solid var(--line-strong); border-radius:100px; background:none;
  padding:3px 9px; font-size:10.5px; font-weight:700; color:var(--ink-soft); }
.chip-action:hover { border-color:var(--brand); color:var(--brand); }
.chip-action[aria-pressed="true"] { background:var(--brand-fill); border-color:var(--brand-fill); color:var(--on-accent); }
.empty-note { margin:0; padding:22px 16px; color:var(--muted); font-size:12.5px;
  border-top:1px solid var(--line); }
.gate-note { padding:13px 16px; border-top:1px solid var(--line); background:var(--sand);
  color:var(--ink-2); font-size:11.5px; line-height:1.55; }
.gate-note[hidden] { display:none; }
.gate-note button { margin-top:7px; }

.map-attribution { position:absolute; z-index:2; margin:0; right:8px; bottom:5px;
  padding:3px 7px; border-radius:3px; background:var(--frost-solid);
  color:var(--ink-soft); font-size:9.5px; }
.map-attribution a { color:inherit; }

/* --- How it works -------------------------------------------------------- */
.how-strip { display:grid; grid-template-columns:1fr 2fr; gap:34px;
  padding:58px 8vw; background:var(--brand-deep); color:#e7f2f1; }
.how-strip .eyebrow { color:var(--glow); }
.how-strip h2 { margin:0; font-size:28px; }
.how-steps { display:grid; grid-template-columns:repeat(3,1fr); gap:24px;
  margin:0; padding:0; list-style:none; }
.how-steps li { display:flex; gap:12px; }
.step-num { flex:none; width:27px; height:27px; display:grid; place-items:center;
  border:1px solid rgba(127,227,208,.5); border-radius:50%; color:var(--glow);
  font:600 12px 'DM Mono',monospace; }
.how-steps b { font-size:13px; }
.how-steps p { margin:4px 0 0; color:#a9c6c6; font-size:12px; line-height:1.6; }

/* --- Staying safe -------------------------------------------------------- */
.safety-section { display:grid; grid-template-columns:1fr 1.35fr; gap:30px 7vw;
  padding:62px 8vw; background:var(--sand); }
.safety-section h2 { margin:0; font-size:30px; max-width:340px; }
.safety-section h2 em { font-family:Georgia,serif; font-style:italic;
  font-weight:400; color:var(--brand); }
.safety-copy p { margin:0 0 14px; color:var(--ink-2); font-size:13.5px; line-height:1.72; }
.safety-callout { padding:15px 17px; border-left:3px solid var(--pin);
  background:var(--surface); color:var(--ink-2); font-weight:600; }

/* --- Dialogs ------------------------------------------------------------- */
dialog { width:min(520px, calc(100% - 28px)); max-height:88vh; max-height:88dvh;
  padding:0; border:0; border-radius:var(--radius); overflow:auto;
  background:var(--surface); color:var(--ink); box-shadow:0 24px 70px rgba(5,20,28,.45); }
dialog::backdrop { background:rgba(8,22,30,.62); backdrop-filter:blur(3px); }
.dialog-body { padding:24px; }
.dialog-header { display:flex; justify-content:space-between; gap:14px; }
.dialog-header h2 { margin:0; font-size:23px; }
.close-button { flex:none; width:34px; height:34px; border:1px solid var(--line-strong);
  border-radius:var(--radius); background:none; font-size:19px; color:var(--ink-soft); }
.close-button:hover { border-color:var(--danger); color:var(--danger); }

/* The way into your own account: an initial, not a word. "My profile" spelled
   out cost a header slot that a phone does not have to spare, and an avatar is
   what people already look for in that corner. */
.avatar-button { display:grid; place-items:center; width:34px; height:34px;
  padding:0; border:1px solid var(--brand); border-radius:50%;
  background:var(--brand-fill); color:var(--on-accent); font:800 14px/1 inherit;
  text-transform:uppercase; cursor:pointer; }
.avatar-button:hover { background:var(--brand-lift, var(--brand)); }
.avatar-button[hidden] { display:none; }

/* Language, in the header, for everyone — signed in or not. A native <select>
   on purpose: a phone renders it as its own wheel, it is reachable by keyboard
   without any code of ours, and it is the one control on this site a visitor
   may need before they can read the rest. */
.lang-picker { display:flex; align-items:center; }
.lang-picker select { padding:7px 26px 7px 10px; border:1px solid var(--top-line); border-radius:var(--radius);
  background:transparent; color:var(--top-ink-2); font-size:12.5px; cursor:pointer;
  appearance:none; -webkit-appearance:none;
  background-image:linear-gradient(45deg,transparent 50%,var(--top-ink-2) 50%),
                   linear-gradient(135deg,var(--top-ink-2) 50%,transparent 50%);
  background-position:right 12px top 52%, right 7px top 52%;
  background-size:5px 5px, 5px 5px; background-repeat:no-repeat; }
.lang-picker select:hover { border-color:var(--glow); color:var(--top-ink); }

/* The dark-mode switch. A moon that fills in when it is on — one shape in two
   states rather than two shapes, so nothing jumps as it toggles. */
.theme-button { flex:none; display:grid; place-items:center; width:32px; height:32px;
  padding:0; border:1px solid var(--top-line); border-radius:var(--radius);
  background:transparent; cursor:pointer; }
.theme-button:hover { border-color:var(--glow); }
.theme-button:focus-visible { outline:2px solid var(--glow); outline-offset:2px; }
/* One of the two, never both, and chosen by the theme rather than by script:
   the attribute is already on <html> before the first paint, so the right one
   is there in the first frame. */
.theme-icon { display:none; width:17px; height:17px; color:var(--top-ink-2);
  transition:color .18s ease; }
:root[data-theme="light"] .theme-icon.is-sun,
:root:not([data-theme="dark"]) .theme-icon.is-sun { display:block; }
:root[data-theme="dark"] .theme-icon.is-sun { display:none; }
:root[data-theme="dark"] .theme-icon.is-moon { display:block; color:var(--glow); }
.theme-button:hover .theme-icon { color:var(--top-ink); }
@media (prefers-reduced-motion: reduce) { .theme-icon { transition:none; } }
.lang-picker select:focus-visible { outline:2px solid var(--glow); outline-offset:1px; }
/* The list itself is drawn by the browser, on the page's own background. */
.lang-picker option { background:var(--paper); color:var(--ink); }
.form-note { color:var(--ink-soft); font-size:12.5px; line-height:1.6; }

.dialog-body label { display:block; margin-top:15px; font-size:11.5px; font-weight:800;
  color:var(--ink-2); }
.dialog-body input[type="email"], .dialog-body input[type="search"],
.dialog-body input[type="text"], .dialog-body input:not([type]),
.dialog-body input[type="date"], .dialog-body input[type="time"],
.dialog-body input[type="password"],
.dialog-body select, .dialog-body textarea {
  width:100%; margin-top:6px; padding:11px; border:1px solid var(--line-strong);
  border-radius:4px; background:var(--surface); font-size:13px; }
.dialog-body textarea { resize:vertical; }
.dialog-body input:focus, .dialog-body select:focus, .dialog-body textarea:focus {
  outline:2px solid var(--brand-lift); outline-offset:0; border-color:var(--brand); }
.char-count { display:block; margin-top:4px; text-align:right;
  color:var(--muted); font:10px 'DM Mono',monospace; }

/* What happened, ticked off as facts. Replaced a low/medium/high severity
   radio group: a reporter cannot grade their own risk, but they do know
   whether money went, whether anyone was hurt and whether they were
   threatened. Any combination is possible, so these are checkboxes. */
.impact-picker { margin:16px 0 0; padding:0; border:0; }
.impact-picker legend { padding:0; font-size:11.5px; font-weight:800; color:var(--ink-2); }
.legend-hint { margin-left:6px; color:var(--muted); font-size:11px; font-weight:500; }
.impact-picker label { display:flex; align-items:center; gap:9px; margin-top:8px;
  padding:10px 11px; border:1px solid var(--line-strong); border-radius:4px;
  font-size:12.5px; font-weight:600; cursor:pointer; }
.impact-picker label:has(input:checked) { border-color:var(--brand); background:var(--tint-brand); }
.impact-picker input { flex:none; accent-color:var(--brand); }
.impact-glyph { font-size:15px; line-height:1; }
.field-hint { margin:8px 0 0; color:var(--muted); font-size:11px; }

/* Date and time side by side. Native inputs, so a phone opens its own
   pickers and the 7-day range is enforced by the field itself. */
.when-row { display:flex; gap:8px; }
.when-row input { flex:1; min-width:0; }

/* --- Emergency numbers ----------------------------------------------------
   Sits in the corner of the map, for the country on screen rather than the
   country the phone is in. Numbers are tel: links: on a phone that opens the
   dialer with the number already in it. */
/* Bottom centre on a wide screen: the filter panel owns the bottom left and
   the reports card the bottom right, and this was sitting underneath the
   filter panel where nobody could read it. A phone stacks those panels below
   the map instead, so there it spans the width — see the media query. */
.emergency-bar { position:absolute; left:50%; transform:translateX(-50%);
  bottom:26px; z-index:3;
  display:flex; flex-wrap:wrap; align-items:baseline; gap:4px 10px;
  max-width:calc(100% - 20px); padding:7px 11px;
  border:1px solid var(--line-strong); border-radius:var(--radius);
  background:var(--frost-solid); backdrop-filter:blur(3px);
  box-shadow:0 2px 10px rgba(20,30,28,0.10); font-size:11.5px; }
.emergency-bar[hidden] { display:none; }
.eb-label { color:var(--ink-2); font-weight:600; }
.eb-label b { font-weight:800; }
.eb-numbers { display:flex; flex-wrap:wrap; gap:4px 9px; }
.eb-num { display:inline-flex; align-items:baseline; gap:4px; }
.eb-num span { color:var(--muted); font-size:10.5px; }
.eb-num a { color:var(--danger); font-weight:800; font-size:13px;
  text-decoration:none; letter-spacing:0.01em; }
.eb-num a:hover, .eb-num a:focus { text-decoration:underline; }

/* --- Profile dashboard ---------------------------------------------------
   Your own corner of the site: what you filed, what it collected, and the two
   settings that are actually yours to change. */
.profile-identity { margin:0 0 16px; font-size:13px; font-weight:700; }
.profile-since { display:block; margin-top:2px; color:var(--muted);
  font-size:11px; font-weight:500; }

/* --- Standing: level, bar, badges ---------------------------------------- */
.standing { margin-bottom:14px; padding:13px 14px; border:1px solid var(--line);
  border-radius:var(--radius); background:var(--paper); }
.standing-head { display:flex; align-items:baseline; justify-content:space-between; gap:10px; }
.standing-level { margin:0; display:flex; align-items:baseline; gap:7px; flex-wrap:wrap; }
.standing-number { font:700 13px/1.2 'DM Mono',monospace; color:var(--brand);
  letter-spacing:.02em; }
.standing-name { font-size:15px; font-weight:800; line-height:1.2; }
.standing-points { margin:0; color:var(--muted); font:11px/1.3 'DM Mono',monospace; }

/* The rung, not the climb. Measured from the points that reached this level to
   the points that reach the next, so it moves for everybody — a bar against
   the top of a ladder sits near zero for almost every member and tells them
   only that they are nowhere. */
.standing-bar { margin-top:10px; height:6px; border-radius:100px;
  background:var(--line); overflow:hidden; }
.standing-bar i { display:block; height:100%; border-radius:100px;
  background:var(--brand-fill); transition:width .35s ease; }
.standing-next { margin:7px 0 0; color:var(--ink-2); font-size:11.5px; line-height:1.4; }
.standing-how { margin:4px 0 0; color:var(--muted); font-size:10.5px; line-height:1.45; }

.badge-row { display:flex; flex-wrap:wrap; gap:6px; margin-top:9px; }
.badge-chip { display:inline-flex; align-items:center; gap:5px; padding:3px 9px 3px 7px;
  border:1px solid var(--tint-warm-line); border-radius:100px; background:var(--tint-warm);
  font-size:10.5px; font-weight:700; color:var(--ink-2); }

/* --- The board ----------------------------------------------------------- */
.board-row { display:flex; align-items:center; gap:9px; padding:7px 0;
  border-top:1px solid var(--line); }
.board-row:first-child { border-top:0; }
.board-rank { flex:none; width:20px; color:var(--muted);
  font:600 11px 'DM Mono',monospace; text-align:right; }
.board-name { flex:1; min-width:0; font-size:12.5px; font-weight:700;
  overflow:hidden; text-overflow:ellipsis; white-space:nowrap; }
.board-badges { flex:none; display:flex; gap:4px; }
.board-level { flex:none; color:var(--brand); font:600 10.5px 'DM Mono',monospace; }

.listed-row { margin-top:14px; }

.stat-row { display:grid; grid-template-columns:repeat(4, 1fr); gap:8px; }
/* Each tile is a button: a number you cannot act on is trivia, a number that
   shows you what it counted is a way around your own reports. */
.stat-tile { padding:11px 9px; border:1px solid var(--line); border-radius:var(--radius);
  background:var(--paper); text-align:center; cursor:pointer; font:inherit;
  transition:border-color .12s, background .12s; }
.stat-tile:hover { border-color:var(--brand); }
.stat-tile.is-active { border-color:var(--brand); background:var(--tint-brand); }
.stat-tile b { display:block; font-size:19px; line-height:1.1; color:var(--brand); }
.stat-tile span { display:block; margin-top:4px; color:var(--muted);
  font-size:10.5px; line-height:1.3; }

#profile-signout { margin-top:10px; }
.profile-section { margin-top:22px; padding-top:18px; border-top:1px solid var(--line); }
.section-head { display:flex; align-items:center; justify-content:space-between; gap:10px; }

/* Click-to-open, so a form you rarely touch is one line until you want it. */
.disclosure > summary { cursor:pointer; list-style:none; display:flex;
  align-items:center; justify-content:space-between; gap:8px; }
.disclosure > summary::-webkit-details-marker { display:none; }
.disclosure > summary::after { content:"▾"; color:var(--muted); font-size:11px;
  transition:transform .15s; }
.disclosure[open] > summary::after { transform:rotate(180deg); }
.disclosure > summary:hover { color:var(--brand); }
.settings-label { margin:14px 0 0; font-size:11.5px; font-weight:800; color:var(--ink-2); }
.settings-label.is-danger { color:var(--danger); }
/* The destructive half, fenced off from the half above it. */
.settings-danger { margin-top:20px; padding-top:16px; border-top:1px solid var(--line); }
#account-disclosure label { display:block; margin-top:12px; font-size:11.5px;
  font-weight:800; color:var(--ink-2); }
.ghost-button.is-danger { margin-top:10px; border-color:var(--danger); color:var(--danger); }
.ghost-button.is-danger:hover { background:var(--danger); color:var(--on-accent); }
.ghost-button.is-danger:disabled { opacity:.5; cursor:not-allowed; }
.ghost-button.is-danger:disabled:hover { background:none; color:var(--danger); }
.profile-section .block-title { margin:0 0 10px; }
/* Language and theme side by side: two short selects that each took a full
   row, and neither needs one. Falls back to stacking where there is no room. */
.settings-pair { display:grid; grid-template-columns:1fr 1fr; gap:10px; align-items:start; }
.settings-pair .field-hint { margin-top:6px; }
.settings-pair select { width:100%; }
@media (max-width: 420px) { .settings-pair { grid-template-columns:1fr; gap:0; } }

.inline-row { display:flex; gap:8px; align-items:stretch; }
.inline-row input { flex:1; min-width:0; }
.inline-row .ghost-button { flex:none; margin-top:6px; }

.profile-reports { max-height:320px; overflow:auto; }
.profile-report { display:flex; gap:11px; padding:11px 0; border-top:1px solid var(--line); }
.profile-report:first-child { border-top:0; padding-top:0; }
.profile-report.is-expired { opacity:0.66; }

/* The headline takes you to it on the map. */
.report-open { display:block; width:100%; padding:0; border:0; background:none;
  color:inherit; font:800 13px/1.35 inherit; text-align:left; cursor:pointer; }
.report-open:hover, .report-open:focus { color:var(--brand); text-decoration:underline; }

.withdraw-confirm { display:flex; flex-wrap:wrap; align-items:center; gap:8px;
  margin-top:8px; padding:9px 10px; border:1px solid var(--line-strong);
  border-radius:var(--radius); background:var(--paper); }
.withdraw-confirm[hidden] { display:none; }
.withdraw-confirm span { flex:1; min-width:150px; font-size:11.5px; font-weight:600; }
.chip-action.is-danger { border-color:var(--danger); color:var(--danger); }
.chip-action.is-danger:hover { background:var(--danger); color:var(--on-accent); }
.chip-action.is-primary { border-color:var(--brand-fill); background:var(--brand-fill); color:var(--on-accent); }

.edit-form { margin-top:9px; padding:11px; border:1px solid var(--line-strong);
  border-radius:var(--radius); background:var(--paper); }
.edit-form[hidden] { display:none; }
.edit-form label { display:block; margin-top:9px; font-size:11px; font-weight:800;
  color:var(--ink-2); }
.edit-form label:first-child { margin-top:0; }
.edit-form input[type="text"], .edit-form input:not([type]), .edit-form textarea,
.edit-form input[type="date"], .edit-form input[type="time"] {
  width:100%; margin-top:5px; padding:9px; border:1px solid var(--line-strong);
  border-radius:4px; background:var(--surface); font-size:12.5px; font-family:inherit; }
.edit-form textarea { resize:vertical; }
.edit-when-toggle { display:flex; align-items:center; gap:7px; margin-top:11px;
  font-size:11.5px; font-weight:600; color:var(--ink-2); cursor:pointer; }
.edit-when-toggle input { accent-color:var(--brand); }
.edit-form .when-row[hidden] { display:none; }
.edit-form .when-row { margin-top:7px; }
.edit-form .report-actions { margin-top:11px; }
.report-actions[hidden] { display:none; }
.profile-status { display:flex; flex-wrap:wrap; gap:6px; margin-top:6px;
  font-size:10.5px; font-weight:700; }
.profile-status .is-live { color:var(--safe); }
.profile-status .is-gone { color:var(--muted); }
.profile-status .is-confirms { color:var(--pin-deep); }
.profile-status .is-flagged { color:var(--danger); }

.location-block { margin-top:18px; padding:13px; border:1px solid var(--line);
  border-radius:var(--radius); background:var(--paper); }
.block-title { margin:0 0 9px; font-size:11.5px; font-weight:800; color:var(--ink-2); }
.address-row { display:flex; gap:8px; }
.address-row input { flex:1; min-width:0; margin-top:0; padding:10px;
  border:1px solid var(--line-strong); border-radius:4px; font-size:13px; }
.pin-status { margin:9px 0 0; color:var(--ink-soft); font-size:11.5px; }
.pin-status.is-set { color:var(--brand); font-weight:700; }

/* `hidden` loses to any author rule that sets display, and .ghost-button.wide
   below sets display:block — so the magic-link button needs naming here or
   it stays on screen with the attribute set. */
.or-rule[hidden], .google-button[hidden], .ghost-button.wide[hidden] { display:none; }
/* The allowance line on the report form. Its own rule because `hidden` has to
   beat .form-note, and because it should read as a quiet fact rather than a
   warning until there is nothing left. */
.form-note.is-allowance[hidden] { display:none; }
.form-note.is-allowance { font-weight:600; color:var(--ink-soft); }
.form-note.is-allowance.is-spent { color:var(--hospital, #c5382c); }
.or-rule { display:flex; align-items:center; gap:12px; margin:18px 0;
  color:var(--muted); font-size:11px; }
.or-rule::before, .or-rule::after { content:""; flex:1; height:1px; background:var(--line); }
.auth-buttons { display:flex; gap:10px; margin-top:18px; }
.auth-buttons .primary-button { flex:1; }
.auth-buttons .ghost-button { flex:1; }
.ghost-button.wide { display:block; width:100%; padding:13px; text-align:center; }
.google-button { margin-top:10px; display:flex; align-items:center; justify-content:center; gap:10px;
  width:100%; padding:13px; border:1px solid var(--line-strong); border-radius:var(--radius);
  background:var(--surface); font-size:13.5px; font-weight:700; }
.google-button:hover { border-color:var(--ink-soft); background:var(--paper); }
.g-mark { display:grid; place-items:center; width:20px; height:20px; border-radius:50%;
  background:var(--ink); color:var(--paper); font:800 12px Manrope,sans-serif; }
.fine-print { margin:16px 0 0; color:var(--muted); font-size:11px; line-height:1.6; }

/* --- Map popups ---------------------------------------------------------- */
/* Background and colour are set here on purpose. MapLibre's own stylesheet
   paints this element white with black text, and we were only overriding the
   padding, radius and shadow — so in the dark theme every popup opened as a
   white card over a dark map. It is ours to colour, not the library's. */
.maplibregl-popup-content { padding:14px 15px; border-radius:var(--radius);
  background:var(--surface); color:var(--ink);
  border:1px solid var(--line-strong);
  box-shadow:var(--shadow); font-family:Manrope,Arial,sans-serif; }
.maplibregl-popup-tip { display:none; }   /* we draw no tip, so no colour to fix */
.maplibregl-popup-close-button { top:4px; right:6px; width:28px; height:28px;
  border-radius:4px; color:var(--muted); font-size:19px; background:none; }
.maplibregl-popup-close-button:hover { background:var(--paper); color:var(--ink); }
.popup-head { display:flex; gap:10px; align-items:flex-start; padding-right:20px; }
.popup-glyph { flex:none; width:34px; height:34px; display:grid; place-items:center;
  border-radius:50%; background:var(--tint-warm); border:1px solid var(--tint-warm-line); font-size:16px; }
.popup-glyph.is-hospital { background:var(--tint-hospital); border-color:var(--tint-hospital-line); }
/* Three things can sit here now — kind, emergency department, hours — so it
   wraps rather than pushing the window wider. */
.popup-kicker { margin:0 0 6px; line-height:1.5; font:10px 'DM Mono',monospace;
  letter-spacing:.09em; text-transform:uppercase; color:var(--pin-deep); }
.popup-title { margin:2px 0 0; font-size:14px; font-weight:800; line-height:1.3;
  letter-spacing:-.015em; }
.popup-body { margin:10px 0 0; color:var(--ink-2); font-size:12.5px; line-height:1.6; }
/* Getting there and calling ahead, as the two things you would actually do. */
.popup-actions { display:flex; flex-wrap:wrap; gap:7px; margin-top:11px; }
.popup-action { display:inline-flex; align-items:center; gap:5px; padding:7px 12px;
  border:1px solid var(--line-strong); border-radius:100px; background:var(--surface);
  color:var(--ink-2); font-size:11.5px; font-weight:800; text-decoration:none;
  white-space:nowrap; }
.popup-action:hover { border-color:var(--brand); color:var(--brand); }
.popup-action.is-primary { border-color:var(--brand-fill); background:var(--brand-fill); color:var(--on-accent); }
.popup-action.is-primary:hover { background:var(--brand-deep); color:var(--on-accent); }
/* Going somewhere else, as plain text. A weather popup had two of these as
   pills with one filled in, which put "read about this elsewhere" above "when
   does it end" — see .hazard-when. They are what you read after the warning,
   so they are sized like an afterthought rather than like the answer. */
/* What a counted circle holds, on hover. Smaller and quieter than a report
   popup: it is a label on something, not a thing opened. No close button —
   see wireCellTooltip, which only binds it where there is a pointer. */
.cell-tip .maplibregl-popup-content { padding:8px 11px; border-radius:var(--radius);
  background:var(--surface); border:1px solid var(--line-strong); box-shadow:var(--shadow-sm); }
.cell-tip .maplibregl-popup-tip { border-top-color:var(--surface); }
.ct-line { margin:0; color:var(--ink); font-size:12.5px; line-height:1.4; }
.ct-line.is-quiet { color:var(--muted); font-size:11px; }

.popup-links { display:flex; flex-wrap:wrap; gap:3px 14px; margin:10px 0 0; }
.popup-link { color:var(--brand); font-size:11.5px; font-weight:700;
  text-decoration:underline; text-underline-offset:2px; }
.popup-link:hover { color:var(--ink); }
/* The agency's own measurement — "Magnitude 5.2M, Depth:10km". Monospaced
   because it is numbers and units, and it should read as something read off an
   instrument rather than as another sentence of ours. */
.popup-measure { margin:7px 0 0; color:var(--ink-2); font:12px/1.5 'DM Mono',monospace; }

/* The age, which is the one thing in this line that changes what the report
   means. Bigger than the street name beside it, and in the ink colour rather
   than the muted one. */
.popup-age { font:700 12px 'DM Mono',monospace; color:var(--ink-2);
  letter-spacing:-.01em; }
/* And tinted once it is nearly out of the window — the warm tint the rest of
   the site uses for a report, not a red one: an old report is not a worse
   report, it is one whose moment has nearly passed. */
.popup-age.is-fading { padding:2px 8px; border-radius:100px;
  background:var(--tint-warm); border:1px solid var(--tint-warm-line);
  color:var(--pin-deep); }

.popup-meta { margin:10px 0 0; padding-top:9px; border-top:1px solid var(--line);
  color:var(--muted); font:10px/1.5 'DM Mono',monospace; }

/* --- Toast --------------------------------------------------------------- */
/* A popover, so it can be over an open dialog. The reset undoes what the
   browser gives [popover] by default — centred in the viewport, with a border
   and its own colours — and puts it back at the bottom where it was. */
#toast { position:fixed; z-index:60; inset:auto; left:50%; bottom:24px;
  width:max-content; height:auto; margin:0; overflow:visible;
  max-width:calc(100% - 32px); transform:translate(-50%,160px);
  padding:13px 18px; border:0; border-radius:var(--radius);
  background:var(--ink); color:var(--paper);
  box-shadow:0 10px 30px rgba(8,22,30,.45); font-size:12.5px;
  transition:transform .28s ease; }
/* Without popover support the element is still in the flow, so it has to be
   hidden the old way: off the bottom until .show moves it up. */
#toast:not(:popover-open) { display:block; }
#toast.show { transform:translate(-50%,0); }
#toast.is-error { background:var(--danger); }

/* ==========================================================================
   Responsive — overrides everything above.
   ========================================================================== */

/* Small laptops: tighten the floating panels. */
@media (max-width:1180px) {
  :root { --gutter:26px; }
  .map-intro { width:308px; }
  .filter-panel { width:214px; }
  .reports-card { width:286px; }
  .how-strip, .safety-section { padding-left:5vw; padding-right:5vw; }
}

/* Tablets and phones: panels leave the map and stack in normal flow, so
   nothing can overlap and nothing can hide the map. */
@media (max-width:900px) {
  :root { --gutter:18px; }
  .topbar { height:62px; gap:14px; }
  .topbar nav, .place-chip, .brand-tagline { display:none; }
  .top-actions { margin-left:auto; }
  .primary-button, .ghost-button { min-height:44px; }

  :root { --map-rail:14px; }
  .map-area { height:auto; min-height:0; max-height:none; overflow:visible;
    display:flex; flex-direction:column; gap:14px; padding-bottom:22px; background:var(--paper); }
  .map-intro { position:static; order:1; width:auto; padding:22px var(--gutter) 0;
    border:0; border-radius:0; background:none; backdrop-filter:none;
    -webkit-backdrop-filter:none; box-shadow:none; }
  /* The map is the product on a phone: give it the room that headings,
     intro copy and the explainer sections were using, and put the zoom
     controls on top of it rather than in a row of their own. */
  /* In flow here, so the rails are margins rather than an inset. Narrower
     than on a desktop — a thumb needs less room to find the edge of a phone
     than a pointer needs to find 30px of a 1440px page — but they are the
     only way to scroll past a map that takes two thirds of the screen. */
  .map-frame { position:relative; inset:auto; order:2;
    width:calc(100% - 2 * var(--map-rail)); margin:0 auto;
    border-radius:var(--radius); overflow:hidden; }
  .city-map { position:relative; inset:auto; width:100%;
    height:clamp(400px,64vh,640px); }
  .floating-controls { position:absolute; top:10px; right:10px; flex-direction:column; }
  .icon-button { width:34px; height:30px; font-size:16px; }
  /* The zoom controls own the right of the map here, so the chip takes the
     left. Smaller than on a desktop: it sits on the map rather than beside it,
     and a phone's map has less to give away. The width cap keeps it clear of
     the zoom buttons however long a country's name runs. */
  .map-chips { top:12px; left:12px; right:auto; align-items:flex-start;
    max-width:min(58%, 210px); gap:6px; }
  .advisory-chip { gap:7px; padding:6px 9px; }
  /* A thumb needs more than a caret does, and the stack's gap is smaller here. */
  .hazard-menu { margin-top:-6px; }
  .hazard-tab { width:52px; height:26px; }
  /* The stack is on the left here, so the panel opens down the left too — and
     it is allowed to be wider than the chip that opened it, because the layer
     names need the room and there is nothing under it but map. */
  .hazard-panel { left:0; right:auto;
    width:min(280px, calc(100vw - 24px)); }
  /* Narrower here, so a long layer name wraps rather than being cut off — a
     truncated label is worse than an uneven row. */
  .hz-name { white-space:normal; }
  .ac-dot { width:8px; height:8px; }
  .ac-kicker { font-size:8.5px; }
  .ac-level, .advisory-chip.is-empty .ac-name { font-size:11.5px; }
  .icon-button:first-child { border-radius:var(--radius) var(--radius) 0 0; }
  .icon-button:last-child { border-radius:0 0 var(--radius) var(--radius); border-top:0; }
  .filter-panel { position:static; order:3; width:auto; margin:0 var(--gutter); }
  .category-section { max-height:none; }
  .reports-card { position:static; order:4; width:auto; margin:0 var(--gutter);
    max-height:none; }
  .report-list { max-height:360px; }
  /* No collapsing on a phone. The card is in normal flow below the map here
     and covers nothing, so there is nothing to put away — and a head that
     looked tappable but did nothing would be worse than no head at all.
     app.js holds it open; these two take away the invitation to close it. */
  .reports-head { cursor:default; }
  .reports-chevron { display:none; }
  .reports-head:hover .reports-head-text h2 { color:inherit; }
  .map-attribution { position:static; order:5; margin:0 var(--gutter);
    padding:0; background:none; font-size:10.5px; line-height:1.55; }

  .filter-row { min-height:36px; font-size:13px; }
  .filter-row input { width:17px; height:17px; }
  .cat-chip { padding:8px 12px 8px 9px; font-size:12px; }
  /* A bigger thumb for a thumb. */
  #age-range { height:26px; }
  #age-range::-webkit-slider-thumb { width:22px; height:22px; margin-top:-8px; }
  #age-range::-moz-range-thumb { width:22px; height:22px; }
  .age-ends { font-size:11px; }
  .chip-action { padding:6px 12px; font-size:11.5px; }
  .close-button { width:44px; height:44px; }

  /* Info windows: smaller, and capped so a long one scrolls inside itself
     rather than running off the map. The map also centres the pin before
     opening one — see showPopup in app.js — so it has room either side. */
  .maplibregl-popup-content { padding:11px 12px; max-height:48vh; overflow:auto; }
  .popup-title { font-size:13px; }
  .popup-kicker { font-size:9.5px; }
  .popup-glyph { width:30px; height:30px; font-size:14px; }
  .popup-body { margin-top:8px; font-size:11.5px; line-height:1.5; }
  .popup-meta { margin-top:8px; padding-top:7px; font-size:10.5px; }
  .popup-age { font-size:12px; }
  .popup-actions { margin-top:9px; gap:6px; }
  .popup-action { padding:6px 10px; font-size:11px; }
  .popup-links { margin-top:9px; gap:3px 12px; }
  .popup-link { font-size:11px; }
  .hazard-when { margin-top:8px; padding:8px 10px; gap:8px; }
  .hazard-when strong { font-size:14px; }
  .impact-tag { font-size:10px; }

  /* On a phone the bar spans the width and drops the lead-in, so the numbers
     themselves get the room. */
  .emergency-bar { left:8px; right:8px; bottom:8px; transform:none;
    max-width:none; padding:6px 9px; gap:3px 8px; }
  .eb-label { font-size:10.5px; }

  /* Four stat tiles will not fit a phone; two rows of two read fine. */
  .stat-row { grid-template-columns:repeat(2, 1fr); }
  .profile-reports { max-height:none; }

  /* 16px keeps iOS Safari from zooming the page when a field is focused. */
  .place-search input, .dialog-body input, .dialog-body select,
  .dialog-body textarea, .address-row input { font-size:16px; }

  /* Trim the prose. On a phone these read as a wall of text between the
     visitor and the map, and the map already explains itself. */
  .map-intro { padding:14px var(--gutter) 0; }
  .map-intro .eyebrow { display:none; }
  .how-strip { display:none; }

  /* Keep the one thing here that is genuinely safety information. */
  .safety-section { grid-template-columns:1fr; gap:12px; padding:26px var(--gutter); }
  .safety-section .eyebrow, .safety-section h2 { display:none; }
  .safety-copy p { display:none; }
  .safety-copy .safety-callout { display:block; margin:0; padding:13px 15px;
    font-size:12.5px; line-height:1.55; }

  /* Collapsed filter panel: one tappable row. */
  .filter-panel { border-radius:var(--radius); overflow:hidden; }
  .panel-heading { padding:13px var(--gutter); font-size:12.5px; }
}

/* Phones. */
@media (max-width:560px) {
  :root { --gutter:16px; }
  .topbar { height:auto; flex-wrap:wrap; padding:10px var(--gutter); gap:10px; }
  .brand { margin-right:auto; font-size:16px; }
  .topbar .lang-picker select { padding:6px 24px 6px 9px; font-size:12px; }
  .top-actions { width:100%; order:2; }
  .top-actions .ghost-button { flex:1; }
  .top-actions .primary-button { flex:2; }
  /* Beside the brand on the first row, where there is room going spare. */
  .lang-picker { order:1; flex:none; }
  .city-map { height:clamp(380px,62vh,600px); }
  .setup-banner { font-size:11.5px; }

  /* Full-width sheet, so the whole form is reachable on a small screen. */
  dialog { width:100%; max-width:100%; margin:auto 0 0; max-height:92vh; max-height:92dvh;
    border-radius:var(--radius) var(--radius) 0 0; }
  .dialog-body { padding:20px var(--gutter) calc(20px + env(safe-area-inset-bottom)); }
  .dialog-header h2 { font-size:20px; }
  #toast { bottom:16px; }
}

@media (prefers-reduced-motion:reduce) {
  html { scroll-behavior:auto; }
  #toast { transition:none; }
}

/* --- Reference pages ------------------------------------------------------
   guide.html and admin.html. They share the header with the map and nothing
   else: no map frame, so the body scrolls normally and the content gets a
   measure rather than the full width of a desktop. */
body.page { min-height:100vh; background:var(--paper); }
.prose-page { max-width:780px; margin:0 auto; padding:34px var(--gutter) 90px; }
.prose-head { margin-bottom:34px; }
.prose-head h1 { margin:6px 0 0; font-size:clamp(26px, 4.4vw, 38px); line-height:1.12;
  letter-spacing:-.015em; text-wrap:balance; }
.prose-lede { margin:14px 0 0; max-width:62ch; color:var(--ink-2); font-size:15.5px;
  line-height:1.6; text-wrap:pretty; }

.prose-section { margin-top:40px; padding-top:28px; border-top:1px solid var(--line); }
.prose-section h2 { margin:0 0 14px; font-size:21px; line-height:1.2; letter-spacing:-.01em; }
.prose-section h3 { margin:26px 0 8px; font-size:14.5px; letter-spacing:.01em; }
.prose-section p { margin:0 0 12px; max-width:62ch; font-size:14px; line-height:1.65;
  color:var(--ink-2); text-wrap:pretty; }
.prose-note { color:var(--muted) !important; font-size:12.5px !important; }
.prose-back { margin-top:28px !important; }
.prose-back a { color:var(--brand); font-weight:700; }

/* Two cards rather than two paragraphs: the whole point is that these are the
   two steps of a scale, and side by side is how a scale reads. */
.grade-cards { display:grid; grid-template-columns:repeat(auto-fit, minmax(230px, 1fr));
  gap:12px; margin:18px 0 6px; }
.grade-card { padding:14px 15px; border:1px solid var(--line); border-radius:var(--radius);
  background:var(--surface); border-left-width:4px; }
.grade-card p { margin:0; font-size:13px; }
.grade-card .grade-name { margin-bottom:6px; font-weight:800; font-size:13.5px; color:var(--ink); }
.grade-card.is-severe { border-left-color:#c5382c; }
.grade-card.is-notice { border-left-color:#d08a21; }

.prose-table { width:100%; border-collapse:collapse; margin:14px 0 18px; font-size:13.5px; }
.prose-table th, .prose-table td { padding:9px 10px; text-align:left; vertical-align:top;
  border-bottom:1px solid var(--line); }
.prose-table thead th { color:var(--muted); font-size:10.5px; letter-spacing:.09em;
  text-transform:uppercase; font-weight:600; }
.prose-table tbody th { font-weight:700; color:var(--ink); }
.prose-table td { color:var(--ink-2); }
.lv-n { display:inline-block; min-width:62px; color:var(--brand);
  font:600 12px 'DM Mono',monospace; }
.lv-name { font-weight:700; }

/* Three groups now, not one list: given, earned for what others confirmed,
   earned for how far the map reaches. The heading carries the distinction a
   flat row of eleven chips could not. */
.badge-group { margin:22px 0 2px; font-size:12px; letter-spacing:.02em;
  color:var(--ink-soft); }
.badge-group:first-child { margin-top:4px; }
.badge-explain { display:flex; align-items:flex-start; gap:12px; padding:11px 0;
  border-bottom:1px solid var(--line); }
.badge-explain .badge-chip { flex:none; margin-top:1px; }
.badge-explain p { margin:0; font-size:13px; }

.hp-link { color:var(--brand); font-weight:700; text-decoration:underline;
  text-underline-offset:2px; }

@media (max-width: 620px) {
  .prose-page { padding-top:22px; }
  .prose-table { font-size:13px; }
  .prose-table th, .prose-table td { padding:8px 7px; }
  .lv-n { min-width:0; display:block; }
}

/* --- Admin ---------------------------------------------------------------
   A card per member rather than a table with ten columns. A table is tidier on
   a desktop and unusable on anything narrower, and the controls here are
   selects and checkboxes that a table cell makes cramped at any width. */
/* Wider than the reference page, because this one is a working surface rather
   than something to read: a member row carries a name, an address, four
   numbers, a role, a level and four badges, and squeezing that into a reading
   measure is what made it three stacked blocks. */
.admin-page { max-width:860px; }
@media (min-width: 1120px) { .admin-page { max-width:1180px; } }
.admin-gate { padding:18px 20px; border:1px solid var(--line); border-radius:var(--radius);
  background:var(--surface); }
.admin-gate p { margin:0 0 12px; color:var(--ink-2); font-size:14px; line-height:1.6; }

.admin-toolbar { display:flex; align-items:center; gap:12px; margin-bottom:16px; }
/* Wraps rather than scrolls: five controls on one line is a line nobody can
   read on a laptop, and a filter you cannot see is a filter you forget is on
   — which is the whole reason the count beside them says "matching". */
.admin-filters { flex-wrap:wrap; }
.admin-filters input[type="search"] { flex:1 1 220px; }
.admin-filters .admin-count { margin-left:auto; }
.admin-toolbar input[type="search"] { flex:1; min-width:0; padding:10px 13px;
  border:1px solid var(--line-strong); border-radius:var(--radius);
  background:var(--surface); color:var(--ink); font:inherit; font-size:14px; }
.admin-toolbar input[type="search"]:focus-visible { outline:2px solid var(--brand);
  outline-offset:1px; }
.admin-toolbar select { flex:none; padding:10px 11px; border:1px solid var(--line-strong);
  border-radius:var(--radius); background:var(--surface); color:var(--ink);
  font:inherit; font-size:13.5px; }
.admin-count { flex:none; color:var(--muted); font:11px 'DM Mono',monospace; }

.admin-row { display:grid; gap:12px; padding:15px 16px; margin-bottom:10px;
  border:1px solid var(--line); border-radius:var(--radius); background:var(--surface); }
.ar-name { margin:0; font-size:15px; font-weight:800; display:flex; align-items:center; gap:7px; }
.ar-you { padding:1px 7px; border-radius:100px; background:var(--tint-brand);
  color:var(--brand); font:600 10px 'DM Mono',monospace; }
.ar-mail { margin:2px 0 0; color:var(--ink-2); font:12px 'DM Mono',monospace;
  overflow:hidden; text-overflow:ellipsis; }
.ar-meta { margin:4px 0 0; color:var(--muted); font-size:11.5px; }
.ar-where { margin:5px 0 0; color:var(--muted); font-size:11px;
  display:flex; flex-wrap:wrap; align-items:center; gap:4px; }
.ar-cc { padding:1px 6px; border-radius:5px; background:var(--paper);
  border:1px solid var(--line); font:600 10px 'DM Mono',monospace; color:var(--ink-2); }

.ar-controls { display:flex; flex-wrap:wrap; align-items:flex-end; gap:12px; }
.ar-field { display:flex; flex-direction:column; gap:4px; }
.ar-field > span { color:var(--muted); font-size:10.5px; letter-spacing:.06em;
  text-transform:uppercase; font-weight:600; }
.ar-field select { padding:7px 9px; border:1px solid var(--line-strong);
  border-radius:var(--radius); background:var(--surface); color:var(--ink);
  font:inherit; font-size:13px; }
.ar-check { display:flex; align-items:center; gap:7px; font-size:12.5px; color:var(--ink-2); }

.ar-badges { display:flex; flex-wrap:wrap; gap:8px; padding-top:11px;
  border-top:1px solid var(--line); }
/* The chip itself is the control. A checkbox beside a chip saying the same
   thing twice is two things to read for one decision; the chip dims when the
   badge is off, which is the same language the category filters already use. */
.ar-badge { display:inline-flex; align-items:center; gap:6px; cursor:pointer; opacity:.4; }
.ar-badge.is-on { opacity:1; }
.ar-badge input { accent-color:var(--brand-fill); }
/* Set by hand rather than counted. The outline says so without a second line
   of text beside every chip — an admin needs to tell "we decided this" from
   "the reports say this" at a glance, because only the first is theirs to
   undo. */
.ar-badge.is-hand .badge-chip { border-style:dashed; border-color:var(--brand); }
.ar-auto { border:0; background:none; padding:0 2px; cursor:pointer;
  color:var(--brand); font-size:13px; line-height:1; }
.ar-auto[hidden] { display:none; }
.ar-auto:hover { color:var(--ink); }
.ar-badge-note { margin:6px 0 0; color:var(--muted); font-size:11px; }

.ar-danger { padding-top:10px; border-top:1px solid var(--line); }
.link-danger { border:0; background:none; padding:0; color:#c5382c; font:inherit;
  font-size:12.5px; font-weight:700; cursor:pointer; text-decoration:underline;
  text-underline-offset:2px; }
.link-danger[disabled] { color:var(--muted); cursor:not-allowed; text-decoration:none; }

.primary-button.is-danger { background:#c5382c; }
.primary-button.is-danger:hover { background:#a92d23; }
.confirm-actions { display:flex; gap:10px; justify-content:flex-end; margin-top:20px; }

.admin-page code { display:block; margin-top:6px; padding:8px 10px; border-radius:8px;
  background:var(--paper); border:1px solid var(--line); color:var(--ink-2);
  font:11px/1.5 'DM Mono',monospace; overflow-x:auto; white-space:pre-wrap;
  word-break:break-word; }

@media (min-width: 720px) {
  .admin-row { grid-template-columns:minmax(0, 1fr) auto; align-items:start; }
  .ar-controls { justify-content:flex-end; }
  .ar-badges, .ar-danger { grid-column:1 / -1; }
}

/* --- Admin console: tabs and the four screens ---------------------------- */
.admin-tabs { display:flex; flex-wrap:wrap; gap:4px; margin-bottom:18px;
  padding-bottom:10px; border-bottom:1px solid var(--line); }
.admin-tab { border:1px solid transparent; border-radius:100px; background:none;
  padding:7px 14px; font:inherit; font-size:13px; font-weight:700; color:var(--muted);
  cursor:pointer; }
.admin-tab:hover { color:var(--ink); }
.admin-tab.is-on { background:var(--brand-fill); color:var(--on-accent); }
.admin-tab:focus-visible { outline:2px solid var(--brand); outline-offset:2px; }

.admin-actions { display:flex; flex-wrap:wrap; align-items:flex-end; gap:10px;
  margin-top:16px; }
.admin-actions .ar-field { flex:1; min-width:180px; }
.admin-actions input[type="text"] { padding:8px 10px; border:1px solid var(--line-strong);
  border-radius:var(--radius); background:var(--surface); color:var(--ink);
  font:inherit; font-size:13px; width:100%; }
.ghost-button.small { padding:7px 12px; font-size:12.5px; }

.admin-table { width:100%; border-collapse:collapse; font-size:13.5px; }
.admin-table th, .admin-table td { padding:9px 10px; text-align:left;
  border-bottom:1px solid var(--line); vertical-align:middle; }
.admin-table thead th { color:var(--muted); font-size:10.5px; letter-spacing:.09em;
  text-transform:uppercase; font-weight:600; }
.admin-table tbody th { font-weight:700; white-space:nowrap; }
.admin-table input { padding:6px 9px; border:1px solid var(--line-strong);
  border-radius:8px; background:var(--surface); color:var(--ink); font:inherit;
  font-size:13px; width:9ch; }
.admin-table input:disabled { background:var(--paper); color:var(--muted); }
.set-key { display:inline; padding:2px 6px; border:0; background:var(--paper);
  font:11.5px 'DM Mono',monospace; border-radius:6px; }
.set-note { color:var(--muted); font-size:12px; line-height:1.5; }

/* Reports. The text is the point, so it gets a column of its own rather than
   being clamped into a cell. */
.report-row .ar-controls { align-items:center; }
.rr-status { padding:1px 8px; border-radius:100px; font:600 10.5px 'DM Mono',monospace; }
.rr-status.is-published { background:var(--tint-brand); color:var(--brand); }
.rr-status.is-under_review { background:#f3e2c4; color:#8a5d10; }
.rr-status.is-removed { background:#f0d8d5; color:#9c2f24; }
.rr-flags { color:#c5382c; font-weight:700; }
.rr-ok { color:var(--brand); font-weight:700; }
.ar-meta > span + span { margin-left:9px; }
.rr-text { margin:8px 0 0; color:var(--ink-2); font-size:13px; line-height:1.55;
  max-width:68ch; text-wrap:pretty; }

/* Regions. A long list, so it scrolls inside its own box rather than pushing
   Save off the bottom of a 250-row page. */
.region-state { margin:0 0 14px; font-size:13.5px; font-weight:700; color:var(--brand); }
.region-state.is-closed { color:#c5382c; }
.region-list { max-height:360px; overflow:auto; border:1px solid var(--line);
  border-radius:var(--radius); background:var(--surface); }
.region-row { display:flex; align-items:center; gap:10px; padding:8px 12px;
  border-bottom:1px solid var(--line); cursor:pointer; font-size:13px; }
.region-row:last-child { border-bottom:0; }
.region-row:hover { background:var(--paper); }
.region-row.is-on { background:var(--tint-warm); }
.rg-name { flex:1; font-weight:700; }
.rg-kind { color:var(--muted); font:10.5px 'DM Mono',monospace; }
.rg-count { width:3ch; text-align:right; color:var(--muted);
  font:11px 'DM Mono',monospace; }

@media (max-width: 620px) {
  .admin-tab { padding:7px 11px; font-size:12.5px; }
  .admin-table th, .admin-table td { padding:8px 6px; }
  .set-note { display:none; }
}

/* --- Admin: locks and notices -------------------------------------------- */
/* The lock form is a grid of short fields with two that want a whole row. */
.lock-form, .notice-form { display:grid; gap:12px; margin:16px 0 20px;
  grid-template-columns:repeat(auto-fit, minmax(180px, 1fr)); align-items:end; }
.lock-form .ar-field.wide, .notice-form .ar-field.wide { grid-column:1 / -1; }
.lock-form .lock-scopes, .lock-form .admin-actions,
.notice-form .admin-actions { grid-column:1 / -1; }
.lock-scopes { display:grid; gap:8px; border:1px solid var(--line); border-radius:var(--radius);
  padding:12px 14px; margin:0; }
.lock-scopes legend { font-size:11px; font-weight:700; color:var(--muted);
  text-transform:uppercase; letter-spacing:.06em; padding:0 4px; }
.scope-pick { display:flex; gap:10px; align-items:flex-start; font-size:13px; }
.scope-pick span { display:flex; flex-direction:column; gap:2px; }
.scope-pick em { font-style:normal; font-size:11.5px; color:var(--muted); }
/* The one switch that takes information away from travellers reads differently
   from the five that only hide a third party's feed. */
.scope-pick.is-heavy strong { color:var(--hospital, #c5382c); }
.prose-note.is-warning { border-left:3px solid var(--hospital, #c5382c); padding-left:12px; }
/* An expired lock or a notice that is gone stays listed, greyed, so that
   "we paused this last month" is answerable. */
.admin-table tr.is-done { opacity:.55; }
.notice-kind { font-size:11px; font-weight:700; padding:2px 7px; border-radius:999px;
  border:1px solid var(--line-strong); white-space:nowrap; }
.notice-kind.is-info { color:var(--muted); }
.notice-kind.is-warning { color:#9a5b00; border-color:#e0a24a; }
.notice-kind.is-danger { color:#fff; background:var(--hospital, #c5382c); border-color:transparent; }

/* --- Notices and pauses, over the map ----------------------------------- */
/* Across the top, under the header, above the chips. A notice is the one thing
   on this map a person wrote on purpose for the country in front of you. */
.notice-band { position:absolute; left:50%; transform:translateX(-50%); top:12px; z-index:4;
  display:flex; flex-direction:column; gap:6px;
  width:min(620px, calc(100% - 20px)); }
.notice-band[hidden] { display:none; }
.nb-item { display:flex; gap:9px; align-items:flex-start; text-align:left;
  padding:9px 12px; border-radius:var(--radius); font-size:12.5px; line-height:1.45;
  border:1px solid var(--line-strong); background:var(--surface);
  box-shadow:0 2px 10px rgba(0,0,0,.09); }
.nb-item b { display:block; font-size:13px; }
.nb-item em { font-style:normal; color:var(--ink-2); }
.nb-glyph { flex:none; font-size:14px; line-height:1.3; }
/* Three voices. Serious gets the filled treatment; information stays quiet,
   because a festival warning in the same red as a border closure teaches a
   reader to ignore both. */
.nb-item.is-info { border-left:3px solid var(--line-strong); }
.nb-item.is-warning { border-left:3px solid #e0a24a; }
.nb-item.is-danger { border-left:3px solid var(--hospital, #c5382c); }
.nb-item.is-danger b { color:var(--hospital, #c5382c); }
/* A paused layer reads as a note about the map, not as a warning about the
   place — it is our doing, not the country's. */
.nb-item.is-paused { border-left:3px dashed var(--line-strong); color:var(--ink-2); }
@media (max-width: 640px) {
  .notice-band { top:8px; width:calc(100% - 16px); }
  .nb-item { font-size:12px; padding:8px 10px; }
}

/* --- Country typeahead, in the console ----------------------------------- */
/* The list floats over whatever is under it rather than pushing the form
   around as it opens and closes. */
.ar-field.has-suggest { position:relative; }
.suggest-list { position:absolute; top:100%; left:0; right:0; margin-top:4px;
  max-height:240px; z-index:8; }
.search-hit .hit-code { float:right; font-family:var(--mono, monospace);
  font-size:11px; color:var(--muted); font-weight:600; }
/* What the codes in the field mean, read back. Quiet, because it confirms
   rather than instructs. */
.suggest-echo { display:block; margin-top:5px; font-style:normal;
  font-size:11.5px; color:var(--muted); min-height:1em; }
.suggest-echo.is-unknown { color:var(--hospital, #c5382c); }
