/* ---------------------------------------------------------------------------
   .article--compact: variant used inside the globe panel.
   ---------------------------------------------------------------------------
   Single-column layout, smaller logo, title shrunk to text-sm. Keeps the
   ¶ Summarise button so the panel stays a usable reading surface, but
   drops the body excerpt — at 33% viewport width the headline list
   matters more than the lede.
   --------------------------------------------------------------------------- */
.article--compact {
  grid-template-columns: 1fr;
  gap: var(--sp-2);
  padding: var(--sp-3) var(--sp-2);
  cursor: pointer;                 /* card click opens the Inspector */
  border-radius: var(--r-md);
  transition: background-color var(--dur-fast) var(--ease);
}
.article--compact:hover { background: color-mix(in srgb, var(--accent) 8%, transparent); }
.article--compact .article__meta-col {
  flex-direction: row;
  align-items: center;
  gap: var(--sp-2);
  font-size: var(--text-xs);
  /* Allow the meta row to shrink below its intrinsic min-content width
   * — without min-width:0 a long outlet name would push the row past
   * its container and clip the trailing time/section labels. */
  min-width: 0;
}
.article--compact .article__title {
  font-size: var(--text-base);
  line-height: 1.3;
}
.article--compact .outlet-logo-tile {
  flex-direction: row;
  align-items: center;
  gap: 6px;
  /* The tile itself becomes a flex item inside .article__meta-col; let
   * it grow so its caption has room while the logo box stays fixed
   * at 28px on the left. */
  flex: 1 1 auto;
  min-width: 0;
}
.article--compact .outlet-logo-tile__logo {
  width: 28px; height: 28px;
  min-height: 28px; max-height: 28px;
  padding: 2px;
  flex: 0 0 auto;
}
.article--compact .outlet-logo-tile__logo img {
  max-height: 22px;
}
.article--compact .outlet-logo-tile__name {
  font-size: var(--text-2xs);
  /* Allow up to two lines so long outlet names like "The Wall Street
   * Journal" or "International Herald Tribune" don't get cut. The
   * panel column only fits ~2 lines in the meta row visually anyway. */
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  display: -webkit-box;
  text-align: left;
  /* min-width:0 lets the flex parent shrink the caption when the
   * containing panel narrows; without this Chrome leaves the caption
   * at its longest-word intrinsic width and overflows the card. */
  min-width: 0;
  flex: 1 1 auto;
  overflow: hidden;
  word-break: break-word;
}
/* Push the time stamp to the far right of the meta row so the layout
 * reads (logo + name) ··· (time). */
.article--compact .article__meta-col .article__time {
  margin-left: auto;
  flex-shrink: 0;
  white-space: nowrap;
}
/* Hide summary / excerpt + AI summary block in the panel — too dense
 * for the narrow width. Users can click through to the article. */
.article--compact .article__summary,
.article--compact .article__ai-summary {
  display: none;
}

/* C Inspector (headline detail mode) inside the globe panel — list ↔ detail. */
.globe-panel__detail { display: block; padding: var(--sp-2) 0; }
.globe-panel__back {
  display: inline-flex; align-items: center; font: inherit; font-size: 13px;
  padding: 4px 10px; margin-bottom: 12px; cursor: pointer;
  border: 1px solid var(--glass-hairline); border-radius: var(--r-md);
  background: transparent; color: var(--ink-muted);
}
.globe-panel__back:hover { border-color: var(--accent); color: var(--ink); }
.globe-panel__detail-meta { font-size: var(--text-xs); color: var(--ink-muted); margin-bottom: 8px; }
.globe-panel__detail-title { font-size: var(--text-base); line-height: 1.35; margin: 0 0 10px; }
/* (Glass Terminal §1.3) 14px sits between --text-sm (13px) and --text-base (15px)
   with no exact token; kept literal to avoid a ±1px shift on the inspector body. */
.globe-panel__detail-summary { font-size: 14px; line-height: 1.6; color: var(--ink-muted); margin: 0 0 12px; }
.globe-panel__detail-label {
  font-size: var(--text-2xs); letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-dim); margin-bottom: 6px;
}
.globe-panel__actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin-top: 14px; }
.globe-panel__action {
  display: inline-flex; align-items: center; gap: 6px; font: inherit; font-size: 13px;
  padding: 6px 12px; border-radius: var(--r-md); cursor: pointer; text-decoration: none;
  border: 1px solid var(--glass-hairline); background: transparent; color: var(--ink);
}
.globe-panel__action:hover { border-color: var(--accent); }
.globe-panel__action--primary {
  background: var(--accent-soft); color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
}
.globe-panel__action.is-active { color: var(--accent); border-color: var(--accent); }
/* J — follow-up status pills on a flagged headline (inspector). */
.globe-panel__flagstatus { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
.globe-panel__flagstatus[hidden] { display: none; }
.globe-panel__statuspill {
  font: inherit; font-size: var(--text-xs); cursor: pointer;
  padding: 3px 10px; border-radius: 999px;
  border: 1px solid var(--glass-hairline); background: transparent; color: var(--ink-muted);
  transition: color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.globe-panel__statuspill:hover { color: var(--ink); }
.globe-panel__statuspill.is-active {
  color: var(--ink); border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 12%, transparent);
}
.globe-panel__memo { margin-top: 12px; }
.globe-panel__memo-input {
  width: 100%; box-sizing: border-box; resize: vertical; font: inherit; font-size: 13px;
  padding: 8px 10px; border: 1px solid var(--glass-hairline); border-radius: var(--r-md);
  background: var(--bg-elev-1); color: var(--ink);
}
.globe-panel__memo-status { font-size: var(--text-2xs); color: var(--ink-dim); margin-top: 4px; min-height: 14px; }
.globe-panel__login-hint { font-size: 12px; color: var(--ink-dim); margin-top: 12px; }
/* Feature #45: "Filtered by your current view" note atop the country panel list,
   with an inline × to disable main-filter carryover for this panel session. */
.globe-panel__filternote {
  display: flex; align-items: center; gap: 6px;
  list-style: none; font-size: var(--text-2xs); color: var(--ink-dim);
  padding: 4px 6px; margin-bottom: 6px;
  border: 1px solid var(--glass-hairline); border-radius: var(--r-md);
  background: color-mix(in srgb, var(--accent) 6%, transparent);
}
.globe-panel__filternote-x {
  margin-left: auto; border: 0; background: transparent; cursor: pointer;
  color: var(--ink-dim); font-size: 14px; line-height: 1; padding: 0 2px;
  display: inline-flex; align-items: center;   /* C2 — centre the SVG × */
}
.globe-panel__filternote-x:hover { color: var(--ink); }
.globe-panel__entity-memo { margin-top: var(--sp-4); }   /* C4 — entity-mode notes in the left profile */

.page { max-width: 1480px; margin: 0 auto; padding: var(--sp-5) var(--sp-page-x) var(--sp-8); }

.geo-panel {
  /* B4 (2026-07-07): padding 24→16, shadow dropped. The geo-panel and
     filter-rail sit stacked in the same left column; dropping BOTH shadows
     (see .filter-rail) is the lighter-touch merge — they now read as one flat
     filter stack (border-only) instead of two floating cards. */
  background: var(--bg-elev-1); border: 1px solid var(--border);
  border-radius: var(--r-lg); padding: var(--sp-4);
  margin-bottom: var(--sp-5);
}
.geo-panel__header {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: var(--sp-4); margin-bottom: var(--sp-4);
}
.geo-panel__heading { display: flex; align-items: baseline; gap: var(--sp-3); }
.geo-panel__title {
  font-family: var(--font-serif); font-size: var(--text-md); font-weight: 600;
  margin: 0; letter-spacing: -0.01em;
}
.geo-panel__subtitle { color: var(--ink-muted); font-size: var(--text-sm); }
.geo-panel__toggle {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 4px 10px; background: transparent;
  border: 1px solid var(--border); border-radius: 999px;
  color: var(--ink-muted); font-size: var(--text-xs);
  /* perf (audit #28): was transition:all — only colour + border change on hover. */
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.geo-panel__toggle:hover { color: var(--ink); border-color: var(--border-strong); }
.geo-panel__toggle .caret {
  display: inline-block; width: 10px; height: 10px;
  border-right: 1.6px solid currentColor; border-bottom: 1.6px solid currentColor;
  /* Expanded: caret points UP (click to collapse / hide). */
  transform: rotate(-135deg) translate(-2px, -2px);
  transition: transform var(--dur-base) var(--ease);
}
.geo-panel.is-collapsed .geo-panel__toggle .caret {
  /* Collapsed: caret points DOWN (click to expand / reveal). */
  transform: rotate(45deg) translate(-2px, -2px);
}

.chip-row { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-3); align-items: center; }
/* Stage-3 polish: give the two Event-view chip rows a little vertical rhythm
   and keep the Events header compact — the topic row can run to 31 chips, so
   cap it at ~2-3 rows and let it scroll rather than pushing the list down.
   B5: a static bottom fade-mask hints there's more below the fold when the row
   is scrolled/overflowing. It's a fixed 12px fade (not scroll-position aware),
   which is acceptable per the audit and needs no reduced-motion handling (the
   mask is static, not animated). */
#event-topic-chips {
  margin-top: var(--sp-2); max-height: 5.75rem; overflow-y: auto;
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 12px), transparent);
  mask-image: linear-gradient(to bottom, #000 calc(100% - 12px), transparent);
}
#event-region-chips { margin-bottom: 0; }

/* B5 (audit U2#5): Events filter disclosure. Reuses .filter-rail__advanced for
   the summary caret + badge recipe, but it lives free-standing in the events
   header (not inside the rail card), so reset the rail's top border / padding. */
.event-filters.filter-rail__advanced {
  border-top: 0; padding-top: 0;
  margin-top: var(--sp-2); margin-bottom: var(--sp-3);
}
.event-filters[open] { padding-bottom: 0; }
/* First chip row inside the open disclosure gets a little breathing room below
   the summary. */
