/* Eigenslate homepage — plain static CSS, no build step.
   Palette = the app's "graphite" theme (app/src/styles/theme.css
   [data-theme="graphite"]). Graphite is dark-only, so there is deliberately
   no prefers-color-scheme light variant: `color-scheme: dark` below tells the
   UA to render form controls and scrollbars dark to match. Token NAMES are the
   site's own; only their values are borrowed from the app. */

:root {
  color-scheme: dark;

  --bg: #242424;          /* app --bg-0, document area */
  --bg-raised: #1b1b1b;   /* app --bg-1, surfaces: topbar, cards, dropdowns */
  --surface: #424242;     /* app --bg-3, hover/secondary-button surface */
  --surface-hi: #595959;  /* app --bg-2, the lighter of the two grey surfaces */
  --fg: #dcdcdc;          /* app --fg-0 */
  /* Muted text: labels, the tagline, footer lines, docs captions. Raised from
     #808080 (2026-09-21): that failed WCAG AA text contrast on every surface
     it is used on (3.93:1 on --bg, 4.36:1 on --bg-raised). This is the
     smallest grey that clears 4.5:1 on all three, --bg being the worst case
     at 4.56:1. Never used as text on --surface or --surface-hi. */
  --fg-dim: #8b8b8b;
  --accent: #ffc800;
  --accent-hover: #d4b34a;  /* app --accent-dim; the bright accent darkens on hover */
  --accent-fg: #1b1b1b;     /* app --on-accent */
  --border: #313131;
  --border-hover: #424242;
  /* Boundary for a control whose border is the ONLY thing identifying it: the
     text fields, and nothing else (owner decision 2026-09-21). WCAG 1.4.11
     asks 3:1 of that border, and --border reaches 1.19:1 on --bg. This is the
     smallest grey clearing 3:1 on both surfaces the fields sit on (3.04:1 on
     --bg, 3.38:1 on --bg-raised); one step darker lands on 3.000004:1, which
     is a rounding error away from failing.

     Buttons do NOT take it: a button is identified by its label and its fill,
     so 1.4.11 does not reach its edge, and .btn-secondary keeps --border.
     Decorative rules, card edges, table lines and section separators stay on
     --border too. */
  --border-control: #6e6e6e;
  --shadow: rgba(0, 0, 0, 0.5);
  /* The lighter stripe of the "asset pending" diagonal fill; sits between
     --bg and --bg-raised. Homepage placeholders only. */
  --placeholder-stripe: #1f1f1f;

  /* Caution, distinct from --danger: nothing is broken and nothing is being
     destroyed, the reader just has to know something before they act. Both are
     the accent taken down to surface strength, so a caution box reads as the
     site's own amber rather than as a fourth colour. */
  --warning-bg: rgba(255, 200, 0, 0.08);
  --warning-border: #6b5a1e;   /* --accent blended ~35% into --border */

  --danger: #e88098;           /* destructive text / icons on dark */
  /* Destructive button fill. Darkened from #cf4b5e (2026-09-21), where white
     label text reached only 4.36:1; this is the smallest darkening that clears
     4.5:1 (4.55:1), and it keeps the fill's own 3:1 boundary against the page
     (3.41:1 on --bg). */
  --danger-solid: #ca495c;
  --danger-solid-hover: #b83e50;  /* --danger-solid darkened ~9% for hover */
  --on-danger-solid: #ffffff;
  --danger-border: #714d55;    /* --danger blended ~35% into --border */

  /* app --font-mono. Used by the report page's error-detail block, which shows
     a machine-produced stack rather than prose. */
  --font-mono: ui-monospace, "Cascadia Code", "Fira Code", Consolas, monospace;

  /* Font size the homepage's static MathQuill renders AND displays at. Must
     equal HOME_MATH_FONT_SIZE_PX in scripts/render-home-math.mjs: the markup
     carries an absolute size baked in at render time and does not scale, and
     mathquill.css sizes stretchy delimiters against the inherited size. The
     value is .home-feature-body .home-expr's own 14.5px, so the math sits at
     exactly the scale the other four blocks' expression lines do. */
  --home-math-size: 14.5px;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

/* The hidden attribute must always win — author display rules (.card,
   .page are flex) would otherwise override the UA's [hidden] default. */
[hidden] {
  display: none !important;
}

html {
  height: 100%;
}

/* min-height (not height): pages taller than the viewport must grow the
   body, not compress its flex children (the top bar was shrinking). */
body {
  min-height: 100%;
}

body {
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--fg);
  font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  line-height: 1.5;
  /* Site-wide so the shared top bar and footer render at the same weight as
     page content on macOS. Single source of truth: do not re-declare. */
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* ── Keyboard access (spec/site.md §6) ───────────────────────────── */

/* The first focusable element of every page carrying the chrome. It is NOT
   display:none when unfocused: an element the UA does not render is not
   focusable either, so it would leave the tab order altogether. It is moved
   off the top of the viewport instead, and slides back into place the moment
   it takes focus. */
.skip-link {
  position: absolute;
  top: 8px;
  left: 8px;
  z-index: 100;
  padding: 10px 14px;
  border-radius: 8px;
  background: var(--accent);
  color: var(--accent-fg);
  font-size: 14px;
  font-weight: 550;
  text-decoration: none;
  transform: translateY(calc(-100% - 16px));
}

.skip-link:focus {
  transform: translateY(0);
}

/* ONE focus ring for the whole site. Before this rule the sheet set no author
   focus style anywhere and removed the browser's own on the docs filter, so
   the indicator was whatever Chromium happened to draw.

   --accent on --bg is 9.99:1 and on --bg-raised 11.08:1, both far past the
   3:1 a focus indicator has to reach, and the offset lifts the ring clear of
   the control's own border so it reads on the accent-filled buttons too.

   The element selectors are the point: every control the site ships is an
   anchor, a button or a field, so .btn, .cta, .topbar-link, .footer-link, the
   account-menu items, the inbox rows, the clip controls and every input and
   textarea are covered by ONE rule, and a control added later is covered
   without anybody remembering to extend a list. `main` is in it because the
   skip link focuses it (it carries tabindex="-1" and is otherwise not a tab
   stop), so the jump has somewhere visible to land. */
a:focus-visible,
button:focus-visible,
input:focus-visible,
textarea:focus-visible,
select:focus-visible,
summary:focus-visible,
main:focus-visible,
[tabindex]:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* ── Top bar ─────────────────────────────────────────────────────── */

.topbar {
  display: flex;
  align-items: center;
  padding: 0 24px;
  height: 56px;
  flex-shrink: 0;
  border-bottom: 1px solid var(--border);
  background: var(--bg-raised);
  filter:drop-shadow(0 0 15px var(--border));
}

.brand {
  display: flex;
  align-items: center;
  gap: 10px;
  text-decoration: none;
  color: var(--fg);
}

/* The logo image replaces the old λ tile + wordmark (owner asset,
   site/logo.svg). height attr 30 matches; width follows the SVG's aspect.
   OPTICAL CENTERING: an element of the artwork extends above the wordmark,
   putting the text's center at 57.7% of the image height — box-centering
   alone leaves the text (57.7 − 50) = 7.7% of the height too low, so the
   translate lifts the TEXT center onto the bar's center at any height. */
.brand-logo {
  display: block;
  height: 30px;
  width: auto;
  transform: translateY(-7.7%);
}

/* ── Hero ────────────────────────────────────────────────────────── */

.hero {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 18px;
  padding: 48px 24px;
  text-align: center; /* base rule (auth pages); .home .hero overrides */
}

.hero h1 {
  font-size: clamp(2rem, 5vw, 3.25rem);
  font-weight: 750;
  letter-spacing: -0.02em;
  max-width: 18ch;
}

.tagline {
  font-size: clamp(1rem, 2vw, 1.2rem);
  color: var(--fg-dim);
  max-width: 46ch;
}

/* Two CTAs side by side; wraps to a stack when the row won't fit. */
.cta-row {
  margin-top: 10px;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 12px;
}

.cta {
  padding: 13px 30px;
  border: 1px solid transparent;
  border-radius: 10px;
  font-size: 17px;
  font-weight: 600;
  text-decoration: none;
  /* border-color transitions too so the secondary variant's outline tracks
     its fill; the transparent border on both keeps their heights equal. */
  transition: background 120ms ease, border-color 120ms ease, transform 120ms ease;
}

.cta:hover {
  transform: translateY(-1px);
}

.cta:active {
  transform: translateY(0);
}

.cta-primary {
  background: var(--accent);
  color: var(--accent-fg);
}

.cta-primary:hover {
  background: var(--accent-hover);
}

.cta-secondary {
  background: var(--surface);
  border-color: var(--surface-hi);
  color: var(--fg);
}

.cta-secondary:hover {
  background: var(--surface-hi);
}

/* ── Top bar auth slot ───────────────────────────────────────────── */

/* Product and docs links, on the homepage and under /docs. Sits next to the
   brand; the auth slot still takes the right edge via its own margin-left. */
.topbar-nav {
  display: flex;
  gap: 2px;
  margin-left: 14px;
}

.topbar-auth {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: 10px;
}

.topbar-link {
  color: var(--fg);
  text-decoration: none;
  font-size: 14px;
  font-weight: 550;
  padding: 8px 12px;
  border-radius: 8px;
}

/* --surface (not --border): on the dark topbar a #313131 wash is nearly
   invisible against #1b1b1b, so hovers use the app's dedicated hover grey. */
.topbar-link:hover {
  background: var(--surface);
}

/* Signed-in account dropdown (same feature as the graph page's AccountMenu). */

.account-menu {
  position: relative;
}

.account-menu-button {
  border: none;
  background: none;
  cursor: pointer;
  font: inherit;
}

.account-menu-list {
  position: absolute;
  top: calc(100% + 4px);
  right: 0;
  min-width: 170px;
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 8px;
  box-shadow: 0 6px 24px var(--shadow);
  padding: 4px;
  display: flex;
  flex-direction: column;
  z-index: 20;
}

.account-menu-item {
  display: block;
  padding: 8px 12px;
  border: none;
  border-radius: 6px;
  background: none;
  color: var(--fg);
  cursor: pointer;
  font-size: 14px;
  text-align: left;
  text-decoration: none;
}

.account-menu-item:hover {
  background: var(--surface);
}

/* ── Auth / account cards ────────────────────────────────────────── */

.page {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  padding: 48px 24px;
  gap: 20px;
}

.card {
  width: 100%;
  max-width: 400px;
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 12px;
  padding: 28px;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.card h1 {
  font-size: 1.35rem;
  font-weight: 700;
  letter-spacing: -0.01em;
}

.card h2 {
  font-size: 1.05rem;
  font-weight: 650;
}

/* ── Stacks: the sheet's spacing rule for groups nested in a card ─── */

/* .card and the section layouts space their own children with `gap`, and a gap
   reaches its own children and nothing deeper. A group NESTED inside one of
   them — a form, a reveal-in-place confirmation, a step-up block, a list a
   script fills — lays its contents out in normal flow instead, where the
   `* { margin: 0 }` reset at the top of this sheet has taken every scrap of
   spacing away: a button then sits flush against the sentence above it and
   reads as part of that sentence rather than as the thing to press.

   `.stack` gives any such group the card's own 14px rhythm, and every <form>
   inside a card takes it without asking, so a page that puts a control under a
   paragraph gets the room without an inline margin of its own. Margins, not
   flex, deliberately: the rule adds spacing and changes nothing else about how
   its children lay out or how wide they are. */
.card form > * + *,
.stack > * + * {
  margin-top: 14px;
}

/* A control that IS its box's one action spans the box, so there is nothing to
   guess about where to press. The consent gate's single Allow is the case that
   asks for it (spec/site.md §6.1). */
.btn-block {
  display: block;
  width: 100%;
}

/* A lone control that closes a centred box, such as the sign-out under the
   consent gate (spec/site.md §6.1): centred with the box rather than ranged
   left as a bare paragraph would be. */
.btn-center {
  text-align: center;
}

/* The two answers to one confirmation, side by side and centred under the
   sentence they answer. A confirmation REPLACES the button that revealed it
   (spec/account-ui.md §3), so the pair sits centred where that button sat
   rather than ranged to one edge. */
.btn-row {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px;
}

/* The pair shares the row, and a label too long to sit beside its partner
   takes a row of its own at full width rather than leaving the two ragged and
   differently sized on two lines. */
.btn-row > .btn {
  flex: 1 1 auto;
}

.field {
  display: flex;
  flex-direction: column;
  gap: 5px;
}

.field label {
  font-size: 13px;
  font-weight: 550;
  color: var(--fg-dim);
}

.field input,
.field textarea {
  padding: 10px 12px;
  /* --border-control, not --border: this outline is the only thing that says
     where the field is (WCAG 1.4.11). */
  border: 1px solid var(--border-control);
  border-radius: 8px;
  background: var(--bg);
  color: var(--fg);
  font-size: 15px;
  font-family: inherit;
}

.field input:focus,
.field textarea:focus {
  outline: 2px solid var(--accent);
  outline-offset: -1px;
}

.btn {
  padding: 11px 16px;
  border: none;
  border-radius: 8px;
  background: var(--accent);
  color: var(--accent-fg);
  font-size: 15px;
  font-weight: 600;
  font-family: inherit;
  cursor: pointer;
}

.btn:hover {
  background: var(--accent-hover);
}

.btn:disabled {
  opacity: 0.55;
  cursor: default;
}

/* --border, NOT --border-control (owner decision 2026-09-21). A button is
   identified by its label and its fill, so its border is not the sole
   identifier and 1.4.11 does not reach it; the quiet edge stays quiet. Only a
   text field, whose border is the only thing saying where it is, takes the
   raised token. */
.btn-secondary {
  background: transparent;
  color: var(--fg);
  border: 1px solid var(--border);
}

.btn-secondary:hover {
  background: var(--surface);
  border-color: var(--border-hover);
}

.btn-danger {
  background: var(--danger-solid);
  color: var(--on-danger-solid);
}

.btn-danger:hover {
  background: var(--danger-solid-hover);
}

.status {
  font-size: 14px;
  color: var(--fg-dim);
  border-radius: 8px;
}

.status.is-error {
  color: var(--danger);
}

/* A boxed caution the reader must take in before acting, as opposed to .status,
   which reports on something that already happened. Boxed and amber-ruled so it
   cannot be skimmed past: its one use is the test-mode payment notice
   (spec/account-ui.md §3), which stands between a reader and their real card.
   Absent while Pro is coming soon; kept for its return with the call to
   action. */
.notice-warning {
  padding: 10px 12px;
  border: 1px solid var(--warning-border);
  border-left-width: 3px;
  border-radius: 8px;
  background: var(--warning-bg);
  color: var(--fg);
  font-size: 13.5px;
  line-height: 1.55;
}

.notice-warning strong {
  color: var(--accent);
  font-weight: 650;
}

/* The terms gate banner (spec/account-ui.md §3 "Re-acceptance"): built by
   auth.js right after the top bar while the session is gated, on a page that
   opts in because the gate disables something there (/account). A body-level flex child, so it is centred and capped here rather
   than inheriting the .page column. */
.terms-gate-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 8px 16px;
  width: calc(100% - 32px);
  max-width: 720px;
  margin: 16px auto 0;
}

/* The card number is a string to be copied, not prose. */
.notice-code {
  font-family: var(--font-mono);
  white-space: nowrap;
}

.divider {
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--fg-dim);
  font-size: 12px;
}

