/* ============================================================
   erulia.com v2 — SHARED SHELL
   Layer 2. Loads AFTER tokens.css (which is a verbatim copy of
   design/tokens.css and stays that way — do not edit it here).

   This file holds only what the site adds on top of the design
   language: the marketing type scale at weight 550, the section
   rhythm + container-width hairline, the nav, the footer, the
   single CTA, and the four-item motion budget.

   Every deviation from tokens.css is labelled with the clause of
   erulia-site-v2-structure.md that authorises it. There are four,
   and no others:
     §2.9  headlines 750/700/650 → 550   (the highest-leverage change)
     §4    nav 64px → 56px, links 14px/500
     §4    section separator = 1px --line at CONTAINER width
     §4    hero word-rise gets its own easing (see --ease-rise)
   ============================================================ */

/* Self-hosted variable fonts (latin subset, from Google Fonts v2 —
   the same files the css2 URL serves. Self-hosting removes the
   render-blocking third-party chain and any Google pings). */
@font-face {
  font-family: 'Hanken Grotesk';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('fonts/hanken-grotesk-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url('fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

:root {
  /* Container + gutter as variables so the section hairline can be drawn
     at exactly container width without hard-coding the number twice (§4). */
  --container: 1120px;
  --gutter: 24px;
  --nav-h: 56px;

  /* The ONE easing this file adds. The motion budget (§4) specifies the hero
     word-rise as 0.55s cubic-bezier(.22,1,.36,1) — an overshoot-free ease-out
     with a longer tail than --ease-out, measured off the reference. It is
     scoped to the H1 rise and nothing else may use it. */
  --ease-rise: cubic-bezier(.22, 1, .36, 1);
  --t-rise: 550ms;
}
@media (min-width: 1024px) {
  :root { --gutter: 32px; }
}

/* ============ base ============ */
html { scroll-behavior: smooth; scroll-padding-top: calc(var(--nav-h) + 16px); }
img, svg { max-width: 100%; }
a { color: inherit; }

.container { max-width: var(--container); margin: 0 auto; padding: 0 var(--gutter); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}
.skip-link {
  position: absolute; left: var(--gutter); top: 8px; z-index: 100;
  transform: translateY(-200%);
  background: var(--bg); color: var(--ink); border: 1px solid var(--line-strong);
  border-radius: var(--r-sm); padding: 10px 16px;
  font-size: 14px; font-weight: 600; text-decoration: none;
}
.skip-link:focus-visible { transform: none; }

/* ============ marketing type scale (§2.9 — nothing heavier than 550) ============
   tokens.css sets .display/h1/h2/h3 at 750/700/700/650 for the design language
   at large. The site drops all four to 550: the reference sets EVERY heading at
   500, and the weight — not the serif — is what makes it read expensive. 550
   because Hanken carries more ink per stem than the reference face at 500.
   Buttons and eyebrows stay at 600: those are UI-face component contracts from
   ERULIA-DESIGN §6, not headline weight, and dropping them costs legibility. */
h1, h2, h3, .display { font-weight: 550; }

.h-hero {                                   /* the H1 on a full hero (§5 block 2) */
  font-family: var(--font-display);
  font-size: clamp(40px, 6vw, 76px);
  line-height: 1.02;
  letter-spacing: -0.022em;
  font-weight: 550;
  margin: 0;
  text-wrap: balance;
  /* No color here on purpose: it inherits, so .on-dark can invert it. An
     explicit `color: var(--ink)` on this rule beats the ancestor .on-dark
     at equal specificity and paints the hero H1 ink-on-ink (contrast 1.0). */
}
h2 {                                        /* section head */
  font-size: clamp(32px, 4vw, 52px);
  line-height: 1.08;
  letter-spacing: -0.023em;
}
h3 {                                        /* row sub-head (28px in the reference) */
  font-size: clamp(24px, 2.2vw, 28px);
  line-height: 1.22;
  letter-spacing: -0.018em;
}
.lead, .sub {
  font-family: var(--font-ui);
  font-size: 19px; line-height: 1.55; font-weight: 500;
  color: var(--ink-2);
  margin: 0;
}
.caption { font-size: 13px; line-height: 1.6; color: var(--ink-3); margin: 0; }
.eyebrow { margin: 0 0 16px; }
.eyebrow.quiet { color: var(--ink-3); }

/* On the dark hero band (§6 D1) the shell inverts. Only the hero does this —
   the rest of every page stays light. This is not a dark theme (banlist 3). */
.on-dark { color: #FFFFFF; }
/* tokens.css sets `color: var(--ink)` on .display/h1/h2/h3 directly, which the
   inherited .on-dark colour cannot beat. Invert them explicitly. */
.on-dark .display, .on-dark h1, .on-dark h2, .on-dark h3 { color: #FFFFFF; }
.on-dark .lead, .on-dark .sub { color: rgba(255, 255, 255, .78); }
.on-dark .caption { color: rgba(255, 255, 255, .62); }
.on-dark .eyebrow { color: rgba(255, 255, 255, .70); }
.on-dark :focus-visible { outline-color: #FFFFFF; }

/* ============ section rhythm + the container-width hairline (§4) ============
   The single strongest signature borrowed from the reference: a 1px rule
   between every section, stopping at container width — never full-bleed.
   Drawn by the FOLLOWING section so a page never ends on a stray rule. */
.section { position: relative; padding: var(--space-section) 0; }
.section--soft { background: var(--bg-soft); }
.section--tint { background: var(--bg-tint); }   /* max ONE per page (§4) */
.section--flush { padding-top: 0; }
.section--dark { background: var(--ink); }
/* A strip is a section that carries one line of content (the six source
   kinds, §5 block 5). Full section rhythm around one line reads as a gap
   in the page rather than a band in it. */
.section--slim { padding: var(--space-48) 0; }

.rule { position: relative; height: 1px; }   /* standalone separator, same geometry */
.section + .section::before,
.rule::before {
  content: "";
  position: absolute; top: 0; left: 50%;
  transform: translateX(-50%);
  width: calc(min(var(--container), 100%) - var(--gutter) * 2);
  height: 1px;
  background: var(--line);
}
/* "Between every section" (§4) means every section — including the edges of a
   soft or tint band, where the hairline sits over the background change rather
   than replacing it. The ONE exception is the dark hero band: --line on --ink
   is not a hairline, it is a smudge, and the band's job is to dissolve. */
.section + .section--dark::before,
.section--dark + .section::before { display: none; }

/* Section head block — used by every page, so it lives in the shell.
   The measure is set on the HEADING, not on the wrapper. `ch` resolves
   against the element's own font-size, so a max-width on the wrapper is
   34 characters of 17px Inter — 365px — and it squeezes a 52px headline
   into a five-line column. Measured on the first real page, block 2.
   34ch of the heading's own face is ~880px, which is the two-line
   headline the wireframes are drawn with. */
.section-head { margin: 0 0 var(--space-48); }
.section-head > h1, .section-head > h2, .section-head > .display { max-width: 34ch; }
.section-head p.lead { margin-top: var(--space-20); max-width: 54ch; }

/* ============ the one CTA (§4) ============
   "Get Erulia", same words, same pill, one per section. Secondary actions
   are plain text links — never a second button. .btn geometry comes from
   tokens.css; the shell only adds the 44px tap floor and the nav-sized cut. */
.btn { min-height: 48px; }
.btn .arr { transition: transform var(--t-fast) var(--ease-out); }
.btn:hover .arr { transform: translateX(2px); }
.btn[disabled] { opacity: .6; cursor: default; transform: none; box-shadow: none; }
.btn-sm { min-height: 44px; padding: 11px 20px; font-size: 15px; }
.btn-on-dark { background: #FFFFFF; color: var(--ink); }
.btn-on-dark:hover { background: rgba(255, 255, 255, .88); }

.textlink {                                  /* the ONLY secondary action shape */
  display: inline-flex; align-items: center; min-height: 44px;
  color: var(--ink); font-size: 16px; font-weight: 500;
  text-decoration: underline; text-decoration-color: var(--line-strong);
  text-underline-offset: 4px; text-decoration-thickness: 1px;
  transition: text-decoration-color var(--t-fast) var(--ease-out);
}
.textlink:hover { text-decoration-color: var(--accent-600); }
.on-dark .textlink { color: #FFFFFF; text-decoration-color: rgba(255, 255, 255, .45); }
.on-dark .textlink:hover { text-decoration-color: #FFFFFF; }

/* ============ nav (§4: 56px, 1120px, transparent over hero → solid past 40px) ============ */
.nav {
  position: sticky; top: 0; z-index: 50;
  height: var(--nav-h);
  background: transparent;
  backdrop-filter: none;
  border-bottom: 1px solid transparent;
  transition: background var(--t-base) var(--ease-out),
              border-color var(--t-base) var(--ease-out);
}
.nav.is-solid {
  background: rgba(255, 255, 255, .86);
  backdrop-filter: blur(12px);
  border-bottom-color: var(--line);
}
.nav-in {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-16);
  height: var(--nav-h);
}
.wordmark {
  display: inline-flex; align-items: center; gap: 4px;
  font-family: var(--font-display); font-weight: 550; font-size: 21px; letter-spacing: -.02em;
  color: var(--ink); text-decoration: none;
  transition: opacity var(--t-fast) var(--ease-out), color var(--t-base) var(--ease-out);
}
.wordmark:hover { opacity: .75; }
.wordmark .dot {
  display: inline-block; width: 7px; height: 7px;
  border-radius: var(--r-pill); background: var(--accent-500);
}
.nav-links { display: flex; align-items: center; gap: 2px; }
.nav-links a {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 10px;
  font-size: 14px; font-weight: 500; color: var(--ink-2); text-decoration: none;
  border-radius: var(--r-sm);
  transition: color var(--t-fast) var(--ease-out);
}
.nav-links a:hover { color: var(--ink); }
.nav-links a[aria-current="page"] { color: var(--ink); }
.nav-end { display: flex; align-items: center; gap: var(--space-8); }

/* `Sign up` in the nav end, added with step 9. It shipped with a class
   and NO rule — so it inherited `a { color: inherit }`, which over the
   dark hero on `/` measured **2.0:1** against rgb(30,36,48): present in
   the DOM, invisible on the page until the nav solidifies on scroll. It
   also missed the 44px floor every other control in this bar keeps.
   It gets the `.nav-links a` contract rather than one of its own,
   because it is the same object — a text destination in the bar. The
   only difference is which flex container it sits in. */
.nav-link {
  display: inline-flex; align-items: center; min-height: 44px; padding: 0 10px;
  font-size: 14px; font-weight: 500; color: var(--ink-2); text-decoration: none;
  border-radius: var(--r-sm);
  transition: color var(--t-fast) var(--ease-out);
}
.nav-link:hover { color: var(--ink); }

/* Over a dark hero, before the nav solidifies. */
.nav--over-dark:not(.is-solid) .wordmark { color: #FFFFFF; }
.nav--over-dark:not(.is-solid) .nav-links a,
.nav--over-dark:not(.is-solid) .nav-link { color: rgba(255, 255, 255, .74); }
.nav--over-dark:not(.is-solid) .nav-links a:hover,
.nav--over-dark:not(.is-solid) .nav-links a[aria-current="page"],
.nav--over-dark:not(.is-solid) .nav-link:hover { color: #FFFFFF; }
.nav--over-dark:not(.is-solid) .btn { background: #FFFFFF; color: var(--ink); }
.nav--over-dark:not(.is-solid) .btn:hover { background: rgba(255, 255, 255, .88); }
.nav--over-dark:not(.is-solid) .sheet-btn { border-color: rgba(255, 255, 255, .38); background: transparent; }
.nav--over-dark:not(.is-solid) .sheet-btn .bars,
.nav--over-dark:not(.is-solid) .sheet-btn .bars::before,
.nav--over-dark:not(.is-solid) .sheet-btn .bars::after { background: #FFFFFF; }

/* Sheet menu — below 640px (§4). */
.sheet-btn {
  display: none; align-items: center; justify-content: center;
  width: 44px; height: 44px; padding: 0;
  border: 1px solid var(--line-strong); border-radius: var(--r-md);
  background: var(--bg); cursor: pointer;
  transition: background var(--t-fast) var(--ease-out), border-color var(--t-fast) var(--ease-out);
}
.sheet-btn:hover { background: var(--bg-soft); }
.sheet-btn .bars { position: relative; display: block; width: 18px; height: 2px; background: var(--ink); border-radius: var(--r-pill); }
.sheet-btn .bars::before, .sheet-btn .bars::after {
  content: ""; position: absolute; left: 0; width: 18px; height: 2px;
  background: var(--ink); border-radius: var(--r-pill);
}
.sheet-btn .bars::before { top: -6px; }
.sheet-btn .bars::after { top: 6px; }

.sheet {
  display: none;
  background: var(--bg); border-bottom: 1px solid var(--line);
}
.sheet.is-open { display: block; }
.sheet a {
  display: flex; align-items: center; min-height: 52px; padding: 0 var(--gutter);
  font-size: 16px; font-weight: 500; color: var(--ink); text-decoration: none;
  border-top: 1px solid var(--line);
  transition: background var(--t-fast) var(--ease-out);
}
.sheet a:hover { background: var(--bg-soft); }
.sheet .sheet-cta { color: var(--accent-600); font-weight: 600; }

@media (max-width: 639px) {
  .nav-links { display: none; }
  .nav-end .btn { display: none; }
  /* Same reason as the line above, and it arrived later (step 9): the
     sheet already carries `Sign up`, so leaving the nav-end copy visible
     puts a 25px tap target beside a 44px one and says the same thing
     twice. Hidden rather than enlarged — the control is redundant here,
     not too small. */
  .nav-end .nav-link { display: none; }
  .sheet-btn { display: inline-flex; }
}

/* ============ footer (§3: 4 columns + status line) ============ */
.footer { background: var(--bg-soft); border-top: 1px solid var(--line); }
.footer-in { padding: var(--space-64) 0 var(--space-40); }
.footer-cols {
  display: grid;
  grid-template-columns: 1.4fr repeat(3, 1fr);
  gap: var(--space-40) var(--space-32);
}
.footer-brand .caption { margin-top: var(--space-12); max-width: 26ch; }
.footer h4 {
  font-family: var(--font-ui); font-size: 13px; font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase; color: var(--ink-3);
  margin: 0 0 var(--space-16);
}
.footer ul { list-style: none; margin: 0; padding: 0; }
.footer li + li { margin-top: 2px; }
.footer li a {
  display: inline-flex; align-items: center; min-height: 32px;
  font-size: 15px; font-weight: 500; color: var(--ink-2); text-decoration: none;
  transition: color var(--t-fast) var(--ease-out);
}
.footer li a:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }
/* Phase-2/3 destinations are named but not linked — a nav item that goes
   nowhere is worse than one that says it isn't open yet. */
.footer li span.soon {
  display: inline-flex; align-items: center; min-height: 32px; gap: 6px;
  font-size: 15px; font-weight: 500; color: var(--ink-3);
}
.footer li span.soon::after {
  content: "soon";
  /* 12px, not 11: 13px is the site's smallest text and this is the one thing
     allowed under it, so it stops one step down rather than two. */
  font-size: 12px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-3); background: var(--surface-3);
  border-radius: var(--r-pill); padding: 2px 8px;
}
.footer-status {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--space-12) var(--space-24);
  margin-top: var(--space-48); padding-top: var(--space-24);
  border-top: 1px solid var(--line);
}
.footer-status .status {
  display: inline-flex; align-items: center; gap: var(--space-8);
  font-size: 13px; color: var(--ink-2);
}
.footer-status .status .pip {
  width: 7px; height: 7px; border-radius: var(--r-pill);
  background: var(--accent-500); flex: none;
}

@media (max-width: 899px) {
  .footer-cols { grid-template-columns: 1fr 1fr; }
  .footer-brand { grid-column: 1 / -1; }
}
@media (max-width: 479px) {
  .footer-cols { grid-template-columns: 1fr; gap: var(--space-32); }
}

/* ============ motion budget — FOUR, total (§4) ============
   1. hero word-rise   0.55s, 40ms stagger, H1 only
   2. section reveal   opacity + 14px, 420ms, once per section
   3. hover lift       −4px + shadow-3, 220ms
   4. accordion        220ms
   Nothing else animates. All four are gated behind .js-anim, which shell.js
   only sets once it has proved an IntersectionObserver actually calls back —
   so content is never left invisible if the observer can't run. */

/* 1 — word-rise */
.js-anim .word-rise .w { display: inline-block; overflow: hidden; vertical-align: top; }
.js-anim .word-rise .w > span {
  display: inline-block;
  transform: translateY(105%);
  transition: transform var(--t-rise) var(--ease-rise);
  transition-delay: calc(var(--i, 0) * 40ms);
}
.js-anim .word-rise.in .w > span { transform: none; }

/* 2 — section reveal */
.js-anim .rv {
  opacity: 0; transform: translateY(14px);
  transition: opacity var(--t-slow) var(--ease-out), transform var(--t-slow) var(--ease-out);
}
.js-anim .rv.in { opacity: 1; transform: none; }

/* 3 — hover lift */
.lift {
  transition: transform var(--t-base) var(--ease-out),
              box-shadow var(--t-base) var(--ease-out),
              border-color var(--t-base) var(--ease-out);
}
.lift:hover { transform: translateY(-4px); box-shadow: var(--shadow-3); border-color: var(--line-strong); }

/* 4 — accordion */
.acc-item { border-top: 1px solid var(--line); }
.acc-item:last-child { border-bottom: 1px solid var(--line); }
.acc-q {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-24);
  width: 100%; padding: var(--space-24) 0;
  background: none; border: 0; cursor: pointer; text-align: left;
  font-family: var(--font-ui); font-size: 17px; font-weight: 500; color: var(--ink);
}
.acc-q .sign { position: relative; flex: none; width: 14px; height: 14px; }
.acc-q .sign::before, .acc-q .sign::after {
  content: ""; position: absolute; left: 0; top: 6px; width: 14px; height: 2px;
  background: var(--ink-3); border-radius: var(--r-pill);
  transition: transform var(--t-base) var(--ease-out);
}
.acc-q .sign::after { transform: rotate(90deg); }
.acc-q[aria-expanded="true"] .sign::after { transform: rotate(0deg); }
.acc-panel { display: grid; grid-template-rows: 0fr; transition: grid-template-rows var(--t-base) var(--ease-out); }
.acc-panel > div { overflow: hidden; }
.acc-item.is-open .acc-panel { grid-template-rows: 1fr; }
.acc-panel p { margin: 0 0 var(--space-24); max-width: 62ch; color: var(--ink-2); font-size: 16px; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { transition-duration: .01ms !important; animation-duration: .01ms !important; }
  .js-anim .rv { opacity: 1; transform: none; }
  .js-anim .word-rise .w > span { transform: none; }
  .acc-panel { transition: none; }
}

/* ============================================================
   PAGE COMPOSITION
   Added with block 2. These are the block shapes the wireframes
   in §5 are assembled from, and every one of them is used by at
   least three of the seven pages — the hero, the alternating
   row, the refusal band, the FAQ and the closing CTA all recur.
   That is why they live in the shell and not in a home-only
   stylesheet: the second page to want an alternating row must
   not get to re-derive one by eye.
   Product mocks are layer 3 — see mocks.css.
   ============================================================ */

/* ============ hero (§5 block 2) ============
   The dark band is a BAND, not a theme (§6 D1): it dissolves to
   white inside the first screen and every other section on every
   page is light. Nothing here may leak past the hero. */
.hero {
  position: relative;
  min-height: min(92vh, 880px);
  display: flex; flex-direction: column; justify-content: center;
  /* The nav is sticky, which means it OCCUPIES 56px of flow above main —
     so "transparent over the hero" is not free. Pull the band up under it
     and pay the height back as padding. Without this the nav sits on the
     body's white above the band and nav--over-dark paints white on white. */
  margin-top: calc(var(--nav-h) * -1);
  padding-top: calc(var(--nav-h) + clamp(32px, 5vw, 72px));
  padding-bottom: clamp(48px, 6vw, 88px);
  overflow: hidden;
  isolation: isolate;
}
/* One very faint lightening behind the headline, so a flat fill doesn't
   read as a cheap block of colour. This is a ground vignette — the
   reference's radial mask, minus its photograph — and it is emphatically
   NOT a floating blurred blob (banlist 1): it is anchored to the band,
   fills it edge to edge, and never moves. */
.hero::before {
  content: "";
  position: absolute; inset: 0; z-index: -2;
  background: radial-gradient(78% 62% at 50% 8%, rgba(255, 255, 255, .075), transparent 72%);
}
/* The dissolve. The hero has to hand over to a white page without a seam,
   which is the whole reason the band can exist at all. */
.hero::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: 0; z-index: -1;
  height: 300px;
  background: linear-gradient(to bottom, rgba(255, 255, 255, 0), var(--bg) 88%);
  pointer-events: none;
}
.hero-in { text-align: center; }
.hero .sub { margin: var(--space-24) auto 0; max-width: 52ch; }
.hero-cta {
  margin: var(--space-32) 0 0;
  display: flex; align-items: center; justify-content: center;
  gap: var(--space-12) var(--space-24); flex-wrap: wrap;
}
.hero .hero-note { margin-top: var(--space-20); }

/* The product zone. Two real objects, never three (§5 block 2): the
   readiness card, and one Lurest capture row behind it and left,
   clipped by the viewport edge. */
/* The hero clips (overflow: hidden) so the second object can run off the
   viewport edge — which means the card must stay INSIDE the band. No
   negative bottom margin here: it would hang the card over the clip. */
.hero-stage {
  position: relative;
  margin: clamp(40px, 6vw, 72px) auto 0;
  width: 100%;
  max-width: 620px;                     /* card 560–640px (§5) */
}
.hero-card { position: relative; z-index: 2; }
.hero-behind {
  position: absolute; z-index: 1;
  top: 62px; right: calc(100% - 76px);  /* clipped by the viewport edge */
  width: 460px;
  opacity: .92;
}
/* Below 1200px there is no room to the left of the card for a second
   object without it colliding. Two objects or one — never a squeezed two. */
@media (max-width: 1199px) {
  .hero-behind { display: none; }
}

/* ---- the interior hero (§5, added with /readiness) ----
   Every page after `/` opens on a compact hero: no photo, no dark band
   (D1 puts the dark band on the home hero and says the rest of the site
   stays light), text left rather than centred, and one real object beside
   it instead of a stage. It is still `.hero` so the H1 word-rise, the
   type and the CTA row are the same ones — only the band is different. */
.hero--compact {
  min-height: 0;
  display: block;
  overflow: visible;                    /* nothing is clipped off-viewport here */
  padding-top: calc(var(--nav-h) + clamp(28px, 4vw, 56px));
  padding-bottom: clamp(48px, 6vw, 80px);
}
/* The ground vignette and the white dissolve both exist to hand a DARK
   band over to a white page. On a light hero they are white-on-white. */
.hero--compact::before, .hero--compact::after { display: none; }

.hero-split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.04fr);
  align-items: center;
  gap: clamp(40px, 5vw, 72px);
}
.hero--compact .hero-copy { text-align: left; }
.hero--compact .sub { margin-left: 0; margin-right: 0; max-width: 46ch; }
.hero--compact .hero-cta { justify-content: flex-start; }
.hero--compact .hero-note { max-width: 46ch; }
/* The stage's max-width exists to hold the home hero's card at 560–640px
   in a centred column. In the split hero the grid column already does that. */
.hero-stage--split { margin: 0; max-width: none; }

@media (max-width: 899px) {
  .hero-split { grid-template-columns: 1fr; gap: var(--space-40); }
  .hero--compact .hero-stage--split { margin-bottom: 0; }
}

/* ---- the stacked interior hero (§5 /revula, added with step 4) ----
   /readiness opens on a split hero because its object is one ring and
   one panel — narrow, and it belongs beside the sentence it explains.
   /revula's object is a whole app screen, and the wireframe asks for it
   full width. So the copy keeps the compact hero's left-aligned column
   and the stage drops below it at container width, which is the only
   other honest way to put a real screen in a hero: shrink a 1120px
   product screen into a 540px column and the mock fidelity floor (three
   rows of real content at the in-app 13px floor) stops being reachable. */
.hero--stack .hero-copy { max-width: 62ch; }
.hero-stage--wide { margin: clamp(40px, 5vw, 64px) 0 0; max-width: none; }

/* ============ alternating rows (§2.4) ============
   28px sub-head + one paragraph + a live mock opposite. On `/` the
   alternation carries real information: it is a pipeline, not three
   features, so the eye is meant to travel left-right-left. */
.rows { display: flex; flex-direction: column; gap: clamp(64px, 8vw, 112px); }
.row {
  display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.12fr);
  align-items: center; gap: clamp(32px, 5vw, 72px);
}
.row--flip .row-text { order: 2; }
.row-text { max-width: 46ch; }
.row-text .m-label { margin-bottom: var(--space-16); }
.row-text h3 { margin: 0 0 var(--space-16); }
.row-text p { font-size: 17px; line-height: 1.65; color: var(--ink-2); margin: 0; }
.row-text p + p { margin-top: var(--space-16); }
.row-text .textlink { margin-top: var(--space-12); }

/* A row's own footnote (added with /readiness). Where a mechanism's
   ambition runs ahead of the arithmetic the mock beside it is actually
   showing, the row says so here rather than letting the two contradict
   each other quietly. It is deliberately small and deliberately not
   hidden: on a page whose whole job is "this number can be audited",
   the caveats are load-bearing. `.row-text p` would otherwise win on
   specificity and set this at 17px.

   Three selectors, ONE rule, generalised in step 7. /download needs the
   same footnote in two places that are not alternating rows — a column
   of the requirements sheet, and the prose beside the seam — and the
   caveat device is the last thing on this site that should exist in
   three slightly different sizes. The bare `.note` is the general form;
   the two descendant selectors stay only because they have to outrank
   the container's own `p` rule. */
.row-text .row-note,
.req .row-note,
.note {
  margin-top: var(--space-20);
  padding-left: var(--space-16);
  border-left: 2px solid var(--line);
  font-size: 13px; line-height: 1.6; color: var(--ink-3);
}

/* ============ the six-kind strip (§5 block 5) ============ */
.strip-line {
  display: flex; align-items: baseline; gap: var(--space-16) var(--space-32);
  flex-wrap: wrap;
}
.strip-line .lead-in { font-size: 15px; font-weight: 500; color: var(--ink-3); }

/* ============ the refusal band (§5 block 7) ============
   The reference answers this slot with three 48px/400 stat numerals, and
   the SIZE is the confidence signal. Ours are refusals, at 44px/550 —
   equal mass, since 550 carries more ink than 400 — hand-broken to exactly
   two lines in all three cells so the trio reads as one shape (§7.1).
   The <br> in each cell is load-bearing. Do not reflow it.

   `.facts` / `.fact` / `.count` (added with /revula) is the same band
   holding the other kind of statement. `/` and `/readiness` end on three
   things the product refuses to do; /revula ends on three things that can
   be counted — 4 modes, 1 note, every citation. Same geometry on purpose:
   it is one band that either refuses or counts, not two bands, and the
   only difference is which words go in the big type.

   `.commits` / `.commit` / `.pledge` (added with /how-we-build) is the
   third and last verb: the band that PROMISES. Same three cells, same
   authored two-line break. A fourth alias would mean the band had stopped
   being one shape, so if a page ever needs one, generalise the names
   instead of adding to this list. */
.refusals, .facts, .commits { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-40); }
/* Its own class, NOT `.refusal p` — the cell also holds a body paragraph,
   and a bare descendant selector sets that one at 44px/nowrap too, which
   is 2500px wide and takes the whole document with it. */
.refusal .refuse,
.fact .count,
.commit .pledge {
  font-family: var(--font-display);
  font-size: clamp(32px, 3.4vw, 44px);
  line-height: 1.1;
  letter-spacing: -.022em;
  font-weight: 550;
  color: var(--ink);
  margin: 0 0 var(--space-16);
  white-space: nowrap;                  /* the break is authored, not computed */
}
.refusal .m-body, .fact .m-body, .commit .m-body { color: var(--ink-2); }
@media (max-width: 899px) {
  .refusals, .facts, .commits { grid-template-columns: 1fr; gap: var(--space-32); }
}

/* ============ FAQ (§5 block 8) ============ */
.acc { max-width: 76ch; }

/* ============ closing CTA (§5 block 9) ============
   Shared verbatim with /access (§7.4) — the door must not be plainer
   than the sign pointing at it. */
.closing { position: relative; overflow: hidden; isolation: isolate; text-align: center; }
/* Soft viridian rising from the page bottom. A MASKED FIELD, not a blurred
   blob (§5 block 9): it is pinned to the section's bottom edge, spans the
   full width, and has no shape of its own. */
.closing::after {
  content: "";
  position: absolute; left: 0; right: 0; bottom: 0; z-index: -1;
  height: 340px;
  background: var(--accent-500); opacity: .08;
  -webkit-mask-image: radial-gradient(115% 100% at 50% 100%, #000 0%, transparent 68%);
  mask-image: radial-gradient(115% 100% at 50% 100%, #000 0%, transparent 68%);
  pointer-events: none;
}
.closing-in { max-width: 44ch; margin: 0 auto; }
.closing-in .lead { margin-top: var(--space-20); }
/* The price line under a closing pill. `/` and /readiness fill this slot
   with the access form's own note; the product pages have no form there,
   so they need the same spacing without borrowing a form class. */
.closing .cta-note { margin: var(--space-16) 0 0; }

/* `.access-form` — the inline waitlist field — was deleted with the rest
   of the waitlist at §7.1. Its rules went with it rather than being left
   as dead CSS: `grep -rl access-form *.html *.js` returns nothing.
   `.form-note` stays; /signup, /signin and /reset all use it. */
.form-note { margin: var(--space-16) 0 0; min-height: 1.6em; }
.form-note .err { color: var(--danger); }

/* The pen-tick success moment, migrated from v1. Swaps in place, focus
   moves to the heading, and there is an escape hatch back to the form. */
.success { text-align: center; }
.success .tickwrap {
  display: inline-flex; align-items: center; justify-content: center;
  width: 46px; height: 46px; border-radius: var(--r-pill);
  background: var(--accent-50); color: var(--accent-600);
  margin: var(--space-16) 0 var(--space-20);
}
/* The pen-tick draw is the one personality accent in the design language,
   and it is a fifth motion only in the sense that a form success is not a
   page animation — it fires once, on an explicit user action, and is
   gated on the same reduced-motion rule as the other four. */
.success .tick path { stroke-dasharray: 26; stroke-dashoffset: 26; }
.success.in .tick path { transition: stroke-dashoffset 420ms var(--ease-out) 60ms; stroke-dashoffset: 0; }
.success h3 { margin: 0 0 var(--space-12); }
.success p { color: var(--ink-2); margin: 0 auto; max-width: 46ch; }
.success .again {
  margin-top: var(--space-24);
  background: none; border: 0; padding: 0; cursor: pointer;
  font-family: var(--font-ui); font-size: 16px; font-weight: 500; color: var(--accent-600);
  min-height: 44px;
}
.success .again:hover { text-decoration: underline; text-underline-offset: 4px; }

@media (prefers-reduced-motion: reduce) {
  .success .tick path { stroke-dashoffset: 0; }
}

/* ============ row + hero stacking ============ */
@media (max-width: 899px) {
  /* Mock always after text (§4), including in flipped rows — reading order
     wins over alternation once there is only one column. */
  .row { grid-template-columns: 1fr; gap: var(--space-32); }
  .row--flip .row-text { order: 0; }
  .row-text { max-width: 60ch; }
  .hero { min-height: 0; padding-bottom: var(--space-32); }
  .hero-stage { margin-bottom: calc(var(--space-32) * -1); }
}

/* ============================================================
   STEP 6 · /how-we-build
   The first block in this file whose shapes have exactly one
   user, and that is not an oversight to fix later. The header
   above says a pattern earns its place here by recurring on
   three of the seven pages; the trust page is the one page
   built out of nothing else — a statement hero with no object
   beside it, a two-column ledger, and a list of the product's
   own unbuilt claims. None of those three has a second page
   that wants it, and a fourth stylesheet for one page is a
   worse trade than a labelled block in the third one.
   If a second page ever wants the ledger, move it up into
   PAGE COMPOSITION rather than copying it.
   ============================================================ */

/* ---- the statement hero (no product object) ----
   Every other interior hero is sized by the thing beside it. This one
   has nothing beside it, so the type has to hold the screen alone: the
   H1 measure is tightened so it breaks into three lines of roughly equal
   length rather than one long ribbon, and the band gets more air below
   than a hero that hands over to a mock. */
.hero--type { padding-bottom: clamp(56px, 7vw, 96px); }
.hero--type .h-hero { max-width: 17ch; }
.hero--type .sub { max-width: 54ch; }
.hero--type .hero-note { max-width: 54ch; margin-top: var(--space-32); }

/* ---- the ledger ----
   Two columns of named entries, hairline-separated. Deliberately NOT a
   table: the two columns are two lists that happen to sit side by side,
   and nothing in row 3 of the left column corresponds to row 3 of the
   right. A <table> would assert a correspondence that isn't there, and
   would break badly at the point where the columns stack. */
.ledger {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0 clamp(40px, 5vw, 80px);
}
.ledger-col h3 { margin: 0 0 var(--space-8); }
.ledger-col > .m-meta { margin-bottom: var(--space-24); max-width: 34ch; }
.led { list-style: none; margin: 0; padding: 0; }
.led > li { border-top: 1px solid var(--line); padding: var(--space-20) 0; }
.led > li:last-child { border-bottom: 1px solid var(--line); }
.led-t {
  font-family: var(--font-ui);
  font-size: 17px; line-height: 1.4; font-weight: 600; letter-spacing: -.01em;
  color: var(--ink);
  margin: 0 0 6px;
}
.led-d { font-size: 15px; line-height: 1.6; color: var(--ink-2); margin: 0; }
.led-d + .led-d { margin-top: var(--space-8); }
.ledger-foot { margin: var(--space-40) 0 0; }

/* Stacked, the two columns need their own separation — the second
   column's first hairline would otherwise read as the first column's
   last row. */
@media (max-width: 899px) {
  .ledger { grid-template-columns: 1fr; gap: var(--space-48); }
}

/* ---- what isn't built yet ----
   Three gaps, each carrying the file it can be checked against. The
   source line is `.m-anchor` (mocks.css) rather than a new mono style:
   it is the same device as an anchor inside a mock — a thing on screen
   that a reader could go and look up — and it must not drift from one. */
.gaps { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-40); }
.gap > .m-anchor { display: block; color: var(--ink-3); margin: 0 0 var(--space-12); }
/* The title is an <h3> and has to be. Semantically it heads two paragraphs,
   so it belongs in the outline — and structurally, the paragraph rule below
   is `.gap p:not(.m-anchor)` at (0,2,1), which BEATS a `.g-t` class at
   (0,1,0). As a <p class="g-t"> the title silently rendered at 15px body
   size while this block said 19. Same failure as the `.ring--lg` bug in
   step 3: a modifier that loses on specificity doesn't break, it just
   quietly stops applying. */
.g-t {
  font-family: var(--font-ui);
  font-size: 19px; line-height: 1.35; font-weight: 600; letter-spacing: -.012em;
  color: var(--ink);
  margin: 0 0 var(--space-12);
}
.gap p:not(.m-anchor) { font-size: 15px; line-height: 1.6; color: var(--ink-2); margin: 0; }
.gap p + p:not(.m-anchor) { margin-top: var(--space-12); }
/* The counterweight under the three. Full measure, above the fold of the
   band's own rule, because a page that lists three gaps and stops there
   has told half the truth. */
.counter { margin: clamp(40px, 5vw, 64px) 0 0; padding-top: var(--space-32); border-top: 1px solid var(--line); }
.counter p { max-width: 68ch; font-size: 17px; line-height: 1.65; color: var(--ink-2); margin: 0; }
.counter .textlink { margin-top: var(--space-16); display: inline-block; }
@media (max-width: 899px) {
  .gaps { grid-template-columns: 1fr; gap: var(--space-32); }
}

/* ---- the founder line ----
   One paragraph and a signature. No portrait, no quotation marks and no
   card: a pull-quote treatment would make a plain statement look like a
   testimonial, which is the one shape this page exists to refuse. */
.founder { max-width: 58ch; }
.founder p + p { margin-top: var(--space-20); }
/* `.signature`, NOT `.sign` — renamed in step 7, and it is a bug fix
   rather than a tidy. `.sign` is the accordion's plus-glyph, and has been
   since step 2: `.acc-q .sign` sets it 14×14 and paints two bars in the
   pseudo-elements. This block landed in step 6 as a bare `.sign` at
   (0,1,0), which does not beat `.acc-q .sign` on the sizing it also
   declares — but it declares FOUR properties that rule never mentions,
   and those applied unopposed. Every accordion glyph on the site was
   rendering with a 32px top margin, 20px of padding, a border-top and
   15px/600 type: 14×21 instead of 14×14, on `/`, `/readiness`,
   `/revula`, `/lurest` and `/download`. It never errored and it never
   looked broken; the glyph just sat low with a hairline over it.
   Renamed rather than fenced behind `.founder .sign`, because the
   collision is the name: a class called `sign` will be reached for
   again. Used on one element, `how-we-build.html`. */
.signature {
  margin: var(--space-32) 0 0;
  padding-top: var(--space-20);
  border-top: 1px solid var(--line);
  font-size: 15px; line-height: 1.6; font-weight: 600; color: var(--ink);
}
.signature span { display: block; font-weight: 400; color: var(--ink-3); }

/* ============================================================
   STEP 7 · /download
   Three shapes. The platform card is here rather than in
   mocks.css on purpose: it is the only object on this site
   that is neither a picture of the product nor marketing
   furniture. It is a real control, reading the real browser,
   and it is the one frame that must NOT carry a Sample label.
   ============================================================ */

/* The same one margin reset the step-7 block in mocks.css opens with,
   and for the same measured reason — `.plat-act` was carrying 17px of
   the browser's default `<p>` margin under it, `.note` 13px. Bottom
   only: every intentional margin below is a margin-top, so this
   collides with nothing and wins no arguments. */
.plat p, .req p, .tier p, .seam-say p { margin-bottom: 0; }

/* ---- the platform card (block 3 — "this block is the page") ----
   Sized like a hero object rather than a card in a grid, because the
   wireframe is right that it is the page: everything else on /download
   is a consequence of what this block says your device does. */
.plat {
  max-width: 680px; margin: 0 auto;
  background: var(--bg); border: 1px solid var(--line);
  border-radius: var(--r-xl); box-shadow: var(--shadow-2);
  overflow: hidden;
}
.plat-head {
  padding: var(--space-24) var(--space-28) var(--space-20);
  border-bottom: 1px solid var(--line);
  background: var(--bg-soft);
}
.plat-head .m-label { margin-bottom: var(--space-8); }
.plat-name {
  font-family: var(--font-display);
  font-size: clamp(24px, 2.6vw, 30px); line-height: 1.15;
  letter-spacing: -.018em; font-weight: 550; color: var(--ink); margin: 0;
}
.plat-body { padding: var(--space-28); }
.plat-gesture { font-size: 17px; line-height: 1.6; color: var(--ink-2); margin: 0; max-width: 52ch; }
.plat-gesture b { font-weight: 600; color: var(--ink); }
.plat-act {
  display: flex; align-items: center; gap: var(--space-12) var(--space-16);
  flex-wrap: wrap; margin-top: var(--space-24);
}
.plat-why { margin-top: var(--space-16); max-width: 56ch; }

/* The disclosure. Closed by default ONLY once JS has named a platform —
   the page ships with the table open, so a reader with no JS gets the
   four rows rather than a dead summary line. Same rule readiness.js
   follows: render the correct state, then let JS narrow it. */
.plat-more { border-top: 1px solid var(--line); }
.plat-toggle {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-16);
  width: 100%; min-height: 52px; padding: var(--space-12) var(--space-28);
  background: none; border: 0; cursor: pointer; text-align: left;
  font-family: var(--font-ui); font-size: 15px; font-weight: 500; color: var(--ink-2);
}
.plat-toggle:hover { color: var(--ink); background: var(--bg-soft); }
.plat-toggle .sign { position: relative; width: 12px; height: 12px; flex: none; }
.plat-toggle .sign::before, .plat-toggle .sign::after {
  content: ""; position: absolute; background: var(--ink-3);
  transition: transform var(--t-base) var(--ease-out);
}
.plat-toggle .sign::before { top: 5px; left: 0; width: 12px; height: 2px; }
.plat-toggle .sign::after { left: 5px; top: 0; width: 2px; height: 12px; }
.plat-toggle[aria-expanded="true"] .sign::after { transform: rotate(90deg); }
/* No grid-template-rows animation here: the panel holds a table whose
   rows must not be able to render at zero height on a page whose whole
   job is telling you which row is yours. It is shown or it is not. */
.plat-table { padding: 0 var(--space-28) var(--space-24); }
.plat-table[hidden] { display: none; }
.prow { padding: var(--space-16) 0; border-top: 1px solid var(--line); }
.prow:first-child { border-top: 0; padding-top: var(--space-8); }
.prow .p-where { font-size: 15px; font-weight: 600; color: var(--ink); margin: 0; }
.prow .p-how { font-size: 15px; line-height: 1.6; color: var(--ink-2); margin: 4px 0 0; }
.prow .p-how b { font-weight: 600; color: var(--ink); }
/* The reader's own row, marked once JS has decided which it is. */
.prow.is-you { background: var(--accent-50); border-radius: var(--r-md); padding-left: var(--space-16); padding-right: var(--space-16); border-top-color: transparent; }
.prow.is-you .p-where::after {
  content: "Your device"; margin-left: var(--space-12);
  font-size: 12px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--accent-700);
}

/* ---- requirements (block 6) ----
   A spec sheet, not a feature grid: three headed paragraphs, hairline
   separated, in the order a reader asks them. */
.reqs { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--space-40); }
.req { border-top: 2px solid var(--ink); padding-top: var(--space-20); }
.req h3 { font-size: 20px; line-height: 1.3; letter-spacing: -.012em; font-weight: 600; margin: 0 0 var(--space-12); }
.req p { font-size: 15px; line-height: 1.65; color: var(--ink-2); margin: 0; }
.req p + p { margin-top: var(--space-12); }
/* The caveat device itself is defined once, up in the alternating-row
   block — `.row-text .row-note, .req .row-note, .note`. Only the
   spacing differs at column width. */