.event-filters[open] #event-topic-chips { margin-top: var(--sp-3); }
/* Result-count feedback next to the Event Feed title. */
.event-view__count { display: block; font-size: var(--text-xs); color: var(--ink-muted); margin-top: 2px; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.event-view__count:empty { display: none; }
.chip-row__label {
  display: inline-flex; align-items: center;
  font-size: var(--text-xs); color: var(--ink-dim);
  letter-spacing: 0.08em; text-transform: uppercase;
  margin-right: var(--sp-2); padding: 4px 0;
}
/* (Glass Terminal §1.4) chips normalised off the 999px pill to the --r-sm
   control radius, with a tier-2 glass tint fill + hairline border. Pills are
   reserved for dots/avatars/FAB; filter chips read as terminal tag-cells. */
.chip {
  padding: 5px 12px; background: var(--glass-2-bg);
  color: var(--ink-muted); border: 1px solid var(--glass-hairline);
  border-radius: var(--r-sm); font-size: var(--text-sm);
  /* perf (audit #28): was transition:all — hover/active change colour,
     border-color and background only. */
  transition: color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease);
}
/* C5: three chip hover tiers are INTENTIONALLY different — do not unify.
 *   .chip:hover            — full recipe: color + border-color + background
 *                            (standalone filter chips, most prominent).
 *   .globe-layer-chip:hover — color-only (~973): chips overlay the globe, a
 *                            bg/border shift would fight the canvas.
 *   .globe-profile__chip:hover — border-only (~1871): quiet inline tags in
 *                            the profile panel, subtler than filter chips.
 * Future agents: leave these three distinct. */
.chip:hover { color: var(--ink); border-color: var(--border-strong); background: var(--bg-elev-2); }
.chip--active { color: #fff; background: var(--accent); border-color: var(--accent); }
.chip--active:hover { background: var(--accent); color: #fff; }
.chip--group { font-weight: 600; letter-spacing: 0.03em; }

.geo-panel__map-wrap {
  overflow: hidden; max-height: 900px; opacity: 1;
  transition: max-height var(--dur-slow) var(--ease), opacity var(--dur-base) var(--ease), margin var(--dur-base) var(--ease);
}
.geo-panel.is-collapsed .geo-panel__map-wrap { max-height: 0; opacity: 0; margin-top: 0; }

.map-frame {
  position: relative; overflow: hidden;
  border-radius: var(--r-md);
  background: var(--map-sphere);
  border: 1px solid var(--border);
  /* Match the SVG viewBox aspect (1100 × 560 in map.js) so the frame never
   * leaves empty space at the bottom. Prior rule `min-height: 320px` was
   * width-agnostic and opened a ~1/3 gap whenever the column was narrower
   * than ~628px (320 ÷ 560 × 1100). aspect-ratio keeps the frame snug to
   * the actual map at every width. */
  aspect-ratio: 1100 / 560;
}
.map-frame svg { display: block; width: 100%; height: auto; }
.map-frame .country.continent-na { fill: var(--map-tint-na); }
.map-frame .country.continent-sa { fill: var(--map-tint-sa); }
.map-frame .country.continent-eu { fill: var(--map-tint-eu); }
.map-frame .country.continent-as { fill: var(--map-tint-as); }
.map-frame .country.continent-af { fill: var(--map-tint-af); }
.map-frame .country.continent-oc { fill: var(--map-tint-oc); }
.map-frame .country.continent-an { fill: var(--map-tint-an); }

.map-frame .country {
  fill: var(--map-land); stroke: var(--map-land-stroke); stroke-width: 0.5;
  /* Matched to body's var(--dur-base) so light/dark theme toggle feels like one
   * coordinated shift rather than two staggered transitions. */
  transition: fill var(--dur-base) var(--ease); cursor: pointer;
}
.map-frame .country:hover { fill: var(--map-hover); }
.map-frame .country.highlighted {
  fill: var(--map-selected); opacity: 0.62;
}
.map-frame .country.selected { fill: var(--map-selected); opacity: 1; }
.map-frame .country.in-group { fill: var(--map-selected); opacity: 0.78; }
.map-frame .country:focus-visible { outline: 2px solid var(--accent); }
/* The svg itself is the single tabstop — give it a clear focus ring so
 * keyboard users can see they've landed on the map. */
.map-frame svg:focus { outline: none; }
.map-frame svg:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px; border-radius: var(--r-md);
}
.map-frame .graticule { fill: none; stroke: var(--border); stroke-width: 0.3; stroke-opacity: 0.55; }
.map-frame .sphere { fill: none; stroke: var(--border-strong); stroke-width: 0.8; }

/* (Glass Terminal) tier-3 glass tooltip: opaque fallback first, @supports adds
   translucency + blur. Trio = inset top highlight + hairline border + shadow-3. */
.map-tooltip {
  position: absolute; pointer-events: none;
  padding: 6px 10px; background: var(--glass-3-fallback);
  border: 1px solid var(--glass-hairline); border-radius: var(--r-sm);
  font-size: var(--text-xs); color: var(--ink); white-space: nowrap;
  opacity: 0; transform: translate(-50%, -120%);
  transition: opacity var(--dur-fast) var(--ease); z-index: var(--z-globe-1);
  box-shadow: var(--shadow-3), inset 0 1px 0 var(--glass-highlight);
}
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .map-tooltip {
    background: var(--glass-3-bg);
    -webkit-backdrop-filter: blur(var(--glass-3-blur)) saturate(var(--glass-sat));
    backdrop-filter: blur(var(--glass-3-blur)) saturate(var(--glass-sat));
  }
}
.map-tooltip.visible { opacity: 1; }

/* --- Map zoom/pan toolbar & hint ---------------------------------- */

.map-toolbar {
  display: flex; justify-content: flex-end;
  margin-bottom: var(--sp-2);
}
/* (Glass Terminal) tier-1 glass mini-surface: glass tint + hairline + inset top
   highlight + soft lift. @supports below progressively adds blur. */
.map-toolbar__reset {
  font: inherit; font-size: var(--text-xs); font-weight: 500;
  padding: 6px 12px; border-radius: 999px;
  color: var(--ink-muted); background: var(--glass-1-fallback);
  border: 1px solid var(--glass-hairline); cursor: pointer;
  box-shadow: inset 0 1px 0 var(--glass-highlight), 0 1px 2px var(--scrim-08);
  transition: color var(--dur-fast) var(--ease),
              background-color var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease),
              opacity var(--dur-fast) var(--ease);
}
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .map-toolbar__reset {
    background: var(--glass-1-bg);
    -webkit-backdrop-filter: blur(var(--glass-1-blur)) saturate(var(--glass-sat));
    backdrop-filter: blur(var(--glass-1-blur)) saturate(var(--glass-sat));
  }
}
.map-toolbar__reset:hover:not(:disabled) {
  color: var(--ink); background: var(--bg-elev-2);
  border-color: var(--border-strong);
}
.map-toolbar__reset:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.map-toolbar__reset:disabled { opacity: 0.45; cursor: default; }

/* Grab cursor so users discover drag-to-pan. d3-zoom applies its own
 * inline cursor during an active drag, but this gives the correct idle
 * state. */
.map-frame svg { cursor: grab; }
.map-frame svg:active { cursor: grabbing; }

/* Centred pill shown briefly when the user wheels over the map without
 * holding Ctrl. Pointer-events:none so it never eats map interactions. */
.map-zoom-hint {
  position: absolute; left: 50%; top: 50%;
  transform: translate(-50%, -50%);
  padding: 10px 16px; border-radius: 999px;
  background: rgba(15, 18, 24, 0.82); color: #fff;
  font-size: var(--text-sm); font-weight: 500;
  pointer-events: none; z-index: var(--z-globe-2);
  opacity: 0; transition: opacity var(--dur-base) var(--ease);
  box-shadow: var(--shadow-2);
  white-space: nowrap;
}
.map-zoom-hint.is-visible { opacity: 1; }
.map-zoom-hint.is-hidden { display: none; }

/* (Glass Terminal) tier-1 glass mini-surface — a self-contained legend strip
   (inline-flex hugs its content) with the glass trio. @supports adds blur. */
.map-legend {
  display: inline-flex; align-items: center; gap: var(--sp-4);
  margin-top: var(--sp-2); padding: var(--sp-1) var(--sp-3);
  font-size: var(--text-xs); color: var(--ink-muted);
  background: var(--glass-1-fallback);
  border: 1px solid var(--glass-hairline); border-radius: var(--r-sm);
  box-shadow: inset 0 1px 0 var(--glass-highlight), 0 1px 2px var(--scrim-08);
}
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .map-legend {
    background: var(--glass-1-bg);
    -webkit-backdrop-filter: blur(var(--glass-1-blur)) saturate(var(--glass-sat));
    backdrop-filter: blur(var(--glass-1-blur)) saturate(var(--glass-sat));
  }
}
.map-legend__dot {
  display: inline-block; width: 10px; height: 10px; border-radius: 3px;
  margin-right: 6px; vertical-align: -1px;
}

/* Task #22 (P1 IA): Active-filters breadcrumb moved into the
 * results-header. Replaces the old `.active-filters` + `.pill` pair. The
 * container is `display: contents` so the segments participate in the
 * results-header's own flex flow — "Headlines · 240 · Asia × · Economy ×"
 * reads as one continuous baseline-aligned line that wraps naturally on
 * narrow viewports. Each segment shows a middle-dot separator via a
 * `::before` pseudo-element (hidden on the first child) so the DOM stays
 * screen-reader-clean.
 */
.results-breadcrumb {
  display: contents;
}
.results-breadcrumb[hidden] { display: none; }

.results-breadcrumb__seg {
  display: inline-flex;
  align-items: baseline;
  gap: 2px;
  color: var(--ink-muted);
  font-size: var(--text-sm);
}
.results-breadcrumb__seg::before {
  /* Middle-dot separator before every segment (including the first, which
   * gets the separator between the count and the first filter). CSS-only
   * so the DOM has no presentational filler nodes for assistive tech. */
  content: "\00B7";
  margin: 0 8px;
  color: var(--ink-dim);
  font-weight: 600;
}
.results-breadcrumb__seg-text {
  /* The text content itself. Kept as a span (rather than letting the
   * button carry it) so the × click target is tightly scoped and doesn't
   * activate when the user clicks on the label to copy it. */
  line-height: 1.35;
  /* W#14 hardening: a long unbroken filter value wraps instead of pushing the
   * breadcrumb past the header edge on narrow viewports. */
  min-width: 0; overflow-wrap: anywhere;
}
.results-breadcrumb__seg-x {
  /* 24×24 touch target keeps us comfortably above WCAG 2.5.8 (minimum
   * target) on mobile. Visual weight stays low — just a glyph — so the
   * breadcrumb still reads as editorial text rather than a chip strip. */
  appearance: none;
  width: 24px; height: 24px;
  margin-left: 2px;
  padding: 0;
  border: 0;
  background: transparent;
  color: var(--ink-dim);
  display: inline-grid;
  place-items: center;
  font-size: 16px;
  line-height: 1;
  cursor: pointer;
  border-radius: 50%;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.results-breadcrumb__seg-x:hover,
.results-breadcrumb__seg-x:focus-visible {
  background: var(--accent-soft);
  color: var(--accent);
  outline: none;
}
.results-breadcrumb__seg-x:focus-visible {
  box-shadow: var(--focus-ring);
}

.filter-rail {
  display: flex; flex-direction: column; gap: var(--sp-3);
  padding: var(--sp-4);
  background: var(--bg-elev-1); border: 1px solid var(--border);
  border-radius: var(--r-lg); margin-bottom: var(--sp-5);
  /* B4: shadow dropped (border kept) so the geo-panel + filter-rail read as
     one flat filter stack rather than two floating cards. */
}
.filter-rail__primary,
.filter-rail__advanced-grid {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--sp-3);
}
/* B6 (audit U2#6/#10): sort + match live in a thin 2-col meta sub-row below the
 * content group. Narrower minmax than the content grid so the two controls sit
 * side-by-side on all but the tightest columns, reading as a paired preference
 * strip rather than two more filters. */
