/* Command Center — design system. One source of truth: tokens, base, components, pages.
   Direction: "Annunciator" — a cold blue-steel instrument chassis lit by warm sodium-amber
   legends. Machined edges, etched labels, recessed data wells, tabular figures. The one
   signature element is the annunciator lamp: it lights only when a panel holds live data. */

/* The three families below are shipped with the site rather than assumed to be
   installed. Naming them in a font stack alone meant the whole typographic
   system — condensed legends, tabular mono figures — silently collapsed to
   system defaults on any machine without Fira, which is nearly all of them. */
@import url('fonts.css?v=20260722shell1');

:root {
  color-scheme: dark;

  /* type — condensed legends, plain body, mono for every number */
  --font-sans: 'Fira Sans', 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-legend: 'Fira Sans Condensed', 'Roboto Condensed', 'Archivo Narrow', 'Segoe UI Variable Display', system-ui, sans-serif;
  --font-mono: 'JetBrains Mono', 'Hack', 'Noto Sans Mono', ui-monospace, 'SFMono-Regular', Menlo, Consolas, monospace;

  /* surfaces — cold blue-steel chassis */
  --bg: #0d1116;
  --surface: #151b23;
  --surface-raised: #1c232c;
  --surface-hover: #242d38;
  --well: #0a0e13;
  --border: rgba(150, 172, 198, 0.13);
  --border-strong: rgba(150, 172, 198, 0.26);

  /* text — cold cast, matched to the chassis */
  --text: #dce3ec;
  --text-muted: #8794a5;
  --text-faint: #58636f;

  /* accent — sodium amber, the panel backlight */
  --accent: #ffb000;
  --accent-strong: #ffc957;
  --accent-dim: #a9762a;
  --accent-soft: rgba(255, 176, 0, 0.12);
  --accent-border: rgba(255, 176, 0, 0.45);
  --accent-contrast: #0d1116;
  --accent-glow: rgba(255, 176, 0, 0.55);

  /* gold — retained token, folded into the amber family */
  --gold: #ffb000;
  --gold-strong: #ffc957;
  --gold-soft: rgba(255, 176, 0, 0.12);
  --gold-border: rgba(255, 176, 0, 0.4);

  /* semantic — market colours stay legible and conventional */
  --success: #35d6a0;
  --danger: #ff5f56;
  --warn: #f2b84b;

  /* machined chamfers, not rounded cards */
  --r-sm: 2px;
  --r-md: 3px;
  --r-lg: 4px;
  --r-xl: 6px;
  --r-pill: 999px;

  /* panels sit on the chassis rather than floating above it */
  --shadow-sm: 0 1px 0 rgba(0, 0, 0, 0.5);
  --shadow-md: 0 2px 0 rgba(0, 0, 0, 0.5), 0 10px 26px rgba(0, 0, 0, 0.34);
  --shadow-lg: 0 20px 52px rgba(0, 0, 0, 0.62);
  --bezel: inset 0 1px 0 rgba(190, 210, 235, 0.07);
  --ease: cubic-bezier(0.22, 1, 0.36, 1);

  --site-gutter-visual-width: 12px;
}

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

html, body {
  min-height: 100%;
  margin: 0;
  padding: 0;
  scrollbar-width: thin;
  scrollbar-color: rgba(150, 172, 198, 0.26) transparent;
}

html::-webkit-scrollbar, body::-webkit-scrollbar {
  width: var(--site-gutter-visual-width);
  height: var(--site-gutter-visual-width);
}
html::-webkit-scrollbar-track, body::-webkit-scrollbar-track { background: var(--well); }
html::-webkit-scrollbar-thumb, body::-webkit-scrollbar-thumb {
  background: var(--surface-raised);
  border: 3px solid var(--well);
  border-radius: var(--r-sm);
}
html::-webkit-scrollbar-thumb:hover, body::-webkit-scrollbar-thumb:hover { background: var(--surface-hover); }

html:has(body.site-page) { scrollbar-gutter: stable; }
body.site-page { scrollbar-gutter: stable; }

body {
  font-family: var(--font-sans);
  font-size: 15px;
  line-height: 1.6;
  color: var(--text-muted);
  padding: 18px;
  box-sizing: border-box;
  min-height: 100vh;
  background-color: var(--bg);
  /* a single warm wash from above: the panel is lit from one source */
  background-image:
    radial-gradient(ellipse 1100px 460px at 50% -12%, rgba(255, 176, 0, 0.05), transparent 68%),
    linear-gradient(180deg, #0b0f14 0%, #0d1116 42%, #11161d 100%);
  background-attachment: fixed;
}

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

h1, h2, h3 { margin: 0; color: var(--text); }

/* legend: an etched plate label, not a heading */
h2 {
  font-family: var(--font-legend);
  font-size: 11px;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.2em;
  color: var(--text-muted);
  margin: 0 0 14px;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.6);
}

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

/* Every page needs one h1 naming it, but the chassis says what page you are on
   with the lit nav button rather than a title bar — so the heading is carried
   for the document outline and read only by assistive tech. */
.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ---------- buttons + inputs ---------- */
button, .home-button, .dropbtn {
  height: 38px;
  padding: 0 15px;
  border-radius: var(--r-md);
  border: 1px solid var(--border-strong);
  background: var(--surface-raised);
  color: var(--text);
  cursor: pointer;
  font-family: var(--font-legend);
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  text-decoration: none;
  line-height: 1;
  white-space: nowrap;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  box-shadow: var(--bezel);
  transition: background 0.16s var(--ease), border-color 0.16s var(--ease), color 0.16s var(--ease), box-shadow 0.16s var(--ease);
}

button:hover, .home-button:hover, .dropbtn:hover {
  border-color: var(--accent-border);
  background: var(--surface-hover);
  color: var(--accent-strong);
  box-shadow: var(--bezel), 0 0 0 1px var(--accent-soft);
}

button:active, .home-button:active, .dropbtn:active {
  background: var(--well);
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.5);
}

button:disabled, .home-button:disabled {
  opacity: 0.4;
  cursor: not-allowed;
}

input[type="text"], input[type="search"], input[type="range"], select, textarea {
  height: 40px;
  padding: 0 14px;
  border-radius: var(--r-md);
  border: 1px solid var(--border-strong);
  /* recessed well: inputs read as cut into the panel */
  background: var(--well);
  color: var(--text);
  font-family: inherit;
  font-size: 0.85rem;
  font-weight: 500;
  letter-spacing: 0.01em;
  outline: none;
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.45);
  transition: border-color 0.16s ease, box-shadow 0.16s ease;
}

input[type="text"]::placeholder, input[type="search"]::placeholder { color: var(--text-faint); font-weight: 500; }
input[type="text"]:focus, input[type="search"]:focus, select:focus {
  border-color: var(--accent-border);
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.45), 0 0 0 3px var(--accent-soft);
}

/* ---------- layout shell ---------- */
.page {
  width: min(96%, 1480px);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  padding-bottom: 18px;
}

