/* app.css - the new app shell (Brief/Slate/Moves/Signals/Memory), styled
   entirely from tokens.css. Not to be confused with public/portal.css,
   which is V1's glow-site-derived stylesheet for "the sheet" documents
   (Flyby, Plan, etc.) - those stay a deliberately different, separate
   design language (see DESIGN-CONTRACT.md, GRAND-PLAN.md). This file is
   the app; that file is the document.

   Build step 3 (empty shell): chrome, router, five nav entries,
   hard-coded telemetry. No features - the Brief pane below is the
   mockup's own example content, hard-coded, not fetched. See
   mockups/brief-mockup.png, which this is built to match. */

* { box-sizing: border-box; }

html, body {
  height: 100%;
  margin: 0;
  padding: 0;
  overflow: hidden;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--text-body);
  line-height: var(--leading-body);
}

button {
  font-family: inherit;
  font-size: inherit;
  color: inherit;
  background: none;
  border: none;
  cursor: pointer;
}

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

.icon {
  width: 16px;
  height: 16px;
  display: inline-block;
  vertical-align: middle;
}
.icon svg { width: 100%; height: 100%; display: block; }

/* ---- Label: the one uppercase treatment in the app ---- */
.label {
  font-family: var(--font-mono);
  font-weight: var(--weight-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
}
.label.accent { color: var(--accent); }

/* ---- Shell layout ----
   Top bar spans the full width first (logo lives above everything, not
   beside the rail); the rail then sits below it on the left only; the
   telemetry footer spans the full width again underneath both, cutting
   across the rail's vertical divider rather than stopping at it.
   (Fixed 23 Jul 2026 - first pass had the rail running the full height,
   which was wrong against the mockup.) */
.app-shell {
  display: grid;
  grid-template-columns: var(--rail-w) 1fr;
  grid-template-rows: auto 1fr auto;
  grid-template-areas:
    "topbar topbar"
    "rail   main"
    "footer footer";
  height: 100vh;
}

/* ---- Left rail ---- */
.rail {
  grid-area: rail;
  background: var(--ground);
  border-right: var(--border-hair) solid var(--hairline);
  padding: var(--s5) 0;
}
.rail-nav { display: flex; flex-direction: column; }
.rail-item {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3) var(--s5);
  border-left: var(--border-status) solid transparent;
  color: var(--muted);
  width: 100%;
  text-align: left;
}
.rail-item .icon { color: var(--muted); }
.rail-item[aria-current="page"] {
  color: var(--accent);
  border-left-color: var(--accent);
  font-weight: var(--weight-strong);
}
.rail-item[aria-current="page"] .icon { color: var(--accent); }

/* ---- Top bar ---- */
.topbar {
  grid-area: topbar;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--s3) var(--s5);
  border-bottom: var(--border-hair) solid var(--hairline);
}
/* Wordmark + eyebrow: the old sheet-chrome lockup (lib/layout.js
   renderHeader, portal.css .wordmark-show) carried over on Chris's
   request - "Signalism." plus a small muted "for {show}" underneath,
   rather than a separate workspace-switcher control. One identity
   element instead of two bits of chrome saying the same thing. */
.wordmark-lockup { display: flex; flex-direction: column; gap: 2px; color: inherit; text-decoration: none; }
.wordmark { display: flex; align-items: center; gap: var(--s2); font-weight: var(--weight-strong); font-size: var(--text-title); line-height: 1; color: var(--ink); }
.wordmark-dot { width: 9px; height: 9px; border-radius: 50%; background: var(--accent); display: inline-block; }
.wordmark-eyebrow { font-family: var(--font-sans); font-size: var(--text-meta); color: var(--muted); line-height: 1; }
.topbar-utility { display: flex; align-items: center; gap: var(--s4); color: var(--muted); }
/* Top-right utility icons (24 Jul 2026 quick pass) - real buttons now,
   not decorative spans. Questions (portal only) hovers to accent like any
   other clickable icon; the rest are disabled - same dimmed treatment
   .chip-btn:disabled already uses elsewhere, not a new pattern. */
.icon-btn { display: flex; align-items: center; color: inherit; }
.icon-btn:hover:not(:disabled) { color: var(--accent); }
.icon-btn:disabled { opacity: 0.5; cursor: default; }

