/* ============================================================================
   dispatch — SHARED SITE CHROME.  Load this on EVERY page, always:
       <link rel="stylesheet" href="/dispatch/assets/site.css">
   and pair it with:
       <script src="/dispatch/assets/langselect.js" defer></script>

   It owns the design tokens, the header (desktop + mobile), the nav + hamburger,
   and the footer — so a page only writes its OWN content styles and can never
   ship a broken header again. Start new pages from  site/_template.html.

   Loaded AFTER a page's inline <style>, so these rules win on equal specificity.
   ============================================================================ */

/* ── design tokens ─────────────────────────────────────────────────────────── */
:root{
  --bg:#0A0A0C; --surface:#121216; --surface-2:#17171D; --raise:#1D1D24;
  --line:#26262E; --line-strong:#34343E;
  --text:#F5F5F7; --muted:#8E8E98; --faint:#5A5A64;
  --gold:#F2B01E; --gold-dim:rgba(242,176,30,.13); --gold-line:rgba(242,176,30,.34);
  --danger:#FF4D4D; --warn:#FF7A45;
  --sans:-apple-system,BlinkMacSystemFont,"SF Pro Text","SF Pro Display","Helvetica Neue",Arial,sans-serif;
  --mono:ui-monospace,"SF Mono","SFMono-Regular",Menlo,Monaco,"Cascadia Code",monospace;
  --wrap:1080px;
  --bar-h:62px;
}

/* ── base ──────────────────────────────────────────────────────────────────── */
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--text);font-family:var(--sans);line-height:1.55;
  -webkit-font-smoothing:antialiased;min-height:100vh}
::selection{background:var(--gold);color:#0A0A0C}
.wrap{max-width:var(--wrap);margin:0 auto;padding:0 24px}
.mono{font-family:var(--mono)}
.eyebrow{font-family:var(--mono);font-size:11.5px;letter-spacing:.22em;text-transform:uppercase;
  color:var(--gold);font-weight:600}
.label{font-family:var(--mono);font-size:10.5px;letter-spacing:.2em;text-transform:uppercase;color:var(--faint)}

/* ── header ────────────────────────────────────────────────────────────────── */
/* `header.bar` (not `.bar`) so this beats any page-level `header.bar` rule. */
header.bar{position:sticky;top:0;z-index:100;background:rgba(10,10,12,.72);
  -webkit-backdrop-filter:saturate(140%) blur(12px);backdrop-filter:saturate(140%) blur(12px);
  border-bottom:1px solid var(--line)}
/* Pinned width so the bar is identical regardless of a page's own .wrap. */
header.bar .wrap{max-width:var(--wrap);margin:0 auto;padding:0 24px}
.bar-in{position:relative;display:flex;align-items:center;gap:20px;height:var(--bar-h)}

.brand{display:flex;align-items:center;gap:11px;font-weight:600;letter-spacing:-.01em;
  text-decoration:none;color:var(--text)}
.brand:hover{color:var(--text)}
.brand img{width:30px;height:30px;display:block;flex:none}   /* never let the logo render full-size */
.brand b{font-size:16.5px;color:var(--text);font-weight:600} /* explicit — kills default link purple */

.nav{margin-left:auto;display:flex;align-items:center;gap:22px}
.nav a{font-size:13.5px;color:var(--muted);text-decoration:none;transition:color .15s}
.nav a:hover,.nav a:focus-visible{color:var(--text)}

/* hamburger — injected by langselect.js as the FIRST child of .bar-in */
.navtoggle{display:none;background:none;border:0;color:var(--text);cursor:pointer;
  padding:6px;border-radius:8px;line-height:0}
.navtoggle svg{width:24px;height:24px;display:block}
.navtoggle:focus-visible{outline:2px solid var(--gold);outline-offset:2px}

/* language selector (langselect.js appends it last) */
.dl-lang.in-bar{margin-left:auto}

/* ── header · mobile: hamburger LEFT, brand CENTERED, language RIGHT ───────── */
@media (max-width:840px){
  .navtoggle{display:inline-flex;align-items:center;order:1}
  /* absolute centring is immune to the differing widths of the two side items */
  .brand{position:absolute;left:50%;transform:translateX(-50%);order:2}
  .dl-lang.in-bar{margin-left:auto;order:3}

  /* A floating rounded card, inset from the edges — the SAME treatment as the
     language menu (.dl-lang-menu), so the two dropdowns match. `fixed` so no
     ancestor can clip it; it scrolls rather than truncating if it gets tall. */
  .nav{position:fixed;top:calc(var(--bar-h) + 8px);left:16px;right:16px;z-index:99;margin:0;
    flex-direction:column;align-items:stretch;gap:2px;
    max-height:calc(100vh - var(--bar-h) - 24px);overflow-y:auto;
    background:var(--surface);border:1px solid var(--line);border-radius:14px;
    padding:6px;display:none;
    box-shadow:0 26px 55px -28px rgba(0,0,0,.92)}
  header.bar.nav-open .nav{display:flex}
  .nav a{font-size:15px;padding:13px 12px;border-radius:9px;color:var(--text)}
  .nav a:hover,.nav a:active,.nav a:focus-visible{background:var(--gold-dim);color:var(--gold)}
}

/* ── footer ────────────────────────────────────────────────────────────────── */
footer{border-top:1px solid var(--line);padding:26px 0;color:var(--faint)}
.foot-in{display:flex;justify-content:space-between;gap:14px;flex-wrap:wrap;
  font-family:var(--mono);font-size:11px;letter-spacing:.04em}

@media (prefers-reduced-motion:reduce){
  .nav a,.brand,.navtoggle{transition:none}
}

/* ── scroll reveal ─────────────────────────────────────────────────────────────
   Blocks fade + rise as they enter the viewport. Everything is scoped under
   html.reveal-ready, which reveal.js adds to <html> itself — so if the script
   never runs (JS off, parse error, reduced-motion bail) nothing is hidden and
   the page renders exactly as it did before. Never author the hidden state
   outside that scope.

   The .rise override matters: use-case pages define .rise with a load-time
   `animation:rise … forwards`, which fires on page load regardless of scroll
   position. site.css is linked *after* those <style> blocks, so cancelling the
   animation here lets the same elements reveal on scroll instead. Their inline
   animation-delay values go inert with it; reveal.js re-staggers via --rv-d. */
html.reveal-ready .rv{opacity:0;transform:translateY(16px);
  transition:opacity .7s cubic-bezier(.22,.7,.2,1),transform .7s cubic-bezier(.22,.7,.2,1);
  transition-delay:var(--rv-d,0s)}
html.reveal-ready .rv.rv-in{opacity:1;transform:none}
html.reveal-ready .rise{animation:none}

/* Blocks already on screen when the script runs get .rv and .rv-in in the same
   tick, so they simply render visible — no transition, because there is no
   painted start state to animate from. Only scrolled-to blocks animate, which
   is the whole point of the effect.
   An earlier version gave that first batch a keyframe so it would fade in on
   load too. Don't: an engine that isn't advancing its animation clock (headless
   under a virtual time budget, and by extension anything that throttles
   animations) holds `animation-fill-mode: both` at the `from` state — opacity 0
   — and the entire above-the-fold page renders blank. Instant is correct here;
   nothing that hides content may depend on an animation actually running. */