.main {
  position: relative;
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

/* Every page carries .site-page for the shared chassis, plus one class naming
   itself. The base used to be .home-page, which the dashboard, media, games and
   repos pages all had to claim they were; the dashboard was then singled out
   with body:has(.dashboard-header) because it plainly was not the home page. */
body.site-page {
  overflow-y: auto;
}
body.site-page .page {
  height: auto;
  /* tall enough to reach the bottom of the viewport so the footer seats there on
     short pages (the media portal) instead of floating mid-screen; .main takes
     the slack via flex: 1. Pages with real content just scroll past it. */
  min-height: calc(100vh - 36px);
}

/* dashboard: fixed viewport, preserved 82% width */
body.dashboard-page {
  height: 100vh;
  overflow: hidden;
}
body.dashboard-page .page {
  width: 82%;
  max-width: 82%;
  height: 100vh;
  overflow: hidden;
}
body.dashboard-page .main {
  flex: 1;
  overflow: hidden;
  min-height: 0;
}

/* stream: preserved 99% width, natural scroll fallback */
body.stream-page {
  height: auto;
  min-height: 100vh;
  overflow-y: auto;
}
body.stream-page .page {
  width: 99%;
  max-width: 99%;
  height: auto;
  min-height: calc(100vh - 36px);
}
body.stream-page .main {
  flex: 1;
  min-height: 0;
}

/* ---------- header: the top bezel of the chassis ---------- */
header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
  min-height: 58px;
  padding: 0 14px;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: linear-gradient(180deg, var(--surface-raised), var(--surface));
  box-shadow: var(--bezel), var(--shadow-md);
  position: relative;
  z-index: 10;
  flex: 0 0 auto;
  margin-bottom: 14px;
}
/* amber filament along the bottom edge — the panel is powered */
header::after {
  content: '';
  position: absolute;
  left: 12px; right: 12px; bottom: -1px;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--accent-border), transparent);
  opacity: 0.7;
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
  margin-right: 6px;
  padding-right: 14px;
  border-right: 1px solid var(--border);
  color: var(--text);
}
.brand:hover { color: var(--text); }
.brand-mark {
  width: 20px;
  height: 20px;
  flex: 0 0 20px;
  display: inline-flex;
}
.brand-mark svg { width: 100%; height: 100%; display: block; }
.brand-name {
  font-family: var(--font-legend);
  font-size: 13px;
  font-weight: 700;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  white-space: nowrap;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.6);
}

/* The nav seats against the brand on every page and takes the slack, so whatever
   a page hangs on the right — status rail, ticker, stream controls — is pushed to
   the far edge rather than deciding where the nav lands. It must not shrink or
   wrap: it used to be the only flexible item in the row, so on the stream page it
   absorbed every pixel the controls demanded and stacked one link per line. */
.header-left {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  flex: 0 0 auto;
  flex-wrap: nowrap;
  margin-right: auto;
}

/* ---------- status rail ----------
   Readings recessed into the header bezel, scoped to the home page so the
   dashboard / stream / media / repos headers are untouched. Deliberately
   lampless: see the note in index.html. */
body.home-page .status-rail {
  display: inline-flex;
  align-items: center;
  gap: 14px;
  flex: 0 0 auto;
  padding: 5px 12px;
  background: var(--well);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  box-shadow: inset 0 2px 5px rgba(0, 0, 0, 0.45);
}
body.home-page .rail-cell {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1px;
  line-height: 1.1;
}
body.home-page .rail-cell + .rail-cell {
  padding-left: 14px;
  border-left: 1px solid var(--border);
}
body.home-page .rail-legend {
  font-family: var(--font-legend);
  font-size: 0.6rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--text-faint);
}
body.home-page .rail-value {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  font-weight: 500;
  color: var(--text);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
body.home-page .rail-reading {
  display: inline-flex;
  align-items: center;
  gap: 7px;
}
body.home-page .rail-countdown { color: var(--text-muted); font-size: 0.76rem; }
/* Same chip class, but not the same reading: the index rows report whether a
   quote is Live or Last, the rail reports whether the market is Open or Closed.
   Two words on one instrument means the rail has to look unlike the rows, so it
   is drawn as an outline rather than a filled lens - and it has to size to its
   text, since the rows are pinned at 26px for "Live"/"Last". */
body.home-page .status-rail .market-index-state {
  width: auto;
  padding: 2px 7px;
  background: transparent;
  letter-spacing: 0.1em;
}

.header-left .home-button {
  position: relative;
  height: 36px;
  padding: 0 13px;
  border-radius: var(--r-sm);
  border: 1px solid transparent;
  background: transparent;
  color: var(--text-muted);
  font-size: 12px;
  letter-spacing: 0.13em;
  box-shadow: none;
}
.header-left .home-button:hover {
  color: var(--text);
  background: rgba(150, 172, 198, 0.06);
  border-color: transparent;
  box-shadow: none;
}
/* active section reads as a lit indicator, not a filled pill */
.header-left .home-button[aria-current='page'] {
  color: var(--accent-strong);
  background: var(--accent-soft);
}
.header-left .home-button[aria-current='page']::after {
  content: '';
  position: absolute;
  left: 9px; right: 9px; bottom: 3px;
  height: 2px;
  background: var(--accent);
  box-shadow: 0 0 7px var(--accent-glow);
}

.controls {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  justify-content: flex-end;
  min-width: 0;
}

/* A header carrying page controls holds three unshrinkable blocks — brand, nav,
   controls — that together need about 1500px. Rather than pick a breakpoint to
   move the controls at, let the row wrap: the controls take the second line the
   moment they stop fitting on the first, at whatever width that happens to be.
   Without this the two blocks simply overlapped, and the stream nav printed
   underneath the URL field. */
header:has(.controls) { flex-wrap: wrap; row-gap: 8px; padding-top: 8px; padding-bottom: 8px; }
header .controls { flex: 1 1 auto; }

/* plain secondary header used by stream / media / games */
.page-header { min-height: 58px; }

/* dashboard header — same 58px bezel as every other page. The ticker tape used
   to live inside it, which forced the header to 88px here and nowhere else, and
   at tablet width squeezed the widget into a 60px sliver clipped mid-number. It
   is a full-width reading, so it gets its own strip under the header. */
.ticker-strip {
  flex: 0 0 auto;
  /* The widget lays out at 104px: symbol row, price row, then TradingView's
     attribution line. 80px is the first height that shows the symbol and the
     price whole — below it the price row is sliced, which is how it looked
     inside the old 88px header too. The attribution stays clipped, as it was
     before; showing it costs another 30px of chart height. */
  height: 80px;
  margin-bottom: 14px;
  overflow: hidden;
  display: flex;
  align-items: center;
  padding: 2px;
  border-radius: var(--r-md);
  border: 1px solid var(--border);
  background: var(--well);
  box-shadow: inset 0 2px 6px rgba(0, 0, 0, 0.5);
}
.ticker-strip tv-ticker-tape { flex: 1; border-radius: var(--r-sm); overflow: hidden; }

tv-ticker-tape { width: 100%; height: 100%; }

/* ---------- panel (card) ---------- */
.card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  padding: 16px;
  position: relative;
  box-shadow: var(--bezel), var(--shadow-sm);
  transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
.card:hover { border-color: var(--border-strong); box-shadow: var(--bezel), var(--shadow-md); }
/* only panels that actually carry a legend plate go flush at the top */
.card:has(> .home-box-title:first-child), .card:has(> h2:first-child) { padding-top: 0; }

/* legend plate: full-bleed etched label strip across the top of a panel */
.card > .home-box-title:first-child,
.card > h2:first-child {
  display: flex;
  align-items: center;
  gap: 9px;
  margin: 0 -16px 14px;
  padding: 10px 16px;
  text-align: left;
  background: var(--well);
  border-bottom: 1px solid var(--border);
  border-radius: var(--r-lg) var(--r-lg) 0 0;
}
/* .center is overridden deliberately: plates align left so lamps form a column */
.card > .home-box-title.center:first-child { text-align: left; justify-content: flex-start; }

/* ---------- the signature: annunciator lamp ----------
   A lamp appears only on panels that actually carry live data, and lights only
   once that data has arrived. Panels with nothing to report (quick links, the
   race card) get a plain etched plate — no lamp, so a dark lamp always means
   "waiting on data" and never "nothing here". */
.card:has(.market-index-list) > .home-box-title:first-child::before,
.card:has(.news-container) > .home-box-title:first-child::before,
.card:has(.weather-box) > .home-box-title:first-child::before,
.card:has(.spacewx-scale) > .home-box-title:first-child::before,
.card:has(.hn-container) > .home-box-title:first-child::before,
.card:has(.quake-container) > .home-box-title:first-child::before,
.card:has(.subs-grid) > .home-box-title:first-child::before {
  content: '';
  flex: 0 0 7px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--accent-dim);
  box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.55);
  opacity: 0.5;
}
/* lit: the panel is holding real, current data */
/* `.quake-empty` counts as arrived data on purpose: "nothing above M4.5 today" is
   a reading the feed actually returned, so the panel is reporting, not waiting. */
