/* ══ Public site ══════════════════════════════════════════════════════════
   SEMANTIC TOKENS ONLY. --surface, --border, --text, --accent … never the raw
   palette (--ink-900, --money). Raw palette references cannot theme: they
   resolve to one fixed value, so a component built with them looks right in the
   theme it was written in and unreadable in the other. verify_local.py fails
   the build if one appears here. */

/* ─── base ─────────────────────────────────────────────────────────────── */

body.site {
  background: var(--surface); color: var(--text);
  font-family: var(--font-body); font-size: var(--text-base);
  line-height: var(--leading-normal);
  margin: var(--space-0);
}

body.site h1, body.site h2, body.site h3, .wordmark, .pull, .cmp th {
  font-family: var(--font-display); font-weight: var(--weight-semibold);
  letter-spacing: var(--tracking-tight); line-height: var(--leading-tight);
  color: var(--text-strong);
}
body.site h1 { font-size: var(--text-4xl); margin: var(--space-0) var(--space-0) var(--space-4); }
body.site h2 { font-size: var(--text-2xl); margin: var(--space-0) var(--space-0) var(--space-4); }
body.site h3 { font-size: var(--text-lg);  margin: var(--space-0) var(--space-0) var(--space-2); }
body.site p  { margin: var(--space-0) var(--space-0) var(--space-4); max-width: var(--measure-prose); }
/* The WHOLE selector goes inside :where(), not just the `a`.
   `body.site :where(a)` still scores (0,1,1) — :where() only zeroes what is
   inside it, and `body.site` is element+class. That was enough to beat a plain
   component class: it blanked the sidebar Create-account button once, and after
   the first fix it came back and blanked the mobile bar's CTA the same way,
   brass on brass. `:where(body.site a)` is (0,0,0) — an actual default. */
:where(body.site a) { color: var(--accent); }

/* Every figure, everywhere. */
body.site .num, body.site td, body.site .credstrip, body.site time {
  font-variant-numeric: tabular-nums;
}

/* ─── reading progress ─────────────────────────────────────────────────── */

.progressbar {
  position: fixed; inset: 0 auto auto 0; height: var(--edge-border);
  width: 0; background: var(--accent); z-index: 60;
  transition: width var(--duration-fast) linear;
}

/* ─── header ───────────────────────────────────────────────────────────── */

.sitehead {
  position: sticky; top: 0; z-index: 50;
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4); flex-wrap: wrap;
  padding: var(--space-5) var(--space-5);
  border-bottom: var(--edge-border) solid transparent;
  transition: padding var(--duration-base) var(--ease-standard),
              background var(--duration-base) var(--ease-standard),
              border-color var(--duration-base) var(--ease-standard);
}
.sitehead.condensed {
  padding-block: var(--space-3);
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
  border-bottom-color: var(--border);
}
.headinner {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-4); flex-wrap: wrap;
  width: 100%; max-width: var(--container-wide); margin-inline: auto;
}

/* PROVISIONAL wordmark — brand/02-identity/assets/ has no logo yet. Space Grotesk
   set in the accent stands in until one exists. */
.wordmark {
  font-size: var(--text-lg); color: var(--accent); text-decoration: none;
  letter-spacing: var(--tracking-normal);
}
.navwrap { display: flex; align-items: center; gap: var(--space-4); }
.sitenav { display: flex; gap: var(--space-4); flex-wrap: wrap; }
.sitenav a {
  color: var(--text-muted); text-decoration: none; font-size: var(--text-sm);
  transition: color var(--duration-fast) var(--ease-standard);
}
.sitenav a:hover { color: var(--accent); }

/* 44px minimum hit area — the spec's number, and the platform guideline. */
.themetoggle {
  width: 44px; height: 44px; margin: var(--space-0); padding: var(--space-0);
  display: grid; place-items: center;
  background: transparent; color: var(--text-muted);
  border: var(--edge-border) solid var(--border);
  border-radius: var(--edge-radius-pill); cursor: pointer;
  transition: color var(--duration-fast) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard);
}
.themetoggle:hover { color: var(--accent); border-color: var(--accent); }
.themetoggle:focus-visible { outline: var(--edge-border) solid var(--accent); outline-offset: var(--edge-border); }
.themetoggle svg { width: 20px; height: 20px; }
/* One icon shows per theme; both ship so the swap costs no request. */
.themetoggle .moon { display: none; }
[data-theme="dark"] .themetoggle .moon { display: block; }
[data-theme="dark"] .themetoggle .sun  { display: none; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .themetoggle .moon { display: block; }
  :root:not([data-theme="light"]) .themetoggle .sun  { display: none; }
}

/* ─── layout ───────────────────────────────────────────────────────────── */

.inner { max-width: var(--container-base); margin-inline: auto; padding-inline: var(--space-5); }
.inner.narrow { max-width: var(--container-narrow); }
.inner.wide   { max-width: var(--container-wide); }
.prose p, .prose li { max-width: var(--measure-prose); }

/* Full-bleed bands with contained inner content. The alternation does most of
   the work of making a page feel designed. */
.band { padding-block: var(--space-8); }
.band.raised  { background: var(--surface-raised); }
.band.sunken  { background: var(--surface-sunken); }
.band.tinted  { background: var(--surface-raised); }

/* ─── hero ─────────────────────────────────────────────────────────────── */

.hero {
  position: relative; overflow: hidden;
  min-height: 88vh; display: flex; align-items: center;
  background: var(--surface);
  padding-block: var(--space-7);
}
.hero.short { min-height: auto; padding-block: var(--space-8) var(--space-6); }

/* Grain. Kills the flat-digital feel more effectively than anything else here,
   costs about a kilobyte, and is switched off under reduced motion. */
.hero::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  opacity: var(--edge-grain);
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='g'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.82' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23g)'/%3E%3C/svg%3E");
}

.herogrid {
  display: grid; gap: var(--space-7); align-items: center;
  grid-template-columns: 1.15fr 0.85fr;
  width: 100%; max-width: var(--container-wide);
  margin-inline: auto; padding-inline: var(--space-5);
  position: relative; z-index: 1;
}
.hero h1 {
  font-size: var(--h1-size); letter-spacing: var(--tracking-tight);
  margin-bottom: var(--space-4);
}
.hero .lede { font-size: var(--text-xl); color: var(--text-muted); line-height: var(--leading-relaxed); }

/* Animated rule under the H1. Width, not transform, so it reads as a measure
   being drawn rather than an object sliding in. */
.rule {
  height: var(--edge-border); width: 0; margin-bottom: var(--space-5);
  background: var(--rule-gradient);
  animation: draw var(--duration-slow) var(--ease-enter) forwards;
}
@keyframes draw { to { width: 100%; } }

.credstrip {
  margin-top: var(--space-5); padding-top: var(--space-4);
  border-top: var(--edge-border) solid var(--border);
  font-family: var(--font-mono); font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide); color: var(--text-faint);
}

.portrait { position: relative; }
.portrait img {
  width: 100%; height: auto; display: block;
  border-radius: var(--edge-radius);
  border: var(--edge-border) solid var(--border);
  box-shadow: var(--shadow-lg), var(--shadow-md);
}
.portrait figcaption {
  margin-top: var(--space-3); font-family: var(--font-mono);
  font-size: var(--text-xs); color: var(--text-faint);
}
.assetgap {
  display: grid; place-items: center; aspect-ratio: 1;
  border: var(--edge-border) dashed var(--border-strong);
  border-radius: var(--edge-radius); color: var(--text-faint);
  font-family: var(--font-mono); font-size: var(--text-sm); text-align: center;
  padding: var(--space-5);
}

@media (max-width: 60rem) {
  .herogrid { grid-template-columns: 1fr; gap: var(--space-6); }
  /* Portrait above the text on mobile, capped so it cannot eat the fold. */
  .portrait { order: -1; max-height: 40vh; }
  .portrait img { max-height: 40vh; width: auto; margin-inline: auto; }
  .hero h1 { font-size: var(--text-3xl); }
  .hero { min-height: auto; padding-block: var(--space-6); }
}

/* ─── type ─────────────────────────────────────────────────────────────── */

body.site .lede { font-size: var(--text-xl); color: var(--text-muted); line-height: var(--leading-relaxed); }
.eyebrow {
  color: var(--accent); font-size: var(--text-xs);
  text-transform: uppercase; letter-spacing: var(--tracking-wider);
  margin-bottom: var(--space-2);
}
/* About's opening paragraph carries the page. */
.leadpara { font-size: var(--text-lg); color: var(--text-strong); }

.pull {
  margin-block: var(--space-6); padding: var(--space-5) var(--space-6);
  border-left: var(--edge-border) solid var(--accent);
  font-size: var(--text-2xl); color: var(--text-strong);
  background: var(--surface-raised);
  border-radius: 0 var(--edge-radius-sm) var(--edge-radius-sm) 0;
}

/* The credibility block. Larger than body copy, quieter than a pull quote —
   it is a statement of fact, not a flourish. */
.credblock {
  font-size: var(--text-lg); color: var(--text-strong);
  line-height: var(--leading-relaxed); max-width: var(--measure-prose);
  padding-left: var(--space-5);
  border-left: var(--edge-border) solid var(--accent);
  margin-block: var(--space-0);
}

/* ─── buttons ──────────────────────────────────────────────────────────── */

.ctarow { display: flex; gap: var(--space-3); flex-wrap: wrap; margin-top: var(--space-5); }
.btn {
  display: inline-block; width: auto; margin: var(--space-0);
  padding: var(--space-3) var(--space-5); border-radius: var(--edge-radius-sm);
  font-weight: var(--weight-semibold); font-size: var(--text-sm);
  text-decoration: none; cursor: pointer;
  transition: background var(--duration-fast) var(--ease-standard),
              transform var(--duration-fast) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard);
}
/* translateY only — a scale transform reads consumer-app, not practitioner. */
.btn:hover { transform: translateY(-1px); }
.btn.primary {
  background: var(--accent); color: var(--accent-contrast);
  border: var(--edge-border) solid var(--accent);
}
.btn.primary:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn.ghost {
  background: transparent; color: var(--accent);
  border: var(--edge-border) solid var(--border-strong);
}
.btn.ghost:hover { border-color: var(--accent); }

/* ─── comparison table ─────────────────────────────────────────────────── */

.cmp { width: 100%; border-collapse: collapse; margin-top: var(--space-5); }
.cmp th, .cmp td {
  padding: var(--space-4) var(--space-4);
  border-bottom: var(--edge-border) solid var(--border);
  text-align: left; vertical-align: top;
}
.cmp thead th { font-size: var(--text-lg); }
.cmp th[scope="row"] {
  font-family: var(--font-mono); font-size: var(--text-xs);
  text-transform: uppercase; letter-spacing: var(--tracking-wide);
  color: var(--text-faint); font-weight: var(--weight-regular);
  white-space: nowrap;
}
.cmp .feel { color: var(--text-muted); }
.cmp .inst { background: var(--accent-soft); color: var(--text-strong); }
.cmp thead .inst { color: var(--accent); }
@media (max-width: 46rem) {
  .cmp, .cmp thead, .cmp tbody, .cmp tr, .cmp th, .cmp td { display: block; }
  .cmp thead { display: none; }
  .cmp tr { margin-bottom: var(--space-5); border-bottom: var(--edge-border) solid var(--border); }
  .cmp td { border-bottom: 0; padding-block: var(--space-2); }
  .cmp td::before {
    display: block; font-family: var(--font-mono); font-size: var(--text-xs);
    text-transform: uppercase; letter-spacing: var(--tracking-wide);
    color: var(--text-faint); margin-bottom: var(--space-1);
  }
  .cmp td.feel::before { content: "Running on feel"; }
  .cmp td.inst::before { content: "Running on instruments"; }
}

/* ─── pillar cards ─────────────────────────────────────────────────────── */

.pillargrid {
  display: grid; gap: var(--space-5); margin-top: var(--space-6);
  grid-template-columns: repeat(auto-fit, minmax(17rem, 1fr));
}
.pillarcard {
  position: relative; overflow: hidden;
  display: flex; flex-direction: column;
  min-height: 20rem; padding: var(--space-6);
  text-decoration: none; color: var(--text);
  background: var(--surface-raised);
  border: var(--edge-border) solid var(--border);
  border-top: 3px solid var(--accent);
  border-radius: var(--edge-radius);
  transition: transform var(--duration-base) var(--ease-standard),
              box-shadow var(--duration-base) var(--ease-standard),
              border-color var(--duration-base) var(--ease-standard);
}
.pillarcard:hover { transform: translateY(-4px); box-shadow: var(--shadow-md); border-color: var(--accent); }
.pillarcard p { font-size: var(--text-sm); color: var(--text-muted); }
.pillarcard .cardlink { margin-top: auto; padding-top: var(--space-4); }
/* Ghosted numeral gives each card an identity without needing imagery. */
.pillarcard .ghost {
  position: absolute; right: var(--space-4); bottom: var(--space-2);
  font-family: var(--font-display); font-size: 8rem; line-height: 1;
  color: var(--accent); opacity: 0.08; pointer-events: none;
}
.pillarcard.dim { border-top-color: var(--border-strong); }
.pillarcard.dim .badge-status { color: var(--text-faint); border-color: var(--border); }
.cardlink { color: var(--accent); font-size: var(--text-sm); font-weight: var(--weight-medium); }

.badge-status {
  display: inline-block; margin-bottom: var(--space-3);
  padding: var(--space-1) var(--space-3); border-radius: var(--edge-radius-pill);
  font-size: var(--text-xs); letter-spacing: var(--tracking-wide);
  color: var(--accent);
  border: var(--edge-border) solid color-mix(in srgb, var(--accent) 40%, transparent);
}

/* ─── about strip ──────────────────────────────────────────────────────── */

.aboutstrip {
  display: grid; gap: var(--space-6); align-items: center;
  grid-template-columns: 0.8fr 1.2fr;
}
@media (max-width: 52rem) { .aboutstrip { grid-template-columns: 1fr; } }
.aboutstrip img {
  width: 100%; height: auto; border-radius: var(--edge-radius);
  border: var(--edge-border) solid var(--border);
}

/* ─── capture, affiliate, markers ──────────────────────────────────────── */

.capture {
  margin-block: var(--space-7);
  background: var(--surface-raised);
  border: var(--edge-border) solid var(--border);
  border-radius: var(--edge-radius); padding: var(--edge-pad);
}
.captureform { display: flex; gap: var(--space-2); flex-wrap: wrap; margin-top: var(--space-4); }
.captureform input {
  flex: 1 1 16rem; padding: var(--space-3) var(--space-4);
  background: var(--surface); color: var(--text);
  border: var(--edge-border) solid var(--border-strong);
  border-radius: var(--edge-radius-sm); font: inherit;
}
.captureform input:focus-visible { outline: var(--edge-border) solid var(--accent); outline-offset: var(--edge-border); }
.captureform button {
  width: auto; margin: var(--space-0); padding: var(--space-3) var(--space-5);
  background: var(--accent); color: var(--accent-contrast);
  border: 0; border-radius: var(--edge-radius-sm);
  font: inherit; font-weight: var(--weight-semibold); cursor: pointer;
}
.micro { color: var(--text-faint); font-size: var(--text-xs); margin-top: var(--space-3); }

/* Disclosure above the link, at body weight, never muted, never collapsed.
   That is what clear and conspicuous means. */
