/* =========================================================================
 * FeelFree — a geographic situation page.
 *
 * The shape of this stylesheet follows the shape of the question people
 * open the page to ask, in order: what is happening HERE, what has been
 * reported nearby, and what does the rest of the country look like. The
 * desk composition puts the first two in a column on the left and the
 * third — a real map, not a thumbnail below a page of telemetry — beside
 * them, above the fold. The phone gets the same order as a single calm
 * column, not a squashed copy of the desk.
 *
 * THREE RULES THIS FILE KEEPS.
 *
 * 1. A calm-looking surface may never imply a calm reading. Whitespace,
 *    hairlines and normal-case labels are the page's ordinary voice; the
 *    state word, its colour and its emblem are the only things that say
 *    what is happening, and they say it in words first.
 * 2. Colour is never the only carrier. Every state on this page — in the
 *    band, in the neighbour list, in the map legend — is spelled out.
 * 3. Light is the page's own default and dark is designed, not inverted:
 *    both palettes are written out below, and neither is derived from the
 *    other by a filter.
 *
 * Layers: tokens → reset → controls → shell → situation → lists → map →
 * overlays → widths. Widths come last and only ever place things; no rule
 * down there introduces a colour or a type size that is not already a
 * token above.
 * ===================================================================== */

/* ============================================================== FONTS === */

@font-face {
  font-family: 'Fixel Text';
  src: url('fixel-text-400.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Fixel Text';
  src: url('fixel-text-600.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Fixel Display';
  src: url('fixel-display-700.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Fixel Display';
  src: url('fixel-display-800.woff2') format('woff2');
  font-weight: 800;
  font-style: normal;
  font-display: swap;
}

/* ============================================================= TOKENS === */

/* The complete palette lives on bare :root and it is the LIGHT one. That
 * is the change of default: a reader with no stored choice and a system
 * that states no preference opens this page on paper, not on an
 * instrument panel. The dark palette is claimed twice below — once for a
 * system that asks for it, once for a reader who chose it here — and it
 * is written out in full rather than inverted. */
:root {
  color-scheme: light;

  /* — ground — */
  --canvas: #f3f5f3;
  --surface: #ffffff;
  --surface-2: #eef1ee; /* insets: plot ground, chips, search field */
  --line: #dfe5e0; /* hairline between rows */
  --line-strong: #c3cec6; /* the edge of a surface that must be found */

  /* — ink. Supporting ink is 6.4:1 on the canvas, measured, not eyeballed:
     it is the colour most of the page's second line is set in. — */
  --ink: #17231f;
  --ink-dim: #4c5b55;
  /* 5.2:1 on the darkest of the three light grounds. It was #6b7a73,
     which is 4.0:1 — under the floor for the 12–13px metadata it is used
     for, and sitting beside another value does not exempt text. */
  --ink-faint: #586760;

  /* — brand / interactive. Evergreen, and deliberately NOT the calm
     state's green: a link and an all-clear must not be the same colour. — */
  --brand: #265e4a;
  --brand-ink: #1d4a3a;
  --brand-wash: #e6efe9;
  /* The ink that goes ON the brand fill, stated rather than borrowed from
     --surface. White on the light evergreen is 7.6:1; white on the DARK
     theme's pale evergreen would be 1.7:1, which is what borrowing a
     surface colour for a foreground quietly risked. */
  --on-brand: #ffffff;

  /* — the four states. Each is a text-weight colour plus the faintest
     possible field; there are no filled slabs on this page. — */
  --red: #b92d36;
  --red-wash: #fbecea;
  --red-line: #eec7c3;

  --amber: #875400; /* dark amber: yellow text on white is unreadable */
  --amber-wash: #fdf3e0;
  --amber-line: #ecd6a8;

  --calm: #1d6b46;
  --calm-wash: #e8f2ea;
  --calm-line: #c4ddcc;

  /* Unknown is slate and always carries its own words. */
  --slate: #4b5b60;
  --slate-wash: #edf0f1;
  --slate-line: #ccd6d8;

  /* An active alert whose LEVEL the feed did not state. Its own colour,
     because it is neither a confirmed red nor a yellow notice nor calm,
     and a grey that reads as "nothing here" would be the wrong lie. */
  --unstated: #6b3f7d;
  --unstated-wash: #f2ecf5;
  --unstated-line: #dbc9e2;

  /* — threat kinds, used for the type badges only — */
  --t-artillery: #9a4700;
  --t-urban: #7a5300;
  --t-chemical: #3d6b00;
  --t-nuclear: #9c0f7f;

  /* — the map's land. Lower saturation than the text colours: a filled
     country at 1200px is a lot of colour, and the labels have to stay
     readable on top of it. — */
  --map-red: #d0616a;
  --map-amber: #dda94e;
  --map-nearby: #e8c98b;
  --map-calm: #a8c9b3;
  --map-unstated: #a986b8;
  --map-void: #dfe4e0;
  --map-stroke: #ffffff;
  --map-edge: #8b9a91; /* the country's own outline */
  --map-label: #2c3a34;

  /* — geometry — */
  --sp-1: 4px;
  --sp-2: 8px;
  --sp-3: 12px;
  --sp-4: 16px;
  --sp-5: 20px;
  --sp-6: 24px;
  --sp-8: 32px;
  --sp-10: 40px;
  --pad: 20px; /* page gutter; the desk widens it to 32 */
  --gap: 24px;
  --r-surface: 24px;
  --r-control: 12px;
  --tap: 44px; /* tap-target floor */

  /* — type — */
  --t-micro: 12px;
  --t-meta: 13px;
  --t-small: 15px;
  --t-body: 16px;
  --t-lead: 18px;
  --t-sub: 20px;
  --t-title: 22px;
  --t-place: 26px;
  --t-word: clamp(40px, 11.4vw, 52px);

  --face-display:
    'Fixel Display', 'Fixel Text', system-ui, -apple-system, 'Segoe UI',
    sans-serif;
  --face-text:
    'Fixel Text', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;

  /* The live state's own three, re-pointed by .band--* below. Neutral
     until a reading says otherwise. */
  --state-ink: var(--slate);
  --state-wash: var(--slate-wash);
  --state-line: var(--slate-line);

  /* The one shadow in the file, and only overlays may use it. */
  --lift: 0 6px 24px rgb(23 35 31 / 0.14);
}

/* THERE IS NO prefers-color-scheme BLOCK, and its absence is the feature.
   The page's default is light — bare :root above — and dark is a choice
   the reader makes here and this page stores. Keying the default off the
   OS gave the page two defaults, only one of which anybody could see from
   inside it. An explicit stored choice is honoured below, and it is the
   only thing that switches the palette. */

/* The dark palette, for the reader who asked for it. */
:root[data-theme='dark'] {
  color-scheme: dark;
  --canvas: #111917;
  --surface: #1a2521;
  --surface-2: #16201d;
  --line: #2b3a34;
  --line-strong: #3d5049;

  --ink: #eff5f0;
  --ink-dim: #b0beb6;
  --ink-faint: #9aaaa3; /* 6.5:1 on the lightest dark ground */

  --brand: #9bd2b7;
  --brand-ink: #b5e2c9;
  --brand-wash: #1b2a24;
  /* Dark ink on the pale evergreen: 9.5:1. */
  --on-brand: #17231f;

  --red: #ff7b80;
  --red-wash: #2c1618;
  --red-line: #5a2b2d;

  --amber: #efbd62;
  --amber-wash: #2a2114;
  --amber-line: #5b4521;

  --calm: #79cea0;
  --calm-wash: #16261d;
  --calm-line: #2c4a38;

  --slate: #a7b8bd;
  --slate-wash: #1a2326;
  --slate-line: #35464b;

  --unstated: #c9a3dc;
  --unstated-wash: #241a2b;
  --unstated-line: #463451;

  --t-artillery: #ff9a4d;
  --t-urban: #e3bd52;
  --t-chemical: #9ad354;
  --t-nuclear: #f07ad0;

  --map-red: #b2434c;
  --map-amber: #a87a2a;
  --map-nearby: #7a5f28;
  --map-calm: #2f5f45;
  --map-unstated: #6a4a80;
  --map-void: #222e2a;
  --map-stroke: #111917;
  --map-edge: #6d817a;
  --map-label: #e6efe9;

  --lift: 0 6px 24px rgb(0 0 0 / 0.5);
}

/* ============================================================== RESET === */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  background: var(--canvas);
  color: var(--ink);
  font-family: var(--face-text);
  font-size: var(--t-body);
  line-height: 1.5;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
}

h1,
h2,
h3,
p,
ul,
figure {
  margin: 0;
}

ul {
  padding: 0;
  list-style: none;
}

/* The UA's own [hidden] rule is a bare element selector, so any class rule
   with a class in it outranks it. Everything on this page is toggled by
   hidden, so it is made to win once, here. */
[hidden] {
  display: none !important;
}

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.noscript {
  margin: var(--sp-8) auto;
  max-width: 42rem;
  padding: var(--sp-6);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-surface);
  background: var(--surface);
}

/* Keyboard focus is always visible and is never removed for a mouse: this
   page is used one-handed, in the dark, in a hurry. */
:focus-visible {
  outline: 2px solid var(--brand);
  outline-offset: 2px;
  border-radius: 6px;
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    transition-duration: 0.01ms !important;
    animation-duration: 0.01ms !important;
  }
}