.divider::before,
.divider::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--border);
}

/* Cloudflare Turnstile container (spec/auth.md §4). The widget is a fixed-size
   cross-origin iframe injected by the third-party script, so this only places
   it: centred, and free to shrink on narrow screens. It has no intrinsic height
   until the script renders into it, which is why the rule sets none — an empty
   reserved box would show as a gap if the script were ever blocked. */
.captcha {
  display: flex;
  justify-content: center;
  overflow-x: auto;
}

/* The acceptance notice on /signup and /login (spec/account-ui.md §3). It is
   the ONLY place a reader is told that creating an account accepts the two
   documents, so it is set in the full foreground colour rather than the dimmed
   grey the rest of the small print uses, and at the sheet's smallest body size
   rather than below it. Sits inside the card, above the controls it governs. */
.legal-notice {
  margin: 0;
  font-size: 13px;
  line-height: 1.5;
  color: var(--fg);
}

.legal-notice a {
  color: var(--accent);
  text-decoration: underline;
}

.card-links {
  display: flex;
  justify-content: space-between;
  font-size: 13px;
}

.card-links a,
.inline-link {
  color: var(--accent);
  text-decoration: none;
}

.card-links a:hover,
.inline-link:hover {
  text-decoration: underline;
}

.badge {
  display: inline-block;
  font-size: 11px;
  font-weight: 650;
  padding: 2px 8px;
  border-radius: 999px;
  background: var(--accent);
  color: var(--accent-fg);
  vertical-align: middle;
}

/* ── My reports (/reports, spec/support.md §3/§4) ──────────────────── */

/* Master-detail. On a wide viewport the list and the report sit side by side,
   so picking one never costs you sight of the others. Below the breakpoint
   there is no room for two columns, so the report REPLACES the list and the
   in-pane back control (hidden above the breakpoint, where it would be a
   button that undoes nothing visible) brings it back. */