.affiliate {
  margin-block: var(--space-5); padding: var(--space-5);
  background: var(--surface-raised);
  border: var(--edge-border) solid var(--border);
  border-radius: var(--edge-radius);
}
.disclosure { color: var(--text); font-size: var(--text-base); margin-bottom: var(--space-4); }
.affiliate-cta { background: var(--accent); color: var(--accent-contrast); }
.footnote { color: var(--text-muted); font-size: var(--text-sm); }

.needscopy {
  color: var(--warning); font-family: var(--font-mono); font-size: var(--text-sm);
  border: var(--edge-border) dashed color-mix(in srgb, var(--warning) 50%, transparent);
  border-radius: var(--edge-radius-sm); padding: var(--space-3) var(--space-4);
}
.unresolved {
  margin-bottom: var(--space-5); padding: var(--space-3) var(--space-4);
  background: var(--surface-raised);
  border-left: var(--edge-border) solid var(--warning);
  border-radius: 0 var(--edge-radius-sm) var(--edge-radius-sm) 0;
  color: var(--text-muted); font-size: var(--text-sm);
}
.creds { padding-left: var(--space-5); }
.creds li { margin-bottom: var(--space-2); }
li.needscopy { list-style: none; margin-left: calc(var(--space-5) * -1); }

/* ─── footer ───────────────────────────────────────────────────────────── */

.sitefoot {
  margin-top: var(--space-9); background: var(--surface-sunken);
  border-top: var(--edge-border) solid var(--border);
  padding-block: var(--space-7);
}
.footinner {
  max-width: var(--container-base); margin-inline: auto;
  padding-inline: var(--space-5);
  display: flex; flex-direction: column; gap: var(--space-3);
}
.footnav { display: flex; gap: var(--space-4); flex-wrap: wrap; }
.footnav a { color: var(--text-muted); text-decoration: none; font-size: var(--text-sm); }
.footnav a:hover { color: var(--accent); }
.legalline {
  color: var(--text-faint); font-size: var(--text-xs);
  margin-top: var(--space-3); padding-top: var(--space-3);
  border-top: var(--edge-border) solid var(--border);
}

.visually-hidden {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}
.legal h2 { font-size: var(--text-xl); }
.legal h3 { font-size: var(--text-base); color: var(--text-muted); }

/* ─── scroll reveal ────────────────────────────────────────────────────────
   The hidden state is applied FROM JS (.reveal-armed), never in base CSS. A
   scroll-reveal that hides by default renders a blank page for anyone with JS
   disabled or blocked. */

.reveal-armed {
  opacity: 0; transform: translateY(16px);
  transition: opacity var(--duration-slow) var(--ease-enter),
              transform var(--duration-slow) var(--ease-enter);
  transition-delay: var(--reveal-delay, 0ms);
}
.reveal-armed.revealed { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  /* Content stays visible — reduced motion means no animation, not no content. */
  .reveal-armed { opacity: 1; transform: none; transition: none; }
  .rule { width: 100%; animation: none; }
  .hero::after { display: none; }
  .btn:hover, .pillarcard:hover { transform: none; }
}

/* ─── member accounts ──────────────────────────────────────────────────── */

.authform { max-width: var(--container-narrow); }
.authform label.wide { display: flex; flex-direction: column; gap: var(--space-2); margin-bottom: var(--space-4); }
.authform input[type="email"],
.authform input[type="password"],
.authform input[type="text"],
.authform input:not([type]) {
  padding: var(--space-3) var(--space-4);
  background: var(--surface-raised); color: var(--text);
  border: var(--edge-border) solid var(--border-strong);
  border-radius: var(--edge-radius-sm); font: inherit;
}
.authform input:focus-visible {
  outline: var(--edge-border) solid var(--accent);
  outline-offset: var(--edge-border);
}
.authform button {
  width: auto; margin-top: var(--space-4);
  padding: var(--space-3) var(--space-5);
  background: var(--accent); color: var(--accent-contrast);
  border: 0; border-radius: var(--edge-radius-sm);
  font: inherit; font-weight: var(--weight-semibold); cursor: pointer;
}
.authform button.danger { background: var(--danger); color: var(--text-inverse); }
.authform .check { margin: var(--space-4) var(--space-0); }

.formerror {
  padding: var(--space-3) var(--space-4);
  border-left: var(--edge-border) solid var(--danger);
  background: var(--surface-raised); color: var(--text);
  border-radius: 0 var(--edge-radius-sm) var(--edge-radius-sm) 0;
}

/* A reminder, not a gate — the free content is already unlocked. */
.verifybanner {
  margin-top: var(--space-4); padding: var(--space-3) var(--space-4);
  border-left: var(--edge-border) solid var(--warning);
  background: var(--surface-raised); color: var(--text-muted);
  border-radius: 0 var(--edge-radius-sm) var(--edge-radius-sm) 0;
  font-size: var(--text-sm);
}

.docstamp {
  font-family: var(--font-mono); font-size: var(--text-xs);
  letter-spacing: var(--tracking-wide); color: var(--text-faint);
  margin: var(--space-0) var(--space-0) var(--space-3);
}
.subnav form.inline { display: inline; margin: var(--space-0); }
.subnav form.inline button { width: auto; margin: var(--space-0); }
.subnav .away { margin-left: auto; }

/* ══ App shell ════════════════════════════════════════════════════════════
   Panels of --surface-raised on --surface. The page surface is one step darker
   than raised (lighter in light theme), so a panel reads as raised without a
   shadow doing the work — that is the v3 panel model. Semantic + edge tokens
   only; no literals. */

body.shellbody { background: var(--surface); }

.app {
  max-width: var(--app-max); margin-inline: auto; padding: var(--edge-gap);
  display: grid; grid-template-columns: 212px 1fr; gap: var(--edge-gap);
  align-items: start;
}
@media (max-width: 920px) {
  .app { grid-template-columns: 1fr; padding: 0; gap: 0; }
}

/* ─── sidebar ──────────────────────────────────────────────────────────── */

.side {
  background: var(--surface-raised);
  border: var(--edge-border) solid var(--border);
  border-radius: var(--edge-radius);
  padding: var(--space-4);
  position: sticky; top: var(--edge-gap); height: fit-content;
  box-shadow: var(--shadow-sm);
}
/* Under 920 it stops being a sidebar and becomes a 56px bar. */
.sidetop { display: block; }
.baracts { display: none; }        /* desktop: the account block is in the panel */

@media (max-width: 920px) {
  .side {
    /* A bottom hairline, not a shadow: the bar sits on the page surface, and a
       shadow under it reads as a floating card rather than the top of the
       site. */
    position: sticky; top: 0; z-index: 20;
    border-radius: 0; border-width: 0 0 var(--edge-border);
    padding: 0 var(--space-4);
    box-shadow: none;
  }
  .sidetop {
    display: flex; align-items: center; gap: var(--space-3);
    height: 56px;
  }
  .mark {
    margin: 0; padding: 0; border-bottom: 0; flex: 1; min-width: 0;
    font-size: var(--text-base); line-height: 1;
  }
  /* The strapline is the first thing to go — the bar is 56px and the wordmark
     has to share it with two controls. */
  .mark small { display: none; }
  .baracts { display: flex; align-items: center; gap: var(--space-2); }
  .navtoggle {
    width: 44px; height: 44px; margin: 0; padding: 0; flex: none;
    display: grid; place-items: center;
    background: transparent; color: var(--text-muted);
    border: var(--edge-border) solid var(--border);
    border-radius: var(--edge-radius-sm); cursor: pointer;
  }
  .navtoggle svg { width: 22px; height: 22px; }
  .navtoggle .ex { display: none; }
  .navtoggle[aria-expanded="true"] { color: var(--text-strong); }
  .navtoggle[aria-expanded="true"] .bars { display: none; }
  .navtoggle[aria-expanded="true"] .ex { display: block; }

  /* The panel is closed by default and slides down from the bar. A CLASS, not
     an attribute, so with no script the nav is still in the document and
     reachable — see the html.js rule below.

     The 920 in this query's condition is repeated as --nav-collapse on the
     base .sidepanel rule further down, which is what the script reads. It is
     declared outside every query on purpose — see the comment there. */
  .sidepanel {
    padding: var(--space-2) 0 var(--space-4);
    border-top: var(--edge-border) solid var(--border);
    animation: slidedown var(--duration-base) var(--ease-enter);
  }
  /* Only when script is running — otherwise the hamburger would be inert
     and the nav unreachable. html.js is set by the shell's own script. */
  html.js .sidepanel:not(.open) { display: none; }
  @keyframes slidedown {
    from { opacity: 0; transform: translateY(-6px); }
    to   { opacity: 1; transform: none; }
  }
  .side nav a { min-height: 44px; }
  .sidefoot { justify-content: flex-start; }
  .sidefoot .themetoggle { margin-left: 0; }
  /* The account block is already in the bar on mobile. */
  .acct { display: none; }
}

/* Desktop: no hamburger. The collapse rule above lives inside the mobile media
   query, so there is nothing here to undo. */
@media (min-width: 921px) { .navtoggle { display: none; } }

@media (prefers-reduced-motion: reduce) {
  .sidepanel { animation: none; }
}

/* The lockup: tick · OBNAMIA · hairline · slogan. One row on the 56px bar; in
   the 212px sidebar the slogan wraps under the wordmark, because the row is
   about 270px wide and the column is not. */
.mark {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  text-decoration: none;
  padding-bottom: var(--space-3);
  border-bottom: var(--edge-border) solid var(--border);
  margin-bottom: var(--space-3);
}
/* A MARK IS NOT BODY TEXT, and it was behaving like it.
   `style.css` carries a global `a:hover { text-decoration: underline }` and it
   outranks `.mark { text-decoration: none }` on hover — (0,1,0) against
   (0,1,1) — so the wordmark underlined like a sentence in a paragraph. This is
   a specificity fix and a design decision in the same three lines: the mark
   gets a mark's hover state instead, the tick lifting to the accent's hover
   token and the name to full strength. */
.mark:hover, .mark:focus-visible { text-decoration: none; }
.mark:hover .mkname { color: var(--text-strong); }
.mark:hover .mktick { background: var(--accent-hover); }
.mktick {
  width: 9px; height: 9px; flex: none;
  background: var(--accent);
  border-radius: calc(var(--edge-radius-sm) / 3);
  transition: background var(--duration-fast) var(--ease-standard);
}
.mkname {
  font-family: var(--font-display); font-weight: 700;
  font-size: var(--text-base); color: var(--text-strong);
  letter-spacing: var(--tracking-wide); white-space: nowrap;
}
/* Only ever drawn where the slogan sits beside the wordmark. In the sidebar it
   would be a divider between a line and the line under it. */
.mkdiv { display: none; width: 1px; height: 13px; flex: none; background: var(--border-strong); }
@media (max-width: 920px) { .mkdiv { display: block; } }
/* Below 600px the tick and the wordmark stay and everything else goes. */
@media (max-width: 600px) { .mkdiv, .mark small { display: none; } }
.mark small {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); text-transform: uppercase;
  color: var(--text-faint); font-weight: var(--weight-regular);
  white-space: nowrap;
}

.side nav { display: flex; flex-direction: column; gap: var(--space-1); }
.side nav a {
  display: flex; align-items: center; gap: var(--space-2);
  /* The 3px active bar is a transparent border here, so an item does not shift
     sideways by 3px the moment it becomes current. */
  padding: var(--space-2) var(--space-3);
  border-left: 3px solid transparent;
  border-radius: var(--edge-radius-sm); text-decoration: none;
  color: var(--text-muted);
  font-family: var(--font-body); font-size: var(--edge-nav-size);
  text-transform: var(--edge-nav-transform); letter-spacing: var(--edge-nav-tracking);
  transition: color var(--duration-fast) var(--ease-standard),
              background var(--duration-fast) var(--ease-standard);
}
/* Four signals at once — bar, fill, weight, glow. Background-and-colour alone
   was not legible at a glance, and colour by itself is not a signal everyone
   receives. The bar carries the item's own accent, so which section you are in
   is readable from the colour as well as the position. */
.side nav a:hover { background: var(--surface); color: var(--text-strong); }
.side nav a.on {
  background: var(--surface-input);
  color: var(--text-strong);
  border-left-color: var(--accent);
  font-weight: var(--weight-semibold);
}
/* The pillar square. data-pillar on the link sets --accent, so one rule
   colours every item without a class per pillar. */
.side nav a i {
  width: 7px; height: 7px; flex: none;
  border-radius: calc(var(--edge-radius-sm) / 3);
  background: var(--accent);
}
.side nav a.on i { box-shadow: 0 0 10px var(--accent); }
.side nav a .lk {
  margin-left: auto; font-family: var(--font-mono);
  font-size: var(--edge-label-size); color: var(--text-faint);
}

/* ── the hub's rail: categories, then products ────────────────────────────
   The public site's rail is a flat map of four destinations; the hub's launches
   products, and products belong to categories. Both are the same <nav> — these
   rules add the grouping, and the rows above still style the anchors.

   The accent is set by data-pillar on the GROUP, from ops/modules.yml, so one
   rule colours four categories four ways. It used to be one hard-coded token
   on every row. */
.railgrp { margin-bottom: var(--space-3); }
.railcat {
  display: flex; align-items: center; gap: var(--space-2);
  margin: 0 0 var(--space-1); padding: 0 var(--space-3);
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); text-transform: uppercase;
  color: var(--accent); font-weight: var(--weight-medium);
}
.railcat i {
  width: 6px; height: 6px; flex: none;
  border-radius: calc(var(--edge-radius-sm) / 3); background: currentColor;
}
/* Indented under their heading, so the rail reads as a tree rather than a
   list — the same grouping the grid shows, in a narrower column. */
.side nav a.railrow, .side nav .railrow {
  padding-left: var(--space-6);
}
.side nav .railrow em {
  margin-left: auto; font-style: normal;
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  font-variant-numeric: tabular-nums;
  /* The category's accent, not --text-faint. At this size and this weight the
     faint token is technically compliant and reads as unreadable. */
  color: var(--accent);
}
/* Planned rows are present and are NOT links — the old rail dropped them, so
   the grid promised four brain games and the rail knew about none of them.
   Dashed edge and a muted label. Never opacity: it composites text toward the
   page background and defeats the contrast the tokens guarantee. */
.side nav .railrow.soon {
  display: flex; align-items: center;
  padding: var(--space-2) var(--space-3) var(--space-2) var(--space-6);
  border-left: 3px dashed var(--border-strong);
  border-radius: var(--edge-radius-sm);
  color: var(--text-faint); cursor: default;
  font-family: var(--font-body); font-size: var(--edge-nav-size);
}
/* Overview, Admin and Account: full-width, unindented, no category. */
.side nav a.railtop { padding-left: var(--space-3); }
/* ── the RESTING state of a top row, and only the resting state ───────────
   Forrest, twice: "overview looks good when selected, but when not, it kinda
   blends in", and then "the overview tab doesn't make that nice view". The
   current state was approved and shipped; the row it sits on had no treatment
   of its own at rest, so between visits it read as a stray link floating above
   the categories rather than as the block that leads them.

   `:not([aria-current="page"])` rather than source order. Both rules would
   otherwise be (0,3,1) and whichever came later would win, which makes "the
   current state is untouched" a fact about line numbers. This way it cannot
   reach the current state at all.

   The left border stays 3px, not the mockup's 2px: `.side nav a` reserves 3px
   transparent so a row does not shift sideways by the width of its own bar the
   moment it becomes current. Tokens are the repo's — the mockup's --sunk,
   --line and --line2 are --surface-sunken, --border and --border-strong. */
