/* intel-viewer shared stylesheet (intel-4rn).
 *
 * Owns the palette + every cross-page primitive (body, nav, h1/h2,
 * .panel, tables, form controls, .muted, .badge). Per-page-specific
 * styles stay in each template's {% block head_extra %} block.
 *
 * Why a single file: every template inlined this same :root + body + ...
 * block before, so changing a colour required editing 8 files. One
 * sheet means one place to bump tokens.
 */

/* Why `html` and not `:root`:
 *
 * Firefox's Light theme injects its OWN palette tokens (--bg, --fg,
 * --border, --hover, --confirmation, ...) at the :root level on every
 * page. These compete with ours in the cascade and, in the user's
 * environment, win (the dev tools showed --bg: #eee and --fg: #494949
 * coming from :root regardless of our color-scheme: dark declaration).
 *
 * Declaring our palette on `html` instead of `:root` puts ours one
 * specificity rung higher than Firefox's injected tokens, so var(--bg)
 * et al. resolve to our values everywhere they're used — body, panels,
 * the whole inheritance chain. color-scheme stays on `html` so the form
 * controls still render in dark mode. */
html {
  color-scheme: dark;

  /* Surface palette */
  --bg:      #0d1117;
  --panel:   #161b22;
  --border:  #30363d;

  /* Text palette */
  --fg:      #c9d1d9;   /* primary body — 12.4:1 vs --bg, AAA */
  /* --muted bumped 2026-06-04 (intel-4rn) from #8b949e (4.6:1, AA-large
   * only) to #a8b2bd (~6.5:1, AAA-large + AA-normal). User flagged the
   * older value as too low-contrast in Firefox. */
  --muted:   #a8b2bd;
  --accent:  #58a6ff;   /* links + headings — 7.3:1 vs --bg, AAA */

  /* Severity / classification palette (used by alerts, alpha pages,
   * regions.enabled toggle). Kept centralised so alerts.html and
   * alpha_fred_8k.html see the same green/amber/red. */
  --sev-info:   #3fb950;
  --sev-warn:   #d29922;
  --sev-alert:  #f85149;

  /* Aliases for alpha + regions, point at the severity slots so
   * "pos" and "neg" and "enabled-yes" all share the same hex. */
  --pos:    var(--sev-info);
  --neg:    var(--sev-alert);
  --strong: var(--sev-info);
  --weak:   var(--sev-warn);
  --noise:  var(--muted);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  padding: 1rem;
  background: var(--bg);
  color: var(--fg);
  font: 13px/1.4 ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;
}

/* ---------------------------------------------------------------------
 * Top navigation
 * ---------------------------------------------------------------------
 * Replaces the per-template "<h1>... links ...</h1>" pattern. base.html
 * renders this; pages no longer touch it. */
.app-nav {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
  margin: 0 0 1rem 0;
}
.app-nav-brand {
  font-size: 1.1rem;
  color: var(--accent);
  font-weight: bold;
}
.app-nav-links {
  font-size: 0.8rem;
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  flex-wrap: wrap;
}
.app-nav-links a {
  color: var(--muted);
  text-decoration: none;
}
.app-nav-links a:hover {
  color: var(--accent);
  text-decoration: underline;
}
.app-nav-links a.active {
  color: var(--accent);
  font-weight: bold;
}
.app-nav-links .sep {
  color: var(--border);
}

/* ---------------------------------------------------------------------
 * Top-nav health dot (intel-qoy)
 * --------------------------------------------------------------------- *
 * Tri-state heartbeat driven by source_cadence_violation firings in the
 * last 30 min. Click links to /status which surfaces the per-source
 * details. The pulse animation only runs on red so a quiet system reads
 * as quiet at a glance. */
.app-nav-health {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  text-decoration: none;
  margin-right: 0.5rem;
}
.app-nav-health-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  display: inline-block;
  background: var(--muted);
}
.app-nav-health-dot.green { background: var(--sev-info); }
.app-nav-health-dot.amber { background: var(--sev-warn); }
.app-nav-health-dot.red   { background: var(--sev-alert); animation: heartbeat 1.4s ease-in-out infinite; }
@keyframes heartbeat {
  0%, 100% { transform: scale(1);   opacity: 1;   }
  50%      { transform: scale(1.4); opacity: .6;  }
}