.reports {
  display: flex;
  align-items: flex-start;
  gap: 20px;
  width: 100%;
  max-width: 1100px;
}

.reports-list-pane {
  flex: 0 0 340px;
  max-width: none;
}

.reports-detail-pane {
  flex: 1;
  min-width: 0;
  max-width: none;
}

.reports-back {
  display: none;
  align-self: flex-start;
}

@media (max-width: 900px) {
  .reports {
    display: block;
    max-width: 560px;
  }

  .reports-list-pane,
  .reports-detail-pane {
    flex: none;
  }

  .reports.is-detail .reports-list-pane { display: none; }
  .reports:not(.is-detail) .reports-detail-pane { display: none; }
  .reports.is-detail .reports-back { display: inline-block; }
}

/* The status chip reuses .badge's shape but not its accent fill: on this page
   the accent is reserved for the primary action, and a row of accent-yellow
   status pills would out-shout every button on the page. */
.report-list .badge {
  background: var(--surface);
  color: var(--fg);
  font-weight: 600;
}

/* The unread marker keeps the accent — it is the one thing on the page the
   reader is meant to act on. */
.report-list .badge-unread {
  background: var(--accent);
  color: var(--accent-fg);
}

.report-list {
  list-style: none;
  margin: 8px 0 0;
  padding: 0;
}

/* An anchor, not a button: every report has a real URL (/reports?id=…), so a
   row is openable in a new tab and shareable. The page intercepts a plain
   left click to swap the detail pane in place and push that same URL, which is
   what keeps refresh and browser-back working. */
.report-row {
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 6px;
  width: 100%;
  text-align: left;
  text-decoration: none;
  background: none;
  border: 1px solid var(--border);
  border-radius: 8px;
  color: var(--fg);
  font: inherit;
  padding: 8px 12px;
  margin-bottom: 6px;
  cursor: pointer;
}

.report-row:hover {
  border-color: var(--border-hover);
  background: var(--bg-raised);
}

.report-row.is-selected {
  border-color: var(--accent);
  background: var(--bg-raised);
}

.report-row-title {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.report-row-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
}

.report-msg {
  border-left: 3px solid var(--border);
  padding: 4px 0 4px 12px;
  margin: 10px 0;
}

.report-msg.is-operator {
  border-left-color: var(--accent);
}

/* Report text is whatever the reporter typed, newlines included — the server
   keeps line structure on multi-line fields precisely so it can be shown. */
.report-body {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  margin: 4px 0;
}

/* A body rendered as blocks (inbox.js renderBody): a notice's item lines are a
   real list (spec/moderation.md §8.1), so the body is paragraphs and lists
   rather than one run of text, and the reset above has left them with no
   spacing of their own. Paragraph line breaks still survive, because
   `white-space: pre-wrap` is inherited from the body itself. */
.report-body > * + * {
  margin-top: 10px;
}

/* The items a notice names (moderation.md §8.1): secondary to the sentence
   that introduces them, so smaller and muted. An item that names something on
   this site is that name as a link (inbox.js fillListItem): body colour so it
   reads as the item and not as an address, a quiet underline so it reads as
   openable, the accent on hover like every other link on the site. */
.report-body-list {
  padding-left: 20px;
  font-size: 0.9em;
  color: var(--fg-dim);
}

.report-body-list li + li {
  margin-top: 4px;
}

.report-body-list a {
  color: var(--fg);
  text-decoration: underline;
  text-decoration-color: var(--fg-dim);
  text-underline-offset: 2px;
}

.report-body-list a:hover {
  color: var(--accent);
  text-decoration-color: var(--accent);
}

/* ── The report reader (the detail pane of /reports) ─────────────── */

/* The reader wraps its own sections, so it sits between them and .card's flex
   gap and has to supply the spacing itself; without this every heading butts
   against the block above it. */
.report-reader {
  display: flex;
  flex-direction: column;
  gap: 14px;
}

/* One labelled block of the submission. The heading is small and dim so the
   reporter's own words carry the page rather than our field names. */
.report-section {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.report-section h2 {
  font-size: 12px;
  font-weight: 650;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--fg-dim);
}

/* Error detail is a machine-produced stack, not prose: monospace, scrollable,
   and never allowed to stretch the card. */
.report-detail-block {
  font-family: var(--font-mono);
  font-size: 12px;
  line-height: 1.5;
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 10px 12px;
  max-height: 260px;
  overflow: auto;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* The questionnaire answers, one row per question. */
.report-answers {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.report-thread-empty {
  color: var(--fg-dim);
  font-size: 13px;
}

.kv {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  font-size: 14px;
}

.kv .k {
  color: var(--fg-dim);
}

.danger-zone {
  border-color: var(--danger-border);
}

.muted {
  color: var(--fg-dim);
  font-size: 13px;
}

/* ── Homepage ────────────────────────────────────────────────────── */
/* Everything below is homepage-only and namespaced `home-`. The other five
   pages that share this sheet use .page/.card and never match these rules.
   Colours come from the :root tokens above; no literal hexes here. */

/* The design is a 1180px canvas with 40px side padding, i.e. 1100px of
   content. The max-width container is ours (the design has none) and is set
   so the full-width rendering matches the canvas exactly. */
.home {
  flex: 1;
  width: 100%;
  /* The column IS the lead block: 52ch prose + 40px gap + two 336px clips
     + their 12px gap = 1173px, + 2x40px section padding. The hero and the
     other blocks left-align against this column and leave any slack on the
     right; the column centers in the page. */
  max-width: 1253px;
  margin: 0 auto;
  padding: 0 40px;
}

/* Left-aligned hero, 900px measure. Spacing is by margin, not flex gap, so
   the sub-line and CTA row keep their own distinct rhythm. */
.home .hero {
  display: block;
  flex: 0 0 auto;
  padding: 96px 0 72px;
  max-width: 900px;
  text-align: left;
}

.home .hero h1 {
  /* 3.5rem = the design's 56px. */
  font-size: clamp(2.125rem, 1.2rem + 3.6vw, 3.5rem);
  line-height: 1.06;
  letter-spacing: -0.025em;
  font-weight: 600;
  max-width: none;
  text-wrap: balance;
}

.home .tagline {
  font-size: clamp(1.0625rem, 0.95rem + 0.45vw, 1.25rem);
  line-height: 1.5;
  color: var(--fg);
  max-width: 720px;
  margin-top: 26px;
  text-wrap: pretty;
}

/* The whole page is left-aligned, so the CTA rows are too. */
.home .cta-row {
  justify-content: flex-start;
  margin-top: 36px;
  gap: 12px;
}

.home .cta {
  padding: 13px 22px;
  border-radius: 6px;
  font-size: 15px;
}

.home .cta-secondary {
  border-color: transparent;
  font-weight: 500;
}

/* One continuous surface divided by rules: sections are separated by a top
   border, never by a background change. */
.home-section {
  padding: 36px 0;
  border-top: 1px solid var(--border);
}

/* There is no standalone examples strip: every feature block carries the
   example that proves it, as an expression line plus this link. */
.home-example-link {
  font-size: 14.5px;
  color: var(--accent);
  text-decoration: none;
}

.home-example-link:hover {
  text-decoration: underline;
}

/* Expressions are bare accent monospace lines, not boxed inputs. min-width: 0
   plus overflow-x lets a long one scroll inside its grid cell instead of
   forcing the page wide. */
.home-expr {
  display: block;
  width: 100%;
  min-width: 0;
  overflow-x: auto;
  color: var(--accent);
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: 15px;
  line-height: 1.5;
  white-space: nowrap;
}

/* ── Feature blocks ──────────────────────────────────────────────── */

/* Five blocks at descending weight, each separated from the previous by a
   rule. The section's own border-top serves the first. */
.home-features {
  padding: 0;
}

.home-feature {
  display: grid;
  /* Text shrinks to its 52ch wrap and the media follows at the grid gap,
     so prose and example sit together with no dead space between them. */
  grid-template-columns: fit-content(52ch) auto;
  justify-content: start;
  gap: 40px;
  align-items: start;
  padding: 36px 0;
}

.home-feature + .home-feature {
  border-top: 1px solid var(--border);
}

.home-feature-body {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  min-width: 0;
}

.home-feature-body > * + * {
  margin-top: 10px;
}

.home-feature h2 {
  font-size: 22px;
  line-height: 1.2;
  letter-spacing: -0.015em;
  font-weight: 600;
}

.home-feature p {
  font-size: 16px;
  line-height: 1.55;
  color: var(--fg);
  max-width: 52ch;
}

/* Inline code inside prose stays inline, unlike the standalone .home-expr. */
.home-feature p code {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.95em;
  color: var(--accent);
}

/* The example sits below the prose: expression first, then the link into the
   calculator. Both override the body's 10px default rhythm. */
.home-feature-body .home-expr {
  margin-top: 18px;
  font-size: 14.5px;
}

.home-feature-body .home-example-link {
  margin-top: 20px;
}

/* A block whose expression IS the demonstration carries static MathQuill in
   place of the text line (spec/site.md §6, currently only the
   n-dimensional-arrays block). It takes the same slot and the same rhythm as
   .home-expr, and the same accent colour: it is that block's expression line,
   so it reads as one of the five, not as a quotation from the docs.

   The rendered markup carries its own absolute font size, baked in at build
   time from HOME_MATH_FONT_SIZE_PX in scripts/render-home-math.mjs — MathQuill
   output does not scale with an inherited font-size. --home-math-size below is
   that same number, and it is what a delimiter drawn by mathquill.css sizes
   against, so the two must never drift apart.

   min-width/overflow-x mirror .home-expr: a wide expression scrolls inside the
   column instead of forcing the page wide. It cannot reflow — the glyph
   positions are measured and absolute — so scrolling is the only honest
   narrow-viewport behaviour, and the 915px rule that steps .home-expr down to
   13.5px deliberately leaves this alone. */
.home-expr-math {
  display: block;
  width: 100%;
  min-width: 0;
  margin-top: 18px;
  overflow-x: auto;
  color: var(--accent);
  font-size: var(--home-math-size);
}

/* The lead block is deliberately heavier than the four below it: more type,
   more room, a wider media column. This asymmetry is the point of the layout.
   The media column carries the two clips side by side rather than one box. */
.home-feature-lead {
  /* Both columns shrink to content like the other blocks; this block's
     natural span defines the page column (see .home). */
  grid-template-columns: fit-content(52ch) auto;

  justify-content: start;
  gap: 40px;
  padding: 36px 0 40px;
}

.home-feature-lead h2 {
  font-size: 24px;
  line-height: 1.18;
  letter-spacing: -0.016em;
}

.home-feature-lead p {
  font-size: 16.5px;
}

.home-feature-lead .home-expr {
  margin-top: 20px;
  font-size: 15px;
}

/* ── Lead-block clips ────────────────────────────────────────────── */

/* The two clips are one comparison, so they sit side by side in the lead
   block's media column and shrink together. The 336px track cap is the
   delivery contract, not decoration: it is the widest a clip ever renders at
   any breakpoint, and the encoded files are 2x it (672px). Raising it means
   re-encoding, so raise both or neither. */
.home-clips {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 336px));
  justify-content: start;
  gap: 12px;
}