/* Tabular figures wherever a number is compared with the number above it —
   times, distances, ages, counts — and nowhere else. */
.obs__km,
.obs__age,
.obs__n,
.chan__num,
.chan__age,
.report__time,
.topbar__clock,
.band__clock,
.day__value,
.event__range,
.map__oblast-n,
.map__count,
.decay__left {
  font-variant-numeric: tabular-nums;
  font-feature-settings: 'tnum' 1;
}

/* =========================================================== CONTROLS === */

/* Two recipes, and both are geometry-only: a variant changes colour and
   weight, never height or radius. */
.btn,
.ghost {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--sp-2);
  min-height: var(--tap);
  padding: 0 var(--sp-4);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-control);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: var(--t-small);
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
}

.btn:hover,
.ghost:hover {
  border-color: var(--brand);
  color: var(--brand-ink);
}

.btn--primary {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--on-brand);
  font-weight: 600;
}

.btn--primary:hover {
  background: var(--brand-ink);
  border-color: var(--brand-ink);
  color: var(--on-brand);
}

/* The bar's controls: no box until they are reached for. A row of framed
   buttons in the header is what made the old bar read as a toolbar. */
.ghost {
  border-color: transparent;
  background: transparent;
  color: var(--ink-dim);
}

.ghost:hover {
  background: var(--brand-wash);
  border-color: transparent;
}

.ghost__glyph,
.brand__mark,
.place__chev,
.mapcard__glyph,
.mapcard__chev {
  width: 20px;
  height: 20px;
  flex: none;
}

/* =============================================================== APP === */

.app {
  min-height: 100dvh;
  display: flex;
  flex-direction: column;
}

/* ============================================================ TOPBAR === */

.topbar {
  position: sticky;
  top: 0;
  z-index: 20;
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  padding: var(--sp-3) var(--pad);
  padding-top: max(var(--sp-3), env(safe-area-inset-top));
  background: var(--canvas);
  border-bottom: 1px solid var(--line);
}

.brand {
  display: none; /* the phone bar belongs to the place name */
  align-items: center;
  gap: var(--sp-2);
  color: var(--brand);
  font-family: var(--face-display);
  font-weight: 700;
  font-size: var(--t-lead);
  letter-spacing: -0.01em;
}

.topbar__h1 {
  flex: 1 1 auto;
  min-width: 0;
  font-size: inherit;
  font-weight: inherit;
}

/* The place is a heading that happens to be a button. It is the largest
   thing in the bar because it is what a reader checks first and what they
   change; the chevron is there so that reads as an affordance and not as
   a title that happens to be tappable. */