/* ---------------------------------------------------------------------
 * Hero-map layout for / (intel-qoy)
 * --------------------------------------------------------------------- *
 * The map dominates: ~70% width × ~70vh height. Sidebar column reserved
 * to its right (populated by ticket intel-a8p). Below the hero, signals
 * + stream share a row at ~25vh max.
 *
 * Page-specific (only / uses it) so it lives in app.css under the
 * .live-hero scope rather than in index.html's <style>. Keeping it
 * centralised here avoids the map sizing drifting if other pages later
 * want to share a similar geometry (e.g. /regions could embed a map
 * in this same shape). */
body.live-hero { max-width: none; }
.live-grid {
  display: grid;
  gap: 1rem;
  grid-template-columns: minmax(0, 1fr) 320px;
  /* First row is exactly 70vh (clamped to a minimum). minmax() with
   * vh would still let the row expand past the max when sidebar
   * content exceeds it — so we use a fixed max() expression that
   * never grows beyond the map size. The sidebar scrolls inside
   * this fixed-size row. (intel-a8p regression 2026-06-08.) */
  grid-template-rows: max(480px, 70vh) auto;
  grid-template-areas:
    "map   sidebar"
    "below below";
}
.live-grid .map-panel    { grid-area: map; min-height: 0; }
.live-grid .map-sidebar  { grid-area: sidebar; min-height: 0; }
.live-grid .below-row    { grid-area: below; }
.live-grid #quake-map    { height: 100%; min-height: 480px; border-radius: 4px; }
.live-grid .below-row {
  display: grid;
  gap: 1rem;
  grid-template-columns: 1fr 1fr;
  align-items: stretch;
}
.live-grid .below-row > section {
  max-height: 30vh;
  overflow-y: auto;
}
@media (max-width: 1100px) {
  /* Collapse the sidebar column and stack everything when the screen
   * can't comfortably hold map + sidebar + below in two rows. */
  .live-grid {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto auto;
    grid-template-areas: "map" "sidebar" "below";
  }
  .live-grid #quake-map { height: 60vh; }
  .live-grid .below-row { grid-template-columns: 1fr; }
  .live-grid .below-row > section { max-height: none; }
  /* Stacked layout: let the sidebar grow naturally and use page
   * scroll. The desktop-only pin (overflow:hidden + inner scroll)
   * would clip the sidebar off-screen on mobile. */
  .live-grid .map-sidebar { overflow: visible; }
  .live-grid .map-sidebar > .sidebar-section { overflow-y: visible; }
}

/* Sidebar (intel-5cb v1: layer toggles). Vertical stack of
 * sub-panels; the first one holds the layer-toggle controls migrated
 * out of Leaflet's L.control.layers widget. v2 (intel-a8p) will add
 * sub-panels for time-windows, search, opacity.
 *
 * Sidebar panels stretch to fill the column width — without this an
 * <aside> with no explicit width collapses to fit content and floats
 * in the top-right of the grid cell instead of filling it. */