.home-clip {
  min-width: 0;
}

/* aspect-ratio (not a fixed height) reserves the box before the poster loads,
   so nothing shifts when the clip starts. */
.home-clip-video {
  display: block;
  width: 100%;
  height: auto;
  /* The video toggles playback on click (spec/site.md section 6), so it reads
     as the control it is. */
  cursor: pointer;
  aspect-ratio: 16 / 9;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-raised);
}

/* Eyebrow treatment: monospace in the accent, per the type scale. */
.home-clip-label {
  margin-top: 8px;
  color: var(--accent);
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: 12.5px;
  letter-spacing: 0.01em;
}

/* ── Feature-block media ─────────────────────────────────────────── */

/* Blocks 2-5 carry one recorded clip each (spec/site.md §6). The media column
   is fluid, but the clip is not: it never renders wider than 336 CSS px — the
   same delivery cap as the lead clips, and the encoded file is 2x it (672) —
   so it is centred in the column rather than stretched across it. Raising the
   cap means re-recording, so raise both or neither. */
.home-media {
  display: flex;
  flex-direction: column;
  align-items: center;
}

/* The same frame as .home-shot, so a block with real media still reads as a
   media box beside the ones still waiting for an asset. A block's media is a
   clip or a still (spec/site.md §6) and the two are framed identically — only
   what fills the frame differs. */
.home-media-video,
.home-media-image {
  display: block;
  width: 100%;
  max-width: 336px;
  height: auto;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-raised);
}

/* Only the video needs a declared ratio: aspect-ratio (not a fixed height)
   reserves its box before the poster loads, so nothing shifts when the clip
   starts, and 8/5 is the 336x210 delivery box. A still's own width/height
   attributes already give the browser the same ratio. The pointer cursor is
   the other thing only the video wants: clicking it toggles playback
   (spec/site.md section 6), and the still in the arrays block does nothing. */
.home-media-video {
  aspect-ratio: 8 / 5;
  cursor: pointer;
}

/* The playback control's positioning context (spec/site.md section 6,
   "Playback control"). It wraps the video and nothing else, so the control
   lands on the clip's own bottom-right corner rather than on the caption
   below it or on the fluid media column around it. 336px is the same delivery
   cap the clips themselves carry, moved out here so the overlay tracks the
   clip's edge and not the column's. */
.home-clip-frame {
  position: relative;
  display: block;
  width: 100%;
  max-width: 336px;
  min-width: 0;
}

/* The pause/play control index.js overlays on every clip (WCAG 2.2.2). A small
   round icon button, opaque so it stays legible over whatever frame is under
   it; the glyph is drawn in currentColor and hidden from assistive technology,
   and the button's aria-label carries the name. The keyboard indicator is the
   shared button:focus-visible ring at the top of this sheet. */
.home-clip-toggle {
  position: absolute;
  right: 8px;
  bottom: 8px;
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border: 1px solid var(--border);
  border-radius: 50%;
  background: var(--surface);
  color: var(--fg);
  cursor: pointer;
  opacity: 1;
  transition: opacity 120ms ease, background 120ms ease;
}

.home-clip-toggle:hover {
  background: var(--surface-hi);
}

/* Out of the way while the clip runs, and never out of the DOM: opacity only,
   so it stays a tab stop and a screen reader still finds it. display:none
   here would take the pause control away from exactly the reader who needs
   it most. */
.home-clip-frame.is-playing .home-clip-toggle {
  opacity: 0;
}

/* Back the moment it could be wanted: the pointer over the clip or its block,
   or keyboard focus anywhere in the frame. The focus-visible selector is
   spelled out as well as focus-within so the ring is never drawn around
   something invisible, whichever of the two a browser settles first. */
.home-clip:hover .home-clip-frame.is-playing .home-clip-toggle,
.home-media:hover .home-clip-frame.is-playing .home-clip-toggle,
.home-clip-frame.is-playing:focus-within .home-clip-toggle,
.home-clip-frame.is-playing .home-clip-toggle:focus-visible {
  opacity: 1;
}

/* Placeholder image boxes: the diagonal stripe reads unmistakably as "asset
   pending". Keep it until real assets land. */
.home-shot {
  display: grid;
  place-items: center;
  height: 210px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: repeating-linear-gradient(
    135deg,
    var(--placeholder-stripe) 0 8px,
    var(--bg-raised) 8px 16px
  );
}

.home-shot span {
  padding: 5px 10px;
  border: 1px dashed var(--surface-hi);
  border-radius: 4px;
  color: var(--fg-dim);
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: 12px;
}

/* ── Notebook section ────────────────────────────────────────────── */

/* The second product gets its own section, not a feature block: a wide intro
   above, then sub-points beside a taller media box. */
.home-notebook {
  padding: 64px 0 72px;
  /* Shrink-wrapped and left-aligned to the column edge like every other
     section. */
  width: fit-content;
}

.home-notebook-intro {
  max-width: 760px;
}

.home-notebook-intro h2 {
  font-size: 30px;
  line-height: 1.15;
  letter-spacing: -0.02em;
  font-weight: 600;
  text-wrap: balance;
}

.home-notebook-intro p {
  margin-top: 14px;
  font-size: 16.5px;
  line-height: 1.55;
  text-wrap: pretty;
}

.home-notebook-grid {
  display: grid;
  /* Natural-width columns so the section shrink-wraps. The shot column is
     the lead block's media width (two 336px clips + 12px = 684px) at the
     same 40px gap, so the section is exactly as wide as the column and a
     landscape page screenshot sits uncropped (spec/site.md §5). */
  grid-template-columns: fit-content(52ch) 684px;
  gap: 40px;
  align-items: start;
  margin-top: 40px;
}

.home-notebook-points {
  display: flex;
  flex-direction: column;
  gap: 26px;
  min-width: 0;
}

.home-notebook-point h3 {
  font-size: 19px;
  line-height: 1.25;
  letter-spacing: -0.012em;
  font-weight: 600;
}

.home-notebook-point p {
  margin-top: 9px;
  font-size: 16px;
  line-height: 1.55;
  max-width: 52ch;
}

/* The button is one more item in the sub-point column, so it takes the
   column's 26px gap rather than the hero CTA row's 36px margin. */