.req .row-note { margin-top: var(--space-16); }

/* ---- the prose beside the seam (block 5) ----
   The right half of an `.objpair` holding paragraphs instead of a
   second frame. /revula and /lurest both put two mocks in that pair;
   this page has one screen worth showing and a thing to say about it,
   and a second invented frame to balance the first is exactly the
   "no invented chrome" line in mocks.css. */
.seam-say h3 { margin: 0; }
.seam-say > * + * { margin-top: var(--space-16); }
.seam-say .note { margin-top: var(--space-24); }
.seam-more { margin: var(--space-24) 0 0; }

/* ---- free vs paid (block 7) ----
   Two lines, and the wireframe means two lines: the table lives at
   /pricing and putting half of it here is how two pages start
   disagreeing about a number. */
.tiers { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: clamp(32px, 4vw, 56px); max-width: 900px; }
.tier .t-k {
  font-family: var(--font-display); font-size: 26px; line-height: 1.2;
  letter-spacing: -.015em; font-weight: 550; color: var(--ink); margin: 0 0 var(--space-12);
}
.tier p { font-size: 17px; line-height: 1.65; color: var(--ink-2); margin: 0; }
.tier .textlink { margin-top: var(--space-16); display: inline-block; }

@media (max-width: 899px) {
  .reqs { grid-template-columns: 1fr; gap: var(--space-32); }
  .tiers { grid-template-columns: 1fr; gap: var(--space-32); }
}
@media (max-width: 599px) {
  .plat-body, .plat-toggle, .plat-table { padding-left: var(--space-20); padding-right: var(--space-20); }
  .plat-head { padding-left: var(--space-20); padding-right: var(--space-20); }
  /* Two actions at ~280px is 130px a button, which folds "Install Revula"
     onto two lines inside a pill. They stack, and both go full width so
     neither reads as the secondary one — they are two apps, not a
     primary and an alternative. */
  .plat-act { flex-direction: column; align-items: stretch; }
  .plat-act .btn { width: 100%; }
}