.side nav a.railtop:not([aria-current="page"]) {
  background: var(--surface-sunken);
  border: var(--edge-border) solid var(--border);
  border-left: 3px solid var(--border-strong);
  border-radius: var(--edge-radius-sm);
}
/* Overview carries the open-loop total. Same treatment as a category row's
   number so the rail reads as one column of figures, not two conventions. */
.side nav a.railtop em {
  margin-left: auto; font-style: normal;
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  font-variant-numeric: tabular-nums; color: var(--accent);
}
.raildiv {
  height: var(--edge-border); background: var(--border);
  margin: var(--space-3) var(--space-3) var(--space-3);
}

/* Who you are, at the foot of the rail. Displayed, never invented — a
   break-glass session says "operator session" rather than an address. */
.whoami .wnote {
  margin: 0; font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); text-transform: uppercase;
  color: var(--text-faint);
}
.whoami .wlabel {
  margin: var(--space-1) 0 0; font-size: var(--text-sm);
  color: var(--text-muted); word-break: break-all;
}

/* The affiliation, structurally below Forrest's own work. A visible break, so
   the separation the markup makes is a separation the reader sees. */
.affband { border-top: var(--edge-border) solid var(--border); }

.divider {
  height: var(--edge-border); background: var(--border);
  margin: var(--space-4) 0;
}

/* Direct child only. `.acct a` also matched the sign-in link nested in the
   paragraph below, which inherited this rule's amber fill and shipped as a
   second primary button in the sidebar. */
.acct > a {
  display: block; text-align: center; text-decoration: none;
  font-family: var(--font-body); font-size: var(--edge-nav-size);
  font-weight: 700; padding: var(--space-3);
  border-radius: var(--edge-radius-sm);
  background: var(--accent); color: var(--accent-contrast);
  transition: background var(--duration-fast) var(--ease-standard);
}
.acct > a:hover { background: var(--accent-hover); }
.acct p {
  font-size: var(--text-xs); color: var(--text-faint);
  text-align: center; margin: var(--space-2) 0 0; line-height: 1.5;
}

.acctalt {
  margin: var(--space-2) 0 0; text-align: center;
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); text-transform: uppercase;
}
.acctalt a { color: var(--text-muted); text-decoration: none; }
.acctalt a:hover { color: var(--text-strong); }

/* Bar links. Mono text, not buttons — the hero owns the call to action, and a
   second amber button in the bar competed with it. --text-muted rather than
   --text-faint: at 10px this is small text and has to clear 4.5:1 on a raised
   surface in both themes. */
.navl {
  display: inline-flex; align-items: center;
  min-height: 44px; padding: 0 var(--space-2);
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); text-transform: uppercase;
  color: var(--text-muted); text-decoration: none; white-space: nowrap;
}
.navl:hover { color: var(--text-strong); }

.sidefoot {
  margin-top: var(--space-4); padding-top: var(--space-3);
  border-top: var(--edge-border) solid var(--border);
  display: flex; justify-content: flex-end;
}

/* ─── panels ───────────────────────────────────────────────────────────── */

.main { display: flex; flex-direction: column; gap: var(--edge-gap); min-width: 0; }

.pane {
  background: var(--surface-raised);
  border: var(--edge-border) solid var(--border);
  border-radius: var(--edge-radius);
  box-shadow: var(--shadow-sm);
  overflow: hidden;
}
.panebody { padding: var(--edge-pad); }
.panebody > :last-child { margin-bottom: 0; }

.kick {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform);
  color: var(--accent); margin-bottom: var(--space-3);
}
.pane h1 {
  font-family: var(--font-display); font-weight: 700;
  font-size: var(--h1-size); line-height: 1.04;
  letter-spacing: var(--tracking-tight); color: var(--text-strong);
  margin: 0 0 var(--space-4);
}
.pane h2 {
  font-family: var(--font-display); font-weight: 600;
  font-size: var(--text-xl); letter-spacing: var(--tracking-tight);
  color: var(--text-strong); margin: 0 0 var(--space-3);
}
.pane .lede {
  font-size: var(--text-lg); color: var(--text-muted);
  line-height: var(--leading-relaxed); max-width: var(--measure-prose);
}
.pane .note { font-size: var(--text-sm); color: var(--text-faint); }

.credchips { display: flex; gap: var(--space-2); margin-top: var(--space-5); flex-wrap: wrap; }
.credchip {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); text-transform: uppercase;
  border: var(--edge-border) solid var(--border-strong);
  color: var(--text-muted);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--edge-radius-pill);
}

.lockmsg {
  margin-top: var(--space-3);
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); color: var(--text-faint);
  border: var(--edge-border) dashed var(--border-strong);
  border-radius: var(--edge-radius-sm);
  padding: var(--space-2) var(--space-3);
}

/* ─── the position sheet ───────────────────────────────────────────────── */

.sheet {
  display: grid; grid-template-columns: 1fr 1fr;
  border-top: var(--edge-border) solid var(--border);
}
@media (max-width: 740px) { .sheet { grid-template-columns: 1fr; } }
.sheet .col { padding: var(--space-4) var(--edge-pad); min-width: 0; }
.sheet .col:first-child { border-right: var(--edge-border) solid var(--border); }
@media (max-width: 740px) {
  .sheet .col:first-child {
    border-right: 0; border-bottom: var(--edge-border) solid var(--border);
  }
}
.colhead {
  display: flex; align-items: center; justify-content: space-between;
  margin-bottom: var(--space-3);
}
.colhead .l {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform); color: var(--text-faint);
}
.addbtn {
  width: auto; margin: 0; padding: var(--space-1) var(--space-2);
  background: transparent; color: var(--accent);
  border: var(--edge-border) solid var(--border-strong);
  border-radius: var(--edge-radius-sm);
  font-family: var(--font-mono); font-size: var(--text-xs); cursor: pointer;
}
.addbtn:hover { border-color: var(--accent); }

.rows { display: flex; flex-direction: column; gap: var(--space-2); }
.row { display: grid; grid-template-columns: 1fr 8rem auto; gap: var(--space-2); }
.row input {
  padding: var(--space-2) var(--space-3);
  background: var(--surface-input); color: var(--text);
  border: var(--edge-border) solid var(--border);
  border-radius: var(--edge-radius-sm);
  font: inherit; font-size: var(--text-sm); min-width: 0;
}
.row .ra { font-family: var(--font-mono); text-align: right; font-variant-numeric: tabular-nums; }
.row .rx {
  width: auto; margin: 0; padding: 0 var(--space-2);
  background: transparent; color: var(--text-faint);
  border: var(--edge-border) solid var(--border);
  border-radius: var(--edge-radius-sm); cursor: pointer; font-size: var(--text-sm);
}
.row .rx:hover { color: var(--danger); border-color: var(--danger); }

.readout {
  border-top: var(--edge-border) solid var(--border);
  display: grid; grid-template-columns: repeat(3, 1fr);
}
@media (max-width: 740px) { .readout { grid-template-columns: 1fr; } }
.readout .stat {
  padding: var(--edge-pad);
  border-right: var(--edge-border) solid var(--border);
}
.readout .stat:last-child { border-right: 0; }
@media (max-width: 740px) {
  .readout .stat {
    border-right: 0; border-bottom: var(--edge-border) solid var(--border);
  }
  .readout .stat:last-child { border-bottom: 0; }
}
.readout .l {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform);
  color: var(--text-faint); margin-bottom: var(--space-2);
}
.readout .v {
  font-family: var(--font-display); font-weight: 700;
  font-size: var(--text-2xl); letter-spacing: var(--tracking-tight);
  color: var(--text-strong); font-variant-numeric: tabular-nums;
}
.readout .stat.key .v { color: var(--accent); font-size: var(--text-3xl); }
.readout .stat.key .v.neg { color: var(--danger); }
.readout .s {
  font-family: var(--font-mono); font-size: var(--text-xs);
  color: var(--text-faint); margin-top: var(--space-1);
}

.actions { display: flex; gap: var(--space-3); flex-wrap: wrap; align-items: center; }
.actions form.inline { display: inline; margin: 0; }
.actions .note { flex-basis: 100%; margin: 0; }
.btn.pri, .btn.gho {
  width: auto; margin: 0; padding: var(--space-3) var(--space-5);
  border-radius: var(--edge-radius-sm);
  font-family: var(--font-body); font-size: var(--text-sm); font-weight: 600;
  cursor: pointer; text-decoration: none; display: inline-block;
}
.btn.pri {
  background: var(--accent); color: var(--accent-contrast);
  border: var(--edge-border) solid var(--accent);
}
.btn.pri:hover { background: var(--accent-hover); border-color: var(--accent-hover); }
.btn.gho {
  background: transparent; color: var(--accent);
  border: var(--edge-border) solid var(--border-strong);
}
.btn.gho:hover { border-color: var(--accent); }

.footpane { padding: var(--edge-pad); }
.footpane .footnav { display: flex; gap: var(--space-4); flex-wrap: wrap; }
.footpane .legalline { margin-bottom: 0; }

/* ─── converted pages inside the shell ──────────────────────────────────────
   /about, the pillar pages and the legal pages were written as full-bleed
   bands. Inside .main they become panels instead, so the whole public site
   reads as one shell without rewriting each template's markup. */

.main > .band,
.main > .hero,
.main > .capture {
  background: var(--surface-raised);
  border: var(--edge-border) solid var(--border);
  border-radius: var(--edge-radius);
  box-shadow: var(--shadow-sm);
  padding: var(--edge-pad);
  margin: 0;
  min-height: 0;
}
/* The band variants stop meaning anything once each one is its own panel —
   alternating tone against a page surface was the point, and the panel model
   replaces it. */
.main > .band.raised,
.main > .band.sunken,
.main > .band.tinted { background: var(--surface-raised); }

/* Bands carry their own inner container; the panel already constrains width. */
.main > .band > .inner,
.main > .hero > .inner,
.main > .hero > .herogrid {
  padding-inline: 0; margin-inline: 0; max-width: none;
}
.main > .band > .inner.narrow p,
.main > .hero > .inner.narrow p { max-width: var(--measure-prose); }

.main > .hero { min-height: 0; }
.main > .hero::after { display: none; }   /* grain belongs to the page, not a panel */
.main > .hero h1 { font-size: var(--h1-size); }

/* The email capture is already a panel; nesting a second frame doubles it. */
.main .capture { border: 0; box-shadow: none; padding: 0; margin: 0; }

/* ══ Homepage catalogue ═══════════════════════════════════════════════════
   The 1-2-3, the filter chips, and the pillar sections. Structure follows the
   ratified design in brand/02-identity/directions/prototype-v2.html.
   Semantic + edge tokens only — check_tokens.py enforces it on this file. */

/* ─── the 1-2-3 ─────────────────────────────────────────────────────────── */

.steps {
  display: grid; grid-template-columns: repeat(3, 1fr);
  border-top: var(--edge-border) solid var(--border);
}
@media (max-width: 740px) { .steps { grid-template-columns: 1fr; } }
.step {
  padding: var(--space-5) var(--edge-pad);
  border-right: var(--edge-border) solid var(--border);
}
.step:last-child { border-right: 0; }
@media (max-width: 740px) {
  .step { border-right: 0; border-bottom: var(--edge-border) solid var(--border); }
  .step:last-child { border-bottom: 0; }
}
.step .num {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking);
  color: var(--accent); margin-bottom: var(--space-2);
}
.step h3 {
  font-family: var(--font-display); font-weight: var(--weight-semibold);
  font-size: var(--text-lg); letter-spacing: var(--tracking-tight);
  color: var(--text-strong); margin: 0 0 var(--space-1);
}
.step p {
  font-size: var(--text-sm); color: var(--text-faint);
  line-height: var(--leading-normal); margin: 0;
}

/* ─── filter chips ──────────────────────────────────────────────────────── */

.offerfilter {
  display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: center;
  padding: var(--space-4) var(--edge-pad);
  border-bottom: var(--edge-border) solid var(--border);
}
.flabel {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform);
  color: var(--text-faint); margin-right: var(--space-1);
}
.fchip {
  width: auto; margin: 0;
  font-size: var(--text-sm); font-weight: var(--weight-medium);
  padding: var(--space-2) var(--space-4);
  border-radius: var(--edge-radius-pill);
  border: var(--edge-border) solid var(--border-strong);
  color: var(--text-muted); background: transparent; cursor: pointer;
  transition: color var(--duration-fast) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard),
              background var(--duration-fast) var(--ease-standard);
}
.fchip:hover { border-color: var(--accent); color: var(--accent); }
.fchip[aria-pressed="true"] {
  background: var(--accent); border-color: var(--accent);
  color: var(--accent-contrast); font-weight: var(--weight-semibold);
}

@media (prefers-reduced-motion: reduce) {
  .door { transition: none; }
  .door:hover { transform: none; }
}

.nofilter {
  margin: 0 var(--edge-pad) var(--edge-pad);
  padding: var(--space-6); text-align: center;
  font-size: var(--text-sm); color: var(--text-faint);
  border: var(--edge-border) dashed var(--border-strong);
  border-radius: var(--edge-radius-sm);
}
.nofilter[hidden] { display: none; }

/* ══ Hub chrome on the shared shell ══════════════════════════════════════
   The hub used to carry its own topbar from _shell.html. It now uses the same
   sidebar as the public site, so only the parts that genuinely differ live
   here: the sign-out button and the module grid. */

/* margin-left:auto rather than justify-content on the parent, so the toggle
   sits right whether or not a sign-out form is beside it. */
.sidefoot .themetoggle { margin-left: auto; }
.signout { margin: 0; }
.sbtn {
  width: auto; margin: 0;
  background: transparent; color: var(--text-faint);
  border: var(--edge-border) solid var(--border);
  border-radius: var(--edge-radius-sm);
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform);
  padding: var(--space-2) var(--space-3); cursor: pointer;
  transition: color var(--duration-fast) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard);
}
.sbtn:hover { color: var(--accent); border-color: var(--accent); }

/* ── the hub's block grid ────────────────────────────────────────────────
   ONE GRID, NOT FOUR BANDS. The four category bands each had a heading, a
   blurb and a sub-grid; the grouping now survives as a kicker in the
   category's accent on each block, which is one scan instead of four.

   Separate blocks with their own borders, not cells sharing a hairline: a
   block is a thing you click, and the old grid drew a table.

   The accent arrives through `data-pillar` on the block, from the registry —
   tokens.css repaints `--accent` from that attribute and the rail already
   works this way. No second variable. */
/* **ROWS, NOT A BOX GRID (3.05).** Forrest: "we can just list things out in
   rows, not a box grid which is difficult to understand." A grid of seven
   cards reads as a shape to decode — which column is which category, why
   that one is wide — when the thing being listed is a flat set of places to
   go. A row is one line: what band it is in, what it is called, the number
   if there is one, and what it does. */
.modgrid { display: grid; gap: var(--space-2); max-width: 1020px; }
.modcard {
  position: relative; overflow: hidden; text-decoration: none;
  display: grid; align-items: baseline;
  grid-template-columns: 7rem minmax(8rem, 13rem) 5rem minmax(0, 1fr);
  gap: var(--space-3) var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--surface-raised);
  border: var(--edge-border) solid var(--border);
  border-radius: var(--edge-radius);
  transition: border-color var(--duration-fast) var(--ease-standard),
              transform var(--duration-fast) var(--ease-standard),
              box-shadow var(--duration-fast) var(--ease-standard);
}
/* The accent rule along the top edge, and the wash behind it on hover. Both
   are PSEUDO-ELEMENTS whose opacity fades — the element's own opacity is never
   touched, because that composites its text toward the page background and
   defeats every contrast measurement taken on the tokens. */