.filter-rail__meta {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: var(--sp-3);
}
.filter-rail__advanced {
  border-top: 1px solid var(--border);
  padding-top: var(--sp-3);
}
.filter-rail__advanced[open] { padding-bottom: 0; }
.filter-rail__advanced-summary {
  list-style: none; cursor: pointer;
  display: inline-flex; align-items: center; gap: 8px;
  font-size: var(--text-xs); color: var(--ink-dim);
  letter-spacing: 0.08em; text-transform: uppercase;
  padding: 4px 6px; border-radius: var(--r-sm);
  user-select: none;
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.filter-rail__advanced-summary::-webkit-details-marker { display: none; }
.filter-rail__advanced-summary::marker { content: ""; }
.filter-rail__advanced-summary::before {
  content: ""; display: inline-block;
  width: 6px; height: 6px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(-45deg) translate(1px, -1px);
  transition: transform var(--dur-base) var(--ease);
}
.filter-rail__advanced[open] .filter-rail__advanced-summary::before {
  transform: rotate(45deg) translate(-1px, -1px);
}
.filter-rail__advanced-summary:hover { color: var(--ink); }
.filter-rail__advanced-summary:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.filter-rail__advanced-badge {
  display: inline-grid; place-items: center;
  min-width: 18px; height: 18px; padding: 0 6px;
  background: var(--accent); color: #fff;
  border-radius: 999px;
  font-size: var(--text-2xs); font-weight: 600; letter-spacing: 0;
  text-transform: none;
}
.filter-rail__advanced-badge.is-hidden { display: none; }
/* Wave-1 handoff (a): the Saved-tab count badge (#saved-count-badge) reuses this
   pill class inside a view tab. Give it left separation from the tab label here
   instead of an inline style in app.js updateSavedBadge(). */
.view-tabs__tab .filter-rail__advanced-badge { margin-left: 6px; }
/* Wave-1 handoff (b): on the active Saved tab the tab background is --accent and
   the badge base is also --accent bg → the badge disappears. Invert it on the
   active tab so it stays legible (white pill, accent text). */
.view-tabs__tab.is-active .filter-rail__advanced-badge {
  background: #fff; color: var(--accent);
}
.filter-rail__advanced-grid { margin-top: var(--sp-3); }
.filter-rail__group { display: flex; flex-direction: column; gap: 4px; position: relative; }
.filter-rail__label {
  font-size: var(--text-xs); color: var(--ink-dim);
  letter-spacing: 0.08em; text-transform: uppercase;
}
.filter-rail select {
  appearance: none; -webkit-appearance: none; width: 100%;
  padding: 8px 32px 8px 12px;
  /* (Glass Terminal) recessed input well — the --field-well surface + a soft
     inset top shadow reads as a sunken control, consistent with the topbar
     search (all recessed wells share --field-well). */
  background: var(--field-well); color: var(--ink);
  border: 1px solid var(--border); border-radius: var(--r-md);
  box-shadow: inset 0 1px 2px var(--scrim-08);
  font-size: var(--text-sm);
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%237a8694' stroke-width='1.5'><path d='M3 4.5l3 3 3-3'/></svg>");
  background-repeat: no-repeat; background-position: right 10px center;
  background-size: 12px 12px; cursor: pointer;
  transition: border-color var(--dur-fast) var(--ease);
}
.filter-rail select:hover { border-color: var(--border-strong); }
.filter-rail select:focus { border-color: var(--link); } /* A1: keyboard ring from :focus-visible baseline (~226) */

/* ---- Multi-select checkbox dropdown --------------------------------------
   A minimal custom combobox: `msel__summary` looks like a `<select>`, and
   when `.is-open` is added to `.msel`, a panel with a scrollable checkbox
   list floats underneath.                                                  */
.msel { position: relative; }
.msel__summary {
  appearance: none; width: 100%;
  padding: 8px 32px 8px 12px; text-align: left;
  /* (Glass Terminal) recessed input well (--field-well) — matches .filter-rail select. */
  background: var(--field-well); color: var(--ink);
  border: 1px solid var(--border); border-radius: var(--r-md);
  box-shadow: inset 0 1px 2px var(--scrim-08);
  font-size: var(--text-sm); cursor: pointer;
  display: inline-flex; align-items: center; gap: 6px;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%237a8694' stroke-width='1.5'><path d='M3 4.5l3 3 3-3'/></svg>");
  background-repeat: no-repeat; background-position: right 10px center;
  background-size: 12px 12px;
  transition: border-color var(--dur-fast) var(--ease);
}
.msel__summary:hover { border-color: var(--border-strong); }
/* A1: was :focus-visible{outline:none} which suppressed the keyboard ring.
 * Split: plain :focus keeps border feedback (mouse), :focus-visible defers
 * to the baseline ring (~226) by not overriding outline. */
.msel__summary:focus { border-color: var(--link); }
.msel.is-open > .msel__summary { border-color: var(--link); }
.msel__summary-text {
  flex: 1 1 auto; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.msel__summary-text--placeholder { color: var(--ink-dim); }
.msel__badge {
  flex: 0 0 auto;
  display: inline-grid; place-items: center; min-width: 20px; height: 18px;
  padding: 0 6px; border-radius: 999px;
  background: var(--accent); color: #fff;
  font-size: var(--text-3xs); font-weight: 600; letter-spacing: 0.02em;
}
/* (Glass Terminal) tier-3 glass dropdown: opaque fallback + @supports blur.
   Trio = inset top highlight + hairline border + shadow-3. `display` stays in
   this base rule + the .is-open toggle below (class-driven, never inline). */
.msel__panel {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: var(--z-dropdown);
  background: var(--glass-3-fallback); border: 1px solid var(--glass-hairline);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-3), inset 0 1px 0 var(--glass-highlight);
  display: none; flex-direction: column; max-height: 340px;
  padding: var(--sp-2);
}
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .msel__panel {
    background: var(--glass-3-bg);
    -webkit-backdrop-filter: blur(var(--glass-3-blur)) saturate(var(--glass-sat));
    backdrop-filter: blur(var(--glass-3-blur)) saturate(var(--glass-sat));
  }
}
.msel.is-open > .msel__panel { display: flex; }
.msel__search {
  flex: 0 0 auto; width: 100%; margin-bottom: var(--sp-2);
  padding: 6px 10px; font-size: var(--text-sm);
  /* Recessed well (--field-well); a child of the glass panel, so tint-only (no nested blur). */
  background: var(--field-well); color: var(--ink);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  box-shadow: inset 0 1px 2px var(--scrim-08);
}
.msel__search::placeholder { color: var(--ink-dim); }
.msel__search:focus { border-color: var(--link); } /* A1: keyboard ring from :focus-visible baseline (~226) */
.msel__actions {
  display: flex; gap: 6px; margin-bottom: var(--sp-2);
  justify-content: space-between; align-items: center;
}
.msel__action {
  padding: 2px 8px; font-size: var(--text-xs); color: var(--link);
  border-radius: var(--r-sm);
}
.msel__action:hover { background: var(--bg-elev-2); }
.msel__action:disabled { opacity: 0.4; cursor: not-allowed; }
.msel__list {
  flex: 1 1 auto; overflow-y: auto;
  display: flex; flex-direction: column; gap: 1px;
  min-height: 0;
}
.msel__option {
  display: flex; align-items: center; gap: var(--sp-2);
  padding: 6px 8px; border-radius: var(--r-sm);
  color: var(--ink); font-size: var(--text-sm);
  cursor: pointer; user-select: none;
}
/* Hover = accent tint (transparent-based so it composits over the glass panel,
   not an opaque fill). Checked row = a stronger accent tint + full --ink text;
   the tick itself is the native accent-color box below. */