/* ============================================================
   STEP 8 · /pricing
   Three shapes: the period toggle, the plan column, and the
   comparison table. Everything else on the page is reused —
   `.tiers` for the free band, `.reqs` for fair use, `.refusals`
   for the tint band, `.objpair` + `.seam-say` for the trial,
   `.note` for all four caveats.

   That reuse is the reason this block is short, and it is
   deliberate: /pricing is the seventh page, and by now a new
   page wanting a two-column band or a three-column spec sheet
   should be finding one rather than drawing one.
   ============================================================ */

/* The step-4/7 margin reset, third time. `.plan-act` and `.pp` are
   `<p>`s carrying the browser's 1em default under them, which the
   column's own `+` margins then stack on. Bottom only, above every
   rule that sets a margin-top — same reasoning as the two blocks
   above, and the reason it is written a third time rather than added
   to one of theirs is that a shared reset listing every block's
   selectors is a rule nobody can delete safely. */
.plan p, .feat caption { margin-bottom: 0; }

/* ---- the period toggle (block 2, in the hero) ----
   Two buttons in a pill, `aria-pressed` rather than a checkbox: the
   control does not submit anything and has no third state.

   It ships `hidden`. pricing.js unhides it once it has run, because
   until then the page is showing both prices and there is nothing to
   toggle between — see the header of that file. */