/* THE ACCENT MOVES FROM THE TOP EDGE TO THE LEADING ONE, because a 2px line
   across the top of a full-width row is a divider rather than a mark. */
.modcard::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 2px;
  background: var(--accent); opacity: .55;
  transition: opacity var(--duration-fast) var(--ease-standard);
}
.modcard::after {
  content: ""; position: absolute; inset: 0; pointer-events: none; opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-standard);
  background: radial-gradient(120% 70% at 50% 0%,
              color-mix(in srgb, var(--accent) 16%, transparent), transparent 70%);
}
/* NO LIFT ON A ROW. `translateY` on a card in a grid is a card picking
   itself up; on a row in a list it is the line jumping out of the line. */
a.modcard:hover {
  border-color: color-mix(in srgb, var(--accent) 50%, transparent);
}
a.modcard:hover::before, a.modcard:hover::after { opacity: 1; }
/* The category, in the category's colour. This is what the four headings
   became. */
.modkick {
  position: relative; z-index: 1;
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform);
  color: var(--accent); margin: 0;
}
.modcard > * { position: relative; z-index: 1; }
.modcard h3 { margin: 0; font-size: var(--text-base); }
.modcard .num { margin: 0; font-size: var(--text-sm); }
.modblurb { margin: 0; font-size: var(--text-sm); color: var(--text-faint); }

@media (max-width: 860px) {
  /* The blurb drops under the name rather than squeezing it to nothing. */
  .modcard { grid-template-columns: 7rem minmax(0, 1fr) auto; }
  .modblurb { grid-column: 2 / -1; }
}
@media (max-width: 640px) {
  .modgrid { grid-template-columns: 1fr; }
}
.modcard .num {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform);
  color: var(--accent); margin-bottom: var(--space-2);
}
.modcard h3 {
  font-family: var(--font-display); font-weight: var(--weight-semibold);
  font-size: var(--text-lg); letter-spacing: var(--tracking-tight);
  color: var(--text-strong); margin: 0 0 var(--space-2);
}
.modcard p {
  font-size: var(--text-sm); color: var(--text-faint);
  line-height: var(--leading-normal); margin: 0;
}


/* The hub's legacy table and dropzone labels used --dim / --text-faint, which
   were sized for the old flat page. On the shell's raised panel they measure
   2.5–3.9:1 — below AA. --text-muted is the same role one step darker.

   Prefixed with .pane deliberately. style.css @imports public.css at the top,
   so its `.recent th` (0,1,1) lands later and beats an equal-specificity rule
   here; .pane makes these (0,2,1) and they win without touching style.css,
   which the journal pages still depend on. */
.pane .recent th, .pane .recent td.dim, .pane .recent td.num,
.pane .recent caption, .pane .dropzone .note { color: var(--text-muted); }

/* ══ Hub pages on the shared shell ════════════════════════════════════════
   The journal, playbook and market templates moved off _shell.html, whose
   topbar was a second set of page chrome competing with the sidebar. Their
   bodies still use the hub's own components (.panel, .statgrid, tables), which
   style.css owns; these are the pieces that belonged to the retired shell. */

.hubpage { display: flex; flex-direction: column; min-width: 0; }
.hubpage > h1 {
  font-family: var(--font-display); font-weight: var(--weight-bold);
  font-size: var(--text-3xl); line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight); color: var(--text-strong);
  margin: 0 0 var(--space-2);
}
.hubpage > .lede {
  font-size: var(--text-lg); color: var(--text-muted);
  line-height: var(--leading-relaxed); max-width: var(--measure-prose);
  margin: 0 0 var(--space-5);
}

/* ─── breadcrumb ─────────────────────────────────────────────────────────
   `nav.crumb`, not `.crumb`: style.css defines .crumb and is imported first, so
   an equal-specificity rule here would lose. The element selector costs
   nothing and the macro always emits a <nav>. */

nav.crumb {
  display: flex; align-items: center; gap: var(--space-2); flex-wrap: wrap;
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform);
  margin: 0 0 var(--space-3);
}
nav.crumb a { color: var(--text-muted); text-decoration: none; }
nav.crumb a:hover { color: var(--accent); text-decoration: underline; }
/* The current page is not a link. A control that navigates to where you
   already are is a dead control. */
nav.crumb [aria-current="page"] { color: var(--text-strong); }
nav.crumb .sep { color: var(--text-faint); }

/* ─── section tabs ─────────────────────────────────────────────────────────
   The row inside a section (Overview / Sessions / Trades). style.css defines
   .subnav and lands later in the cascade, so these are prefixed with .hubpage
   to win without editing the rules the shape still depends on. */

.hubpage .subnav {
  display: flex; gap: var(--space-1); flex-wrap: wrap;
  margin: 0 0 var(--space-5); padding: 0;
  border-bottom: var(--edge-border) solid var(--border);
}
.hubpage .subnav a {
  padding: var(--space-2) var(--space-3);
  border-radius: 0; border-bottom: 2px solid transparent;
  color: var(--text-muted); text-decoration: none;
  font-size: var(--edge-nav-size);
  background: transparent;
  transition: color var(--duration-fast) var(--ease-standard),
              border-color var(--duration-fast) var(--ease-standard);
}
.hubpage .subnav a:hover { background: transparent; color: var(--text-strong); }
/* Underline + full-strength text, not bold alone. Weight is the weakest of the
   three signals and the only one the old rule used beyond a fill. */
.hubpage .subnav a.on {
  background: transparent;
  color: var(--text-strong);
  border-bottom-color: var(--accent);
  font-weight: var(--weight-semibold);
}
.hubpage .subnav a.away { margin-left: auto; color: var(--accent); }

/* ══ The hero ═════════════════════════════════════════════════════════════
   Headline, then the ask, then the reassurance. The order is the argument. */

.hero-r .panebody { padding-block: var(--space-7); }
.hero-r h1 { margin-bottom: 0; }
/* Two blocks, so the lines never reflow into each other at any width. */
.hero-r h1 .l { display: block; }
.hero-r h1 .l2 { color: var(--accent); }

.herocta {
  display: inline-flex; align-items: center; gap: var(--space-2);
  margin-top: var(--space-5); padding: var(--space-3) var(--space-5);
  min-height: 44px;
  background: var(--accent); color: var(--accent-contrast);
  border-radius: var(--edge-radius-sm);
  font-weight: var(--weight-bold); text-decoration: none;
  transition: background var(--duration-fast) var(--ease-standard);
}
.herocta:hover { background: var(--accent-hover); }
.herocta i { font-style: normal; transition: transform var(--duration-fast) var(--ease-standard); }
.herocta:hover i { transform: translateX(4px); }

.herofine {
  margin: var(--space-4) 0 0; max-width: var(--measure-narrow);
  font-size: var(--text-sm); color: var(--text-muted);
  line-height: var(--leading-normal);
}
.herofine strong { color: var(--text-strong); font-weight: var(--weight-medium); }
@media (max-width: 860px) { .herocta { width: 100%; justify-content: center; } }

/* ══ The doors ════════════════════════════════════════════════════════════
   The homepage is the front of the free catalogue: three doors that expand IN
   PLACE to list what is behind them, one open at a time. Every colour derives
   from --accent, which data-pillar sets on the section, so one rule paints all
   three and the strength follows the theme.

   Tint strengths are --tint / --tintb. The mockup typed 13% collapsed and 16%
   open; tokens.json caps it at 10% with a measurement attached — above that,
   accent-coloured text on the card falls under 4.5:1. The token wins, and the
   open state is distinguished by its border rather than a stronger fill. */

.doors {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: var(--edge-gap); align-items: start;
}
/* Stacked below the doors' natural minimum, not at an arbitrary breakpoint:
   three columns of this much prose stop being readable well before 920px. */
@media (max-width: 860px) { .doors { grid-template-columns: 1fr; } }

.door {
  border-radius: var(--edge-radius); overflow: hidden;
  background: color-mix(in srgb, var(--accent) var(--tint), var(--surface-raised));
  border: var(--edge-border) solid color-mix(in srgb, var(--accent) var(--tintb), transparent);
  box-shadow: var(--shadow-sm);
  transition: border-color var(--duration-base) var(--ease-standard);
}
.door.open { grid-column: 1 / -1; border-color: var(--accent); }
@media (max-width: 860px) { .door.open { grid-column: auto; } }

/* The collapse is gated on html.js, exactly like the sidebar panel: the class
   is set by a blocking script in <head>, so nobody sees the open state flash,
   and a visitor with no JavaScript gets the whole catalogue rather than three
   inert headings. */
html.js .door:not(.open) .dpanel { display: none; }

/* style.css styles every <button> as a full-width amber submit. The header is a
   button because it toggles something, so it opts out of all of it. */
.dhead {
  position: relative; display: flex; flex-direction: column;
  width: 100%; margin: 0; padding: var(--edge-pad); overflow: hidden;
  text-align: left; background: none; color: inherit; font: inherit;
  border: 0; border-radius: 0; cursor: pointer;
}
.dhead:hover { background: color-mix(in srgb, var(--accent) 8%, transparent); }
.dhead:focus-visible { outline: 2px solid var(--accent); outline-offset: -3px; }

/* The one piece of motion on the page. It says "this opens" without a word. */
.drule {
  position: absolute; inset: 0 auto auto 0; height: 3px; width: 34%;
  background: var(--accent);
  transition: width var(--duration-slow) var(--ease-standard);
}
.dhead:hover .drule, .door.open .drule { width: 100%; }

/* Mixed toward --text-strong rather than left as raw --accent: at 10px on a
   tinted card the accent alone measures under 4.5:1 in both themes. */
.deyebrow {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform);
  color: color-mix(in srgb, var(--accent) 76%, var(--text-strong));
  margin: 0 0 var(--space-3);
}
.dh {
  display: block; margin-bottom: var(--space-3);
  font-family: var(--font-display); font-weight: var(--weight-bold);
  font-size: var(--text-xl); line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight); color: var(--text-strong);
}
.dbody {
  display: block; margin: 0;
  font-size: var(--text-sm); color: var(--text-muted);
  line-height: var(--leading-normal);
}
.dfoot {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3);
  margin: var(--space-5) 0 0; padding-top: var(--space-3);
  border-top: var(--edge-border) solid color-mix(in srgb, var(--accent) var(--tintb), transparent);
}
.dcount, .dopen {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking);
}
.dcount { color: var(--text-muted); }
.dopen {
  display: inline-flex; align-items: center; gap: var(--space-2);
  text-transform: var(--edge-label-transform);
  color: color-mix(in srgb, var(--accent) 76%, var(--text-strong));
}
.dopen i { font-style: normal; transition: transform var(--duration-base) var(--ease-standard); }
.door.open .dopen i { transform: rotate(180deg); }

/* ─── the panel ─────────────────────────────────────────────────────────── */

.dpanel { padding: 0 var(--edge-pad) var(--edge-pad); }
.dblurb {
  max-width: var(--measure-prose); margin: 0 0 var(--space-5);
  color: var(--text); line-height: var(--leading-normal);
}

.items { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3);
         grid-template-columns: repeat(2, 1fr); }
@media (max-width: 820px) { .items { grid-template-columns: 1fr; } }

.it {
  display: flex; gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--edge-radius-sm);
  background: color-mix(in srgb, var(--surface-sunken) 60%, transparent);
  border: var(--edge-border) solid color-mix(in srgb, var(--accent) 18%, transparent);
}
.it h4 {
  margin: 0 0 var(--space-1);
  font-family: var(--font-display); font-weight: var(--weight-semibold);
  font-size: var(--text-base); letter-spacing: var(--tracking-tight);
  color: var(--text-strong);
}
.it h4 a { color: inherit; text-decoration: none; }
.it h4 a:hover { color: var(--accent); }
.it p { margin: 0; font-size: var(--text-sm); color: var(--text-muted); line-height: var(--leading-normal); }

/* The badge is the whole product decision in eight characters: what this item
   costs the person reading it. Fixed width so the column of them lines up. */
.itb {
  flex: none; align-self: flex-start; min-width: 5.5rem; text-align: center;
  padding: var(--space-1) var(--space-2);
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); text-transform: uppercase;
  border: var(--edge-border) solid var(--border-strong);
  border-radius: calc(var(--edge-radius-sm) / 2);
  color: var(--text-muted); white-space: nowrap;
}
.it-open .itb { background: var(--accent); color: var(--accent-contrast); border-color: var(--accent); }
.it-account .itb { border-color: color-mix(in srgb, var(--accent) 55%, transparent); color: var(--accent); }
/* Dimmed and dashed. It is a plan and it is labelled as one. */
/* A coming item. NO OPACITY — it composites the text toward the page
   background and silently defeats every contrast measurement taken on the
   tokens. This carried `opacity: .55` from the day the doors shipped, which is
   the same violation brand/CLAUDE.md was written about after the pillars-v1
   mockup, sitting in the stylesheet the whole time.
   It recedes through the parts that are not prose: a dashed edge and a muted
   badge. Every word stays at full contrast. */
.it-soon { border-style: dashed; }
.it-soon .itb { color: var(--text-muted); border-style: dashed; }
.it-soon h4 { color: var(--text-muted); }
.it-affiliate .itb { border-style: dashed; }

/* Clear and conspicuous: inside the panel holding the items, above that
   panel's own CTA, at body weight and never collapsed. */
.disc {
  margin: var(--space-4) 0 0; max-width: var(--measure-prose);
  font-size: var(--text-sm); color: var(--text-muted);
  line-height: var(--leading-normal);
}
.dcta {
  display: flex; gap: var(--space-4); align-items: center; flex-wrap: wrap;
  margin-top: var(--space-5); padding-top: var(--space-4);
  border-top: var(--edge-border) solid color-mix(in srgb, var(--accent) var(--tintb), transparent);
}
.dcta .btn {
  margin: 0; padding: var(--space-3) var(--space-5);
  background: var(--accent); color: var(--accent-contrast);
  border-radius: var(--edge-radius-sm);
  font-size: var(--text-sm); font-weight: var(--weight-bold);
  text-decoration: none; white-space: nowrap;
}
.dmore {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); text-transform: var(--edge-label-transform);
  color: color-mix(in srgb, var(--accent) 76%, var(--text-strong));
  text-decoration: none; white-space: nowrap;
}
.dmore i { font-style: normal; transition: transform var(--duration-fast) var(--ease-standard); }
.dmore:hover i { transform: translateX(4px); }
.dcta p { margin: 0; max-width: var(--measure-narrow);
          font-size: var(--text-sm); color: var(--text-muted); line-height: var(--leading-normal); }
.dcta em { font-style: normal; color: var(--text); }

/* ─── the diagrams ──────────────────────────────────────────────────────────
   _diagrams.html carries geometry only. Everything visible is derived from
   --accent, so each diagram follows its pillar and both themes for free. */

.dg { display: block; width: 100%; height: auto; max-height: 118px;
      margin-bottom: var(--space-4); }
