/* ──────────────────────────────────────────────────────────
   CNL · shared marketing-page styles (legal + simple pages)
   Matches the landing page system: Geist, Geist Mono, Fraunces.

   THEME TOKENS — the single source of truth for the whole
   marketing site (site.css, app.css and every template style
   block read these; landing.html no longer restates them).

   Light is the DEFAULT and is the product's own `ink` palette
   (cream paper, black ink — the palette solo_barber/barbershop
   tenant sites seed with, from CNLmodel branding/visual.py), so
   the marketing site feels like the product it sells. Dark is
   the site's previously-shipped dark scheme, applied when the
   inline pre-paint script in each base template sets
   <html data-theme="dark"> from localStorage('cnl-theme').
   No stored choice → light, always (the owner's default).
   ────────────────────────────────────────────────────────── */
:root {
  color-scheme: light;

  /* Light is the mirror of dark: white paper, black ink, neutral greys.
     No tint at any step — every earlier attempt at a warm or cool cast read
     as muddy at full-page scale, and the page gets its life from the
     light/dark section rhythm and from real contrast, not from a hue.

     The two muted greys are pinned by WCAG 1.4.3 (4.5:1 for normal-size
     text), computed against every surface they paint on, not just white —
     both tokens carry real content (billing disclosures, footer links,
     placeholders) on --bg-card fills. Don't "tidy" them lighter:
       --muted   #6b6b6b → 5.33:1 on --bg, 5.11:1 on --bg-elev, 4.89:1 on --bg-card
       --muted-2 #707070 → 4.95:1 on --bg, 4.74:1 on --bg-elev, 4.54:1 on --bg-card
     (The old #737373/#8f8f8f pair failed on cards: 4.35:1 and 2.97:1.) */
  --bg: #ffffff;
  --bg-elev: #fafafa;
  --bg-card: #f5f5f5;
  --ink: #0a0a0a;
  --ink-soft: #262626;
  --muted: #6b6b6b;
  --muted-2: #707070;
  --rule: #e5e5e5;
  --rule-strong: #d4d4d4;
  /* Form-control boundary (WCAG 1.4.11 needs 3:1 non-text contrast; the
     decorative --rule-strong hairline is only 1.48:1 on white). 3.45:1 on
     --bg, 3.17:1 on --bg-card. Inputs and radio circles only — card
     hairlines stay on --rule/--rule-strong deliberately. */
  --input-border: #8a8a8a;
  --accent: #0a0a0a;
  --accent-ink: #ffffff;

  /* interaction + surface derivatives */
  --hover-border: #a3a3a3;
  --hover-bg: #f5f5f5;
  --grid-line: rgba(0,0,0,.05);
  --grid-line-soft: rgba(0,0,0,.035);
  --scrim: rgba(10,10,10,.45);
  --shadow-lg: 0 40px 80px rgba(0,0,0,.13);
  --shadow-md: 0 18px 40px rgba(0,0,0,.11);
  --shadow-sm: 0 14px 30px rgba(0,0,0,.09);

  /* browser-frame chrome around tenant-site screenshots/iframes.
     Deliberately the DARK device values in BOTH themes (they match the
     dark block below): demo chrome is black hardware, like the landing
     hero's phone bezel — a product shot, not tinted paper. The warm
     tenant palettes inside the frames read against a bezel this way,
     instead of clashing with the cream page. Derived bits (frame
     borders, URL pills) are pinned to device values where they're
     used: .browser-* in landing.html, .mb-* in site.css. */
  --shell-bg: #050505;
  --shell-chrome: #0c0c0c;
  --shell-dot: #2a2a2a;

  /* status colors */
  --ok: #2e6b4f;
  --ok-bg: rgba(46,107,79,.09);
  --ok-border: rgba(46,107,79,.38);
  --danger: #a8412f;
  --danger-bg: rgba(168,65,47,.08);
  --danger-border: rgba(168,65,47,.38);
  --warn: #8a6420;
  --warn-bg: rgba(138,100,32,.09);
  --warn-border: rgba(138,100,32,.38);
  --info: #3d5a80;
  --info-bg: rgba(61,90,128,.08);
  --info-border: rgba(61,90,128,.38);
  --rose: #9c5b74;
  --rose-bg: rgba(156,91,116,.12);

  /* data-viz greys (chart bars, heat cells) */
  --bar: #d4d4d4;
  --bar-hi: #0a0a0a;
  --cal-0: #f5f5f5;
  --cal-1: #e5e5e5;
  --cal-2: #a3a3a3;
  --cal-3: #525252;
  --cal-4: #0a0a0a;

  --container-w: 1200px;
  --sans: 'Geist', -apple-system, system-ui, 'Segoe UI', sans-serif;
  --mono: 'Geist Mono', ui-monospace, Menlo, monospace;
  --serif: 'Fraunces', Georgia, serif;
}
:root[data-theme="dark"] {
  color-scheme: dark;

  --bg: #000000;
  --bg-elev: #0a0a0a;
  --bg-card: #0f0f0f;
  --ink: #ededed;
  --ink-soft: #d4d4d4;
  --muted: #888888;
  /* Was #767676 — 4.22:1 on --bg-card #0f0f0f (dark input fills,
     theme-toggle text), under the 4.5:1 floor. #808080 → 5.32:1 on --bg,
     4.85:1 on --bg-card, 4.66:1 on --hover-bg #141414. */
  --muted-2: #808080;
  --rule: #1c1c1c;
  --rule-strong: #262626;
  /* 3.66:1 on --bg #000, 3.34:1 on --bg-card #0f0f0f (see light block). */
  --input-border: #666666;
  --accent: #ededed;
  --accent-ink: #000000;

  --hover-border: #3a3a3a;
  --hover-bg: #141414;
  --grid-line: rgba(255,255,255,.04);
  --grid-line-soft: rgba(255,255,255,.03);
  --scrim: rgba(0,0,0,.62);
  --shadow-lg: 0 40px 80px rgba(0,0,0,.6);
  --shadow-md: 0 18px 40px rgba(0,0,0,.5);
  --shadow-sm: 0 14px 30px rgba(0,0,0,.6);

  --shell-bg: #050505;
  --shell-chrome: #0c0c0c;
  --shell-dot: #2a2a2a;

  --ok: #7be0b5;
  --ok-bg: #11241a;
  --ok-border: #2d5a41;
  --danger: #e08585;
  --danger-bg: #160e0e;
  --danger-border: #4a2d2d;
  --warn: #f4c77a;
  --warn-bg: #1a1408;
  --warn-border: #4a3d1f;
  --info: #6fa8ff;
  --info-bg: #0d121a;
  --info-border: #2a3344;
  --rose: #c98aa0;
  --rose-bg: rgba(201,138,160,.13);

  --bar: #2a2a2a;
  --bar-hi: #ededed;
  --cal-0: #161616;
  --cal-1: #262626;
  --cal-2: #4a4a4a;
  --cal-3: #8a8a8a;
  --cal-4: #ededed;

  /* Brand mark (_brand_mark.html + the hand-inlined copies in docs.html
     and errors/500.html). Light mode sets nothing — the svg's inline
     fallbacks paint the Instagram colours. On black the real slate C
     (#2c3640) all but disappears, so dark mode lifts the C to a lighter
     slate of the same hue and nudges the L up with it. */
  --cnl-c-top: #9db0c2; --cnl-c-front: #8698aa; --cnl-c-side: #718396;
  --cnl-l-top: #e6e5e5; --cnl-l-front: #d5d4d4; --cnl-l-side: #c2c1c0;
}
* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--sans);
  font-size: 15px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
}
a { color: inherit; text-decoration: none; }
::selection { background: var(--ink); color: var(--bg); }