.place {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  width: 100%;
  min-height: var(--tap);
  padding: var(--sp-1) var(--sp-2) var(--sp-1) 0;
  border: 0;
  border-radius: var(--r-control);
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.place:hover .place__name {
  color: var(--brand-ink);
}

.place__text {
  min-width: 0;
}

.place__name {
  display: block;
  font-family: var(--face-display);
  font-weight: 700;
  font-size: var(--t-place);
  line-height: 1.15;
  letter-spacing: -0.015em;
  color: var(--ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.place__oblast {
  display: block;
  font-size: var(--t-meta);
  line-height: 1.35;
  color: var(--ink-dim);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.place__chev {
  color: var(--ink-dim);
}

.topbar__nav {
  display: none;
  gap: var(--sp-5);
  font-size: var(--t-small);
}

.topbar__nav a {
  color: var(--ink-dim);
  text-decoration: none;
  padding: var(--sp-2) 0;
  border-bottom: 2px solid transparent;
}

.topbar__nav a:hover {
  color: var(--brand-ink);
  border-bottom-color: var(--brand);
}

/* The controls close the bar on the right, and they carry the margin that
   puts them there themselves.

   They used to inherit that position from a sibling: below 600px the h1
   grows and pushes them over, and from 1180px the nav takes `margin-left:
   auto`. Between those two the h1 stops growing (≥600px) while the nav is
   still display:none (it turns on at 1180px), so nothing pushed anything
   and the whole bar collapsed to the left — measured at 1100x900, the
   tools ended at x=844.8 against a content edge of 1068: 223.2px of dead
   space beside them. The same gap opened on a wide but short window,
   where the 1180px rule's min-height:640px does not match.

   `margin-left: auto` here does not disturb either neighbour: while the
   h1 grows it consumes the free space first and this resolves to zero,
   and the 1180px rule below sets it back to 0 where the nav leads the
   right-hand group. */
.topbar__tools {
  display: flex;
  align-items: center;
  gap: var(--sp-1);
  flex: none;
  margin-left: auto;
}

/* Quiet supporting information, never a competing title. */
.topbar__clock {
  font-size: var(--t-small);
  color: var(--ink-dim);
  padding-right: var(--sp-2);
}

/* The label is a word on a desk and a pictogram on a phone. */
.ghost--icon .ghost__label {
  display: none;
}

#change-place {
  display: none;
}

/* =============================================================== MAIN === */

.main {
  flex: 1 0 auto;
  display: grid;
  gap: var(--sp-8);
  padding: var(--sp-6) var(--pad) var(--sp-10);
  align-content: start;
}

/* One section head everywhere: a normal-case title and, at the far end,
   the reading it was taken from. No strings of tiny capitals. */
.sec__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4);
  margin-bottom: var(--sp-4);
}

.sec__title {
  font-family: var(--face-display);
  font-weight: 700;
  font-size: var(--t-sub);
  line-height: 1.25;
  letter-spacing: -0.01em;
  color: var(--ink);
}

.sec__meta {
  font-size: var(--t-meta);
  color: var(--ink-dim);
  text-align: right;
  flex: none;
}

.sec__none {
  font-size: var(--t-small);
  color: var(--ink-dim);
  padding: var(--sp-3) 0;
}

/* ========================================================= СИТУАЦІЯ === */

.situation {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  min-width: 0;
}

/* Small, and deliberately smaller than the state word under it: it names
   the question, and the answer is what the reader came for. */
.situation__title {
  font-size: var(--t-small);
  font-weight: 600;
  color: var(--ink-dim);
  letter-spacing: 0.01em;
}

/* ---- the band: one state, said in a word -------------------------------
 *
 * There is no caution tape and no filled slab here any more. The state is
 * carried by a large word in the state's own colour, a restrained emblem
 * beside it, and the sentence that explains it — on the page's ordinary
 * white surface with a one-pixel edge tinted by the state. That is enough
 * to be unmistakable and quiet enough to read for an hour.
 */

.band-slot {
  display: block;
}

.band {
  display: flex;
  flex-direction: column;
  gap: var(--sp-2);
  padding: var(--sp-6);
  border: 1px solid var(--state-line);
  border-radius: var(--r-surface);
  background: var(--state-wash);
  position: relative;
}

/* The emblem. A dot, sized like a word rather than like an icon, so the
   state is legible from across a room without a slab of colour. */
.band__word {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  font-family: var(--face-display);
  font-weight: 800;
  font-size: var(--t-word);
  line-height: 1.02;
  letter-spacing: -0.02em;
  color: var(--state-ink);
  /* Long Ukrainian words — НЕМАЄ ДАНИХ — must fit rather than clip. */
  overflow-wrap: break-word;
  hyphens: manual;
}

.band__word::before {
  content: '';
  flex: none;
  width: 0.42em;
  height: 0.42em;
  border-radius: 50%;
  background: var(--state-ink);
}

/* The state's own sentence, directly under the word: the source's reason
   for the siren, or this page's own account of what it watched happen. */
.band__sub {
  font-size: var(--t-lead);
  font-weight: 600;
  color: var(--ink);
  line-height: 1.35;
}

.band__type {
  font-size: var(--t-body);
  color: var(--ink);
}

/* «рівень не вказано», beside ТРИВОГА. Set in the state's own ink so it
   reads as part of the headline rather than as a footnote to it. */
.band__level {
  font-size: var(--t-small);
  font-weight: 700;
  color: var(--state-ink);
  text-transform: lowercase;
}

.band__meta {
  font-size: var(--t-small);
  color: var(--ink-dim);
}

/* The running clock. On the phone it shares the word's row; the wide
   layout gives it its own line under the word, where it has room. */
.band__top {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  flex-wrap: wrap;
}

.band__clock {
  font-family: var(--face-display);
  font-weight: 700;
  font-size: var(--t-title);
  color: var(--state-ink);
  line-height: 1.1;
}

/* The two-cell foot: «почалась» / «чия тривога». Facts, in a row, with
   normal-case labels above them. */
.band__foot {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-6);
  margin-top: var(--sp-2);
  padding-top: var(--sp-4);
  border-top: 1px solid var(--state-line);
}

.band__cell {
  min-width: 0;
}

.band__label {
  font-size: var(--t-meta);
  color: var(--ink-dim);
}

.band__value {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--t-lead);
  font-weight: 600;
  color: var(--ink);
  min-width: 0;
}

.band__value--sub {
  font-size: var(--t-meta);
  font-weight: 400;
  color: var(--ink-dim);
}

.band__glyph {
  width: 20px;
  height: 20px;
  flex: none;
  background-color: currentColor;
}

.band__cell[data-type='ARTILLERY'] .band__value {
  color: var(--t-artillery);
}
.band__cell[data-type='URBAN_FIGHTS'] .band__value {
  color: var(--t-urban);
}
.band__cell[data-type='CHEMICAL'] .band__value {
  color: var(--t-chemical);
}
.band__cell[data-type='NUCLEAR'] .band__value {
  color: var(--t-nuclear);
}

/* A cell that is admitting ignorance is not coloured: the quiet style IS
   the signal that this is not a reading. */
.band__cell[data-unknown='1'] .band__value {
  color: var(--ink-dim);
  font-weight: 400;
}

/* The phone's one-line answer under ПОРУЧ. */
.band__answer {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  font-size: var(--t-lead);
  color: var(--ink);
}

.band__answer-word {
  font-weight: 600;
}

.band__answer-km {
  color: var(--ink-dim);
  font-size: var(--t-small);
}

/* — the five states — */

.band--alert {
  --state-ink: var(--red);
  --state-wash: var(--red-wash);
  --state-line: var(--red-line);
}

/* An alert whose level the source did not state. Still the alert band —
   same headline, same layout — in the plum the map already uses for the
   same fact, so it is not read as a confirmed red. */
.band--alert[data-level='unknown'] {
  --state-ink: var(--unstated);
  --state-wash: var(--unstated-wash);
  --state-line: var(--unstated-line);
}

.band--notice,
.band--nearby {
  --state-ink: var(--amber);
  --state-wash: var(--amber-wash);
  --state-line: var(--amber-line);
}

.band--calm {
  --state-ink: var(--calm);
  --state-wash: var(--calm-wash);
  --state-line: var(--calm-line);
}

.band--nodata {
  --state-ink: var(--slate);
  --state-wash: var(--slate-wash);
  --state-line: var(--slate-line);
}

.band .retry,
.signal__retry {
  align-self: flex-start;
  margin-top: var(--sp-3);
  background: var(--surface);
  border-color: var(--line-strong);
}

/* ---- НЕМАЄ СИГНАЛУ: a qualifier on the state, not a state ---- */

.signal {
  margin-top: var(--sp-4);
  padding: var(--sp-4);
  border: 1px solid var(--amber-line);
  border-radius: var(--r-control);
  background: var(--surface);
  display: flex;
  flex-direction: column;
  gap: var(--sp-1);
}

.signal__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
  flex-wrap: wrap;
}

.signal__word {
  font-family: var(--face-display);
  font-weight: 700;
  font-size: var(--t-body);
  color: var(--amber);
}

.signal__age,
.signal__why {
  font-size: var(--t-meta);
  color: var(--ink-dim);
}