/* `.last` counts for the same reason, and leaving it out was a bug: it lit only on
   `.live`, so the lamp went dark every evening purely because the market had shut,
   while the panel sat there holding three real quotes and a sentiment reading. That
   is the exact failure the status rail refuses to carry a lamp to avoid - a dark
   lamp has to keep meaning "waiting on data". Only `.na` is waiting; `.last` is a
   quote that arrived and is simply the closing one. */
.card:has(.market-index-state.live) > .home-box-title:first-child::before,
.card:has(.market-index-state.last) > .home-box-title:first-child::before,
.card:has(.news-list) > .home-box-title:first-child::before,
.card:has(.weather-day) > .home-box-title:first-child::before,
.card:has(.spacewx-scale.is-set) > .home-box-title:first-child::before,
.card:has(.quake-list) > .home-box-title:first-child::before,
.card:has(.hn-list) > .home-box-title:first-child::before,
.card:has(.quake-empty) > .home-box-title:first-child::before,
.card:has(.subs-card) > .home-box-title:first-child::before {
  background: var(--accent);
  opacity: 1;
  box-shadow: 0 0 8px var(--accent-glow), 0 0 2px var(--accent);
  /* strikes once when the data lands, then settles into the idle breath */
  animation: lamp-strike 0.55s var(--ease) both, lamp-breathe 3.4s var(--ease) 0.55s infinite;
}

@keyframes lamp-breathe {
  0%, 100% { box-shadow: 0 0 8px var(--accent-glow), 0 0 2px var(--accent); }
  50% { box-shadow: 0 0 12px var(--accent-glow), 0 0 3px var(--accent); }
}

/* ---------- boot sequence ----------
   The console powers on: panels rise in a left-to-right cascade, then each lamp
   strikes as its data actually lands. The stagger is short on purpose — this is
   one orchestrated moment at load, not an effect the page keeps performing.

   Every element's resting state is the visible one; the animation only supplies
   a starting offset via `both`. That way, if animation is disabled the content
   is still shown rather than stranded at opacity 0. */
@keyframes boot-in {
  from { opacity: 0; transform: translateY(9px); }
  to { opacity: 1; transform: none; }
}

/* a lamp does not fade up — it overshoots, then settles */
@keyframes lamp-strike {
  0% { background: var(--accent-dim); box-shadow: inset 0 0 0 1px rgba(0, 0, 0, 0.55); }
  35% { background: #fff4d9; box-shadow: 0 0 18px var(--accent), 0 0 6px #fff; }
  100% { background: var(--accent); box-shadow: 0 0 8px var(--accent-glow), 0 0 2px var(--accent); }
}

header { animation: boot-in 0.42s var(--ease) both; }
body.home-page .status-rail { animation: boot-in 0.42s var(--ease) 0.08s both; }
body.home-page .home-search-wrap { animation: boot-in 0.42s var(--ease) 0.12s both; }

.home-main .card { animation: boot-in 0.45s var(--ease) both; }
.home-news-col .card:nth-child(1) { animation-delay: 0.18s; }
.home-news-col .card:nth-child(2) { animation-delay: 0.24s; }
.home-links-col .card:nth-child(1) { animation-delay: 0.22s; }
.home-links-col .card:nth-child(2) { animation-delay: 0.28s; }
.home-links-col .card:nth-child(3) { animation-delay: 0.34s; }
.home-stats-col .card:nth-child(1) { animation-delay: 0.26s; }
.home-stats-col .card:nth-child(2) { animation-delay: 0.32s; }
.home-stats-col .card:nth-child(3) { animation-delay: 0.38s; }
/* the bottom bezel seats last, closing the chassis. It waits out the home page's
   panel cascade; on pages with no cascade it follows straight after the header. */
.site-footer { animation: boot-in 0.42s var(--ease) 0.14s both; }
.home-main ~ .site-footer { animation-delay: 0.38s; }

/* ---------- home search ---------- */
/* the search field is the hero: a wide machined slot, set to the news column's
   width so its edges line up with the panel below it */
.home-search-wrap {
  width: 100%;
  max-width: 730px;
  margin: 0 auto 30px;
  padding: 0;
  border-radius: var(--r-lg);
  border: 1px solid var(--border-strong);
  background: var(--well);
  box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.55), var(--bezel);
  transition: border-color 0.18s ease, box-shadow 0.18s ease;
}
.home-search-wrap:focus-within {
  border-color: var(--accent-border);
  box-shadow: inset 0 3px 8px rgba(0, 0, 0, 0.55), 0 0 0 3px var(--accent-soft), 0 0 18px rgba(255, 176, 0, 0.12);
}
.home-search-form { margin: 0; width: 100%; display: flex; justify-content: center; }
.home-search-input {
  width: 100%;
  height: 52px;
  border: 0;
  border-radius: var(--r-lg);
  background: transparent;
  color: var(--text);
  font-family: var(--font-mono);
  font-size: 0.92rem;
  font-weight: 400;
  letter-spacing: 0.02em;
  padding: 0 18px;
  box-shadow: none;
}
.home-search-input::placeholder { color: var(--text-faint); }
.home-search-input:focus { box-shadow: none; }

.home-columns {
  display: flex;
  gap: 16px;
  align-items: flex-start;
  justify-content: center;
  width: 100%;
  margin-bottom: 40px;
}
.home-links-col, .home-stats-col { flex: 1; display: flex; flex-direction: column; gap: 16px; max-width: 300px; }
/* width is fixed by design - see the layout note; the gap only matters now that the
   column carries a second panel under the news card */
.home-news-col { flex: 0 0 730px; width: 730px; max-width: 730px; display: flex; flex-direction: column; gap: 16px; min-height: 0; align-self: flex-start; }

.home-box-title { margin: 0 0 12px; }

/* ---------- quick links ---------- */
/* Sub-legend: subordinate to the panel's legend plate on purpose - no full-bleed
   background, no lamp, just an etched divider. If these read as plates in their
   own right, merging the three link panels would have bought nothing. */