.live-grid .map-sidebar {
  display: flex;
  flex-direction: column;
  gap: .75rem;
  align-self: stretch;
  /* Confine the sidebar to the map row's height; the inner panel
   * scrolls (intel-a8p). Without this the sidebar height drives
   * the row taller than the map and leaves a black gap below. */
  min-height: 0;
  overflow: hidden;
}
.live-grid .map-sidebar > .sidebar-section {
  width: 100%;
  min-height: 0;
  overflow-y: auto;
  /* The lone sidebar-section takes all available column height so
   * scrolling lives inside it (not the column wrapper). Sticky search
   * row makes the search input always reachable. */
  flex: 1 1 auto;
}
.live-grid .map-sidebar .sidebar-search {
  position: sticky;
  top: 0;
  background: var(--panel);
  /* Small bottom shadow so content underneath stays distinct on
   * scroll without competing with the search box. */
  padding-bottom: .35rem;
  z-index: 1;
}
.sidebar-section h3 {
  font-size: 0.8rem;
  margin: 0 0 .5rem 0;
  color: var(--accent);
  font-weight: normal;
  text-transform: uppercase;
  letter-spacing: .05em;
}
.layer-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: .25rem;
}
.layer-row {
  display: flex;
  align-items: center;
  gap: .5rem;
  font-size: 0.85rem;
  padding: .15rem .1rem;
}
.layer-row input[type="checkbox"] {
  accent-color: var(--accent);
  margin: 0;
}
.layer-row .layer-swatch {
  width: 10px;
  height: 10px;
  border-radius: 2px;
  display: inline-block;
  flex-shrink: 0;
}
.layer-row label {
  cursor: pointer;
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.layer-list-section + .layer-list-section {
  margin-top: .5rem;
  padding-top: .5rem;
  border-top: 1px solid var(--border);
}
.layer-list-empty {
  color: var(--muted);
  font-size: 0.78rem;
  padding: .25rem .1rem;
  font-style: italic;
}

/* intel-a8p: per-layer sub-controls (time-window select + opacity
 * slider). Sits as a sibling <li> under each layer row, indented
 * to read as "settings for the layer above." */
.layer-sub-controls {
  padding: 0.1rem 0.5rem 0.35rem 1.65rem;
  display: flex;
  flex-direction: column;
  gap: 0.2rem;
  font-size: 0.75rem;
  color: var(--muted);
}
.layer-sub-controls label {
  display: flex;
  align-items: center;
  gap: 0.4rem;
}
.layer-sub-controls label > span {
  flex: 1;
  min-width: 0;
}
.layer-sub-controls select,
.layer-sub-controls input[type="range"] {
  flex: 0 1 auto;
  font-size: inherit;
  background: var(--panel);
  color: var(--accent);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 0 0.2rem;
  min-width: 0;
}
.layer-sub-controls input[type="range"] {
  accent-color: var(--accent);
  width: 5.5rem;
  padding: 0;
}

/* Global search box at the top of the sidebar. Filters markers
 * across every layer in-place — no API roundtrip. */
.sidebar-search {
  margin-bottom: 0.5rem;
}
.sidebar-search input {
  width: 100%;
  padding: 0.3rem 0.45rem;
  font-family: inherit;
  font-size: 0.8rem;
  background: var(--panel);
  color: var(--accent);
  border: 1px solid var(--border);
  border-radius: 4px;
}
.sidebar-search input::placeholder { color: var(--muted); }
.sidebar-search input:focus {
  outline: none;
  border-color: var(--accent);
}
/* Non-empty search → visible "filtering active" cue so the user
 * always knows WHY non-matching markers are dim. Class is toggled
 * by the input handler. */
.sidebar-search input.has-query {
  border-color: var(--accent);
  box-shadow: 0 0 0 1px var(--accent);
}
.sidebar-search-status {
  display: none;
  font-size: 0.7rem;
  color: var(--accent);
  margin-top: 0.2rem;
}
.sidebar-search input.has-query + .sidebar-search-status {
  display: block;
}

/* "Reset all controls" link at the bottom of the sidebar.
 * Defensive escape hatch — a stale persisted search or opacity
 * makes the map look broken on next visit; this gives the user
 * a one-click way out without DevTools. */
.sidebar-reset {
  margin-top: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px dashed var(--border);
  font-size: 0.75rem;
  color: var(--muted);
  text-align: right;
}
.sidebar-reset a {
  color: var(--muted);
  cursor: pointer;
}
.sidebar-reset a:hover { color: var(--accent); }

h1 { font-size: 1.1rem; margin: 0 0 1rem 0; color: var(--accent); }
h2 { font-size: 0.95rem; margin: 0 0 .5rem 0; color: var(--accent); }

a { color: var(--accent); text-decoration: none; }
a:hover { text-decoration: underline; }

.muted { color: var(--muted); }

/* intel-cjc6: original-language headline under a translated one on
 * /news + /state-media. Smaller + dimmer than the body title so it
 * reads as a subtitle. */
.title-original {
  font-size: 0.78rem;
  margin-top: 0.1rem;
}

/* intel-cjc6: expandable full-translation block under each
 * translated row. <details> is native and zero-JS. */
.description-full {
  margin-top: 0.35rem;
  font-size: 0.82rem;
}
.description-full > summary {
  cursor: pointer;
  font-size: 0.7rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  user-select: none;
}
.description-full > summary:hover { color: var(--accent); }
.description-full-translated {
  margin-top: 0.3rem;
  line-height: 1.4;
  max-width: 70ch;
}

/* intel-cjc6: small "translated from X" tag inline with the
 * translated headline. ↻ symbol + ISO lang code (JA, FR, ...) reads
 * unambiguously and avoids the lang→country mapping problem of
 * flag emoji. */
.translation-tag {
  display: inline-block;
  font-size: 0.65rem;
  letter-spacing: 0.05em;
  color: var(--muted);
  border: 1px solid var(--border);
  border-radius: 3px;
  padding: 0 0.25rem;
  margin-left: 0.3rem;
  vertical-align: 0.15em;
  white-space: nowrap;
}

/* Map popup "more info" links (intel-db0). Keeps the link row visually
 * subordinate to the popup body — smaller, separated by a thin rule,
 * and clearly clickable without overpowering the data above. */
.popup-links {
  margin-top: .4rem;
  padding-top: .35rem;
  border-top: 1px dashed var(--border);
  font-size: 0.78rem;
}
.popup-links a { margin-right: .15rem; }

/* ---------------------------------------------------------------------
 * Cards
 * --------------------------------------------------------------------- */
.panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 6px;
  padding: .75rem;
}