/* ---- the statusline: when this was last true ---- */

/* No horizontal padding: this line shares the rail's own edges. It used
   to carry `padding: 0 var(--sp-1)`, which inset it by 4px and left it
   aligned with nothing — measured at 1440px, everything else in the rail
   (the band card, the section heads, the cause text) starts at x=32
   while #live started at x=36 and #viewers ended at 428 against the
   rail's 432. */
.statusline {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-4);
  flex-wrap: wrap;
  font-size: var(--t-meta);
  color: var(--ink-dim);
}

.live {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

.live__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--calm);
  flex: none;
}

.live[data-age='stale'] .live__dot {
  background: var(--amber);
}

.live[data-age='dead'] .live__dot {
  background: var(--red);
}

.live[data-age='dead'],
.live[data-age='stale'] {
  color: var(--ink);
  font-weight: 600;
}

.live__at,
.live__flag {
  color: var(--ink-faint);
}

.live__flag {
  font-weight: 600;
  color: var(--amber);
}

/* ====================================================== ПОВІДОМЛЕННЯ === */

.cause,
.hood,
.reports,
.chart,
.channels {
  min-width: 0;
}

.cause__lede {
  display: flex;
  align-items: flex-start;
  gap: var(--sp-3);
  padding-bottom: var(--sp-4);
}

.cause__glyph {
  width: 28px;
  height: 28px;
  flex: none;
  background-color: var(--state-ink, var(--ink));
  margin-top: 2px;
}

.cause__kind {
  font-family: var(--face-display);
  font-weight: 700;
  font-size: var(--t-title);
  line-height: 1.2;
  letter-spacing: -0.01em;
}

.cause__model,
.cause__sources {
  font-size: var(--t-meta);
  color: var(--ink-dim);
}

.cause__headline {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-3);
  font-weight: 600;
}

.cause__headline-icon {
  width: 20px;
  height: 20px;
  flex: none;
  align-self: center;
  background-color: var(--state-ink, currentColor);
}

.cause__headline--unknown {
  color: var(--ink-dim);
  font-weight: 400;
}

.cause__headline-km,
.cause__where,
.cause__since,
.cause__note {
  font-size: var(--t-meta);
  color: var(--ink-dim);
}

.cause__empty-head {
  font-size: var(--t-body);
  color: var(--ink);
  margin-bottom: var(--sp-1);
}

.cause--gap {
  padding: var(--sp-4) 0;
}

/* ============================================================= LISTS ===
 *
 * Every table on this page is the same object: rows separated by one
 * hairline, a quiet normal-case header, and no box around anything. The
 * old screen drew a frame around each cell group and gave every row the
 * same weight, which is what made it read as a debug dump.
 */

.list {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--t-small);
}

.list th {
  font-weight: 400;
  font-size: var(--t-meta);
  color: var(--ink-faint);
  text-align: left;
  padding: 0 var(--sp-2) var(--sp-2);
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}

.list td {
  padding: var(--sp-3) var(--sp-2);
  border-bottom: 1px solid var(--line);
  vertical-align: baseline;
}

.list tbody tr:last-child td {
  border-bottom: 0;
}

.list th:first-child,
.list td:first-child {
  padding-left: 0;
}

.list th:last-child,
.list td:last-child {
  padding-right: 0;
}

/* ==================================== the observation list component ===
 *
 * The one row shape for every list that answers "what is being reported,
 * and where". Two lines, sometimes three, each of them full width:
 *
 *   [icon] Біла Церква  область            77 км
 *          ударні БпЛА Шахеди ×2           32хв
 *          звіт згасає через ▬▬▬▭          13хв
 *
 * It is a list and not a table because the surface it has to fit is 400px
 * wide beside the map. As a table this content needed six columns of
 * sixty-odd pixels: words broke mid-syllable and the right-hand cells
 * spilled over the map. Every line here wraps instead, so a long name
 * grows the item and nothing is ever clipped to make a width work.
 */

.obs-list {
  display: flex;
  flex-direction: column;
}

.obs {
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: var(--sp-3) 0;
  border-bottom: 1px solid var(--line);
}

.obs-list .obs:last-child,
.tier__list .obs:last-child {
  border-bottom: 0;
}

/* Each line: content at the left, its measured number at the right edge,
   where the eye compares it down the list. */
.obs__line {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-3);
}

.obs__who,
.obs__what {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: var(--sp-1) var(--sp-2);
  min-width: 0;
}

.obs__icon {
  width: 18px;
  height: 18px;
  flex: none;
  align-self: center;
  color: var(--ink-dim);
}

/* WHERE — primary, and the only thing on the item set at body size. */
.obs__place {
  font-size: var(--t-body);
  font-weight: 600;
  color: var(--ink);
  overflow-wrap: anywhere;
}

.obs__note {
  font-size: var(--t-micro);
  color: var(--ink-faint);
}

.obs__km {
  flex: none;
  font-size: var(--t-small);
  font-weight: 600;
  color: var(--ink);
  white-space: nowrap;
}

/* WHAT — secondary. */
.obs__kind {
  font-size: var(--t-meta);
  color: var(--ink-dim);
  overflow-wrap: anywhere;
}

.obs__model {
  font-size: var(--t-meta);
  color: var(--ink-faint);
  overflow-wrap: anywhere;
}

/* ×N, beside the kind it counts. */
.obs__n {
  font-size: var(--t-meta);
  font-weight: 600;
  color: var(--ink-dim);
  white-space: nowrap;
}

.obs__n::before {
  content: '×';
  color: var(--ink-faint);
  font-weight: 400;
}

/* The siren's own type word, on the rows that report one. */
.obs__badge {
  font-size: var(--t-micro);
  font-weight: 600;
  color: var(--slate);
  white-space: nowrap;
}

.obs__badge[data-type='ARTILLERY'] {
  color: var(--t-artillery);
}
.obs__badge[data-type='URBAN_FIGHTS'] {
  color: var(--t-urban);
}
.obs__badge[data-type='CHEMICAL'] {
  color: var(--t-chemical);
}
.obs__badge[data-type='NUCLEAR'] {
  color: var(--t-nuclear);
}
.obs__badge[data-type='AIR'] {
  color: var(--red);
}

/* A stated level tags the whole item, and the yellow one is amber
   wherever it appears — including on a badge whose type says AIR. */
.obs[data-level='yellow'] .obs__badge {
  color: var(--amber);
}

.obs__age {
  flex: none;
  font-size: var(--t-meta);
  color: var(--ink-faint);
  white-space: nowrap;
}

/* The third line: quiet metadata about the REPORT, not about the sky. */
.obs__expiry {
  gap: var(--sp-2);
  padding-top: 2px;
}

.obs__expiry-label {
  font-size: var(--t-micro);
  color: var(--ink-faint);
}

/* An oblast-wide entry explains the rows under it, so it is the one item
   that carries a ground of its own. */
.obs--oblast {
  background: var(--surface-2);
  border-radius: var(--r-control);
  padding-left: var(--sp-3);
  padding-right: var(--sp-3);
  margin-bottom: var(--sp-1);
}

