:root{
  --bg-deep:#080d14;
  --bg-panel:#0f1620;
  --bg-raised:#161f2c;
  /* One step BELOW the feed panel, not above it. The case-developments strip
     replaces the feed in place, so it has to read as a different surface at a
     glance; recessing it also lets the rows (--bg-raised) sit forward, which
     is the right relationship for a list you scroll and click. */
  --bg-sunken:#0a1119;
  --line:#273140;
  --line-soft:#1b2430;
  --text:#e7edf4;
  --text-muted:#8494aa;
  --text-dim:#53647b;
  --accent:#c9a24b;
  --accent-dim:#8a7238;
  /* Confidence: one hue (teal, 195deg), stepped by lightness AND chroma so the
     three tiers separate on two channels rather than one. Must stay monotone.
     Adjacent steps sit at delta-E ~25 under every vision type — on a par with
     the domain hues' own 19.3, which is the right relationship: a scale should
     be at least as easy to read as the categories it qualifies.
     A previous flat-chroma version of this ramp sat at 9.0 and was too subtle
     to read; the ordinal check's ΔL >= 0.06 floor passes ramps that are, in
     practice, three shades of the same thing. Check adjacent delta-E, not
     just the floor. */
  --grade-indicative:#3a5252;
  --grade-probable:#6ca0a0;
  --grade-confirmed:#a4f6f5;
  /* The same scale at TEXT weight. The fill ramp's dark end is 1.87:1 on a
     hovered card — correct for a labelled 8px swatch, unreadable as an 11px
     chip label. Trying to make one ramp do both jobs is what compressed the
     fills in the first place: the 4.5:1 floor pinned every step to the light
     end. Two sets, one hue, same order. */
  --grade-ink-indicative:#7c8d8d;
  --grade-ink-probable:#a9bebd;
  --grade-ink-confirmed:#d9f1f0;
  /* Reserved STATUS colours. --alert sits at delta-E 6.0 from sabotage
     #d95926: the red region is crowded by two domain hues and no red clears
     8, so this is the 6-8 band, legal only with secondary encoding. All three
     uses have it — the live dot and banner pulse both blink, and the trend
     delta carries an arrow glyph and a number — and none of them ever render
     beside a sabotage swatch that a reader must tell them apart from. These were previously --grade-confirmed and
     --grade-probable, which meant the live dot, the new-event banner, the
     "incidents are up" chip and the weak-source warning were all painted with
     a data value — so any change to the confidence scale silently repainted
     four status indicators, and the scale could never move without breaking
     them. Status is not a series and does not belong on the data ramp. */
  --alert:#d14343;
  --warn:#d9a441;
  --calm:#5fb87c;
  /* Links had been reading --cat-cyber — the same borrowing problem as the
     status colours above, and it left anchor text at 4.30:1 on a hovered card,
     under the 4.5:1 floor for body text. Same blue to the eye, its own token,
     and 4.65:1 on the worst ground it lands on. */
  --link:#5aa9e6;
  /* Domain hues. Values AND their order in app.js are load-bearing. */
  --cat-cyber:#3987e5;
  --cat-sabotage:#d95926;
  --cat-maritime:#199e70;
  --cat-migration:#c98500;
  --cat-airspace:#d55181;
  --cat-economic:#008300;
  --cat-disinformation:#9085e9;
  --cat-espionage:#e66767;
  --font-display:"Archivo",system-ui,sans-serif;
  --font-body:"IBM Plex Sans",system-ui,sans-serif;
  --font-mono:"IBM Plex Mono",ui-monospace,Consolas,monospace;
}

*{box-sizing:border-box;}

/* V21: tell browsers/OS-native controls that Hybrid Watch is a dark UI.
   Native date-picker popovers are browser-owned, but Chromium/Safari/Firefox
   use color-scheme when deciding whether to render their dark calendar UI. */
:root{color-scheme:dark;}
html,body{margin:0;padding:0;height:100%;background:var(--bg-deep);}
body{
  font-family:var(--font-body);
  color:var(--text);
  -webkit-font-smoothing:antialiased;
  display:flex;flex-direction:column;
  min-height:100vh;
  background:
    radial-gradient(ellipse at 20% -10%, rgba(34,126,189,0.055), transparent 55%),
    radial-gradient(ellipse at 100% 110%, rgba(201,162,75,0.045), transparent 50%),
    var(--bg-deep);
}
a{color:var(--link);}
:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}

/* ---------- status bar ---------- */
.statusbar{
  display:flex;align-items:center;gap:18px;
  padding:8px 18px;
  background:linear-gradient(180deg,#0f1620 0%,#0d141e 100%);
  border-bottom:1px solid var(--line);
  flex-wrap:nowrap;
  min-height:64px;
}
.brand{
  display:flex;
  flex-direction:row;
  align-items:center;
  justify-content:flex-start;
  gap:9px;
  margin-right:auto;
  min-width:0;
}
.brand-logo{
  display:block;
  width:clamp(285px,26vw,370px);
  height:auto;
  max-height:50px;
  object-fit:contain;
  object-position:left center;
  flex:none;
  filter:drop-shadow(0 0 8px rgba(37,200,232,0.12));
}
.brand .tagline{
  font-family:var(--font-mono);
  font-size:9.5px;
  color:var(--text-dim);
  letter-spacing:0.065em;
  text-transform:uppercase;
  white-space:nowrap;
  margin:0;
  flex:none;
}
@media(max-width:980px){.brand .tagline{display:none;}}

@media(min-width:641px){
  .brand{gap:5px;}
  .brand-logo{
    width:auto;
    height:50px;
    max-height:none;
  }
}

.live-pill{
  display:flex;align-items:center;gap:7px;
  font-family:var(--font-mono);font-size:11.5px;color:var(--text-muted);
  padding:5px 10px;border:1px solid var(--line);border-radius:3px;
  background:var(--bg-raised);
  box-shadow:inset 0 0 0 1px rgba(201,162,75,0.025);
}
.live-dot{
  width:7px;height:7px;border-radius:50%;background:var(--alert);
  box-shadow:0 0 6px var(--alert);
  animation:blink 2.2s ease-in-out infinite;
}
@keyframes blink{0%,100%{opacity:1;}50%{opacity:0.35;}}
.clock{font-family:var(--font-mono);font-size:12px;color:var(--text-muted);white-space:nowrap;}
.clock b{color:var(--text);font-weight:500;}
/* Dimmer than the time: the date is context you read once, the time is the
   thing that ticks. Both in the same YYYY-MM-DD/mono as the ingest stamp below
   them, so the two are comparable at a glance. */
.clock-date{color:var(--text-dim);}
.refresh-note{font-family:var(--font-mono);font-size:11px;color:var(--text-dim);white-space:nowrap;}


/* ---------- trend strip ---------- */
.trendstrip{
  display:flex;align-items:center;gap:18px;
  padding:10px 20px;background:var(--bg-panel);border-bottom:1px solid var(--line);
  flex-wrap:wrap;
}
.trend-summary{flex:none;}
.trend-headline{display:flex;align-items:baseline;gap:8px;}
.trend-headline .n{font-family:var(--font-display);font-size:22px;font-weight:800;color:var(--text);}
.trend-headline .label{font-family:var(--font-mono);font-size:10.5px;color:var(--text-dim);text-transform:uppercase;letter-spacing:0.06em;}
.trend-sub{font-family:var(--font-mono);font-size:10px;color:var(--text-dim);margin-top:2px;}

.trend-delta-block{display:flex;flex-direction:column;gap:3px;flex:none;}
.trend-delta{
  font-family:var(--font-mono);font-size:12px;padding:3px 8px;border-radius:3px;font-weight:500;
  align-self:flex-start;
}
.trend-delta.up{color:var(--alert);background:rgba(209,67,67,0.12);}
.trend-delta.down{color:var(--calm);background:rgba(95,184,124,0.12);}
.trend-delta.flat{color:var(--text-muted);background:var(--bg-raised);}

/* Staleness. Uses the reserved status colours, never the confidence ramp —
   "this data is old" is a state of the board, not a value in the data. */
.refresh-note.stale{color:var(--warn);}
.refresh-note.very-stale{color:var(--alert);}
.refresh-note .stale-flag{
  font-family:var(--font-mono);font-size:9.5px;font-weight:700;letter-spacing:0.1em;
  padding:2px 6px;border-radius:3px;margin-right:8px;vertical-align:1px;
  background:rgba(217,164,65,0.14);border:1px solid var(--warn);color:var(--warn);
}
.refresh-note.very-stale .stale-flag{
  background:rgba(209,67,67,0.14);border-color:var(--alert);color:var(--alert);
}
/* Raw counts sit beside the percentage on purpose: "+83%" off a base of six
   events reads very differently once you can see the six. */
.trend-delta-note{font-family:var(--font-mono);font-size:9.5px;color:var(--text-dim);white-space:nowrap;}

.trend-chart-col{flex:1;min-width:240px;max-width:600px;}
.trend-chart-wrap{position:relative;height:58px;}
.trend-chart{width:100%;height:100%;}
.trend-chart-wrap svg{display:block;width:100%;height:100%;overflow:visible;}
.trend-axis{position:relative;height:13px;margin-top:1px;}
.trend-axis span{
  position:absolute;top:0;transform:translateX(-50%);
  font-family:var(--font-mono);font-size:8.5px;color:var(--text-dim);
  letter-spacing:0.04em;white-space:nowrap;
}

.trend-key{display:flex;flex-direction:column;gap:2px;flex:none;max-width:220px;}
.trend-key .tk{
  display:flex;align-items:center;gap:6px;
  font-family:var(--font-mono);font-size:9.5px;color:var(--text-muted);
}
.trend-key .sw{width:8px;height:8px;border-radius:1px;flex:none;}
.trend-key .sw.confirmed{background:var(--grade-confirmed);}
.trend-key .sw.probable{background:var(--grade-probable);}
.trend-key .sw.indicative{background:var(--grade-indicative);}
.trend-key .tk-note{
  font-family:var(--font-mono);font-size:9px;color:var(--text-dim);
  line-height:1.45;margin-top:5px;
}

.trend-tooltip{
  position:absolute;pointer-events:none;background:var(--bg-raised);border:1px solid var(--line);
  border-radius:3px;padding:6px 9px;font-family:var(--font-mono);font-size:10.5px;color:var(--text);
  white-space:nowrap;opacity:0;transition:opacity .1s ease;transform:translate(-50%,-108%);
  z-index:5;line-height:1.5;box-shadow:0 6px 18px rgba(0,0,0,0.5);
}
.trend-tooltip b{color:var(--accent);font-weight:500;}
.trend-tooltip.show{opacity:1;}

/* ---------- date range row ---------- */
.rangebar{
  display:flex;align-items:center;gap:12px;flex-wrap:wrap;
  padding:9px 20px;background:var(--bg-panel);border-bottom:1px solid var(--line);
}
.rb-label{
  font-family:var(--font-mono);font-size:10px;color:var(--text-dim);
  text-transform:uppercase;letter-spacing:0.1em;flex:none;
}
.rb-presets{display:flex;gap:4px;flex:none;}
.rb-presets button{
  font-family:var(--font-mono);font-size:10.8px;letter-spacing:0.02em;
  padding:5px 10px;border-radius:3px;border:1px solid var(--line);
  background:var(--bg-raised);color:var(--text-muted);cursor:pointer;
  transition:border-color .15s ease, color .15s ease;white-space:nowrap;
}
.rb-presets button:hover{border-color:var(--text-dim);color:var(--text);}
.rb-presets button.on{
  border-color:var(--accent-dim);color:var(--accent);background:rgba(201,162,75,0.10);
}
.rb-custom{display:flex;align-items:center;gap:6px;flex:none;}
.date-entry{position:relative;display:flex;align-items:center;flex:0 0 auto;}
.rb-custom .date-input{
  width:112px;font-family:var(--font-mono);font-size:10.8px;
  background:var(--bg-raised);border:1px solid var(--line);border-radius:3px;
  color:var(--text-muted);padding:4px 27px 4px 7px;
}
.rb-custom .date-input:focus{outline:none;border-color:var(--accent-dim);color:var(--text);}
.rb-custom .date-input::placeholder{color:var(--text-dim);}
.rb-custom .date-input[aria-invalid="true"]{border-color:var(--warn);color:var(--warn);}
.date-picker-glyph{
  position:absolute;right:7px;top:50%;transform:translateY(-50%);
  width:15px;height:15px;color:var(--text-dim);pointer-events:none;z-index:1;
}
.date-picker-glyph svg{
  display:block;width:100%;height:100%;fill:none;stroke:currentColor;stroke-width:1.7;
  stroke-linecap:round;stroke-linejoin:round;
}
/* The real native date control sits transparently over the caret. Clicking it
   therefore opens the browser/OS calendar directly, with no custom picker. */
.date-picker-native{
  position:absolute;right:0;top:0;width:28px;height:100%;margin:0;padding:0;
  opacity:0;cursor:pointer;z-index:2;border:0;
  color-scheme:dark;
  background:#080d14;color:#e7edf4;
}
.date-entry:focus-within .date-picker-glyph{color:var(--accent);}
.rb-dash{color:var(--text-dim);font-size:11px;}
.rb-clear{
  font-family:var(--font-mono);font-size:10px;background:none;border:none;
  color:var(--text-dim);cursor:pointer;text-decoration:underline;padding:2px 4px;
}
.rb-clear:hover{color:var(--text);}

/* ---------- category filter row ---------- */
.catbar{
  display:flex;gap:6px;flex-wrap:wrap;padding:10px 20px;
  background:var(--bg-panel);border-bottom:1px solid var(--line);
}
.cat-chip{
  display:flex;align-items:center;gap:6px;
  font-family:var(--font-mono);font-size:10.8px;letter-spacing:0.02em;
  padding:5px 10px 5px 8px;border-radius:3px;border:1px solid var(--line);
  background:var(--bg-raised);color:var(--text-muted);cursor:pointer;
  user-select:none;transition:opacity .15s ease, border-color .15s ease;
}
.cat-chip .dot{width:8px;height:8px;border-radius:50%;flex:none;}
.cat-chip.off{opacity:0.35;}
.cat-chip:hover{border-color:var(--text-dim);}
.cat-chip .count{color:var(--text-dim);}
.cat-toggle-all{
  display:flex;align-items:center;justify-content:center;
  font-family:var(--font-mono);font-size:10.3px;font-weight:600;letter-spacing:0.06em;
  padding:5px 10px;border-radius:3px;border:1px solid var(--accent-dim);
  background:rgba(217,164,65,0.08);color:var(--accent);cursor:pointer;
  white-space:nowrap;flex:none;transition:background .15s ease,border-color .15s ease,color .15s ease;
}
.cat-toggle-all:hover{background:rgba(217,164,65,0.14);border-color:var(--accent);}
.cat-toggle-all:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}