.per-wrap { margin-top: var(--space-32); }
.per-toggle {
  display: inline-flex; align-items: center; gap: 2px;
  padding: 3px;
  background: var(--surface-3); border-radius: var(--r-pill);
}
.per-toggle[hidden] { display: none; }
.per-btn {
  /* 44, not 40. The shell puts a 44px tap floor on every other control
     it owns — `.btn-sm`, `.nav-links a`, `.textlink`, `.sheet-btn` —
     and this one shipped at 40 until it was measured. A segmented pill
     looks fine at 40 and is the first control on the page. */
  min-height: 44px; padding: 0 var(--space-20);
  border: 0; border-radius: var(--r-pill);
  background: transparent; color: var(--ink-2); cursor: pointer;
  font-family: var(--font-ui); font-size: 15px; font-weight: 600; line-height: 1;
  transition: background var(--t-fast) var(--ease-out), color var(--t-fast) var(--ease-out);
}
.per-btn:hover { color: var(--ink); }
/* The selected state is carried by the raised white ground AND by
   aria-pressed, never by colour alone — the same rule `.chip` follows
   for its direction. A reader who can't tell #FFFFFF from #E7EAF2 has
   the pressed state read out to them instead. */
.per-btn[aria-pressed="true"] {
  background: var(--bg); color: var(--ink); box-shadow: var(--shadow-1);
}
.per-note { margin-top: var(--space-12); max-width: 54ch; }