/* ---------------------------------------------------------------------
 * Tables
 * --------------------------------------------------------------------- */
table { width: 100%; border-collapse: collapse; }
th, td {
  text-align: left;
  padding: .4rem .5rem;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}
th {
  color: var(--muted);
  font-weight: normal;
  text-transform: uppercase;
  font-size: 0.75rem;
}

/* ---------------------------------------------------------------------
 * Form controls
 * --------------------------------------------------------------------- */
select, input[type="text"], input[type="search"], input[type="number"] {
  background: var(--bg);
  color: var(--fg);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: .25rem;
  font: inherit;
}

/* ---------------------------------------------------------------------
 * Status / severity badges (used by /alerts, anywhere we need a chip)
 * --------------------------------------------------------------------- */
.badge {
  display: inline-block;
  padding: .15rem .4rem;
  border-radius: 3px;
  font-size: 0.7rem;
  font-weight: bold;
  text-transform: uppercase;
  letter-spacing: .05em;
  white-space: nowrap;
}
.badge-info  { background: rgba(63,185,80,.15);  color: var(--sev-info); }
.badge-warn  { background: rgba(210,153,34,.15); color: var(--sev-warn); }
.badge-alert { background: rgba(248,81,73,.15);  color: var(--sev-alert); }

/* ---------------------------------------------------------------------
 * Stream grouping (intel-bg1)
 * ---------------------------------------------------------------------
 * The cross-source stream panel collapses consecutive same-source
 * events within a 60s window into a single <details> row showing a
 * count chip. The default closed state matches the chronological table
 * shape so visual rhythm with non-collapsed rows is preserved. */
.stream-table .stream-group {
  margin: 0;
}
.stream-table .stream-group > summary {
  display: flex;
  align-items: baseline;
  gap: .5rem;
  cursor: pointer;
  padding: 0;
  list-style: revert;
}
.stream-table .stream-group > summary::-webkit-details-marker { display: revert; }
.stream-table .stream-group-time {
  font-variant-numeric: tabular-nums;
}
.stream-table .stream-group-source { /* keep flush with non-collapsed source col */ }
.stream-table .stream-group-count {
  display: inline-block;
  padding: 0 .35rem;
  border-radius: 3px;
  background: rgba(88,166,255,0.15);
  color: var(--accent);
  font-size: 0.75rem;
  font-weight: bold;
}
.stream-table .stream-group-hint {
  font-size: 0.78rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
  max-width: 40ch;
}
.stream-table .stream-group-detail {
  margin: .35rem 0 .25rem 1.5rem;
  border-left: 1px solid var(--border);
  padding-left: .75rem;
}
.stream-table .stream-group-detail td {
  font-size: 0.78rem;
  padding: .15rem .4rem;
  border-bottom: none;
}