/* ---------- layout ---------- */
/* min-height, not 0. The page was fitting the viewport exactly, so adding the
   317px analysis row had to come out of somewhere — and flex took it from the
   map, crushing #mapwrap to 161px. The legend is absolutely positioned from
   the bottom, so its top computed to -103px and it floated up over the range
   and category controls. Letting the page scroll instead is the honest answer:
   the content genuinely does not fit a short viewport any more. */
.layout{display:flex;flex:1;min-height:460px;}
@media(max-width:900px){.layout{flex-direction:column;}}

.mapwrap{position:relative;flex:1 1 62%;min-width:0;min-height:0;background:var(--bg-deep);}
#map{width:100%;height:100%;background:var(--bg-deep);}
@media(max-width:900px){
  /* Desktop pins everything to the viewport and scrolls the feed panel inside
     itself. On a phone that maths cannot close — map plus feed exceed the
     screen — so the flex children overflowed their parent and the search bar
     rendered on top of the footer. Below 900px the PAGE scrolls instead: the
     layout sizes to its content, the feed list stops being its own scroll
     container (nested scrolling is miserable on touch anyway), and the search
     header sticks so it stays reachable however far you scroll. */
  html,body{height:auto;}
  .layout{flex:0 0 auto;min-height:0;}
  .mapwrap{min-height:260px;flex:0 0 44vh;}
  .feed{flex:0 0 auto;min-height:0;max-height:none;}
  .feed-list{overflow:visible;max-height:none;flex:0 0 auto;}
  .feed-head{position:sticky;top:0;z-index:500;background:var(--bg-panel);}
}

/* Leaflet dark-theme overrides */
.leaflet-container{background:var(--bg-deep);font-family:var(--font-body);}
.leaflet-control-zoom a{
  background:var(--bg-raised) !important;color:var(--text) !important;
  border-color:var(--line) !important;
}
.leaflet-control-zoom a:hover{background:var(--line) !important;}
.leaflet-control-attribution{
  background:rgba(16,22,31,0.85) !important;color:var(--text-dim) !important;
  font-size:10px !important;
}
.leaflet-control-attribution a{color:var(--text-muted) !important;}
.leaflet-popup-content-wrapper{
  background:var(--bg-raised);color:var(--text);border:1px solid var(--line);
  border-radius:5px;box-shadow:0 10px 30px rgba(0,0,0,0.55);
}
.leaflet-popup-tip{background:var(--bg-raised);border:1px solid var(--line);}
.leaflet-popup-close-button{color:var(--text-dim) !important;}
.leaflet-popup-close-button:hover{color:var(--text) !important;}

.popup{font-family:var(--font-body);max-width:280px;}
.popup .p-top{display:flex;align-items:center;gap:7px;margin-bottom:6px;}
.popup .p-dot{width:9px;height:9px;border-radius:50%;flex:none;}
.popup .p-title{font-size:13.5px;font-weight:600;line-height:1.35;}
.popup .p-meta{
  display:flex;flex-wrap:wrap;gap:7px;align-items:center;
  font-family:var(--font-mono);font-size:10px;color:var(--text-dim);margin-bottom:7px;
}
.popup .p-grade{
  font-family:var(--font-mono);font-size:9px;letter-spacing:0.05em;text-transform:uppercase;
  padding:1.5px 6px;border-radius:2px;font-weight:500;
}
.popup .p-grade.indicative{color:var(--grade-ink-indicative);border:1px solid var(--grade-indicative);}
.popup .p-grade.probable{color:var(--grade-ink-probable);border:1px solid var(--grade-probable);}
.popup .p-grade.confirmed{color:var(--grade-ink-confirmed);border:1px solid var(--grade-confirmed);background:rgba(164,246,245,0.10);}
/* Same bounds as the feed card, and for the same event: clicking the Leipzig
   marker opened a popup 118 citations long, which on a laptop ran off the top
   and bottom of the map with no way to reach either end. The header and the
   grade chips stay put; the prose and the citations scroll. */
.popup .p-summary{
  font-size:12px;line-height:1.55;color:var(--text-muted);margin-bottom:8px;
  max-height:calc(10 * 1.55em);padding-right:2px;
}
.popup .p-sources{border-top:1px solid var(--line-soft);padding-top:7px;}
.popup .p-sources-list{
  --src-lh:1.5;
  line-height:var(--src-lh);
  max-height:calc(4 * var(--src-lh) * 1em + 6px);
  padding-right:2px;
}
.popup .p-sources-label{
  font-family:var(--font-mono);font-size:9px;color:var(--text-dim);text-transform:uppercase;
  letter-spacing:0.06em;margin-bottom:4px;
}
.popup .p-source{
  display:block;font-size:11px;line-height:1.5;color:var(--link);
  text-decoration:none;margin-bottom:2px;
}
.popup .p-source:hover{text-decoration:underline;}
.popup .p-source .pub{color:var(--text-dim);font-family:var(--font-mono);font-size:9.5px;}

.new-pulse-icon{position:relative;width:14px;height:14px;}
.new-pulse-icon .core{
  position:absolute;left:2px;top:2px;width:10px;height:10px;border-radius:50%;
}
.new-pulse-icon .ring{
  position:absolute;left:0;top:0;width:14px;height:14px;border-radius:50%;
  animation:mapping 2.4s cubic-bezier(0,0,.2,1) infinite;
}
@keyframes mapping{
  0%{transform:scale(0.5);opacity:0.9;}
  100%{transform:scale(2.4);opacity:0;}
}
@media (prefers-reduced-motion:reduce){
  .new-pulse-icon .ring{animation:none;opacity:0.3;}
  .live-dot{animation:none;}
}

.legend{
  position:absolute;left:14px;bottom:14px;z-index:400;
  background:rgba(16,22,31,0.92);border:1px solid var(--line);
  border-radius:4px;
  font-size:11.5px;color:var(--text-muted);
  max-width:230px;backdrop-filter:blur(3px);
  overflow:hidden;
}
.legend-toggle{
  display:flex;width:100%;align-items:center;justify-content:space-between;gap:9px;
  background:none;border:none;cursor:pointer;color:var(--text-muted);
  font-family:var(--font-mono);font-size:10.5px;letter-spacing:0.08em;
  text-transform:uppercase;padding:7px 10px;
}
.legend-toggle:hover{color:var(--text);background:rgba(255,255,255,0.035);}
.legend-toggle:focus-visible{outline:2px solid var(--accent);outline-offset:-2px;}
.legend-toggle .chev{transition:transform .18s ease;font-size:9px;}
.legend.collapsed{max-width:none;}
.legend.collapsed .legend-toggle .chev{transform:rotate(-90deg);}
.legend-body{padding:4px 14px 12px;}
.legend.collapsed .legend-body{display:none;}
.legend h3{
  margin:0 0 8px;font-family:var(--font-display);font-size:10.5px;
  letter-spacing:0.1em;text-transform:uppercase;color:var(--text-dim);font-weight:700;
}
.legend-row{display:flex;align-items:center;gap:7px;padding:2px 0;}
.legend-swatch{width:9px;height:9px;border-radius:50%;flex:none;}
/* wrap: three grade labels at 12px gap overflowed the 230px panel and clipped
   "Confirmed" against the right edge */