/* ---- the three columns (block 3) ----
   Equal columns, and the featured one is featured by ground and border
   rather than by scale: growing the middle column is the move that
   makes the other two look like the mistake, and this page's own table
   says Complete is the union of them and nothing more. */
.plans {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(20px, 2.4vw, 28px);
  align-items: stretch;
}
.plan {
  display: flex; flex-direction: column;
  padding: var(--space-28);
  background: var(--bg);
  border: 1px solid var(--line); border-radius: var(--r-xl);
  box-shadow: var(--shadow-1);
}
.plan--feat {
  background: var(--accent-50);
  border-color: var(--accent-500);
  box-shadow: var(--shadow-2);
}
.plan-flag {
  font-family: var(--font-ui);
  font-size: var(--fs-meta); line-height: var(--lh-meta);
  font-weight: 600; letter-spacing: .08em; text-transform: uppercase;
  color: var(--accent-700);
  margin: 0 0 var(--space-8);
}
/* An <h2>, because it heads the column in the document outline — the
   page's section headings are h2 as well, and a plan name is a peer of
   them, not a caption. It is set down to 23px rather than the shell's
   clamp(32px, 4vw, 52px): a 52px "Erulia Complete" would outweigh the
   price under it, which is the number the column exists to show. */
.plan-name {
  font-family: var(--font-display);
  font-size: 23px; line-height: 1.25; letter-spacing: -.014em; font-weight: 600;
  color: var(--ink); margin: 0;
}
.plan-what {
  font-size: var(--fs-body); line-height: var(--lh-body); color: var(--ink-2);
  margin: var(--space-12) 0 0;
}