.msel__option:hover { background: color-mix(in srgb, var(--accent) 8%, transparent); }
.msel__option:has(input:checked) {
  background: color-mix(in srgb, var(--accent) 12%, transparent);
  color: var(--ink);
}
.msel__option input[type="checkbox"] {
  width: 14px; height: 14px; flex: 0 0 auto; accent-color: var(--accent);
}
.msel__option-label {
  flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.msel__empty { padding: var(--sp-3); text-align: center; color: var(--ink-dim); font-size: var(--text-xs); }

/* Task #22 (P1 IA): results-header now hosts the breadcrumb as inline
 * siblings. flex-wrap:wrap lets the line break naturally on narrow
 * viewports. column-gap stays 0 because each non-title child carries its
 * own leading `·` separator via ::before — keeping inter-item spacing
 * driven by the separator glyph rather than a gap fallback means the
 * wrapped rows don't pick up phantom indentation on the left. */
.results-header {
  display: flex; flex-wrap: wrap; align-items: baseline;
  row-gap: 4px; column-gap: 0;
  margin-bottom: var(--sp-4);
}
.results-header__title {
  font-family: var(--font-serif); font-size: var(--text-lg);
  font-weight: 600; letter-spacing: -0.01em; margin: 0;
}
.results-header__count {
  color: var(--ink-muted); font-size: var(--text-sm);
  /* Result tally in mono + tabular so the count reads as terminal data. */
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
}
.results-header__count:empty { display: none; }
.results-header__count::before {
  /* Middle-dot between "Headlines" and "240" — matches the breadcrumb
   * separator so the whole row reads as one continuous sentence. */
  content: "\00B7";
  margin: 0 8px;
  color: var(--ink-dim);
  font-weight: 600;
}

.headline-list { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: 1fr; gap: 0; }
.article {
  display: grid; grid-template-columns: 116px 1fr;
  gap: var(--sp-4); padding: 14px var(--sp-3);
  /* W#1 scan structure: a bright terminal hairline divides every row (theme-
     aware — dark line on light, light line on dark) instead of the muddy warm
     --border, so the list reads as separated rows, not a text wall. */
  border-top: 1px solid var(--glass-hairline);
  cursor: pointer;   /* C3 — card click opens the inline Inspector */
  border-radius: var(--r-md);
  /* box-shadow carries the hover accent scan-edge (inset left bar); animate it
     with the tint. Both are compositor/paint-only — no layout reflow. */
  transition: background-color var(--dur-fast) var(--ease-out),
              box-shadow var(--dur-fast) var(--ease-out);
}
.article:last-child { border-bottom: 1px solid var(--glass-hairline); }
/* W#1 hover: tier-2 glass tint + a 2px accent scan-edge on the hovered row
   only. The accent bar reads as the scan "cursor"; the title already sits at
   full --ink (W#5) so the row lifts without an over-loud headline hue-shift. */
.article:hover {
  background: color-mix(in srgb, var(--accent) 6%, var(--glass-2-bg));
  box-shadow: inset 2px 0 0 0 var(--accent);
}
/* Selected (inspector open): persistent accent scan-edge + a hair stronger
   tint so the open row stays anchored while its strip is read. */
.article.is-inspecting {
  background: color-mix(in srgb, var(--accent) 9%, var(--glass-2-bg));
  box-shadow: inset 2px 0 0 0 var(--accent);
}
/* G — keyboard-nav focus ring (j/k); Enter activates the focused card. */
.article.is-kbd-focus { outline: 2px solid var(--accent); outline-offset: -2px; }
.article--compact.is-kbd-focus { outline-offset: -1px; }
/* W#14 overflow fix — the col-2 grid item (.article__body, set in app.js) kept
   the default min-width:auto, so a long unbroken headline/URL forced the 1fr
   track wider than its container (audit: 648px edge in a 588px viewport).
   min-width:0 lets the track shrink; .article__title wraps via overflow-wrap. */
.article__body { min-width: 0; }
/* B3 (2026-07-07) — TRUE compact: single-line terminal rows (~34px each),
   20+ per screen. The class lives on #headlines.headline-list, toggled by
   app.js applyDensity(); compact is now the DEFAULT for new users.
   Flex row (not grid) so the three inline atoms place precisely:
     [outlet-name prefix]  [title, ellipsis, grows]  [time, right].
   The inspector, when open, wraps to its own full-width line (flex-basis:100%). */
.headline-list.is-compact .article {
  display: flex; align-items: baseline; flex-wrap: wrap;
  gap: 12px; padding: 7px 12px;
}
/* Meta column collapses to an inline outlet-name prefix (12px). The logo
   image, section, and header-time are hidden; only the name text remains. */
.headline-list.is-compact .article__meta-col {
  flex: 0 1 auto; flex-direction: row; align-items: baseline; gap: 0;
  min-width: 0; max-width: 180px;
}
.headline-list.is-compact .article__outlet {
  flex-wrap: nowrap; min-width: 0; overflow: hidden;
}
.headline-list.is-compact .outlet-logo-tile { min-width: 0; overflow: hidden; }
.headline-list.is-compact .outlet-logo-tile__logo,
.headline-list.is-compact .article__outlet .article__time,
.headline-list.is-compact .article__section {
  display: none;
}
.headline-list.is-compact .outlet-logo-tile__name {
  font-size: var(--text-xs); font-weight: 600;
  -webkit-line-clamp: 1; line-height: 1.3;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  display: block; min-width: 0;
}
/* Body grows and clips; title is the single visible line. */
.headline-list.is-compact .article__body {
  flex: 1 1 0; min-width: 0;
}
.headline-list.is-compact .article__title {
  /* (Glass Terminal §1.3) 14px has no exact --text-* token (--text-sm 13 /
     --text-base 15); left literal — density sacred, no ±1px shift on the dense
     compact rows. */
  font-size: 14px; font-weight: 500; line-height: 1.3;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* Time: last flex atom, pushed right, tabular so it never jitters. */
.headline-list.is-compact .article__body-time {
  flex: 0 0 auto; margin-left: auto;
  font-size: var(--text-2xs); color: var(--ink-dim);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums; white-space: nowrap;
  letter-spacing: -0.01em;
}
/* Inspector, when open, breaks to its own full-width line below the row. */
.headline-list.is-compact .article__inspector { flex: 1 1 100%; }
/* Collapse everything non-essential in compact rows. */
.headline-list.is-compact .article__summary,
.headline-list.is-compact .article__mentions,
.headline-list.is-compact .article__tags,
.headline-list.is-compact .article__ai-row,
.headline-list.is-compact .article__ai-summary {
  display: none;
}
/* Standard mode: the trailing compact-time atom is hidden (the header-time
   in the meta stack is the visible one there). */
.headline-list:not(.is-compact) .article__body-time { display: none; }
/* G — density toggle pill, right-aligned in the results header (flex). */
.density-toggle {
  margin-left: auto; align-self: center;
  font: inherit; font-size: var(--text-sm); cursor: pointer;
  padding: 3px 11px; border-radius: 999px;
  border: 1px solid var(--border); background: transparent; color: var(--ink-muted);
  transition: color var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.density-toggle:hover { color: var(--ink); background: var(--bg-elev-1); }
/* Pressed (compact active): ink-inversion selected language (matches the globe
   chip selected convention) — the active density reads as a filled dark segment.
   aria-pressed is JS-owned — style only; transitions inherit the base rule. */
.density-toggle[aria-pressed="true"] {
  color: var(--bg); border-color: var(--ink);
  background: var(--ink);
}
.density-toggle:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
/* C3 — inline Inspector strip (spans both grid columns, below the card content). */
.article__inspector {
  grid-column: 1 / -1; margin-top: var(--sp-2);
  padding-top: var(--sp-3); border-top: 1px solid var(--glass-hairline);
}
.article__inspector-actions { display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 8px; }
/* B2: origin-country · region, relocated here from the card meta stack. */
.article__inspector-geo { font-size: var(--text-xs); color: var(--ink-muted); margin-bottom: 8px; }
/* B2 (2026-07-07): meta column collapsed from a 4-tier stack to 2 lines.
   Line 1 = (transparent logo + outlet name 13/600) + time 12px --ink-muted.
   Line 2 = section 11px. The origin-country · region line was dropped from
   the card (it survives in the inspector). */
.article__meta-col {
  display: flex; flex-direction: column; gap: var(--sp-1);
  /* W#5: metadata steps down to 11px / --ink-dim so the outlet name + serif
     title stay dominant. (Outlet name, section and time set their own size.) */
  font-size: var(--text-2xs); color: var(--ink-dim); min-width: 0;
}
/* Line 1: logo + outlet name inline, then the relative time. wrap so a long
   outlet name pushes the time to the next visual line rather than clipping. */
.article__outlet {
  display: flex; align-items: baseline; flex-wrap: wrap;
  column-gap: 6px; row-gap: 2px; min-width: 0;
}

/* ---- Outlet logo tile (B1: box killed) -----------------------------------
   Was a bordered/filled 44px square that shredded scan rhythm. Now a bare
   transparent favicon (max 20px) sitting inline with the outlet name; no
   background, border, or padding. The name is the primary signal.          */
.outlet-logo-tile {
  display: inline-flex; align-items: baseline; gap: 6px; min-width: 0;
}
.outlet-logo-tile__logo {
  display: inline-grid; place-items: center;
  width: auto; max-height: 20px;
  flex: 0 0 auto; overflow: hidden;
  align-self: center;
}
.outlet-logo-tile__logo img {
  max-width: 100%; max-height: 20px;
  width: auto; height: auto; display: block;
  object-fit: contain;
}
.outlet-logo-tile__logo .outlet-initials {
  font-family: var(--font-serif); font-size: var(--text-2xs); font-weight: 600;
  color: var(--ink-dim); letter-spacing: -0.02em; text-align: center; line-height: 1;
}
.outlet-logo-tile__name {
  font-family: var(--font-sans); font-size: var(--text-sm); font-weight: 600;
  color: var(--ink); text-align: left; line-height: 1.3;
  letter-spacing: 0.005em; min-width: 0;
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical;
  overflow: hidden;
}

.article__section {
  text-transform: uppercase; letter-spacing: 0.08em;   /* C7 — earbrow tracking unified */
  font-size: var(--text-2xs); color: var(--ink-dim);
}
/* W#5: timestamps in mono + tabular (terminal data), 11px --ink-dim. */
.article__time {
  color: var(--ink-dim); font-size: var(--text-2xs);
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  letter-spacing: -0.01em;
}
.article__title {
  /* B2: 17px→16px, lh 1.32→1.28 (serif stays). Denser, still editorial.
     W#5: the title holds the top of the hierarchy — serif, 16px, full --ink.
     (Glass Terminal §1.3) intended token step is --text-md, but that computes
     17px; the B2 density pass deliberately holds this at 16px — a +1px shift
     would loosen the dense list — so the literal is kept intentionally. */
  font-family: var(--font-serif); font-size: 16px;
  font-weight: 600; line-height: 1.28; letter-spacing: -0.01em;
  color: var(--ink); margin: 0;
  /* W#14: wrap long unbroken headlines/URLs rather than overflow the column. */
  overflow-wrap: anywhere;
}
.article__title a { color: inherit; text-decoration: none; }
.article__title a:hover {
  color: var(--warm-text);   /* C6 — headline hover text on light */
  /* Colour-only hover cues fail WCAG 1.4.1; underline makes the link
   * affordance robust against low-vision and B/W printouts. */
  text-decoration: underline;
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
  text-decoration-color: color-mix(in srgb, var(--warm) 60%, transparent);
}
.article__summary {
  /* B2: 13px→12.5px, colour --ink-muted→--ink-dim; keep the 2-line clamp. */
  margin-top: var(--sp-2); color: var(--ink-dim);
  font-size: 12.5px; line-height: 1.5;
  display: -webkit-box; -webkit-line-clamp: 2;
  -webkit-box-orient: vertical; overflow: hidden;
  /* W#14: a long unbroken URL in the lede wraps inside the clamp box. */
  overflow-wrap: anywhere;
}
/* B7: density-adjusted — tag row margin-top sp-3→sp-2 to match the tighter card. */
.article__tags { margin-top: var(--sp-2); display: flex; flex-wrap: wrap; gap: 6px; }
.tag {
  /* (Glass Terminal §1.4) tag chips off the 999px pill onto the --r-sm control
     radius — pills are reserved for dots/avatars/FAB. Semantic variant colours
     (topic/sector/religion) below keep their tinted borders. */
  font-size: var(--text-2xs); padding: 2px 8px;
  border: 1px solid var(--border); border-radius: var(--r-sm);
  color: var(--ink-muted); letter-spacing: 0.02em;
  background: var(--bg-elev-1);
}
.tag--topic    { color: var(--tag-topic);    border-color: color-mix(in srgb, var(--tag-topic) 32%, transparent); }
.tag--sector   { color: var(--tag-sector);   border-color: color-mix(in srgb, var(--tag-sector) 32%, transparent); }
.tag--religion { color: var(--tag-religion); border-color: color-mix(in srgb, var(--tag-religion) 32%, transparent); }
.tag--more {
  color: var(--ink-dim); cursor: default;
  font-variant-numeric: tabular-nums;
}

/* Mentioned-countries row — sits between the summary and the tag row.
 * Visually lighter than .tag to keep the primary story pulls dominant. */
.article__mentions {
  margin-top: var(--sp-2);
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 6px;
  font-size: var(--text-2xs);
}
.article__mentions-label {
  color: var(--ink-dim);
  letter-spacing: 0.08em; text-transform: uppercase;
  margin-right: 2px;
}
/* B5: mention chip converged to the .tag visual language — identical radius,
   padding, border-width, font-size, weight, letter-spacing and line-height.
   Only the colour/background stay semantic (accent-tinted vs neutral). */
.article__mention-chip {
  /* Kept converged with .tag — same --r-sm radius (design §1.4). */
  padding: 2px 8px; border-radius: var(--r-sm);
  background: var(--accent-soft); color: var(--ink);
  border: 1px solid color-mix(in srgb, var(--accent) 22%, transparent);
  font-size: var(--text-2xs); font-weight: 400; line-height: 1.4; letter-spacing: 0.02em;
}
.article__mention-more {
  color: var(--ink-dim);
  font-variant-numeric: tabular-nums;
}

/* Feature B — clickable entity chips (tags + mentions that toggle a filter).
   Button reset so they match the span chips; hover + filtered (is-active) state
   uses each chip's own colour (currentColor), accent for the mention chips. */
button.tag, button.article__mention-chip {
  font-family: inherit; font-size: var(--text-2xs); line-height: 1.4; margin: 0;
  -webkit-appearance: none; appearance: none; cursor: pointer;
}
.tag--entity { transition: background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease); }
.tag--entity:hover { border-color: currentColor; }
.tag.tag--entity.is-active {
  background: color-mix(in srgb, currentColor 15%, transparent);
  border-color: currentColor;
}
.article__mention-chip.tag--entity:hover { border-color: var(--accent); }
.article__mention-chip.tag--entity.is-active {
  background: color-mix(in srgb, var(--accent) 24%, transparent);
  border-color: var(--accent);
}
.tag--entity:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }

/* Watch ★ (feature D) — a toggle bound to the watchlist controller. Gold matches
   the globe ring. `--btn` is the prominent country toggle in the profile; `--chip`
   is the compact star beside a linked entity chip. */
.watch-star {
  font: inherit; cursor: pointer; background: transparent; border: 0;
  color: var(--ink-muted); line-height: 1; padding: 2px;
  /* C2 — icon + label lockup (SVG star replaced the ★/☆ glyph). */
  display: inline-flex; align-items: center; gap: 5px;
  transition: color var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease), background-color var(--dur-fast) var(--ease), border-color var(--dur-fast) var(--ease);
}
.watch-star .skt-icon { flex: 0 0 auto; }
.watch-star__label { line-height: 1; }
.watch-star:hover { color: var(--ink); }
/* C2 — empty star follows the button text colour (stroke=currentColor); the
   FILLED star sets fill=var(--warm) inline, so is-watching only needs to tint
   the label. C6 — label is TEXT, so --warm-text (AA on light); the star fill
   stays --warm (a fill, not text). */