.legend-grades{display:flex;flex-wrap:wrap;gap:6px 10px;margin-top:9px;padding-top:9px;border-top:1px solid var(--line-soft);}
.legend-grade{display:flex;align-items:center;gap:5px;font-size:10.5px;}
.grade-swatch{width:9px;height:9px;border-radius:50%;flex:none;}
/* Filled like the other two, because the ramp is the encoding now — a hollow
   swatch dated from when the three tiers were unrelated hues and needed a
   shape cue to tell them apart. The dotted ring stays as redundant, non-colour
   texture, but takes the INK step so it is visible against the dark fill. */
.grade-swatch.indicative{background:var(--grade-indicative);
  border:1.5px dotted var(--grade-ink-indicative);}
.grade-swatch.probable{background:var(--grade-probable);}
.grade-swatch.confirmed{background:var(--grade-confirmed);}
@media(max-width:900px){
  .legend{font-size:10.5px;max-width:180px;left:8px;bottom:8px;}
  .legend-body{padding:2px 12px 11px;}
  .legend h3{font-size:9px;margin-bottom:5px;}
  .legend-row{padding:2px 0;}
  .legend-grades{margin-top:6px;padding-top:6px;gap:10px;flex-wrap:wrap;}
}

/* ---------- feed panel ---------- */
.feed{
  flex:1 1 38%;min-width:320px;max-width:480px;
  background:var(--bg-panel);border-left:1px solid var(--line);
  display:flex;flex-direction:column;min-height:0;
}
@media(max-width:900px){.feed{max-width:none;border-left:none;border-top:1px solid var(--line);}}

.feed-head{
  padding:12px 16px;border-bottom:1px solid var(--line);
  display:flex;flex-direction:column;gap:9px;flex:none;
}
.feed-head-top{display:flex;align-items:center;justify-content:space-between;gap:10px;}
.feed-head h2{
  margin:0;font-family:var(--font-display);font-size:12.5px;
  letter-spacing:0.1em;text-transform:uppercase;font-weight:700;color:var(--text-muted);
}
.feed-count{font-family:var(--font-mono);font-size:11px;color:var(--text-dim);white-space:nowrap;}