.obs__caption {
  font-size: var(--t-meta);
  color: var(--ink-faint);
  padding-bottom: var(--sp-2);
  border-bottom: 1px solid var(--line);
}

/* — ЗГАСАННЯ ЗВІТУ — how much of a report's trusted life is left. — */
.decay-box {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  justify-content: flex-end;
}

.decay {
  display: block;
  width: 48px;
  height: 4px;
  border-radius: 2px;
  background: var(--surface-2);
  overflow: hidden;
  flex: none;
}

.decay__fill {
  display: block;
  height: 100%;
  background: var(--state-ink, var(--slate));
}

.decay__left {
  font-size: var(--t-micro);
  color: var(--ink-faint);
  white-space: nowrap;
}

/* — живі звіти: three lines of one item, sharing the observation
     component's frame. The post text gets a line of its own, which is
     what a log is for. — */

.report__time {
  flex: none;
  font-size: var(--t-meta);
  color: var(--ink-faint);
  white-space: nowrap;
}

.report__source {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  flex: none;
  white-space: nowrap;
}

.report__source-name {
  color: var(--ink-dim);
  font-size: var(--t-meta);
}

.report__trust {
  color: var(--ink-faint);
  font-size: var(--t-micro);
}

.report__raw {
  display: block;
  color: var(--ink-dim);
  font-size: var(--t-small);
  line-height: 1.45;
  padding-top: 2px;
  overflow-wrap: anywhere;
}

/* — канали — */
.chan__dot-cell {
  width: 18px;
}

.chan__dot {
  display: block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--calm);
}

.chan__dot[data-state='stale'] {
  background: var(--amber);
}

.chan__dot[data-state='dead'] {
  background: var(--red);
}

.chan__name {
  font-weight: 600;
  color: var(--ink);
}

.chan__note {
  font-size: var(--t-meta);
  color: var(--ink-dim);
  padding-top: var(--sp-3);
}

/* ============================================= the phone's tiered feed === */

.feed {
  display: flex;
  flex-direction: column;
  gap: var(--sp-6);
}

.tier__label {
  display: flex;
  align-items: baseline;
  gap: var(--sp-2);
  font-family: var(--face-display);
  font-weight: 700;
  font-size: var(--t-body);
  color: var(--ink-dim);
  margin-bottom: var(--sp-2);
}

.tier__chip {
  font-size: var(--t-meta);
  font-weight: 400;
  color: var(--ink-faint);
}

.tier__list {
  display: flex;
  flex-direction: column;
}

.feed__more {
  font-size: var(--t-meta);
  color: var(--ink-faint);
  padding-top: var(--sp-2);
}

/* =========================================== ОСТАННІ 24 ГОДИНИ (журнал) === */

.chart__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--sp-4);
  margin-bottom: var(--sp-4);
}

.chart__title {
  font-family: var(--face-display);
  font-weight: 700;
  font-size: var(--t-sub);
  letter-spacing: -0.01em;
  color: var(--ink);
}

.chart__sum {
  font-size: var(--t-meta);
  color: var(--ink-dim);
  text-align: right;
}

/* The day's figures as three plain facts, not a stats dashboard. */
.day {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-8);
  padding-bottom: var(--sp-5);
}

.day__label {
  font-size: var(--t-meta);
  color: var(--ink-dim);
}

.day__value {
  font-family: var(--face-display);
  font-weight: 700;
  font-size: var(--t-title);
  color: var(--ink);
  line-height: 1.2;
}

/* The strip. NEUTRAL on purpose: the source journal carries no level on
   its records, so a red bar would be this page inventing one. */
.chart__plot {
  position: relative;
  height: 34px;
  border-radius: 8px;
  background: var(--surface-2);
  overflow: hidden;
}

/* THE ROLES, THE WAY THE RENDERER ACTUALLY EMITS THEM. renderChart puts
   a .chart__grid in the PLOT for each clock mark, positioned by `left`,
   and a .chart__tick in the AXIS for each mark it has room to label, also
   positioned by `left`. They were styled the other way round: the labels
   became 1px vertical lines (the hatching down the right of the strip)
   and «зараз», left as the only in-flow child of a flex axis, sat at the
   start of a row whose line is at the end. */

.chart__grid {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--line);
}

.chart__span {
  position: absolute;
  top: 0;
  bottom: 0;
  background: var(--slate);
  border-radius: 3px;
}

.chart__span--ongoing {
  background: repeating-linear-gradient(
    90deg,
    var(--slate) 0 6px,
    transparent 6px 10px
  );
}

.chart__now {
  position: absolute;
  top: 0;
  bottom: 0;
  right: 0;
  width: 2px;
  background: var(--brand);
}

/* Every child is placed by the renderer, so the axis is a positioned box
   with a stated height rather than a flex row — a flex row is what put
   «зараз» at the left edge, and it is the one label on this axis that may
   never be in the wrong place. The height is its own, so a tick can never
   draw into the content below it. */
.chart__axis {
  position: relative;
  height: 1.4em;
  font-size: var(--t-micro);
  color: var(--ink-faint);
  margin-top: var(--sp-2);
}

.chart__tick {
  position: absolute;
  top: 0;
  /* Centred on its mark. The renderer overrides this to translateX(0)
     for the first label, which is pinned to the left edge so it does not
     hang off the strip. */
  transform: translateX(-50%);
  white-space: nowrap;
}

/* «зараз» is right-aligned at 100%, where the now-line is. */
.chart__now-label {
  position: absolute;
  top: 0;
  right: 0;
  color: var(--brand-ink);
  white-space: nowrap;
}

.chart__note {
  font-size: var(--t-meta);
  color: var(--ink-dim);
  padding-top: var(--sp-3);
}

.chart__events {
  display: flex;
  flex-direction: column;
  padding-top: var(--sp-3);
}

.event {
  display: flex;
  align-items: baseline;
  gap: var(--sp-3);
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--line);
  font-size: var(--t-small);
}

.chart__events .event:last-child {
  border-bottom: 0;
}

.event__range {
  font-weight: 600;
  color: var(--ink);
}

.event__type {
  color: var(--ink-dim);
}

.chart__none {
  font-size: var(--t-small);
  color: var(--ink-dim);
}

/* ============================================================== КАРТА === */

.geo {
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.geo__body {
  display: flex;
  flex-direction: column;
  gap: var(--sp-4);
  min-height: 0;
}

.map__frame {
  position: relative;
  height: 340px;
  border: 1px solid var(--line);
  border-radius: var(--r-surface);
  background: var(--surface);
  overflow: hidden;
  touch-action: none;
}

.map__svg {
  display: block;
  width: 100%;
  height: 100%;
}

.map__reset {
  position: absolute;
  left: var(--sp-4);
  bottom: var(--sp-4);
  min-height: var(--tap);
  padding: 0 var(--sp-4);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-control);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: var(--t-small);
  cursor: pointer;
  box-shadow: var(--lift);
}

.map__reset:hover {
  border-color: var(--brand);
  color: var(--brand-ink);
}