.watch-star.is-watching { color: var(--warm-text); }
[data-theme="dark"] .watch-star.is-watching { color: var(--warm-text); }
.watch-star:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; border-radius: 4px; }
.watch-star--btn {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 12px; border-radius: 999px;
  border: 1px solid var(--border); font-size: var(--text-sm);
  color: var(--ink);
}
.watch-star--btn:hover { background: var(--bg-elev-1); color: var(--ink); }
.watch-star--btn.is-watching {
  border-color: var(--warm); color: var(--warm-text);   /* C6 — text darkened, border stays */
  background: color-mix(in srgb, var(--warm) 10%, transparent);
}
[data-theme="dark"] .watch-star--btn.is-watching {
  border-color: var(--warm); color: var(--warm-text);
  background: color-mix(in srgb, var(--warm) 12%, transparent);
}
.watch-star--chip { font-size: 13px; padding: 0 3px; }
.globe-profile__watchrow { margin-bottom: var(--sp-3, 12px); }
.entity-chip-group { display: inline-flex; align-items: center; gap: 1px; margin: 0 4px 4px 0; }
.entity-chip-group .tag--entity { margin: 0; }

/* F — command palette (Ctrl/⌘+K). Mirrors the .modal backdrop+blur pattern; sits
   above modals (z 1100). Country results fly the globe; commands reuse controls. */
.cmdk { position: fixed; inset: 0; z-index: var(--z-cmdk); display: grid; }
.cmdk[hidden] { display: none; }
.cmdk__backdrop {
  position: absolute; inset: 0; background: var(--scrim-42);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px);
}
.cmdk__panel {
  position: relative; z-index: var(--z-globe-base); justify-self: center;
  margin-top: 12vh; width: min(560px, 92vw); max-height: 70vh;
  display: flex; flex-direction: column;
  /* (Glass Terminal, 2026-07-11) tier-4 glass (command palette). Opaque
     fallback first; @supports below adds translucency + blur. */
  background: var(--glass-4-fallback); border: 1px solid var(--glass-hairline);
  border-radius: var(--r-lg, 14px);
  box-shadow: var(--shadow-4), inset 0 1px 0 var(--glass-highlight); overflow: hidden;
}
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .cmdk__panel {
    background: var(--glass-4-bg);
    -webkit-backdrop-filter: blur(var(--glass-4-blur)) saturate(var(--glass-sat));
    backdrop-filter: blur(var(--glass-4-blur)) saturate(var(--glass-sat));
  }
}
.cmdk__input {
  font: inherit; font-size: var(--text-lg); padding: 16px 18px; border: 0;
  border-bottom: 1px solid var(--glass-hairline); background: transparent; color: var(--ink); outline: none;
}
.cmdk__input::placeholder { color: var(--ink-muted); }
.cmdk__results { list-style: none; margin: 0; padding: 6px; overflow-y: auto; flex: 1 1 auto; }
.cmdk__item {
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
  padding: 9px 12px; border-radius: var(--r-md, 8px); cursor: pointer; color: var(--ink);
  transition: background-color var(--dur-fast) var(--ease);
}
/* Pointer hover = quiet tint (tint over the glass panel, no nested blur). */
.cmdk__item:hover { background: color-mix(in srgb, var(--accent) 8%, transparent); }
/* Active descendant (keyboard nav) = a 2px accent left bar + stronger tint,
   the professional command-palette selection idiom (Raycast-grade). */
.cmdk__item.is-sel {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  box-shadow: inset 2px 0 0 0 var(--accent);
}
.cmdk__item-label { font-size: var(--text-sm); }
.cmdk__item-hint {
  font-size: var(--text-xs); color: var(--ink-dim); flex: none;
  font-variant-numeric: tabular-nums;
}
.cmdk__empty { padding: 16px; text-align: center; color: var(--ink-muted); font-size: var(--text-sm); }
.cmdk__foot {
  display: flex; gap: 14px; padding: 8px 14px; border-top: 1px solid var(--glass-hairline);
  font-size: var(--text-xs); color: var(--ink-muted);
}
/* kbd chips in mono on a glass-hairline outline + tier-2 tint — a real key. */
.cmdk__foot kbd {
  font: inherit; font-family: var(--font-mono);
  font-size: var(--text-2xs); padding: 1px 5px; border: 1px solid var(--glass-hairline);
  border-radius: var(--r-sm); background: var(--glass-2-bg);
  color: var(--ink-dim); margin-right: 2px;
}
@media (prefers-reduced-motion: no-preference) {
  .cmdk:not([hidden]) .cmdk__panel { animation: cmdk-in 140ms var(--ease, ease) both; }
  @keyframes cmdk-in { from { opacity: 0; transform: translateY(-6px) scale(0.99); } to { opacity: 1; transform: none; } }
}

/* B2/B3 — Watchlist dock: top-right collapsible list of watched countries.
   Collapsed = just the toggle pill; expanded = the list drops below it. Shifts
   left of the right country panel when one is open. Built/driven by app.js. */
.watchlist-dock {
  position: absolute;
  top: var(--globe-top);         /* D1: shared top grid line — aligns with the layers bar */
  right: var(--globe-gutter);    /* D1: right grid line (was 18px) */
  z-index: var(--z-globe-4);
  width: max-content;  /* collapsed = compact pill (fits beside the layers bar); expands to the panel width */
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  font-size: var(--text-sm);
}
/* Panel-open reposition is DESKTOP-only: on mobile (≤720px) the dock becomes a
   fixed bottom-sheet overlay (.is-overlay, ~2963) which owns its own inset. This
   rule outweighs that overlay by specificity, so scope it to >720px to keep the
   two from fighting when a country panel + the watchlist overlay are both open. */
@media (min-width: 721px) {
  .globe-hero[data-panel-state="open"] .watchlist-dock {
    top: var(--globe-top);                 /* line up with the layers bar — wide screens have room beside it */
    right: calc(max(25%, 320px) + calc(var(--globe-gutter) + var(--sp-3)));   /* sit just left of the right country panel (=+28px) */
  }
}
/* B13 — right-gutter collision at 1280–1340px: with a panel open the reset/geo
   pills already hide, but the dock + bottom legends still crowd the narrow strip
   left of the right country panel. Move the dock to the LEFT half instead.
   Scope: only ≤1340px. The desktop left profile aside (.globe-panel--profile:
   left:16px, width:max(25%,320px), full column height) owns the top-left corner
   at EVERY desktop width, so we anchor the dock just to the RIGHT of that aside
   (mirroring the wide-screen "just left of the right panel" offset) rather than at
   left:18px — which would sit on top of the aside's header. This lands the dock in
   the free top-centre-left zone, clear of the right gutter, the centred layers bar
   (≈50%), and the bottom-right legends. The dock still expands DOWNWARD from its
   top anchor. Profile is hidden only ≤720px, where the non-overlay dock is
   display:none anyway, so no width needs a plain left:18px. */