.plan-price { margin-top: var(--space-24); padding-top: var(--space-20); border-top: 1px solid var(--line); }
.plan--feat .plan-price { border-top-color: var(--accent-100); }
/* The two periods are interleaved in the markup — price, its sub-line,
   price, its sub-line — and not grouped as two prices then two
   sub-lines. With JS that ordering is invisible, because only one pair
   is displayed. Without it the grouped version renders $9.99 and
   $44.99 as a stack with both captions underneath, and the reader has
   to work out which caption belongs to which number. Measured that way
   first; it is the sort of thing only the no-JS state exposes. */
.pp { display: flex; align-items: baseline; gap: 7px; margin: 0; }
.pp .amt {
  font-family: var(--font-display);
  font-size: clamp(34px, 3.4vw, 42px); line-height: 1;
  letter-spacing: -.024em; font-weight: 550; color: var(--ink);
}
.pp .per { font-size: 15px; font-weight: 500; color: var(--ink-3); }
.pp-sub { font-size: var(--fs-meta); line-height: var(--lh-meta); color: var(--ink-3); margin-top: var(--space-8); }
.pp-sub b { font-weight: 600; color: var(--ink); }
/* The no-JS state is BOTH periods, so the second price needs a rule
   above it to read as a second price rather than as a continuation of
   the first. Once pricing.js sets data-show nothing matches this — the
   selector needs two visible siblings — but the state without JS is
   the state that ships if the script 404s. */
.pp-sub + .pp { margin-top: var(--space-16); padding-top: var(--space-16); border-top: 1px dashed var(--line); }
.plan--feat .pp-sub + .pp { border-top-color: var(--accent-100); }

/* The swap. Attribute selector on the CONTAINER, so one attribute
   moves all six prices and there is no per-column state to fall out of
   sync. `display: none` and not a height transition: a price that
   animates is a price a reader watches instead of reads, and the
   motion budget is four items and full (§4). */
.plans[data-show="monthly"] [data-per="annual"],
.plans[data-show="annual"] [data-per="monthly"] { display: none; }

.plan-act { margin-top: var(--space-24); }
.plan-act .btn { width: 100%; }

.plan-list { list-style: none; margin: var(--space-24) 0 0; padding: var(--space-20) 0 0; border-top: 1px solid var(--line); }
.plan--feat .plan-list { border-top-color: var(--accent-100); }
.plan-list li {
  position: relative; padding-left: var(--space-20);
  font-size: var(--fs-body); line-height: var(--lh-body); color: var(--ink-2);
}
.plan-list li + li { margin-top: var(--space-12); }
/* A rule, not a tick and not a bullet glyph — the banlist rules out
   emoji bullets, and a green check on every row of a price column is
   the shape that makes four true statements look like an ad. */
.plan-list li::before {
  content: ""; position: absolute; left: 0; top: 11px;
  width: 8px; height: 1px; background: var(--line-strong);
}
.plan--feat .plan-list li::before { background: var(--accent-500); }
.plan-list b { font-weight: 600; color: var(--ink); }

/* The caveat under the columns is the page's most load-bearing
   sentence, so it gets the full measure rather than a column's. */