/* ---- Main pane ----
   Chrome (rail, topbar, footer) stays put; this is the only part of the
   shell that scrolls. min-height: 0 overrides the grid item default
   (auto), which would otherwise let the pane grow past its row instead
   of scrolling within it. */
.main {
  grid-area: main;
  min-height: 0;
  overflow-y: auto;
  padding: var(--s6);
  display: flex;
  justify-content: center;
}
.sheet-wrap { position: relative; width: 100%; max-width: calc(var(--measure) + 60px); }
.sheet {
  position: relative;
  z-index: 1;
  background: var(--paper);
  border: var(--border-hair) solid var(--hairline);
  border-radius: var(--radius);
  padding: var(--s6);
}
/* The Context drawer reads as a second sheet tucked behind the visible
   one - same paper, still poking out the right side (24 Jul 2026: kept -
   the breadcrumb tabs sit further right again, past this sliver, no
   conflict), now ALSO poking out a bit at the bottom, same offset amount
   as the side. It isn't a real drawer yet (no evidence content wired up),
   just the resting/"closed" look. */
.context-sheet {
  position: absolute;
  z-index: 0;
  top: 0;
  left: var(--s4);
  right: calc(-1 * var(--s4));
  bottom: calc(-1 * var(--s4));
  background: var(--paper);
  border: var(--border-hair) solid var(--hairline);
  border-radius: var(--radius);
}
.sheet-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--s5);
}
.sheet-head-right { display: flex; align-items: center; gap: var(--s5); }
.show-context-btn { display: flex; align-items: center; gap: var(--s2); color: var(--muted); }
.show-context-btn .icon { color: var(--muted); }

.headline {
  font-size: var(--text-display);
  line-height: var(--leading-display);
  font-weight: var(--weight-strong);
  max-width: var(--measure-headline);
  margin: 0 0 var(--s3);
}
/* Secondary headline (e.g. "Your Flyby is ready for you" under the day's
   greeting) - a size down, title scale rather than display. */
.headline--sub {
  font-size: var(--text-title);
  line-height: var(--leading-title, var(--leading-display));
}
.status-line { color: var(--muted); font-size: var(--text-meta); margin: 0 0 var(--s5); }
.status-line .dot { color: var(--accent); }

hr.rule { border: none; border-top: var(--border-hair) solid var(--hairline); margin: 0 0 var(--s5); }

/* ---- Ledger card + Evidence card, connected ---- */
.evidence-row { display: flex; align-items: stretch; gap: 0; margin-bottom: var(--s4); }
.ledger-card {
  flex: 1 1 auto;
  background: var(--paper);
  border: var(--border-hair) solid var(--hairline);
  border-left: var(--border-status) solid var(--ink);
  border-radius: var(--radius-tight);
  padding: var(--s4);
  display: flex;
  gap: var(--s3);
}
.ledger-card.watching { border-left-color: var(--accent); }
/* A ledger card that IS the thing it names (e.g. the Brief's "your Flyby
   is ready" card) - the whole card is the link, not just its bottom
   action row. Anchor reset only; layout is identical to a plain
   .ledger-card div. */
.ledger-card-link { color: inherit; text-decoration: none; cursor: pointer; }
.ledger-card-link:hover { border-color: var(--accent); }
.ledger-ordinal { font-family: var(--font-mono); font-weight: var(--weight-mono); color: var(--muted); font-size: var(--text-meta); flex: 0 0 auto; padding-top: 2px; }
.ledger-body { flex: 1 1 auto; }
.ledger-top { display: flex; align-items: center; justify-content: space-between; margin-bottom: var(--s1); }
.ledger-title { font-size: var(--text-title); font-weight: var(--weight-strong); margin: var(--s1) 0; }
.ledger-meta { color: var(--muted); font-size: var(--text-meta); display: flex; align-items: center; justify-content: space-between; margin-top: var(--s2); }
.ledger-action { display: flex; align-items: center; gap: var(--s1); color: var(--accent); }