.door.open .dg { max-width: 230px; max-height: 96px; }
.dg-page { fill: none; stroke: color-mix(in srgb, var(--accent) 34%, transparent); stroke-width: 1; }
.dg-band { fill: color-mix(in srgb, var(--accent) 20%, transparent); }
.dg-lbl  { fill: color-mix(in srgb, var(--accent) 70%, transparent); }
.dg-num  { fill: var(--accent); }
.dg-row rect { fill: color-mix(in srgb, var(--text-muted) 42%, transparent); }
.dg-tot  { fill: color-mix(in srgb, var(--accent) 62%, transparent); }
.dg-div  { stroke: color-mix(in srgb, var(--accent) 30%, transparent); stroke-width: 1; }
.dg-fig circle { fill: var(--accent); }
.dg-limb { stroke: var(--accent); stroke-width: 2.4; stroke-linecap: round; fill: none; }
.dg-loop { stroke: color-mix(in srgb, var(--accent) 55%, transparent); stroke-width: 1.6;
           stroke-linecap: round; fill: none; }
.dg-ring { fill: none; stroke: color-mix(in srgb, var(--accent) 28%, transparent); stroke-width: 1.4; }
.dg-arc  { fill: none; stroke: var(--accent); stroke-width: 2.6; stroke-linecap: round; }
.dg-node circle { fill: var(--accent); }
.dg-cap {
  font-family: var(--font-mono); font-size: 9px;
  letter-spacing: var(--edge-label-tracking); fill: var(--text-muted);
}
.dg-mid {
  font-family: var(--font-display); font-size: 11px; font-weight: var(--weight-medium);
  fill: var(--text-muted); text-anchor: middle;
}

/* ══ Permissioned quote ═══════════════════════════════════════════════════ */

.quote { margin: 0; }
.quote blockquote {
  margin: 0 0 var(--space-4);
  padding-left: var(--space-5);
  border-left: 3px solid var(--accent);
  font-family: var(--font-display); font-weight: var(--weight-medium);
  font-size: var(--text-lg); line-height: var(--leading-relaxed);
  color: var(--text-strong); max-width: var(--measure-prose);
}
.quote figcaption {
  display: flex; gap: var(--space-2); flex-wrap: wrap; align-items: baseline;
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform);
}
.quote .qname { color: var(--text-strong); }
.quote .qctx { color: var(--text-muted); }
/* Sits with the quote, never in a footer. It qualifies the claim above it and
   a reader who never scrolls to the bottom still has to see it. */
.quote .qdis {
  margin: var(--space-3) 0 0;
  font-size: var(--text-sm); color: var(--text-muted);
}

/* ══ /money ═══════════════════════════════════════════════════════════════
   Figures, comparison table, segments, objections, price. */

/* ─── inline SVG figures ────────────────────────────────────────────────
   Typography via classes rather than font-family attributes: an attribute
   would hard-code a face the token set is allowed to change, and it would not
   flip with the theme. Fills stay as attributes because they are per-shape. */

.fig { margin: var(--space-5) 0 0; }
.figttl {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform);
  color: var(--text-faint); margin: 0 0 var(--space-3);
}
.fig svg { display: block; width: 100%; height: auto; }
.fig figcaption {
  margin-top: var(--space-4); font-size: var(--text-sm);
  color: var(--text-muted); line-height: var(--leading-normal);
  max-width: var(--measure-prose);
}
.fig .fl {                                   /* small caps label */
  font-family: var(--font-mono); font-size: 10px;
  letter-spacing: var(--edge-label-tracking); fill: var(--text-faint);
}
.fig .fm { font-family: var(--font-mono); font-size: 11px; fill: var(--text-faint); }
.fig .fnum { font-family: var(--font-mono); fill: var(--text); }
.fig .frow { font-family: var(--font-body); font-size: 12.5px; fill: var(--text-muted); }
.fig .fb { font-family: var(--font-body); font-size: 12.5px; fill: var(--text-muted); }
.fig .fh {
  font-family: var(--font-display); font-weight: var(--weight-bold);
  font-size: 16px; fill: var(--text-strong);
}
.fig .fbig {
  font-family: var(--font-display); font-weight: var(--weight-bold); font-size: 24px;
}
.fig .ftot {
  font-family: var(--font-display); font-weight: var(--weight-bold);
  font-size: 15px; fill: var(--text-strong);
}

/* ─── comparison table ─────────────────────────────────────────────────── */

.cmp { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.cmp th, .cmp td {
  text-align: left; padding: var(--space-3);
  border-bottom: var(--edge-border) solid var(--border);
  vertical-align: top;
}
.cmp thead th {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform); color: var(--text-faint);
}
.cmp tbody th { color: var(--text-strong); font-weight: var(--weight-semibold); white-space: nowrap; }
.cmp td { color: var(--text-muted); }
.cmp tbody td:last-child { color: var(--text); }
.cmp tr:last-child th, .cmp tr:last-child td { border-bottom: 0; }

/* ─── segments, objections, price ──────────────────────────────────────── */

.seg { display: grid; grid-template-columns: 1fr 1fr; gap: var(--edge-gap); margin: var(--space-5) 0; }
@media (max-width: 740px) { .seg { grid-template-columns: 1fr; } }
.segbox {
  padding: var(--edge-pad); border-radius: var(--edge-radius);
  background: var(--surface); border: var(--edge-border) solid var(--border);
}
.segbox .l {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform);
  color: var(--accent); margin: 0 0 var(--space-3);
}
.segbox p:last-child { margin-bottom: 0; }

.obj { margin-bottom: var(--space-5); }
.obj .q {
  font-family: var(--font-display); font-weight: var(--weight-semibold);
  font-size: var(--text-lg); color: var(--text-strong);
  letter-spacing: var(--tracking-tight); margin: 0 0 var(--space-2);
}
.obj p:last-child { margin-bottom: 0; }

.pull {
  margin: var(--space-5) 0; padding-left: var(--space-5);
  border-left: 3px solid var(--accent);
  font-family: var(--font-display); font-weight: var(--weight-medium);
  font-size: var(--text-lg); line-height: var(--leading-relaxed);
  color: var(--text-strong);
}

.price { display: grid; grid-template-columns: 1fr 1fr; gap: var(--edge-gap); margin: var(--space-5) 0; }
@media (max-width: 740px) { .price { grid-template-columns: 1fr; } }
.pbox {
  padding: var(--edge-pad); border-radius: var(--edge-radius);
  background: var(--surface); border: var(--edge-border) solid var(--border);
  display: flex; flex-direction: column;
}
.pbox.hi {
  background: color-mix(in srgb, var(--accent) var(--tint), var(--surface-raised));
  border-color: color-mix(in srgb, var(--accent) var(--tintb), transparent);
}
.pbox .tg {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform);
  color: var(--accent); margin: 0 0 var(--space-2);
}
.pbox .amt {
  font-family: var(--font-display); font-weight: var(--weight-bold);
  font-size: var(--text-3xl); letter-spacing: var(--tracking-tight);
  color: var(--text-strong); margin: 0 0 var(--space-4);
}
.pbox ul { margin: 0 0 var(--space-5); padding-left: var(--space-5); flex: 1; }
.pbox li { font-size: var(--text-sm); color: var(--text-muted); margin-bottom: var(--space-2); }

/* The Position Sheet's offline state. The rows stay, the totals go — there is
   no local fallback that computes them (money engine invariant 5). */
.sheeterr {
  margin: 0 var(--edge-pad) var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: var(--edge-border) solid var(--warning);
  border-radius: var(--edge-radius-sm);
  font-size: var(--text-sm); color: var(--text-muted);
  line-height: var(--leading-normal);
}
.sheeterr[hidden] { display: none; }

/* Conditions sit above the buy button, never in a footnote. */
.pconds { margin: 0 0 var(--space-4); padding-left: var(--space-5); }
.pconds li {
  font-size: var(--text-xs); color: var(--text-muted);
  line-height: var(--leading-normal); margin-bottom: var(--space-2);
}
.pseats, .psoon {
  margin: 0; font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); color: var(--text-muted);
}

/* ------------------------------------------------------------------ *
 * The Position Sheet reader: three-step strip, drop zone, panels.
 *
 * Every colour here is a semantic token. There are no categorical series
 * tokens in this system, so nothing in the reader encodes identity by hue —
 * magnitude bars are one colour and identity is carried by the row label.
 * ------------------------------------------------------------------ */

.psteps {
  display: grid; grid-template-columns: repeat(3, 1fr);
  gap: var(--space-4); list-style: none; margin: var(--space-5) 0 0; padding: 0;
}
@media (max-width: 740px) { .psteps { grid-template-columns: 1fr; } }
.psteps li {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: var(--space-2); padding-top: var(--space-3);
  border-top: var(--edge-border) solid var(--border);
}
/* The current step is marked by weight and a rule, never by colour alone. */
.psteps li[aria-current] { border-top-color: var(--accent); }
.psteps .pn {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); color: var(--text-faint);
}
.psteps li[aria-current] .pn { color: var(--accent); }
.psteps .pl { font-weight: var(--weight-medium); color: var(--text-strong); }
.psteps li[aria-current] .pl { text-decoration: underline; text-underline-offset: 0.25em; }
.psteps .ph {
  font-size: var(--text-sm); color: var(--text-muted);
  line-height: var(--leading-normal);
}
.psteps .btn { margin-top: var(--space-1); }

.psdrop {
  margin: var(--space-5) 0 0; padding: var(--space-6) var(--edge-pad);
  text-align: center; background: var(--surface-sunken);
  border: var(--edge-border) dashed var(--border-strong);
  border-radius: var(--edge-radius);
}
.psdrop.over { border-color: var(--accent); border-style: solid; }
.psdrop .dh {
  margin: 0 0 var(--space-2); font-size: var(--text-lg);
  font-weight: var(--weight-medium); color: var(--text-strong);
}
.psdrop .dn {
  margin: 0 auto; max-width: var(--measure-prose);
  font-size: var(--text-sm); color: var(--text-muted);
}
/* Visually hidden, still focusable and still in the accessibility tree. */
.psfile {
  position: absolute; width: 1px; height: 1px; padding: 0;
  margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}
.psdrop label.btn { display: inline-block; margin: var(--space-4) 0 var(--space-2); }
.psfile:focus-visible + label.btn { outline: 2px solid var(--accent); outline-offset: 2px; }
.psbusy, .pserr { margin: var(--space-3) 0 0; font-size: var(--text-sm); }
.psbusy { color: var(--text-muted); }
.pserr { color: var(--danger); }
.psbusy[hidden], .pserr[hidden], .psmode[hidden] { display: none; }

.psmode {
  margin: var(--space-5) 0 0; padding: var(--space-3) var(--space-4);
  font-size: var(--text-sm); color: var(--text-muted);
  background: var(--accent-soft);
  border-left: var(--space-1) solid var(--accent);
  border-radius: var(--edge-radius-sm);
}

.pspanel { margin-top: var(--space-6); }
.pspanel[hidden] { display: none; }
.pspanel h3 { margin: 0 0 var(--space-1); font-size: var(--text-xl); }
.pswhy {
  margin: 0 0 var(--space-3); max-width: var(--measure-prose);
  font-size: var(--text-sm); color: var(--text-muted);
  line-height: var(--leading-normal);
}
/* The empty and partial states. Both name what is missing; neither says "no data". */
.psneed {
  margin: 0; padding: var(--space-4);
  font-size: var(--text-sm); color: var(--text-muted);
  background: var(--surface-sunken);
  border: var(--edge-border) dashed var(--border);
  border-radius: var(--edge-radius-sm);
}
/* Saved closes, 2.10. The strip under each panel is the product: one snapshot
   is a number, two is a trend. Tone comes from Python — `good`/`bad` are what a
   RISE means for that particular figure, never the sign of the change, because
   a page that painted every rise green would congratulate someone for
   borrowing more. */
.psdelta {
  display: flex; flex-wrap: wrap; gap: var(--space-2) var(--space-4);
  align-items: baseline;
  margin: var(--space-3) 0 0; padding: var(--space-3) var(--space-4);
  background: var(--surface-sunken);
  border-left: var(--edge-border) solid var(--border);
  border-radius: var(--edge-radius-sm);
}
.psdelta .dl {
  flex: 1 0 100%; margin: 0;
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform); color: var(--text-faint);
}
.psdelta .dc { display: inline-flex; gap: var(--space-2); align-items: baseline; }
.psdelta .dc .k { font-size: var(--text-sm); color: var(--text-muted); }
.psdelta .dc .v { font-family: var(--font-mono); font-size: var(--text-sm); }
.psdelta .dc.good .v { color: var(--success); }
.psdelta .dc.bad .v { color: var(--danger); }
.psdelta .dc.flat .v, .psdelta .dc.neutral .v { color: var(--text-muted); }

.pscloses { margin: var(--space-7) 0 0; }
.pscloses h3 { margin: 0 0 var(--space-1); font-size: var(--text-xl); }
.pscloses .pstablewrap { margin-bottom: var(--space-4); }
.pscloses td.good { color: var(--success); }
.pscloses td.bad { color: var(--danger); }
.psok {
  margin: var(--space-3) 0 0; max-width: var(--measure-prose);
  font-size: var(--text-sm); color: var(--success);
  line-height: var(--leading-normal);
}
.psok[hidden], .pscloses button[hidden] { display: none; }
/* The sentence a stranger reads before deciding whether to trust this with
   their numbers. It sits in the drop zone, above the control, on purpose. */
.psstore { max-width: var(--measure-prose); }

.pscap {
  margin: var(--space-3) 0 0; max-width: var(--measure-prose);
  font-size: var(--text-sm); color: var(--text-muted);
  line-height: var(--leading-normal);
}
.psh {
  margin: 0 0 var(--space-2); font-family: var(--font-mono);
  font-size: var(--edge-label-size); letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform); color: var(--text-faint);
}
.pstwo { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-5); }
@media (max-width: 740px) { .pstwo { grid-template-columns: 1fr; } }

.pstiles {
  display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--space-4);
}
@media (max-width: 980px) { .pstiles { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 460px) { .pstiles { grid-template-columns: 1fr; } }
/* ---- the monthly close ---------------------------------------------------
   The seven steps. A tick is a STATE, never an affordance: three of the seven
   are acts the engine cannot observe and they render unticked with what the
   engine can say beside them, so nothing here is clickable and nothing looks
   it. */
.pschecks { display: grid; gap: var(--space-3); margin-bottom: var(--space-4); }
.pschk { display: flex; gap: var(--space-3); align-items: flex-start; }
.pschk .box {
  flex: 0 0 auto; width: 1.35rem; height: 1.35rem;
  border: var(--edge-border) solid var(--border);
  border-radius: var(--edge-radius-sm);
  display: flex; align-items: center; justify-content: center;
  font-size: .8rem; line-height: 1; color: var(--text-faint);
}
/* Done is carried by BOTH the fill and the mark, never by colour alone —
   the mark is what survives a monochrome screen and a colour-blind reader. */
.pschk .box.done {
  border-color: var(--accent); color: var(--accent);
  background: color-mix(in srgb, var(--accent) 14%, transparent);
}
.pschk .cti { display: block; color: var(--text-strong); }
.pschk .cdi {
  display: block; font-size: var(--text-sm); color: var(--text-muted);
}
/* THE GATE COVERS THE TREND ONLY. The four figures above it are this close's
   read and the read is never gated — three rungs, and nothing is gated on the
   read itself because the read is the one thing that has to reach a stranger.
   No chart element in here at all: a line through one point is a claim the
   data cannot carry. */
.psgate {
  margin-top: var(--space-4); padding: var(--space-4);
  border: var(--edge-border) dashed var(--border);
  border-radius: var(--edge-radius-sm); background: var(--surface-sunken);
}
.psgate .l {
  margin: 0 0 var(--space-2); font-family: var(--font-mono);
  font-size: var(--edge-label-size); letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform); color: var(--text-faint);
}
.psgate .n { margin: 0; font-size: var(--text-sm); color: var(--text-muted); }

