/* ==========================================================================
   GlobeVerge — PAR AVION
   The full system is written down in DESIGN.md. Two rules explain most of
   this file:

     1. The ground is cold blue-grey. Never cream, never warm.
     2. Nothing lifts. There is no box-shadow in this stylesheet, and no
        border-radius except 2px on the transit-strike squares.

   Depth comes from three flat devices only: the crease hairline, the
   barber-stripe band, and the tonal step between --stock and --sheet.
   ========================================================================== */

/* --- Tokens --------------------------------------------------------------- */

:root {
  /* Postal inks. Blue means home, red means world — see The Two Axes Rule. */
  --airmail-blue: #1d3fb8;
  --cancel-red: #c62a17;
  --customs-green: #166b40;
  --stamp-violet: #6b3f9e;
  --handstamp: #9c4708;

  /* Cold stock. */
  --stock: #e8edf4;
  --sheet: #f4f7fb;
  --ink: #141b2d;
  --ink-soft: #4a5670;
  --crease: #c3cdde;

  /* Two faces, no more. The mono is for measurement, never for prose. */
  --grotesque: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
  --mono: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, "Liberation Mono", monospace;

  /* The type ramp. Nine steps, and no size in this file is written as a
     literal — every font-size below references one of these. The three fluid
     steps carry their own responsive behaviour, so there are no font-size
     overrides in the media queries. */
  --t-display: clamp(1.75rem, 5.5vw, 3rem);
  --t-brand: clamp(1.125rem, 3vw, 1.375rem);
  --t-headline: clamp(1.0625rem, 2.4vw, 1.3125rem);
  --t-prose: 1rem;
  --t-body: 0.9375rem;
  --t-sub: 0.875rem;
  --t-mark: 0.75rem;
  --t-label: 0.6875rem;
  --t-micro: 0.625rem;

  /* 4px rhythm. */
  --xs: 4px;
  --sm: 8px;
  --md: 12px;
  --lg: 16px;
  --xl: 24px;
  --x2: 32px;
  --x3: 48px;
  --x4: 64px;

  --strike-ease: cubic-bezier(.16, 1, .3, 1);

  color-scheme: light dark;
}

/* The night rendition moves the same inks onto deep ink-navy rather than
   dimming them. The two blocks below are identical by necessity — vanilla CSS
   has no way to share them. Change one, change both. */
:root[data-theme="dark"] {
  --airmail-blue: #5b7cf5;
  --cancel-red: #f4614a;
  --customs-green: #39b477;
  --stamp-violet: #a87ce0;
  --handstamp: #f0913c;
  --stock: #0c1220;
  --sheet: #141c2e;
  --ink: #e4eaf4;
  --ink-soft: #94a3bd;
  --crease: #25324a;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --airmail-blue: #5b7cf5;
    --cancel-red: #f4614a;
    --customs-green: #39b477;
    --stamp-violet: #a87ce0;
    --handstamp: #f0913c;
    --stock: #0c1220;
    --sheet: #141c2e;
    --ink: #e4eaf4;
    --ink-soft: #94a3bd;
    --crease: #25324a;
  }
}

/* --- Base ---------------------------------------------------------------- */

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--stock);
  color: var(--ink);
  font-family: var(--grotesque);
  font-size: var(--t-body);
  line-height: 1.5;
  -webkit-text-size-adjust: 100%;
}

a { color: inherit; text-decoration: none; }

::selection { background: var(--airmail-blue); color: var(--sheet); }

:focus-visible { outline: 2px solid var(--airmail-blue); outline-offset: 2px; }

img { max-width: 100%; }

.hidden { display: none !important; }

.skip {
  position: absolute;
  left: -9999px;
  top: var(--sm);
  padding: var(--sm) var(--md);
  background: var(--airmail-blue);
  color: var(--sheet);
  font-weight: 800;
  z-index: 10;
}
.skip:focus { left: var(--sm); }

.sheet-wrap {
  max-width: 1080px;
  margin: 0 auto;
  padding: var(--lg) var(--lg) var(--x3);
}

/* --- Type roles ---------------------------------------------------------- */

/* The etiquette voice: caps, heavy, widely tracked. */
.label {
  font-size: var(--t-label);
  font-weight: 800;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  line-height: 1;
}