.home-notebook .cta-row {
  margin-top: 0;
}

/* The notebook shot is a notebook page, so portrait-leaning and taller than
   the feature boxes. */
.home-notebook .home-shot {
  height: 420px;
}

/* The real page screenshot: the width of its column, height from its own
   intrinsic size (width/height attributes reserve the box before load),
   framed like the clips. */
.home-notebook-shot {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--border);
  border-radius: 6px;
}

/* ── Homepage responsive ─────────────────────────────────────────── */
/* The design has no responsive behaviour at all; everything below is ours. */

@media (max-width: 900px) {
  .home {
    padding: 0 28px;
  }

  .home .hero {
    padding: 72px 0 56px;
  }

  .home-feature,
  .home-feature-lead,
  .home-notebook-grid {
    gap: 28px;
  }

  /* The lead block collapses one breakpoint earlier than the others: two clips
     sharing a squeezed media column fall to about 208px each here, below what
     the stutter needs. Prose above, clips at full width, keeps them at 336.
     The notebook grid carries the same 684px media column, so it collapses
     here too; the shot then spans the column at up to its own width. */
  .home-feature-lead,
  .home-notebook-grid {
    grid-template-columns: 1fr;
  }

  .home-notebook-shot {
    max-width: 684px;
  }
}

@media (max-width: 760px) {
  .home {
    padding: 0 20px;
  }

  .home .hero {
    padding: 48px 0 40px;
  }

  .home .tagline {
    margin-top: 20px;
  }

  .home .cta-row {
    margin-top: 28px;
  }

  /* Both feature layouts and the notebook grid collapse to one column; the
     visual follows the prose it supports. */
  .home-feature,
  .home-feature-lead,
  .home-notebook-grid {
    grid-template-columns: 1fr;
    gap: 22px;
  }

  .home-shot {
    height: 170px;
  }

  .home-notebook {
    padding: 44px 0 52px;
  }

  .home-notebook-intro h2 {
    font-size: 25px;
  }

  .home-notebook-grid {
    margin-top: 28px;
  }

  .home-notebook .home-shot {
    height: 300px;
  }
}

/* The hero CTAs cover both PRODUCTS on small screens, so those two links drop
   and the bar stays on one line. Docs and Pricing stay: nothing else on the
   page leads to either, and the top bar is their only entry point.

   560, not the 460 the rest of the small-screen rules use: four nav links plus
   the brand and the auth slot need about 520px, so between 460 and 560 the
   auth slot wrapped onto a second line. This breakpoint is set by the WIDEST
   nav, so adding a fifth link means measuring it again. */
@media (max-width: 560px) {
  .topbar-link-product {
    display: none;
  }

  .topbar-nav {
    margin-left: 8px;
  }
}

@media (max-width: 460px) {
  .topbar {
    padding: 0 14px;
  }

  .home {
    padding: 0 16px;
  }

  .home-expr,
  .home-feature-body .home-expr,
  .home-feature-lead .home-expr {
    font-size: 13.5px;
  }

  .home-feature h2 {
    font-size: 20px;
  }

  .home-feature-lead h2 {
    font-size: 22px;
  }

  .home-notebook-intro h2 {
    font-size: 22px;
  }

  .home-notebook-point h3 {
    font-size: 18px;
  }

  .home .cta {
    padding: 12px 18px;
  }

  /* Below 460 a side-by-side pair is under 180px a clip, where the stutter
     stops reading. They stack instead, still capped at the 336px delivery
     width so the encode is never upscaled. */
  .home-clips {
    grid-template-columns: minmax(0, 336px);
    gap: 18px;
  }
}

/* ── Pricing ─────────────────────────────────────────────────────── */

/* /pricing (spec/site.md §9). The container is the homepage's exactly (1180
   with 40px sides), so the two pages share a left edge and the same page
   rhythm; only the tier grid below is narrowed, since a comparison read side
   by side wants a shorter line than the homepage's feature blocks do. The tier
   boxes take .home-shot's frame treatment (1px border, 6px radius, raised
   background) so a tier reads as the same kind of panel the rest of the site
   uses. */

.pricing {
  flex: 1;
  width: 100%;
  max-width: 1180px;
  margin: 0 auto;
  padding: 88px 40px 96px;
}

.pricing-head {
  max-width: 640px;
}

.pricing h1 {
  font-size: 40px;
  line-height: 1.1;
  letter-spacing: -0.025em;
  font-weight: 600;
  text-wrap: balance;
}

.pricing-intro {
  margin-top: 14px;
  font-size: 16.5px;
  line-height: 1.55;
  text-wrap: pretty;
}

/* Equal columns: the two tiers are read against each other, so neither gets
   more width. align-items is left at its stretch default so both boxes take
   the taller one's height and the pair reads as one comparison. */
.pricing-tiers {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 24px;
  max-width: 900px;
  margin-top: 44px;
}

.pricing-tier {
  display: flex;
  flex-direction: column;
  min-width: 0;
  padding: 28px 28px 30px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-raised);
}

/* Pro is distinguished by a lighter frame only. It already carries the page's
   one accent CTA (spec/site.md §9), so an accent frame on top of that would
   double the emphasis and flatten the pair it is meant to be compared with. */
.pricing-tier-pro {
  border-color: var(--surface-hi);
}

.pricing-tier-head {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 10px;
  padding-bottom: 18px;
  border-bottom: 1px solid var(--border);
}

.pricing-tier-name {
  font-size: 20px;
  line-height: 1.2;
  letter-spacing: -0.015em;
  font-weight: 600;
}

/* margin-left: auto, so both boxes put their headline fact in the same place. */
.pricing-price {
  margin-left: auto;
  font-size: 28px;
  line-height: 1.1;
  letter-spacing: -0.02em;
  font-weight: 650;
}

/* The billing period rides the price at body weight: it qualifies the figure
   rather than competing with it, and keeps "$4.99" the thing the eye lands on
   at the same size Free's "$0" is read at. */
.pricing-period {
  font-size: 15px;
  font-weight: 500;
  letter-spacing: 0;
  color: var(--fg-dim);
}

/* flex: 1 pushes each box's call to action to the foot of the box, so the two
   line up across the pair however the lists differ in length. */
.pricing-list {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 12px;
  margin-top: 22px;
  list-style: none;
  font-size: 16px;
  line-height: 1.5;
}

/* The marker is drawn rather than a list-style bullet so it takes the accent
   and sits on the first line rather than on the block's centre. */
.pricing-list li {
  position: relative;
  padding-left: 22px;
}

.pricing-list li::before {
  content: "";
  position: absolute;
  left: 4px;
  top: 0.55em;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--accent);
}

/* The page is left-aligned, like the homepage, so its one CTA row is too. */
.pricing-cta {
  justify-content: flex-start;
  margin-top: 26px;
}

.pricing .cta {
  padding: 13px 22px;
  border-radius: 6px;
  font-size: 15px;
}

/* The Pro call to action is a <button> when it starts a checkout and an <a>
   otherwise (spec/account-ui.md §3), so the button has to be talked out of the
   UA's own font and cursor to sit level with the anchor beside it. */
button.cta {
  font-family: inherit;
  line-height: inherit;
  cursor: pointer;
}

button.cta:disabled {
  opacity: 0.55;
  cursor: default;
  transform: none;
}

.pricing-note {
  margin-top: 26px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--fg-dim);
}

/* The billing terms and the test-mode notice sit directly above the button they
   qualify, so they close up against it rather than reading as separate blocks. */
.pricing-note + .pricing-cta,
.pricing-note + .pricing-soon,
.pricing-note + .notice-warning,
.notice-warning + .pricing-cta {
  margin-top: 16px;
}

/* The pre-launch "Coming soon" label (spec/account-ui.md §3) stands where the
   Pro call to action will: the CTA's height and type size, so the two boxes
   still end level, but no fill, border or pointer, so nothing reads as
   clickable. */
.pricing-soon {
  padding: 13px 0;
  font-size: 15px;
  font-weight: 600;
  color: var(--fg-dim);
}

/* The tiers stack at the site's existing 760px breakpoint: below it the two
   columns put the longer feature lines onto three and four lines each. */
@media (max-width: 900px) {
  .pricing {
    padding: 64px 28px 72px;
  }

  .pricing h1 {
    font-size: 34px;
  }
}

@media (max-width: 760px) {
  .pricing {
    padding: 48px 20px 60px;
  }

  .pricing h1 {
    font-size: 30px;
  }

  .pricing-tiers {
    grid-template-columns: 1fr;
    gap: 18px;
    margin-top: 32px;
  }
}

@media (max-width: 460px) {
  .pricing {
    padding: 40px 16px 52px;
  }

  .pricing h1 {
    font-size: 27px;
  }

  .pricing-tier {
    padding: 22px 20px 24px;
  }
}