.pstile {
  padding: var(--space-4); background: var(--surface-raised);
  border: var(--edge-border) solid var(--border);
  border-radius: var(--edge-radius-sm); min-width: 0;
}
.pstile .l {
  margin: 0 0 var(--space-2); font-family: var(--font-mono);
  font-size: var(--edge-label-size); letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform); color: var(--text-faint);
}
.pstile .v {
  margin: 0; font-family: var(--edge-numeral-family);
  font-size: var(--text-2xl); font-variant-numeric: tabular-nums;
  color: var(--text-strong); overflow-wrap: anywhere;
}
.pstile .v .u { font-size: var(--text-sm); color: var(--text-muted); }
.pstile .v.neg { color: var(--danger); }
.pstile .v.pos { color: var(--success); }
.pstile .n {
  margin: var(--space-2) 0 0; font-size: var(--text-xs); color: var(--text-muted);
  line-height: var(--leading-normal);
}

.pstablewrap { overflow-x: auto; }
.pstable { width: 100%; border-collapse: collapse; font-size: var(--text-sm); }
.pstable th, .pstable td {
  padding: var(--space-2) var(--space-3);
  border-bottom: var(--edge-border) solid var(--border); text-align: left;
}
.pstable th {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform);
  color: var(--text-faint); font-weight: var(--weight-regular);
}
.pstable td.n, .pstable th.n {
  text-align: right; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.pstable td.dim { color: var(--text-muted); }
.pstable td.neg { color: var(--danger); }
.pstable td.pos { color: var(--success); }
.pstable tr:last-child td { border-bottom: 0; }
.pstable .bl { display: block; }
/* Magnitude, one hue. The label above it carries which line this is. */
.psbar {
  display: block; height: 6px; margin-top: var(--space-1);
  background: var(--surface-sunken); border-radius: var(--edge-radius-pill);
  overflow: hidden;
}
.psbar i {
  display: block; height: 100%; background: var(--accent);
  border-radius: var(--edge-radius-pill);
}

/* 390x844. A financial table at 390px is unreadable and a sideways scroll
   inside a card is worse than stacking, so every row becomes its own block
   with the column header printed beside each cell. */
@media (max-width: 640px) {
  .pstable, .pstable tbody, .pstable tr, .pstable td { display: block; width: 100%; }
  .pstable thead { position: absolute; width: 1px; height: 1px; overflow: hidden;
    clip-path: inset(50%); white-space: nowrap; }
  .pstable tr {
    padding: var(--space-3) 0;
    border-bottom: var(--edge-border) solid var(--border);
  }
  .pstable td {
    display: flex; align-items: baseline; justify-content: space-between;
    gap: var(--space-3); padding: var(--space-1) 0; border-bottom: 0;
    text-align: left;
  }
  .pstable td.n { text-align: right; white-space: normal; }
  .pstable td::before {
    content: attr(data-label); flex: none;
    font-family: var(--font-mono); font-size: var(--edge-label-size);
    letter-spacing: var(--edge-label-tracking);
    text-transform: var(--edge-label-transform); color: var(--text-faint);
  }
  /* The first cell holds the label and its bar, so it stacks instead. */
  .pstable td:first-child { display: block; }
  .pstable td:first-child::before { display: none; }
}

/* Validation issues and alerts. Copy: brand/03-copy/validation-messages.md.
   Severity is carried by a word in the badge as well as the rule colour —
   colour alone is never the distinction between what blocks and what does not. */
.psissue, .psalert {
  display: flex; gap: var(--space-3); align-items: flex-start;
  padding: var(--space-3) 0;
  border-bottom: var(--edge-border) solid var(--border);
}
.psissue:last-child, .psalert:last-child { border-bottom: 0; }
.ibadge {
  flex: none; margin: 0; padding: var(--space-1) var(--space-2);
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); text-transform: uppercase;
  border-radius: var(--edge-radius-sm);
  background: var(--surface-sunken); color: var(--text-muted);
}
.is-error .ibadge, .is-critical .ibadge {
  color: var(--danger); background: color-mix(in srgb, var(--danger) 14%, transparent);
}
.is-warning .ibadge, .is-serious .ibadge {
  color: var(--warning); background: color-mix(in srgb, var(--warning) 16%, transparent);
}
.ish {
  margin: var(--space-5) 0 0; font-family: var(--font-mono);
  font-size: var(--edge-label-size); letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform); color: var(--text-strong);
}
.psbody > .ish:first-child { margin-top: 0; }
.ish em {
  font-style: normal; text-transform: none; letter-spacing: 0;
  color: var(--text-muted);
}
.itab {
  margin: var(--space-3) 0 0; font-size: var(--text-sm);
  font-weight: var(--weight-medium); color: var(--text-muted);
}
.iw { margin: 0; font-size: var(--text-xs); color: var(--text-faint); }
.iask {
  margin: 0; font-weight: var(--weight-medium); color: var(--text-strong);
}
/* pre-line, because the source-type message glosses seven values one per line
   and collapsing them puts the list back into a paragraph nobody reads. */
.im {
  margin: var(--space-1) 0 0; max-width: var(--measure-prose);
  font-size: var(--text-sm); color: var(--text-muted);
  line-height: var(--leading-normal); white-space: pre-line;
}
.psfoot[hidden] { display: none; }

/* ══ /about ═══════════════════════════════════════════════════════════════
   Structure follows brand/02-identity/directions/about-v1.html: hero with the
   portrait and the credential chips · the method band with the loop diagram ·
   the prose in a sticky-heading two-column layout · what I'm not · the CTA.

   The page's job is "is this person solid?", so nothing here is decorative:
   every band is either a claim or the evidence for one. */

.ahero {
  display: grid; grid-template-columns: 1.35fr .65fr;
  gap: clamp(var(--space-5), 4vw, var(--space-8));
  align-items: start;
}
@media (max-width: 840px) { .ahero { grid-template-columns: 1fr; } }
.ahero h1 {
  max-width: 20ch;
  font-family: var(--font-display); font-weight: 700;
  font-size: var(--h1-size); line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight); color: var(--text-strong);
  margin: 0;
}
.asub {
  margin: var(--space-4) 0 0; max-width: var(--measure-lede);
  font-size: var(--text-lg); color: var(--text-muted);
  line-height: var(--leading-normal);
}
.asub strong { color: var(--text-strong); font-weight: var(--weight-semibold); }

/* PROVISIONAL. The slot ships whether or not the cutout exists — the layout is
   designed around it, and a hero that reflows when the image lands is a second
   design. */
.aportrait {
  margin: 0; aspect-ratio: 4 / 5;
  border: var(--edge-border) solid var(--border-strong);
  border-radius: var(--edge-radius-sm);
  overflow: hidden;
}
.aportrait img { width: 100%; height: 100%; object-fit: cover; display: block; }
.aportrait.placeholder {
  display: flex; align-items: flex-end; padding: var(--space-4);
  background: linear-gradient(160deg,
              color-mix(in srgb, var(--accent) var(--tintb), var(--surface-raised)),
              var(--surface-sunken));
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); text-transform: uppercase;
  color: var(--text-muted);
}

/* The method band. The diagram is the same macro the Mind door draws. */
.mband {
  display: grid; grid-template-columns: 1fr 260px;
  gap: clamp(var(--space-5), 4vw, var(--space-7));
  align-items: center;
}
@media (max-width: 840px) { .mband { grid-template-columns: 1fr; } }
.mband p { max-width: var(--measure-prose); color: var(--text-muted); }
.mband .dg { max-height: 190px; margin-bottom: 0; }

.kv { display: flex; gap: var(--space-5); flex-wrap: wrap; margin-top: var(--space-4); }
.kv div { min-width: 130px; flex: 1 1 130px; }
.kv b {
  display: block; margin-bottom: var(--space-1);
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); text-transform: uppercase;
  color: var(--accent);
}
.kv span { font-size: var(--text-sm); color: var(--text-muted); line-height: var(--leading-normal); }

/* Heading left, prose right, and the heading stays put while the section
   scrolls — so you always know which question you are reading the answer to. */
.cols {
  display: grid; grid-template-columns: 200px 1fr;
  gap: clamp(var(--space-5), 3vw, var(--space-7));
  padding-block: var(--space-6);
  border-bottom: var(--edge-border) solid var(--border);
}
.cols:last-child { border-bottom: 0; padding-bottom: 0; }
.cols:first-child { padding-top: 0; }
@media (max-width: 840px) { .cols { grid-template-columns: 1fr; gap: var(--space-3); } }
.cols h2 {
  position: sticky; top: var(--space-5); align-self: start;
  margin: 0; font-family: var(--font-display); font-weight: 700;
  font-size: var(--text-lg); letter-spacing: var(--tracking-tight);
  color: var(--text-strong);
}
.cols p { margin: 0 0 var(--space-4); color: var(--text-muted); line-height: var(--leading-relaxed); }
.cols p:last-child { margin-bottom: 0; }
.cols strong { color: var(--text-strong); font-weight: var(--weight-semibold); }
/* The sticky heading has nothing to stick to on a phone, and the media query
   above collapses the grid — so it must stop being sticky at the same width. */
@media (max-width: 840px) { .cols h2 { position: static; } }

/* Lineage is the credential in this world, so it is set apart from the prose
   that claims it. Verbatim from claims-bank.md. */
.lineage {
  margin-top: var(--space-4); padding: var(--space-4);
  border-radius: var(--edge-radius-sm);
  background: color-mix(in srgb, var(--accent) var(--tint), var(--surface-raised));
  border: var(--edge-border) solid color-mix(in srgb, var(--accent) var(--tintb), transparent);
}
.lineage p {
  margin: 0; font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); text-transform: uppercase;
  color: var(--text-muted); line-height: var(--leading-relaxed);
}

/* Two of these three are disclaimers, so they are on the page in body copy —
   not compressed into a footer line nobody reads. */
.notlist { list-style: none; margin: 0; padding: 0; }
.notlist li {
  position: relative; margin-bottom: var(--space-3); padding-left: var(--space-5);
  max-width: var(--measure-prose);
  color: var(--text-muted); line-height: var(--leading-normal);
}
.notlist li:last-child { margin-bottom: 0; }
.notlist li::before { content: "—"; position: absolute; left: 0; color: var(--accent); }
.notlist strong { color: var(--text-strong); font-weight: var(--weight-semibold); }

.endcta .ctarow { margin-top: var(--space-4); }
.endcta .note { margin: var(--space-4) 0 0; max-width: var(--measure-narrow); }

/* ══ The shelf ════════════════════════════════════════════════════════════
   A pillar page is a shelf, not an essay. Named groups of cards, each stating
   what it is, what it costs and where it goes — built from the same registry
   the homepage doors read, so a card and a door can never disagree.

   Structure follows brand/02-identity/directions/pillars-v1.html. Every colour
   derives from --accent, which data-pillar on <body> sets, so one rule paints
   all three pages. */

.shelfhead {
  display: flex; align-items: baseline; gap: var(--space-3);
  flex-wrap: wrap; margin-bottom: var(--space-2);
}
.shelfhead h2 { margin: 0; }
.shelfn {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); text-transform: var(--edge-label-transform);
  color: var(--text-muted);
}
.shelfsub {
  max-width: var(--measure-prose); margin: 0 0 var(--space-5);
  color: var(--text-muted); line-height: var(--leading-normal);
}

.shelf {
  display: grid; gap: var(--edge-gap);
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
}
/* One column well before the cards would clip. A 300px minimum still leaves a
   two-up at 640, and two columns of this much prose is unreadable on a phone. */
@media (max-width: 640px) { .shelf { grid-template-columns: 1fr; } }

.scard {
  position: relative; display: flex; flex-direction: column; overflow: hidden;
  padding: var(--edge-pad);
  background: var(--surface-sunken);
  border: var(--edge-border) solid var(--border);
  border-radius: var(--edge-radius);
}
/* The accent tick down the left edge. It is the pillar's colour and the only
   place the card carries it, so the card reads as belonging to this page
   without tinting every one of them. */
.scard::before {
  content: ""; position: absolute; left: 0; top: 0; bottom: 0;
  width: 2px; background: var(--accent);
}
/* One tinted card per page: the first on the first shelf, marking the thing to
   do first. --tint, not a typed percentage — the token carries a measurement. */
.scard.tint { background: color-mix(in srgb, var(--accent) var(--tint), var(--surface-raised)); }
/* Listed, and visibly not open yet.
   NOT `opacity`. The mockup dims the whole card to .55, which composites its
   text toward the page: measured, the body copy lands at 4.05:1 dark and
   2.68:1 light, both under AA. So the recession is carried by the parts that
   are not prose — a dashed edge, a neutral tick, a muted badge, the cost line,
   and no link. Four signals, and every word stays readable. */
.scard.dim { border-style: dashed; }
.scard.dim::before { background: var(--border-strong); }
.scard.dim h3 { color: var(--text-muted); }
.scard.aff::before { background: var(--text-faint); }

.cbadge {
  align-self: flex-start; margin-bottom: var(--space-4);
  padding: var(--space-1) var(--space-3);
  border-radius: var(--edge-radius-pill);
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); text-transform: uppercase;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
  /* Mixed toward --text-strong: raw --accent on the tinted chip measures under
     4.5:1 at 10px in both themes. */
  color: color-mix(in srgb, var(--accent) 76%, var(--text-strong));
}
.cbadge.muted {
  background: var(--surface-raised);
  color: var(--text-muted);
  border: var(--edge-border) solid var(--border-strong);
}

.scard h3 {
  margin: 0 0 var(--space-2);
  font-family: var(--font-display); font-weight: var(--weight-semibold);
  font-size: var(--text-lg); line-height: var(--leading-tight);
  letter-spacing: var(--tracking-tight); color: var(--text-strong);
}
.scard > p { margin: 0; color: var(--text-muted); line-height: var(--leading-normal); }

/* Above the link, inside the card, at body weight and never collapsed. That is
   what "clear and conspicuous" means in the FTC Endorsement Guides. */
.cdisc {
  margin: var(--space-4) 0 0; padding-top: var(--space-3);
  border-top: var(--edge-border) solid var(--border);
  font-size: var(--text-sm); color: var(--text); line-height: var(--leading-normal);
}
.cdisc + .cdisc { border-top: 0; padding-top: var(--space-2); }
.cfda { color: var(--text-muted); }

.cardfoot {
  display: flex; align-items: center; justify-content: space-between;
  gap: var(--space-3); flex-wrap: wrap;
  margin-top: auto; padding-top: var(--space-5);
}
.cost {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); color: var(--text-muted);
}
.go {
  display: inline-flex; align-items: center; gap: var(--space-2);
  font-size: var(--text-sm); font-weight: var(--weight-semibold);
  color: var(--accent); text-decoration: none; white-space: nowrap;
}
.go i { font-style: normal; transition: transform var(--duration-fast) var(--ease-standard); }
.go:hover i { transform: translateX(4px); }

/* ─── the numbered stage strip ──────────────────────────────────────────── */