/* The legend is a short honest key, not a wall of pills. */
.map__legend {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2) var(--sp-5);
  font-size: var(--t-meta);
  color: var(--ink-dim);
}

.map__key {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
}

.map__key-dot {
  width: 10px;
  height: 10px;
  border-radius: 3px;
  background: var(--map-void);
  flex: none;
}

.map__key-dot[data-state='direct'] {
  background: var(--map-red);
}
.map__key-dot[data-state='notice'] {
  background: var(--map-amber);
}
.map__key-dot[data-state='unstated'] {
  background: var(--map-unstated);
}
.map__key-dot[data-state='nearby'] {
  background: var(--map-nearby);
}
.map__key-dot[data-state='calm'] {
  background: var(--map-calm);
}
.map__key-dot[data-state='nodata'] {
  background: var(--map-void);
  border: 1px solid var(--line-strong);
}

.panel__stale {
  display: inline-block;
  margin-left: var(--sp-2);
  font-size: var(--t-micro);
  font-weight: 600;
  color: var(--amber);
}

.map__counts {
  font-size: var(--t-meta);
  color: var(--ink-dim);
}

.map__count[data-state='direct'] {
  color: var(--red);
  font-weight: 600;
}
.map__count[data-state='notice'] {
  color: var(--amber);
  font-weight: 600;
}
.map__count[data-state='unstated'] {
  color: var(--unstated);
  font-weight: 600;
}
.map__count[data-state='nearby'] {
  color: var(--amber);
}
.map__count[data-state='calm'] {
  color: var(--calm);
}

/* Three columns, and they are declared HERE — on the list — so that every
   row is measured against the same tracks.

   As a per-row flex with justify-content:space-between this was not a
   table at all: space-between pins the first item's left edge and the
   last item's right edge and lets the middle land wherever the other two
   leave it. Measured on the live page at 1440px, the count cell started
   at x=901.5, 905, 909.9, 912, 913.4, 920.5, 931.1, 934.1, 946.5 and
   962.1 down a single list — 60.6px of drift, driven entirely by the
   length of the oblast name and of the type word beside it. The type
   word is also optional (app.js appends it only when the feed stated a
   type), and a row without one has just two flex children, which threw
   its count all the way to the right-hand edge.

   Subgrid is what makes the tracks shared rather than per-row: the row
   keeps its own box, so the rule under it still spans the full width,
   while its cells are placed in the list's columns.

   The slack is split between the outer two tracks so the count keeps
   sitting near the middle of the row, where the old flex happened to
   leave it on average — the reading rhythm the row was designed with,
   only now at one x rather than eleven. */
.map__oblasts {
  display: grid;
  grid-template-columns: minmax(0, 1fr) max-content minmax(0, 1fr);
  column-gap: var(--sp-3);
  max-height: 220px;
  overflow-y: auto;
  font-size: var(--t-small);
}

.map__oblast {
  display: grid;
  grid-column: 1 / -1;
  grid-template-columns: subgrid;
  align-items: baseline;
  padding: var(--sp-2) 0;
  border-bottom: 1px solid var(--line);
}

.map__oblasts .map__oblast:last-child {
  border-bottom: 0;
}

.map__oblast-name {
  color: var(--ink);
}

.map__oblast-n {
  color: var(--ink-dim);
  font-size: var(--t-meta);
  white-space: nowrap;
}

/* The type word closes the row on the right. Its track is as wide as the
   longest word in the list, so it no longer pushes the count around. */
.map__oblast .badge {
  justify-self: end;
}

/* — the land itself, and the two ways it is drawn. app.js paints filled
     hromada outlines into <g class="map__shapes"> when map-shapes.json has
     loaded, and falls back to the dot layer (.map__dots) when it has not.
     The sibling rules below are what switch between them: with shapes on
     the canvas the country outline stops being a fill and the dots stop
     being drawn at all, so the two never stack.

     Every colour here comes from app.css BY CLASS. Never as a fill or
     stroke attribute — fill="var(--x)" as an attribute does not resolve,
     and both themes have to work. Strokes are non-scaling and text sizes
     divide by --map-k (the live zoom, set on the canvas by app.js), so a
     zoomed country keeps hairlines and labels at their designed size. — */

.map__shape {
  stroke: var(--map-stroke);
  stroke-width: 0.4;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
  fill: var(--map-void);
}

.map__shape[data-state='calm'] {
  fill: var(--map-calm);
}
.map__shape[data-state='nearby'] {
  fill: var(--map-nearby);
}
.map__shape[data-state='notice'] {
  fill: var(--map-amber);
}
.map__shape[data-state='unstated'] {
  fill: var(--map-unstated);
}
.map__shape[data-state='direct'] {
  fill: var(--map-red);
}
.map__shape[data-state='direct'][data-type='ARTILLERY'] {
  fill: var(--t-artillery);
}
.map__shape[data-state='direct'][data-type='URBAN_FIGHTS'] {
  fill: var(--t-urban);
}
.map__shape[data-state='direct'][data-type='CHEMICAL'] {
  fill: var(--t-chemical);
}
.map__shape[data-state='direct'][data-type='NUCLEAR'] {
  fill: var(--t-nuclear);
}

/* The country's own edge. It is a FILL while the map is dots — the land
   has to be something — and an outline once the shapes are painted. */