/* ── Keyboard focus (WCAG 2.4.7 / 1.4.11) ─────────────────
   One site-wide indicator instead of per-control ad-hoc styles: --ink is
   19.8:1 / 17.9:1 against its page, and the 2px offset keeps it legible on
   filled controls (buttons, the nav CTA) whose own background matches the
   ring color. :focus-visible, not :focus, so mouse clicks don't ring
   everything — and no rule anywhere may set `outline: none` on a focusable
   control without replacing it with an indicator that meets 3:1. */
:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
/* The landing's inverted band paints --ink-on---ink otherwise. */
.hv-band :focus-visible { outline-color: var(--hv-band-ink, var(--bg)); }
/* The skip-link target: main takes focus programmatically (tabindex="-1")
   only so the tab sequence continues from the content — a page-sized ring
   around it would be noise, and 2.4.7 applies to controls, not landmarks. */
main:focus { outline: none; }

/* ── Skip link (WCAG 2.4.1) ───────────────────────────────
   First tabbable element on every page (the three base shells render it
   before the nav): visually hidden until keyboard focus reaches it, then a
   solid chip in the top-left, above the sticky nav. */
.skip-link {
  position: absolute; top: 0; left: -9999px; z-index: 100;
  background: var(--accent); color: var(--accent-ink);
  padding: 10px 16px; border-radius: 0 0 8px 0;
  font-size: 13.5px; font-weight: 500;
}
.skip-link:focus { left: 0; }