.plans-note, .frees-note, .ftable-note { max-width: 76ch; }
.plans-note { margin-top: var(--space-40); }
.frees-note, .ftable-note { margin-top: var(--space-40); }

/* ---- the comparison table (block 6) ----
   A real <table>, and unlike /how-we-build's ledger it has to be: every
   row here corresponds across all three columns, which is precisely the
   assertion the ledger could not make and a table always does.

   It scrolls inside its own box rather than shrinking: four columns of
   numbers at 360px is how a comparison table turns into a word search. */
.ftable { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.feat {
  width: 100%; min-width: 560px;
  border-collapse: collapse;
  text-align: left;
}
.feat th, .feat td { padding: var(--space-16) var(--space-16); border-top: 1px solid var(--line); }
.feat thead th { border-top: 0; border-bottom: 2px solid var(--ink); padding-bottom: var(--space-12); }
.feat thead th {
  font-family: var(--font-display); font-size: 17px; font-weight: 600;
  letter-spacing: -.012em; color: var(--ink);
}
.feat tbody th {
  font-family: var(--font-ui); font-size: var(--fs-body); line-height: var(--lh-body);
  font-weight: 500; color: var(--ink-2); padding-left: 0;
}
.feat td {
  font-size: 17px; font-weight: 600; color: var(--ink);
  white-space: nowrap; width: 1%;
}
/* The em-dash cells. `--ink-3` and not `--line-strong`: it is content
   (this plan does not have this), so it stays above the 4.5:1 floor. */
.feat td.none { color: var(--ink-3); font-weight: 400; }
.feat td.dep { font-size: var(--fs-body); font-weight: 400; color: var(--ink-3); white-space: normal; }
/* The featured column, carried down the table so the header is not the
   only thing tying nine cells to a plan. */
.feat .is-feat { background: var(--accent-50); }
.feat thead .is-feat { color: var(--accent-700); }
.feat tbody tr:last-child .is-feat { border-bottom-left-radius: var(--r-sm); border-bottom-right-radius: var(--r-sm); }
.feat thead .is-feat { border-top-left-radius: var(--r-sm); border-top-right-radius: var(--r-sm); }
/* The price row is a footer in everything but markup — it is not a
   capability, it is what the column above it costs. */
.feat tr.frow th, .feat tr.frow td { border-top: 2px solid var(--ink); padding-top: var(--space-20); }
.feat tr.frow th { font-weight: 600; color: var(--ink); }

@media (max-width: 1023px) {
  /* Three price columns below ~1000px is 300px each, which folds
     "Erulia Complete" and drops the amount onto its own line. Two up,
     one across: the featured column takes the full width underneath,
     where it reads as the conclusion of the two above it rather than
     as a squeezed third. */
  .plans { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .plan--feat { grid-column: 1 / -1; }
}
@media (max-width: 639px) {
  .plans { grid-template-columns: 1fr; }
  .plan { padding: var(--space-24); }
  .per-toggle { display: flex; width: 100%; }
  .per-btn { flex: 1; }
}

/* ============================================================
   STEP 9 · /signin, /signup, /reset
   Auth pages. One card, one form, one task. They keep the whole
   shell — nav, CTA and footer — because an auth page that drops
   the nav reads as a different site, and signing in is the one
   moment a reader is most entitled to check that it isn't.

   The card is the .plat (§7) / .plan (§8) frame: 440px, --r-xl,
   1px --line, --shadow-2. The difference is density — a platform
   card holds a heading, a gesture and a table; an auth card holds
   a heading and a form. It sits on a --bg-soft band rather than
   on --bg, because a white card on a white page is framed by a
   hairline alone and stops reading as a card at all.

   /signin and /signup are the same page with different fields.
   Every rule here is shared by both, and neither carries page-local
   CSS — two definitions of .auth-card is how two pages drift.
   ============================================================ */

/* ---- the section ----
   Marketing rhythm (--space-section, up to 128px) is sized for a
   full page of blocks. One 440px card needs less, not more, so
   this stops at 96px — and the viewport-tall box centres the card
   on a page that has nothing else in it. */
.auth-section {
  padding: clamp(64px, 8vw, 96px) 0;
  min-height: calc(100vh - var(--nav-h));
  display: flex;
  align-items: center;
}
/* A flex item shrinks to its content unless told otherwise, and
   .container is a flex item here. Without this the card sizes
   itself from its longest line — 332px, not 440px — and the email
   field clips an ordinary address. */
.auth-section > .container { width: 100%; }

/* ---- the card ----
   Same frame geometry as .plat (§7) and .plan (§8):
   --r-xl radius, 1px --line, --shadow-2. The card is
   not a .card because .card carries its own 28px padding
   and the auth form needs more control over vertical rhythm. */
.auth-card {
  max-width: 440px;
  margin: 0 auto;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--r-xl);
  box-shadow: var(--shadow-2);
  padding: var(--space-40);
}

/* ---- header ---- */
.auth-header { margin-bottom: var(--space-32); }
.auth-title {
  font-family: var(--font-display);
  font-size: clamp(28px, 3vw, 32px);
  line-height: 1.15;
  letter-spacing: -.018em;
  font-weight: 550;
  color: var(--ink);
  margin: 0;
}
.auth-sub {
  font-size: 17px;
  line-height: 1.55;
  color: var(--ink-2);
  margin: var(--space-12) 0 0;
}

/* ---- form ---- */
.auth-form { display: flex; flex-direction: column; gap: var(--space-20); }
/* An author `display` beats the UA sheet's `[hidden] { display: none }`, so
   the line above would otherwise make `form.hidden = true` a no-op and leave
   a live, re-enabled form stacked above the success state. Same reason
   `.plat-table` and `.per-toggle` carry this rule (§7, §8). */
.auth-form[hidden] { display: none; }
.auth-field { display: flex; flex-direction: column; gap: var(--space-8); }
.auth-label {
  font-family: var(--font-ui);
  font-size: 15px;
  font-weight: 500;
  color: var(--ink);
}
.auth-submit { width: 100%; margin-top: var(--space-8); }
.auth-note { min-height: 1.6em; margin: 0; }
.auth-note .err { color: var(--danger); }

/* ---- footer links ---- */
.auth-footer {
  margin-top: var(--space-28);
  padding-top: var(--space-24);
  border-top: 1px solid var(--line);
  text-align: center;
}
.auth-alt {
  font-size: 15px;
  line-height: 1.6;
  color: var(--ink-2);
  margin: 0;
}
.auth-alt + .auth-alt { margin-top: var(--space-8); }

/* ---- /account info block ----
   `.account-info` is a `dl` and `.account-value` is a `dd`, so both
   need their UA margins taken off — a `dd` ships with 40px of inline
   start margin that no other element here has. */
.account-info { display: flex; flex-direction: column; gap: var(--space-16); margin: 0 0 var(--space-24); }
.account-field { display: flex; flex-direction: column; gap: 2px; padding: var(--space-12) var(--space-16); background: var(--bg-soft); border-radius: var(--r-md); }
.account-value { margin: 0; font-size: 17px; line-height: 1.55; color: var(--ink); }
.account-muted { color: var(--ink-3); }

/* ---- /billing body ---- */
.billing-body { display: flex; flex-direction: column; gap: var(--space-24); margin-bottom: var(--space-24); }
.bill-section-title {
  font-family: var(--font-display);
  font-size: 20px; line-height: 1.25; letter-spacing: -.012em;
  font-weight: 550; color: var(--ink);
  margin: 0 0 var(--space-12);
}
.bill-card {
  background: var(--bg-soft); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: var(--space-16);
}
.bill-card--muted { background: transparent; }
.bill-card-top { display: flex; align-items: center; gap: var(--space-12); margin-bottom: var(--space-8); }
.bill-plan-name { font-size: 19px; font-weight: 600; color: var(--ink); }
.bill-plan-badge {
  font-size: 12px; font-weight: 600; letter-spacing: .06em; text-transform: uppercase;
  color: var(--accent-700); background: var(--accent-50);
  border-radius: var(--r-pill); padding: 3px 9px;
}
.bill-plan-price { font-size: 28px; font-weight: 550; color: var(--ink); margin: 0; }
.bill-plan-period { font-size: 15px; color: var(--ink-3); margin: 2px 0 0; }
.bill-card-actions { margin-top: var(--space-12); padding-top: var(--space-12); border-top: 1px solid var(--line); }
.bill-empty { font-size: 15px; line-height: 1.55; color: var(--ink-2); margin: 0; }
.bill-empty-sub { font-size: 13px; color: var(--ink-3); margin: var(--space-8) 0 0; }
.bill-usage-row { display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-12); }
.bill-usage-row + .bill-usage-row { margin-top: var(--space-16); }
.bill-usage-label { font-size: 15px; color: var(--ink-2); }
.bill-usage-value { font-size: 17px; font-weight: 600; color: var(--ink); font-variant-numeric: tabular-nums; }
.bill-meter { height: 6px; border-radius: var(--r-pill); background: var(--surface-3); overflow: hidden; margin: var(--space-8) 0 4px; }
.bill-meter-fill { display: block; height: 100%; border-radius: var(--r-pill); background: var(--accent-500); }
.bill-meter-label { font-size: 13px; color: var(--ink-3); margin: 0 0 var(--space-12); }
.bill-note {
  margin-top: var(--space-12); padding-top: var(--space-12); border-top: 1px solid var(--line);
  font-size: 13px; line-height: 1.6; color: var(--ink-3);
}