.links-sublegend {
  font-family: var(--font-legend);
  font-size: 0.66rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.16em;
  color: var(--text-faint);
  margin: 0 0 8px;
  padding-top: 12px;
  border-top: 1px solid var(--border);
}
.links-sublegend:first-of-type { padding-top: 0; border-top: 0; }
.quick-links-list + .links-sublegend { margin-top: 14px; }
.quick-links-list { list-style: none; padding: 0; margin: 0; display: grid; grid-template-columns: repeat(2, 1fr); gap: 6px; width: 100%; }
.quick-links-list li { list-style: none; padding: 0; margin: 0; display: flex; }
.quick-links-list li a {
  --ql-accent: var(--text-muted);
  position: relative;
  flex: 1;
  display: flex; align-items: center;
  padding: 9px 10px 9px 16px;
  background: var(--surface-raised);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  color: var(--text-muted);
  text-decoration: none;
  text-align: left;
  font-weight: 500;
  font-size: 0.78rem;
  letter-spacing: 0.01em;
  transition: border-color 0.16s ease, background 0.16s ease, color 0.16s ease;
}
/* each link carries its service's colour as a coded edge marker */
.quick-links-list li a::before {
  content: '';
  position: absolute;
  left: 0; top: 0; bottom: 0;
  width: 3px;
  background: var(--ql-accent);
  opacity: 0.55;
  transition: opacity 0.16s ease;
}
.quick-links-list li a:hover {
  border-color: var(--border-strong);
  background: var(--surface-hover);
  color: var(--text);
}
.quick-links-list li a:hover::before { opacity: 1; }