/* Machine type. Every number a reader might compare between rows. */
.mark {
  font-family: var(--mono);
  font-size: var(--t-mark);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.02em;
  line-height: 1;
  color: var(--ink-soft);
}

/* --- Masthead ------------------------------------------------------------ */

.masthead {
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: var(--md);
  padding-bottom: var(--md);
  border-bottom: 1px solid var(--crease);
}

.masthead-aside {
  display: flex;
  align-items: center;
  gap: var(--md);
  margin-left: auto;
}

.masthead-brand {
  display: flex;
  align-items: center;
  gap: var(--sm);
  margin-right: auto;
  min-width: 0;
}

.masthead-logo { width: 34px; height: 34px; object-fit: contain; }

.masthead h1 {
  margin: 0;
  font-size: var(--t-brand);
  font-weight: 800;
  letter-spacing: -0.02em;
}

.masthead-sub {
  margin: 1px 0 0;
  font-size: var(--t-micro);
  font-weight: 800;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* The two-timezone spine: the reader is not in the place they are reading
   about, and the clocks say so before any headline does. */
.clocks { display: flex; flex-direction: column; gap: 3px; }

.clock {
  display: flex;
  gap: var(--sm);
  align-items: baseline;
  font-family: var(--mono);
  font-size: var(--t-mark);
  font-variant-numeric: tabular-nums;
  line-height: 1.25;
  color: var(--ink-soft);
  white-space: nowrap;
}

.clock b {
  font-family: var(--grotesque);
  font-size: var(--t-micro);
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.clock-home { color: var(--airmail-blue); }

/* Weather sits in the clock stack because it answers the same question they do:
   where is the reader, as distinct from which country's news they came for. It
   inherits .clock wholesale and adds only the parts that differ.

   Deliberately monochrome. The Two Axes Rule reserves blue for home and red for
   world, and spending an ink on "partly cloudy" would spend it on nothing. A
   thunderstorm is the exception that earns one. */
.weather { gap: var(--sm); }

.weather-temp {
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}

.weather-cond {
  font-family: var(--grotesque);
  font-size: var(--t-micro);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.weather-storm .weather-temp,
.weather-storm .weather-cond { color: var(--cancel-red); }
.weather-storm b { color: var(--cancel-red); }

/* --- Tags (navigation, buttons, the theme switch) ------------------------ */

.tag {
  padding: 7px 11px;
  border: 1px solid var(--crease);
  border-radius: 0;
  background: transparent;
  color: var(--ink);
  font-family: var(--grotesque);
  font-size: var(--t-label);
  font-weight: 800;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  cursor: pointer;
  transition: color 120ms linear, border-color 120ms linear;
}
.tag:hover { border-color: var(--airmail-blue); color: var(--airmail-blue); }
.tag.active {
  background: var(--airmail-blue);
  border-color: var(--airmail-blue);
  color: var(--sheet);
}

/* --- Modules ------------------------------------------------------------- */

.module { margin-top: var(--x3); }

/* The stripe band. A section announces its axis with the barber stripe and
   the ink; the name is secondary to both. */
.band {
  display: flex;
  align-items: center;
  gap: var(--md);
  padding: var(--sm) var(--md);
  background: var(--airmail-blue);
  color: var(--sheet);
  margin-bottom: var(--lg);
  flex-wrap: wrap;
}

.band-world { background: var(--cancel-red); }

/* The mixed list is neither axis, so it takes neither ink. Cancel Red here
   would dilute The Two Axes Rule. */
.band-plain { background: var(--ink); }

.band-stripe {
  flex: 0 0 48px;
  height: 14px;
  background: repeating-linear-gradient(-45deg,
      var(--sheet) 0 6px, transparent 6px 12px);
}

.band-name { margin-right: auto; }

.band-count {
  font-family: var(--mono);
  font-size: var(--t-mark);
  font-variant-numeric: tabular-nums;
}

/* The origin field lives inside the Home band — a diaspora reader's home is a
   choice, not their IP address. */
.origin {
  appearance: none;
  padding: 4px 22px 4px 8px;
  border: 1px solid currentColor;
  border-radius: 0;
  background-color: transparent;
  background-image: linear-gradient(45deg, transparent 50%, currentColor 50%),
                    linear-gradient(135deg, currentColor 50%, transparent 50%);
  background-position: right 9px center, right 5px center;
  background-size: 4px 4px, 4px 4px;
  background-repeat: no-repeat;
  color: inherit;
  font-family: var(--grotesque);
  font-size: var(--t-label);
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
}
.origin option { color: var(--ink); background: var(--sheet); }

/* --- Postmark ------------------------------------------------------------ */

/* "Since you last looked" — a single struck line rather than a block, so the
   lead story keeps the first screen. Absent entirely when nothing is new, and
   the only animated element in the system. */
.postmark-strip {
  display: flex;
  align-items: center;
  gap: var(--md);
  flex-wrap: wrap;
  margin-top: var(--md);
  padding-bottom: var(--md);
  border-bottom: 1px solid var(--crease);
}

.postmark {
  padding: 5px var(--sm);
  border: 2px double var(--stamp-violet);
  color: var(--stamp-violet);
  font-family: var(--mono);
  font-size: var(--t-mark);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  transform: rotate(-1.5deg);
  animation: strike 340ms var(--strike-ease) both;
}

@keyframes strike {
  from { opacity: 0; transform: rotate(-1.5deg) scale(1.35); }
  to   { opacity: 1; transform: rotate(-1.5deg) scale(1); }
}

.postmark-text { font-size: var(--t-sub); color: var(--ink-soft); }
.postmark-text b { color: var(--ink); }

/* --- The front ----------------------------------------------------------- */

/* The lead, then the reader's country, then their city — in that order stacked,
   and with country and city sharing a rail beside the lead on a wide screen. */
.front { display: grid; gap: var(--x2); margin-top: var(--xl); }
.front > .module, .front-rail > .module { margin-top: 0; }
.front-rail { display: grid; gap: var(--x2); align-content: start; }

/* Local takes the customs green: it is the one section whose relevance is a fact
   about the reader rather than about the news. */
.band-local { background: var(--customs-green); }

.band-action {
  margin-left: auto;
  padding: 4px var(--sm);
  border: 1px solid currentColor;
  border-radius: 0;
  background: transparent;
  color: inherit;
  font-family: var(--grotesque);
  cursor: pointer;
}
/* Derived from the Sheet token rather than a raw white, so it stays correct on
   whichever ink the band happens to be carrying. */
.band-action:hover { background: color-mix(in srgb, var(--sheet) 18%, transparent); }

.more-local { margin-top: var(--md); }

/* --- The signal slip ------------------------------------------------------ */

/* Back on every story, but as a control rather than an always-open panel. */
.signal-wrap { display: inline-flex; }

/* The slip is positioned against the whole row, not against the little button it
   hangs off. Anchored to the button it ran past the right edge of a phone and
   gave the page a horizontal scrollbar; bounded left-and-right by the row, it
   cannot. */
.story { position: relative; }

/* The Manifest control. As a grey ghost button it read as metadata — the register
   button it read as metadata — the register readers are trained to skip. It now
   carries the signal's own violet ink throughout — border, text, and a wash of the
   same hue — so it reads as stamped rather than labelled.

   The prominence is entirely in the ink. An earlier version added a 3px left border
   to suggest a stamp edge; it is gone, because a thick coloured edge down one side is
   the most generic move in contemporary UI and it was adding nothing the violet was
   not already doing on its own.

   The pip is the informative half: it takes its colour from the story's own signal
   level, so the closed control already says "this one is critical" before anyone
   opens it. Colour where the data earns it, per DESIGN.md.

   Resting state is a tint, not a fill. Twelve solid violet blocks down a page was
   exactly the density failure that got the signal panel removed the first time. */
.signal-btn {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  padding: 3px var(--sm) 3px 7px;
  border: 1px solid var(--stamp-violet);
  border-radius: 0;
  background: transparent;
  background: color-mix(in srgb, var(--stamp-violet) 9%, transparent);
  color: var(--stamp-violet);
  font-family: var(--grotesque);
  font-size: var(--t-label);
  font-weight: 800;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  cursor: pointer;
  transition: color 120ms linear, border-color 120ms linear, background-color 120ms linear;
}

.signal-pip {
  flex: 0 0 auto;
  width: 6px;
  height: 6px;
  background: currentColor;
}

/* A model wrote this one. The reader is told so by the label; the stamp keeps Stamp
   Violet, because the signal is still the signal. The only visual difference is a
   double rule — the second impression a re-stamped cover carries. Spending a new
   ink here would break The Two Axes Rule for a distinction the word "AI" already
   makes, and the padding is trimmed by the 2px the wider border adds so the button
   stays the same height as its keyword twin beside it in the same row. */
.signal-btn.signal-ai { border-style: double; border-width: 3px; padding-block: 1px; }

/* Open, hovered or focused: invert to a solid stamp. The level is spelled out in
   the panel by then, so the pip losing its colour here costs nothing. */
.signal-btn:hover,
.signal-btn:focus-visible,
.signal-wrap[data-open="true"] .signal-btn {
  background: var(--stamp-violet);
  color: var(--sheet);
}

/* .signal-high resolves to the same violet the button already uses, so it needs no
   colour of its own — only protection from the lighter font-weight that rule sets
   for inline use. Two classes beats one whatever the source order. */
.signal-btn.signal-high { font-weight: 800; }

/* A critical story stamps in its own ink rather than the generic violet. */
.signal-btn.signal-critical {
  border-color: var(--cancel-red);
  color: var(--cancel-red);
  background: transparent;
  background: color-mix(in srgb, var(--cancel-red) 9%, transparent);
  font-weight: 800;
}
.signal-btn.signal-critical:hover,
.signal-btn.signal-critical:focus-visible,
.signal-wrap[data-open="true"] .signal-btn.signal-critical {
  background: var(--cancel-red);
  color: var(--sheet);
}

/* The slip itself: a customs declaration torn off and held against the row. */
.signal-slip {
  position: absolute;
  z-index: 20;
  top: 100%;
  left: 0;
  right: 0;
  max-width: 360px;
  padding: var(--md);
  background: var(--sheet);
  border: 1px solid var(--stamp-violet);
  opacity: 0;
  visibility: hidden;
  transition: opacity 120ms linear;
}

/* Hover for a pointer, focus for a keyboard, data-open for a tap. */
.signal-wrap:hover .signal-slip,
.signal-wrap:focus-within .signal-slip,
.signal-wrap[data-open="true"] .signal-slip { opacity: 1; visibility: visible; }

.slip-head {
  display: flex;
  align-items: center;
  gap: var(--sm);
  flex-wrap: wrap;
  padding-bottom: var(--sm);
  margin-bottom: var(--sm);
  border-bottom: 1px solid var(--crease);
  color: var(--ink-soft);
}

.signal-slip dl { margin: 0; }
.slip-row { padding: var(--xs) 0; }
.slip-row dt {
  font-size: var(--t-label);
  font-weight: 800;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.slip-row dd { margin: 2px 0 0; font-size: var(--t-sub); line-height: 1.4; }

/* The last rows in a list would otherwise open a slip off the bottom of the
   viewport; flipping above the row keeps it on screen. */
.story:nth-last-child(-n+3) .signal-slip { top: auto; bottom: calc(100% + 6px); }

/* --- The wire (pulse readings) ------------------------------------------- */

.pulse-row {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 1px;
  background: var(--crease);
  border: 1px solid var(--crease);
}

.pulse {
  display: grid;
  grid-template-columns: auto 1fr;
  grid-template-areas: "dot name" "value value" "count count";
  gap: 3px var(--sm);
  align-items: center;
  padding: var(--md);
  background: var(--stock);
  border: 0;
  border-radius: 0;
  text-align: left;
  font-family: var(--grotesque);
  color: var(--ink);
}
.pulse-live { cursor: pointer; transition: background 120ms linear; }
.pulse-live:hover { background: var(--sheet); }
.pulse-live:focus-visible { outline: 2px solid var(--airmail-blue); outline-offset: -2px; }

.pulse-dot { grid-area: dot; width: 9px; height: 9px; border-radius: 50%; background: var(--ink-soft); }
.pulse-name { grid-area: name; color: var(--ink-soft); }
.pulse-value { grid-area: value; font-size: var(--t-headline); font-weight: 800; letter-spacing: -0.017em; line-height: 1.1; }
.pulse .mark { grid-area: count; }

.pulse-red { background: var(--cancel-red); }
.pulse-amber { background: var(--handstamp); }
.pulse-green { background: var(--customs-green); }
.pulse-blue { background: var(--airmail-blue); }
.pulse-grey { background: var(--ink-soft); }

/* --- Where it's happening ------------------------------------------------ */

.regions { display: grid; gap: var(--sm); }

.region {
  display: grid;
  grid-template-columns: 104px 1fr auto;
  align-items: center;
  gap: var(--md);
  padding: var(--sm);
  background: transparent;
  border: 0;
  border-radius: 0;
  cursor: pointer;
  text-align: left;
  font-family: var(--grotesque);
  color: var(--ink);
  transition: background 120ms linear;
}
.region:hover { background: var(--sheet); }
.region:focus-visible { outline: 2px solid var(--airmail-blue); outline-offset: -2px; }
.region[aria-pressed="true"] { background: var(--sheet); }
.region[aria-pressed="true"] .region-bar span { background: var(--cancel-red); }

.region-name { color: var(--ink-soft); }
.region[aria-pressed="true"] .region-name { color: var(--ink); }

/* A bar, not a chart: it is one number per row and the count sits beside it. */
.region-bar { height: 8px; background: var(--crease); overflow: hidden; }
.region-bar span { display: block; height: 100%; background: var(--airmail-blue); }

/* --- Story rows --------------------------------------------------------- */

/* A story is a headline, a byline row and a crease. No card, no container. */
.story { padding-bottom: var(--lg); border-bottom: 1px solid var(--crease); }
.story + .story { margin-top: var(--lg); }

.story-head {
  display: block;
  font-weight: 700;
  letter-spacing: -0.017em;
  line-height: 1.2;
  font-size: var(--t-headline);
  transition: color 120ms linear;
}
.story-head:hover { color: var(--airmail-blue); }

.story-meta {
  display: flex;
  align-items: center;
  gap: var(--sm) var(--md);
  flex-wrap: wrap;
  margin-top: var(--sm);
}

.story-pub {
  font-size: var(--t-label);
  font-weight: 800;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.story-excerpt {
  margin: var(--sm) 0 0;
  max-width: 68ch;
  color: var(--ink-soft);
  font-size: var(--t-sub);
}

/* Dense variant for the ranked list at the foot of the page. */
.story-tight { padding-bottom: var(--md); }
.story-tight + .story-tight { margin-top: var(--md); }
.story-tight .story-head { font-size: var(--t-body); line-height: 1.3; letter-spacing: -0.01em; }

/* A picture sits beside the headline, never above it, so a row without one is
   the same shape as a row with one. */
/* Flex, not a two-column grid. A broken image removes itself via onerror, and a
   grid would keep reserving the 96px track — the headline then rendered inside
   the empty thumbnail column, eight words tall. With flex the text simply takes
   the whole row back. */
.story-illustrated { display: flex; align-items: flex-start; gap: var(--md); }

.story-thumb { flex: 0 0 96px; display: block; }
.story-illustrated .story-text { flex: 1 1 auto; min-width: 0; }
.story-thumb img,
.story-mark {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  background: var(--sheet);
}

/* The publisher's handstamp, for the quarter of the feed whose newsroom ships no
   picture. It fills the same 96px square rather than leaving a hole, and it is a
   mark rather than a fake photograph — flat Sheet ground, a Crease rule, and the
   masthead's own initials letterspaced in the way the postmark sets a cancellation.
   No fetch, so it cannot fail; a picture that 404s is replaced by this. */
.story-mark {
  display: grid;
  place-items: center;
  border: 1px solid var(--crease);
  font-family: var(--grotesque);
  font-size: var(--t-mark);
  font-weight: 800;
  letter-spacing: 0.16em;
  text-indent: 0.16em;
  color: var(--ink-soft);
}

/* Everything else runs in two columns once there is room. It is much the longest
   module on the page — 25 rows against the five and three above it — and in one
   column it buried the colophon most of a screen down. Two columns halve the
   scroll without changing the row itself.

   `minmax(0, 1fr)` rather than `1fr`: a grid track's default minimum is its
   content, so one unbreakable headline would widen its column and squeeze the
   other. Row spacing moves from the sibling margin to `row-gap`; leaving both
   would double-space every row after the first pair. */
@media (min-width: 768px) {
  #dispatchList {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--xl);
    row-gap: var(--md);
    align-items: start;
  }
  #dispatchList .story + .story { margin-top: 0; }
}

/* --- The wire crawl ------------------------------------------------------ */

/* The one thing on the page that moves on its own. It is allowed because its
   subject *is* movement — the feed is larger than the page and does not stop —
   and because it moves at a reading pace rather than to attract attention.
   Everything else still obeys the No Motion rule.

   Creases top and bottom, no container: the strip is a rule the headlines travel
   along, not a box they sit inside. */
.crawl {
  /* Tight to the postmark above it, not a section's remove — the two are one band
     answering one question, and 48px between them broke that. */
  margin-top: var(--lg);
  border-top: 1px solid var(--crease);
  border-bottom: 1px solid var(--crease);
  padding: var(--sm) 0;
  overflow: hidden;
}
.crawl:empty { display: none; }

.crawl-track { display: flex; width: max-content; animation: crawl var(--crawl-duration, 70s) linear infinite; }
.crawl-run { display: flex; }

/* Half the track is a duplicate, so travelling exactly -50% lands on the seam. */
@keyframes crawl { to { transform: translateX(-50%); } }

/* Reading something that is moving is a choice, so it stops for anyone who
   reaches for it — pointer or keyboard. */
.crawl:hover .crawl-track,
.crawl:focus-within .crawl-track { animation-play-state: paused; }

.crawl-item {
  display: flex;
  align-items: baseline;
  gap: var(--sm);
  padding: 0 var(--xl);
  border-right: 1px solid var(--crease);
  white-space: nowrap;
  text-decoration: none;
  color: inherit;
}
.crawl-item:hover .crawl-head { color: var(--airmail-blue); }
.crawl-item:focus-visible { outline: 2px solid var(--airmail-blue); outline-offset: 2px; }

.crawl-pub {
  font-size: var(--t-label);
  font-weight: 800;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.crawl-head { font-size: var(--t-body); font-weight: 700; letter-spacing: -0.01em; }

/* The global reduced-motion rule kills the animation, which would strand the
   track mid-slide showing one headline. Hand the reader the same content as a
   strip they scroll themselves, and drop the duplicate run so it is not met
   twice. */
@media (prefers-reduced-motion: reduce) {
  .crawl { overflow-x: auto; }
  .crawl-track { width: auto; }
  .crawl-run[aria-hidden="true"] { display: none; }
}

/* --- Transit strike ----------------------------------------------------- */

/* Corroboration as accumulated cancellations: one square per newsroom
   carrying the story. Green only once the breadth floor actually clears. */
.strike { display: inline-flex; align-items: center; gap: 3px; }

.strike i { width: 6px; height: 6px; border-radius: 2px; background: var(--ink-soft); }

.strike-cleared i { background: var(--customs-green); }

.strike-text { margin-left: var(--xs); }
.strike-cleared .strike-text { color: var(--customs-green); }

.signal-level { text-transform: uppercase; letter-spacing: 0.08em; }
.signal-critical { color: var(--cancel-red); font-weight: 700; }
.signal-high { color: var(--stamp-violet); font-weight: 700; }

/* --- Lead story --------------------------------------------------------- */

.lead { background: var(--sheet); border: 1px solid var(--crease); }

.lead-figure { margin: 0; }

.lead-figure img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background: var(--stock);
}

.lead-body { padding: var(--lg); }

.lead-claim {
  display: inline-block;
  margin-bottom: var(--md);
  padding: 4px var(--sm);
  background: var(--cancel-red);
  color: var(--sheet);
  font-size: var(--t-label);
  font-weight: 800;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}

.lead-head {
  display: block;
  margin: 0 0 var(--md);
  font-size: var(--t-display);
  font-weight: 800;
  line-height: 1.04;
  letter-spacing: -0.03em;
  transition: color 120ms linear;
}
.lead-head:hover { color: var(--airmail-blue); }

.lead-excerpt { margin: var(--md) 0 0; max-width: 68ch; font-size: var(--t-prose); color: var(--ink-soft); }

.cta {
  display: inline-block;
  margin-top: var(--lg);
  padding: var(--md) var(--lg);
  background: var(--airmail-blue);
  color: var(--sheet);
  font-size: var(--t-label);
  font-weight: 800;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  transition: background 120ms linear;
}
.cta:hover { background: var(--ink); }

/* --- Declaration block -------------------------------------------------- */

/* The full Manifest, laid out as a CN22 customs declaration. Once per page, on
   the lead story only. `GlobeSignal` is reserved for the AI analysis — see
   src/signal-prompt.js — and this box is a keyword lookup. */
.declaration { margin-top: var(--lg); border: 1px solid var(--crease); }

.declaration-head {
  display: flex;
  align-items: center;
  gap: var(--sm);
  padding: var(--sm) var(--md);
  border-bottom: 1px solid var(--crease);
  color: var(--ink-soft);
}

.declaration dl { margin: 0; }

.declaration-row {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--xs);
  padding: var(--md);
  border-bottom: 1px solid var(--crease);
}
.declaration-row:last-child { border-bottom: 0; }

.declaration-row dt {
  font-size: var(--t-label);
  font-weight: 800;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.declaration-row dd { margin: 0; font-size: var(--t-sub); }

.chain { display: flex; flex-wrap: wrap; gap: var(--xs); align-items: center; }

.chain span { padding: 3px var(--sm); border: 1px solid var(--crease); font-size: var(--t-mark); font-weight: 700; }

.chain em { color: var(--ink-soft); font-style: normal; }

/* --- Sorting bar -------------------------------------------------------- */

.sorting { display: flex; flex-wrap: wrap; gap: var(--sm); margin-bottom: var(--md); }

.sorting input,
.sorting select {
  padding: 8px 10px;
  border: 1px solid var(--crease);
  border-radius: 0;
  background: var(--sheet);
  color: var(--ink);
  font-family: var(--grotesque);
  font-size: var(--t-sub);
}
.sorting input { flex: 1 1 200px; min-width: 0; }
.sorting input::placeholder { color: var(--ink-soft); }

.tags { display: flex; flex-wrap: wrap; gap: var(--xs); margin-bottom: var(--lg); }

/* --- Notices and empty states ------------------------------------------- */

.notice {
  margin-top: var(--xl);
  padding: var(--md) var(--lg);
  border: 1px solid var(--cancel-red);
  color: var(--ink);
  font-size: var(--t-sub);
}

.notice button { margin-left: var(--sm); }

.empty-note { padding: var(--xl) 0; color: var(--ink-soft); font-size: var(--t-sub); }

/* --- Skeletons ---------------------------------------------------------- */

.sk { background: var(--crease); opacity: 0.45; height: 14px; margin-bottom: var(--sm); }
.sk-head { height: 26px; }
.sk-lead { height: 200px; margin-bottom: 0; }

/* --- Footer ------------------------------------------------------------- */

.colophon {
  /* The section above already carries `.module`'s 48px, and 64px on top of that left
     a band of dead space taller than a story row. */
  margin-top: var(--x3);
  padding-top: var(--lg);
  border-top: 1px solid var(--crease);
  color: var(--ink-soft);
  font-size: var(--t-sub);
}

.colophon p { margin: 0 0 var(--md); max-width: 68ch; }
.colophon b { color: var(--ink); }

/* Two columns, matching the feed directly above it. As a single 68ch column under a
   two-column feed the disclosure read as an afterthought pushed into the corner, with
   half the sheet empty beside it — and it is the part of the page that says what the
   site is and is not, so it should not look like something left over. The links and
   the imprint span both, because they are the foot of the page rather than a column
   of it. */
@media (min-width: 768px) {
  .colophon {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: var(--xl);
    align-items: start;
  }
  .colophon p { max-width: none; }
  .colophon-links,
  .colophon > p:last-child { grid-column: 1 / -1; }
}

.colophon-links { display: flex; flex-wrap: wrap; gap: var(--sm) var(--lg); }
.colophon-links a { text-decoration: underline; text-underline-offset: 2px; }
.colophon-links a:hover { color: var(--airmail-blue); }

/* --- Article page ------------------------------------------------------- */

/* src/article.js links this same stylesheet rather than carrying its own copy.
   It used to inline a near-duplicate that had already drifted — different
   .signal-headline size, a hard-coded amber because the token did not exist
   there. One file, one source of truth, no drift possible. */

.reading-wrap {
  max-width: 720px;
  margin: 0 auto;
  padding: var(--lg) var(--lg) var(--x3);
}

.reading-masthead {
  display: flex;
  align-items: center;
  gap: var(--sm);
  padding-bottom: var(--md);
  margin-bottom: var(--lg);
  border-bottom: 1px solid var(--crease);
}
.reading-masthead img { width: 30px; height: 30px; object-fit: contain; }
.reading-masthead span {
  font-size: var(--t-brand);
  font-weight: 800;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.crumb { margin-bottom: var(--lg); color: var(--ink-soft); }
.crumb a { text-decoration: underline; text-underline-offset: 2px; }
.crumb a:hover { color: var(--airmail-blue); }

/* The category stamp. Conflict takes Cancel Red; everything else is the
   neutral ink, because a category is not an alarm. */
.article-stamp {
  display: inline-block;
  margin-bottom: var(--md);
  padding: 4px var(--sm);
  background: var(--ink);
  color: var(--stock);
  font-size: var(--t-label);
  font-weight: 800;
  letter-spacing: 0.13em;
  text-transform: uppercase;
}
.article-stamp-conflict { background: var(--cancel-red); color: var(--sheet); }

.article-title {
  margin: 0 0 var(--md);
  font-size: var(--t-display);
  font-weight: 800;
  line-height: 1.04;
  letter-spacing: -0.03em;
}

.article-figure { margin: var(--lg) 0; }
.article-figure img {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  background: var(--sheet);
}

.tracked { margin: var(--sm) 0 0; color: var(--ink-soft); }

.article-body {
  margin: var(--lg) 0;
  max-width: 68ch;
  font-size: var(--t-prose);
  line-height: 1.6;
}

.also, .share, .more { margin-top: var(--xl); }

.also { color: var(--ink-soft); font-size: var(--t-sub); }
.also a { color: var(--airmail-blue); text-decoration: underline; text-underline-offset: 2px; }

.share { display: flex; flex-wrap: wrap; gap: var(--sm) var(--md); align-items: center; }
.share a { text-decoration: underline; text-underline-offset: 2px; }
.share a:hover { color: var(--airmail-blue); }

.more { padding-top: var(--lg); border-top: 1px solid var(--crease); }
.more h2 { margin: 0 0 var(--md); font-size: var(--t-label); font-weight: 800; letter-spacing: 0.13em; text-transform: uppercase; color: var(--ink-soft); }
.more ul { margin: 0; padding: 0; list-style: none; }
.more li { padding-bottom: var(--md); margin-bottom: var(--md); border-bottom: 1px solid var(--crease); }
.more li:last-child { border-bottom: 0; margin-bottom: 0; padding-bottom: 0; }
.more a { font-weight: 700; letter-spacing: -0.01em; }
.more a:hover { color: var(--airmail-blue); }

@media (min-width: 900px) {
  .pulse-row { grid-template-columns: repeat(4, 1fr); }
  .pulse { grid-template-areas: "dot name" "value value" "count count"; }
}

@media (min-width: 1024px) {
  /* The lead story and home, side by side above the fold — the reader sees what
     the world is leading with and what is happening at home in one glance. */
  .front { grid-template-columns: 1.6fr 1fr; align-items: start; }
  .region { grid-template-columns: 110px 1fr auto; }
}

@media (min-width: 768px) {
  .reading-wrap { padding: var(--xl) var(--xl) var(--x4); }
}

/* --- Responsive --------------------------------------------------------- */

@media (max-width: 639px) {
  .masthead-sub { display: none; }
  .masthead { padding-bottom: var(--sm); }
  .masthead-aside { gap: var(--sm); }
  .postmark-strip { margin-top: var(--sm); padding-bottom: var(--sm); }
  .front { margin-top: var(--lg); }
  .band { margin-bottom: var(--md); }
}

@media (min-width: 640px) {
  .declaration-row { grid-template-columns: 150px 1fr; gap: var(--lg); align-items: baseline; }
}

@media (min-width: 768px) {
  .sheet-wrap { padding: var(--xl) var(--xl) var(--x4); }
  .masthead-logo { width: 42px; height: 42px; }
  .clocks { flex-direction: row; gap: var(--lg); }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}