.container { max-width: var(--container-w); margin: 0 auto; padding: 0 24px; }
.arrow { display: inline-block; transform: translateY(-1px); }

/* ── NAV ─────────────────────────────────────────────── */
nav.top {
  position: sticky; top: 0; z-index: 50;
  background: color-mix(in srgb, var(--bg) 85%, transparent);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid var(--rule);
}
nav.top .row {
  display: flex; align-items: center; justify-content: space-between;
  height: 64px;
}
.brand {
  display: inline-flex; align-items: center; gap: 8px;
  font-weight: 600; font-size: 15px; letter-spacing: -0.01em;
  color: var(--ink);
}
.brand-mark {
  display: inline-flex; align-items: center; justify-content: center;
  flex: none;
}
/* The mark is portrait (399:591) where the old triangle was square, so it
   sizes by height and width follows. 26px, not the triangle's 22 — at equal
   heights the narrow mark reads lighter next to the 15px wordmark, and the
   64px bar has the room. */
.brand-mark svg { height: 26px; width: auto; aspect-ratio: 399 / 591; }

.nav-back {
  font-family: var(--mono); font-size: 12.5px; color: var(--muted);
  display: inline-flex; align-items: center; gap: 6px;
  transition: color 120ms;
}
.nav-back:hover { color: var(--ink); }

/* The nav's Start pill. The BASE rule lives here, not site.css, so the legal
   shell can carry it too — /contact opts in (base_legal's nav_actions block).
   Explicit color because `a { color: inherit }` above would otherwise paint
   the label ink-on-ink in the light theme. site.css keeps only its
   .nav-links-context override, which must outrank `.nav-links a`. */
.nav-cta {
  background: var(--accent); color: var(--accent-ink);
  padding: 7px 14px; font-size: 13.5px; font-weight: 500;
  border-radius: 6px; transition: opacity 120ms;
}
.nav-cta:hover { opacity: 0.85; }

/* Groups .nav-back + .nav-cta on /contact so the row keeps its two-cluster
   shape (brand left, actions right) instead of space-between scattering
   three children. */
.nav-actions { display: inline-flex; align-items: center; gap: 18px; }
@media (max-width: 480px) {
  /* Not enough room for both on the narrowest phones; the brand link goes
     to the same place the back link does. */
  .nav-actions .nav-back { display: none; }
}