.stages {
  display: grid; gap: var(--edge-gap); margin-top: var(--space-6);
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
}
.stage {
  padding: var(--space-4);
  background: var(--surface-sunken);
  border: var(--edge-border) solid var(--border);
  border-radius: var(--edge-radius);
}
.stage b {
  display: block; margin-bottom: var(--space-2);
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); color: var(--accent);
}
.stage h4 {
  margin: 0 0 var(--space-1);
  font-family: var(--font-display); font-weight: var(--weight-semibold);
  font-size: var(--text-base); color: var(--text-strong);
}
.stage p { margin: 0; font-size: var(--text-sm); color: var(--text-muted); line-height: var(--leading-normal); }

/* A credential strip, not a badge — badges read as certification theatre to
   this audience, and the claim is a credential and never a form of address. */
.credline {
  margin: var(--space-5) 0 0; padding-top: var(--space-4);
  border-top: var(--edge-border) solid var(--border);
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); text-transform: uppercase;
  color: var(--text-muted); line-height: var(--leading-relaxed);
}

/* ─── modules 4-7 ───────────────────────────────────────────────────────────
   The engine has computed Debt, Collateral, Allocation and Horizon on every
   upload since it shipped; the reader threw them away because they were
   designated paid tier. Same panel shape as modules 1-3 — these two rules are
   the only new visual vocabulary the four of them needed. */

/* The row you are standing on in the sensitivity table.
   NO FILL. A 10% accent tint measured fine under normal text but put a --danger
   cell — the Change column when the move is negative — at 4.03:1 dark, under
   AA. The combination is unreachable today, because the marked row is the
   zero-move row and zero is never negative; it is one refactor from being
   reachable, and a rule that fails on a combination nobody has written yet is
   still a rule that fails. The bar, the weight and the row's own words carry
   it, which is three signals and no new backdrop to re-measure. */
.pstable tr.isnow td {
  font-weight: var(--weight-semibold);
  box-shadow: inset 3px 0 0 var(--accent);
}

/* Runway's assumptions. Every one of them holds at once, which is the point of
   listing them under the number rather than behind a link — a runway figure
   read without them is the most confidently wrong number in the product. */
.psassume {
  margin: var(--space-3) 0 0; padding-left: var(--space-5);
  max-width: var(--measure-prose);
}
.psassume li {
  margin-bottom: var(--space-1);
  font-size: var(--text-sm); color: var(--text-muted);
  line-height: var(--leading-normal);
}

/* ─── the feedback line ─────────────────────────────────────────────────────
   Revealed after a real render. It asks what someone LEARNED, never asks for a
   rating — so it needs room for a sentence and almost none for the control. */

.psfb {
  margin-top: var(--space-5); padding-top: var(--space-5);
  border-top: var(--edge-border) solid var(--border);
  max-width: var(--measure-prose);
}
.psfb[hidden] { display: none; }
.fbq {
  margin: 0 0 var(--space-3);
  font-family: var(--font-display); font-weight: var(--weight-semibold);
  font-size: var(--text-lg); color: var(--text-strong);
}
.fbrow { display: flex; gap: var(--space-4); flex-wrap: wrap; margin-bottom: var(--space-3); }
.fbyn {
  display: inline-flex; align-items: center; gap: var(--space-2);
  min-height: 44px; margin: 0;
  color: var(--text); font-size: var(--text-base); cursor: pointer;
}
.fbyn input { accent-color: var(--accent); width: 18px; height: 18px; }
.psfb textarea, .psfb input[type="email"] {
  width: 100%; margin-bottom: var(--space-3);
  padding: var(--space-3);
  background: var(--surface-sunken); color: var(--text);
  border: var(--edge-border) solid var(--border-strong);
  border-radius: var(--edge-radius-sm);
  /* 16px, or iOS zooms the field on focus and the page jumps. */
  font: inherit; font-size: var(--text-base);
}
.psfb textarea { resize: vertical; line-height: var(--leading-normal); }
.psfb textarea:focus-visible, .psfb input:focus-visible {
  outline: 2px solid var(--accent); outline-offset: 2px;
}
.psfb button {
  width: auto; margin: 0; padding: var(--space-3) var(--space-5);
  background: var(--accent); color: var(--accent-contrast);
  border: 0; border-radius: var(--edge-radius-sm);
  font: inherit; font-weight: var(--weight-semibold); cursor: pointer;
}
.psfb button:hover { background: var(--accent-hover); }
.psfb .micro { margin-top: var(--space-3); }
.psfbok {
  margin-top: var(--space-5); padding-top: var(--space-5);
  border-top: var(--edge-border) solid var(--border);
  color: var(--text-strong);
}
.psfbok[hidden] { display: none; }

/* ------------------------------------------------ the hub's categorised launcher --

   Categories come from ops/modules.yml. Each band carries data-pillar, which is
   the same one-line accent mechanism the public pillar pages use — a category is
   tinted by an existing token rather than by a new colour, so adding one costs
   nothing in tokens.css. */

/* `.catband` and `.cathead` went with the four bands in 2.55. The category is
   a kicker on the block now — see `.modkick`. */

/* The number, and what it is a number OF. The unit is what stops "17" from
   being a figure the reader has to guess at. */
.modcard .num .unit {
  display: block; margin-top: var(--space-1);
  color: var(--text-muted); text-transform: none;
  letter-spacing: var(--tracking-normal);
}
.modcard .num.up   { color: var(--success); }
.modcard .num.down { color: var(--danger); }
.modcard .num.warn { color: var(--warning); }

/* A planned tool. Not a link, no date, and NOT dimmed with opacity — opacity
   composites the text toward the page background and defeats every contrast
   measurement taken on the tokens. It recedes through the parts that are not
   prose: a dashed edge, a neutral tick, a muted label. Every word stays at full
   contrast, which is the whole point. */
.modcard.planned {
  border-style: dashed;
  background: transparent; cursor: default;
}
/* The accent rule stays solid so the category still reads; the block recedes
   through its own edge, not through its text. */
.modcard.planned::after { content: none; }
.modcard.planned .num { color: var(--text-muted); }
.modcard.planned .num .tick { margin-right: var(--space-1); }
.modcard.planned h3 { color: var(--text-muted); }

/* ---------------------------------------------- the library's reading order --

   Four cards in a grid with no order is a folder. One item is the start point
   and the rest carry their position, so the reader never has to guess which to
   open first. */
.seq {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform);
  color: var(--text-muted); margin: 0 0 var(--space-2);
}
.seq.start { color: var(--accent); font-weight: var(--weight-semibold); }
/* The start card is marked by its edge, not by a fill behind small text — the
   same rule the shelf cards follow, and for the same contrast reason. */
.pillarcard.starthere { border-color: var(--accent); }

/* ================================================ the public top nav (2.15) ==

   Two silhouettes, one shell. The public site is a top nav; the app keeps its
   left rail. A rail is the right shape for many destinations and a top nav is
   the right shape for four, and the change of shape is the signal that you have
   crossed from a site you read into software you use. */

.pubbar {
  position: sticky; top: 0; z-index: 30;
  background: color-mix(in srgb, var(--surface) 92%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: var(--edge-border) solid var(--border);
}
.pubbarin {
  max-width: var(--measure-wide); margin: 0 auto;
  padding: 0 var(--edge-pad); min-height: 62px;
  display: flex; align-items: center; gap: var(--space-3);
}
.pubbar .mark { flex: none; }
.mainnav {
  margin-left: auto; display: flex; align-items: center;
  gap: var(--space-5); flex-wrap: wrap;
}
.mainnav a {
  color: var(--text-muted); font-size: var(--text-sm); text-decoration: none;
}
.mainnav a:hover, .mainnav a.on { color: var(--text-strong); }
.mainnav a.on { font-weight: var(--weight-semibold); }
.mainnav a.signin { color: var(--text-faint); }
.mainnav a.cta {
  background: var(--accent); color: var(--accent-contrast);
  font-weight: var(--weight-semibold);
  padding: var(--space-2) var(--space-4); border-radius: var(--edge-radius);
}
.mainnav a.cta:hover { background: var(--accent-hover); color: var(--accent-contrast); }

/* Below 860px the links collapse into the panel the same toggle already opens,
   so one script serves both shapes. */
/* --nav-collapse IS THE BREAKPOINT, and both are declared HERE — outside every
   media query — because a custom property inside one only exists while that
   query matches. The script reads it once at load, so a page loaded wide would
   read nothing and fall back to the other silhouette's width: the same
   off-by-one gap, moved rather than removed.

   The two collapse at different widths on purpose — a 212px rail runs out
   sooner than a four-item top nav — which is why one hard-coded number in the
   script was wrong for one of them by construction.

   The media queries below repeat the number because CSS cannot use a custom
   property in a media condition. That is the one duplication left and the
   guard asserts the two agree. */
.sidepanel { --nav-collapse: 920px; }
.toppanel  { --nav-collapse: 860px; display: none; }
.topfoot { display: flex; justify-content: flex-end; padding-top: var(--space-3); }
@media (max-width: 860px) {
  .mainnav { display: none; }
  .toppanel {
    display: block; padding: var(--space-3) var(--edge-pad) var(--space-4);
    border-top: var(--edge-border) solid var(--border);
  }
  /* THE RULE THAT WAS MISSING, and the whole of the bug. `.toppanel[hidden]`
     hid on an ATTRIBUTE that nothing ever set, while the script toggles a
     CLASS — so every public page opened on a phone with the navigation
     expanded, and the tap changed the icon and nothing else. The rail branch
     had the matching selector; the top branch never got one when the second
     silhouette was built. One element, two names.
     `html.js` scoping for the same reason it is on the rail: without script
     the hamburger is inert, and an inert hamburger makes the site
     unnavigable. */
  html.js .toppanel:not(.open) { display: none; }
  .toppanel nav { display: flex; flex-direction: column; gap: var(--space-1); }
  .toppanel nav a { color: var(--text-muted); padding: var(--space-2) 0; text-decoration: none; }
  .toppanel nav a.on { color: var(--text-strong); font-weight: var(--weight-semibold); }
}
@media (min-width: 861px) {
  /* The hamburger belongs to the narrow layout only. */
  .pubbar .navtoggle { display: none; }
}
/* The rail's grid does not apply when there is no rail. */
.app.apptop { display: block; }
.app.apptop .main { max-width: var(--measure-wide); margin: 0 auto; padding: 0 var(--edge-pad); }

/* ============================================== the homepage catalogue (2.15) ==

   THE PAGE. Three columns, every item badged, driven entirely by pillars.json —
   the same registry the pillar shelves and the doors read. It replaced the
   expanding doors: a door is right when a page's job is to route, and this
   page's job is to show the whole catalogue at once.

   Class names are catcol* rather than col*/cathead: `.colhead` belongs to the
   market journal and `.cathead` to the hub launcher, and style.css imports this
   file first, so a same-specificity clash there would be won by the later rule
   and would land on the wrong page. */

.kickh {
  font-family: var(--font-display); font-weight: var(--weight-semibold);
  font-size: var(--text-xl); letter-spacing: var(--tracking-tight);
  color: var(--text-strong); margin: 0 0 var(--space-2);
}
.kicks {
  color: var(--text-muted); margin: 0 0 var(--space-5);
  max-width: var(--measure-prose);
}
.cat {
  display: grid; gap: var(--edge-gap);
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
/* Columns stack to one at 640, which is the constraint that keeps 390 free of
   horizontal scroll. auto-fit alone would already do it; this states it. */
@media (max-width: 640px) { .cat { grid-template-columns: 1fr; } }

.catcol {
  background: var(--surface); position: relative; overflow: hidden;
  border: var(--edge-border) solid var(--border);
  border-radius: var(--edge-radius);
}
/* data-pillar on the column sets --accent, so this one rule paints all three. */
.catcol::before {
  content: ""; position: absolute; left: 0; right: 0; top: 0; height: 3px;
  background: var(--accent);
}
.catcolhead {
  padding: var(--space-5) var(--edge-pad) var(--space-4);
  border-bottom: var(--edge-border) solid var(--border);
  background: color-mix(in srgb, var(--accent) var(--tint), transparent);
}
.catcolp {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform);
  color: var(--accent); margin: 0 0 var(--space-2);
}
.catcolhead h3 {
  font-family: var(--font-display); font-weight: var(--weight-semibold);
  font-size: var(--text-lg); letter-spacing: var(--tracking-tight);
  color: var(--text-strong); margin: 0 0 var(--space-2);
}
.catcolhead p { margin: 0; font-size: var(--text-sm); color: var(--text-muted); }
.catn {
  display: block; margin-top: var(--space-3);
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking);
  text-transform: var(--edge-label-transform); color: var(--text-faint);
}
.catcol .items { padding: var(--space-4) var(--edge-pad); }
.catcol .disc { padding: 0 var(--edge-pad); }
.catmore { padding: var(--space-3) var(--edge-pad) var(--space-5); margin: 0; }
.catmore a { font-size: var(--text-sm); }
.itcost {
  margin: var(--space-1) 0 0 !important;
  font-size: var(--text-xs); color: var(--text-faint);
}

/* ═══ /body/practice — The Daily Tai Chi Practice ═══════════════════════════
   Every value here is a token. The four diagrams carry no colour of their own
   beyond `currentColor` and three named variables; see _body_diagrams.html. */

.practice .credline {
  margin: var(--space-3) 0 0;
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); text-transform: uppercase;
  color: var(--text-faint);
}
/* The timestamp a line came from. Small, muted, and always present — it is
   what makes "nothing here was authored by a chat" checkable by a reader
   rather than only by a guard. */
.practice .src {
  display: block; margin: var(--space-2) 0 0;
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); color: var(--text-faint);
}

.practice .players { display: grid; gap: var(--space-5); }
/* 2.62: THE PLAYERS ARE THE POINT OF THE PAGE, so `big` is the whole column
   above 900px and stacked below it. 390x844 gets one full-width player above
   another, each tappable across its whole face — the button is the box. */
.practice .players.big { gap: var(--space-4); margin-bottom: var(--space-2); }
@media (min-width: 900px) { .practice .players.big { grid-template-columns: 1fr 1fr; } }
@media (min-width: 780px) { .practice .players { grid-template-columns: 1fr 1fr; } }
.practice .player { margin: 0; }
.practice .player figcaption {
  margin: 0 0 var(--space-2);
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); text-transform: uppercase;
  color: var(--text-faint);
}
.practice .ratio { position: relative; padding-top: 56.25%; }
.practice .ratio iframe {
  position: absolute; inset: 0; width: 100%; height: 100%;
  border: 0; border-radius: var(--edge-radius-sm);
}
/* CLICK TO LOAD, 2.61. The poster is ours and there is no iframe in the
   document until the person presses play — the first third-party request this
   site has ever made had to be one the visitor asked for. The box holds its own
   16:9 whether it is showing the poster or the player, so pressing play cannot
   move the page under the finger that pressed it. */
.practice .ratio[data-youtube-id] { background: var(--surface-sunken);
  border: var(--edge-border) solid var(--border);
  border-radius: var(--edge-radius-sm); overflow: hidden; }
.practice .ratio[data-youtube-id] button {
  position: absolute; inset: 0; width: 100%; height: 100%;
  display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: var(--space-2);
  background: none; border: 0; cursor: pointer; color: var(--text-muted);
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); text-transform: uppercase;
}
.practice .ratio[data-youtube-id] button:hover { color: var(--accent); }
/* A REAL POSTER TREATMENT, drawn by us and by nothing Google serves. The wash
   is the pillar accent — health is `water` — through the token the page
   already carries on its ancestor, so it cannot drift from the rail. */