@media (min-width: 721px) and (max-width: 1340px) {
  .globe-hero[data-panel-state="open"] .watchlist-dock {
    left: calc(max(25%, 320px) + calc(var(--globe-gutter) + var(--sp-3)));   /* just right of the left profile aside (=+28px) */
    right: auto;
    top: calc(var(--globe-top) + 40px);   /* drop below the centred layers bar row so the expanded 264px panel clears it (=124px) */
  }
}
.watchlist-dock__toggle {
  display: flex; align-items: center; gap: 8px;
  padding: 7px 10px;
  /* (Glass Terminal) tier-1 glass mini-surface (shared recipe with
     .globe-status-stamp): opaque fallback + @supports blur below; trio edges. */
  background: var(--glass-1-fallback);
  border: 1px solid var(--glass-hairline);
  border-radius: var(--r-md);
  color: var(--ink);
  cursor: pointer;
  box-shadow: var(--shadow-2), inset 0 1px 0 var(--glass-highlight);
}
.watchlist-dock__toggle:hover { border-color: var(--accent); }
.watchlist-dock__toggle > svg:first-child { color: var(--accent); flex: none; }
.watchlist-dock__toggle-label { font-weight: 600; }
.watchlist-dock__toggle-count {
  margin-left: auto;
  font-size: var(--text-xs); color: var(--ink-muted);
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
}
.watchlist-dock__chev { flex: none; color: var(--ink-muted); transition: transform var(--dur-base) var(--ease); }
.watchlist-dock.is-collapsed .watchlist-dock__chev { transform: rotate(-90deg); }
.watchlist-dock__panel {
  width: 264px;        /* fixed list width (the dock is otherwise sized to the compact pill) */
  /* (Glass Terminal) tier-1 glass surface — opaque fallback + @supports blur
     below; deeper tier-3 shadow for the dropped list + inset top highlight. */
  background: var(--glass-1-fallback);
  border: 1px solid var(--glass-hairline);
  border-radius: var(--r-md);
  box-shadow: var(--shadow-3), inset 0 1px 0 var(--glass-highlight);
  max-height: min(46vh, 420px);
  overflow-y: auto;
  padding: 4px;
}
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .watchlist-dock__toggle,
  .watchlist-dock__panel {
    background: var(--glass-1-bg);
    -webkit-backdrop-filter: blur(var(--glass-1-blur)) saturate(var(--glass-sat));
    backdrop-filter: blur(var(--glass-1-blur)) saturate(var(--glass-sat));
  }
}
.watchlist-dock.is-collapsed .watchlist-dock__panel { display: none; }
.watchlist-dock__list { list-style: none; margin: 0; padding: 0; }
/* B12 — zero-watch empty state (reuses .saved-list__empty title+body recipe);
   trim the vertical padding so it reads compact inside the dock panel. */