/* ── Contact ─────────────────────────────────────────────────────── */

/* /contact (spec/site.md §10). Same container as /pricing and the homepage
   (1180 with 40px sides), so all three share a left edge and page rhythm.
   The page is pure prose — four routes and a closing line — so it needs no
   grid of its own, only a measure short enough to read. */

.contact {
  flex: 1;
  width: 100%;
  max-width: 1180px;
  margin: 0 auto;
  padding: 88px 40px 96px;
}

.contact-head {
  max-width: 640px;
}

.contact h1 {
  font-size: 40px;
  line-height: 1.1;
  letter-spacing: -0.025em;
  font-weight: 600;
  text-wrap: balance;
}

/* 640px at 16.5px is roughly the 72ch the docs read at, one notch tighter.
   Each route is its own paragraph and they are scanned rather than read
   straight through, so the gap between them is wider than the line height. */
.contact-body {
  display: flex;
  flex-direction: column;
  gap: 22px;
  max-width: 640px;
  margin-top: 32px;
  font-size: 16.5px;
  line-height: 1.6;
  text-wrap: pretty;
}

/* The bold lead-in is the routing key: a reader picks their paragraph by it
   without reading the sentence. Full --fg against the body's inherited
   colour, so it separates by weight AND value. */
.contact-body strong {
  font-weight: 650;
  color: var(--fg);
}

/* Sits below the four routes as a note about all of them, not a fifth route,
   so it drops to the secondary colour and takes extra space above. */
.contact-close {
  margin-top: 6px;
  font-size: 15px;
  color: var(--fg-dim);
}

@media (max-width: 900px) {
  .contact {
    padding: 64px 28px 72px;
  }

  .contact h1 {
    font-size: 34px;
  }
}

@media (max-width: 760px) {
  .contact {
    padding: 48px 20px 60px;
  }

  .contact h1 {
    font-size: 30px;
  }

  .contact-body {
    margin-top: 26px;
    font-size: 16px;
  }
}

@media (max-width: 460px) {
  .contact {
    padding: 40px 16px 52px;
  }

  .contact h1 {
    font-size: 27px;
  }
}

/* ── Legal documents ─────────────────────────────────────────────── */

/* /terms, /privacy, /terms-of-sale, /copyright (spec/site.md §11). Generated
   from the root repo's legal/*.md by scripts/build-legal.mjs, so every rule
   here is written against what that renderer emits and nothing else.

   Same container as /contact and /pricing, so the four legal pages share a
   left edge and page rhythm with the rest of the site. The document itself
   takes the docs measure (72ch): these are long documents read straight
   through, and a wider measure is what makes a policy unreadable. */

.legal {
  flex: 1;
  width: 100%;
  max-width: 1180px;
  margin: 0 auto;
  padding: 72px 40px 96px;
}

.legal-doc {
  max-width: 72ch;
  font-size: 16.5px;
  line-height: 1.7;
}

.legal-doc h1 {
  font-size: 34px;
  font-weight: 650;
  letter-spacing: -0.02em;
  line-height: 1.15;
  text-wrap: balance;
  margin-bottom: 20px;
}

/* The document's metadata, under the title: a version and a date, and only
   what a reader can act on (spec/site.md §11 sanitises the rest away). One
   quiet line rather than the source's monospaced block — a preformatted box at
   the top of a legal page reads as a draft artefact. It wraps to one item per
   line on a narrow screen, so the separator is drawn per item rather than
   between them. */
.legal-doc .legal-meta {
  display: flex;
  flex-wrap: wrap;
  gap: 4px 14px;
  margin: 0 0 34px;
  padding: 0;
  list-style: none;
  font-size: 13.5px;
  line-height: 1.5;
  color: var(--fg-dim);
}

.legal-doc .legal-meta li {
  margin: 0;
}

.legal-doc .legal-meta li + li {
  padding-left: 14px;
  border-left: 1px solid var(--border);
}

/* Numbered sections. A legal document is navigated by section number, so the
   rule above each h2 gives the eye the same landmark the numbering does. */
.legal-doc h2 {
  font-size: 21px;
  font-weight: 620;
  letter-spacing: -0.012em;
  margin: 44px 0 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border);
}

.legal-doc h3 {
  font-size: 16.5px;
  font-weight: 620;
  margin: 30px 0 8px;
}

.legal-doc p {
  margin-bottom: 16px;
  text-wrap: pretty;
}

.legal-doc ul,
.legal-doc ol {
  margin: 0 0 16px 22px;
}

.legal-doc li {
  margin-bottom: 10px;
}

.legal-doc strong {
  font-weight: 650;
  color: var(--fg);
}

/* The `---` between the plain-language summary and the operative sections. */
.legal-doc hr {
  height: 1px;
  margin: 36px 0;
  border: 0;
  background: var(--border);
}

/* Cross-references to the other three documents, and the four addresses. The
   accent is the site's link colour; these are the only links in the body, so
   they do not need to be quieter than it. */
.legal-doc a {
  color: var(--accent);
  text-decoration: none;
}

.legal-doc a:hover {
  text-decoration: underline;
}

/* Inline code here is a placeholder still to be filled (`[ENTITY - TBD]`),
   never math, so it takes the surface treatment without the accent that
   spec/site.style-guide.md reserves for the site's math voice. */
.legal-doc code {
  font-family: ui-monospace, "Cascadia Code", "Fira Code", Consolas, monospace;
  font-size: 0.87em;
  color: var(--fg-dim);
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 5px;
}

/* A reproduced licence text on /open-source (spec/site.md §11). It is quoted
   material published as its author wrote it, so it keeps its own line breaks
   and is set apart from the page's prose rather than reflowed into it. The
   wrapping is what keeps the body from scrolling sideways: a licence is hard
   wrapped at about 80 columns, which is wider than the measure on a phone, and
   `pre-wrap` folds the overlong lines while leaving the author's own breaks
   intact. */