/* ── PAGE HEADER (h1 + lede) ─────────────────────────── */
.page-head {
  border-bottom: 1px solid var(--rule);
  padding: 80px 0 56px;
  position: relative;
}
.page-head::before {
  content: ''; position: absolute; inset: 0;
  background-image:
    linear-gradient(to right, var(--grid-line) 1px, transparent 1px),
    linear-gradient(to bottom, var(--grid-line) 1px, transparent 1px);
  background-size: 56px 56px;
  -webkit-mask-image: radial-gradient(ellipse 70% 90% at 50% 50%, #000 20%, transparent 75%);
          mask-image: radial-gradient(ellipse 70% 90% at 50% 50%, #000 20%, transparent 75%);
  pointer-events: none;
}
.page-head .inner { position: relative; }
.page-eyebrow {
  font-family: var(--mono); font-size: 11px;
  color: var(--muted-2); letter-spacing: 0;
  margin: 0 0 22px;
}
.page-h1 {
  font-family: var(--sans); font-weight: 400;
  font-size: 44px; line-height: 1.08;
  letter-spacing: -0.025em;
  margin: 0; max-width: 22ch; color: var(--ink);
}
.page-meta {
  margin: 22px 0 0;
  font-family: var(--mono); font-size: 12.5px;
  color: var(--muted);
}
.page-lede {
  margin: 18px 0 0;
  font-size: 16px; color: var(--muted);
  max-width: 60ch; line-height: 1.65;
}

/* ── DOC LAYOUT (TOC + body) ─────────────────────────── */
.doc-wrap { padding: 56px 0 96px; }
.doc-grid {
  display: grid;
  grid-template-columns: 220px 1fr;
  gap: 64px;
  align-items: start;
}
.doc-grid.no-toc { grid-template-columns: 1fr; }

/* TOC */
.toc {
  position: sticky; top: 92px;
  max-height: calc(100vh - 120px);
  overflow-y: auto;
  padding-right: 8px;
}
.toc h4 {
  font-family: var(--mono); font-size: 11px;
  font-weight: 400; color: var(--muted-2);
  margin: 0 0 14px; letter-spacing: 0;
}
.toc ol {
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-direction: column; gap: 4px;
  counter-reset: toc;
}
.toc li { counter-increment: toc; }
.toc a {
  display: flex; gap: 10px;
  font-size: 12.5px; color: var(--muted);
  padding: 4px 6px; border-radius: 4px;
  line-height: 1.4;
  transition: color 120ms, background 120ms;
}
.toc a::before {
  content: counters(toc, ".", decimal-leading-zero);
  font-family: var(--mono); font-size: 10.5px;
  color: var(--muted-2); flex-shrink: 0;
  padding-top: 1px;
}
.toc a:hover { color: var(--ink); background: var(--bg-card); }
.toc a.active { color: var(--ink); }
.toc a.active::before { color: var(--ink); }
.toc::-webkit-scrollbar { width: 4px; }
.toc::-webkit-scrollbar-thumb { background: var(--rule-strong); border-radius: 2px; }

/* Mobile TOC */
.toc-mobile {
  display: none;
  border: 1px solid var(--rule-strong);
  border-radius: 8px;
  background: var(--bg-card);
  margin-bottom: 32px;
}
.toc-mobile summary {
  list-style: none;
  cursor: pointer;
  padding: 12px 16px;
  font-family: var(--mono); font-size: 12px;
  color: var(--ink);
  display: flex; align-items: center; justify-content: space-between;
}
.toc-mobile summary::-webkit-details-marker { display: none; }
.toc-mobile summary::after { content: '+'; color: var(--muted); }
.toc-mobile[open] summary::after { content: '−'; }
.toc-mobile .toc { position: static; max-height: 50vh; padding: 4px 12px 14px; }

/* Body */
.doc-body {
  max-width: 720px;
  font-size: 15.5px;
  line-height: 1.72;
  color: var(--ink);
}
.doc-body p,
.doc-body ul,
.doc-body ol,
.doc-body table { margin: 0 0 18px; }
.doc-body p { color: var(--ink-soft); }
.doc-body h2 {
  font-family: var(--sans); font-weight: 500;
  font-size: 22px; line-height: 1.25; letter-spacing: -0.018em;
  color: var(--ink);
  margin: 56px 0 18px;
  scroll-margin-top: 88px;
}
.doc-body h2:first-child { margin-top: 0; }
.doc-body h2 .h2-num {
  font-family: var(--mono); font-size: 13px; font-weight: 400;
  color: var(--muted-2); margin-right: 14px; letter-spacing: 0;
}
.doc-body h3 {
  font-family: var(--sans); font-weight: 500;
  font-size: 16px; line-height: 1.35; letter-spacing: -0.012em;
  color: var(--ink);
  margin: 36px 0 14px;
  scroll-margin-top: 88px;
}
.doc-body h3 .h3-num {
  font-family: var(--mono); font-size: 12px; font-weight: 400;
  color: var(--muted-2); margin-right: 10px;
}
.doc-body h4 {
  font-family: var(--mono); font-size: 12px; font-weight: 400;
  color: var(--muted); letter-spacing: 0;
  margin: 28px 0 10px;
  text-transform: none;
}
.doc-body strong { color: var(--ink); font-weight: 500; }
.doc-body em { font-style: italic; color: var(--ink-soft); }
.doc-body a {
  color: var(--ink);
  border-bottom: 1px solid var(--rule-strong);
  transition: border-color 120ms;
}
.doc-body a:hover { border-color: var(--ink); }
.doc-body ul, .doc-body ol {
  padding-left: 20px;
  color: var(--ink-soft);
}
.doc-body li { margin: 6px 0; }
.doc-body li::marker { color: var(--muted-2); }
.doc-body code {
  font-family: var(--mono); font-size: 13px;
  background: var(--bg-card);
  padding: 2px 6px; border-radius: 4px;
  color: var(--ink);
}
.doc-body blockquote {
  border-left: 2px solid var(--rule-strong);
  padding: 4px 0 4px 18px;
  margin: 18px 0;
  color: var(--muted);
  font-style: italic;
}

/* Tables */
.doc-body .table-wrap {
  overflow-x: auto;
  margin: 0 0 24px;
  border: 1px solid var(--rule);
  border-radius: 8px;
}
.doc-body table {
  width: 100%; border-collapse: collapse;
  font-size: 13.5px;
  margin: 0;
  min-width: 520px;
}
.doc-body thead th {
  background: var(--bg-card);
  font-family: var(--mono); font-weight: 400;
  font-size: 11px; letter-spacing: 0.02em;
  color: var(--muted);
  text-align: left;
  padding: 12px 16px;
  border-bottom: 1px solid var(--rule-strong);
}
.doc-body tbody td {
  padding: 14px 16px;
  border-top: 1px solid var(--rule);
  vertical-align: top;
  color: var(--ink-soft);
  line-height: 1.55;
}
.doc-body tbody td:first-child { color: var(--ink); font-weight: 500; }

/* Inline contact / address block */
.doc-body .contact-block {
  border: 1px solid var(--rule-strong);
  background: var(--bg-card);
  border-radius: 8px;
  padding: 22px 24px;
  margin: 24px 0;
  font-size: 14px;
  line-height: 1.65;
}
.doc-body .contact-block p { margin: 0; color: var(--ink); }

/* ── FOOTER ──────────────────────────────────────────── */
footer.site-foot {
  border-top: 1px solid var(--rule);
  padding: 56px 0 32px;
  background: var(--bg);
}
.footer-slim {
  padding-bottom: 40px;
  display: flex; flex-direction: column; gap: 14px;
}
.footer-slim .brand { display: inline-flex; }
.footer-tag {
  font-size: 13.5px; color: var(--muted);
  max-width: 56ch; margin: 0; line-height: 1.55;
}
.footer-inline {
  display: flex; gap: 14px; align-items: center;
  font-size: 13.5px; color: var(--muted); margin-top: 4px;
  flex-wrap: wrap;
}
.footer-inline a { color: var(--muted); transition: color 120ms; }
.footer-inline a:hover { color: var(--ink); }
.footer-inline .sep { color: var(--muted-2); }
.footer-bot {
  border-top: 1px solid var(--rule); padding-top: 24px;
  display: flex; justify-content: space-between;
  font-family: var(--mono); font-size: 11.5px; color: var(--muted-2);
  flex-wrap: wrap; gap: 12px;
}

/* ── Theme toggle (footer) ───────────────────────────────
   The same L | D segmented control the product's admin login
   uses — platform and product feel related. Lives in the
   footer's bottom row on every base template; writes
   localStorage('cnl-theme') + data-theme on <html> via the
   inline handlers in templates/_theme_toggle.html. */
.theme-seg {
  display: inline-flex; align-items: center; gap: 8px;
}
.theme-seg .theme-seg-label { color: var(--muted-2); }
.theme-seg .seg {
  display: inline-flex; align-items: center; gap: 3px;
  border: 1px solid var(--rule-strong); border-radius: 999px;
  padding: 2px; background: var(--bg-card);
}
.theme-seg button {
  appearance: none; border: 0; background: transparent; cursor: pointer;
  font-family: var(--mono); font-size: 10.5px; line-height: 1;
  /* 7px/10px padding + min-width, not the old 5px/9px: keeps each button at
     or above the 24px target floor (WCAG 2.5.8) without changing the pill's
     look at a glance. */
  color: var(--muted-2); padding: 7px 10px; min-width: 26px; border-radius: 999px;
  transition: color 120ms, background 120ms;
}
.theme-seg button:hover { color: var(--ink); }
.theme-seg button.on { background: var(--ink); color: var(--bg); }

/* ── Simple content pages (contact / privacy choices) ── */
.simple {
  padding: 80px 0 96px;
  border-bottom: 1px solid var(--rule);
}
.simple .inner { max-width: 640px; }
.simple .lede {
  font-size: 16px; line-height: 1.65; color: var(--muted);
  margin: 0 0 32px;
}
.contact-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 16px;
  margin-bottom: 32px;
}
.contact-card {
  border: 1px solid var(--rule-strong);
  background: var(--bg-card);
  border-radius: 10px;
  padding: 22px;
}
.contact-card .role {
  font-family: var(--mono); font-size: 11px;
  color: var(--muted); margin: 0 0 12px;
  letter-spacing: 0;
}
.contact-card .name {
  font-size: 15px; font-weight: 500; color: var(--ink);
  margin: 0 0 6px; letter-spacing: -0.005em;
}
.contact-card a.email {
  font-size: 14px; color: var(--ink);
  border-bottom: 1px solid var(--rule-strong);
}
.contact-card a.email:hover { border-color: var(--ink); }

.contact-block {
  border: 1px solid var(--rule-strong);
  background: var(--bg-card);
  border-radius: 10px;
  padding: 22px;
  display: grid;
  grid-template-columns: 120px 1fr;
  gap: 8px 20px;
  font-size: 14px;
  line-height: 1.65;
  align-items: baseline;
}
.contact-block .k {
  font-family: var(--mono); font-size: 11px;
  color: var(--muted-2);
}
.contact-block .v { color: var(--ink); }
.contact-block .v.addr { font-style: normal; white-space: pre-line; }

/* Forms */
.form-section {
  display: flex; flex-direction: column; gap: 18px;
  margin-top: 8px;
}
.field {
  display: flex; flex-direction: column; gap: 6px;
}
.field label {
  font-family: var(--mono); font-size: 11.5px;
  color: var(--muted); letter-spacing: 0;
}
.field label .req { color: var(--danger); margin-left: 2px; }
.field input[type="text"],
.field input[type="email"],
.field select,
.field textarea {
  width: 100%;
  background: var(--bg-card);
  border: 1px solid var(--input-border);
  border-radius: 6px;
  padding: 11px 14px;
  font-family: var(--sans); font-size: 14px;
  color: var(--ink);
  transition: border-color 120ms, background 120ms;
  font-feature-settings: "ss01" on;
}
.field input::placeholder, .field textarea::placeholder { color: var(--muted-2); }
/* No `outline: none` here: the border shift alone was a 1.70:1 change a
   sighted keyboard user could not see (the old signup-form failure). The
   global :focus-visible ring is the real indicator; the border snaps to
   --ink so mouse-focused fields (no ring) read active too. Was
   --hover-border, which is now LIGHTER than the resting --input-border. */
.field input:focus,
.field select:focus,
.field textarea:focus {
  border-color: var(--ink);
  background: var(--hover-bg);
}
.field textarea {
  resize: vertical; min-height: 100px;
  font-family: var(--sans);
}
.field .hint {
  font-size: 12px; color: var(--muted-2);
}
.field-row {
  display: grid; grid-template-columns: 1fr 1fr; gap: 14px;
}
.radio-group {
  display: flex; flex-wrap: wrap; gap: 8px;
}
.radio-group label {
  flex: 1; min-width: 120px;
  display: inline-flex; align-items: center; gap: 8px;
  border: 1px solid var(--rule-strong);
  background: var(--bg-card);
  border-radius: 6px;
  padding: 10px 14px;
  font-family: var(--sans); font-size: 13.5px;
  color: var(--ink);
  cursor: pointer;
  letter-spacing: -0.005em;
  transition: border-color 120ms, background 120ms;
  text-transform: none;
}
.radio-group label:hover { border-color: var(--hover-border); }
.radio-group input[type="radio"] {
  appearance: none; -webkit-appearance: none;
  width: 14px; height: 14px; border-radius: 50%;
  /* --input-border, not --rule-strong: the circle is the control's boundary
     and needs 3:1 (1.4.11); the label card around it stays a hairline. */
  border: 1px solid var(--input-border);
  background: var(--bg);
  position: relative;
  cursor: pointer;
  flex-shrink: 0;
  margin: 0;
}
.radio-group input[type="radio"]:checked {
  border-color: var(--ink);
}
.radio-group input[type="radio"]:checked::after {
  content: ''; position: absolute; inset: 3px;
  background: var(--ink); border-radius: 50%;
}
.radio-group label:has(input:checked) {
  border-color: var(--ink);
  background: var(--hover-bg);
}

.hp { /* honeypot */
  position: absolute; left: -9999px; width: 1px; height: 1px;
  opacity: 0; pointer-events: none;
}

.btn-primary {
  display: inline-flex; align-items: center; gap: 8px;
  background: var(--accent); color: var(--accent-ink);
  padding: 11px 18px; font-size: 13.5px; font-weight: 500;
  border-radius: 8px; border: 1px solid var(--accent);
  cursor: pointer;
  font-family: var(--sans);
  transition: opacity 120ms;
  align-self: flex-start;
}
.btn-primary:hover { opacity: 0.85; }
.btn-primary:disabled { opacity: 0.5; cursor: not-allowed; }

/* Server-side form errors (signup.html / manage.html render
   <p class="form-error" role="alert">). The class existed in markup with no
   rule anywhere, so errors printed as plain body text — visually
   indistinguishable from copy. Same treatment as signup_readers'
   .reader-error. */
.form-error {
  color: var(--danger); font-size: 13px; line-height: 1.5;
  margin: 0 0 16px;
}

.form-confirm {
  border: 1px solid var(--ok-border);
  background: var(--ok-bg);
  border-radius: 10px;
  padding: 22px 24px;
  display: none;
}
.form-confirm.show { display: block; }
.form-confirm .ok {
  font-family: var(--mono); font-size: 11px;
  color: var(--ok); letter-spacing: 0; margin: 0 0 10px;
}
.form-confirm p { margin: 0; color: var(--ink); font-size: 14.5px; line-height: 1.6; }

/* ── Sticky mobile signup bar (_mobile_cta.html) ─────── */
/* HIDDEN BY DEFAULT AT EVERY WIDTH — the reveal is JS-only (the inline
   script in _mobile_cta.html adds .show once the visitor has scrolled and
   no in-content /signup CTA is on screen). Shipping it visible flashed the
   bar over the landing hero's own Start button on every load, which is the
   bug this state machine exists to prevent; if the script never runs, the
   bar simply never appears (enhancement-only by design).

   Even .show only displays inside the 820px media block: above it the
   sticky top nav keeps its own "Start" CTA on screen (site.css's 820px nav
   breakpoint — the exact width at which that CTA collapses behind the
   hamburger), so a stray .show at desktop widths still shows nothing. The
   base rule stands FIRST and the media override AFTER it: at equal
   specificity source order decides, and the reverse order is the
   invisible-at-every-width bug documented in test_signup_pricing_anim.py.
   The toggle is a plain display flip — no transition, so there is nothing
   prefers-reduced-motion would need to switch off.

   position: sticky, not fixed — as the last element in <body> it reserves
   its own layout slot after the footer: it rides the viewport bottom while
   scrolling and settles into normal flow below the footer at full scroll,
   so it can never hide the footer and needs no body padding. The bottom
   padding folds in env(safe-area-inset-bottom) to clear the iOS home
   indicator. z-index below nav.top's 50; they never overlap anyway. */
.mobile-cta,
.mobile-cta.show { display: none; }
@media (max-width: 820px) {
  .mobile-cta {
    position: sticky; bottom: 0; z-index: 40;
    padding: 10px 16px calc(10px + env(safe-area-inset-bottom));
    background: color-mix(in srgb, var(--bg) 88%, transparent);
    backdrop-filter: saturate(180%) blur(14px);
    -webkit-backdrop-filter: saturate(180%) blur(14px);
    border-top: 1px solid var(--rule);
  }
  .mobile-cta.show { display: block; }
  .mobile-cta .btn-primary {
    display: flex; justify-content: center;
    width: 100%; align-self: auto;
    padding: 13px 22px; font-size: 14.5px;
  }
}

/* ── First-visit consent bar (_consent.html) ─────────── */
/* Slim, bottom, one click to dismiss, and gone for a year once the
   `cnl_privacy` cookie exists — the server stops rendering it, so there is
   no "hidden but still in the DOM" state to reason about.

   position: fixed, NOT sticky. The sticky trick the signup bar uses (last in
   flow, settles below the footer at full scroll) is right for a CTA that may
   scroll away; it is wrong for a notice that must stay reachable until it is
   answered. Fixed costs us the reserved space that sticky gives for free,
   which is what the body padding-bottom below buys back: without it the bar
   permanently covers the last ~76px of every page, and on a legal page that
   is the last paragraph of a policy.

   z-index 60 — above nav.top's 50, because a fixed notice that a sticky
   header can slide over is a notice someone can be shown and never see. All
   colors are tokens, so light and dark come out of the theme block; nothing
   here is a hex. --bg-elev rather than --bg so the bar reads as a layer over
   the page in both themes, and --rule-strong (not --rule) for the top edge
   because a 1.48:1 hairline vanishes against a white page. */
.consent-bar {
  position: fixed; left: 0; right: 0; bottom: 0;
  z-index: 60;
  background: var(--bg-elev);
  border-top: 1px solid var(--rule-strong);
  box-shadow: var(--shadow-md);
}
.consent-inner {
  max-width: var(--container-w);
  margin: 0 auto;
  padding: 10px 20px calc(10px + env(safe-area-inset-bottom));
  display: flex; align-items: center; gap: 16px;
  flex-wrap: wrap; justify-content: space-between;
}
.consent-text {
  margin: 0;
  font-family: var(--sans); font-size: 13px; line-height: 1.5;
  color: var(--muted);
  max-width: 62ch;
}
/* --muted is pinned at 5.11:1 on --bg-elev in light and the dark --muted
   #888888 is pinned likewise (see the token block at the top of this file);
   the inline link lifts to --ink-soft, which clears AA on every surface in
   both themes. Underlined, not color-only — 1.4.1 does not accept a hue
   difference as the only signal that a run of text is a link. */
.consent-text a {
  color: var(--ink-soft);
  text-decoration: underline;
  text-underline-offset: 2px;
}
.consent-actions {
  display: flex; align-items: center; gap: 14px;
  flex-shrink: 0;
}
/* Never lighter than --muted: this is the escape hatch for anyone who does
   not want to accept everything, and a "secondary" link greyed below AA is
   how a preference centre quietly becomes unreachable. */
.consent-manage {
  font-family: var(--sans); font-size: 13px;
  color: var(--ink-soft);
  text-decoration: underline;
  text-underline-offset: 2px;
  white-space: nowrap;
}
/* Deliberately the .btn-primary idiom (same --accent/--accent-ink pair, same
   8px radius, same 500 weight) at one step down in padding, so the bar reads
   as part of the site rather than a vendor widget bolted on. No `outline`
   rule of any kind — the global :focus-visible ring is the focus indicator
   everywhere on this site and this button is not an exception. */
.consent-ok {
  background: var(--accent); color: var(--accent-ink);
  border: 1px solid var(--accent);
  border-radius: 8px;
  padding: 9px 16px;
  font-family: var(--sans); font-size: 13.5px; font-weight: 500;
  cursor: pointer;
  transition: opacity 120ms;
  white-space: nowrap;
}
.consent-ok:hover { opacity: 0.85; }

/* Reserve the bar's height so a fixed element can never permanently cover
   the end of a page. --consent-bar-h is published by the measuring script in
   _consent.html from the bar's real rendered height; the 76px fallback is
   what stands if that script never runs, and is sized for the two-line wrap
   rather than the one-line best case (too much padding is a little dead
   space, too little is hidden content). :has() keeps the whole rule local to
   this block — the alternative was threading a "bar is showing" flag through
   three base templates and every child that overrides a block in them. */
body:has(.consent-bar) {
  padding-bottom: var(--consent-bar-h, 76px);
}
/* Lift the sticky signup bar clear of the notice. That bar is
   position: sticky; bottom: 0 and reveals itself after half a viewport of
   scroll, so on a FIRST visit — the only time the notice renders — the two
   would land on the same strip of screen, with the fixed notice on top of
   the CTA it is sitting over. Same variable, same fallback, so they move
   together. */
body:has(.consent-bar) .mobile-cta {
  bottom: var(--consent-bar-h, 76px);
}

/* Narrow phones: the row cannot hold a sentence and two controls, and
   flex-wrap alone would leave the actions stranded right-aligned under the
   text. Stack, and let the actions sit left under the copy they belong to. */
@media (max-width: 560px) {
  .consent-inner {
    flex-direction: column; align-items: flex-start; gap: 10px;
    padding: 12px 16px calc(12px + env(safe-area-inset-bottom));
  }
  .consent-text { font-size: 12.5px; }
  .consent-actions { width: 100%; justify-content: space-between; }
}

/* ── Mobile ──────────────────────────────────────────── */
@media (max-width: 880px) {
  .page-head { padding: 56px 0 36px; }
  .page-h1 { font-size: 32px; }
  .doc-wrap { padding: 36px 0 64px; }
  .doc-grid { grid-template-columns: 1fr; gap: 0; }
  .toc { display: none; }
  .toc-mobile { display: block; }
  .doc-body { max-width: 100%; }
  .doc-body h2 { font-size: 19px; margin-top: 40px; }
  .contact-grid { grid-template-columns: 1fr; }
  .contact-block { grid-template-columns: 1fr; gap: 4px 0; }
  .contact-block .k { margin-top: 8px; }
  .field-row { grid-template-columns: 1fr; }
  .footer-bot { flex-direction: column; }
}

/* ── Print ───────────────────────────────────────────── */
@media print {
  :root {
    --bg: #ffffff; --ink: #111; --muted: #555; --muted-2: #777;
    --rule: #e0e0e0; --rule-strong: #c8c8c8; --bg-card: #f6f6f4;
  }
  body { background: #fff; color: #111; }
  nav.top, footer.site-foot, .toc, .toc-mobile { display: none !important; }
  .page-head::before { display: none; }
  .doc-grid { grid-template-columns: 1fr; }
  .doc-body { max-width: 100%; }
  .doc-body p, .doc-body li, .doc-body td, .doc-body em { color: #222; }
  .doc-body h2, .doc-body h3, .doc-body h4 { color: #000; page-break-after: avoid; }
  .doc-body a { color: #111; border-bottom-color: #999; }
  .container { max-width: none; padding: 0; }
  .page-head { padding: 0 0 24px; }
  .page-h1 { font-size: 24px; }
}