.chip {
  font-family: var(--font-mono);
  font-weight: var(--weight-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
  border: var(--border-hair) solid var(--hairline);
  border-radius: var(--radius-tight);
  padding: 2px var(--s2);
}

.evidence-connector { flex: 0 0 auto; width: 24px; position: relative; }
.evidence-connector::before {
  content: "";
  position: absolute;
  top: 50%;
  left: 0;
  right: 0;
  border-top: var(--border-hair) solid var(--hairline);
}
.evidence-card {
  flex: 0 0 260px;
  background: var(--paper);
  border: var(--border-hair) solid var(--hairline);
  border-radius: var(--radius-tight);
  padding: var(--s4);
}
.evidence-statement { font-size: var(--text-meta); margin: var(--s2) 0 var(--s3); }
.evidence-count { display: flex; align-items: center; gap: var(--s1); color: var(--muted); font-family: var(--font-mono); font-size: var(--text-label); padding-top: var(--s3); border-top: var(--border-hair) solid var(--hairline); }

/* ---- Row item ---- */
.row-item {
  display: flex;
  align-items: center;
  gap: var(--s3);
  border: var(--border-hair) solid var(--hairline);
  border-radius: var(--radius-tight);
  padding: var(--s3) var(--s4);
}
.row-item .icon { color: var(--muted); flex: 0 0 auto; }
.row-item-body { flex: 1 1 auto; }
.row-item-title { font-size: var(--text-body); margin-top: 2px; }
.row-item-meta { color: var(--muted); font-size: var(--text-meta); flex: 0 0 auto; }
.row-item-chevron { color: var(--muted); flex: 0 0 auto; }
/* A row-item that IS the thing it names (admin's Shows/Engagement lists,
   24 Jul 2026) - same anchor-reset idea as .ledger-card-link, applied to
   this primitive instead. Stacked with margin, not gap, since these
   render as a plain list rather than the Brief's single-card layout. */
.row-item-link { display: flex; color: inherit; text-decoration: none; cursor: pointer; margin-bottom: var(--s3); }
.row-item-link:hover { border-color: var(--accent); }
.row-item-link:last-child { margin-bottom: 0; }

/* ---- Side tabs (breadcrumbs) ----
   24 Jul 2026: was a single static "Context" tab; now a real breadcrumb
   trail for how you got to the page you're on - Brief always on top, one
   more tab attached directly below it per level in (Flyby, for now).
   Earlier crumbs are clickable (go back to that page); the current one is
   own-coloured and inert, same idea as any breadcrumb. Empty (no children)
   outside Brief/Flyby, so it shows nothing on Slate/Moves/Signals/Memory -
   there's no drill-down there yet for it to describe. */
.side-tabs {
  position: absolute;
  z-index: 2;
  top: var(--s6);
  right: calc(-1 * var(--s4) - 1px);
  transform: translateX(100%);
  display: flex;
  flex-direction: column;
}
.side-tab {
  writing-mode: vertical-rl;
  background: var(--paper);
  border: var(--border-hair) solid var(--hairline);
  border-left: none;
  border-radius: 0 var(--radius-tight) var(--radius-tight) 0;
  padding: var(--s3) var(--s2);
  margin-top: -1px;
  color: var(--muted);
  letter-spacing: var(--tracking-label);
  font-family: var(--font-mono);
  font-weight: var(--weight-mono);
  font-size: var(--text-label);
  text-transform: uppercase;
  cursor: pointer;
}
.side-tab:first-child { margin-top: 0; }
.side-tab.current { color: var(--accent); border-color: var(--accent); cursor: default; }

/* ---- Placeholder panes (Slate/Moves/Signals/Memory - not built yet) ---- */
.pane-placeholder { color: var(--muted); }

/* ---- Sheet document (Flyby, natively - see app.js's flybyDocView) ----
   Rendered straight into #view-root, same padding as every other pane -
   no iframe, no separate frame modifier needed any more. */
.back-to-brief {
  display: block;
  margin: 0 0 var(--s4);
  color: var(--muted);
  font-family: var(--font-mono);
  font-weight: var(--weight-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
}
.back-to-brief:hover { color: var(--accent); }

/* Rendered markdown (mdToHtml output from the Flyby's own body) - no
   custom classes to hook into, just the bare tags it produces, styled
   from the same tokens as everything else. */
.doc-prose { max-width: var(--measure); }
.doc-prose p { margin: 0 0 var(--s4); }
.doc-prose h2 {
  font-size: var(--text-title);
  font-weight: var(--weight-strong);
  margin: var(--s5) 0 var(--s3);
}
.doc-prose h2:first-child { margin-top: 0; }
.doc-prose ul, .doc-prose ol { margin: 0 0 var(--s4); padding-left: var(--s5); }
.doc-prose li { margin-bottom: var(--s2); }
.doc-prose strong { font-weight: var(--weight-strong); }
.doc-prose a { text-decoration: underline; text-underline-offset: 2px; }

/* The close: a primary CTA plus a quieter "not for me" reveal. First real
   button in the new system - not yet one of DESIGN-CONTRACT's approved
   primitives, kept deliberately close to the existing vocabulary (accent
   text + arrow, like .ledger-action; no fill, no border, no shadow) rather
   than inventing a new filled-button style unreviewed. */
.doc-close { margin-top: var(--s5); }
.btn-primary {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  color: var(--accent);
  font-weight: var(--weight-strong);
  font-size: var(--text-body);
}
.not-sure-row { margin: var(--s3) 0 0; }
.text-link-btn { color: var(--muted); font-size: var(--text-meta); }
.text-link-btn:hover { color: var(--accent); }
.objection-panel {
  margin-top: var(--s4);
  padding-top: var(--s4);
  border-top: var(--border-hair) solid var(--hairline);
}
.objection-options { display: flex; flex-wrap: wrap; gap: var(--s2); }
.chip-btn {
  font-family: var(--font-mono);
  font-weight: var(--weight-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
  border: var(--border-hair) solid var(--hairline);
  border-radius: var(--radius-tight);
  padding: var(--s2) var(--s3);
}
.chip-btn:hover { border-color: var(--accent); color: var(--accent); }
.chip-btn:disabled { opacity: 0.5; cursor: default; }
/* Persistent selected state (admin's Engagement sort/stage toggles, 24 Jul
   2026) - same accent treatment as :hover, just not dependent on the
   pointer still being there. */
.chip-btn.active { border-color: var(--accent); color: var(--accent); }

/* Section heading inside a doc pane (Signalism One's "What you get" /
   "Pricing" / "Before you start") - same weight as .doc-prose h2, used
   outside it for sections that aren't rendered markdown. */
.doc-subhead {
  font-size: var(--text-title);
  font-weight: var(--weight-strong);
  margin: var(--s5) 0 var(--s3);
}
.doc-subhead:first-of-type { margin-top: 0; }

/* ---- Pricing card (Signalism One, natively) ----
   Ledger-card language (paper, hairline border, tight radius) rather than
   the old sheet's separate warm/cream price-card treatment - one visual
   system, not two. */
.price-card {
  background: var(--paper);
  border: var(--border-hair) solid var(--hairline);
  border-radius: var(--radius-tight);
  padding: var(--s5);
  max-width: var(--measure);
  margin-bottom: var(--s5);
}
.pay-toggle { display: inline-flex; border: var(--border-hair) solid var(--hairline); border-radius: var(--radius-tight); overflow: hidden; margin-bottom: var(--s4); }
.pill-btn {
  font-family: var(--font-mono);
  font-weight: var(--weight-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
  padding: var(--s2) var(--s3);
}
.pill-btn.active { color: var(--paper); background: var(--ink); }
.price-num { font-size: var(--text-display); font-weight: var(--weight-strong); line-height: 1.1; }
.price-term { margin: var(--s1) 0; }

/* ---- FAQ accordion (Signalism One, natively) ---- */
.faq-item { border-top: var(--border-hair) solid var(--hairline); }
.faq-item:first-child { border-top: none; }
.faq-q {
  display: block;
  width: 100%;
  text-align: left;
  padding: var(--s3) 0;
  font-size: var(--text-body);
  font-weight: var(--weight-strong);
}
.faq-a { padding: 0 0 var(--s3); color: var(--muted); }
.faq-a p { margin: 0; }

/* ---- Chat popup (Ask a question) ----
   24 Jul 2026, reshaped twice: first into a message-box pane, then -
   per Chris, wanting an actual pop-up window rather than a page - into
   fixed chrome the footer icon toggles, floating above whatever pane is
   showing rather than replacing it. No box-shadow (not an approved
   token); a plain hairline border + radius does the "floating panel" job
   instead. Ledger-card language for the messages themselves (paper,
   hairline border, tight radius, accent = Chris's messages) rather than
   filled chat bubbles, which the contract doesn't allow outside the one
   notification badge. Your own messages sit right, Chris's replies sit
   left, same convention as any message thread. */
.chat-popup {
  position: fixed;
  z-index: 20;
  right: var(--s5);
  bottom: calc(var(--s6) + var(--s4));
  width: 380px;
  max-width: calc(100vw - var(--s5) * 2);
  max-height: 60vh;
  display: flex;
  flex-direction: column;
  background: var(--paper);
  border: var(--border-hair) solid var(--hairline);
  border-radius: var(--radius);
  padding: var(--s4);
}
/* .chat-popup's own `display: flex` is an author rule at the same
   specificity as the browser default `[hidden] { display: none }` - author
   rules win cascade ties regardless of source order, so without this the
   hidden attribute was being silently ignored and the popup never actually
   closed (or even started hidden on load). This selector is more specific,
   so it correctly wins instead. */
.chat-popup[hidden] { display: none; }
.chat-popup-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: var(--s2);
}
.chat-popup-close {
  color: var(--muted);
  font-size: var(--text-title);
  line-height: 1;
  padding: var(--s1) var(--s2);
}
.chat-popup-close:hover { color: var(--accent); }
.chat-popup-intro { margin: 0 0 var(--s4); }

.chat-thread {
  flex: 1 1 auto;
  min-height: 120px;
  overflow-y: auto;
  margin-bottom: var(--s4);
  display: flex;
  flex-direction: column;
  gap: var(--s3);
}
.chat-message { max-width: 85%; }
.chat-message--mine { align-self: flex-end; }
.chat-message--reply { align-self: flex-start; }
.chat-meta {
  display: flex;
  gap: var(--s3);
  color: var(--muted);
  font-family: var(--font-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  margin-bottom: var(--s1);
}
.chat-message--mine .chat-meta { flex-direction: row-reverse; }
.chat-bubble {
  background: var(--paper);
  border: var(--border-hair) solid var(--hairline);
  border-radius: var(--radius-tight);
  padding: var(--s3);
  font-size: var(--text-meta);
}
.chat-message--reply .chat-bubble { border-left: var(--border-status) solid var(--accent); }
.chat-pending { color: var(--muted); font-size: var(--text-meta); align-self: flex-start; max-width: 85%; margin: 0; }
.chat-empty { margin-bottom: 0; }

.chat-composer {
  flex: 0 0 auto;
  display: flex;
  gap: var(--s3);
  align-items: flex-end;
  padding-top: var(--s4);
  border-top: var(--border-hair) solid var(--hairline);
}
.chat-input {
  flex: 1 1 auto;
  font-family: inherit;
  font-size: var(--text-meta);
  padding: var(--s3);
  border: var(--border-hair) solid var(--hairline);
  border-radius: var(--radius-tight);
  background: var(--paper);
  color: var(--ink);
  resize: vertical;
}
.chat-send-btn { flex: 0 0 auto; }

@media (max-width: 640px) {
  .chat-popup { right: var(--s4); left: var(--s4); width: auto; bottom: calc(var(--s6) + var(--s3)); }
}

/* ---- Telemetry footer ---- */
.telemetry {
  grid-area: footer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 var(--s5);
  height: var(--s6);
  border-top: var(--border-hair) solid var(--hairline);
  font-family: var(--font-mono);
  font-weight: var(--weight-mono);
  font-size: var(--text-label);
  color: var(--muted);
}
.telemetry-segments { display: flex; align-items: center; gap: var(--s6); }
.telemetry-segment { display: flex; align-items: center; gap: var(--s2); }
.telemetry-chat { position: relative; display: flex; align-items: center; color: var(--muted); }
.telemetry-badge {
  position: absolute;
  top: -6px;
  right: -8px;
  background: var(--accent);
  color: var(--paper);
  border-radius: 50%;
  min-width: 16px;
  height: 16px;
  font-size: 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0 4px;
}
/* Same cascade-tie issue as .chat-popup above - this rule's own
   display: flex otherwise beats the browser's default [hidden] rule. */
.telemetry-badge[hidden] { display: none; }

/* ---- Mobile: rail becomes a bottom tab bar ----
   Chris's call (23 Jul 2026): bottom tab bar, not a hamburger drawer.
   No mobile mockup exists yet, so this is a first pass on the chrome
   only - card-level reflow (evidence row stacking) is included since a
   horizontally-broken screen undermines "prove the app feel", but the
   real content views (Slate/Moves/Signals/Memory) haven't been designed
   for mobile at all yet; revisit once they're built for real. */
@media (max-width: 720px) {
  .app-shell {
    grid-template-columns: 1fr;
    grid-template-rows: auto 1fr auto auto;
    grid-template-areas:
      "topbar"
      "main"
      "footer"
      "rail";
  }

  .rail {
    border-right: none;
    border-top: var(--border-hair) solid var(--hairline);
    padding: 0;
  }
  .rail-nav { flex-direction: row; }
  .rail-item {
    flex: 1 1 0;
    flex-direction: column;
    justify-content: center;
    gap: var(--s1);
    padding: var(--s2) var(--s1);
    border-left: none;
    border-top: var(--border-status) solid transparent;
    text-align: center;
    font-size: var(--text-label);
  }
  .rail-item[aria-current="page"] { border-left-color: transparent; }

  .wordmark-eyebrow { display: none; }

  .main { padding: var(--s4); }
  .sheet { padding: var(--s4); }
  .side-tabs, .context-sheet { display: none; }

  .sheet-head { flex-wrap: wrap; row-gap: var(--s2); }

  /* The ledger/evidence pairing only makes sense side by side; stacked,
     the connecting hairline reads as a stray line, not a connection. */
  .evidence-row { flex-direction: column; }
  .evidence-connector { display: none; }
  .evidence-card { flex: 1 1 auto; }

  .telemetry-segments { display: none; }
  .telemetry { justify-content: flex-end; }
}

/* ---- Admin form field (public/admin.js, 24 Jul 2026) ----
   Same tokens .chat-input already established for a bordered text input
   (paper/hairline/radius-tight), generalised with a Label-style caption -
   the admin surface's forms (New Flyby, Show detail) are the first place
   this app needs plain labelled fields rather than a chat composer. */
.admin-field { margin-bottom: var(--s4); }
.admin-field label {
  display: block;
  font-family: var(--font-mono);
  font-weight: var(--weight-mono);
  font-size: var(--text-label);
  letter-spacing: var(--tracking-label);
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: var(--s2);
}
.admin-field input,
.admin-field select,
.admin-field textarea {
  width: 100%;
  font-family: inherit;
  font-size: var(--text-body);
  padding: var(--s3);
  border: var(--border-hair) solid var(--hairline);
  border-radius: var(--radius-tight);
  background: var(--paper);
  color: var(--ink);
}
.admin-field input:disabled { color: var(--muted); }

/* ---- Auth pages (/signin, expired-link) - 24 Jul 2026 restyle ----
   No session exists at this point, so this is deliberately NOT the
   app-shell grid (no rail/topbar/telemetry - nothing real to show in any
   of it yet) - just the .sheet card (already an established primitive),
   centred on its own, same tokens/fonts as everywhere else. Shared by
   lib/auth-page.js for both the sign-in form and the expired-link
   fallback (functions/auth/callback.js). */
.auth-body { min-height: 100vh; display: flex; align-items: center; justify-content: center; }
.auth-sheet { width: 100%; max-width: 400px; }
.auth-wordmark { margin-bottom: var(--s5); }
.auth-note:empty { display: none; }

/* ---- Activity timeline (admin, natively) ----
   Plain divided rows, same idea as .faq-item's border-top list - nothing
   on this feed is ever "done", so no checkbox/tick-circle treatment. */
.activity-item { padding: var(--s4) 0; border-top: var(--border-hair) solid var(--hairline); }
.activity-item:first-child { border-top: none; padding-top: 0; }
.activity-item p { margin: var(--s1) 0 0; }