.map__land {
  fill: var(--map-void);
  fill-rule: evenodd;
  stroke: var(--map-edge);
  stroke-width: 0.9;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.map__shapes ~ .map__land {
  fill: none;
}

.map__shapes ~ .map__dots {
  display: none;
}

.map__dots {
  fill: var(--map-void);
}

.map__dots[data-state='calm'] {
  fill: var(--map-calm);
}
.map__dots[data-state='nearby'] {
  fill: var(--map-nearby);
}
.map__dots[data-state='notice'] {
  fill: var(--map-amber);
}
.map__dots[data-state='unstated'] {
  fill: var(--map-unstated);
}
.map__dots[data-state='direct'] {
  fill: var(--map-red);
}
.map__dots[data-state='direct'][data-type='ARTILLERY'] {
  fill: var(--t-artillery);
}
.map__dots[data-state='direct'][data-type='URBAN_FIGHTS'] {
  fill: var(--t-urban);
}
.map__dots[data-state='direct'][data-type='CHEMICAL'] {
  fill: var(--t-chemical);
}
.map__dots[data-state='direct'][data-type='NUCLEAR'] {
  fill: var(--t-nuclear);
}

.map__dots[data-state='nodata'] {
  fill: none;
  stroke: var(--map-edge);
  stroke-width: 0.6;
  vector-effect: non-scaling-stroke;
}

/* WHERE THE READER IS. Drawn last, in the ink colour, so it survives
   whatever the fill under it happens to be — a 7px ring over a red field
   at country scale is otherwise a speck, and a red map read as a red
   verdict is exactly the misreading this outline exists to prevent. */
.map__mine {
  fill: none;
  stroke: var(--ink);
  stroke-width: 2;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
}

.map__home {
  fill: none;
  stroke: var(--ink);
  stroke-width: 1.6;
  vector-effect: non-scaling-stroke;
  r: calc(7px / var(--map-k, 1));
}

.map__home-label {
  fill: var(--ink);
  font-family: var(--face-display);
  font-weight: 700;
  font-size: calc(12px / var(--map-k, 1));
  paint-order: stroke;
  stroke: var(--surface);
  stroke-width: calc(3px / var(--map-k, 1));
  stroke-linejoin: round;
}

/* Oblast names, on top of the land. The halo is the page's own surface
   colour, which is what keeps a name readable over a filled region. */
.map__label {
  fill: var(--map-label);
  font-family: var(--face-text);
  font-size: calc(10px / var(--map-k, 1));
  paint-order: stroke;
  stroke: var(--surface);
  stroke-width: calc(3px / var(--map-k, 1));
  stroke-linejoin: round;
}

.map__label[data-alert='1'] {
  fill: var(--ink);
  font-weight: 600;
}

/* ---- the phone's way in ---- */

.mapcard__btn {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  min-height: 72px;
  padding: var(--sp-4);
  border: 1px solid var(--line);
  border-radius: var(--r-surface);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.mapcard__btn:hover {
  border-color: var(--brand);
}

.mapcard__glyph {
  width: 24px;
  height: 24px;
  color: var(--brand);
}

.mapcard__text {
  flex: 1 1 auto;
  min-width: 0;
}

.mapcard__title {
  display: block;
  font-family: var(--face-display);
  font-weight: 700;
  font-size: var(--t-lead);
  color: var(--ink);
}

.mapcard__sub {
  display: block;
  font-size: var(--t-meta);
  color: var(--ink-dim);
}

.mapcard__chev {
  color: var(--ink-dim);
}

/* The phone's full map view: the same panel, the same renderer, lifted
   over the face. Nothing here instantiates a second map. */
.app[data-view='map'] .main > *:not(.geo) {
  display: none;
}

.app[data-view='map'] .geo {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: var(--canvas);
  padding: var(--sp-4) var(--pad);
  padding-top: max(var(--sp-4), env(safe-area-inset-top));
  padding-bottom: max(var(--sp-4), env(safe-area-inset-bottom));
  overflow-y: auto;
}

.app[data-view='map'] .map__frame {
  height: min(58vh, 520px);
}

.geo__head {
  align-items: center;
}

.geo__close {
  order: 3;
  border: 1px solid var(--line-strong);
}

/* ============================================ ДЖЕРЕЛА (a disclosure) === */

.sources {
  display: none;
  border-top: 1px solid var(--line);
  padding-top: var(--sp-4);
}

.sources__summary,
.how summary {
  display: flex;
  align-items: center;
  gap: var(--sp-2);
  min-height: var(--tap);
  font-size: var(--t-small);
  color: var(--ink-dim);
  cursor: pointer;
  list-style: none;
}

.sources__summary::-webkit-details-marker,
.how summary::-webkit-details-marker {
  display: none;
}

.sources__summary::before,
.how summary::before {
  content: '';
  width: 8px;
  height: 8px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  flex: none;
  margin-right: var(--sp-1);
}

.sources[open] > .sources__summary::before,
.how[open] > summary::before {
  transform: rotate(45deg);
}

.sources__summary:hover,
.how summary:hover {
  color: var(--brand-ink);
}

/* ============================================================= FOOTER === */

.foot {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--sp-3) var(--sp-6);
  padding: var(--sp-5) var(--pad);
  padding-bottom: max(var(--sp-5), env(safe-area-inset-bottom));
  border-top: 1px solid var(--line);
  font-size: var(--t-meta);
  color: var(--ink-faint);
}

.how {
  flex: none;
}

.how__body {
  max-width: 60ch;
  padding-top: var(--sp-2);
  font-size: var(--t-meta);
  line-height: 1.5;
  color: var(--ink-dim);
}

.how__body p + p {
  margin-top: var(--sp-2);
}

.foot__src {
  flex: 1 1 auto;
}

.foot__ver {
  flex: none;
}

/* =========================================================== OVERLAYS === */

.sheet,
.picker {
  position: fixed;
  inset: 0;
  z-index: 60;
  background: var(--canvas);
  display: flex;
  flex-direction: column;
}

.sheet {
  align-items: center;
  justify-content: center;
  padding: var(--pad);
  background: rgb(23 35 31 / 0.45);
}

.sheet__panel {
  width: min(560px, 100%);
  max-height: 90dvh;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  padding: var(--sp-6);
  border-radius: var(--r-surface);
  background: var(--surface);
  box-shadow: var(--lift);
}

.sheet__bar,
.picker__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--sp-3);
}

.sheet__title,
.picker__title {
  font-family: var(--face-display);
  font-weight: 700;
  font-size: var(--t-title);
  letter-spacing: -0.01em;
}

.sheet__note,
.sheet__url,
.sheet__said {
  font-size: var(--t-meta);
  color: var(--ink-dim);
}

.sheet__said {
  color: var(--brand-ink);
  min-height: 1.2em;
}

.sheet__text {
  width: 100%;
  padding: var(--sp-4);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-control);
  background: var(--surface-2);
  color: var(--ink);
  font-family: var(--face-text);
  font-size: var(--t-small);
  line-height: 1.5;
  resize: vertical;
}

.sheet__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

/* ---- picker ---- */

.picker__top {
  padding: var(--sp-4) var(--pad);
  padding-top: max(var(--sp-4), env(safe-area-inset-top));
  border-bottom: 1px solid var(--line);
  display: flex;
  flex-direction: column;
  gap: var(--sp-3);
  background: var(--canvas);
}

.picker__search input {
  width: 100%;
  min-height: var(--tap);
  padding: 0 var(--sp-4);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-control);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: var(--t-body);
}

.picker__count,
.picker__crumbs,
.picker__state {
  font-size: var(--t-meta);
  color: var(--ink-dim);
}

.picker__body {
  flex: 1 1 auto;
  overflow-y: auto;
  padding: var(--sp-4) var(--pad);
  padding-bottom: max(var(--sp-6), env(safe-area-inset-bottom));
}

.picker__intro {
  max-width: 46ch;
  font-size: var(--t-small);
  color: var(--ink-dim);
  padding-bottom: var(--sp-4);
}

.recent {
  padding-bottom: var(--sp-4);
}

.recent__label {
  font-size: var(--t-meta);
  color: var(--ink-faint);
  padding-bottom: var(--sp-2);
}

.recent__row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

.recent__row button {
  min-height: var(--tap);
  padding: 0 var(--sp-4);
  border: 1px solid var(--line-strong);
  border-radius: var(--r-control);
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-size: var(--t-small);
  cursor: pointer;
}

.rows {
  display: flex;
  flex-direction: column;
}

.rows button.row {
  display: flex;
  align-items: center;
  gap: var(--sp-3);
  width: 100%;
  min-height: 56px;
  padding: var(--sp-3) var(--sp-2);
  border: 0;
  border-bottom: 1px solid var(--line);
  background: none;
  color: var(--ink);
  font: inherit;
  text-align: left;
  cursor: pointer;
}