.quick-links-list a[href*="youtube.com"] { --ql-accent: #ff4a5e; }
.quick-links-list a[href*="twitch.tv"] { --ql-accent: #9d7dff; }
.quick-links-list a[href*="netflix.com"] { --ql-accent: #e9515f; }
.quick-links-list a[href*="primevideo.com"] { --ql-accent: #5ca8ff; }
.quick-links-list a[href*="hbomax.com"], .quick-links-list a[href*="max.com"] { --ql-accent: #7f7dff; }
.quick-links-list a[href*="chatgpt.com"] { --ql-accent: #45c69b; }
.quick-links-list a[href*="gemini.google.com"] { --ql-accent: #7ca5ff; }
.quick-links-list a[href*="claude.ai"] { --ql-accent: #f2a15f; }
.quick-links-list a[href*="reddit.com"] { --ql-accent: #ff6b45; }
.quick-links-list a[href*="wikipedia.org"] { --ql-accent: #9aa8bf; }
.quick-links-list a[href*="tradingview.com"] { --ql-accent: #4ca9ff; }
.quick-links-list a[href*="github.com"] { --ql-accent: #8da0be; }
.quick-links-list a[href*="proton.me"] { --ql-accent: #8c7dff; }
.quick-links-list a[href*="gmail.com"] { --ql-accent: #ff7d73; }
.quick-links-list a[href*="amazon.com"] { --ql-accent: #ffb45c; }
.quick-links-list a[href*="finance.yahoo.com"] { --ql-accent: #8d6dff; }
.quick-links-list a[href*="coinmarketcap.com"] { --ql-accent: #5ea4ff; }
.quick-links-list a[href*="bloomberg.com"] { --ql-accent: #5fc7d8; }
.quick-links-list a[href*="reuters.com"] { --ql-accent: #f2ad63; }

/* ---------- news ---------- */
.news-card { padding: 14px; display: flex; flex-direction: column; min-height: 0; }
.news-card > .home-box-title:first-child { margin: 0 -14px 12px; padding: 10px 14px; }
.news-plate-label { flex: 1 1 auto; min-width: 0; }
/* staleness readout doubles as the refresh control: the feed caches for an hour,
   so the timestamp is the thing you want to act on when it looks old */
/* Reads as the same stamp every other panel carries — it is the same reading,
   and boxing this one made the news panel look like it wore a control the others
   were missing. It is still a button; the underline on hover is what says so. */
.news-asof {
  flex: 0 0 auto;
  height: auto;
  padding: 0;
  border: 0;
  border-bottom: 1px solid transparent;
  border-radius: 0;
  background: transparent;
  color: var(--text-faint);
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  cursor: pointer;
  box-shadow: none;
}
.news-asof:hover {
  border-bottom-color: var(--accent-border);
  color: var(--accent-strong);
  background: transparent;
  box-shadow: none;
}
.news-asof:active { background: transparent; box-shadow: none; }
.news-asof.is-loading { opacity: 0.5; cursor: progress; }
.news-controls { display: flex; gap: 6px; margin-bottom: 12px; justify-content: flex-start; flex-wrap: wrap; }
.news-controls .home-button { height: 32px; padding: 0 11px; font-size: 11px; }
.news-controls .home-button.active { border-color: var(--accent-border); background: var(--accent-soft); color: var(--accent-strong); }
.news-container, #news-container { text-align: left; overflow: visible; }
.news-list { padding-left: 0; margin: 0; list-style: none; }
/* text-align is explicit: the home column centres its children, so a status line
   left to inherit would sit centred and read as a heading rather than a note */
.news-status-note { margin: 0; padding: 2px 0; font-size: 0.78rem; color: var(--text-faint); text-align: left; }
.news-item-card {
  margin-bottom: 1px;
  padding: 11px 14px;
  border: 0;
  border-top: 1px solid var(--border);
  border-radius: 0;
  background: transparent;
  transition: background 0.14s ease;
}
.news-item-card:first-child { border-top: 0; }
.news-item-card:hover { background: var(--surface-raised); }
.news-item-link { color: var(--text); text-decoration: none; font-size: 0.98rem; line-height: 1.45; font-weight: 400; display: block; }
.news-item-link:hover { color: var(--accent-strong); }
.news-item-meta {
  font-size: 0.7rem;
  color: var(--text-faint);
  margin-top: 5px;
  font-family: var(--font-mono);
  letter-spacing: 0.02em;
  text-transform: uppercase;
}

/* ---------- info widgets (sentiment / clock / weather / nascar) ---------- */
.info-card { display: flex; flex-direction: column; gap: 2px; }
.info-value {
  font-family: var(--font-mono);
  font-size: 1.5rem;
  margin: 4px 0 6px;
  font-weight: 700;
  color: var(--accent-strong);
  line-height: 1.15;
  letter-spacing: -0.01em;
  font-variant-numeric: tabular-nums;
}

/* ---------- sentiment gauge ----------
   A recessed scale with an index mark, not a filled bar. The track carries no
   colour of its own: the reading above it is already coloured by zone. */
.sentiment-gauge { margin: 2px 0 10px; }
.sentiment-track {
  position: relative;
  height: 10px;
  background: var(--well);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.55);
  overflow: hidden;
}
.sentiment-zone {
  position: absolute;
  top: 0;
  bottom: 0;
  width: 1px;
  background: var(--border-strong);
}
/* the mark stays hidden until a real score lands, so an unlit gauge always
   means "waiting on data" - the same contract the annunciator lamps keep */
.sentiment-mark {
  position: absolute;
  top: -1px;
  bottom: -1px;
  width: 3px;
  margin-left: -1.5px;
  background: var(--accent);
  box-shadow: 0 0 7px var(--accent-glow);
  opacity: 0;
  transition: left 0.5s var(--ease), opacity 0.3s ease;
}
.sentiment-mark.is-set { opacity: 1; }
.sentiment-scale {
  display: flex;
  justify-content: space-between;
  margin-top: 4px;
  font-family: var(--font-legend);
  font-size: 0.6rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--text-faint);
}

/* Title text on a legend plate that also carries a stamp: takes the slack so the
   stamp is pushed to the right edge. Same rule the per-panel *-plate-label
   classes carry; those predate it and stay for the markup already using them. */
.plate-label { flex: 1 1 auto; min-width: 0; }

/* ---------- panel freshness stamp ----------
   Every panel that pulls from the network carries one, on the legend plate beside
   its title - the same place the news card has always put its own. It reads as
   plate engraving, not content: faint, mono, matching .hn-scope and .quake-scope
   so a title bar never carries two competing type treatments.

   `.is-stale` lights it amber: the panel is showing the last good reading and
   could not confirm it, which has to be legible at a glance. The plate is a flex
   row, so `margin-left: auto` pins the stamp right whether or not the title also
   carries a scope label. */
.panel-asof {
  flex: 0 0 auto;
  margin-left: auto;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-variant-numeric: tabular-nums;
  color: var(--text-faint);
}
/* ---------- panel scope caption ----------
   Two panels qualify their reading ("Hacker News · Top", "N. America · M2.5+ · 24h").
   That used to ride the legend plate beside the title and the stamp, but three
   items never fit one row: Seismic has 266px of plate and needs about 378px, so it
   wrapped and those two legends stood a row or two taller than every other plate on
   the page. The scope now sits directly under the plate instead. Every legend is
   title-plus-stamp on a single row, and the qualifier is still the first thing read
   beneath it. */
.panel-scope {
  display: block;
  margin: -4px 0 10px;
  text-align: left;
}

/* Lets the plate become a multi-row legend: it carries the deliberate break above
   on the scoped panels, and on a title-plus-stamp panel it still absorbs a long
   title rather than crushing the stamp against it. */
.home-box-title:has(.panel-asof) { flex-wrap: wrap; row-gap: 3px; }
.panel-asof.is-stale { color: var(--warn); }
.panel-asof.is-stale::after { content: ' · stale'; }

/* ---------- shared panel well ----------
   Label/value rows recessed into a panel face. The NASCAR card predates this and
   keeps its own copy of the shape; anything new uses these. */
.panel-well { display: grid; gap: 1px; margin-top: 12px; background: var(--border); border: 1px solid var(--border); border-radius: var(--r-sm); overflow: hidden; }
.panel-line { display: grid; grid-template-columns: 1fr auto; align-items: baseline; gap: 8px; padding: 8px 10px; background: var(--well); text-align: left; }
.panel-label { font-family: var(--font-legend); font-size: 0.66rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-faint); font-weight: 700; }
.panel-value { font-family: var(--font-mono); font-size: 0.78rem; color: var(--text); font-weight: 500; font-variant-numeric: tabular-nums; justify-self: end; white-space: nowrap; }

/* ---------- space weather ----------
   The Kp reading and its condition share a baseline: the number is the instrument,
   the chip is the legend under it. */
.spacewx-head { display: flex; align-items: baseline; justify-content: space-between; gap: 8px; }
.spacewx-state {
  flex: 0 0 auto;
  padding: 2px 7px;
  border: 1px solid var(--border-strong);
  border-radius: var(--r-sm);
  font-family: var(--font-legend);
  font-size: 0.6rem; font-weight: 700; letter-spacing: 0.1em; text-transform: uppercase;
  color: var(--text-faint);
  white-space: nowrap;
}
.spacewx-state.calm { color: var(--success); border-color: color-mix(in srgb, var(--success) 45%, transparent); background: color-mix(in srgb, var(--success) 12%, transparent); }
.spacewx-state.active { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 40%, transparent); background: color-mix(in srgb, var(--warn) 12%, transparent); }
.spacewx-state.storm { color: var(--danger); border-color: color-mix(in srgb, var(--danger) 45%, transparent); background: color-mix(in srgb, var(--danger) 12%, transparent); }
.spacewx-state.na { color: var(--text-faint); border-color: var(--border-strong); background: transparent; }

/* Segmented, not round: the circular lamp is the site's one signature and stays
   unique to panel legends. These are machined cells in a recessed track. */
.spacewx-scale {
  display: grid;
  grid-template-columns: repeat(9, minmax(0, 1fr));
  gap: 1px;
  height: 14px;
  margin-top: 2px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.55);
  overflow: hidden;
}
.spacewx-cell { background: var(--well); transition: background 0.3s var(--ease); }
.spacewx-cell.lit { background: var(--accent); }
/* Kp 5 is the G1 threshold - storm cells fill red, and the boundary is scored */
.spacewx-cell.lit.storm { background: var(--danger); }
.spacewx-cell:nth-child(5) { box-shadow: inset 1px 0 0 var(--accent-dim); }
.spacewx-legend {
  display: flex;
  justify-content: space-between;
  margin-top: 4px;
  font-family: var(--font-legend);
  font-size: 0.6rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--text-faint);
}

/* ---------- tech pulse ----------
   Two-up cells, deliberately not the news list's full-width rows: this panel sits
   directly beneath that list and needs to read as a different instrument. */
.hn-card { padding: 14px; }
.hn-card > .home-box-title:first-child { margin: 0 -14px 12px; padding: 10px 14px; }
.hn-scope {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
}
.hn-status-note { margin: 0; padding: 2px 0; font-size: 0.78rem; color: var(--text-faint); text-align: left; }
.hn-list {
  list-style: none; margin: 0; padding: 0;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  overflow: hidden;
}
.hn-list li { margin: 0; padding: 0; display: flex; }
.hn-cell {
  flex: 1;
  display: grid;
  grid-template-columns: 46px minmax(0, 1fr);
  align-items: start;
  gap: 10px;
  padding: 9px 11px;
  background: var(--well);
  /* opts out of the page's centred text - see the note on .quake-link */
  text-align: left;
  text-decoration: none;
  transition: background 0.14s ease;
}
.hn-cell:hover { background: var(--surface-raised); }
/* score reads as a gauge, so it keeps the mono/tabular treatment numbers get
   everywhere else on the site */
.hn-score {
  font-family: var(--font-mono);
  font-size: 0.82rem;
  font-weight: 700;
  line-height: 1.3;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.hn-score.hot { color: var(--accent-strong); }
.hn-title {
  display: block;
  font-size: 0.78rem;
  line-height: 1.35;
  color: var(--text);
  /* two lines, then clip - every cell in the grid stays the same height */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.hn-cell:hover .hn-title { color: var(--accent-strong); }
/* One row, and the domain is the only part allowed to lose characters to it: the
   count and the age are short and fixed, so they always survive. */
.hn-meta {
  display: flex;
  margin-top: 4px;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.05em;
  color: var(--text-faint);
  white-space: nowrap;
  overflow: hidden;
}
.hn-meta-domain { min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.hn-meta-rest { flex: 0 0 auto; }

/* ---------- seismic watch ---------- */
/* the threshold rides the legend plate: a short list only reads as "quiet" if you
   know what was being watched for */
.quake-scope {
  font-family: var(--font-mono);
  font-size: 0.62rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}
.quake-status, .quake-empty { margin: 0; padding: 2px 0; font-size: 0.76rem; line-height: 1.4; color: var(--text-faint); text-align: left; }
.quake-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 1px; background: var(--border); border: 1px solid var(--border); border-radius: var(--r-sm); overflow: hidden; }
.quake-list li { margin: 0; padding: 0; }
.quake-link {
  display: grid;
  grid-template-columns: 34px 1fr;
  align-items: start;
  gap: 10px;
  padding: 8px 10px;
  background: var(--well);
  text-decoration: none;
  /* .welcome sets text-align:center for the page; any panel holding a list has to
     opt back out or its columns stop sharing a left edge */
  text-align: left;
  transition: background 0.14s ease;
}
.quake-link:hover { background: var(--surface-raised); }
.quake-mag {
  font-family: var(--font-mono);
  font-size: 0.95rem;
  font-weight: 700;
  line-height: 1.25;
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}
.quake-mag.moderate { color: var(--warn); }
.quake-mag.major { color: var(--danger); }
.quake-place { display: block; font-size: 0.74rem; line-height: 1.3; color: var(--text); }
.quake-link:hover .quake-place { color: var(--accent-strong); }
.quake-time {
  display: block;
  margin-top: 3px;
  font-family: var(--font-mono);
  font-size: 0.62rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--text-faint);
  font-variant-numeric: tabular-nums;
}

/* data wells: readings sit recessed into the panel */
.market-index-list { margin-top: 12px; display: grid; gap: 1px; width: 100%; background: var(--border); border: 1px solid var(--border); border-radius: var(--r-sm); overflow: hidden; }
.market-index-row {
  display: grid;
  grid-template-columns: 1.1fr 0.6fr 1.2fr 1fr;
  align-items: center; justify-items: center; gap: 6px;
  padding: 9px 8px;
  background: var(--well);
  font-size: 0.76rem;
}
.market-index-name { color: var(--text); font-weight: 600; font-size: 0.78rem; justify-self: start; }
.market-index-state {
  display: inline-flex; align-items: center; justify-content: center;
  width: 26px; padding: 2px; border-radius: var(--r-sm);
  border: 1px solid var(--border-strong);
  font-family: var(--font-legend);
  font-size: 0.56rem; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase;
}
.market-index-state.live { color: var(--success); border-color: color-mix(in srgb, var(--success) 45%, transparent); background: color-mix(in srgb, var(--success) 12%, transparent); }
.market-index-state.last { color: var(--warn); border-color: color-mix(in srgb, var(--warn) 40%, transparent); background: color-mix(in srgb, var(--warn) 12%, transparent); }
.market-index-state.na { color: var(--text-faint); border-color: var(--border-strong); background: transparent; }
.market-index-price { color: var(--text-muted); font-weight: 400; font-family: var(--font-mono); font-size: 0.74rem; white-space: nowrap; font-variant-numeric: tabular-nums; }
.market-index-change { font-family: var(--font-mono); font-weight: 700; font-size: 0.74rem; white-space: nowrap; font-variant-numeric: tabular-nums; justify-self: end; }
.market-index-change.up { color: var(--success); }
.market-index-change.down { color: var(--danger); }
.market-index-change.flat { color: var(--text-faint); }

.weather-box { text-align: left; width: 100%; }
.weather-temp {
  font-family: var(--font-mono);
  font-size: 2.1rem; margin: 0; font-weight: 700;
  color: var(--accent-strong); line-height: 1.05;
  font-variant-numeric: tabular-nums;
}
.weather-range { font-family: var(--font-mono); font-size: 0.8rem; margin: 6px 0 4px; color: var(--text-muted); font-weight: 400; font-variant-numeric: tabular-nums; }
.weather-desc { font-size: 0.86rem; margin: 0; color: var(--text); }
.weather-city { font-family: var(--font-legend); font-size: 0.72rem; margin: 4px 0 0; color: var(--text-faint); text-transform: uppercase; letter-spacing: 0.12em; }
/* next six hours — a horizontal readout strip above the weekly grid */
.weather-hourly {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 1px;
  margin-top: 12px;
  background: var(--border);
  border: 1px solid var(--border);
  border-radius: var(--r-sm);
  overflow: hidden;
}
.weather-hourly:empty { display: none; }
.weather-hour {
  background: var(--well);
  padding: 7px 2px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  min-width: 0;
}
.weather-hour-time {
  font-family: var(--font-legend);
  font-size: 0.62rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--text-faint);
  white-space: nowrap;
}
.weather-hour-icon { font-size: 0.9rem; line-height: 1; }
.weather-hour-temp {
  font-family: var(--font-mono);
  font-size: 0.72rem;
  font-weight: 700;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

.weather-summary:empty { display: none; }
.weather-summary { display: grid; grid-template-columns: 1fr 1fr; gap: 1px; margin-top: 12px; background: var(--border); border: 1px solid var(--border); border-radius: var(--r-sm); overflow: hidden; padding: 0; text-align: left; }
.weather-day { border: 0; background: var(--well); border-radius: 0; padding: 8px 9px; display: flex; flex-direction: column; gap: 3px; min-height: 64px; }
.weather-day-name { font-family: var(--font-legend); font-size: 0.72rem; font-weight: 700; color: var(--text); text-transform: uppercase; letter-spacing: 0.1em; }
.weather-day-cond { font-size: 0.7rem; color: var(--text-muted); line-height: 1.25; }
.weather-day-temps { font-family: var(--font-mono); font-size: 0.7rem; color: var(--text); font-weight: 500; font-variant-numeric: tabular-nums; }

/* the race name owns the gap below it; stacking a second margin here made this the
   widest gap in the card */
.nascar-meta { display: grid; gap: 1px; margin-top: 0; background: var(--border); border: 1px solid var(--border); border-radius: var(--r-sm); overflow: hidden; }
.nascar-tabs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: 2px; margin: 2px 0 10px; }
.nascar-tab-btn { height: 32px; padding: 0 5px; font-size: 10px; letter-spacing: 0.06em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; border-radius: var(--r-sm); }
.nascar-tab-btn.active { border-color: var(--accent-border); background: var(--accent-soft); color: var(--accent-strong); }
.nascar-line { display: grid; grid-template-columns: 74px 1fr; align-items: baseline; gap: 8px; padding: 8px 10px; border: 0; border-radius: 0; background: var(--well); text-align: left; }
/* No reserved second line. `min-height: 2.7em` held two lines' worth so the card
   would not resize when a longer race name loaded on another series tab, but most
   names are one line, so the steady state was a visible band of empty plate under
   the title. The card resizes on the odd two-line name instead - the same way every
   other content-sized panel on this page behaves. */
.nascar-race-name { font-family: var(--font-sans); font-size: 1rem; margin: 0 0 12px; line-height: 1.35; color: var(--text); font-weight: 500; letter-spacing: 0; text-wrap: balance; }
.nascar-label { font-family: var(--font-legend); font-size: 0.66rem; letter-spacing: 0.14em; text-transform: uppercase; color: var(--text-faint); font-weight: 700; }
.nascar-value { font-size: 0.8rem; color: var(--text); font-weight: 500; }

/* ---------- generic welcome wrapper (home page keeps it full-width via .home-welcome*) ---------- */
.welcome { display: flex; flex-direction: column; align-items: center; padding: 24px 32px; min-height: 0; flex: 1; text-align: center; }
.home-welcome { width: 100%; padding: 0 0 24px; }
.home-welcome-inner { max-width: none; width: 100%; }

/* ---------- dashboard grid ---------- */
.tradingview-grid {
  display: none;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  grid-auto-rows: 1fr;
  gap: 8px;
  height: 100%;
  width: 100%;
  padding: 2px;
  position: relative;
}
.tradingview-grid.show { display: grid; }

.tv-panel {
  display: flex;
  flex-direction: column;
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  background: var(--surface);
  height: 100%;
  overflow: hidden;
  box-shadow: var(--bezel), var(--shadow-sm);
  transition: border-color 0.2s var(--ease), box-shadow 0.2s var(--ease);
}
.tv-panel:hover { border-color: var(--border-strong); box-shadow: var(--bezel), var(--shadow-md); }

.tv-panel-header {
  display: flex;
  gap: 6px;
  align-items: center;
  padding: 7px;
  min-height: 50px;
  background: var(--well);
  border-bottom: 1px solid var(--border);
  flex: 0 0 auto;
}
.tv-panel-header input {
  flex: 1;
  min-width: 0;
  height: 38px;
  font-size: 13px;
}
.tv-panel-header button { min-width: 86px; font-size: 11px; }
.tv-panel-header .dashboard-theater-toggle-btn { min-width: 102px; }
.tv-panel-header .ticker-add-btn { min-width: 90px; }

.tv-panel-header .ticker-select-trigger {
  min-width: 168px;
  max-width: 230px;
  height: 38px;
  justify-content: flex-start;
  text-align: left;
  position: relative;
  padding: 0 28px 0 11px;
  font-size: 11px;
}
.tv-panel-header .ticker-select-trigger::after {
  content: '▾';
  position: absolute; right: 10px; top: 50%; transform: translateY(-50%);
  color: var(--text-faint); font-size: 11px; pointer-events: none;
}
.tv-panel-header .ticker-select-trigger:focus,
.tv-panel-header .ticker-select-trigger[aria-expanded='true'] {
  border-color: var(--accent-border);
  box-shadow: 0 0 0 3px var(--accent-soft);
}

.tv-widget {
  flex: 1;
  min-height: 280px;
  height: 100%;
  background: var(--bg);
  position: relative;
  overflow: hidden;
}
.tradingview-widget-container { width: 100% !important; height: 100% !important; min-height: 100% !important; display: block !important; background-color: var(--bg) !important; }
.tv-widget iframe {
  position: relative !important; display: block !important;
  width: 100% !important; height: 100% !important; min-height: 100% !important;
  visibility: visible !important; opacity: 1 !important; border: 0 !important;
  background: transparent !important; z-index: 1 !important;
}

.ticker-overlay-menu {
  position: fixed;
  z-index: 48;
  min-width: 360px;
  max-width: 520px;
  max-height: calc((var(--ticker-visible-rows, 6) * 38px) + 16px);
  overflow-y: auto;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 4px;
  padding: 8px;
  border-radius: var(--r-md);
  border: 1px solid var(--border-strong);
  background: var(--surface-raised);
  box-shadow: var(--shadow-lg);
}
.ticker-overlay-menu[hidden] { display: none; }
.ticker-overlay-group-header { grid-column: 1 / -1; padding: 4px 2px 2px; color: var(--text-faint); font-family: var(--font-legend); font-size: 10px; font-weight: 700; letter-spacing: 0.16em; text-transform: uppercase; }
.ticker-overlay-option {
  width: 100%; height: 32px; min-height: 32px;
  border: 1px solid var(--border); border-radius: var(--r-sm);
  background: var(--well);
  color: var(--text);
  font-family: var(--font-mono); font-size: 12px; font-weight: 400; text-align: left; padding: 0 10px;
  cursor: pointer;
  letter-spacing: 0;
  text-transform: none;
}
.ticker-overlay-row { display: grid; grid-template-columns: minmax(0, 1fr) 30px; align-items: center; gap: 4px; width: 100%; }
.ticker-overlay-row .ticker-overlay-option { min-width: 0; }
.ticker-overlay-delete {
  width: 30px; min-width: 30px; height: 32px; border-radius: var(--r-sm);
  border: 1px solid color-mix(in srgb, var(--danger) 40%, transparent);
  background: color-mix(in srgb, var(--danger) 12%, var(--well));
  color: var(--danger); font-size: 12px; font-weight: 700; cursor: pointer;
}
.ticker-overlay-delete:hover { border-color: var(--danger); background: color-mix(in srgb, var(--danger) 20%, var(--well)); }
.ticker-overlay-option:hover { border-color: var(--accent-border); color: var(--accent-strong); }
.ticker-overlay-option.is-active { border-color: var(--accent-border); background: var(--accent-soft); color: var(--accent-strong); }

.dashboard-theater-menu {
  position: fixed; z-index: 42;
  display: flex; flex-direction: column; gap: 4px;
  min-width: 170px; padding: 8px;
  border-radius: var(--r-md);
  border: 1px solid var(--border-strong);
  background: var(--surface-raised);
  box-shadow: var(--shadow-lg);
}
.dashboard-theater-menu[hidden] { display: none; }
.dashboard-theater-menu-btn, .dashboard-theater-menu-btn.home-button {
  width: 100%; height: 34px; padding: 0 10px; border-radius: var(--r-sm);
  justify-content: flex-start; font-size: 11px;
}

/* shared fixed exit control for any theater/fullscreen mode */
.dashboard-theater-exit, .dashboard-theater-exit.home-button,
.stream-theater-exit, .stream-theater-exit.home-button,
.subs-theater-exit, .subs-theater-exit.home-button,
.rpg-theater-exit, .rpg-theater-exit.home-button {
  position: fixed; top: 16px; left: 50%; transform: translateX(-50%);
  z-index: 36; display: none;
  background: rgba(10, 14, 19, 0.82);
  border-color: var(--border-strong);
  backdrop-filter: blur(10px);
  color: var(--text);
}

body.dashboard-theater-mode { background: #05070a; padding: 0; }
body.dashboard-theater-mode > .page { margin: 0; padding: 0; width: 100vw !important; max-width: none !important; height: 100dvh; }
body.dashboard-theater-mode > .page > .dashboard-header { display: none; }
body.dashboard-theater-mode .main { margin: 0; padding: 0; overflow: visible; }
body.dashboard-theater-mode .tradingview-grid { position: fixed; inset: 0; z-index: 30; gap: 0; padding: 0; background: #05070a; }
body.dashboard-theater-scope-panel .tradingview-grid { display: block !important; }
body.dashboard-theater-scope-panel .tv-panel { display: none; }
body.dashboard-theater-scope-panel .tv-panel.dashboard-theater-target { display: flex; width: 100vw; height: 100dvh; border-radius: 0; }
body.dashboard-theater-mode .tv-panel, body.dashboard-theater-mode .tv-widget { border-radius: 0; }
body.dashboard-theater-mode .dashboard-theater-exit,
body.stream-theater-mode .stream-theater-exit,
body.subs-theater-mode .subs-theater-exit {
  display: inline-flex; opacity: 0; pointer-events: none;
  transform: translateX(-50%) translateY(-10px);
  transition: opacity 0.18s ease, transform 0.18s ease;
}
body.dashboard-theater-mode .tradingview-grid:hover .dashboard-theater-exit,
body.dashboard-theater-mode .tradingview-grid:focus-within .dashboard-theater-exit,
body.stream-theater-mode .stream-container:hover .stream-theater-exit,
body.stream-theater-mode .stream-container:focus-within .stream-theater-exit,
body.subs-theater-mode .subs-player:hover .subs-theater-exit,
body.subs-theater-mode .subs-player:focus-within .subs-theater-exit {
  opacity: 1; pointer-events: auto; transform: translateX(-50%) translateY(0);
}

/* ---------- dropdown (streamer menu base — detail in stream-inline.css) ---------- */
.dropdown { position: relative; display: inline-block; }
.dropdown-content {
  display: none; position: absolute; right: 0; top: 110%;
  background: var(--surface-raised);
  border: 1px solid var(--border-strong);
  border-radius: var(--r-md);
  width: max-content; min-width: 180px;
  z-index: 20; padding: 8px;
  box-shadow: var(--shadow-lg);
}
.dropdown:hover .dropdown-content, .dropdown:focus-within .dropdown-content, .dropdown.is-open .dropdown-content { display: block; }
.dropdown.force-closed .dropdown-content { display: none !important; }
.dropdown-content a { display: block; padding: 9px 11px; text-decoration: none; color: var(--text); border-radius: var(--r-sm); font-size: 13px; }
.dropdown-content a:hover { background: var(--accent-soft); color: var(--accent-strong); }

/* ---------- footer: the bottom bezel of the chassis ----------
   Same plate construction as the header, lit from the same source above (180deg),
   with its amber filament on the top edge so the two hairlines face each other and
   bracket the console. */
.site-footer {
  display: flex;
  align-items: center;
  gap: 16px 22px;
  flex-wrap: wrap;
  min-height: 54px;
  margin-top: 28px;
  padding: 10px 14px;
  border: 1px solid var(--border);
  border-radius: var(--r-lg);
  background: linear-gradient(180deg, var(--surface-raised), var(--surface));
  box-shadow: var(--bezel), var(--shadow-md);
  position: relative;
  flex: 0 0 auto;
}
.site-footer::before {
  content: '';
  position: absolute;
  left: 12px; right: 12px; top: -1px;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--accent-border), transparent);
  opacity: 0.7;
}

/* the nameplate: stamped into the chassis rather than backlit, so it reads a stop
   dimmer than the live legends in the header and never competes with them */
.footer-plate {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  flex: 0 0 auto;
  color: var(--text-muted);
}
.footer-plate:hover { color: var(--text); }
.footer-plate .brand-mark { width: 16px; height: 16px; flex: 0 0 16px; }
.footer-plate-text { display: flex; flex-direction: column; gap: 1px; line-height: 1.15; }
.footer-plate-name {
  font-family: var(--font-legend);
  font-size: 11px;
  font-weight: 700;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  white-space: nowrap;
  text-shadow: 0 1px 0 rgba(0, 0, 0, 0.6);
}
.footer-plate-stamp {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--text-faint);
  letter-spacing: 0.04em;
}

/* only the destinations the top nav does not carry; pushed to the far edge so
   the bezel reads as a plate with a zone at each end */
.footer-nav {
  display: inline-flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 4px 20px;
  min-width: 0;
  margin-left: auto;
}
.footer-nav a {
  font-family: var(--font-legend);
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--text-faint);
  padding: 3px 0;
  border-bottom: 1px solid transparent;
  transition: color 0.16s ease, border-color 0.16s ease;
}
.footer-nav a:hover { color: var(--accent-strong); border-bottom-color: var(--accent-border); }

/* ---------- responsive ---------- */
@media (max-width: 1200px) {
  .home-columns { flex-direction: column; }
  .home-links-col, .home-news-col, .home-stats-col { max-width: none; }
  .home-news-col { flex: 1 1 auto; width: auto; }
  /* Home only: the stream and dashboard pages keep their own preserved widths. */
  body.home-page .page { width: 100%; max-width: none; }
  body.site-page { padding: 12px; }
}

@media (max-width: 900px) {
  .tradingview-grid { grid-template-columns: 1fr; }
  body.dashboard-page .page { width: 100%; max-width: 100%; }

  /* One column of four charts cannot share a locked 100vh: each panel was left
     with less height than its own header, so the charts were clipped away to
     nothing. Below this the page scrolls like every other page on the site and
     each chart gets a height it can actually be read at. */
  body.dashboard-page { height: auto; min-height: 100vh; overflow-y: auto; }
  body.dashboard-page .page { height: auto; min-height: calc(100vh - 36px); overflow: visible; }
  body.dashboard-page .main { overflow: visible; }
  .tradingview-grid { height: auto; grid-auto-rows: auto; }
  .tv-panel { height: auto; min-height: 380px; }
  /* The tape is 40 symbols scrolling past in a strip; at phone width it is a
     blur that costs a third of the viewport. The charts are the instrument. */
  .ticker-strip { display: none; }

  /* The four controls need ~470px on one line. Held on one line they did not
     shrink evenly - the input collapsed to an empty 24px box and Add Ticker was
     cut off the right edge - so below this they wrap into rows instead. */
  .tv-panel-header { flex-wrap: wrap; }
  .tv-panel-header input { flex: 1 1 100%; min-width: 140px; }
  .tv-panel-header .ticker-select-trigger { flex: 1 1 auto; max-width: none; min-width: 140px; }
  .tv-panel-header .dashboard-theater-toggle-btn,
  .tv-panel-header .ticker-add-btn { flex: 1 1 auto; }
}

@media (max-width: 760px) {
  header, .dashboard-header, .page-header {
    flex-direction: column;
    align-items: stretch;
    height: auto;
    min-height: 58px;
    padding: 10px;
    gap: 8px;
  }
  .brand { justify-content: center; margin-right: 0; padding-right: 0; border-right: 0; padding-bottom: 8px; border-bottom: 1px solid var(--border); }
  .header-left { justify-content: center; width: 100%; flex-wrap: wrap; margin-right: 0; }
  .header-left .home-button { flex: 1 1 28%; padding: 0 8px; }
  header .controls { width: 100%; justify-content: center; }
  .quick-links-list { grid-template-columns: 1fr; }
  .weather-summary { grid-template-columns: 1fr; }
  /* one story per row once the column is phone-width */
  .hn-list { grid-template-columns: 1fr; }
  /* the bezel stacks into rows; the plate's divider becomes the rule under it */
  .site-footer { flex-direction: column; align-items: stretch; text-align: center; gap: 12px; }
  .footer-plate {
    justify-content: center;
    padding-right: 0;
    padding-bottom: 10px;
    border-right: 0;
    border-bottom: 1px solid var(--border);
  }
  .footer-plate-text { align-items: center; }
  /* the auto margin sends the links to the far end of the main axis, which is
     down the page once the bezel stacks — drop it so they centre instead */
  .footer-nav { justify-content: center; margin-left: 0; }
}

/* Brand + six nav buttons + the rail need roughly 900px on one line. Below that,
   drop the rail onto its own centred row instead of letting it crowd the nav.
   Sits after the 760px block so it also covers the stacked-header case. */
@media (max-width: 980px) {
  body.home-page header { flex-wrap: wrap; }
  body.home-page .status-rail {
    margin-left: 0;
    width: 100%;
    justify-content: center;
    border-left: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  * { transition-duration: 0.001ms !important; animation-duration: 0.001ms !important; }
  /* Cancel the boot cascade and the lamp pulse outright rather than letting them
     run at ~0s: each element's resting state is already the visible, lit one, so
     dropping the animation shows the finished console immediately. Leaving only
     the duration clamped would still honour animation-delay and flash the page
     blank for a third of a second. */
  header,
  body.home-page .status-rail,
  body.home-page .home-search-wrap,
  .home-main .card,
  .site-footer,
  .card > .home-box-title:first-child::before { animation: none !important; }
  .sentiment-mark { transition: none !important; }
}