.practice .players.big .ratio[data-youtube-id] {
  background:
    radial-gradient(120% 90% at 50% 0%,
      color-mix(in srgb, var(--accent) 22%, transparent) 0%, transparent 70%),
    var(--surface-sunken);
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
}
.practice .players.big .ratio[data-youtube-id] .playmark {
  width: 4.5rem; height: 4.5rem; font-size: 1.6rem;
  background: color-mix(in srgb, var(--accent) 16%, transparent);
}
.practice .players.big .ratio[data-youtube-id]:hover .playmark {
  background: color-mix(in srgb, var(--accent) 28%, transparent);
}
.practice .players.big figcaption { font-size: var(--text-sm); color: var(--accent); }

/* ---- one stage, both recordings (3.03) -------------------------------------
   `.tcgrid` / `.tccol` / `.summary.tcfull` STOOD HERE AND ARE GONE. They were
   two half-width players side by side, each with its own column of content —
   which made both small in order to show both at once, and you watch one.

   THE SLIDE IS CHOSEN BY `:target`, SO THERE IS NO INPUT ON THIS PAGE. A
   radio picker was written first and the suite rejected it: the page claims
   there is nothing to type into, and that claim is worth more than the
   convenience. Links instead, and each recording gets a URL. */
.practice .tcstage { display: grid; gap: var(--space-5); }
@media (min-width: 900px) {
  .practice .tcstage { grid-template-columns: minmax(0, 1fr) 260px;
    align-items: start; }
}
.practice .tcslide { display: none; }
/* THE FIRST SLIDE IS THE DEFAULT, and it is written as a plain rule rather
   than inside the `:has()` below — if `:has()` is ever unsupported the page
   shows both players, which is the layout it had yesterday. An unsupported
   default written the other way round shows none. */
.practice .tcslide:first-child { display: block; }
.practice .tcslide:target { display: block; }
.practice .tcslides:has(.tcslide:target) .tcslide:first-child:not(:target) {
  display: none;
}
/* The stage is one player wide now, so the pair rule that split it goes. */
.practice .tcstage .players.big { grid-template-columns: 1fr; margin: 0; }

.practice .tclist { display: grid; gap: var(--space-2); align-content: start; }
/* ONE COLUMN, BECAUSE THE NUMERAL WENT (3.05). The grid still reserved 2rem
   for an `01` that is no longer rendered, which left the label a column
   narrow enough to break "The chi kung session" one word per line. */
.practice .tcitem {
  display: block;
  padding: var(--space-3); text-decoration: none;
  border: var(--edge-border) solid var(--border);
  border-radius: var(--edge-radius-sm);
  background: var(--surface-raised);
}
.practice .tcitem:hover { border-color: var(--accent); }
.practice .tcnm { font-family: var(--font-display);
  font-weight: var(--weight-semibold); color: var(--text-strong); }
/* WHICH ONE IS PLAYING, marked on the row. Four signals are overkill for two
   rows; the accent edge and the fill are enough, and neither is opacity. */
.practice .tcstage:has(#tcv-chikung:target) .tcitem[href="#tcv-chikung"],
.practice .tcstage:has(#tcv-set:target) .tcitem[href="#tcv-set"] {
  border-color: var(--accent); background: var(--surface-sunken);
}

/* ---- the summary. Reference, not the page: smaller, tighter, secondary. --- */
.practice .summary { max-width: 62ch; }
.practice .tcblocks { max-width: none; margin-top: var(--space-6);
  padding-top: var(--space-5);
  border-top: var(--edge-border) solid var(--border); }
/* ---- a block, folded (3.03) ------------------------------------------------
   Shut by default. Forrest asked for the two longest; all five fold, because
   they are the same kind of thing and two behaviours for one shape is a page
   you have to learn twice. */
.practice .sblock { margin-top: var(--space-2);
  border: var(--edge-border) solid var(--border);
  border-radius: var(--edge-radius-sm); background: var(--surface-raised); }
.practice .sblock > summary {
  display: flex; align-items: center; gap: var(--space-3); cursor: pointer;
  padding: var(--space-3) var(--space-4); list-style: none;
}
.practice .sblock > summary::-webkit-details-marker { display: none; }
.practice .sblock > summary::marker { content: ""; }
.practice .sbbody { padding: 0 var(--space-4) var(--space-4); max-width: 62ch; }
/* `.sbsrc` STOOD HERE AND IS GONE (3.05) with the label it styled — the
   per-block "which recording" line Forrest called redundant. */
.practice .sblock .chev {
  width: 7px; height: 7px; flex: none;
  border-right: 1.5px solid var(--text-faint);
  border-bottom: 1.5px solid var(--text-faint);
  transform: rotate(45deg) translate(-2px, -2px);
}
.practice .sblock[open] > summary .chev {
  transform: rotate(-135deg) translate(-2px, -2px);
}
.practice .sblock h2 {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); text-transform: uppercase;
  color: var(--text-faint); margin: 0;
}
.practice .sblock h3 { font-size: var(--text-base); margin: 0 0 var(--space-1); }
.practice .sblock p, .practice .sblock li { font-size: var(--text-sm); }
.practice .ssteps { list-style: none; margin: 0; padding: 0; display: grid;
  gap: var(--space-3); }
.practice .ssteps li { display: grid; grid-template-columns: 2rem 1fr; gap: 0 var(--space-3); }
.practice .ssteps .sn { grid-row: span 2; font-family: var(--font-mono);
  font-size: var(--text-xs); color: var(--accent); padding-top: 2px; }
.practice .ssteps .sl { font-weight: var(--weight-semibold); }
.practice .ssteps .sh { color: var(--text-muted); }
/* **A DRAWN BOX, NOT AN `<input>` (3.04).** Forrest asked for checkboxes
   instead of bullets on the checklist. These were real checkboxes until 2.62
   and nothing ever recorded one, which is why they came out — and this page
   claims there is no input of any kind on it, which a reader can check by
   looking. The box is a span with a border: it reads as a checklist and it
   ticks nothing, which is the honest version of what it looks like. */
.practice .schecks { margin: 0; padding: 0; list-style: none;
  display: grid; gap: var(--space-2); }
.practice .schecks li { display: grid; grid-template-columns: 1rem 1fr;
  gap: var(--space-3); align-items: start; font-size: var(--text-sm); }
.practice .sbox { width: 13px; height: 13px; margin-top: 3px;
  border: var(--edge-border) solid var(--border-strong);
  border-radius: 3px; display: block; }
.practice .sbullets, .practice .slist { margin: 0; padding-left: var(--space-4);
  display: grid; gap: var(--space-2); color: var(--text-muted); }
/* TWO ACROSS, in a half column and at 390px alike. Four across was too tight
   the moment the movements moved into a column, and `auto-fit` with a 150px
   floor gives two at both widths without a second breakpoint to keep in step. */
/* **THE SET IS ONE ROW (3.05).** Forrest: "the tai chi set diagrams should
   all be in the same row if possible, not breaking to the next line." It is
   a sequence — Rise and Fall, Cloud Hands, Carry the Cauldron, Grand Tai Chi
   — and a sequence that wraps reads as two groups. 150px was wide enough to
   fit three and wrap the fourth; 110px fits four at the measure this panel
   has, and below that the wrap is the right answer rather than a scroll. */
.practice .smoves { display: grid; gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(110px, 1fr)); }
.practice .smove .movefig { max-width: 120px; }
.practice .smove p { color: var(--text-muted); margin: 0; }
.practice .squals { display: grid; gap: var(--space-3); }
.practice .squal p { color: var(--text-muted); margin: 0; }
.practice .ratio[data-youtube-id] .playmark {
  width: 3.5rem; height: 3.5rem; border-radius: 50%;
  border: var(--edge-border) solid currentColor;
  display: flex; align-items: center; justify-content: center; font-size: 1.25rem;
}
/* The line the person reads BEFORE they press it. Muted, never hidden. */
.practice .vnotice { margin: var(--space-2) 0 0; font-size: var(--text-xs);
  color: var(--text-faint); }

.practice .plansteps { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-4); }
.practice .plansteps li {
  display: grid; gap: var(--space-1);
  padding: var(--space-4); background: var(--surface-raised);
  border: var(--edge-border) solid var(--border);
  border-left: 3px solid var(--accent);
  border-radius: var(--edge-radius-sm);
}
.practice .plansteps .pn {
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); color: var(--accent);
}
.practice .plansteps .pl { font-weight: var(--weight-semibold); color: var(--text-strong); }
.practice .plansteps .ph { color: var(--text-muted); line-height: var(--leading-normal); }

.practice .posture { margin: 0; padding-left: var(--space-5); max-width: var(--measure-prose); }
.practice .posture li { margin-bottom: var(--space-2); line-height: var(--leading-normal); }

.practice .moves, .practice .quals {
  display: grid; gap: var(--space-5);
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
}
.practice .move, .practice .qual {
  padding: var(--space-4); background: var(--surface-raised);
  border: var(--edge-border) solid var(--border);
  border-radius: var(--edge-radius-sm);
}
.practice .move h3, .practice .qual h3 { margin: 0 0 var(--space-2); font-size: var(--text-lg); }
.practice .move .reps {
  font-family: var(--font-mono); font-size: var(--text-sm); color: var(--text-muted);
}
/* `color` is set on the SVG itself, not on this wrapper — a wrapper tints the
   caption underneath it too. */
.practice .movefig svg { display: block; width: 100%; max-width: 160px; margin: 0 auto var(--space-3); color: var(--accent); }

.practice .checkpoints { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--space-3); }
.practice .checkpoints label {
  display: grid; grid-template-columns: auto 1fr; gap: var(--space-1) var(--space-3);
  align-items: start; cursor: pointer;
}
.practice .checkpoints input { margin-top: 4px; width: 18px; height: 18px; accent-color: var(--accent); }
.practice .checkpoints .ct { color: var(--text-strong); font-weight: var(--weight-medium); }
.practice .checkpoints .cd { grid-column: 2; color: var(--text-muted); font-size: var(--text-sm); }

.practice .glossary { display: grid; gap: var(--space-2); margin: 0; max-width: var(--measure-prose); }
.practice .glossary dt { font-family: var(--font-mono); color: var(--accent); }
.practice .glossary dd { margin: 0 0 var(--space-3); color: var(--text-muted); }

.practice .sheet { margin-top: var(--space-4); }
.practice .sheettable { width: 100%; border-collapse: collapse; }
.practice .sheettable th, .practice .sheettable td {
  border: var(--edge-border) solid var(--border);
  padding: var(--space-3); text-align: left;
}
.practice .sheettable td { height: 2.2rem; }
.practice .gridlabel {
  margin: var(--space-5) 0 var(--space-2);
  font-family: var(--font-mono); font-size: var(--edge-label-size);
  letter-spacing: var(--edge-label-tracking); color: var(--text-faint);
}
.practice .grid28 { display: grid; grid-template-columns: repeat(14, 1fr); gap: 4px; max-width: 380px; }
.practice .d28 {
  aspect-ratio: 1; border: var(--edge-border) solid var(--border);
  border-radius: 2px;
}
.practice .rule {
  margin: var(--space-4) 0 0; max-width: var(--measure-prose);
  font-size: var(--text-sm); color: var(--text-muted); line-height: var(--leading-normal);
}
.practice .disclaimer { max-width: var(--measure-prose); }

/* ── print: the sheet alone, one page ──────────────────────────────────────
   THIS WAS WRONG ONCE AND PRODUCED FOUR PAGES. Hiding screen furniture is not
   enough — every non-sheet section has to go, AND the sheet section's own
   siblings inside it. Verified by rendering to PDF and counting pages. */
@media print {
  @page { margin: 12mm; }
  body { background: var(--print-paper); color: var(--print-ink); }
  .pubbar, .side, footer, .practice section { display: none !important; }
  .practice section.printme { display: block !important; border-top: none; margin: 0; padding: 0; }
  .practice section.printme .panebody > * { display: none !important; }
  .practice section.printme .sheet {
    display: block !important; margin: 0; padding: 0;
    background: var(--print-paper); border: 0; page-break-inside: avoid;
  }
  .practice .sheet .grid28 { max-width: 380px; }
  .practice .sheet .rule { font-size: 11px; margin-top: 14px; }
}

/* ---- who is teaching this: the credentials block -------------------------
   ONE COMPONENT, TWO TREATMENTS, selected by a modifier. Everything below the
   visual is identical across both because the template renders it from one
   macro — switching treatment cannot touch a claim, which is the entire design.

   The kit's preview shim declared its own colours. None of them survive here:
   every value below is a token, so the block is water blue on /body and the
   right colour on any pillar it is ever mounted on, without a second file. */
.bc { margin: var(--space-6) 0; }
.bc-panel {
  background: var(--surface-raised);
  border: var(--edge-border) solid var(--border);
  border-radius: var(--edge-radius); overflow: hidden;
}
.bc-media { margin: 0; background: var(--surface-sunken); }
.bc-media img { width: 100%; height: auto; object-fit: cover; display: block; }
/* CAPTION BELOW THE PHOTOGRAPH, not over it. The kit laid it over a dark
   gradient scrim, which needs a near-white literal to stay legible — and the
   colour-literal guard refused it, correctly. "It is a shadow, not a brand
   value" is the shape of every exception that rule exists to turn down. Below
   the image it is ordinary text on ordinary tokens, it reads the same in both
   themes, and it does not depend on what the photograph happens to look like
   in its bottom third. */
.bc-media figcaption {
  padding: var(--space-2) var(--space-4) var(--space-3);
  font-size: var(--text-sm); color: var(--text-muted);
}
.bc-body { padding: var(--space-5); }
/* THE MARK INHERITS --accent AND CARRIES NO HEX. If the token disappears the
   stroke falls to the inherited text colour and looks obviously wrong, which is
   the point — a hardcoded blue would survive the token being renamed and
   nobody would learn anything. */
.bc-mark { color: var(--accent); width: 56px; margin: 0 0 var(--space-3); }
.bc-mark svg { width: 100%; height: auto; display: block; }
.bc-name {
  font-family: var(--font-display); font-size: var(--text-xl);
  font-weight: var(--weight-semibold); letter-spacing: -.02em;
  margin: 0 0 var(--space-1); color: var(--text-strong);
}
.bc-cred { color: var(--accent); margin: 0 0 var(--space-3); font-weight: 500; }
.bc-lede { margin: 0 0 var(--space-4); max-width: 62ch; }
.bc-record {
  list-style: none; margin: 0; padding: var(--space-4) 0 0;
  border-top: var(--edge-border) solid var(--border);
}
.bc-record li {
  display: grid; grid-template-columns: 5.5rem 1fr; gap: var(--space-3);
  padding: var(--space-2) 0; font-size: var(--text-sm);
  color: var(--text-muted); align-items: baseline;
}
.bc-record time {
  font-family: var(--font-mono); font-size: var(--text-xs);
  color: var(--accent); letter-spacing: .04em;
}
.bc-record b { color: var(--text); font-weight: 600; }
.bc-note {
  margin: var(--space-4) 0 0; padding-top: var(--space-3);
  border-top: var(--edge-border) solid var(--border);
  font-size: var(--text-sm); color: var(--text-muted); max-width: 64ch;
}
@media (max-width: 640px) {
  .bc-record li { grid-template-columns: 1fr; gap: 0; }
}