.rows button.row:hover {
  background: var(--brand-wash);
}

.row__label {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--t-body);
}

.row__sub {
  color: var(--ink-dim);
  font-size: var(--t-meta);
}

.row__chev {
  color: var(--ink-faint);
  flex: none;
}

/* ======================================================== KIND ICONS ===
 *
 * The twelve URLs are declared ONCE, into --kind-mask, and every consumer
 * inherits the same masking rules — so a pictogram takes the colour of the
 * row it is in, which an <img> of a currentColor drawing cannot.
 *
 * The fallback in var(--kind-mask, …) is a fully transparent gradient, not
 * `none`: a slot with no data-kind must hold its column's width and draw
 * nothing, and `mask-image: none` would paint the whole box.
 */

.kind-icon {
  display: inline-block;
  background-color: currentColor;
  -webkit-mask-image: var(
    --kind-mask,
    linear-gradient(transparent, transparent)
  );
  mask-image: var(--kind-mask, linear-gradient(transparent, transparent));
  -webkit-mask-repeat: no-repeat;
  mask-repeat: no-repeat;
  -webkit-mask-position: center;
  mask-position: center;
  -webkit-mask-size: contain;
  mask-size: contain;
}

.kind-icon[data-kind='all_clear'] {
  --kind-mask: url('icons/all_clear.svg');
}
.kind-icon[data-kind='artillery'] {
  --kind-mask: url('icons/artillery.svg');
}
.kind-icon[data-kind='ballistic'] {
  --kind-mask: url('icons/ballistic.svg');
}
.kind-icon[data-kind='cruise'] {
  --kind-mask: url('icons/cruise.svg');
}
.kind-icon[data-kind='explosion'] {
  --kind-mask: url('icons/explosion.svg');
}
.kind-icon[data-kind='jet_drone'] {
  --kind-mask: url('icons/jet_drone.svg');
}
.kind-icon[data-kind='kab'] {
  --kind-mask: url('icons/kab.svg');
}
.kind-icon[data-kind='mig'] {
  --kind-mask: url('icons/mig.svg');
}
.kind-icon[data-kind='missile'] {
  --kind-mask: url('icons/missile.svg');
}
.kind-icon[data-kind='recon'] {
  --kind-mask: url('icons/recon.svg');
}
.kind-icon[data-kind='shahed'] {
  --kind-mask: url('icons/shahed.svg');
}
.kind-icon[data-kind='unknown'] {
  --kind-mask: url('icons/unknown.svg');
}

/* ============================================================ WIDTHS ===
 *
 * Placement only. Nothing below introduces a colour or a type size that is
 * not already a token above.
 */

/* ---- ≥600px: the phone stops being narrow ---- */

@media (min-width: 600px) {
  :root {
    --pad: 28px;
    --t-word: clamp(46px, 7vw, 56px);
  }

  .brand {
    display: flex;
  }

  .topbar__h1 {
    flex: 0 1 auto;
  }
}

/* ---- ≥900px: two columns. The map is still HUD-only, so the right-hand
       column is the journal and the two wide lists. ---- */

@media (min-width: 900px) {
  :root {
    --pad: 32px;
    --gap: 24px;
  }

  .main {
    grid-template-columns: minmax(340px, 400px) minmax(0, 1fr);
    grid-template-areas:
      'situation chart'
      'situation hood'
      'situation reports'
      'sources   sources';
    column-gap: var(--sp-8);
    row-gap: var(--sp-8);
  }

  .situation {
    grid-area: situation;
  }
  .chart {
    grid-area: chart;
  }
  .hood {
    grid-area: hood;
  }
  .reports {
    grid-area: reports;
  }
  .sources {
    grid-area: sources;
  }
  .geo {
    grid-area: chart;
  }

  #change-place {
    display: inline-flex;
  }

  .ghost--icon .ghost__label {
    display: inline;
  }
}

/* ---- ≥1180px and tall enough: the desk. The map moves up beside the
       situation and becomes the second thing on the page. ---- */

@media (min-width: 1180px) and (min-height: 640px) {
  :root {
    --t-word: clamp(52px, 3.8vw, 64px);
    --t-place: 30px;
  }

  .topbar {
    gap: var(--sp-6);
    padding: var(--sp-3) var(--pad);
  }

  /* No width cap. The bar's free space already sits between the title and
     the nav — `margin-left: auto` below puts it there — so a hard
     max-width only clipped the name while the room stood empty beside it.
     Measured at 1440px on region 11: the h1 was pinned at w=360 with
     #place-name wanting 330 inside a 324 text box (a 6px ellipsis) while
     the nav did not start until x=749.7, leaving 231px unused.

     `flex: 0 1 auto` keeps the box hugging its own text, so the button is
     never wider than the name it carries; shrink still applies, so a name
     long enough to fill the bar clips as a last resort rather than
     pushing the nav off the end. */
  .topbar__h1 {
    flex: 0 1 auto;
  }

  .topbar__nav {
    display: flex;
    margin-left: auto;
  }

  .topbar__tools {
    margin-left: 0;
  }

  .main {
    grid-template-columns: 400px minmax(0, 1fr);
    grid-template-areas:
      'situation geo'
      'chart     chart'
      'hood      reports'
      'sources   sources';
    column-gap: var(--sp-8);
    row-gap: var(--sp-10);
  }

  .geo {
    grid-area: geo;
    align-self: start;
  }

  /* The map is the point of the right-hand column, so it gets the height
     an actual country needs rather than whatever is left over. */
  .map__frame {
    height: 600px;
  }

  .map__oblasts {
    max-height: 168px;
  }

  .mapcard {
    display: none;
  }

  /* The only content of this disclosure is #channels-panel, which
     applyLayoutMode reveals on exactly this query and whose /v1/health
     lane is polled on the same gate. Tying the wrapper to the same
     condition is what keeps it from ever opening onto nothing. */
  .sources {
    display: block;
  }

  /* The band's word and its running clock stack on the desk; the phone
     puts them on one row because it cannot spend 110px on two things read
     together. */
  .band__top {
    display: block;
  }

  .band {
    padding: var(--sp-8);
    gap: var(--sp-3);
  }

  .situation {
    gap: var(--sp-5);
  }

  /* The disclosure is lifted out of flow so opening it cannot re-lay out
     the instrument under the reader's hands. */
  .how {
    position: relative;
  }

  .how__body {
    position: absolute;
    bottom: calc(100% + var(--sp-2));
    left: 0;
    width: min(56ch, 60vw);
    padding: var(--sp-5);
    border: 1px solid var(--line);
    border-radius: var(--r-control);
    background: var(--surface);
    box-shadow: var(--lift);
    z-index: 10;
  }
}

/* ---- 320px: everything still has to fit and nothing may overflow ---- */

@media (max-width: 360px) {
  :root {
    --pad: 16px;
    --t-place: 22px;
    --t-word: clamp(36px, 11vw, 44px);
  }

  .band {
    padding: var(--sp-5) var(--sp-4);
  }

  .obs__line {
    gap: var(--sp-2);
  }
}