.legal-doc pre.legal-licence {
  margin: 0 0 16px;
  padding: 16px 18px;
  font-family: ui-monospace, "Cascadia Code", "Fira Code", Consolas, monospace;
  font-size: 12.5px;
  line-height: 1.55;
  color: var(--fg-dim);
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 6px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

/* The generator emits .docs-table for every table, so the docs' own table
   rules already apply. Only the overflow behaviour differs: a three-column
   lawful-basis table is wider than the measure on a phone, and the page body
   must never scroll sideways. */
.legal-doc .docs-table {
  display: block;
  max-width: 100%;
  overflow-x: auto;
}

@media (max-width: 900px) {
  .legal {
    padding: 56px 28px 72px;
  }

  .legal-doc h1 {
    font-size: 30px;
  }
}

@media (max-width: 760px) {
  .legal {
    padding: 44px 20px 60px;
  }

  .legal-doc {
    font-size: 16px;
  }

  .legal-doc h1 {
    font-size: 27px;
  }

  .legal-doc h2 {
    font-size: 19px;
    margin-top: 36px;
  }
}

@media (max-width: 460px) {
  .legal {
    padding: 36px 16px 52px;
  }

  .legal-doc h1 {
    font-size: 24px;
  }
}

/* ── Utilities ───────────────────────────────────────────────────── */

/* Visually hidden but available to assistive tech. Used for the docs filter
   label, whose placeholder alone is not an accessible name. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ── Docs ────────────────────────────────────────────────────────── */

/* Pages under /docs, generated by scripts/build-docs.mjs from
   documentation/calculator/ (spec/site.md §8). The site is a dense marketing
   surface; documentation is read rather than scanned, so prose here steps up to
   a wider measure and a larger base while the chrome stays at site density. */

:root {
  /* Font size math boxes render AND display at. MathQuill bakes measured
     bracket/radical sizes in as absolute values at build time, so the render
     harness in scripts/lib/mathquillRender.mjs uses this same number and
     test/docs-build.test.ts asserts the two never drift apart. */
  --docs-box-font-size: 15px;

  /* Font size inline $$math$$ renders AND displays at. Matches docs prose, and
     must equal PROSE_FONT_SIZE_PX in scripts/lib/mathquillRender.mjs — the
     rendered markup carries an absolute size and does not scale. */
  --docs-inline-math-size: 16.5px;
}

.docs {
  flex: 1;
  width: 100%;
  margin: 0 auto;
}

.docs-home {
  max-width: 1000px;
  padding: 48px 24px 72px;
}

.docs-home h1 {
  font-size: 30px;
  font-weight: 650;
  letter-spacing: -0.02em;
  margin-bottom: 24px;
}

/* Filter. Narrows the list already on the page; see site/docs.js. */
.docs-filter input {
  width: 100%;
  max-width: 420px;
  padding: 9px 12px;
  background: var(--bg-raised);
  /* The field's only boundary, so --border-control (audit M7). */
  border: 1px solid var(--border-control);
  border-radius: 6px;
  color: var(--fg);
  font: inherit;
  font-size: 14px;
}

.docs-filter input::placeholder {
  color: var(--fg-dim);
}

/* The border swap is this field's own "you are typing here" state and stays.
   What it must NOT do is suppress the outline as well: the shared
   :focus-visible ring is the keyboard indicator, and a border swap alone is a
   1px colour change (spec/site.md §6). */
.docs-filter input:focus {
  border-color: var(--accent);
}

/* The UA's own clear affordance duplicates Escape and sits off-palette. */
.docs-filter input::-webkit-search-cancel-button {
  -webkit-appearance: none;
  appearance: none;
}

.docs-filter-note {
  margin-top: 12px;
  font-size: 14px;
  color: var(--fg-dim);
}

.docs-no-results {
  margin-top: 24px;
  color: var(--fg-dim);
}

/* Two columns of sections, so the whole index stays within a screen or two as
   it grows. Sections are kept whole across the column break. */
.docs-index-grid {
  margin-top: 32px;
  columns: 2;
  column-gap: 48px;
}

.docs-index-section {
  break-inside: avoid;
  margin-bottom: 32px;
}

.docs-index-section[hidden] {
  display: none;
}

.docs-index-section h2 {
  font-size: 15px;
  font-weight: 650;
  padding-bottom: 8px;
  margin-bottom: 10px;
  border-bottom: 1px solid var(--border);
}

.docs-index-section ul,
.docs-toc ul {
  list-style: none;
}

.docs-index-section a {
  display: flex;
  align-items: baseline;
  gap: 12px;
  padding: 5px 8px;
  margin-left: -8px;
  border-radius: 6px;
  color: var(--fg);
  text-decoration: none;
  font-size: 14.5px;
}

.docs-index-section a:hover {
  background: var(--bg-raised);
}

.docs-index-sub {
  color: var(--fg-dim);
}

/* A listed article with no source file yet. Marked rather than hidden: the
   index is a complete picture of the docs, gaps included (spec/site.md §8). */
.docs-draft-tag {
  margin-left: auto;
  font-size: 10px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
  opacity: 0.75;
}

.docs-draft-dot {
  display: inline-block;
  width: 5px;
  height: 5px;
  margin-left: 7px;
  vertical-align: middle;
  border-radius: 50%;
  background: var(--accent);
  opacity: 0.6;
}

/* ── Docs: math boxes ────────────────────────────────────────────── */

/* A calculator box, reproduced rather than screenshotted. Chrome mirrors the
   app's .slot (app/src/boxes/container/BoxContainerView.module.css): raised
   background, 1px border, 6px radius, 38px minimum height. The output row
   mirrors .calcbox-output — right-aligned, dimmed, 0.82em, "=" then the
   result. Content is MathQuill markup pre-rendered at build time. */

.docs-boxes {
  margin: 0 0 16px;
}

.docs-box-group {
  display: flex;
  gap: 10px;
  align-items: flex-start;
}

/* Stacked boxes are ONE unit, the way consecutive rows of a calculator box
   are: no gap, shared borders collapsed, only the outer corners rounded. The
   group is sized to its widest row and every box fills it, so the stack is a
   clean rectangle rather than a ragged staircase. Mirrors the app's
   chainAbove/chainBelow treatment. */
.docs-boxes--column .docs-box-group {
  flex-direction: column;
  gap: 0;
  width: fit-content;
  max-width: 100%;
  align-items: stretch;
}

.docs-boxes--column .docs-box {
  width: 100%;
}

.docs-boxes--column .docs-box:not(:first-child) {
  /* Collapse the shared 1px border rather than doubling it. */
  margin-top: -1px;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}

.docs-boxes--column .docs-box:not(:last-child) {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}

.docs-boxes--row .docs-box-group {
  flex-direction: row;
  flex-wrap: wrap;
}

/* Side-by-side boxes each keep their own width rather than splitting the row
   evenly, for the same reason a single box hugs its content. */
.docs-boxes--row .docs-box {
  flex: 0 0 auto;
}

/* DOCS-ONLY DEVIATIONS FROM THE APP'S BOX. The app's box is full-width with a
   smaller result on its own line below the input: an expression there is
   arbitrarily long and the result must never collide with it. A figure's
   content is fixed and known when it is written, so neither constraint
   applies, and both are dropped here:
     - result on the same line as the input, at the same size;
     - box sized to its content instead of the column width.
   These are documentation styles. The calculator is unchanged. */
.docs-box {
  display: flex;
  flex-direction: row;
  /* BASELINE, not center. The input and the result are separately-rendered
     MathQuill fragments with different heights — a radical or a fraction makes
     the input box taller than a plain result — so centring their BOXES leaves
     their glyph baselines on different lines and the result reads as sitting
     high. Aligning the baselines is what actually puts the two on one line.
     Vertical centring inside the box comes from the symmetric padding. */
  align-items: baseline;
  gap: 20px;
  width: fit-content;
  max-width: 100%;
  min-height: 38px;
  padding: 14px 20px;
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 6px;
  font-size: var(--docs-box-font-size);
  overflow-x: auto;
}

.docs-box-in {
  color: var(--fg);
}

/* Same size and baseline as the input, so the two read as one line. The '=' is
   part of the rendered math (see build-docs.mjs), not a text node beside it,
   which is what makes it share MathQuill's size and baseline instead of
   sitting smaller and lower. Grey, as in the app. */
.docs-box-out {
  display: inline-flex;
  /* Pass the inner fragment's baseline up to .docs-box, which aligns on it. */
  align-items: baseline;
  margin-left: auto;
  padding-left: 24px;
  color: var(--fg-dim);
}

/* ── Docs: inline math ───────────────────────────────────────────── */

/* $$latex$$ in prose. The rendered markup carries its own absolute size, set
   at build time to --docs-inline-math-size, so this only has to keep the
   glyphs sitting on the text baseline. */
.docs-math {
  display: inline-block;
  vertical-align: baseline;
}

.docs-boxes figcaption {
  margin-top: 8px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--fg-dim);
}

/* MathQuill ships an accessibility description alongside the visual markup.
   Keep it for screen readers, out of the visual flow. */
.docs-box .mq-mathspeak {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip-path: inset(50%);
}

/* ── Docs: animations ────────────────────────────────────────────── */

/* Recorded clips of the real calculator (spec/site.md §8.2). The frame matches
   a math box's chrome so a figure reads the same whether it is a still box or
   a clip. Width is capped rather than stretched: the recording has a fixed
   pixel size and upscaling it just makes the UI blurry. */

.docs-anim {
  margin: 0 0 16px;
}

/* The width/height attributes carry the recorded size, so the clip renders
   1:1 and the browser never rescales it. max-width is a safety net for a
   narrow viewport only; if it engages regularly the clip was recorded too
   wide and the preset size should come down instead. */
.docs-anim-img {
  display: block;
  max-width: 100%;
  height: auto;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: var(--bg-raised);
}

/* Shown only under prefers-reduced-motion, where the still stands until the
   reader asks for the animation. Hidden by default and revealed by docs.js, so
   with JS off it never appears offering something it could not deliver. */
.docs-anim-play {
  display: inline-block;
  margin-top: 8px;
  padding: 6px 12px;
  font: inherit;
  font-size: 14px;
  color: var(--fg);
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 6px;
  cursor: pointer;
}

.docs-anim-play:hover {
  border-color: var(--accent);
}

.docs-anim-play[hidden] {
  display: none;
}

.docs-anim figcaption {
  margin-top: 8px;
  font-size: 14px;
  line-height: 1.5;
  color: var(--fg-dim);
}

/* ── Docs: article pages ─────────────────────────────────────────── */

.docs-article-page {
  display: grid;
  grid-template-columns: 232px minmax(0, 1fr);
  gap: 48px;
  max-width: 1120px;
  padding: 40px 24px 72px;
}

.docs-toc-section {
  margin-bottom: 20px;
}

.docs-toc-title {
  font-size: 12px;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--fg-dim);
  padding: 0 10px 4px;
}

.docs-toc a {
  display: block;
  padding: 5px 10px;
  border-radius: 6px;
  color: var(--fg-dim);
  text-decoration: none;
  font-size: 14px;
}

.docs-toc a:hover {
  background: var(--surface);
  color: var(--fg);
}

.docs-toc a[aria-current="page"] {
  background: var(--surface);
  color: var(--fg);
  font-weight: 550;
  box-shadow: inset 2px 0 0 var(--accent);
}

.docs-article {
  max-width: 72ch;
}