/* ---- /apps body ----
   Same frame as .bill-card above — --bg-soft on a 1px --line at
   --r-md — written again rather than shared, which is the call
   .auth-card, .plat and .plan all already made and wrote down.
   The prefix says which page owns the rule; putting a `.bill-`
   class on /apps would be a name lying about its scope, and this
   file has one rule about names it keeps returning to.

   The letter mark is .mark from mocks.css, restated at 28px.
   mocks.css is the marketing pages' 68KB mock sheet and /apps
   shows no mocks — loading it for two 28px squares would be the
   most expensive style import on the site. */
.apps-body { display: flex; flex-direction: column; gap: var(--space-24); margin-bottom: var(--space-24); }
.app-section-title {
  font-family: var(--font-display);
  font-size: 20px; line-height: 1.25; letter-spacing: -.012em;
  font-weight: 550; color: var(--ink);
  margin: 0 0 var(--space-12);
}
.app-card {
  background: var(--bg-soft); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: var(--space-16);
}
.app-card--plain { background: transparent; }
.app-card + .app-card { margin-top: var(--space-16); }
.app-card-top { display: flex; align-items: center; gap: var(--space-12); margin-bottom: var(--space-12); }
.app-mark {
  width: 28px; height: 28px; border-radius: var(--r-sm); flex: none;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display); font-weight: 600; font-size: 13px;
  background: var(--accent-600); color: #FFFFFF;
}
/* `min-width: 0` so a long host name wraps inside the flex row
   instead of pushing the row wider than the 440px card. */
.app-id { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.app-name { font-size: 17px; font-weight: 600; color: var(--ink); }
.app-host { font-size: 13px; color: var(--ink-3); overflow-wrap: anywhere; }
.app-say { font-size: 15px; line-height: 1.55; color: var(--ink-2); margin: 0 0 var(--space-12); }
.app-access {
  display: flex; justify-content: space-between; align-items: baseline; gap: var(--space-12);
  padding-top: var(--space-12); border-top: 1px solid var(--line);
}
.app-access-k { font-size: 15px; color: var(--ink-2); }
.app-access-v { font-size: 15px; font-weight: 600; color: var(--ink); text-align: right; }
/* Wraps rather than shrinking the button below the 44px target:
   at 375 the card is ~319px wide and these two do not always fit. */
.app-actions { display: flex; align-items: center; flex-wrap: wrap; gap: var(--space-16); margin: var(--space-16) 0 0; }
.app-foot { margin: var(--space-12) 0 0; font-size: 13px; color: var(--ink-3); }
.app-p { font-size: 15px; line-height: 1.55; color: var(--ink-2); margin: 0; }
.app-p + .app-p { margin-top: var(--space-12); }
.app-note {
  margin: var(--space-12) 0 0; padding-top: var(--space-12); border-top: 1px solid var(--line);
  font-size: 13px; line-height: 1.6; color: var(--ink-3);
}

/* ---- responsive ---- */
@media (max-width: 559px) {
  .auth-card { padding: var(--space-28); }
}

/* ============================================================
   LEGAL PAGES  (10-ERULIA-SITE.md §7.4)
   /privacy · /terms · /cookies · /refunds · /subprocessors

   Written once, here, because §7.4 specifies a *shared* shape and
   five copies of a shape is how five pages stop agreeing. The
   numbers are the spec's, not new ones: one column at 65ch,
   17px/400 in --ink (legal body is the one place the site does not
   drop to --ink-2 — grey is what unread text looks like), H2 at
   28px/550, a `Last updated` line under the H1, and a TOC that is
   a list of anchor links.

   No accordion on these pages, by rule. Legal text that hides
   inside a disclosure is legal text nobody read.
   ============================================================ */
.legal { max-width: 65ch; }
.legal > * + * { margin-top: var(--space-20); }

.legal-updated {
  font-size: 13px; color: var(--ink-3); margin: var(--space-16) 0 0;
}

/* The TOC. A list of in-page links, not a component. */
.legal-toc {
  margin: var(--space-40) 0 0; padding: var(--space-24);
  background: var(--bg-soft); border: 1px solid var(--line); border-radius: var(--r-md);
}
.legal-toc h2 { font-size: 15px; font-weight: 600; letter-spacing: 0; margin: 0 0 var(--space-12); }
.legal-toc ol { margin: 0; padding: 0 0 0 1.3em; }
.legal-toc li + li { margin-top: 4px; }
.legal-toc a { display: inline-block; color: var(--ink-2); text-decoration: none; font-size: 16px; }
.legal-toc a:hover { color: var(--ink); text-decoration: underline; text-underline-offset: 3px; }
/* Below 560px each entry becomes a full-width 44px row. Seven anchor
   links at their natural 19px are seven targets under the floor — the
   same fault §6.1 records against the nav link that shipped bare. */
@media (max-width: 559px) {
  .legal-toc li + li { margin-top: 0; }
  .legal-toc a { display: block; padding: 9px 0; }
}

/* `scroll-margin-top` and not a JS offset: the nav is 56px and
   fixed, so an anchor without it lands the heading underneath it. */
.legal-sec { margin-top: var(--space-48); scroll-margin-top: calc(var(--nav-h) + 24px); }
.legal-sec > h2 {
  font-size: 28px; font-weight: 550; letter-spacing: -.02em; line-height: 1.25;
  margin: 0 0 var(--space-16); max-width: none;
}
.legal-sec > * + * { margin-top: var(--space-16); }
/* `:not([class])` and not a bare `.legal p`: the latter is specificity
   0,1,1 and silently outranks .eyebrow (0,1,0), which rendered the
   page's eyebrow as 17px uppercase accent instead of 13px. Every
   classed paragraph on these pages already owns its own rule. */
.legal p:not([class]), .legal li { font-size: 17px; line-height: 1.65; color: var(--ink); }
.legal ul { margin: 0; padding-left: 1.2em; }
.legal li + li { margin-top: var(--space-8); }
/* 550 and not 600: site.css:85 caps every heading on this site at 550
   (§2.9) and a legal sub-head is not the exception. */
.legal h3 { font-size: 19px; font-weight: 550; margin: var(--space-24) 0 var(--space-8); }
.legal code {
  font-family: var(--font-mono); font-size: .88em;
  background: var(--bg-soft); border: 1px solid var(--line);
  border-radius: 4px; padding: 1px 5px; overflow-wrap: anywhere;
}
.legal .back-top { font-size: 13px; }

/* ---- the tables (/cookies and /subprocessors are both tables) ----
   Restacked below 560px rather than put in a horizontal scroller:
   a five-column table is unreadable at 375px whichever way it
   moves, and the site's own rule is no h-scroll at 375. */
.legal-table { width: 100%; border-collapse: collapse; margin-top: var(--space-16); }
.legal-table caption {
  text-align: left; font-size: 13px; color: var(--ink-3);
  padding-bottom: var(--space-12);
}
.legal-table th, .legal-table td {
  text-align: left; vertical-align: top; padding: var(--space-12) var(--space-16) var(--space-12) 0;
  border-bottom: 1px solid var(--line); font-size: 15px; line-height: 1.55;
}
.legal-table thead th {
  font-family: var(--font-ui); font-size: 13px; font-weight: 600;
  color: var(--ink-3); border-bottom: 1px solid var(--line-strong);
  padding-top: 0;
}
.legal-table td { color: var(--ink-2); }
.legal-table th[scope="row"] { color: var(--ink); font-weight: 600; overflow-wrap: anywhere; }
.legal-table td:last-child, .legal-table th:last-child { padding-right: 0; }

@media (max-width: 559px) {
  .legal-table, .legal-table tbody, .legal-table tr, .legal-table td, .legal-table th { display: block; }
  .legal-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
  .legal-table tr {
    border: 1px solid var(--line); border-radius: var(--r-md);
    padding: var(--space-16); margin-top: var(--space-16);
  }
  .legal-table th, .legal-table td { border: 0; padding: 0; }
  .legal-table td { margin-top: var(--space-12); }
  .legal-table td::before {
    content: attr(data-label); display: block;
    font-size: 12px; font-weight: 600; color: var(--ink-3); margin-bottom: 2px;
  }
}