.watchlist-dock__empty { padding: 20px 12px; }
.watchlist-dock__row {
  display: flex; align-items: center; gap: 8px;
  padding: 6px;
  border-radius: 8px;
}
.watchlist-dock__row + .watchlist-dock__row { border-top: 1px solid var(--border); }
.watchlist-dock__row:hover { background: var(--bg-elev-2); }
.watchlist-dock__flag {
  width: 20px; height: 15px; flex: none;
  border-radius: 2px; object-fit: cover;
  box-shadow: 0 0 0 1px var(--scrim-08);
}
.watchlist-dock__flag--none { background: var(--border); }
.watchlist-dock__name {
  flex: 1 1 auto; min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.watchlist-dock__new {
  flex: none; font-size: var(--text-xs); font-weight: 700;
  color: var(--accent); font-family: var(--font-mono); font-variant-numeric: tabular-nums;
}
.watchlist-dock__row.is-surge .watchlist-dock__new { color: var(--surge-text); }
.watchlist-dock__go,
.watchlist-dock__remove {
  flex: none;
  display: inline-flex; align-items: center; justify-content: center;
  width: 24px; height: 24px; padding: 0;
  background: transparent; border: 1px solid transparent; border-radius: 6px;
  color: var(--ink-muted); cursor: pointer;
}
.watchlist-dock__go:hover { color: var(--ink); border-color: var(--border); }
.watchlist-dock__remove { color: var(--accent); font-size: 14px; line-height: 1; }
.watchlist-dock__remove:hover { border-color: var(--border); }

/* Indices section (2026-07-08) — a "지수" sub-list beneath the country rows plus a
   compact client-side add-search. Compact terminal density (rows ~30px); tokens
   only for dark+light parity; transitions reuse var(--dur-fast) var(--ease) so the
   global prefers-reduced-motion block zeroes them (no new @keyframes). */
.watchlist-dock__indices {
  margin-top: 4px;
  padding-top: 4px;
  border-top: 1px solid var(--border);
}
.watchlist-dock__subhead {
  padding: 4px 6px 2px;
  font-size: var(--text-2xs); font-weight: 700;
  letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--ink-muted);
}
.watchlist-dock__ilist { list-style: none; margin: 0; padding: 0; }
.watchlist-dock__iempty { padding: 6px; font-size: var(--text-2xs); color: var(--ink-muted); }
/* Watched-index row: name (truncates) + muted country (flex-none) + ✕ remove. */
.watchlist-dock__icountry {
  flex: none; max-width: 45%;
  font-size: var(--text-2xs); color: var(--ink-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.watchlist-dock__iremove { color: var(--ink-muted); }
.watchlist-dock__iremove:hover { color: var(--accent); border-color: var(--border); }
/* "+ Add index" affordance at the section foot. */
.watchlist-dock__add {
  display: flex; align-items: center; gap: 6px;
  width: 100%; margin-top: 2px; padding: 6px;
  background: transparent; border: 1px solid transparent; border-radius: 8px;
  color: var(--ink-muted); cursor: pointer;
  font: inherit; font-size: var(--text-2xs); text-align: left;
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.watchlist-dock__add:hover { color: var(--ink); background: var(--bg-elev-2); }
.watchlist-dock__add-plus { font-size: var(--text-sm); line-height: 1; color: var(--accent); }
.watchlist-dock__add:focus-visible,
.watchlist-dock__sresult:focus-visible,
.watchlist-dock__search-input:focus-visible { outline: none; box-shadow: var(--focus-ring); }
/* Inline add-search. */
.watchlist-dock__search { padding: 4px 2px 2px; }
.watchlist-dock__search-input {
  width: 100%; padding: 5px 8px;
  background: var(--field-well);
  border: 1px solid var(--border); border-radius: var(--r-sm);
  color: var(--ink); font: inherit; font-size: var(--text-2xs);
}
.watchlist-dock__search-input::placeholder { color: var(--ink-muted); }
.watchlist-dock__search-results {
  list-style: none; margin: 4px 0 0; padding: 0;
  max-height: 168px; overflow-y: auto;
}
.watchlist-dock__sresult {
  display: flex; align-items: center; gap: 6px;
  padding: 5px 6px; border-radius: 6px; cursor: pointer; color: var(--ink);
  transition: background var(--dur-fast) var(--ease);
}
.watchlist-dock__sresult:hover { background: var(--bg-elev-2); }
.watchlist-dock__sname {
  flex: 1 1 auto; min-width: 0; font-size: var(--text-2xs);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.watchlist-dock__scountry {
  flex: none; max-width: 40%;
  font-size: var(--text-3xs); color: var(--ink-muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.watchlist-dock__tier {
  flex: none; padding: 1px 5px; border-radius: 999px;
  font-size: var(--text-3xs); font-weight: 700; letter-spacing: 0.04em;
  border: 1px solid var(--border);
  font-variant-numeric: tabular-nums;
}
.watchlist-dock__tier--dm { color: var(--accent); }
.watchlist-dock__tier--em { color: var(--warm); }
.watchlist-dock__sempty { padding: 6px; font-size: var(--text-2xs); color: var(--ink-muted); }
.watchlist-dock__serror { padding: 6px; font-size: var(--text-2xs); color: var(--surge-text); }

/* Mobile overlay (audit #63) — the side dock is hidden on phones; opened from
 * the topbar menu it becomes a bottom sheet instead. Only .is-overlay is shown;
 * the plain dock stays display:none so it never overlaps the globe on mobile. */
@media (max-width: 720px) {
  .watchlist-dock:not(.is-overlay) { display: none; }
  .watchlist-dock.is-overlay {
    position: fixed;
    left: 0; right: 0; bottom: 0; top: auto;
    width: auto;
    z-index: var(--z-dropdown-over);                        /* above the country sheet (30) + its own backdrop (40) */
    padding: var(--sp-2) var(--sp-3) calc(var(--sp-3) + env(safe-area-inset-bottom, 0px));
    background: var(--bg-elev-1);
    border-top: 1px solid var(--border);
    border-radius: var(--r-lg) var(--r-lg) 0 0;
    box-shadow: 0 -8px 28px var(--scrim-22);
    max-height: 70vh;
  }
  .watchlist-dock.is-overlay .watchlist-dock__toggle {
    box-shadow: none; background: transparent; border-color: transparent;
    -webkit-backdrop-filter: none; backdrop-filter: none;
    padding-left: 0; padding-right: 0;
  }
  /* The chevron flips to an × close affordance meaning; keep the whole toggle
   * row as the close target (wired in app.js). */
  .watchlist-dock.is-overlay .watchlist-dock__panel {
    width: auto;
    max-height: calc(70vh - 52px);
    box-shadow: none; border: 0; padding: 0;
  }
  /* Overlay is always expanded — reveal the list even under .is-collapsed. */
  .watchlist-dock.is-overlay.is-collapsed .watchlist-dock__panel { display: block; }
}
.watchlist-overlay-backdrop {
  position: fixed;
  inset: 0;
  z-index: var(--z-dropdown);
  background: var(--scrim-40);
  -webkit-backdrop-filter: blur(1px);
  backdrop-filter: blur(1px);
}
.watchlist-overlay-backdrop[hidden] { display: none; }
/* The mobile Watchlist menu button is desktop-hidden as a CSS backstop — app.js
 * only un-hides it on coarse/narrow viewports, but this guarantees it can't leak
 * onto the desktop actions row even if that JS gate regresses. */
@media (min-width: 721px) and (pointer: fine) {
  /* Desktop backstop: keep the mobile Watchlist menu button hidden even if the
     JS gate regressed. Proven safe WITHOUT !important (BAN-IMPORTANT lint):
     WatchlistDock.syncMenuBtn() toggles the [hidden] ATTRIBUTE only — it never
     writes inline style.display — so `display` is not JS-inline-owned here. If
     [hidden] were wrongly stripped on desktop, this rule still wins: it loads
     after 10-base's `.btn{display:inline-flex}` at the same (0,1,0) specificity
     (source order decides), and the only other rules touching this button
     (.topbar-float__actions .btn in 60-widgets) set min-height/justify-content,
     never display. */
  .watchlist-menu-btn { display: none; }
}

/* E — "since last visit" brief card. Built by app.js from /api/brief.
   D2b (2026-07-07): moved from top-CENTRE (where it triple-stacked with the chip
   bar + hover label) to the top-RIGHT rail, aligned to --globe-gutter and sitting
   just BELOW the watchlist dock (top-right rail order: dock → brief). z-index
   --z-globe-brief (16) still beats the dock (--z-globe-4) so they read cleanly if
   both open. On phones it reverts to a top full-width card (media rule below). */
.globe-brief {
  position: absolute;
  top: calc(var(--globe-top) + 44px);   /* below the compact dock pill */
  right: var(--globe-gutter);
  z-index: var(--z-globe-brief); width: min(380px, 90vw); max-height: 44vh; overflow-y: auto;
  /* (Glass Terminal) tier-3 glass flyout — opaque fallback first; @supports below
     adds translucency + blur (mirrors .globe-data-pop). Trio = inset top highlight
     + hairline border + shadow-3. */
  background: var(--glass-3-fallback);
  border: 1px solid var(--glass-hairline); border-radius: var(--r-lg);
  box-shadow: var(--shadow-3), inset 0 1px 0 var(--glass-highlight); padding: 12px 14px;
}
@supports ((-webkit-backdrop-filter: blur(1px)) or (backdrop-filter: blur(1px))) {
  .globe-brief {
    background: var(--glass-3-bg);
    -webkit-backdrop-filter: blur(var(--glass-3-blur)) saturate(var(--glass-sat));
    backdrop-filter: blur(var(--glass-3-blur)) saturate(var(--glass-sat));
  }
}
.globe-brief[hidden] { display: none; }
/* D2b: with a country panel open the right 25% is the headlines panel, so shift
   the (right-anchored) brief LEFT of it — mirrors the dock/legends dodge. Desktop
   only; on phones the brief is centred and the panel is a bottom sheet. */
@media (min-width: 641px) {
  .globe-hero[data-panel-state="open"] .globe-brief {
    right: calc(max(25%, 320px) + calc(var(--globe-gutter) + var(--sp-3)));
  }
}
.globe-brief__close {
  position: absolute; top: 7px; right: 10px; background: transparent; border: 0;
  cursor: pointer; color: var(--ink-muted); font-size: 18px; line-height: 1; padding: 2px;
  display: inline-flex; align-items: center;   /* C2 — centre the SVG × */
}
.globe-brief__close:hover { color: var(--ink); }
.globe-brief__head { display: flex; flex-direction: column; gap: 1px; margin-bottom: 8px; padding-right: 18px; }
.globe-brief__head strong { font-size: var(--text-sm); color: var(--ink); }
.globe-brief__sub { font-size: var(--text-xs); color: var(--ink-muted); font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.globe-brief__list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 2px; }
.globe-brief__item {
  display: flex; align-items: center; justify-content: space-between; gap: 10px; width: 100%;
  padding: 6px 9px; border-radius: 8px; border: 0; background: transparent; cursor: pointer;
  font: inherit; color: var(--ink); text-align: left;
}
.globe-brief__item:hover { background: color-mix(in srgb, var(--accent) 10%, transparent); }
.globe-brief__item-label { font-size: var(--text-sm); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.globe-brief__item-count { font-size: var(--text-xs); color: var(--accent); font-weight: 600; flex: none; font-family: var(--font-mono); font-variant-numeric: tabular-nums; }
.globe-brief__item.is-surge .globe-brief__item-count { color: var(--surge-text); }
/* D2b: on phones the dock is a bottom-sheet overlay and the top-right rail is a
   sliver, so keep the brief's prior top full-width-ish card (centred, below the
   wrapped chip bar). No collision there — the hover label never fires on touch. */
@media (max-width: 640px) {
  .globe-brief {
    top: 108px; left: 50%; right: auto; transform: translateX(-50%); max-height: 32vh;
  }
}
@media (prefers-reduced-motion: no-preference) {
  .globe-brief:not([hidden]) { animation: brief-in var(--dur-base) var(--ease, ease) both; }
  /* Desktop: right-anchored → slide down only (no translateX centring). */
  @keyframes brief-in {
    from { opacity: 0; transform: translateY(-6px); }
    to { opacity: 1; transform: none; }
  }
  /* Phone: still centred, so the entry keeps the translateX(-50%). */
  @media (max-width: 640px) {
    @keyframes brief-in {
      from { opacity: 0; transform: translateX(-50%) translateY(-6px); }
      to { opacity: 1; transform: translateX(-50%); }
    }
  }
}

/* J1 — inline "since last visit" brief card at the top of the results column.
   One compact row on desktop (lead text + entity chips share a line and wrap);
   the dismiss control sits at the end. Stacks naturally on narrow screens since
   the flex row wraps. Distinct from .globe-brief (globe-hero overlay). */
.brief-card {
  display: flex; align-items: flex-start; gap: 10px;
  margin: 0 0 12px; padding: 10px 12px;
  background: color-mix(in srgb, var(--accent) 6%, var(--bg-elev-1));
  border: 1px solid color-mix(in srgb, var(--accent) 28%, var(--border));
  border-radius: var(--r-md); /* C2: was 12px; align with neighbors (globe-brief__item / .event use --r-md = 8px) */
}
.brief-card[hidden] { display: none; }
/* Wave-1 handoff (c) / B11: the minimized "Brief ▸" stub is a single button
   row — tighten the padding so it reads as a compact affordance, not a full
   card. */
.brief-card--mini { padding: 6px 10px; }
.brief-card__text {
  flex: 1 1 auto; margin: 0; min-width: 0;
  font-size: var(--text-sm); line-height: 1.5; color: var(--ink);
}
.brief-card__lead { color: var(--ink-muted); }
.brief-card__entity {
  display: inline; padding: 1px 4px; border: 0; border-radius: 6px;
  background: transparent; cursor: pointer; font: inherit;
  color: var(--accent); font-weight: 600;
}
.brief-card__entity:hover,
.brief-card__entity:focus-visible {
  background: color-mix(in srgb, var(--accent) 14%, transparent);
  text-decoration: underline;
}
.brief-card__entity.is-surge { color: var(--surge-text); }
.brief-card__surge {
  display: block; margin-top: 3px;
  font-size: var(--text-xs); color: var(--surge-text); font-weight: 600;
}
.brief-card__close {
  flex: none; align-self: flex-start; margin-top: 1px;
  background: transparent; border: 0; cursor: pointer; padding: 2px 4px;
  font-size: var(--text-xs); color: var(--ink-muted); white-space: nowrap;
}
.brief-card__close:hover,
.brief-card__close:focus-visible { color: var(--ink); text-decoration: underline; }
@media (max-width: 640px) {
  .brief-card { flex-direction: column; gap: 6px; }
  .brief-card__close { align-self: flex-end; }
}

/* Control hint (#5): on a plain wheel-down at the top of the hero, blur the globe
   and flash grey drag-control help (Ctrl/Shift + drag). Absolute over the canvas;
   pointer-events:none so it never blocks. app.js toggles .is-visible. */
.globe-ctrl-hint {
  position: absolute; inset: 0; z-index: var(--z-globe-4);
  display: flex; align-items: center; justify-content: center;
  opacity: 0; pointer-events: none;
  -webkit-backdrop-filter: blur(7px); backdrop-filter: blur(7px);
  background: color-mix(in srgb, var(--bg) 32%, transparent);
  transition: opacity var(--dur-slow) var(--ease);
}
.globe-ctrl-hint.is-visible { opacity: 1; }
.globe-ctrl-hint__inner {
  display: flex; flex-direction: column; gap: 8px; align-items: center; text-align: center;
  color: var(--ink-muted); font-size: var(--text-sm); padding: 16px 22px;
}
.globe-ctrl-hint__inner kbd {
  font: inherit; font-size: var(--text-2xs); padding: 1px 6px;
  border: 1px solid var(--glass-hairline); border-radius: var(--r-sm);
  background: var(--bg-elev-2);
  color: var(--ink-dim); margin: 0 3px;
}
@media (prefers-reduced-motion: reduce) { .globe-ctrl-hint { transition: opacity 1ms linear; } }

/* B4 (audit U2#7): 3-col grid so the nav cluster stays optically centred in
 * the column regardless of the per-page select's width. Left cell is an empty
 * 1fr spacer that mirrors the right cell (the size select), centre cell (auto)
 * holds the nav. */
.pager {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center; gap: var(--sp-3);
  margin-top: var(--sp-6);
  color: var(--ink-muted); font-size: var(--text-sm);
}
/* Centre cell: prev · indicator · next, as a flex cluster (the old .pager flow). */
.pager__nav {
  grid-column: 2;
  display: inline-flex; align-items: center; gap: var(--sp-3);
}
/* Page indicator (#page-indicator, "{n} / {total} …") in mono + tabular so the
   page numbers read as terminal data and don't jitter as they change. Any
   trailing locale unit word falls back through the mono chain. */
.pager__nav #page-indicator {
  font-family: var(--font-mono); font-variant-numeric: tabular-nums;
  font-size: var(--text-xs); color: var(--ink-muted); letter-spacing: 0;
}
/* Right cell: per-page select sits in the third column, right-aligned. */
.pager .masthead__pagesize { grid-column: 3; justify-self: end; }
/* (Glass Terminal, Wave 2) Empty / error / loading states (spec §1.6). Quiet,
   centred, descriptive copy at --ink-dim; the title holds --ink. Used by
   renderHeadlines() (0-results), loadHeadlines() catch (fetch error) and the map
   error path — all render a `.empty` <li> with `.empty__title` + a body line +
   an optional CTA (.btn / .btn--link). */
.empty, .loading { padding: var(--sp-7) var(--sp-5); text-align: center; color: var(--ink-dim); }
.empty__title { font-family: var(--font-serif); font-size: var(--text-lg); color: var(--ink); margin-bottom: var(--sp-2); }
/* Give the retry / clear-filters CTA breathing room below the copy (covers both
   the bare `.btn` retry and the `.btn.btn--link` clear-filters button). */
.empty .btn { margin-top: var(--sp-4); }
/* Error semantics: when app.js tags the fetch-error <li> with `.is-error` (see
   the html_js request), the whole state reads as a tinted --danger-soft card
   with a danger hairline + danger title, distinct from a genuine 0-results
   empty. Forward-looking — inert until the class is added. */
.empty.is-error {
  background: var(--danger-soft);
  border: 1px solid color-mix(in srgb, var(--danger) 30%, transparent);
  border-radius: var(--r-lg);
}
.empty.is-error .empty__title { color: var(--danger); }

@media (max-width: 900px) {
  .masthead { flex-wrap: wrap; gap: var(--sp-3); padding: var(--sp-3); }
  .masthead__search { order: 3; max-width: none; width: 100%; }
  .page { padding: var(--sp-4); }
  .article { grid-template-columns: 1fr; gap: var(--sp-3); }
  .article__meta-col { flex-direction: row; flex-wrap: wrap; align-items: center; gap: var(--sp-3); }
}

/* -------------------------------------------------------------------
 * Task #20 (B안) — Desktop 2-column layout (>=1280px).
 *
 *   left column : geo-panel + preset-bar + filter-rail   ~40%
 *   right column: main-content (results + pager)         ~60%
 *
 * Below the desktop breakpoint the two wrappers inside .page use
 * `display: contents`, which removes their own box from the layout
 * while keeping their children in document order. That way mobile and
 * tablet still render the four sections as direct children of <main>
 * exactly as they did before, with no ordering changes.
 *
 * At >=1280px the wrappers become real flex columns with their own
 * internal vertical gap, so expanding or collapsing the map only
 * changes the *left* column's height — the right column's headlines
 * stay pinned to the top of the row instead of sliding up and down.
 *
 * 40:60 is expressed with fractional units so the columns settle at
 * ~585px + ~875px inside the 1480px page max-width; with Source Serif
 * 4 at 16px, the right column sits around 72 characters per line,
 * which is the sweet spot for editorial body text. `align-items: start`
 * prevents the short column from stretching to match the long one.
 *
 * Tablet (900-1279px) uses the base single-column flow: Hybrid means
 * map stays expanded and results stack below. Mobile (<768px) also
 * single-column; the initial collapse state for the geo-panel on
 * mobile is decided in JS (see init()), not here, because it depends
 * on auth state.
 * ------------------------------------------------------------------- */

/* Layout-transparent wrappers at mobile/tablet — see comment above. */
.page__col { display: contents; }

@media (min-width: 1280px) {
  .page {
    display: grid;
    /* B4 (2026-07-07): 2fr/3fr → 5fr/8fr. Narrows the (now collapsed-by-
       default) left filter column, giving the headline list more room. */
    grid-template-columns: minmax(0, 5fr) minmax(0, 8fr);
    gap: var(--sp-5);
    align-items: start;
  }
  /* Turn each wrapper back into a real box so it can column-stack its
   * own children. `min-width: 0` keeps long words (long outlet names,
   * CJK without spaces) from forcing the column wider than 40%/60%. */
  .page__col {
    display: flex;
    flex-direction: column;
    gap: var(--sp-5);
    min-width: 0;
  }
  /* Child sections already carry their own `margin-bottom` at mobile
   * widths. At desktop the flex `gap` handles spacing between them, so
   * kill the bottom margin to avoid doubled vertical rhythm. */
  .page__col > * { margin-bottom: 0; }

  /* 2026-06-12 (user request): keep the filter stack reachable while
   * scrolling the headlines — previously the left column scrolled up
   * and away with the page. It now pins below the floating topbar
   * (12px inset + ~64px collapsed bar + 12px gap = 88px, same offset
   * convention as .globe-panel). When the column is taller than the
   * viewport (map expanded + advanced filters open) it scrolls
   * internally — without the max-height a pinned element taller than
   * the viewport would leave its lower controls unreachable. Sticky
   * relies on the grid's `align-items: start` above: a stretched
   * column would have no room to stick within. */
  .page__col--left {
    position: sticky;
    top: 88px;
    max-height: calc(100vh - 88px - var(--sp-5));
    overflow-y: auto;
    overscroll-behavior: contain;
    scrollbar-width: thin;
  }
}

/* -------------------------------------------------------------------
 * Task #20 — Masthead scale decoupled from .page.
 *
 * The masthead originally shared --sp-page-x (clamp(20px, 8vw, 160px))
 * with .page, which on 1280-1479px viewports left only ~1060px for the
 * logo + search + four action controls + stats + user menu. That
 * compressed CJK action labels (`새로고침`, `어둡게 전환`) into two
 * lines, which looked worse than the slightly narrower gutter. The
 * masthead is a global nav chrome — there's no editorial reason to
 * honour the same horizontal gutter as the article/list content, so
 * give it a tighter, saner clamp.
 * ------------------------------------------------------------------- */
@media (min-width: 901px) {
  .masthead {
    padding-inline: clamp(16px, 2.5vw, 40px);
  }
}
.masthead__actions .btn,
.masthead__actions .icon-btn,
.masthead__actions .user-menu,
.masthead .stats {
  white-space: nowrap;
}

/* -------------------------------------------------------------------
 * B6 (audit U2#6) — reset-all filter button, full-width footer row.
 *
 * Moved out of the advanced msel grid (where it read as a stray filter
 * cell) into its own right-aligned row below the msels, still inside
 * <details id="advanced-filters">. Right-aligned so it doesn't compete
 * with the msel labels for the eye; separated by a hairline from the
 * grid above.
 * ------------------------------------------------------------------- */
.filter-rail__advanced-footer {
  display: flex; justify-content: flex-end;
  margin-top: var(--sp-3);
}

/* -------------------------------------------------------------------
 * Task #20 follow-up — per-page selector in the masthead.
 *
 * Moved out of .pager so the setting lives next to the stats readout
 * it controls ("Showing X of Y"). Kept <label> wrapper for implicit
 * association with its hidden text label; visually it's just the
 * <select>. Reuses .lang-select look for consistency with the other
 * masthead <select>.
 * ------------------------------------------------------------------- */
.masthead__pagesize {
  display: inline-flex;
  align-items: center;
  gap: var(--sp-2);
}
/* B4: pager is now a 3-col grid — the size select is placed/aligned by
   `.pager .masthead__pagesize { grid-column: 3; justify-self: end }` in the
   .pager block above, so no margin-left nudge is needed here. */
.masthead__pagesize > select {
  appearance: none; -webkit-appearance: none;
  padding: 6px 28px 6px 12px;
  background: var(--bg-elev-1); color: var(--ink);
  border: 1px solid var(--border); border-radius: var(--r-md);
  font-size: var(--text-xs); cursor: pointer;
  font-variant-numeric: tabular-nums;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 12 12' fill='none' stroke='%237a8694' stroke-width='1.5'><path d='M3 4.5l3 3 3-3'/></svg>");
  background-repeat: no-repeat; background-position: right 8px center; background-size: 10px 10px;
}
.masthead__pagesize > select:hover { border-color: var(--link); }

@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 0ms !important; animation: none !important; }
  html { scroll-behavior: auto; }
  .geo-panel__map-wrap { transition: none; }
}

/* Touch-only devices shouldn't keep a hover style after a tap. The easiest
 * fix without refactoring every rule is to neutralise hover visuals for the
 * small set of controls where sticky hover would actually be noticeable. */
@media (hover: none) {
  .btn:hover,
  .icon-btn:hover,
  .lang-select:hover,
  .chip:hover,
  .geo-panel__toggle:hover,
  .masthead__search input:hover,
  .filter-rail select:hover,
  .msel__summary:hover,
  .msel__option:hover,
  .msel__action:hover,
  .pill__x:hover {
    border-color: inherit;
    background: inherit;
    color: inherit;
  }
  .article__title a:hover { color: inherit; text-decoration: none; }
  .map-frame .country:hover { fill: var(--map-land); }
}

/* ---- Skeleton loading rows ------------------------------------------------
 * Match .article layout so the shift is imperceptible when real data lands.
 * The shimmer is a moving gradient; reduced-motion users get a static swatch
 * via the prefers-reduced-motion block above (which zeroes out animations). */
.article--skeleton {
  pointer-events: none;
}
.skeleton {
  /* perf (audit #26): shimmer moved off paint-bound background-position onto a
     composited transform. Base = the gradient's edge colour; a ::after overlay
     carries the bright band and is slid across with translateX. Visual match. */
  position: relative;
  overflow: hidden;
  background: var(--bg-elev-1);
  border-radius: var(--r-sm);
}
.skeleton::after {
  content: "";
  position: absolute;
  inset: 0;
  /* (Glass Terminal) glass-consistent sweep: transparent edges let the base
     .skeleton fill show, and the crest is a soft --ink tint that reads on both
     themes (dark ink → dark sweep on light; light ink → light sweep on dark).
     No hard surface colours, so the shimmer tracks the theme automatically. */
  background: linear-gradient(
    90deg,
    transparent 0%,
    color-mix(in srgb, var(--ink) 8%, transparent) 50%,
    transparent 100%
  );
  animation: skeleton-shimmer 1.4s linear infinite;
}
.skeleton--logo  { width: 60px; height: 40px; margin-bottom: var(--sp-2); border-radius: var(--r-md); }
.skeleton--line  { height: 12px; margin-bottom: 8px; }
.skeleton--title { height: 18px; width: 82%; }
.skeleton--short { height: 10px; width: 50%; }
.skeleton--medium{ width: 66%; }
@keyframes skeleton-shimmer {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(100%); }
}

/* (Glass Terminal, Wave 2) Compact-density skeleton mirror. renderLoading()
   always emits the standard two-column skeleton, but compact is the DEFAULT
   density and its real rows are single-line ~34px terminal rows — so on a
   refresh the tall placeholder → short real row swap jumps. Collapse the compact
   skeleton to match: drop the stale 60x40 logo box + the two extra body lines,
   zero the inter-line margins, and restate the compact body's flex sizing on the
   bare col-2 <div> (it isn't `.article__body`, so it doesn't inherit it). Coupled
   to the fixed renderLoading() DOM: `.article__meta-col` + a trailing bare <div>.
   `display:none` is CSS-owned on skeleton nodes (JS never writes them inline). */
.headline-list.is-compact .article--skeleton > div:last-child { flex: 1 1 0; min-width: 0; }
.headline-list.is-compact .article--skeleton .skeleton { margin-bottom: 0; }
.headline-list.is-compact .article--skeleton .skeleton--logo { display: none; }
/* The outlet-name placeholder needs a definite width: in the compact flex row
   the meta-col is content-sized, so the base `width:50%` would resolve against an
   indefinite parent and collapse to 0. A fixed 72px reads as a short name chip. */
.headline-list.is-compact .article--skeleton .skeleton--short { width: 72px; }
.headline-list.is-compact .article--skeleton > div:last-child > .skeleton--line:not(.skeleton--title) { display: none; }

/* (Glass Terminal, Wave 2) Coarse-pointer ergonomics — feed controls. Touch
   targets >=40px for the density toggle + filter chips (inline-flex so min-height
   applies to the chip buttons), and an iOS input-zoom guard (>=16px) on every
   filter / search / pager field so focusing one doesn't trigger Safari's zoom.
   Gated to pointer:coarse so the dense desktop mouse UI is untouched. */
@media (pointer: coarse) {
  .density-toggle { min-height: 40px; }
  .chip { display: inline-flex; align-items: center; min-height: 40px; }
  .filter-rail select,
  .msel__summary,
  .msel__search,
  .masthead__pagesize > select,
  .watchlist-dock__search-input { font-size: 16px; }
}