.docs-crumb {
  font-size: 13px;
  color: var(--fg-dim);
  margin-bottom: 14px;
}

.docs-crumb a {
  color: var(--fg-dim);
  text-decoration: none;
}

.docs-crumb a:hover {
  color: var(--fg);
}

.docs-article h1 {
  font-size: 30px;
  font-weight: 650;
  letter-spacing: -0.02em;
  line-height: 1.2;
  margin-bottom: 24px;
}

.docs-article h2 {
  font-size: 21px;
  font-weight: 620;
  letter-spacing: -0.012em;
  margin: 44px 0 12px;
  padding-bottom: 8px;
  border-bottom: 1px solid var(--border);
}

.docs-article h3 {
  font-size: 16.5px;
  font-weight: 620;
  margin: 30px 0 8px;
}

.docs-article p,
.docs-article li {
  font-size: 16.5px;
  line-height: 1.7;
}

.docs-article p {
  margin-bottom: 16px;
}

.docs-article ul {
  margin: 0 0 16px 22px;
}

.docs-article li {
  margin-bottom: 10px;
}

/* Monospace is the site's math voice and always carries the accent
   (spec/site.style-guide.md, "Global"). Inline code in the docs is math, so it
   follows that rule rather than inventing a second code style. */
.docs-article code {
  font-family: ui-monospace, "Cascadia Code", "Fira Code", Consolas, monospace;
  font-size: 0.87em;
  color: var(--accent);
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 4px;
  padding: 1px 5px;
}

/* Tables: quick-reference grids (e.g. the indexing table in the matrix
   article). Same surface treatment as boxes and inline code: raised
   background, 1px border, 6px radius. Column alignment, when an article
   declares it, arrives as an inline text-align from the generator. */
.docs-table {
  border-collapse: separate;
  border-spacing: 0;
  margin: 0 0 16px;
  font-size: 15px;
  line-height: 1.6;
  background: var(--bg-raised);
  border: 1px solid var(--border);
  border-radius: 6px;
}

.docs-table th,
.docs-table td {
  padding: 8px 14px;
  text-align: left;
  vertical-align: top;
}

.docs-table th {
  font-weight: 620;
  border-bottom: 1px solid var(--border);
}

.docs-table tr + tr td {
  border-top: 1px solid var(--border);
}

/* Pending media. Reuses the homepage's "asset pending" language exactly: the
   diagonal stripe fill plus a dashed mono chip (spec/site.style-guide.md,
   "Media boxes"), so an unfinished docs figure reads the same as an unfinished
   homepage one. */
.docs-figure {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  min-height: 84px;
  margin: 0 0 16px;
  padding: 16px;
  border: 1px solid var(--border);
  border-radius: 6px;
  background: repeating-linear-gradient(
    135deg,
    var(--placeholder-stripe) 0 8px,
    var(--bg-raised) 8px 16px
  );
}

.docs-figure-kind {
  flex-shrink: 0;
  padding: 3px 8px;
  border: 1px dashed var(--surface-hi);
  border-radius: 4px;
  font-family: ui-monospace, "Cascadia Code", "Fira Code", Consolas, monospace;
  font-size: 12px;
  color: var(--accent);
}

.docs-figure-note {
  font-size: 14px;
  color: var(--fg-dim);
}

.docs-stub {
  padding: 16px 20px;
  border-left: 3px solid var(--accent);
  border-radius: 4px;
  background: var(--bg-raised);
  color: var(--fg);
}

.docs-pager {
  display: flex;
  gap: 16px;
  margin-top: 48px;
  padding-top: 16px;
  border-top: 1px solid var(--border);
}

.docs-pager a {
  flex: 1;
  padding: 12px 16px;
  border: 1px solid var(--border);
  border-radius: 6px;
  color: var(--fg);
  text-decoration: none;
  font-size: 14.5px;
}

.docs-pager a:hover {
  border-color: var(--border-hover);
}

.docs-pager span {
  display: block;
  font-size: 12px;
  color: var(--fg-dim);
  margin-bottom: 2px;
}

.docs-pager-next {
  text-align: right;
}

.docs-pager > span:empty {
  flex: 1;
}

@media (max-width: 900px) {
  .docs-index-grid {
    columns: 1;
  }

  /* The TOC moves above the article rather than disappearing: it is the only
     navigation between articles on a narrow screen. */
  .docs-article-page {
    grid-template-columns: minmax(0, 1fr);
    gap: 28px;
  }

  .docs-toc {
    padding-bottom: 20px;
    border-bottom: 1px solid var(--border);
  }
}

@media (max-width: 460px) {
  .docs-home,
  .docs-article-page {
    padding-left: 16px;
    padding-right: 16px;
  }

  .docs-pager {
    flex-direction: column;
  }
}

/* ── Footer ──────────────────────────────────────────────────────── */

/* Flex-centred rather than text-align: center, because the bar now carries
   two items (the copyright and the Contact link) and wants an explicit gap
   between them rather than a collapsed whitespace node. `wrap` so the pair
   stacks instead of overflowing on a narrow phone. */
.footer {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: 6px 20px;
  padding: 18px 24px;
  flex-shrink: 0;
  border-top: 1px solid var(--border);
  color: var(--fg-dim);
  font-size: 13px;
}

/* Contact, Terms and Privacy live here and nowhere else: spec/site.md §10-§11
   put them in the footer of every page carrying the chrome, and
   site.style-guide.md keeps them out of the top bar. They inherit the footer's
   dim colour rather than taking the accent — the footer is the page's quietest
   row, and an accent link in it would outrank the CTAs above.

   Underlined, always: they inherit that dim colour, so without the underline
   nothing but position tells a link apart from the copyright line beside it
   (spec/site.md §6). */
.footer-link {
  color: inherit;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.footer-link:hover {
  color: var(--fg);
}

/* ── The restricted-account page (spec/site.md §6.1) ──────────────── */

/* The header: one labelled field per fact, so the reader can find the one
   they came for without reading a paragraph. Fields reuse .kv; the notice is
   its own block because it is the operator's sentences, not a value. */
.case-header {
  display: flex;
  flex-direction: column;
  gap: 6px;
  margin-bottom: 4px;
}

.case-notice {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.case-notice .k {
  color: var(--fg-dim);
  font-size: 14px;
}

/* Case history, in the compact style of the admin event log: full content
   width, small type, one row per event. The accent stripe marks the reviewer's
   side, exactly as the report thread does, so "who said this" is readable
   before any of the words are. */
.case-log {
  display: flex;
  flex-direction: column;
  gap: 8px;
}

.case-event {
  border-left: 3px solid var(--border);
  padding: 2px 0 2px 10px;
}

.case-event.is-operator {
  border-left-color: var(--accent);
}

.case-event-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 6px;
  color: var(--fg-dim);
  font-size: 12px;
}

.case-event-body {
  margin: 2px 0 0;
  font-size: 13px;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.case-chip {
  display: inline-block;
  border: 1px solid var(--border);
  border-radius: 999px;
  padding: 0 8px;
  font-size: 12px;
  color: var(--fg-dim);
}

/* ── The inbox bell (spec/account-ui.md §2) ───────────────────────── */

/* Sits before the name in the top bar and links to /reports. Positioned so the
   dot can hang off its corner without a wrapper element. */
.topbar-bell {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 5px 8px;
  border-radius: 6px;
  color: var(--fg);
  flex-shrink: 0;
}

.topbar-bell:hover,
.topbar-bell:focus-visible {
  color: var(--fg);
}

/* The one signal: something is waiting. Deliberately not a number, because the
   count mixes unread replies with pending permission requests and the page it
   opens is where the difference lives. */
.topbar-bell-dot {
  position: absolute;
  top: 4px;
  right: 6px;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--warning, #e0a800);
}

/* ── Consent requests on the inbox (spec/moderation.md §7) ────────── */

/* One block per pending request, set apart from the next so the two answers
   below each can never be mistaken for the ones above. */
.consent-request {
  border-left: 3px solid var(--accent);
  padding: 4px 0 4px 12px;
  margin: 12px 0;
}

.consent-actions {
  display: flex;
  gap: 8px;
  margin-top: 8px;
}


/* One item inside a consent batch: what would be shown, and the two answers.
   The prompt sits once above them all, because a batch is one question. */
.consent-item {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin-top: 8px;
}

.consent-item-name {
  margin: 0;
  margin-right: auto;
  font-size: 13px;
  overflow-wrap: anywhere;
}

/* The gate: the account is suspended until this is answered. Set apart because
   it is a fact about the reader's account, not part of the operator's ask. */
.consent-gate {
  margin: 8px 0 0;
  padding: 6px 10px;
  border: 1px solid var(--warning-border);
  background: var(--warning-bg);
  border-radius: 6px;
  font-size: 13px;
}