/* ---------- search ---------- */
.feed-search{position:relative;display:flex;align-items:center;}
.feed-search .search-icon{
  position:absolute;left:9px;width:13px;height:13px;color:var(--text-dim);
  pointer-events:none;
}
.feed-search input{
  width:100%;background:var(--bg-raised);border:1px solid var(--line);
  border-radius:4px;color:var(--text);
  font-family:var(--font-body);font-size:13px;
  padding:8px 28px 8px 28px;
  -webkit-appearance:none;appearance:none;
}
.feed-search input::placeholder{color:var(--text-dim);}
.feed-search input:focus{outline:none;border-color:var(--accent-dim);background:#1a2634;}
.feed-search input::-webkit-search-cancel-button{display:none;}
.search-clear{
  position:absolute;right:6px;background:none;border:none;cursor:pointer;
  color:var(--text-dim);font-size:17px;line-height:1;padding:2px 5px;
}
.search-clear:hover{color:var(--text);}
mark{background:rgba(201,162,75,0.28);color:var(--text);border-radius:2px;padding:0 1px;}

/* ---------- AJP-2.1 admiralty code ----------
   Deliberately quieter than the confidence chip beside it. The plain-English
   grade is what a general reader acts on; this is the intelligence-staff
   shorthand sitting behind it, and it should read as a reference marking
   rather than compete for attention. */
.ajp-chip{
  font-family:var(--font-mono);font-size:9.5px;letter-spacing:0.06em;font-weight:500;
  padding:1.5px 5px;border-radius:2px;cursor:help;
  color:var(--text-muted);border:1px solid var(--line);background:var(--bg-panel);
}
/* A weak source (D/E/F) or doubtful information (4-6) gets a visible warning
   edge — an E2 from a state outlet must not look like a B2 from a wire. */
.ajp-chip.weak{
  color:var(--warn);border-color:var(--accent-dim);
  background:rgba(217,164,65,0.10);
}

.feed-list{flex:1;overflow-y:auto;padding:6px;min-height:0;}
/* With no cards, flex:1 left the list holding the whole panel and pushed the
   empty-state message to the very bottom, far from the control that caused
   it. Collapse the list so the explanation sits where the first card would. */
.feed-list:empty{ flex:none; }
.feed-list::-webkit-scrollbar{width:9px;}
.feed-list::-webkit-scrollbar-thumb{background:var(--line);border-radius:5px;}
.feed-empty{
  padding:28px 20px;text-align:center;font-size:12.5px;color:var(--text-muted);line-height:1.6;
}
.feed-empty b{color:var(--text);}
.feed-empty .hint{color:var(--text-dim);font-size:11.5px;}

.card{
  --card-bg:var(--bg-raised);
  padding:11px 12px;margin:5px;border-radius:4px;
  background:var(--card-bg);border:1px solid var(--line-soft);
  border-left:3px solid var(--grade-indicative);
  cursor:pointer;transition:border-color .15s ease, background .15s ease;
}
.card:hover, .card.hovered{--card-bg:#1a2634;border-color:var(--accent-dim);}
.card.grade-probable{border-left-color:var(--grade-probable);}
.card.grade-confirmed{border-left-color:var(--grade-confirmed);}
.card.hidden{display:none;}
.card-top{display:flex;align-items:center;gap:8px;margin-bottom:5px;}
.card-cat-dot{width:8px;height:8px;border-radius:50%;flex:none;}
.card-title{font-size:13px;font-weight:600;line-height:1.35;flex:1;}
.new-badge{
  font-family:var(--font-mono);font-size:9px;letter-spacing:0.06em;
  color:var(--bg-deep);background:var(--accent);padding:2px 5px;border-radius:2px;
  font-weight:600;flex:none;
}
.card-meta{
  display:flex;flex-wrap:wrap;gap:8px;align-items:center;
  font-family:var(--font-mono);font-size:10.5px;color:var(--text-dim);margin-bottom:6px;
}
.grade-chip{
  font-family:var(--font-mono);font-size:9.5px;letter-spacing:0.05em;
  padding:1.5px 6px;border-radius:2px;text-transform:uppercase;font-weight:500;
}
.grade-chip.indicative{color:var(--grade-ink-indicative);border:1px solid var(--grade-indicative);}
.grade-chip.probable{color:var(--grade-ink-probable);border:1px solid var(--grade-probable);}
.grade-chip.confirmed{color:var(--grade-ink-confirmed);border:1px solid var(--grade-confirmed);background:rgba(164,246,245,0.10);}
.card-summary{font-size:12.3px;line-height:1.55;color:var(--text-muted);margin-bottom:7px;}
/* ---------- bounded card regions ----------
 *
 * A card grew to whatever its content needed, and two kinds of content made
 * that unusable in a 380px rail. The Leipzig An-124 attack carries 118 sources;
 * a Lithuanian shoot-down carries 52. 104 of 369 events have more than two, and
 * summaries run to 1,278 characters. One such card filled the feed on its own.
 *
 * Nothing is truncated and nothing is hidden — the regions scroll. The title,
 * date, grade chips and the developments badge stay visible at all times, which
 * is the part that has to be scannable; the prose and the citations are what you
 * read once you have stopped on the card.
 *
 * WHY FOUR LINES AND NOT TWO ROWS. The brief was "show two sources". A source
 * line is title + publisher and runs to 89 characters at the median, which wraps
 * to two lines at feed width — so two sources IS four lines, and a box two rows
 * tall would show one source and a sliver. Sized in lines rather than items for
 * that reason: short citations show more of them, long ones show two.
 *
 * overscroll-behavior stops the feed scrolling on underneath once one of these
 * hits its end, which is what makes a scroller this small usable at all.
 */
.card-summary, .card-sources,
.popup .p-summary, .popup .p-sources-list{
  overflow-y:auto;
  overscroll-behavior:contain;
  scrollbar-width:thin;
  scrollbar-color:var(--line) transparent;
}
.card-summary::-webkit-scrollbar, .card-sources::-webkit-scrollbar,
.popup .p-summary::-webkit-scrollbar, .popup .p-sources-list::-webkit-scrollbar{
  width:6px;
}
.card-summary::-webkit-scrollbar-thumb, .card-sources::-webkit-scrollbar-thumb,
.popup .p-summary::-webkit-scrollbar-thumb, .popup .p-sources-list::-webkit-scrollbar-thumb{
  background:var(--line);border-radius:3px;
}
.card-summary::-webkit-scrollbar-thumb:hover, .card-sources::-webkit-scrollbar-thumb:hover,
.popup .p-summary::-webkit-scrollbar-thumb:hover, .popup .p-sources-list::-webkit-scrollbar-thumb:hover{
  background:var(--muted);
}
/* The visible scrollbar is the affordance: a flush-cut box with no bar looks
   like a rendering bug rather than something you can scroll. */
/* A flush cut mid-sentence reads as a rendering fault rather than something you
 * can scroll, and there is no scrollbar to say otherwise: Chrome and macOS both
 * use OVERLAY scrollbars, measured here at zero width and hidden until you are
 * already scrolling. So the cue has to be drawn.
 *
 * It is a mask, not a shadow. A dark shadow over a #161f2c card is invisible —
 * that was the first attempt and it rendered as nothing at all. Fading the
 * content itself works on a dark ground because it is the text, not the
 * background, carrying the contrast.
 *
 * data-scroll is set by markScrollEdges() in app.js, which is where the fade
 * earns its keep: it appears ONLY on an edge you can still move toward, so a
 * region with nothing hidden is never faded and a complete summary never looks
 * truncated. CSS alone cannot know that. */
[data-scroll]{ -webkit-mask-repeat:no-repeat; mask-repeat:no-repeat; }
[data-scroll="down"]{
  -webkit-mask-image:linear-gradient(to bottom,#000 calc(100% - 24px),transparent);
          mask-image:linear-gradient(to bottom,#000 calc(100% - 24px),transparent);
}
[data-scroll="up"]{
  -webkit-mask-image:linear-gradient(to top,#000 calc(100% - 24px),transparent);
          mask-image:linear-gradient(to top,#000 calc(100% - 24px),transparent);
}
[data-scroll="both"]{
  -webkit-mask-image:linear-gradient(to bottom,transparent,#000 24px,
                                     #000 calc(100% - 24px),transparent);
          mask-image:linear-gradient(to bottom,transparent,#000 24px,
                                     #000 calc(100% - 24px),transparent);
}

.card-summary{max-height:calc(8 * 1.55em);padding-right:2px;}

.card-sources{
  --src-lh:1.45;
  display:flex;flex-direction:column;gap:2px;
  font-size:10.8px;line-height:var(--src-lh);
  max-height:calc(4 * var(--src-lh) * 1em + 6px);
  padding-right:2px;
}
.card-sources a{font-size:inherit;line-height:inherit;color:var(--link);text-decoration:none;}
.card-sources a:hover{text-decoration:underline;}
.card-sources .pub{color:var(--text-dim);font-family:var(--font-mono);font-size:9.5px;}
/* Says how many are down there, so the scrollbar is not the only clue. Rendered
   only when it tells you something you cannot already see. */
.card-src-count{
  font-family:var(--font-mono);font-size:9px;color:var(--text-dim);
  text-transform:uppercase;letter-spacing:0.06em;margin-bottom:3px;
}

/* ---------- alert banner ---------- */
.banner{
  position:fixed;top:14px;left:50%;transform:translateX(-50%) translateY(-140%);
  z-index:900;
  background:var(--bg-raised);border:1px solid var(--accent-dim);
  box-shadow:0 8px 28px rgba(0,0,0,0.5);
  border-radius:5px;padding:11px 16px;
  display:flex;align-items:center;gap:12px;
  max-width:min(560px, 92vw);
  transition:transform .35s cubic-bezier(.2,.8,.2,1);
}
.banner.show{transform:translateX(-50%) translateY(0);}
.banner .pulse-icon{
  width:9px;height:9px;border-radius:50%;background:var(--alert);
  box-shadow:0 0 8px var(--alert);flex:none;
  animation:blink 1.4s ease-in-out infinite;
}
@media (prefers-reduced-motion:reduce){.banner .pulse-icon{animation:none;}}
.banner-text{font-size:12.5px;line-height:1.4;}
.banner-text b{color:var(--accent);}
.banner-close{
  margin-left:4px;background:none;border:none;color:var(--text-dim);
  cursor:pointer;font-size:16px;line-height:1;padding:2px 4px;flex:none;
}
.banner-close:hover{color:var(--text);}

/* ---------- footer disclaimer ---------- */
.disclaimer{
  padding:9px 20px;background:var(--bg-panel);border-top:1px solid var(--line);
  font-size:10.8px;color:var(--text-dim);line-height:1.5;
}
.disclaimer strong{color:var(--text-muted);}

.loading-note{
  padding:40px;text-align:center;font-family:var(--font-mono);
  color:var(--text-dim);font-size:12px;
}

/* ---------- phone ----------
   Three problems this block fixes, all reported from a real device:
   the reports were too small to read, the legend covered every popup, and
   the map/feed split left the feed squeezed into a sliver. */
@media(max-width:640px){
  .statusbar{
    display:grid;
    grid-template-columns:auto 1fr auto;
    align-items:center;
    gap:7px 10px;
    padding:8px 12px 9px;
    min-height:0;
  }
  .brand{
    grid-column:1 / -1;
    width:100%;
    margin:0;
    justify-content:center;
  }
  .brand-logo{
    width:min(305px,84vw);
    max-height:50px;
    object-position:center center;
  }
  .brand .tagline{display:none;}
  .live-pill{
    grid-column:1;
    justify-self:start;
    font-size:10.5px;
    padding:4px 8px;
  }
  .clock{
    grid-column:3;
    justify-self:end;
    font-size:10.5px;
    letter-spacing:-0.01em;
  }
  .refresh-note{
    display:block;
    grid-column:1 / -1;
    justify-self:center;
    width:100%;
    text-align:center;
    white-space:normal;
    font-size:9.5px;
    line-height:1.35;
    color:var(--text-dim);
  }

  /* Chart first, key below it — side by side there is no room for either. */
  .trendstrip{padding:10px 14px;gap:12px;}
  .trend-chart-col{min-width:100%;order:3;}
  .trend-summary{order:1;}
  .trend-delta-block{order:2;margin-left:auto;align-items:flex-end;}
  /* nowrap is right beside the badge on desktop, but on a 390px viewport the
     denominator caveat ran off the right edge and was silently clipped — the
     one number a reader needs to judge the trend was the part cut off. */
  .trend-delta-note{font-size:9px;white-space:normal;text-align:right;
                    max-width:100%;overflow-wrap:anywhere;}
  /* flex:none let the long caveat size the box wider than the viewport and
     run off the right edge; min-width:0 lets it actually wrap. */
  .trend-key{order:4;flex:1 1 100%;min-width:0;max-width:none;
             flex-direction:row;flex-wrap:wrap;gap:10px;align-items:center;}
  .trend-key .tk-note{margin-top:0;flex:1 1 100%;min-width:0;white-space:normal;}
  .trend-chart-wrap{height:64px;}

  /* Eight full-width chips stacked into eight rows and pushed the map about a
     thousand pixels down the page. One swipeable row instead. */
  .catbar{
    padding:8px 14px;gap:6px;flex-wrap:nowrap;overflow-x:auto;
    scrollbar-width:none;-webkit-overflow-scrolling:touch;
  }
  .catbar::-webkit-scrollbar{display:none;}
  .cat-chip{font-size:10.5px;padding:6px 9px 6px 8px;flex:0 0 auto;white-space:nowrap;}
  .cat-toggle-all{font-size:10px;padding:6px 9px;flex:0 0 auto;}

  /* Same treatment as the chips: one swipeable row rather than a stack. */
  .rangebar{padding:8px 14px;gap:8px;}
  .rb-label{display:none;}
  .rb-presets{
    flex:1 1 100%;overflow-x:auto;scrollbar-width:none;
    -webkit-overflow-scrolling:touch;
  }
  .rb-presets::-webkit-scrollbar{display:none;}
  .rb-presets button{flex:0 0 auto;}
  .rb-custom{flex:1 1 100%;}
  .date-entry{flex:1 1 0;min-width:0;}
  .rb-custom .date-input{width:100%;min-width:0;font-size:11px;}

  /* Give the feed the majority of the screen — it is the readable surface. */
  .mapwrap{flex:0 0 44vh;min-height:260px;}
  .feed{min-width:0;}
  .feed-head{padding:10px 12px;}

  /* Bigger type: 12.3px summaries were unreadable on a handset. */
  .card{padding:13px 13px;margin:6px 4px;}
  .card-title{font-size:14.5px;line-height:1.4;}
  .card-summary{font-size:13.5px;line-height:1.62;color:#a2b1c3;}
  .card-meta{font-size:11px;gap:9px;}
  .card-sources{--src-lh:1.5;font-size:12px;}
  .card-sources .pub{font-size:10.5px;}
  .card-summary{max-height:calc(9 * 1.62em);}
  .grade-chip{font-size:10px;padding:2px 7px;}

  .feed-search input{font-size:16px;padding:9px 30px;}  /* 16px stops iOS zooming on focus */

  /* Popups get most of the width and real type instead of a cramped card. */
  .popup{max-width:none;}
  .popup .p-title{font-size:14.5px;}
  .popup .p-summary{font-size:13px;line-height:1.6;}
  .popup .p-source{font-size:12px;}
  .leaflet-popup-content{margin:12px 14px;}
  /* Backstop only: p-summary and p-sources-list bound the popup on their own
     now, so this catches an unusually long title or a short viewport rather
     than being the one thing standing between the reader and a 118-source
     popup. */
  .leaflet-popup-content-wrapper{max-height:52vh;overflow-y:auto;}
  .popup .p-summary{max-height:calc(9 * 1.6em);}

  .disclaimer{padding:10px 14px;font-size:10.5px;}
}

/* Must sit after the base .feed / .feed-list rules to win on source order —
   the equivalent declarations up in the 900px block are overridden by them. */
@media(max-width:900px){
  .feed{flex:0 0 auto;min-height:0;max-height:none;}
  .feed-list{overflow:visible;max-height:none;flex:0 0 auto;}
  .feed-head{position:sticky;top:0;z-index:500;background:var(--bg-panel);}
}

/* ---- Analysis rail (left of the map) ----
 * Was a full-width row below the map, which pushed the page past the viewport.
 * As a rail it reclaims that vertical space, and the ranked list is a shape
 * that suits a narrow column better than a wide one anyway.
 */
.analysis{flex:0 0 clamp(268px,21%,330px);min-width:0;display:flex;flex-direction:column;
  border-right:1px solid var(--line-soft);border-bottom:none;background:var(--bg-deep);
  padding:12px 12px 0;overflow:hidden;}
.analysis .an-head{display:block;margin-bottom:8px;flex:none;}
.analysis .an-title{margin-bottom:8px;}
/* Four labels will not sit in one 300px row; two columns keeps them readable
   without shrinking the type to the point of uselessness. */
.analysis .dim-picker{display:grid;grid-template-columns:1fr 1fr;gap:5px;}
.analysis .dim-picker button{width:100%;text-align:center;padding:5px 6px;font-size:10.5px;}
.analysis .country-picker{margin:8px 0 0;}
.analysis .cp-wrap summary{display:block;text-align:center;}
.analysis .an-defs-host{margin:8px 0 0;}
/* The list is the only thing allowed to scroll; the controls stay put. */
.analysis .an-body{flex:1;min-height:0;overflow-y:auto;overscroll-behavior:contain;
  padding-bottom:12px;}

.an-row{display:block;margin-bottom:9px;}
.an-row-top{display:flex;justify-content:space-between;align-items:baseline;gap:8px;
  margin-bottom:3px;}
.an-row-bot{display:flex;align-items:center;gap:7px;}
.an-row-bot .an-track{flex:1;min-width:0;}
.an-spark{width:62px;height:20px;flex:none;display:block;opacity:0.85;}

/* ---- legacy row styles kept for the wide fallback ---- */
/* ---- Analysis row -------------------------------------------------------
 * One dimension picker driving two panels: a ranked comparison and a time
 * series. Filters live in a single row above the charts rather than inside
 * each panel, so it is always obvious that both are showing the same cut.
 */
.analysis{padding:14px 18px 4px;border-bottom:1px solid var(--line-soft);}
.an-head{display:flex;align-items:baseline;gap:16px;flex-wrap:wrap;margin-bottom:10px;}
.an-title{font-family:var(--font-display);font-size:13px;font-weight:700;
  letter-spacing:0.08em;text-transform:uppercase;color:var(--text-muted);margin:0;}
.an-title span{color:var(--accent);}
.dim-picker{display:flex;gap:6px;flex-wrap:wrap;}
.dim-picker button{font-family:var(--font-mono);font-size:11px;letter-spacing:0.04em;
  padding:4px 10px;border-radius:999px;cursor:pointer;
  background:var(--bg-raised);border:1px solid var(--line);color:var(--text-muted);}
.dim-picker button:hover{border-color:var(--accent-dim);color:var(--text);}
.dim-picker button.on{background:rgba(201,162,75,0.12);border-color:var(--accent);color:var(--accent);}
.dim-picker button:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}

.an-grid{display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1.35fr);gap:14px;}
.analysis .an-panel{background:none;border:none;padding:0;}
.an-panel{background:var(--bg-panel);border:1px solid var(--line-soft);border-radius:8px;
  padding:12px 14px 14px;min-width:0;}
.an-panel-head{display:flex;justify-content:space-between;align-items:baseline;
  font-family:var(--font-mono);font-size:10.5px;letter-spacing:0.08em;text-transform:uppercase;
  color:var(--text-dim);margin-bottom:10px;}
.an-hint{color:var(--text-dim);text-transform:none;letter-spacing:0;}

.an-row{display:grid;grid-template-columns:minmax(90px,34%) 1fr auto;align-items:center;
  gap:10px;margin-bottom:6px;}
.an-label{font-size:12px;color:var(--text-muted);overflow:hidden;text-overflow:ellipsis;
  white-space:nowrap;}
.an-track{height:9px;background:var(--bg-raised);border-radius:5px;overflow:hidden;}
/* 4px rounded data-end anchored to the baseline; the bar grows from the axis. */
.an-bar{display:block;height:100%;border-radius:0 4px 4px 0;transition:width .25s ease;}
.an-val{font-family:var(--font-mono);font-size:11.5px;color:var(--text);
  font-variant-numeric:tabular-nums;min-width:2.2em;text-align:right;}
.an-empty{font-size:12px;color:var(--text-dim);margin:4px 0;}

.an-svg{width:100%;height:190px;display:block;overflow:visible;}
.an-axis{font-family:var(--font-mono);font-size:9px;fill:var(--text-dim);}
.an-legend{display:flex;flex-wrap:wrap;gap:10px 14px;margin-top:8px;}
.an-key{display:inline-flex;align-items:center;gap:6px;font-size:11px;color:var(--text-muted);}
.an-key i{width:9px;height:9px;border-radius:2px;flex:none;}

/* Below 900px the page scrolls rather than fitting the viewport, so the two
   panels stack instead of competing for a narrow column. */
@media(max-width:900px){
  .an-grid{grid-template-columns:1fr;}
  .analysis{padding:12px 14px 4px;}
  .an-svg{height:160px;}
  .dim-picker{width:100%;overflow-x:auto;flex-wrap:nowrap;padding-bottom:2px;}
}
.an-tail{margin:10px 0 0;padding-top:8px;border-top:1px solid var(--line-soft);
  font-size:11.5px;color:var(--text-dim);}
.an-tail strong{color:var(--text-muted);font-weight:600;
  font-family:var(--font-mono);font-variant-numeric:tabular-nums;}

/* Belt and braces on the legend. Even with the map guaranteed a minimum
   height, an absolutely-positioned box anchored to the bottom will happily
   render above its container's top edge if it is taller than the container.
   Capping it and letting it scroll means a squeezed map degrades into a
   scrollable legend rather than one that spills over the controls above. */
#legend{max-height:calc(100% - 28px);overflow-y:auto;overscroll-behavior:contain;}

/* ---- Confidence filter in the sitrep feed ---- */
.grade-filter{display:flex;gap:6px;margin-top:8px;flex-wrap:wrap;}
.grade-filter button{display:inline-flex;align-items:center;gap:6px;cursor:pointer;
  font-family:var(--font-mono);font-size:10.5px;letter-spacing:0.04em;padding:3px 9px;
  border-radius:999px;background:var(--bg-raised);border:1px solid var(--line);
  color:var(--text-dim);}
.grade-filter button i{width:8px;height:8px;border-radius:2px;flex:none;opacity:0.35;}
.grade-filter button.on{color:var(--text);border-color:var(--accent-dim);}
.grade-filter button.on i{opacity:1;}
.grade-filter button:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}

/* ---- Country sub-filter (scopes the analysis row only) ---- */
.country-picker{margin-left:auto;}
.cp-wrap summary{cursor:pointer;font-family:var(--font-mono);font-size:11px;
  color:var(--text-muted);padding:4px 10px;border:1px solid var(--line);
  border-radius:999px;background:var(--bg-raised);list-style:none;}
.cp-wrap summary::-webkit-details-marker{display:none;}
.cp-wrap summary::after{content:" ▾";color:var(--text-dim);}
.cp-wrap[open] summary{border-color:var(--accent-dim);color:var(--text);}
.cp-body{position:absolute;z-index:60;margin-top:6px;width:min(320px,90vw);
  background:var(--bg-panel);border:1px solid var(--line);border-radius:8px;padding:10px;
  box-shadow:0 10px 30px rgba(0,0,0,0.55);}
.cp-actions{display:flex;gap:6px;margin-bottom:8px;}
.cp-actions button{flex:1;cursor:pointer;font-family:var(--font-mono);font-size:10.5px;
  padding:4px 8px;border-radius:5px;background:var(--bg-raised);
  border:1px solid var(--line);color:var(--text-muted);}
.cp-actions button:hover{border-color:var(--accent-dim);color:var(--text);}
.cp-list{max-height:260px;overflow-y:auto;overscroll-behavior:contain;}
.cp-item{display:grid;grid-template-columns:auto 1fr auto;align-items:center;gap:8px;
  padding:3px 2px;font-size:12px;color:var(--text-muted);cursor:pointer;}
.cp-item:hover{color:var(--text);}
.cp-item b{font-family:var(--font-mono);font-size:11px;color:var(--text-dim);
  font-variant-numeric:tabular-nums;}

/* ---- Grade definitions ---- */
.an-defs-host{margin:0 0 10px;}
.an-defs-wrap summary{cursor:pointer;font-size:11.5px;color:var(--accent);
  padding:2px 0;list-style:none;}
.an-defs-wrap summary::-webkit-details-marker{display:none;}
.an-defs-wrap summary::before{content:"ⓘ ";}
.an-defs-wrap[open] summary{margin-bottom:8px;}
/* V17: the Source Grade disclosure is sized at runtime to the real space left
   between its top edge and the bottom of the analysis rail/viewport. This avoids
   the old fixed-vh cap, which could leave the bottom of the disclosure itself
   clipped on shorter monitors even though its internal scrollbar had reached the end. */
.analysis .an-defs-wrap[open]{
  max-height:var(--source-grade-def-max-height,340px);
  overflow-y:auto;
  overscroll-behavior:contain;
  padding-right:6px;
  padding-bottom:14px;
  scroll-padding-bottom:14px;
  scrollbar-gutter:stable;
}
.analysis .an-defs-wrap[open]::-webkit-scrollbar{width:8px;}
.analysis .an-defs-wrap[open]::-webkit-scrollbar-thumb{background:var(--line);border-radius:5px;}
.an-defs{display:grid;grid-template-columns:auto 1fr;gap:4px 12px;margin:0;
  font-size:12px;line-height:1.5;}
.an-defs dt{font-family:var(--font-mono);font-size:11px;color:var(--text);white-space:nowrap;}
.an-defs dd{margin:0;color:var(--text-muted);}
.an-defs-grid{display:grid;grid-template-columns:repeat(auto-fit,minmax(260px,1fr));gap:16px;}
.an-defs-grid h4{margin:0 0 3px;font-family:var(--font-display);font-size:11px;
  letter-spacing:0.06em;text-transform:uppercase;color:var(--text);}
.an-defs-note{margin:0 0 6px;font-size:11.5px;color:var(--text-dim);line-height:1.45;}
.an-defs-grid ul{margin:0;padding-left:0;list-style:none;font-size:12px;line-height:1.6;}
.an-defs-grid li{color:var(--text-muted);}
.an-defs-grid b{font-family:var(--font-mono);color:var(--accent);margin-right:6px;}

@media(max-width:900px){
  .country-picker{margin-left:0;width:100%;}
  .cp-body{position:static;width:auto;}
}
.cp-wrap summary.cp-empty{border-color:var(--warn);color:var(--warn);}

/* Three columns need roughly 1180px before the map becomes a stamp. Below that
   the rail returns to a full-width band above the map, where it has room. */
@media(max-width:1180px){
  .layout{flex-wrap:wrap;}
  .analysis{flex:1 1 100%;border-right:none;border-bottom:1px solid var(--line-soft);
    padding:12px 16px;max-height:none;}
  .analysis .an-head{display:flex;align-items:center;gap:12px;flex-wrap:wrap;}
  .analysis .an-title{margin-bottom:0;}
  .analysis .dim-picker{display:flex;grid-template-columns:none;}
  .analysis .dim-picker button{width:auto;}
  .analysis .country-picker{margin:0 0 0 auto;}
  .analysis .an-defs-host{flex:1 1 100%;margin:6px 0 0;}
  .analysis .an-body{overflow:visible;max-height:none;
    display:grid;grid-template-columns:repeat(auto-fill,minmax(230px,1fr));gap:0 18px;}
  .an-tail{grid-column:1/-1;}
  .mapwrap{flex:1 1 60%;}
}
@media(max-width:900px){
  .analysis .an-body{grid-template-columns:1fr;}
  .analysis .country-picker{margin:8px 0 0;width:100%;}
}

/* When the rail wraps to its own line, .mapwrap and .feed share the next line
   inside a container with no definite height — .feed-list then grows to its
   full content (44,000px on mobile) and drags the map with it. Pin the row's
   height explicitly at these widths so the feed keeps its own scroll. */
@media(max-width:1180px) and (min-width:901px){
  .layout{align-items:flex-start;}
  .mapwrap, .feed{height:min(62vh,560px);}
  .feed{display:flex;flex-direction:column;min-height:0;}
  .feed-list{flex:1;min-height:0;overflow-y:auto;}
}

/* Below 900px the page scrolls as one column and the feed list IS the page —
   the same behaviour it had before the rail existed. But the rail now sits
   above the map inside a .layout with a 460px floor, and the map was paying
   for it: 396px before the port, 260px after. Give the map its own height here
   rather than letting it absorb the rail's cost. */
@media(max-width:900px){
  /* flex:0 0 auto matters as much as the height. .layout is a COLUMN at this
     width, so the flex:1 1 60% inherited from the 1180px block lands on the
     main axis and lets the map shrink straight back to its 260px min-height,
     height declaration or not. Taking it out of the flex sizing is what makes
     the height stick. */
  .mapwrap{flex:0 0 auto;height:min(56vh,420px);}
}


/* V9: the map legend is opt-in on all screen sizes. */
#legend.collapsed{max-height:none;overflow:hidden;overscroll-behavior:auto;}
#legend.collapsed .legend-toggle{min-width:82px;}

/* V11: keep the mobile legend clear of Leaflet/MapLibre attribution. */
@media(max-width:900px){
  .legend{left:8px;top:8px;bottom:auto;}
}

/* V13 test: consolidate the activity-type filter into the Breakdown control area.
   On the desktop rail it becomes a compact two-column control; when the analysis
   rail spans the page, it returns to a single swipeable row so it does not add
   several rows of height above the map. */
.analysis-domain-block{
  margin-top:9px;padding-top:9px;border-top:1px solid var(--line-soft);
}
.analysis-filter-label{
  margin:0 0 6px;font-family:var(--font-mono);font-size:9.5px;font-weight:600;
  letter-spacing:0.08em;text-transform:uppercase;color:var(--text-dim);
}
.analysis .catbar{
  display:grid;grid-template-columns:minmax(0,1fr) minmax(0,1fr);gap:5px;
  padding:0;background:transparent;border:0;overflow:visible;
}
.analysis .cat-toggle-all{
  grid-column:1 / -1;width:100%;padding:5px 8px;font-size:9.7px;
}
.analysis .cat-chip{
  min-width:0;padding:5px 6px;font-size:9.5px;line-height:1.2;gap:5px;
  justify-content:flex-start;
}
.analysis .cat-chip .dot{width:7px;height:7px;}
.analysis .cat-chip .count{margin-left:auto;flex:none;}

@media(max-width:1180px){
  .analysis-domain-block{
    flex:1 1 100%;min-width:0;margin-top:0;padding-top:8px;
  }
  .analysis-filter-label{margin-bottom:5px;}
  .analysis .catbar{
    display:flex;flex-wrap:nowrap;gap:6px;overflow-x:auto;overflow-y:hidden;
    scrollbar-width:none;-webkit-overflow-scrolling:touch;
  }
  .analysis .catbar::-webkit-scrollbar{display:none;}
  .analysis .cat-toggle-all{
    grid-column:auto;width:auto;flex:0 0 auto;padding:5px 9px;font-size:10px;
  }
  .analysis .cat-chip{
    flex:0 0 auto;white-space:nowrap;font-size:10.2px;padding:5px 8px;
  }
  .analysis .cat-chip .count{margin-left:2px;}
}

/* V17: touch screens use the same measured height; momentum scrolling remains on. */
@media(max-width:900px){
  .analysis .an-defs-wrap[open]{-webkit-overflow-scrolling:touch;}
}

/* V18/V22: desktop SITREP feed can be widened into the map with a draggable
   separator. V22 makes the grabber visibly interactive without turning it into
   a heavy divider: a larger hit target, persistent center rail and a compact
   grip that brightens on hover/focus/drag. Mobile still removes it entirely. */
.feed-resizer{
  flex:0 0 12px;
  position:relative;
  cursor:col-resize;
  touch-action:none;
  user-select:none;
  z-index:450;
  background:var(--bg-deep);
  outline:none;
}
.feed-resizer::before{
  content:"";
  position:absolute;
  left:50%;top:0;bottom:0;width:1px;
  transform:translateX(-50%);
  background:var(--line);
  box-shadow:0 0 0 1px rgba(255,255,255,.018);
  transition:background .15s ease,box-shadow .15s ease;
}
.feed-resizer::after{
  content:"";
  position:absolute;
  left:50%;top:50%;transform:translate(-50%,-50%);
  width:8px;height:54px;border-radius:6px;
  border:1px solid var(--line);
  background:
    repeating-linear-gradient(
      to bottom,
      transparent 0 7px,
      var(--text-muted) 7px 9px
    ),
    var(--bg-raised);
  box-shadow:0 2px 8px rgba(0,0,0,.35);
  opacity:.82;
  transition:border-color .15s ease,background-color .15s ease,box-shadow .15s ease,opacity .15s ease;
}
.feed-resizer:hover::before,
.feed-resizer:focus-visible::before,
.layout.feed-resizing .feed-resizer::before{
  background:var(--accent);
  box-shadow:0 0 9px rgba(201,162,75,.32);
}
.feed-resizer:hover::after,
.feed-resizer:focus-visible::after,
.layout.feed-resizing .feed-resizer::after{
  border-color:var(--accent-dim);
  background:
    repeating-linear-gradient(
      to bottom,
      transparent 0 7px,
      var(--accent) 7px 9px
    ),
    var(--bg-raised);
  box-shadow:0 0 0 1px rgba(201,162,75,.08),0 3px 10px rgba(0,0,0,.4);
  opacity:1;
}
.layout.feed-resizing,
.layout.feed-resizing *{cursor:col-resize !important;}

/* A user-selected width wins over the normal 480px cap. */
.feed.user-sized{
  flex:0 0 var(--feed-user-width);
  width:var(--feed-user-width);
  max-width:none;
}

@media(max-width:900px){
  /* Phone/tablet layout remains the existing stacked experience. */
  .feed-resizer{display:none;}
  .feed.user-sized{
    flex:0 0 auto;
    width:auto;
    max-width:none;
  }
}



/* V19: hard-isolate the desktop feed resizer from stacked mobile layouts.
   A saved desktop feed width must never become part of the phone's intrinsic
   page width. Every major child is explicitly clamped to the viewport while
   the few intended horizontal scrollers (range/activity controls) keep their
   own local overflow. */
@media(max-width:900px){
  html,body{width:100%;max-width:100%;overflow-x:hidden;}
  .layout{width:100%;max-width:100%;min-width:0;overflow-x:hidden;}
  .analysis,
  .mapwrap,
  .feed,
  .feed.user-sized{
    width:100% !important;
    max-width:100% !important;
    min-width:0 !important;
  }
  .feed.user-sized{flex:0 0 auto !important;}
  .feed-resizer{
    display:none !important;
    width:0 !important;
    min-width:0 !important;
    max-width:0 !important;
    flex:0 0 0 !important;
    overflow:hidden !important;
  }
  .analysis .an-head,
  .analysis-domain-block,
  .analysis .catbar,
  .rangebar,
  .rb-custom,
  .feed-head,
  .feed-list{max-width:100%;min-width:0;}
}

/* V23 TEST — mobile-only information architecture.
   Desktop keeps the existing structure. Phones get one filter surface above
   the map, the map next, optional analysis after it, then the SITREP feed. */
.mobile-filter-panel,
.mobile-analysis-toggle{display:none;}
.mobile-analysis-content{display:contents;}
.cat-chip .cat-name{min-width:0;}

@media(max-width:900px){
  /* ---- Mobile filter panel ---- */
  .mobile-layout-active .mobile-filter-panel{
    display:block;
    width:100%;max-width:100%;
    background:var(--bg-panel);
    border-top:1px solid var(--line-soft);
    border-bottom:1px solid var(--line);
  }
  .mobile-filter-head{
    display:flex;align-items:stretch;gap:0;
    min-width:0;
  }
  .mobile-filter-toggle{
    min-width:0;flex:1;
    display:grid;grid-template-columns:auto minmax(0,1fr) auto;
    align-items:center;gap:8px;
    padding:11px 12px;
    border:0;background:transparent;color:var(--text);
    cursor:pointer;text-align:left;
  }
  .mobile-filter-title{
    font-family:var(--font-display);font-size:12px;font-weight:700;
    letter-spacing:.09em;text-transform:uppercase;
  }
  .mobile-filter-summary{
    min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;
    font-family:var(--font-mono);font-size:9.5px;color:var(--text-dim);
    text-align:right;
  }
  .mobile-filter-chev,
  .mobile-analysis-chev{font-size:10px;color:var(--text-dim);transition:transform .16s ease;}
  .mobile-filter-panel.collapsed .mobile-filter-chev,
  .analysis.mobile-collapsed .mobile-analysis-chev{transform:rotate(-90deg);}
  .mobile-reset-slot{display:flex;align-items:center;padding-right:10px;}
  .mobile-reset-slot #resetFilters{
    position:static !important;
    padding:5px 8px !important;
    font-size:9.5px !important;
    white-space:nowrap;
  }
  .mobile-filter-body{
    display:flex;flex-direction:column;gap:12px;
    padding:3px 12px 13px;
    border-top:1px solid var(--line-soft);
  }
  .mobile-filter-panel.collapsed .mobile-filter-body{display:none;}
  .mobile-filter-group{min-width:0;}
  .mobile-filter-group + .mobile-filter-group{
    padding-top:10px;border-top:1px solid var(--line-soft);
  }
  .mobile-filter-group-label,
  .mobile-filter-panel .analysis-filter-label{
    margin:0 0 7px;
    font-family:var(--font-mono);font-size:9.5px;font-weight:600;
    letter-spacing:.08em;text-transform:uppercase;color:var(--text-dim);
  }

  /* Date presets: all visible — no horizontal hunting. */
  .mobile-filter-panel .rangebar{
    display:block;width:100%;max-width:100%;
    padding:0;background:transparent;border:0;
  }
  .mobile-filter-panel .rb-label{display:none;}
  .mobile-filter-panel .rb-presets{
    display:grid;grid-template-columns:repeat(4,minmax(0,1fr));
    gap:6px;width:100%;overflow:visible;
  }
  .mobile-filter-panel .rb-presets button{
    width:100%;min-width:0;padding:7px 4px;font-size:10px;text-align:center;
  }
  .mobile-filter-panel .rb-custom{
    display:grid;grid-template-columns:minmax(0,1fr) auto minmax(0,1fr) auto;
    gap:6px;align-items:center;margin-top:8px;width:100%;
  }
  .mobile-filter-panel .date-entry{min-width:0;width:100%;}
  .mobile-filter-panel .rb-custom .date-input{width:100%;min-width:0;font-size:11px;}
  .mobile-filter-panel .rb-clear{white-space:nowrap;}

  /* Hybrid domains: 2 × 4 grid plus one full-width select/clear action. */
  .mobile-filter-panel .analysis-domain-block{
    margin:0;padding:0;border:0;min-width:0;width:100%;
  }
  .mobile-filter-panel .catbar{
    display:grid !important;
    grid-template-columns:repeat(2,minmax(0,1fr)) !important;
    gap:6px !important;
    width:100%;padding:0 !important;
    background:transparent !important;border:0 !important;
    overflow:visible !important;
  }
  .mobile-filter-panel .cat-toggle-all{
    grid-column:1 / -1 !important;
    width:100% !important;min-width:0 !important;
    padding:6px 8px !important;font-size:9.8px !important;
  }
  .mobile-filter-panel .cat-chip{
    min-width:0 !important;width:100%;
    display:flex !important;align-items:flex-start !important;
    gap:6px !important;padding:8px 7px !important;
    white-space:normal !important;line-height:1.25 !important;
    font-size:10px !important;
    flex:initial !important;
  }
  .mobile-filter-panel .cat-chip .dot{margin-top:2px;flex:none;}
  .mobile-filter-panel .cat-chip .cat-name{
    flex:1;min-width:0;white-space:normal;overflow-wrap:anywhere;
  }
  .mobile-filter-panel .cat-chip .count{margin-left:3px !important;flex:none;}

  /* Confidence belongs with the filters, not below the map in SITREP. */
  .mobile-filter-panel .grade-filter{
    display:grid;grid-template-columns:repeat(3,minmax(0,1fr));
    gap:6px;margin:0;width:100%;
  }
  .mobile-filter-panel .grade-filter button{
    width:100%;min-width:0;justify-content:center;
    padding:7px 3px;font-size:9.7px;letter-spacing:.02em;
  }
  .mobile-filter-panel .grade-filter button i{width:7px;height:7px;}

  /* ---- Mobile content order: Map → optional Analysis → SITREP ---- */
  .layout{display:flex;flex-direction:column;}
  .mapwrap{order:1;}
  .analysis{order:2;}
  .feed-resizer{order:3;}
  .feed{order:4;}

  /* Analysis becomes an optional, calm disclosure below the map. */
  .analysis{
    padding:0 !important;
    background:var(--bg-panel);
    border-right:0;border-top:1px solid var(--line);border-bottom:1px solid var(--line);
  }
  .mobile-analysis-toggle{
    display:grid;grid-template-columns:auto minmax(0,1fr) auto;
    align-items:center;gap:8px;width:100%;
    padding:11px 14px;border:0;background:transparent;color:var(--text);
    cursor:pointer;text-align:left;
    font-family:var(--font-display);font-size:12px;font-weight:700;
    letter-spacing:.09em;text-transform:uppercase;
  }
  .mobile-analysis-note{
    min-width:0;text-align:right;
    font-family:var(--font-mono);font-size:9.4px;font-weight:400;
    letter-spacing:0;text-transform:none;color:var(--text-dim);
    white-space:nowrap;overflow:hidden;text-overflow:ellipsis;
  }
  .mobile-analysis-content{
    display:block;padding:0 14px 12px;
    border-top:1px solid var(--line-soft);
  }
  .analysis.mobile-collapsed .mobile-analysis-content{display:none;}
  .analysis .an-head{
    display:block !important;margin:0;padding-top:11px;
  }
  .analysis .an-title{margin:0 0 8px !important;}
  .analysis .dim-picker{
    display:grid !important;grid-template-columns:repeat(2,minmax(0,1fr)) !important;
    gap:6px !important;width:100%;overflow:visible !important;padding:0 !important;
  }
  .analysis .dim-picker button{width:100% !important;text-align:center;}
  .analysis .country-picker{margin:8px 0 0 !important;width:100%;}
  .analysis .an-defs-host{margin:8px 0 0 !important;}
  .analysis .an-body{display:block !important;padding-top:10px;}

  /* The activity controls live in the filter panel on mobile, so the analysis
     section should never reserve a ghost gap for their original home. */
  .analysis .analysis-domain-block:empty{display:none;}

  /* SITREP starts with search/content; confidence has already been chosen above. */
  .feed-head{position:sticky;top:0;z-index:500;background:var(--bg-panel);}
}


/* V24: desktop/tablet layout reset lives with the SITREP header. */
.feed-head-actions{display:flex;align-items:center;justify-content:flex-end;gap:9px;min-width:0;}
.layout-reset-btn{
  flex:none;
  font-family:var(--font-mono);font-size:9.5px;font-weight:600;
  letter-spacing:.055em;text-transform:uppercase;
  color:var(--text-dim);background:var(--bg-raised);
  border:1px solid var(--line);border-radius:3px;
  padding:4px 7px;cursor:pointer;white-space:nowrap;
  transition:border-color .15s ease,color .15s ease,background .15s ease;
}
.layout-reset-btn:hover{border-color:var(--accent-dim);color:var(--accent);background:rgba(201,162,75,.08);}
.layout-reset-btn:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
@media(max-width:900px){
  .layout-reset-btn{display:none !important;}
  .feed-head-actions{gap:0;}
}

/* V24: in-page external-source warning. This is DOM UI, not a browser popup,
   so blocker settings cannot suppress the warning or the Proceed control. */
.external-warning[hidden]{display:none !important;}
.external-warning{
  position:fixed;inset:0;z-index:5000;
  display:grid;place-items:center;
  padding:18px;
}
.external-warning-backdrop{
  position:absolute;inset:0;background:rgba(0,0,0,.78);
  backdrop-filter:blur(2px);
}
.external-warning-dialog{
  position:relative;z-index:1;
  width:min(430px,calc(100vw - 32px));
  background:#090e15;border:1px solid var(--line);border-radius:8px;
  box-shadow:0 22px 70px rgba(0,0,0,.72);
  padding:20px;
}
.external-warning-kicker{
  margin:0 0 9px;
  font-family:var(--font-display);font-size:11px;font-weight:700;
  letter-spacing:.1em;text-transform:uppercase;color:var(--accent);
}
.external-warning-dialog p{
  margin:0;color:var(--text);font-size:14px;line-height:1.55;
}
.external-warning-actions{
  display:flex;justify-content:flex-end;gap:8px;margin-top:18px;
}
.external-warning-actions button,
.external-warning-actions a{
  min-width:92px;box-sizing:border-box;
  display:inline-flex;align-items:center;justify-content:center;
  font-family:var(--font-mono);font-size:10.5px;font-weight:600;
  letter-spacing:.055em;text-transform:uppercase;text-decoration:none;
  padding:8px 12px;border-radius:4px;cursor:pointer;
}
.external-warning-cancel{
  color:var(--text-muted);background:var(--bg-raised);border:1px solid var(--line);
}
.external-warning-cancel:hover{color:var(--text);border-color:var(--text-dim);}
.external-warning-proceed{
  color:#0a0e13;background:var(--accent);border:1px solid var(--accent);
}
.external-warning-proceed:hover{filter:brightness(1.08);}
.external-warning-actions button:focus-visible,
.external-warning-actions a:focus-visible{outline:2px solid var(--accent);outline-offset:2px;}
body.external-warning-open{overflow:hidden;}
@media(max-width:520px){
  .external-warning-dialog{padding:17px 15px;}
  .external-warning-actions{display:grid;grid-template-columns:1fr 1fr;}
  .external-warning-actions button,.external-warning-actions a{width:100%;min-width:0;}
}


/* V25 TEST — mobile-only filter discoverability + country filter.
   The filter panel now starts collapsed, advertises its open/close action, and
   carries the same country control used by the desktop Analysis view. */
@media(max-width:900px){
  .mobile-filter-toggle{
    grid-template-columns:minmax(0,1fr) auto 28px !important;
    grid-template-areas:
      "title action chev"
      "summary summary summary";
    row-gap:4px !important;
    padding:11px 10px 10px 12px !important;
    background:linear-gradient(180deg,rgba(201,162,75,.055),rgba(201,162,75,.015));
  }
  .mobile-filter-title{grid-area:title;align-self:center;}
  .mobile-filter-action{
    grid-area:action;align-self:center;
    font-family:var(--font-mono);font-size:9px;font-weight:700;
    letter-spacing:.075em;text-transform:uppercase;
    color:var(--accent);border:1px solid var(--accent-dim);border-radius:999px;
    padding:3px 7px;background:rgba(201,162,75,.08);
  }
  .mobile-filter-summary{
    grid-area:summary;text-align:left !important;
    font-size:9.3px !important;line-height:1.3;
  }
  .mobile-filter-chev{
    grid-area:chev;width:26px;height:26px;
    display:grid;place-items:center;align-self:center;
    box-sizing:border-box;border:1px solid var(--line);border-radius:5px;
    background:var(--bg-raised);color:var(--accent) !important;
    font-size:14px !important;line-height:1;
    transform:none !important;
  }
  .mobile-filter-panel:not(.collapsed) .mobile-filter-chev{transform:rotate(180deg) !important;}
  .mobile-filter-toggle:focus-visible{outline:2px solid var(--accent);outline-offset:-2px;}
  .mobile-filter-toggle:active{background:rgba(201,162,75,.10);}

  .mobile-filter-panel .mobile-country-group{min-width:0;}
  .mobile-filter-panel #mobileCountrySlot{width:100%;min-width:0;}
  .mobile-filter-panel .country-picker{
    display:block;width:100%;margin:0 !important;min-width:0;
  }
  .mobile-filter-panel .cp-wrap{width:100%;}
  .mobile-filter-panel .cp-wrap summary{
    width:100%;box-sizing:border-box;
    display:flex;align-items:center;justify-content:space-between;
    text-align:left;padding:8px 10px;border-radius:4px;
    font-size:10.5px;
  }
  .mobile-filter-panel .cp-wrap summary::after{margin-left:auto;padding-left:10px;}
  .mobile-filter-panel .cp-body{
    position:static;width:100%;box-sizing:border-box;margin-top:6px;
    max-height:none;
  }
  .mobile-filter-panel .cp-list{max-height:230px;}
}

/* V28 — mobile map controls + clearer Analysis disclosure.
   Keep the legend in its approved top-left position, move Leaflet zoom controls
   to the bottom-left, and give Analysis the same explicit Open/Close treatment
   as the mobile Filters row. Desktop/tablet layouts above 900px are unchanged. */
@media(max-width:900px){
  /* Leaflet creates the zoom control in its top-left corner container. Re-anchor
     that container at the bottom without moving the Hybrid Watch legend. */
  .mapwrap .leaflet-top.leaflet-left{
    top:auto !important;
    bottom:8px !important;
  }
  .mapwrap .leaflet-top.leaflet-left .leaflet-control-zoom{
    margin-top:0 !important;
    margin-left:8px !important;
  }

  /* Analysis now advertises the interaction exactly like Filters. */
  .mobile-analysis-toggle{
    grid-template-columns:minmax(0,1fr) auto 28px !important;
    grid-template-areas:
      "title action chev"
      "note note note";
    row-gap:4px !important;
    padding:11px 10px 10px 12px !important;
    background:linear-gradient(180deg,rgba(201,162,75,.055),rgba(201,162,75,.015));
  }
  .mobile-analysis-title{grid-area:title;align-self:center;}
  .mobile-analysis-action{
    grid-area:action;align-self:center;
    font-family:var(--font-mono);font-size:9px;font-weight:700;
    letter-spacing:.075em;text-transform:uppercase;
    color:var(--accent);border:1px solid var(--accent-dim);border-radius:999px;
    padding:3px 7px;background:rgba(201,162,75,.08);
  }
  .mobile-analysis-note{
    grid-area:note;text-align:left !important;
    font-size:9.3px !important;line-height:1.3;
  }
  .mobile-analysis-chev{
    grid-area:chev;width:26px;height:26px;
    display:grid;place-items:center;align-self:center;
    box-sizing:border-box;border:1px solid var(--line);border-radius:5px;
    background:var(--bg-raised);color:var(--accent) !important;
    font-size:14px !important;line-height:1;
    transform:none !important;
  }
  .analysis:not(.mobile-collapsed) .mobile-analysis-chev{transform:rotate(180deg) !important;}
  .mobile-analysis-toggle:focus-visible{outline:2px solid var(--accent);outline-offset:-2px;}
  .mobile-analysis-toggle:active{background:rgba(201,162,75,.10);}
}

/* A source whose URL failed the http(s) check. Shown, because the citation is
   part of the event's provenance and dropping it would silently weaken the
   record — but not clickable, and visibly marked so a reader can tell the
   difference between "no source" and "source we would not link to". */
.card-sources .src-unsafe,
.popup .p-source-unsafe{
  color:var(--text-dim);
  text-decoration:line-through wavy var(--warn);
  text-decoration-thickness:1px;
  cursor:not-allowed;
}

/* ---------- case developments ---------------------------------------------
   Rulings and official attributions that landed inside the selected range,
   for acts that may sit years outside it.

   Deliberately colourless. The design note keeps two sets apart in both
   directions: series colours never carry UI meaning, and the reserved status
   colours never become data. "A court has ruled on this" is neither — it is a
   property of the record, like the AJP chip beside it — so it borrows that
   chip's neutral treatment rather than claiming a hue. The category dot on
   each row is the only colour here, and it means what it always means. */
.devstrip-toggle{
  display:flex;align-items:center;gap:8px;width:100%;flex:none;
  padding:8px 12px;cursor:pointer;text-align:left;
  font-family:var(--font-mono);font-size:10.5px;letter-spacing:0.06em;
  text-transform:uppercase;color:var(--text-dim);
  background:var(--bg-panel);
  /* A heavier rule than the cards use: this is a boundary between two
     different things, not another item in the same list. */
  border:0;border-bottom:2px solid var(--line);
}
/* A class or attribute selector outranks the UA stylesheet's [hidden] rule, so
   the strip kept showing after JS had hidden it — visible, with a stale count,
   over a feed showing nothing. Restate the intent at a specificity that wins. */
.devstrip-toggle[hidden]{ display:none !important; }
/* A disclosure triangle that rotates, rather than a glyph that changes —
   the same affordance the filter and analysis panels use. */
.devstrip-toggle::before{
  content:"";width:0;height:0;flex:none;
  border-left:4px solid currentColor;
  border-top:3.5px solid transparent;border-bottom:3.5px solid transparent;
  transition:transform 0.15s ease;
}
.devstrip-toggle[aria-expanded="true"]::before{ transform:rotate(90deg); }
.devstrip-toggle:hover{ color:var(--text-muted); }
.devstrip-toggle:focus-visible{ outline:2px solid var(--accent-dim);outline-offset:-2px; }
/* Open, the header joins the panel below it: one recessed surface, so the
   strip reads as a mode the feed has been swapped into rather than a card
   that grew. */
.devstrip-toggle[aria-expanded="true"]{
  background:var(--bg-sunken);color:var(--text-muted);
  border-bottom-color:var(--line-soft);
}

/* The strip TAKES THE PLACE of the sitrep feed rather than pushing it down.
   Before, opening it grew the document by a thousand pixels and the reader had
   to scroll the whole page to find the end of a list that had no visible
   boundary. Now the panel fills exactly the space the feed occupied, scrolls
   inside itself, and the page never changes height — so closing the strip puts
   the reader back precisely where they were. */
.devstrip-panel{ display:none; }
.feed.dev-open .devstrip-panel{
  display:flex;flex-direction:column;flex:1 1 auto;min-height:0;
  background:var(--bg-sunken);
  /* Five points of luminance reads clearly on a cropped panel and almost not
     at all on a 1440px screen, which is where people actually look at it. The
     inset shadow does the work the flat tone could not: it says "recessed"
     structurally, and stays neutral — this strip deliberately claims no hue. */
  box-shadow:inset 0 7px 9px -8px rgba(0,0,0,0.95);
}
.feed.dev-open .feed-list,
.feed.dev-open .feed-empty{ display:none; }

.devstrip-list{ flex:1 1 auto;overflow-y:auto;padding:6px;min-height:0;position:relative; }
/* Narrow layout: the feed panel is not height-bounded there — the whole page
   scrolls — so flex:1 resolves to the list's full content height and the strip
   goes back to being a very long inline block with no visible end. Bound it
   explicitly so the same promise holds: the strip is a panel you scroll inside,
   never a section you scroll past. */
@media(max-width:900px){
  .feed.dev-open .devstrip-panel{ max-height:60vh; }
}
/* A row cut off by the scroll boundary looks like a rendering fault. Fading
   the last few pixels says "there is more below" instead. */
.devstrip-list.scrolls{
  -webkit-mask-image:linear-gradient(to bottom, #000 calc(100% - 22px), transparent 100%);
          mask-image:linear-gradient(to bottom, #000 calc(100% - 22px), transparent 100%);
}
.devstrip-list::-webkit-scrollbar{ width:9px; }
.devstrip-list::-webkit-scrollbar-thumb{ background:var(--line);border-radius:5px; }

.devrow{
  padding:8px 9px;border:1px solid var(--line);border-radius:3px;
  background:var(--bg-raised);margin-bottom:5px;cursor:pointer;
}
.devrow:hover{ border-color:var(--text-dim); }
.devrow-top{
  display:flex;align-items:center;gap:8px;margin-bottom:4px;
  font-family:var(--font-mono);font-size:9.5px;letter-spacing:0.05em;
  text-transform:uppercase;color:var(--text-muted);
}
.devrow-date{ margin-left:auto;color:var(--text-dim);text-transform:none; }
.devrow-event{
  display:flex;align-items:baseline;gap:6px;
  font-size:12.3px;line-height:1.4;color:var(--text);margin-bottom:3px;
}
.devrow-event .card-cat-dot{ flex:none;transform:translateY(-1px); }
/* The gap between ruling and act is the reason this strip exists: it is what
   tells the reader the incident is not in the window they are looking at. */
.devrow-meta{
  font-family:var(--font-mono);font-size:10px;color:var(--text-dim);margin-bottom:5px;
}
/* Steps in one case, oldest first. The last one is what the row is dated by;
   the rest are there so a conviction reads as the end of a story the strip
   already carried, not as a second incident. */
.devrow-steps{
  color:var(--text-dim);text-transform:none;letter-spacing:0;
  border:1px solid var(--line);border-radius:2px;padding:0 4px;
}
.devrow-chain{
  display:flex;flex-wrap:wrap;align-items:center;gap:5px;margin-bottom:5px;
  font-family:var(--font-mono);font-size:9.8px;color:var(--text-dim);
}
.devrow-step.is-latest{ color:var(--text-muted); }
.devrow-step-date{ opacity:0.75; }
.devrow-step-sep{ opacity:0.5; }
.devrow-summary{ font-size:11.8px;line-height:1.5;color:var(--text-muted);margin-bottom:5px; }
.devrow-sources{ display:flex;flex-wrap:wrap;gap:8px; }
.devrow-sources a{ font-size:10.5px;color:var(--link);text-decoration:none; }
.devrow-sources a:hover{ text-decoration:underline; }
.devrow-more{ font-size:10.5px;color:var(--text-dim); }

/* Shown on the card wherever the event appears, including at ranges where the
   strip above is not listing it. */
.dev-chip{
  font-family:var(--font-mono);font-size:9.5px;letter-spacing:0.05em;
  padding:1.5px 5px;border-radius:2px;cursor:help;
  color:var(--text-muted);border:1px dashed var(--line);background:var(--bg-panel);
}

/* Clicking a development scrolls to its event; without a moment of emphasis
   the card is easy to lose among identical neighbours. */
@keyframes cardFlash{
  0%   { border-color:var(--text-dim); }
  100% { border-color:var(--line); }
}
.card-flash{ animation:cardFlash 1.4s ease-out; }
@media (prefers-reduced-motion: reduce){
  .card-flash{ animation:none;border-color:var(--text-dim); }
  .devstrip-toggle::before{ transition:none; }
}


/* ---------- map-viewport filter -------------------------------------------
   Scopes the feed to what the map is showing. Chrome, not data: it takes the
   reserved --link blue when active rather than any series colour, matching
   the other stateful controls in the feed head. */
.map-filter-btn{
  display:inline-flex;align-items:center;gap:5px;
  font-family:var(--font-mono);font-size:9.5px;letter-spacing:0.06em;
  text-transform:uppercase;padding:3px 7px;border-radius:3px;
  color:var(--text-dim);border:1px solid var(--line);background:var(--bg-raised);
  cursor:pointer;transition:color 0.12s ease, border-color 0.12s ease;
}
.map-filter-btn:hover{ color:var(--text-muted);border-color:var(--text-dim); }
.map-filter-btn .map-filter-icon{ width:11px;height:11px;flex:none; }
/* An active filter has to be unmistakable. A short feed is otherwise
   indistinguishable from a quiet dataset, which is the failure this control
   could most easily cause. */
.map-filter-btn.on{
  color:var(--link);border-color:var(--link);
  background:rgba(90,169,230,0.12);
}
.map-filter-btn:focus-visible{ outline:2px solid var(--link);outline-offset:1px; }

/* Inline action inside the empty-feed message. */
.link-btn{
  background:none;border:none;padding:0;font:inherit;
  color:var(--link);text-decoration:underline;cursor:pointer;
}
.link-btn:hover{ color:var(--text); }

@media (prefers-reduced-motion: reduce){
  .map-filter-btn{ transition:none; }
}

/* ---------- report button on a card ---------- */
/* Sits under the sources, quiet until hovered: the card is for scanning and the
   report is a deliberate act, so the control should not compete with the
   headline for attention on a feed of three hundred of them. */
.card-actions { display: flex; justify-content: flex-end; margin-top: 8px; }
.card-report {
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .07em;
  text-transform: uppercase; color: var(--muted);
  background: transparent; border: 1px solid var(--line);
  border-radius: 3px; padding: 4px 9px; cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
}
.card:hover .card-report { color: var(--text); border-color: var(--muted); }
.card-report:hover { background: var(--surface-2, rgba(255,255,255,.06)); }
.card-report:focus-visible { outline: 2px solid var(--accent, #6ba3e5); outline-offset: 2px; }
.card-report .pro-tag {
  font-size: 9px; letter-spacing: .08em; padding: 1px 4px; border-radius: 2px;
  background: var(--accent, #6ba3e5); color: #08121c; font-weight: 700;
}

/* ---------- Go Pro ---------- */
.pro-pill {
  font-family: var(--font-mono); font-size: 10px; letter-spacing: .09em;
  text-transform: uppercase; text-decoration: none; white-space: nowrap;
  color: #08121c; background: var(--accent, #6ba3e5);
  border-radius: 999px; padding: 4px 11px; font-weight: 700;
}
.pro-pill:hover { filter: brightness(1.12); }
.pro-pill:focus-visible { outline: 2px solid var(--accent, #6ba3e5); outline-offset: 2px; }
