/* Form693 — marketing site
   One stylesheet, four pages. Palette is derived from the artifact: --rule is the
   paper edge, --accent is pen blue, and --flag is used ONLY where the product
   itself flags a clinical result.

   FIVE FONTS ARE DOWNLOADED, ALL FROM THIS ORIGIN (see the @font-face block
   below). _headers ships `font-src 'self'` and `style-src 'self'` with no Google
   host in either — put a fonts.googleapis.com <link> back in a page and the
   browser refuses it silently, which looks like a caching bug and is not. The
   display serif is still a system stack; no file exists to download.

   NOTHING IN THIS SITE MAY USE A style="" ATTRIBUTE. _headers ships
   `style-src 'self'` with no 'unsafe-inline', so an inline style is refused by
   the browser and the element renders unstyled. scripts/site_preflight.py does
   not catch this — it checks on…= handlers and javascript: URLs, not styles.
   That is also why motion.js sets CLASSES and never el.style, and why the
   reveal stagger below is nth-child delays rather than a per-element variable.

   LAYOUT RULE: sections are CENTRED and symmetric — a heading block on the
   centre line, then an equal-column card grid, never a two-column split with
   text on one side. Keep new sections on .section-head + .cards rather than
   inventing another grid. */

/* ------------------------------------------------------------- typefaces */

/* SELF-HOSTED, NEVER FROM GOOGLE FONTS. The reasoning and the measurements are
   in web/SITE-DESIGN.md under "Three type roles". The rules that bind an edit
   here:

   ⛔ THE unicode-range LINES ARE THE MANIFEST, AND THEY ARE GENERATED. Each one
   is the real cmap of the file beside it, written by scripts/build_site_fonts.py
   and checked two ways: `build_site_fonts.py --check` proves the declaration
   still matches the bytes, and site_preflight.py's font rules prove the page's
   copy stays inside the declarations. DO NOT HAND-EDIT A RANGE TO SILENCE A
   CHECK — widening one promises a glyph the file does not hold, and the browser
   answers that by drawing nothing rather than by falling through. The hand faces
   are subset to the exact characters the specimen cards and the seal set, so
   changing that copy means rebuilding, never editing a range.

   ⛔ NOTHING YOU COULD DO'S RANGE STOPS AT U+00ED, ON PURPOSE. It is a
   Latin-only face, so the Russian card's Cyrillic is not in it and falls through
   to Caveat, which is next in --font-hand. Declaring the real cmap is what makes
   that fallthrough happen by rule, identically everywhere, rather than depending
   on someone else's build and on the platform. */

@font-face {
  font-family: "Manrope";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("fonts/manrope-latin-v1.woff2") format("woff2");
  unicode-range: U+000D,U+0020-007E,U+00A0-00FF,U+0131,U+0152-0153,U+02C6,U+02DA,U+02DC,U+2011,U+2013-2014,U+2018-201A,U+201C-2022,U+2026,U+2030,U+2039-203A,U+2044,U+2060,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215;
}

/* The seven characters past latin are `ş` and the six letters of `прививка`,
   which .reading__origin sets in mono. On Google Fonts they pulled this family's
   whole latin-ext AND cyrillic subsets — 20,488 B, 16% of every font byte on the
   page, for a dozen glyphs, with no way to ask for less. */
@font-face {
  font-family: "JetBrains Mono";
  font-style: normal;
  font-weight: 500 600;
  font-display: swap;
  src: url("fonts/jetbrainsmono-latin-v1.woff2") format("woff2");
  unicode-range: U+000D,U+0020-007E,U+00A0-00FF,U+0131,U+0152-0153,U+015F,U+02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0430,U+0432,U+0438,U+043A,U+043F-0440,U+2010,U+2013-2014,U+2018-201A,U+201C-201E,U+2020-2022,U+2024,U+2026,U+2030,U+2032-2033,U+2039-203A,U+2044,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF;
}

@font-face {
  font-family: "Caveat";
  font-style: normal;
  font-weight: 500 600;
  font-display: swap;
  src: url("fonts/caveat-subset-v4.woff2") format("woff2");
  unicode-range: U+0020,U+0028-0029,U+002B,U+002D-0039,U+0042-0043,U+0047-0049,U+004B,U+004D,U+0051-0054,U+0056,U+0061-0062,U+0064-0065,U+0067-0069,U+006B-0070,U+0072-007A,U+00E7,U+00ED,U+0131,U+0411-0412,U+0430,U+0438,U+043A-043B,U+043E-043F;
}

@font-face {
  font-family: "Nothing You Could Do";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/nothingyoucoulddo-subset-v4.woff2") format("woff2");
  unicode-range: U+0020,U+0028-0029,U+002B,U+002D-0039,U+0042-0043,U+0047-0049,U+004B,U+004D,U+0051-0054,U+0056,U+0061-0062,U+0064-0065,U+0067-0069,U+006B-0070,U+0072-007A,U+00E7,U+00ED;
}

/* The carousel card's scrawl (.rl-card-hand) and nothing else — deliberately the
   least legible face on the site: the card is the "before" picture, and writing
   it in a tidy hand undersells the thing the product reads. Latin-only, so it
   never competes with the other hand faces for the Cyrillic card. */
@font-face {
  font-family: "Meddon";
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url("fonts/meddon-subset-v1.woff2") format("woff2");
  unicode-range: U+0020,U+0028-0029,U+002B,U+002D-0039,U+0042-0043,U+0047-0049,U+004B,U+004D,U+0051-0054,U+0056,U+0061-0062,U+0064-0065,U+0067-0069,U+006B-0070,U+0072-007A,U+00E7,U+00ED,U+0131;
}

/* ---------------------------------------------------------------- tokens */

/* ⛔ THIS SITE IS LIGHT ONLY. No dark theme: no second :root token block under
   `@media (prefers-color-scheme: dark)`, and no .hl--soft override.

   `color-scheme: light` IS THE OTHER HALF OF THAT, and dropping the line
   entirely is NOT the same thing. `light dark` declares to the browser that this
   page handles both — with it, a visitor on a dark OS still gets dark-painted
   scrollbars, dark form controls and a dark canvas behind the page, over a
   stylesheet that only draws light. One word, and the UA stops offering.

   ⛔ DO NOT ADD A `@media (prefers-color-scheme: dark)` BLOCK, here or anywhere
   below. Every colour on this site is a token in this one block, so a single
   stray dark override would repaint half the page and leave the other half
   light — which is worse than either theme. @media print further down is a
   DIFFERENT thing and stays: it maps this same light palette to paper. */
:root {
  color-scheme: light;

  --paper: #fcfbf8;
  --paper-2: #f1f3f7;
  --paper-veil: rgba(252, 251, 248, 0.82);
  --ink: #0a0c0f;
  /* Body copy: a near-ink and a mid-ink. Anything lighter reads as grey on this
     warm paper. --ink-3 is only for furniture (dates, meta, the disclaimer),
     never for a sentence somebody has to read. */
  --ink-2: #23282f;
  --ink-3: #464c55;
  --rule: #e2e6ec;
  --rule-strong: #c3cad5;
  /* Lighter and slightly desaturated on purpose — a near-black navy reads as a
     hard edge everywhere it touches paper. Clears AA on --paper at ~5.7:1,
     which is the floor any replacement has to hold: the eyebrows and the
     .verdict labels are small text set in it. */
  --accent: #35609f;
  --accent-hi: #23477b;
  --accent-wash: #f4f6fa;
  --flag: #a32a28;
  --ok: #1f6a3f;

  /* The highlight behind <mark class="hl">. A translucent flat fill over the
     whole word (operator 2026-08-19, replacing the half-height marker swipe);
     the hero's .hl--soft keeps the drawn swipe and re-tokens the alpha. */
  --hl: rgba(240, 196, 92, 0.5);

  /* Cards sit ON the band, so they carry their own surface and a soft lift
     rather than a heavy outline. */
  --card-bg: #ffffff;
  --card-rule: #e4e8ee;
  --card-rule-hi: #c4d0e4;
  --shadow-sm: 0 1px 2px rgba(12, 20, 38, 0.05), 0 4px 14px rgba(12, 20, 38, 0.045);
  --shadow-md: 0 2px 4px rgba(12, 20, 38, 0.06), 0 18px 42px rgba(12, 20, 38, 0.11);
  /* One step past --shadow-md, for the single element on the page that is meant
     to sit above its neighbours: the recommended plan. */
  --shadow-lift: 0 3px 8px rgba(12, 20, 38, 0.13), 0 26px 56px rgba(12, 20, 38, 0.19);

  /* The two ambient washes behind the hero. Low opacity on purpose: they should
     read as light in the room, never as a coloured panel. */
  --glow-a: rgba(53, 96, 159, 0.11);
  --glow-b: rgba(120, 158, 216, 0.11);
  --dot: rgba(53, 96, 159, 0.13);

  /* A filled navy block — the featured plan and the reel's step badge. It is a
     SEPARATE token from --accent and stays one: --accent is tuned to be read AS
     TEXT on paper, so it is light enough that white on it clears AA by very
     little. This is the darker navy a block gets filled with.

     ⛔ --fill-soft IS THE SAME WHITE AS --fill-ink, HELD BACK BY ALPHA. Never a
     pale blue: secondary text inside a filled block is dimmer white, never a
     second blue on a blue fill. */
  --fill-bg: #33578c;
  --fill-ink: #f6f8fa;
  --fill-soft: rgba(246, 248, 250, 0.86);
  --fill-rule: rgba(246, 248, 250, 0.35);
  /* ⛔ There is no generic filled-block edge token: .plan--featured owns the
     only one. If another filled block ever needs an edge, it goes a shade
     DARKER than --fill-bg.

     The featured plan's edge. That card is the only filled block that sits on
     --accent-wash rather than on --paper, and one step off the fill disappears
     against it. It has to read against BOTH the fill inside it and a pale
     blue-grey outside it. */
  --plan-edge: #15315c;

  --font-display: "Iowan Old Style", "Palatino Linotype", Palatino, Georgia,
    "Times New Roman", serif;
  --font-body: "Manrope", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto,
    "Helvetica Neue", Arial, sans-serif;
  --font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, "SF Mono", Menlo,
    Consolas, "Liberation Mono", monospace;
  /* ORDER IS LOAD-BEARING. "Nothing You Could Do" is the rough hand and covers
     Latin only; Caveat sits behind it because it carries Cyrillic and
     Vietnamese, so a Russian specimen falls through to a hand rather than to
     the default sans. Han and Arabic reach the system hand, which is correct —
     there is no webfont here that has them. */
  --font-hand: "Nothing You Could Do", "Caveat", "Bradley Hand", "Segoe Script",
    cursive;

  --measure: 34rem;
  --measure-wide: 46rem;
  --shell: 73.75rem;
  --gutter: clamp(1.25rem, 0.5rem + 3vw, 2.75rem);
  --band-y: clamp(3.75rem, 2rem + 5vw, 6.5rem);
  --radius: 12px;
  --radius-lg: 20px;
  --ease: cubic-bezier(0.22, 0.68, 0.32, 1);
}

/* ------------------------------------------------------------------ base */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

@media (prefers-reduced-motion: no-preference) {
  html {
    scroll-behavior: smooth;
  }
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 1.0625rem;
  font-weight: 500;
  line-height: 1.6;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

h1,
h2,
h3 {
  font-family: var(--font-display);
  font-weight: 600;
  letter-spacing: -0.02em;
  margin: 0;
}

p,
ul,
ol,
dl,
dd,
figure {
  margin: 0;
}

a {
  color: var(--accent);
  text-decoration-thickness: 1px;
  text-underline-offset: 0.2em;
}

a:hover {
  color: var(--accent-hi);
  text-decoration-thickness: 2px;
}

::selection {
  background: var(--accent);
  color: var(--paper);
}

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}

/* The masthead is sticky, so every anchor jump has to clear it — without this
   "How it works" lands with its own heading hidden under the bar. dl[id] is not
   redundant: privacy.html and terms.html hang #contact on the <dl> itself. */
section[id],
dl[id] {
  scroll-margin-top: 5.5rem;
}

/* #how-it-works IS A BAND, and a band opens with --band-y of padding — up to
   6.5rem of it. Landing its top edge 5.5rem below the bar therefore put the
   eyebrow twelve rems down the screen and pushed the carousel's tabs off the
   bottom. Subtracting the band's own padding back out lands the EYEBROW 1.25rem
   under the masthead instead, and does it at every width, because the same
   clamp() is on both sides of the sum.

   ⛔ ONE LANDING PLACE: the hero button and the nav link both point at
   #how-it-works, never at an id inside the carousel.

   A COMPONENT MEASUREMENT, NOT A GENERAL RULE: it is correct only for a .band
   that opens with a .section-head, which is why the two ids are listed rather
   than lifted to `section[id]`. A band anyone lands on the HEADING of belongs
   here; one linked to as a whole does not. */
#how-it-works,
#pricing {
  scroll-margin-top: calc(6.75rem - var(--band-y));
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--paper);
  color: var(--ink);
  padding: 0.75rem 1rem;
  border: 1px solid var(--rule-strong);
  z-index: 30;
}

.skip:focus {
  left: 0.5rem;
  top: 0.5rem;
}

/* --------------------------------------------------------------- shell */

.shell {
  width: 100%;
  max-width: var(--shell);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

.band {
  position: relative;
  padding-block: var(--band-y);
}

.band + .band {
  border-top: 1px solid var(--rule);
}

/* The first band on a page opens tighter than a mid-page one — a full band's
   worth of air under a sticky bar reads as a gap, not as space. */
.band--open {
  padding-top: clamp(2.5rem, 1rem + 3vw, 4rem);
}

/* A washed band is its own boundary; a hairline on top of the colour change
   just prints a seam. */
.band--wash {
  background: var(--accent-wash);
  border-top: 0;
}

.band--wash + .band,
.band + .band--wash {
  border-top: 0;
}

.measure {
  max-width: var(--measure);
}

.measure-wide {
  max-width: var(--measure-wide);
}

/* ------------------------------------------------------------- ambient */

/* Background art, drawn rather than downloaded: two drifting radial washes and
   a dot field that fades out downward. It is decorative, aria-hidden, and
   pointer-transparent, so nothing here is in anyone's way. A photograph would
   drop in as a self-hosted <img> inside .ambient — `img-src 'self'` already
   allows one and no CSP edit is needed. */
.ambient {
  position: absolute;
  inset: 0;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}

.band > .shell {
  position: relative;
  z-index: 1;
}

.ambient__wash {
  position: absolute;
  display: block;
  border-radius: 50%;
  filter: blur(60px);
}

.ambient__wash--a {
  width: min(46rem, 90vw);
  height: min(30rem, 60vw);
  top: -18%;
  left: -10%;
  background: radial-gradient(circle at 50% 50%, var(--glow-a), transparent 68%);
}

.ambient__wash--b {
  width: min(40rem, 80vw);
  height: min(28rem, 55vw);
  top: 6%;
  right: -12%;
  background: radial-gradient(circle at 50% 50%, var(--glow-b), transparent 68%);
}

/* ⛔ THERE IS NO THIRD WASH. .ambient__wash--c was the warm one under the closing
   band, and the band came off the home page — its token went with it. A new
   ambient goes in the hero's .ambient block with --a and --b, not on a band of
   its own. */

.ambient__grid {
  position: absolute;
  inset: 0;
  background-image: radial-gradient(var(--dot) 1px, transparent 1px);
  background-size: 26px 26px;
  -webkit-mask-image: linear-gradient(to bottom, #000 0%, transparent 72%);
  mask-image: linear-gradient(to bottom, #000 0%, transparent 72%);
  opacity: 0.55;
}

.ambient--quiet .ambient__grid {
  opacity: 0.3;
}

/* ------------------------------------------------------------ typography */

/* It labels the section it opens, so it has to be readable at a glance rather
   than be a decoration above the heading. */
.tag {
  font-family: var(--font-mono);
  font-size: 0.875rem;
  font-weight: 600;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--accent);
}

/* Emphasis inside body copy: a shade darker than the paragraph around it, so a
   bolded clause reads as the sentence's point rather than as a different
   colour. Scoped to the text classes on purpose — a bare `strong {}` would
   also hit the .outputs headings, which set their own weight. */
.lede strong,
.card__text strong,
.section-foot strong,
.reel__line strong,
.reading__read strong {
  font-weight: 800;
  color: var(--ink);
}

/* The highlighter: a flat translucent fill over the whole word, overriding the
   UA's opaque yellow on <mark>. box-decoration-break keeps the padding and the
   rounding on every line of a clause that wraps. */
.hl {
  background: var(--hl);
  color: inherit;
  padding: 0.06em 0.14em;
  border-radius: 0.2em;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
}

/* The underline twin of .hl, for rotating the emphasis on the specimen cards
   so six cards don't stack six yellow blocks. Ink (operator 2026-08-19), and
   never the accent — a blue underline on this site means a link. On a <u>, so
   the emphasis survives with the stylesheet missing. */
.ul {
  text-decoration: underline;
  text-decoration-color: var(--ink);
  text-decoration-thickness: 0.09em;
  text-underline-offset: 0.16em;
}

/* Half-strength, for the hero's handwritten line — and it KEEPS the drawn
   marker swipe the full-strength .hl gave up (operator 2026-08-19: the seal
   stays as it was; only .hl became a flat fill). Its own background fully
   overrides .hl's.

   THE SWIPE HAS TO CLEAR THE PEN STROKE. .seal__num draws a blue rule 0.34em
   BELOW its own text, and a full-height band would run straight through it —
   two marks under one phrase, one of which is already the emphasis. The alpha
   comes down because a band this tall at full strength stops reading as a
   highlighter and starts reading as a filled box.

   0.32em + 0.8em ENDS AT 1.12em, AND THAT CEILING IS LOAD-BEARING. .seal__hand
   sets line-height 1.35, so .seal__num's inline box bottom is at 1.35em and the
   pen stroke hangs from there to 1.69em. Grow this band past ~1.3em and the two
   marks touch.

   ROUNDED ENDS, AND border-radius CANNOT DO IT: a radius clips at the BORDER
   BOX's corners, and this band is nowhere near them — the box of an inline
   element is the font's content area, and Caveat's is about 1.5em tall against
   a 0.8em swipe floating 0.32em down it. So the pill is DRAWN: two half-disc
   caps 0.4em wide, and a rectangle between them inset by exactly one cap on
   each side. The three layers ABUT rather than overlap, the only arrangement
   that works at alpha 0.22 — an overlap doubles the alpha and draws the seam
   it was meant to hide. */
.hl--soft {
  --hl: rgba(240, 196, 92, 0.22);
  background:
    radial-gradient(0.4em 0.4em at right center, var(--hl) 99%, transparent 100%)
      no-repeat left 0.32em / 0.4em 0.8em,
    radial-gradient(0.4em 0.4em at left center, var(--hl) 99%, transparent 100%)
      no-repeat right 0.32em / 0.4em 0.8em,
    linear-gradient(var(--hl), var(--hl))
      no-repeat center 0.32em / calc(100% - 0.8em) 0.8em;
  padding-inline: 0.24em;
}

/* Screen-reader-only. Nothing uses it today, and it is kept because the next
   control that needs a name should reach for this rather than invent a second
   clip-rect block. */
.u-sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

.display {
  font-size: clamp(2.375rem, 1.1rem + 3.6vw, 3.75rem);
  line-height: 1.05;
  letter-spacing: -0.03em;
  text-wrap: balance;
}

.h2 {
  font-size: clamp(1.875rem, 1.25rem + 1.8vw, 2.75rem);
  line-height: 1.08;
  text-wrap: balance;
}

.h3 {
  font-size: clamp(1.1875rem, 1.0625rem + 0.5vw, 1.4375rem);
  line-height: 1.2;
  letter-spacing: -0.018em;
}

.lede {
  font-size: clamp(1.0625rem, 1rem + 0.35vw, 1.1875rem);
  line-height: 1.55;
  color: var(--ink-2);
}

.prose p + p {
  margin-top: 1rem;
}

/* Clause headings inside a document's prose column — the terms and privacy
   pages' sections. The heading opens a clause, so it binds tighter to the
   paragraph under it than to the one above. */
.prose .h3 {
  margin-top: 2.25rem;
}

.prose .h3:first-child {
  margin-top: 0;
}

.prose .h3 + p {
  margin-top: 0.75rem;
}

/* .lede carries no margins of its own — every block that uses one sets the
   spacing around it — so two in a row butt together with no gap at all and read
   as one paragraph that has lost its break. */
.lede + .lede {
  margin-top: 1rem;
}

/* The one line under a prose column that is not part of the argument — a "where
   to write" tail. Set apart with a rule rather than a heading, because it does
   not deserve one. */
.prose__foot {
  margin-top: 1.75rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--rule);
  font-size: 0.9375rem;
  color: var(--ink-2);
}

.note {
  font-size: 0.9375rem;
  line-height: 1.6;
  color: var(--ink-2);
}

/* ⛔ NO BARE "set this in mono" UTILITY. Mono is a TYPE ROLE here, not a
   utility — every data value on the site reaches it through a named part
   (.claim__num, .seal__num, .plan__price, .verdict, .reel__step), which is what
   keeps the role legible. A new number gets a new class. */

/* A centred heading block: label, heading, one optional line of lede. Every
   section on the home page opens with one, which is what makes the page read
   as symmetric rather than as a stack of left-aligned columns. */
.section-head {
  max-width: var(--measure-wide);
  margin-inline: auto;
  text-align: center;
}

.section-head .h2 {
  margin-top: 1rem;
}

.section-head .lede {
  margin-top: 1.125rem;
  max-width: var(--measure);
  margin-inline: auto;
}

.section-head--tight {
  margin-top: clamp(3rem, 5vw, 4.5rem);
}

.section-foot {
  max-width: var(--measure-wide);
  margin: clamp(2rem, 3.5vw, 2.75rem) auto 0;
  text-align: center;
  font-size: 1.0625rem;
  line-height: 1.55;
  color: var(--ink-2);
}

.section-foot--sm {
  font-size: 0.9375rem;
  max-width: 42rem;
}

/* ⛔ .cta-note AND .cta-note--strong ARE GONE with the closing band the operator
   cut from the home page. The BAA sentence they drew — "A Business Associate
   Agreement is signed before any patient data moves" — is NOT retired: it is
   still on privacy.html as a .fact__term, and the get-started surface is where
   it now sits under a button. web/SITE-DESIGN.md "Before it goes live" item 5
   carries the rule. Do not rebuild these two to say it here again. */

/* Page headers on security / privacy / terms use the same centre line. */
.page-head {
  max-width: var(--measure-wide);
  margin-inline: auto;
  text-align: center;
}

.page-head .display {
  margin-top: 1rem;
}

.page-head .lede {
  margin-top: 1.375rem;
  text-align: left;
}

.page-head .note {
  margin-top: 1.25rem;
}

/* --------------------------------------------------------------- header */

.masthead {
  position: sticky;
  top: 0;
  z-index: 20;
  background: var(--paper-veil);
  border-bottom: 1px solid transparent;
  transition: border-color 0.25s var(--ease), box-shadow 0.25s var(--ease);
}

@supports (backdrop-filter: blur(14px)) or (-webkit-backdrop-filter: blur(14px)) {
  .masthead {
    -webkit-backdrop-filter: saturate(180%) blur(14px);
    backdrop-filter: saturate(180%) blur(14px);
  }
}

/* motion.js adds this past the first few pixels of scroll: the bar is
   borderless over the hero and gains its edge once content runs under it. */
.masthead.is-lifted {
  border-bottom-color: var(--rule);
  box-shadow: 0 1px 20px rgba(12, 20, 38, 0.06);
}

.masthead__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.625rem clamp(0.875rem, 3vw, 2.5rem);
  padding-block: 0.875rem;
}

.wordmark {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.375rem;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-decoration: none;
  margin-right: auto;
}

.wordmark span {
  color: var(--accent);
}

.wordmark:hover {
  color: var(--ink);
}

.masthead__nav {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem clamp(0.75rem, 2.2vw, 1.875rem);
  font-size: 0.9375rem;
  font-weight: 600;
  min-width: 0;
}

.masthead__nav a {
  color: var(--ink);
  text-decoration: none;
  padding-block: 0.125rem;
  border-bottom: 1px solid transparent;
  transition: color 0.18s var(--ease), border-color 0.18s var(--ease);
}

.masthead__nav a:hover {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* "page" is set in the markup on security/privacy/terms. "true" is set by
   motion.js's scroll-spy on the home page's in-page links, and means "the
   section you are currently in" — a weaker claim, and the reason it is a
   different token rather than a reused one. */
.masthead__nav a[aria-current="page"],
.masthead__nav a[aria-current="true"] {
  color: var(--accent);
  border-bottom-color: var(--accent);
}

/* On a phone the wordmark, three links and a button wrap the bar onto TWO rows,
   which then hides the heading an anchor jump just landed on. The bar keeps the
   links; the CTA is one thumb-scroll away in the hero.

   ⛔ 38rem IS NOT TUNED TO THE CURRENT LABEL. A longer one wraps the bar around
   34rem; the reason to hide the button on a phone is the two-row bar, and the
   next label change must not be able to bring it back. */
@media (max-width: 38rem) {
  .masthead .btn {
    display: none;
  }
}

/* --------------------------------------------------------------- action */

.actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 2.125rem;
}

.actions--center {
  justify-content: center;
}

/* Stacked buttons match widths rather than each sizing to its own label. */
@media (max-width: 26rem) {
  .actions--center .btn {
    flex: 1 1 100%;
    text-align: center;
  }
}

.btn {
  display: inline-block;
  font-family: var(--font-body);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.2;
  padding: 0.9375rem 1.75rem;
  border-radius: 999px;
  text-decoration: none;
  border: 1.5px solid transparent;
  transition: transform 0.18s var(--ease), background-color 0.18s var(--ease),
    color 0.18s var(--ease), border-color 0.18s var(--ease),
    box-shadow 0.18s var(--ease);
}

.btn--primary {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--ink);
  box-shadow: var(--shadow-sm);
}

.btn--primary:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  transform: translateY(-2px);
  box-shadow: var(--shadow-md);
}

.btn--quiet {
  color: var(--ink);
  border-color: var(--rule-strong);
  background: var(--card-bg);
}

.btn--quiet:hover {
  color: var(--ink);
  border-color: var(--ink);
  transform: translateY(-2px);
  box-shadow: var(--shadow-sm);
}

.btn--sm {
  font-size: 0.875rem;
  padding: 0.6875rem 1.25rem;
  white-space: nowrap;
}

/* ----------------------------------------------------------------- hero */

/* Deliberately tighter on top than a normal band — a full band's air above the
   first line anyone reads is a wall, not space. */
.band--hero {
  padding-top: clamp(2.25rem, 1rem + 3vw, 3.75rem);
  padding-bottom: clamp(3rem, 1.5rem + 4vw, 5rem);
  border-top: 0;
  overflow: hidden;
}

.hero {
  text-align: center;
  max-width: 60rem;
}

.hero__lede {
  margin: 1.5rem auto 0;
  max-width: 42rem;
  font-size: clamp(1.0625rem, 1rem + 0.5vw, 1.25rem);
  color: var(--ink-2);
}

/* The credibility tag above the headline: a margin note — no border, no fill, no
   shadow, set in a hand and written on a slant, with one pen stroke under the
   only number on the page. index.html carries the note on why it is that and not
   a stamp, a pill or a card.

   THE ROTATION IS ON .seal__hand. See the note in index.html — .seal carries
   .reveal, and `.js-motion .reveal.is-in { transform: none }` at (0,3,0) wipes
   any transform declared on .seal. A tilt written there is silently discarded
   the moment the reveal lands.

   CAVEAT, not the --font-hand stack. The stack leads with "Nothing You Could
   Do", a thin scratchy pen that is right for a specimen card at 34px and
   illegible for a sentence at 26px. Caveat 600 is the marker-pen weight.

   ⛔ THIS RULE IS WHAT PULLS CAVEAT DOWN, and the specimens do not: they
   resolve to Nothing You Could Do and reach Caveat for three characters of
   Turkish. This sentence is the reason Caveat is fetched at all. The face is
   subset to exactly the characters this copy sets, and its unicode-range is
   generated from the file — so changing the copy means rebuilding with
   scripts/build_site_fonts.py, and `make site-check` names what is missing.

   THE ONLY EMPHASIS IS THE NUMBER, and it must stay that way. There is exactly
   one true number here and it counts the operator's own work — this is not a
   stat row, a customer count or a testimonial, and none of those exist to put
   here. */
.seal {
  margin-bottom: 1.75rem;
  padding-block: 0.25rem;
  max-width: 100%;
}

.seal__hand {
  display: inline-block;
  font-family: "Caveat", var(--font-hand);
  font-size: clamp(1.375rem, 1.2rem + 0.75vw, 1.75rem);
  font-weight: 600;
  line-height: 1.35;
  letter-spacing: 0.005em;
  color: var(--ink-2);
  transform: rotate(-3deg);
  transform-origin: 50% 100%;
  text-wrap: balance;
}

.seal__num {
  position: relative;
  display: inline-block;
  font-weight: 600;
  color: var(--ink);
}

/* Sits outside the glyph box on purpose — a pen stroke overruns the word it
   underlines. preserveAspectRatio="none" is what lets one drawn curve fit any
   width; the stroke distorts a few percent doing it, which is the point. */
.seal__rule {
  position: absolute;
  left: -5%;
  bottom: -0.34em;
  width: 110%;
  height: 0.34em;
  overflow: visible;
  fill: none;
  stroke: var(--accent);
  stroke-width: 2.4;
  stroke-linecap: round;
}

/* The promise, lifted out of the lede so it lands as its own beat rather than
   as a bolded tail on a longer sentence. */
.hero__punch {
  margin: 1.125rem auto 0;
  max-width: 36rem;
  font-size: clamp(1.125rem, 1rem + 0.7vw, 1.4375rem);
  font-weight: 800;
  line-height: 1.35;
  letter-spacing: -0.015em;
  color: var(--ink);
  text-wrap: balance;
}

/* Breaks the punch line in two only where there is room for two lines to look
   deliberate. Below that it wraps on its own. */
.br-sm {
  display: none;
}

@media (min-width: 34rem) {
  .br-sm {
    display: inline;
  }
}

/* Line break only where the headline has room for the operator's phrasing. */
.br-lg {
  display: none;
}

@media (min-width: 56rem) {
  .br-lg {
    display: inline;
  }
}

/* ⛔ NOTHING FOLLOWS THE HERO'S TWO BUTTONS — no note line, no proof chips. */

/* ---------------------------------------------------------------- cards */

/* One grid for every section. Equal columns, equal gaps, always symmetric —
   the count is a modifier so a section never invents its own layout.

   The plural container classes in the HTML — .readings, .claims, .steps,
   .plans — carry NO rules and are not meant to. They name the block so the
   markup reads, while the layout comes from .cards--N and the look from the
   singular item class (.reading, .claim, .step, .plan). Do not go looking for
   a `.steps` rule; style the item. */
.cards {
  list-style: none;
  padding: 0;
  margin-top: clamp(2.25rem, 4vw, 3.25rem);
  display: grid;
  gap: clamp(1rem, 1.8vw, 1.5rem);
}

/* ⛔ THERE IS NO `.cards--2`. No section is two across; the page runs on --3 and
   --4 only. Adding one is not free — see the note on .cards--4 below. Two
   auto-fit columns are safe where four are not, but write it only when a section
   actually needs it, and count the columns in the markup first. */
.cards--3 {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr));
}

/* Four across on a wide screen, two across on a tablet — never three-and-one,
   which is the asymmetry auto-fit produces if you let it. */
.cards--4 {
  grid-template-columns: minmax(0, 1fr);
}

@media (min-width: 40rem) {
  .cards--4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
  }
}

@media (min-width: 68rem) {
  .cards--4 {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

.card {
  background: var(--card-bg);
  border: 1px solid var(--card-rule);
  border-radius: var(--radius);
  padding: clamp(1.25rem, 2vw, 1.75rem);
  box-shadow: var(--shadow-sm);
  display: flex;
  flex-direction: column;
  transition: transform 0.25s var(--ease), box-shadow 0.25s var(--ease),
    border-color 0.25s var(--ease);
}

.card:hover {
  transform: translateY(-3px);
  border-color: var(--card-rule-hi);
  box-shadow: var(--shadow-md);
}

/* Only the heading-to-body gap is generic. There are deliberately NO
   `.card p` or `.card p + p` rules: at (0,1,1) and (0,1,2) they outrank every
   single-class component below — .plan__price's size, .step__num's size,
   .verdict's `margin-top: auto` — and silently flatten the card. They were
   written once, did exactly that, and are not coming back. A new card part
   carries its own size and margin. */
.card .h3 + .card__text,
.card .h3 + p {
  margin-top: 0.75rem;
}

/* Every card's body copy. One class, so nothing here can outrank a component
   part that sets its own size (.step__num, .claim__num, .plan__price). */
.card__text {
  font-size: 1rem;
  line-height: 1.6;
  color: var(--ink-2);
}

/* A card with two paragraphs. Nothing uses it today; the rule stays because the
   next two-paragraph card must not be written as `.card p + p`. At (0,1,2) that
   outranks every
   single-class component part above and flattens the card, which is the trap
   the note above records. The class-only form is (0,2,0) and reaches nothing
   that does not opt in. */
.card__text + .card__text {
  margin-top: 0.75rem;
}

/* -------------------------------------------------- the specimen cards */

/* Where the card came from, above the line itself. Six cards from six places
   is the point of the section, and without this the reader has to infer the
   country from the script. */
.reading__origin {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin-bottom: 0.5rem;
}

.reading__line {
  border-bottom: 1px solid var(--rule);
  padding-bottom: 0.75rem;
  min-height: 3.25rem;
  display: flex;
  align-items: flex-end;
}

/* The hand is set on a faint ruled strip and each card is rotated a little
   differently, so six specimens read as six cards written by six people rather
   than as one font used six times. The rotation is on the SPAN, inside a flex
   line box, so
   it cannot shift the card's own layout. */
.hand {
  display: inline-block;
  font-family: var(--font-hand);
  font-size: 2.125rem;
  font-weight: 500;
  line-height: 1.15;
  color: var(--accent);
  transform-origin: 0 100%;
  word-break: break-word;
}

/* Non-Latin scripts fall through to a system hand that is set on a much larger
   body than Caveat's, so they need pulling back or they blow the line box.
   Cyrillic is fine — Caveat covers it, which is why it is in --font-hand, and
   THE CALL TO KEEP CARRYING IT STANDS: the specimen block is the page, and a
   Russian card in a system sans is not a handwritten card. It is paid for by
   subsetting Caveat to the exact glyphs the page sets, so `БиВак полио` is part
   of that face's unicode-range. The measurements are in web/SITE-DESIGN.md. */
.hand[lang="zh"],
.hand[lang="ru"] {
  font-size: 1.875rem;
}

/* Han has NO webfont here, and the default CJK face is a printed sans — which
   made the one specimen whose whole point is "this was written by hand" look
   typeset. These are the brush and pen faces that ship with macOS, iOS and
   Windows, in that order; the last entry falls back to the Latin hand stack so
   the rule can never leave the element with no family at all. Nothing is
   downloaded for this. */
.hand[lang="zh"] {
  font-family: "Hanzipen SC", "HanziPen SC", "Yuppy SC", "Kaiti SC", "STKaiti",
    "KaiTi", "DFKai-SB", var(--font-hand);
  font-size: 2.25rem;
  letter-spacing: 0.05em;
}

/* Six different pens. nth-child rather than a per-card variable: setting one
   would mean a style="" attribute, which this site's CSP refuses outright. */
.readings > .reading:nth-child(1) .hand { transform: rotate(-2.4deg); }
.readings > .reading:nth-child(2) .hand { transform: rotate(1.6deg); }
.readings > .reading:nth-child(3) .hand { transform: rotate(-1.1deg); }
.readings > .reading:nth-child(4) .hand { transform: rotate(2.1deg); }
.readings > .reading:nth-child(5) .hand { transform: rotate(-3.2deg); }
.readings > .reading:nth-child(6) .hand { transform: rotate(1.2deg); }

.readings > .reading:nth-child(2) .hand,
.readings > .reading:nth-child(5) .hand { color: var(--ink); }

.readings > .reading:nth-child(4) .hand { color: var(--ink-2); }

.reading__date {
  margin-top: 0.75rem;
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--ink-3);
}

.reading__read {
  margin-top: 0.875rem;
  font-size: 1rem;
  line-height: 1.65;
  color: var(--ink-2);
}

.verdict {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: auto;
  padding-top: 1.125rem;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink);
}

.verdict::before {
  content: "";
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

/* THE COLOUR IS THE FINDING, and nothing here is decorative — red is a row the
   patient has not satisfied, green is a dose that counted. ⛔ Do not add a
   verdict because a card looks bare; a colour on this chip is a claim about what
   the product read.
   There is a THIRD designed verdict with no rule here: `.verdict--hold`, amber,
   a read that stopped and asked — #8a6410. No specimen card carries one today.
   Written down rather than lost: use that hue verbatim if a held row ever
   becomes specimen copy. */
.verdict--open {
  color: var(--flag);
}

.verdict--good {
  color: var(--ok);
}

/* ⛔ THERE IS NO "Three things, in this order" NUMBERED-CARD BAND. index.html
   carries the note on WHERE the three binding claims land instead;
   business/01-strategy.md §5 still requires all three on every page, so the
   missing layout is not a missing rule. */

/* ⛔ NO PULL QUOTE. The featured plan is the only filled block on the site. */

/* ----------------------------------------------------- how a visit runs */

/* THE CAROUSEL. The trade against four text cards is deliberate: each step gets
   a drawing and one sentence rather than a paragraph.

   THE SAFETY CONTRACT, which is the same one .js-motion has: everything that
   HIDES anything is nested under `.is-reel`, and only reel.js adds that class.
   With the script blocked this is four stacked cards with fully-drawn artwork
   and no controls. Never move a hiding rule out from under `.is-reel`, and
   never ship data-active in the markup.

   --reel-dwell is read by reel.js (getComputedStyle) so the dot's progress fill
   and the advance timer cannot drift apart. Change the duration HERE only. */
.reel {
  --reel-dwell: 7s;
  margin-top: clamp(2.25rem, 4vw, 3.25rem);
}

.reel__track {
  list-style: none;
  padding: 0;
  margin: 0;
  display: grid;
  gap: clamp(1rem, 1.8vw, 1.5rem);
}

.reel__slide {
  display: grid;
  gap: clamp(1.25rem, 3vw, 2.5rem);
  align-items: center;
  padding: clamp(1.5rem, 3vw, 2.75rem);
  background: var(--card-bg);
  border: 1px solid var(--card-rule);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}

@media (min-width: 52rem) {
  .reel__slide {
    grid-template-columns: 1.05fr 1fr;
  }
}

.reel__art {
  min-width: 0;
}

.reel__say {
  min-width: 0;
}

.reel__step {
  display: flex;
  align-items: center;
  gap: 0.625rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--accent);
}

.reel__step span {
  background: var(--fill-bg);
  color: var(--fill-ink);
  padding: 0.25rem 0.5rem 0.3125rem;
  border-radius: 5px;
  letter-spacing: 0.1em;
}

/* (0,2,0) beats .h3's (0,1,0). The slide heading is the biggest text in the
   section on purpose — it is doing the work the four deleted paragraphs did. */
.reel__say .h3 {
  margin-top: 1rem;
  font-size: clamp(1.5rem, 1.15rem + 1.4vw, 2.125rem);
  line-height: 1.12;
}

.reel__line {
  margin-top: 0.875rem;
  font-size: clamp(1.0625rem, 1rem + 0.35vw, 1.1875rem);
  line-height: 1.55;
  color: var(--ink-2);
  max-width: 26rem;
}

/* Controls exist only when the script is driving. display:none rather than
   [hidden], because a class beats [hidden] and this file is full of classes. */
.reel__bar {
  display: none;
}

/* ------------------------------------------- carousel mode (reel.js only) */

.is-reel .reel__stage {
  overflow: hidden;
  border: 1px solid var(--card-rule);
  border-radius: var(--radius-lg);
  background: var(--card-bg);
  box-shadow: var(--shadow-md);
}

.is-reel .reel__track {
  display: flex;
  align-items: stretch;
  gap: 0;
  transition: transform 0.62s var(--ease);
}

.is-reel .reel__slide {
  flex: 0 0 100%;
  min-width: 0;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

/* One rule per slide index rather than a translated custom property, for the
   same reason the reveal stagger is nth-child: setting a per-element variable
   from JS means a style="" attribute, and the CSP refuses those. A fifth slide
   needs a fifth rule here. */
.is-reel[data-active="0"] .reel__track { transform: translateX(0); }
.is-reel[data-active="1"] .reel__track { transform: translateX(-100%); }
.is-reel[data-active="2"] .reel__track { transform: translateX(-200%); }
.is-reel[data-active="3"] .reel__track { transform: translateX(-300%); }

/* The wrap is a CUT. Autoplay loops the last slide back to the first, and the
   0.62s transition would play that jump as a rewind sliding backwards through
   every slide in between — the carousel visibly jamming at the end of each
   cycle. reel.js holds this class for exactly that one jump. */
.is-reel.is-snap .reel__track {
  transition: none;
}

.is-reel .reel__bar {
  display: block;
  margin-top: 1.25rem;
}

/* Two up on a phone, four across from tablet width. Never 4-across on a narrow
   screen: "Before the visit" at 4 columns of 80px wraps to three lines and the
   row stops looking like a control. */
.reel__tabs {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 0.25rem 0.75rem;
  list-style: none;
  padding: 0;
  margin: 0;
}

@media (min-width: 44rem) {
  .reel__tabs {
    grid-template-columns: repeat(4, minmax(0, 1fr));
  }
}

/* The whole tab is the target — the progress bar is its top edge, not a
   separate thing to hit. Padding sets a comfortable touch height without a
   fixed one, so a wrapped label still gets a full-height button. */
.reel__tab {
  position: relative;
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  width: 100%;
  padding: 0.8125rem 0.125rem 0.6875rem;
  border: 0;
  background: none;
  text-align: left;
  cursor: pointer;
  color: var(--ink-3);
  transition: color 0.18s var(--ease);
}

/* The unfilled track. On the button rather than the fill so it is there before
   the fill has anything to show. */
.reel__tab::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 0.1875rem;
  border-radius: 999px;
  background: var(--rule-strong);
}

.reel__tab:hover {
  color: var(--ink);
}

.reel__tab[aria-current="true"] {
  color: var(--ink);
}

.reel__tab-n {
  flex: none;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.04em;
  color: var(--ink-3);
}

.reel__tab[aria-current="true"] .reel__tab-n {
  color: var(--accent);
}

.reel__tab-t {
  font-size: 0.875rem;
  font-weight: 700;
  line-height: 1.3;
  letter-spacing: -0.01em;
}

.reel__fill {
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  height: 0.1875rem;
  border-radius: 999px;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: 0 50%;
}

/* The active tab fills over exactly one dwell, which is what makes the carousel
   read as counting down rather than jumping at random.

   IT IS THE SAME NUMBER THE SCRIPT WAITS, AND ONLY BECAUSE reel.js READS
   --reel-dwell BACK OUT OF HERE. Change the duration here and the advance
   follows; write a number into reel.js instead and the bar finishes early for
   the rest of the visit. The restart is not free either — reel.js forces a
   style flush between dropping aria-current and setting it, because re-picking
   the tab that is already current would otherwise leave the bar sitting full. */
.reel__tab[aria-current="true"] .reel__fill {
  animation: reel-fill var(--reel-dwell) linear forwards;
}

/* Hover, keyboard focus, or a backgrounded tab. reel.js banks the unspent time
   in the same statement that adds this class, so the bar and the clock stop and
   start together instead of drifting apart over a visit. */
.reel.is-paused .reel__tab[aria-current="true"] .reel__fill {
  animation-play-state: paused;
}

/* Reduced motion, and nothing else — ⛔ a click must not stop autoplay, the reel
   autoruns always. The bar shows position instead of progress, because nothing
   is counting down. */
.reel.is-still .reel__tab[aria-current="true"] .reel__fill {
  animation: none;
  transform: scaleX(1);
}

@keyframes reel-fill {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

/* ------------------------------------------------------- the slide artwork */

/* Colours live here and never in the markup, so the drawings follow the print
   stylesheet with everything else. A fill=/stroke= presentation attribute is
   allowed by the CSP but would be invisible to it. */
.rl {
  display: block;
  width: 100%;
  height: auto;
  max-width: 30rem;
  margin-inline: auto;
}

.rl-paper { fill: var(--card-bg); stroke: var(--card-rule-hi); stroke-width: 1.5; }
/* The step-1 phone's own chrome: a device gets a decisively darker outline
   than a sheet of paper, and its screen carries a faint accent glass tint —
   an overlay at low opacity rather than a new fill token, so it stays a tint
   of the one accent. */
.rl-device { fill: var(--card-bg); stroke: var(--ink-3); stroke-width: 2.5; }
.rl-glass { fill: var(--accent); opacity: 0.06; }
.rl-soft { fill: var(--paper-2); stroke: var(--rule); stroke-width: 1.5; }
.rl-screen { fill: var(--paper-2); }
.rl-head { fill: var(--accent); opacity: 0.1; }
.rl-head-sq { fill: var(--accent); opacity: 0.1; }
.rl-blk { fill: var(--accent); }
.rl-bar { fill: var(--rule-strong); }
.rl-bar--pale { fill: var(--rule); }
/* No `--flag` bar here: the open Part 10 row in step 3 is a NAMED row, and
   .rl-t--flag carries the colour. */
.rl-chip { fill: var(--accent); opacity: 0.24; }
.rl-chip--dark { fill: var(--accent); opacity: 1; }
.rl-box { fill: var(--card-bg); stroke: var(--rule-strong); stroke-width: 1.6; }
.rl-box--open { stroke: var(--flag); }
.rl-clip { fill: var(--card-bg); stroke: var(--card-rule-hi); stroke-width: 1.5; }
.rl-alert { fill: var(--flag); }
.rl-dot-flag { fill: var(--flag); }
.rl-dot-ok { fill: var(--ok); }
.rl-rule { stroke: var(--rule-strong); stroke-width: 1.5; }

/* Real words on the carousel art — the .fg-t of this prefix, and set MUCH
   larger than those are. The artefact figures are thumbnails on a 4-across card;
   these fill half a slide, and the whole point of putting words on the art is
   that they be readable.

   THE SIZES ARE PICKED FOR THE RENDERED SCALE, NOT THE viewBox. .rl is 480 units
   wide and caps at 30rem, and inside a two-column slide it lands around 0.8:1 —
   so 15px here is roughly 12px on screen and 12px is roughly 9.5px. Anything
   under about 11 units stops being type and becomes texture; that is the floor
   these were sized against, and it is why the Td row says "tetanus ·
   diphtheria" rather than a longer, truer-looking string that would have had to
   be set smaller than it.

   FILL, NOT COLOR. These are SVG <text>, so `color` does nothing. */
.rl-t {
  font-family: var(--font-body);
  font-size: 15px;
  font-weight: 700;
  fill: var(--ink);
}

.rl-t--sub {
  font-size: 12px;
  font-weight: 600;
  fill: var(--ink-3);
}

.rl-t--date {
  font-family: var(--font-mono);
  font-size: 10px;
  font-weight: 600;
  fill: var(--ink-3);
}

.rl-t--head {
  font-size: 12px;
  font-weight: 700;
  fill: var(--accent);
}

/* On the filled chip, so it takes the PAPER colour rather than an ink — and it
   follows the theme for free, because --accent and --paper swap together. */
.rl-t--chip {
  font-family: var(--font-mono);
  font-size: 8px;
  font-weight: 600;
  letter-spacing: 0.04em;
  fill: var(--paper);
}

.rl-t--row {
  font-size: 13px;
  font-weight: 600;
  fill: var(--ink-2);
}

/* The one row that stayed open. MUST STAY LAST of the .rl-t--* rules: it is
   (0,1,0), the same as .rl-t--row it has to beat, so source order is the only
   thing deciding it. */
.rl-t--flag {
  fill: var(--flag);
}

/* The four viewfinder corners on the step-1 phone. Accent, because they are the
   only thing on that screen the software is doing — everything else in the
   frame is the patient's own card. */
.rl-vf {
  fill: none;
  stroke: var(--accent);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The shutter ring. The filled centre is .rl-blk, which already pops. */
.rl-ring {
  fill: none;
  stroke: var(--rule-strong);
  stroke-width: 2.5;
}

.rl-tick {
  fill: none;
  stroke: var(--ok);
  stroke-width: 2.6;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.rl-arc,
.rl-arc-head {
  fill: none;
  stroke: var(--accent);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The step-4 signature. It READS — "Civil surgeon", set in the site's own
   hand (Caveat) rather than drawn as an abstract stroke, which is what the
   old .rl-sign path here was. It reveals left-to-right via .rl-write below,
   so it still looks written on. */
.rl-sign-t {
  font-family: "Caveat", var(--font-hand);
  font-size: 40px;
  font-weight: 600;
  fill: var(--ink);
  /* The letters COLLIDE on purpose — a signature is written in one motion,
     and the negative tracking is what joins Caveat's separate glyphs into
     something that reads as connected. Loosen it and it turns back into
     tidy handwriting. */
  letter-spacing: -0.12em;
}

/* The flourish under the signature — the stroke a signer finishes with. It is
   the only .rl-sign* element that is a drawn path rather than text. */
.rl-sign {
  fill: none;
  stroke: var(--ink);
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The ligature between the two words — pen-weight, matching Caveat's stems
   rather than the heavier flourish. */
.rl-sign--thin {
  stroke-width: 1.8;
}

/* The vaccine card's handwriting, on the step-1 card and again on step 2's.
   Meddon ON PURPOSE — the least legible face on the site (operator 2026-08-19:
   Caveat here was "too legible"). The entries are meant to be deciphered, not
   read: the card is the "before" picture the typed rows in step 2 answer.
   Meddon's x-height is tiny for its em, so these sizes run smaller than the
   Caveat numbers they replaced while rendering about the same. */
.rl-card-hand {
  font-family: "Meddon", var(--font-hand);
  font-size: 14px;
  fill: var(--ink-2);
}

/* The dates crowd their digits — negative tracking runs Meddon's connected
   strokes into each other, which is what a date jotted in one motion does. */
.rl-card-hand--date {
  font-size: 10px;
  fill: var(--ink-3);
  letter-spacing: -0.06em;
}

/* The scan bar is pure motion — it has no resting state worth showing, so it is
   invisible until its animation runs and needs no .is-reel guard. */
.rl-scan {
  fill: var(--accent);
  opacity: 0;
  transform-box: view-box;
  transform-origin: 119px 150px;
}

/* ------------------------------------------------ artwork motion, gated */

/* EVERY hiding rule below is under .is-reel. Script blocked ⇒ the drawings are
   complete and static, which is the whole no-JS promise for this section. */
/* ⛔ THE GAP IS LONGER THAN THE PATH (1 2, not 1). With `dasharray: 1` the
   pattern repeats every 2 units, so a path of pathLength 1 at dashoffset 1 has
   its far END sitting exactly on a dash boundary — and a round linecap paints
   that zero-length dash as a DOT. Both step-4 signature strokes showed theirs
   while waiting out their delay. A 2-unit gap puts the whole path strictly
   inside it; the animation to dashoffset 0 still reveals start-to-end. */
.is-reel .rl-draw {
  stroke-dasharray: 1 2;
  stroke-dashoffset: 1;
}

.is-reel .rl-fade {
  opacity: 0;
}

/* The write-on reveal for text set in a hand face: a clip that opens left to
   right, so the words appear the way a pen would leave them. The vertical
   insets are negative so ascenders and the g's descender are never clipped. */
.is-reel .rl-write {
  clip-path: inset(-30% 100% -30% 0);
}

.is-reel .rl-blk {
  opacity: 0;
  transform: scale(0.55);
  transform-box: fill-box;
  transform-origin: center;
}

.is-reel .rl-row {
  opacity: 0;
  transform: translateX(14px);
  transform-box: view-box;
}

.is-reel .is-live .rl-draw {
  animation: rl-draw 0.75s var(--ease) forwards;
}

.is-reel .is-live .rl-fade {
  animation: rl-fade 0.5s var(--ease) forwards;
}

.is-reel .is-live .rl-write {
  animation: rl-write 1.1s var(--ease) forwards;
}

.is-reel .is-live .rl-blk {
  animation: rl-pop 0.45s var(--ease) forwards;
}

.is-reel .is-live .rl-row {
  animation: rl-slide 0.6s var(--ease) forwards;
}

.is-reel .is-live .rl-sweep {
  animation: rl-sweep 1.5s var(--ease) 0.15s forwards;
}

/* Stagger. Delays are classes rather than an inline custom property for the
   CSP reason recorded at the top of this file.
   ⛔ ONE LINE IS SHARED BY SEVERAL SLIDES, so each arm has to be checked on its
   own. The markup carries exactly one `rl-p*`, the `.rl-blk` on the first slide,
   at `rl-p6` / 0.46s. A beat with no arm left is deleted rather than kept empty
   — the numbers are positions in a sequence, not a scale, so 0.08s means nothing
   on its own. */
.is-reel .is-live .rl-r1 { animation-delay: 0.08s; }
.is-reel .is-live .rl-d2 { animation-delay: 0.16s; }
.is-reel .is-live .rl-d3 { animation-delay: 0.26s; }
.is-reel .is-live .rl-r2 { animation-delay: 0.36s; }
.is-reel .is-live .rl-p6, .is-reel .is-live .rl-d4 { animation-delay: 0.46s; }
.is-reel .is-live .rl-d5 { animation-delay: 0.58s; }
.is-reel .is-live .rl-r3 { animation-delay: 0.66s; }
.is-reel .is-live .rl-d6 { animation-delay: 0.72s; }
.is-reel .is-live .rl-d7 { animation-delay: 0.9s; }

@keyframes rl-draw {
  to { stroke-dashoffset: 0; }
}

@keyframes rl-fade {
  to { opacity: 1; }
}

@keyframes rl-write {
  to { clip-path: inset(-30% -5% -30% 0); }
}

@keyframes rl-pop {
  from { opacity: 0; transform: scale(0.55); }
  70%  { opacity: 1; transform: scale(1.08); }
  to   { opacity: 1; transform: scale(1); }
}

@keyframes rl-slide {
  to { opacity: 1; transform: translateX(0); }
}

/* 114px is the card's height minus the bar's own 10: the bar starts flush with
   the card's top edge and finishes flush with its bottom. Resize the step-2
   card and this number moves with it. */
@keyframes rl-sweep {
  from { transform: rotate(-5deg) translateY(0); opacity: 0; }
  12%  { opacity: 0.3; }
  88%  { opacity: 0.3; }
  to   { transform: rotate(-5deg) translateY(114px); opacity: 0; }
}

/* ------------------------------------------------------------- outputs */

/* Centred, like every other card grid on the page — the section rule at the top
   of this file is that things sit on the centre line. The art above the caption
   is already centred in its panel, so a left-set caption under it reads as an
   accident. */
.output {
  text-align: center;
}

.outputs strong {
  display: block;
  font-size: 1rem;
  font-weight: 800;
  letter-spacing: -0.01em;
}

.outputs span {
  display: block;
  margin-top: 0.5rem;
  font-size: 0.9375rem;
  color: var(--ink-2);
}

/* The four artefacts, each on its own recessed panel so the drawing reads as
   something looked at rather than something printed on the card. The panel is
   full-bleed to the card's padding — hence the negative margins — because a
   figure inset inside an inset is two frames around one small picture.

   THE PANEL TAKES THE CARD'S SLACK, so no empty white space sits below the
   caption. .cards--4 stretches all four cards to the
   tallest, so a card with a two-line caption beside one with four ended on a
   band of empty white under its last line, which reads as a card that did not
   finish rather than one that had less to say. `flex: 1` sends that space
   upward into the panel, where it is air around the artefact.

   The negative margins survive the flex: they are the item's OUTER edges and
   flex-basis resolves on the inner box, so the panel still bleeds to the card's
   padding at every width. Only its height is now elastic.

   THE DRAWINGS THEMSELVES GOT BIGGER in the same pass, and that is a viewBox
   change in index.html, not a rule here — each one is cropped to its own
   content instead of floating in a 200x120 box it only filled two-thirds of.
   Both halves are needed: a taller panel alone would have grown the grey and
   not the picture. */
.output__art {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  margin: calc(clamp(1.25rem, 2vw, 1.75rem) * -1);
  margin-bottom: 1.25rem;
  padding: 0.875rem 0.75rem;
  border-bottom: 1px solid var(--card-rule);
  border-radius: var(--radius) var(--radius) 0 0;
  background: var(--paper-2);
}

.fg {
  display: block;
  width: 100%;
  height: auto;
}

/* ------------------------------------------------- the artefact drawings */

/* Same palette and the same parts as the carousel's .rl artwork, deliberately —
   they are the same objects seen twice. A SEPARATE PREFIX because .rl carries
   the carousel's motion rules, all of which are gated on `.is-reel .is-live`:
   reuse the names here and these four drawings start out hidden, waiting for a
   slide that will never become live. */
.fg-paper {
  fill: var(--card-bg);
  stroke: var(--card-rule-hi);
  stroke-width: 1.4;
}

.fg-back {
  fill: var(--paper);
  stroke: var(--rule);
}

.fg-head { fill: var(--accent); opacity: 0.1; }
.fg-bar { fill: var(--rule-strong); }
.fg-bar--pale { fill: var(--rule); }
/* No "line the draft added" bar: the four fallback drawings use only .fg-bar
   and .fg-bar--pale. */
.fg-mark { fill: var(--accent); }
/* BOTH PILLS CARRY TEXT NOW, and that is what sets these two numbers. A pill
   is drawn in the same colour as the word inside it, so every point of opacity
   added to the pill is contrast taken off the word — 0.24 and 0.3 put the
   accent label at 4.4:1 and the result label at 4.1:1 on paper, under the
   floor. Held back a little they both clear 4.7:1, and they still read as soft
   pills rather than outlines. Going the other way — a stronger fill — makes it
   WORSE, which is the counter-intuitive part worth writing down. */
.fg-chip { fill: var(--accent); opacity: 0.2; }
.fg-chip--ok { fill: var(--ok); opacity: 0.22; }
.fg-chip--dark { fill: var(--accent); }
.fg-box { fill: var(--card-bg); stroke: var(--rule-strong); stroke-width: 1.4; }
.fg-rule { stroke: var(--rule); stroke-width: 1.4; }
.fg-dot-flag { fill: var(--flag); }

.fg-tick {
  fill: none;
  stroke: var(--ok);
  stroke-width: 2.2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* The strike through a superseded line. --flag rather than --ink: it is the
   line that stopped being true, not one somebody crossed out by hand. */
.fg-strike {
  stroke: var(--flag);
  stroke-width: 1.6;
  stroke-linecap: round;
}

/* The paperclip. Stroke only — a filled paperclip is a grey blob at this size. */
.fg-wire {
  fill: none;
  stroke: var(--ink-3);
  stroke-width: 2.2;
  stroke-linecap: round;
}

/* The "applied" badge over the corner of the page. The ring is the card's own
   background, punching the badge clear of the paper edge so it reads as sitting
   on top rather than printed on. */
.fg-badge-ring { fill: var(--paper-2); }
.fg-badge { fill: var(--ok); }

.fg-badge-tick {
  fill: none;
  stroke: var(--card-bg);
  stroke-width: 3;
  stroke-linecap: round;
  stroke-linejoin: round;
}

/* Real words, at the size they would be on the real page. Set in the body face
   so they read as a document and not as a diagram's labels. */
.fg-t {
  font-family: var(--font-body);
  font-size: 7.5px;
  font-weight: 600;
  fill: var(--ink-2);
}

.fg-t--head {
  font-size: 8.5px;
  font-weight: 700;
  fill: var(--accent);
}

.fg-t--title {
  font-size: 11px;
  font-weight: 800;
  fill: var(--ink);
}

/* On the solid accent chip, so it is the PAPER colour rather than an ink —
   knocked out of the fill, the way .fg-chip--dark's own label has to be. */
.fg-t--chip {
  font-size: 7px;
  font-weight: 700;
  letter-spacing: 0.02em;
  fill: var(--paper);
}

/* SIZE AND COLOUR ARE SEPARATE MODIFIERS from here down, deliberately. Real
   text in these drawings needs small-and-green, small-and-muted and
   full-size-and-accent all at once; one class per combination would be six.
   They are all single-class rules that set ONE property each, so .fg-t plus a
   size plus a colour composes without anyone having to think about specificity,
   and a <tspan class="fg-t--ok"> can recolour three words inside a muted line.
   Every one of them is after .fg-t and after .fg-t--head, which is what lets
   them win — equal weight, later wins, so do not move this block up. */

/* Dense detail: the header fields under the patient's name, the value inside a
   pill, the status word at the end of a completed row. MEASURED, not guessed —
   the viewBox is 200 wide and at a 1280 viewport the four-up grid renders each
   drawing at about 230 CSS px, so a unit here is roughly 1.15 px on screen and
   this lands near 8. That is deliberately the same size .fg-t--chip already
   renders at; anything under it stopped being text and went back to being the
   grey bar this replaced. Re-measure before shrinking it. */
.fg-t--sm { font-size: 6.8px; }

/* The specimen patient's name. THE ONE INVENTED STRING IN ALL FOUR DRAWINGS —
   see the note above the card list in index.html. Bigger than the field labels
   under it, smaller than the packet's band title, because on a real I-693 the
   name is the first thing the eye lands on and not a heading. */
.fg-t--name {
  font-size: 9.5px;
  font-weight: 800;
  fill: var(--ink);
}

/* Furniture and superseded lines — the same role --ink-3 has in the page copy,
   never a sentence somebody has to read. */
.fg-t--mute { fill: var(--ink-3); }

.fg-t--acc { fill: var(--accent); }

/* Green, and it has to hold in two places at once: as a status word on white
   paper (6.6:1) and as the label inside .fg-chip--ok, which is the SAME COLOUR
   held back by alpha (4.7:1 — see the note on those pills above). It is the
   token and not a hex so the print stylesheet can take it to black with
   everything else. */
.fg-t--ok { fill: var(--ok); }

/* --------------------------------------------------------------- plans */

/* THE OUTLINE IS SCOPED TO .plan, and on purpose. Every other card grid sits on
   --paper, a cream, where a white card separates on its own and .card's faint
   --card-rule is enough. The plans sit on .band--wash, and white-on-near-white
   left the two unfilled cards reading as three floating columns of text next to
   one solid block.

   ⛔ COLOUR ALONE IS NOT ENOUGH HERE. --card-rule-hi is a pale blue-grey and
   stays a hairline against a wash that is itself pale blue, so the WIDTH carries
   it too: 1.5px in --rule-strong, the darkest neutral in the palette, which
   reads as a drawn edge rather than as the seam between two near-whites.

   .plan--featured overrides the COLOUR only, further down, and keeps this width
   — three cards outlined identically, differing in fill. Shorthand rather than
   border-color, because the width is now part of what is being set. */
.plan {
  text-align: center;
  align-items: center;
  border: 1.5px solid var(--rule-strong);
}

/* The top line of every plan card: the tier label at the left, and on the
   recommended one a badge at the right. A ROW ON ALL THREE CARDS, even the two
   that hold nothing but the label — the min-height is what keeps the three
   plan names sitting on the same baseline, and without it the middle card's
   badge pushes its whole card down by its own height.

   NOT a ribbon absolutely positioned over the card's top edge. That is the
   common treatment and it is the one that breaks here: .card sets overflow
   visible but .plan centres its children, so a ribbon needs a negative margin
   tuned to a clamp()ed padding, and it lands on the tier label on a narrow
   card. In the row it simply cannot collide. */
.plan__top {
  width: 100%;
  min-height: 1.5rem;
  margin-bottom: 0.75rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}

/* The tier numeral, in figures. Deliberately the smallest type on the page — it
   labels the card, it does not sell it. */
.plan__tier {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  line-height: 1;
  color: var(--ink-3);
}

.plan--featured .plan__tier {
  color: var(--fill-soft);
}

/* Reads as a paper tab on the blue: the card's own text colour becomes the
   badge's background, so it can never be a second blue on blue. */
.plan__badge {
  padding: 0.375rem 0.625rem 0.4375rem;
  border-radius: 999px;
  background: var(--fill-ink);
  color: var(--fill-bg);
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  line-height: 1;
  white-space: nowrap;
}

/* The recommended card. Three things separate it from its neighbours and they
   are meant to be read together: the fill, a ring a shade darker than the fill
   so the card has an edge rather than bleeding into the band, and a deeper
   shadow.

   THE EDGE IS ITS OWN TOKEN, and it has to be several steps darker than the
   fill. One step off (#23477b against the #33578c fill) is visible on white
   paper and invisible at 1.5px on the pale wash this band actually sits on,
   which makes the card read as a solid block beside two outlined ones.
   ⛔ This is the ONE filled block with an edge — do not generalise --plan-edge
   into a shared token; the last one outlived every reader it had. */
.plan--featured {
  background: var(--fill-bg);
  border-color: var(--plan-edge);
  color: var(--fill-ink);
  box-shadow: var(--shadow-lift);
}

/* .card:hover is (0,2,0) and would otherwise repaint the fill's own edge with
   the pale --rule-strong the two white cards use. */
.plan--featured:hover {
  border-color: var(--plan-edge);
}

/* Standing taller than its neighbours, by MARGIN and not by transform. A
   transform here is discarded: this card carries .reveal, and
   `.js-motion .reveal.is-in { transform: none }` at (0,3,0) outranks it — the
   same trap that kept the hero tag flat. Only once the three cards are actually
   side by side; in a stacked column a card that overhangs its row reads as a
   layout bug rather than emphasis. */
@media (min-width: 65rem) {
  .plan--featured {
    margin-block: -0.875rem;
    padding-block: calc(clamp(1.25rem, 2vw, 1.75rem) + 0.875rem);
  }
}

.plan__name {
  font-size: 0.9375rem;
  font-weight: 800;
  letter-spacing: -0.01em;
}

/* WHO THE TIER IS FOR, above the price rather than under the fold. It carries
   what a stranded line at the bottom of the card used to — that Tiers 1 and 2
   are for a single civil surgeon — as the first clause of two of these three
   lines.

   THE min-height IS WHAT KEEPS THE THREE PRICES ON ONE LINE. Every card below
   this point is a fixed-height row (.plan__price already sets its own), so the
   only thing that can knock the row out of alignment is a pitch that wraps to a
   different number of lines than its neighbours. 2.55rem is two lines at
   0.875rem × 1.45 plus the rounding, and no string may exceed two lines at the
   width .cards--3 gives them.

   ⚠ SHORTER IS FREE, LONGER IS NOT. Tiers 1 and 2 read "For one civil surgeon."
   on ONE line while Tier 3 takes two — the min-height absorbs that and the
   prices stay level, which is the whole point of a min rather than a fixed
   height. ⛔ A THIRD line is what breaks it: it
   silently drops one card's price half a line below the other two. Count the
   wrap before lengthening any of them, and shorten rather than raise this
   number. */
.plan__pitch {
  margin-top: 0.5rem;
  min-height: 2.55rem;
  font-size: 0.875rem;
  line-height: 1.45;
  color: var(--ink-2);
  width: 100%;
}

.plan--featured .plan__pitch {
  color: var(--fill-soft);
}

/* Fixed height, not fixed size: the "Contact us" tier sets smaller type, and
   without this the divider under it sits a few pixels off its neighbours' —
   which is exactly the kind of near-alignment that reads as sloppy. */
.plan__price {
  margin-top: 0.75rem;
  min-height: 3.25rem;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-mono);
  font-size: 2.375rem;
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.03em;
}

.plan__unit {
  margin-top: 0.25rem;
  font-size: 0.84375rem;
  font-weight: 600;
  color: var(--accent);
}

/* FULL WHITE, not the dimmed --fill-soft. --fill-soft is right for .plan__tier
   above, and that label is the only thing on this card that recedes —
   everything a buyer actually reads is at full strength. */
.plan--featured .plan__unit {
  color: var(--fill-ink);
}

/* THE TICKED LISTS ARE BACK (operator 2026-08-23), shorter than the ones cut
   2026-08-08 — the note above .plans in index.html is the rule book for what
   a line may say. Left-aligned inside a centring card, so both need width.

   THE TICK IS DRAWN, not typed: two borders on an empty box rotated -45° — a
   "✓" is a different glyph in every fallback face and is read aloud as "check
   mark" once per item, and a data: URI bakes its colour in, so the filled
   card would need a second copy and @media print a third. */
.plan__feat {
  width: 100%;
  margin-top: 1.75rem;
  padding-top: 1.25rem;
  border-top: 1px solid var(--rule);
  text-align: left;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--ink-2);
}

.plan--featured .plan__feat {
  border-top-color: var(--fill-rule);
  color: var(--fill-soft);
}

/* The list carries its own divider so Tier 1 — whose list stands with no
   ladder heading — still separates from the price block above; under a
   heading the divider is the heading's, one rule per card. The 1.75rem above
   the rule is the operator's asked-for air between the pricing lines and the
   features. */
.plan__list {
  width: 100%;
  margin: 1.75rem 0 0;
  padding: 1.25rem 0 0;
  border-top: 1px solid var(--rule);
  list-style: none;
  text-align: left;
  font-size: 0.84375rem;
  line-height: 1.5;
}

.plan__feat + .plan__list {
  margin-top: 0.625rem;
  padding-top: 0;
  border-top: 0;
}

.plan--featured .plan__list {
  border-top-color: var(--fill-rule);
}

.plan__list li {
  position: relative;
  padding-left: 1.3125rem;
  margin-top: 0.5625rem;
}

.plan__list li:first-child {
  margin-top: 0;
}

.plan__list li::before {
  content: "";
  position: absolute;
  left: 0.0625rem;
  top: 0.5em;
  width: 0.5rem;
  height: 0.25rem;
  border-left: 2px solid var(--accent);
  border-bottom: 2px solid var(--accent);
  transform: rotate(-45deg);
}

.plan--featured .plan__list li::before {
  border-color: var(--fill-ink);
}

.plan__price--talk {
  font-family: var(--font-display);
  font-size: 1.75rem;
  font-weight: 600;
  letter-spacing: -0.02em;
}

/* The one link in the grid, and it stays a LINK. It inherits the price line's
   face and size so the three cards' third rows still read as one row, and it
   keeps the underline: dropping it would leave accent-coloured display text
   that is only discoverable by hovering, which is how a link becomes a button
   nobody agreed to. The offset is wider than the page default because this type
   is twice body size and a 0.2em underline sits on the descenders at it. */
.plan__talk {
  color: inherit;
  text-underline-offset: 0.16em;
  text-decoration-thickness: 1.5px;
}

.plan__talk:hover {
  color: var(--accent);
  text-decoration-thickness: 2.5px;
}

/* A charge that is true ONCE, under a price that is true every exam. Muted and
   small on purpose — it is not competing with $9 — but on the card and in the
   open, never an asterisk. ⛔ It is the reason the band's footer no longer says
   "no setup fee"; index.html carries the note. */
.plan__fee {
  margin-top: 0.375rem;
  font-size: 0.8125rem;
  line-height: 1.45;
  color: var(--ink-2);
}

/* Only Tier 1 carries one today. The filled card's own contrast set is here so
   the day a fee lands on Tier 2 it does not arrive as grey-on-navy. */
.plan--featured .plan__fee {
  color: var(--fill-soft);
}

/* ⛔ NO PER-CARD CTA and no "on every plan" strip under the grid — the
   band-level Get started under the billing sentence (operator 2026-08-23) is
   the hero CTA's twin, reusing .actions/.btn--primary and needing nothing of
   its own here. index.html's note above .plans has the rest. */

/* --------------------------------------------------- billing period */

/* Monthly / Annual, and it is TWO RADIOS AND NO SCRIPT — _headers ships
   script-src 'self' with no 'unsafe-inline', so a handler on the page is
   refused silently and only in production. Radios also arrive with arrow-key
   movement, a real focus ring and the right announcement already attached,
   which is three things a pair of divs would have had to be given by hand.

   EVERY SWAP BELOW HANGS OFF `#billing-… :checked ~`, so the two inputs must
   stay siblings of both .billing and .plans. index.html carries the long note.

   The <fieldset> is here for the group name only, and it wraps the plans
   because the inputs have to sit beside them. Reset to nothing: its default
   border and padding would draw a box round the grid, and the UA's implicit
   min-width: min-content would stop .cards--3 shrinking on a narrow screen. */
.billing-set {
  min-width: 0;
  margin: 0;
  padding: 0;
  border: 0;
}

/* The track. The same pill idiom as .btn and .plan__badge — a 999px radius and
   the 1.5px --rule-strong hairline .plan itself is outlined in, on the card
   surface so it separates from the band's wash. Centred, because the section
   is a .section-head on the centre line and a symmetric grid under it, and the
   toggle belongs on that line rather than beside it.

   width: max-content so the track hugs the two pills instead of spanning the
   shell; max-width and flex-wrap so it becomes two rows rather than an overflow
   on a very narrow screen. */
.billing {
  width: max-content;
  max-width: 100%;
  margin: clamp(1.75rem, 3vw, 2.25rem) auto 0;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 0.25rem;
  padding: 0.3125rem;
  border: 1.5px solid var(--rule-strong);
  border-radius: 999px;
  background: var(--card-bg);
  box-shadow: var(--shadow-sm);
}

/* One option. The border is transparent here rather than absent so that print
   can mark the picked one without the pill changing size on paper. */
.billing__opt {
  display: inline-flex;
  align-items: center;
  gap: 0.4375rem;
  padding: 0.5rem 1.125rem;
  border: 1.5px solid transparent;
  border-radius: 999px;
  font-size: 0.9375rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.2;
  color: var(--ink-2);
  cursor: pointer;
  transition: background-color 0.18s var(--ease), color 0.18s var(--ease);
}

.billing__opt:hover {
  color: var(--ink);
}

/* The picked one is the masthead button's fill: --ink on --paper. NOT --accent
   — an accent-filled pill is a blue block beside a blue-tinted band, and this is
   the one element in the toggle the eye is meant to land on. --ink/--paper is
   also the pair @media print already maps to black on white. */
#billing-monthly:checked ~ .billing .billing__opt--mo,
#billing-annual:checked ~ .billing .billing__opt--yr {
  background: var(--ink);
  color: var(--paper);
}

/* The input is clipped to a pixel by .u-sr, so the ring it would draw is a ring
   round nothing — it goes on the label the input names instead. Offset 2px
   rather than the global 3px, which would put the ring outside the track. */
#billing-monthly:focus-visible ~ .billing .billing__opt--mo,
#billing-annual:focus-visible ~ .billing .billing__opt--yr {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* The reason to pick Annual, as a chip inside the label rather than a second
   line of copy under the pill.

   ⛔ NOT A COUPON STICKER. A solid --accent block of small mono capitals is
   three shouts at once on the smallest element in the band, and it becomes the
   loudest thing in a pricing section whose own heading is quieter. This is the
   same word in the LABEL'S OWN FACE and case, one step down in size, on a wash
   with a hairline round it — a tab on the pill rather than a sticker over it.

   ⛔ THE FIGURE IS 8 AND ROUNDS DOWN. 200 ÷ 2400 is 8.33%; both dollar amounts
   are on the card below for a reader to check, and an overstated discount is an
   advertising problem, not a rounding preference. */
.billing__save {
  padding: 0.25rem 0.5rem 0.3125rem;
  border: 1px solid var(--card-rule-hi);
  border-radius: 999px;
  background: var(--accent-wash);
  color: var(--accent);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1;
  white-space: nowrap;
}

/* Once the pill under it is filled with --ink, an accent chip is a blue on a
   near-black and the wash it rests on goes invisible against the fill. So it
   inverts to a paper tab, which is precisely what .plan__badge does on the
   featured card. The border goes with it: a hairline that separated the chip
   from white paper only draws a seam once the ground is --ink.

   ⛔ NOT rgba(255,255,255,…) for the inverted ground. It is right on screen and
   wrong on paper, where @media print takes --ink to #000 and a white alpha over
   it is the one combination that vanishes. --paper/--ink move together in both
   places. */
#billing-annual:checked ~ .billing .billing__save {
  background: var(--paper);
  border-color: transparent;
  color: var(--ink);
}

/* THE PRICE SWAP. Only Tier 2 has the pair; the other two cards carry a plain
   .plan__price and are untouched in both states.

   The annual half is hidden by its class alone, which makes Monthly the default
   in CSS as well as in the markup, and the ID rule at (1,1,0) is what turns it
   over. Deliberately a hard swap with no transition — display cannot be
   animated, and a cross-fade on a price is a flourish on the one number nobody
   should have to watch settle.

   No width is reserved and none is needed: .cards--3 is minmax(…, 1fr), so the
   three tracks are equal fractions of the shell and "$2,200" cannot widen its
   own column. .plan__price's min-height: 3.25rem holds the vertical. */
.plan__price-yr,
.plan__unit-yr {
  display: none;
}

#billing-annual:checked ~ .plans .plan__price-mo,
#billing-annual:checked ~ .plans .plan__unit-mo {
  display: none;
}

#billing-annual:checked ~ .plans .plan__price-yr {
  display: block;
}

#billing-annual:checked ~ .plans .plan__unit-yr {
  display: inline;
}

/* ------------------------------------------------- documents and facts */

/* security.html, privacy.html and terms.html are long-form reference pages.
   ONE centred reading column with the term above the paragraph it governs,
   never a two-column term/definition split. Keep new document sections on
   .shell.doc. */
.doc {
  max-width: 54rem;
}

/* The column IS the measure now, so the .measure still on these headings would
   only make them wrap early. (0,1,1) beats .measure's (0,1,0). */
.doc .h2 {
  max-width: none;
}

/* .doc--center STOOD HERE and went with its only user, security.html's closing
   contact band. If a centred closing band ever comes back, the modifier needs
   both halves again: text-align on the block AND margin-inline: auto on the
   .measure children, because a capped block with no auto margin sits at its
   container's left edge whatever text-align says. Never centre .doc itself —
   fact lists are scanned down a left edge. */

/* A heading needs air under it whatever follows. `.facts` brings its own and
   nothing else does, so without this a section opening with a lede or a prose
   column runs its first line straight into the heading. */
.doc .h2 + .lede,
.doc .h2 + .prose {
  margin-top: 1.375rem;
}

.facts {
  margin-top: 2.25rem;
}

.fact {
  padding-block: 1.625rem;
  border-top: 1px solid var(--rule);
}

.fact__term {
  font-size: 1.0625rem;
  font-weight: 800;
  letter-spacing: -0.01em;
  line-height: 1.35;
}

.fact__def {
  margin-top: 0.625rem;
  max-width: var(--measure-wide);
  color: var(--ink-2);
}

.fact__def p + p {
  margin-top: 0.75rem;
}

/* ⛔ THERE IS NO ADDRESS BLOCK ANYWHERE ON THIS SITE — no phone, no email, no
   contact list. That is the decision, not an omission to be filled in. */

/* ⛔ NO "Where the responsibility sits" BLOCK. Every word of it is already on
   the page: the surgeon-decides claim, and the footer disclaimer "does not
   diagnose, does not decide" on all four pages, which
   scripts/site_preflight.py checks for by clause. Do not rebuild it. */

/* ⛔ .rule IS GONE. It was an <hr> dividing the closing band's two beats —
   getting set up, and asking a question instead — and the band came off the home
   page with both of them. No page carries an <hr> now; the bands' own edges are
   the only dividers left.

   ⛔ NO CLOSING BYLINE. The hero's .seal makes that claim ONCE, and a longer
   retelling at the foot of the same page makes it twice. */

/* --------------------------------------------------------------- footer */

.colophon {
  border-top: 1px solid var(--rule);
  padding-block: 3rem 3.5rem;
  text-align: center;
}

.colophon__top {
  display: grid;
  gap: 2.5rem;
  justify-items: center;
  padding-bottom: 2.25rem;
  border-bottom: 1px solid var(--rule);
}

.colophon__brand p {
  margin-top: 0.625rem;
  font-size: 0.9375rem;
  color: var(--ink-2);
}

/* ONE CENTRED ROW, never labelled columns: with five links across three groups,
   two of the headings would be furniture over a single link.

   Flex-wrap and not a grid, deliberately: five links of unequal length in equal
   grid tracks are five links with gaps between them that have nothing to do with
   the words. Wrapped, they centre on every line, which is the layout law the rest
   of the page follows.

   ⛔ There is no heading class for a footer group — do not write one to
   reintroduce the columns. */
.colophon__nav {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: 0.75rem clamp(1.25rem, 3vw, 2.25rem);
  width: 100%;
  max-width: 44rem;
}

.colophon__nav a {
  color: var(--ink);
  text-decoration: none;
  font-size: 0.9375rem;
  font-weight: 600;
}

.colophon__nav a:hover {
  color: var(--accent);
  text-decoration: underline;
}

.disclaimer {
  padding-top: 1.75rem;
  font-size: 0.875rem;
  line-height: 1.6;
  color: var(--ink-3);
}

/* THE TWO DISCLAIMERS ARE A BULLETED LIST IN INK.

   ⛔ Never two columns: side by side they read as a layout rather than as two
   things that are both true. And never --ink-3, the furniture colour that dates,
   meta labels and the copyright line use — these are the site's binding
   statements (business/03-entity-and-legal.md §H), so they get --ink like body
   copy.

   Real markers, not a styled span: two <li>s under one <ul> is what the block
   actually is, and the UA's own bullet needs no pseudo-element.

   NOT `display: grid` WITH A GAP. A grid item is blockified, and a blockified
   list item's outside marker is the thing browsers have historically dropped or
   clipped — the safe way to space two <li>s is a margin between them.

   BOTH BULLETS ARE BOLD, and the weight is set HERE rather than by wrapping
   each <li> in a <strong> on four separate pages. The two
   sentences are the site's binding statements and site_preflight.py reads the
   footer as TEXT, so a rule that carries no markup into the clauses is the one
   that cannot break the check. 700 rather than 600: at 14px on --ink a semibold
   is barely a step off the body copy around it. */
.disclaimer__list {
  max-width: 52rem;
  margin-inline: auto;
  padding-left: 1.25rem;
  text-align: left;
  color: var(--ink);
  font-weight: 700;
}

.disclaimer__list li + li {
  margin-top: 0.625rem;
}

.disclaimer p + p {
  margin-top: 0.75rem;
}

/* The page-specific third paragraph carried by security/privacy/terms. */
.disclaimer__note {
  margin-top: 0.75rem;
  max-width: 48rem;
  margin-inline: auto;
}

/* The copyright, and beside it Privacy and Terms. This is where they belong on a
   site that sells software rather than a mailing list: reachable, and no louder
   than the year. */
.disclaimer__mark {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 0.375rem 1rem;
  margin-top: 1.25rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
}

.disclaimer__mark a {
  color: inherit;
  text-decoration: none;
}

.disclaimer__mark a:hover {
  color: var(--accent);
  text-decoration: underline;
}

/* ------------------------------------------------------- the questions */

/* The bubble in the corner and its panel, ported from the get-started surface
   and redrawn in this stylesheet's own idiom — ink-filled action, accent on
   hover, one pill radius, no second palette.

   FIXED, AND ABOVE THE STICKY MASTHEAD (z-index 20) AND THE SKIP LINK (30): a
   panel that slides under the bar reads as a rendering fault. It is the only
   fixed thing on this site besides the masthead, so nothing else is in its way.

   ⛔ THE MARKUP IS ON index.html ALONE. The three document pages carry no #ask
   anchor and do not load ask.js, so none of this paints there. */

/* FIRST, SCOPED, AND !important. The panel, the error slots and the banner are
   all toggled with the hidden attribute, because a script on this site may not
   touch el.style — and an attribute selector LOSES to a class that sets display,
   so `.ask__panel { display: flex }` alone leaves the panel open on arrival.
   That trap has cost this project a release before. Scoped to .ask rather than
   lifted to the document: everything else here hides with a class, and the rest
   of this file is written for that. */
.ask [hidden] {
  display: none !important;
}

.ask {
  position: fixed;
  right: clamp(0.85rem, 0.4rem + 1.5vw, 1.5rem);
  bottom: clamp(0.85rem, 0.4rem + 1.5vw, 1.5rem);
  z-index: 40;
  display: flex;
  /* REVERSE, so the launcher — first in the DOM, where it belongs for the
     keyboard — renders at the bottom with the panel stacked above it. */
  flex-direction: column-reverse;
  align-items: flex-end;
  gap: 0.7rem;
}

/* Drawn as .btn--primary is: --ink filled, --accent on hover. It floats free of
   the layout, so it is allowed the same 2px lift the page's buttons take. */
.ask__launch {
  display: inline-flex;
  align-items: center;
  font-family: var(--font-body);
  font-size: 0.9375rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.2;
  color: var(--paper);
  background: var(--ink);
  border: 1.5px solid var(--ink);
  border-radius: 999px;
  padding: 0.6875rem 1.25rem 0.6875rem 1rem;
  cursor: pointer;
  box-shadow: var(--shadow-md);
  transition: transform 0.18s var(--ease), background-color 0.18s var(--ease),
    border-color 0.18s var(--ease), box-shadow 0.18s var(--ease),
    padding 0.28s var(--ease);
}

.ask__launch:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  transform: translateY(-2px);
  box-shadow: var(--shadow-lift);
}

.ask__launch:active {
  transform: none;
  box-shadow: var(--shadow-md);
}

.ask__glyph {
  display: block;
  line-height: 0;
}

.ask__glyph svg {
  display: block;
  width: 1.15rem;
  height: 1.15rem;
}

/* The launcher stays put while the panel is open — it is the shut control as
   well as the open one, and a button that vanishes under the cursor is a button
   you have to go and find again. Only its label folds away.

   ⛔ THE SPACE BESIDE THE LABEL FOLDS WITH IT. The gap is the word's own margin
   rather than the flex container's, and the open state evens the pill's padding:
   a container gap plus the wider right padding survives the label's collapse and
   leaves the glyph off-centre. */
.ask.is-open .ask__launch {
  padding: 0.6875rem 1rem;
}

.ask.is-open .ask__word {
  max-width: 0;
  opacity: 0;
  overflow: hidden;
  white-space: nowrap;
  margin-left: 0;
}

.ask__word {
  max-width: 8rem;
  opacity: 1;
  margin-left: 0.5rem;
  transition: max-width 0.28s var(--ease), opacity 0.18s var(--ease),
    margin-left 0.28s var(--ease);
}

.ask__panel {
  display: flex;
  flex-direction: column;
  width: min(26rem, calc(100vw - 1.7rem));
  max-height: min(40rem, calc(100vh - 5rem));
  background: var(--card-bg);
  border: 1px solid var(--card-rule);
  border-radius: var(--radius-lg);
  /* The one place --shadow-lift is spent off the featured plan: this panel is
     genuinely floating over the page and has to read that way from the corner of
     the eye. */
  box-shadow: var(--shadow-lift);
  overflow: hidden;
}

/* Blue wash under a blue-tinted hairline — the same "this belongs to the brand"
   move the washed bands make, one step stronger because a floating surface has
   nothing around it to say whose it is. */
.ask__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding: 0.8125rem 0.75rem 0.8125rem 1.125rem;
  border-bottom: 1px solid var(--card-rule-hi);
  background: var(--accent-wash);
}

/* The display face, like every other heading on the site. */
.ask__title {
  margin: 0;
  font-family: var(--font-display);
  font-size: 1.1875rem;
  font-weight: 600;
  letter-spacing: -0.018em;
  line-height: 1.2;
  color: var(--ink);
}

.ask__x {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  width: 2rem;
  height: 2rem;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--ink-3);
  cursor: pointer;
  transition: color 0.18s var(--ease), background-color 0.18s var(--ease);
}

.ask__x svg {
  width: 1.05rem;
  height: 1.05rem;
}

.ask__x:hover {
  color: var(--accent);
  background: var(--card-bg);
}

/* THE PANEL'S ONE SCROLL AREA. It scrolls rather than the panel, because the
   head has to stay put — a panel whose close control scrolls away is a panel
   somebody is trapped in on a short screen. `overscroll-behavior: contain` stops
   a flick at the bottom of the form scrolling the PAGE underneath it. */
.ask__body {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 1.25rem 1.25rem 1rem;
}

/* ⛔ THE FORM VOCABULARY IS ask.js's. .send__field, .send__label, .send__pair,
   .send__actions, .send__go and .send__err are names the script writes and
   reads; renaming one to suit a redrawing is a rename with a script on the other
   end of it. */
.send__form {
  display: flex;
  flex-direction: column;
  gap: 0.8rem;
}

.send__form.is-waiting {
  opacity: 0.7;
}

.send__field {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}

.send__label {
  font-size: 0.8125rem;
  font-weight: 700;
  color: var(--ink);
}

/* ⛔ minmax(0, …), NEVER A BARE 1fr — a grid track's implicit minimum is auto,
   and a long typed practice name would push the panel wider than the screen it
   is fixed to. */
.send__pair {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 0.6rem;
}

@media (max-width: 24rem) {
  .send__pair {
    grid-template-columns: minmax(0, 1fr);
  }
}

.send__form input,
.send__form textarea {
  width: 100%;
  font-family: var(--font-body);
  font-size: 0.9375rem;
  font-weight: 500;
  line-height: 1.5;
  color: var(--ink);
  background: var(--card-bg);
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  padding: 0.5625rem 0.75rem;
  transition: border-color 0.18s var(--ease), box-shadow 0.18s var(--ease);
}

.send__form textarea {
  resize: vertical;
  max-height: 10rem;
}

.send__form input:hover,
.send__form textarea:hover {
  border-color: var(--card-rule-hi);
}

.send__form input:focus,
.send__form textarea:focus {
  outline: 2px solid transparent;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px rgba(53, 96, 159, 0.18);
}

/* ⛔ THE REFUSAL LANDS BESIDE THE BOX IT IS ABOUT, one slot per field. There is
   deliberately no per-box reddening on .is-wrong: the sentence already names the
   control, and reddening the email box on a name refusal points at the wrong
   one. */
.send__err {
  margin: 0;
  font-size: 0.8125rem;
  font-weight: 600;
  color: var(--flag);
}

.send__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-top: 0.15rem;
}

.send__go {
  font-family: var(--font-body);
  font-size: 0.9375rem;
  font-weight: 700;
  letter-spacing: -0.01em;
  line-height: 1.2;
  color: var(--paper);
  background: var(--ink);
  border: 1.5px solid var(--ink);
  border-radius: 999px;
  padding: 0.5625rem 1.375rem;
  cursor: pointer;
  box-shadow: var(--shadow-sm);
  transition: background-color 0.18s var(--ease),
    border-color 0.18s var(--ease), box-shadow 0.18s var(--ease);
}

.send__go:hover {
  background: var(--accent);
  border-color: var(--accent);
  color: #fff;
  box-shadow: var(--shadow-md);
}

.send__go[disabled] {
  opacity: 0.45;
  cursor: default;
  box-shadow: none;
}

/* ⛔ THE CONFIRMATION IS A STATE OF THE PANEL, NOT A NOTE INSIDE IT. A strip
   under a form the reader has already finished with is the shape of a validation
   message; what happened is that a question left the building. Generous on
   purpose — the state that replaces a four-box form with two lines has to feel
   like an arrival rather than like a form that failed to render. */
.ask__done {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 1.75rem 0.75rem 2.25rem;
}

.ask__done-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3.5rem;
  height: 3.5rem;
  margin-bottom: 1.25rem;
  border-radius: 50%;
  background: rgba(31, 106, 63, 0.1);
  color: var(--ok);
}

.ask__done-mark svg {
  display: block;
  width: 1.9rem;
  height: 1.9rem;
}

.ask__done-title {
  margin: 0 0 0.4rem;
  font-family: var(--font-display);
  font-size: 1.375rem;
  font-weight: 600;
  letter-spacing: -0.02em;
  color: var(--ink);
}

.ask__done-text {
  margin: 0;
  max-width: 20rem;
  font-size: 0.9375rem;
  line-height: 1.55;
  color: var(--ink-2);
}

.ask__alert {
  margin: 0 1.25rem 1rem;
  padding: 0.7rem 0.85rem;
  border: 1px solid var(--flag);
  border-radius: var(--radius);
  background: rgba(163, 42, 40, 0.06);
  font-size: 0.8125rem;
  color: var(--ink);
}

/* ⛔ OFF-CANVAS, NOT display: none. A hidden-by-display input is skipped by most
   form fillers, which is the one thing this box exists to catch. It is out of
   the tab order and aria-hidden in the markup, so nobody who is not a script can
   reach it. */
.ask__pot {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* Nearly the whole screen on a phone, anchored to the edges rather than floated
   over them — a 26rem card with margins on a 390px screen wastes the only space
   there is. The launcher keeps its corner: nothing else on this site is fixed,
   so it can only ever collide with the page's own scroll. */
@media (max-width: 30rem) {
  .ask.is-open {
    left: 0.6rem;
    right: 0.6rem;
    bottom: 0.6rem;
  }

  .ask.is-open .ask__panel {
    width: 100%;
    max-height: calc(100vh - 5rem);
  }
}

@media (prefers-reduced-motion: no-preference) {
  .ask__panel {
    animation: ask-in 0.26s var(--ease) both;
  }

  /* The confirmation arrives on its own beat, a shade slower than the panel,
     because it replaces something rather than appearing from nothing. */
  .ask__done {
    animation: ask-done-in 0.34s var(--ease) both;
  }
}

@keyframes ask-in {
  from {
    opacity: 0;
    transform: translateY(12px) scale(0.98);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes ask-done-in {
  from {
    opacity: 0;
    transform: translateY(8px);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* --------------------------------------------------------------- motion */

/* .reveal is INERT until motion.js adds .js-motion to <html>. That ordering is
   the whole safety property: with JS blocked, broken or still loading, nothing
   is hidden and the page reads exactly as it would have. Never move the
   opacity: 0 out from under .js-motion. */
.js-motion .reveal {
  opacity: 0;
  transform: translateY(18px);
}

.js-motion .reveal.is-in {
  opacity: 1;
  transform: none;
  transition: opacity 0.7s var(--ease), transform 0.7s var(--ease);
}

/* THE HERO IS NEVER HIDDEN, and this is a Largest Contentful Paint rule rather
   than a taste one: AN ELEMENT AT opacity: 0 IS NOT AN LCP CANDIDATE AT ALL, so
   hiding the hero's h1 defers this page's LCP behind motion.js and 0.7s of
   transition. The measurement behind that is in web/SITE-DESIGN.md.

   ⛔ (0,3,0), the same weight as the .is-in rule above, so SOURCE ORDER is the
   only thing making this win — IT MUST STAY AFTER IT. Still under .js-motion, so
   it fails open exactly as the rest of this block does, and @media print already
   forces every .reveal visible regardless. */
.js-motion .band--hero .reveal {
  opacity: 1;
  transform: none;
}

/* Stagger by position within a grid, so a row of cards arrives as a sweep
   rather than all at once. nth-child rather than a per-element variable,
   because setting one would mean an inline style the CSP refuses.

   ⛔ THE LADDER HAS TO REACH THE LONGEST GRID ON THE PAGE. It stopped at four
   while the specimen grid held six, so cards 5 and 6 fell through to the 0s rule
   and arrived BEFORE cards 2 to 4 — a sweep that runs out of order reads as a
   rendering fault, not as motion. Six is the count today (.readings); add a step
   here for any grid that grows past it. */
.js-motion .cards > .reveal.is-in {
  transition-delay: 0s;
}

.js-motion .cards > .reveal:nth-child(2).is-in {
  transition-delay: 0.09s;
}

.js-motion .cards > .reveal:nth-child(3).is-in {
  transition-delay: 0.18s;
}

.js-motion .cards > .reveal:nth-child(4).is-in {
  transition-delay: 0.27s;
}

.js-motion .cards > .reveal:nth-child(5).is-in {
  transition-delay: 0.36s;
}

.js-motion .cards > .reveal:nth-child(6).is-in {
  transition-delay: 0.45s;
}

@media (prefers-reduced-motion: no-preference) {
  .ambient__wash {
    animation: drift 26s var(--ease) infinite alternate;
  }

  .ambient__wash--b {
    animation-duration: 32s;
    animation-delay: -8s;
  }
}

@keyframes drift {
  from {
    transform: translate3d(0, 0, 0) scale(1);
  }
  to {
    transform: translate3d(3%, 4%, 0) scale(1.12);
  }
}

/* Belt and braces: motion.js already refuses to add .js-motion when the user
   asks for less motion, so this only matters if the setting changes mid-visit. */
@media (prefers-reduced-motion: reduce) {
  .js-motion .reveal {
    opacity: 1;
    transform: none;
  }

  .card:hover,
  .btn:hover,
  .ask__launch:hover {
    transform: none;
  }

  /* The carousel STAYS — it is how the section is navigated — but it stops
     moving on its own and stops sliding. reel.js already refuses to autoplay
     here; this is what makes the artwork and the change of slide instant. */
  .is-reel .reel__track {
    transition: none;
  }

  .is-reel .rl-draw { stroke-dashoffset: 0; }
  .is-reel .rl-fade { opacity: 1; }
  .is-reel .rl-write { clip-path: none; }
  .is-reel .rl-blk { opacity: 1; transform: none; }
  .is-reel .rl-row { opacity: 1; transform: none; }

  .is-reel .is-live .rl-draw,
  .is-reel .is-live .rl-fade,
  .is-reel .is-live .rl-write,
  .is-reel .is-live .rl-blk,
  .is-reel .is-live .rl-row,
  .is-reel .is-live .rl-sweep {
    animation: none;
  }
}

/* ---------------------------------------------------------------- print */

@media print {
  :root {
    --paper: #fff;
    --paper-2: #fff;
    --paper-veil: #fff;
    --ink: #000;
    --ink-2: #222;
    --ink-3: #444;
    --rule: #bbb;
    --rule-strong: #888;
    --accent: #000;
    --accent-wash: #fff;
    --card-bg: #fff;
    --card-rule: #bbb;
    --card-rule-hi: #888;
    --shadow-sm: none;
    --shadow-md: none;
    --shadow-lift: none;

    --fill-bg: #fff;
    --fill-ink: #000;
    --fill-soft: #444;
    --fill-rule: #888;
    --plan-edge: #888;
  }

  .masthead {
    position: static;
    background: #fff;
  }

  /* .ask is furniture that floats over the page: printed, it would land on top
     of whatever paragraph happened to be at the foot of the sheet. */
  .masthead__nav,
  .actions,
  .skip,
  .ambient,
  .frame,
  .ask {
    display: none;
  }

  /* ⛔ NO PRINTED CALL TO ACTION ANY MORE. The closing band carried the one set
     of .actions that printed — flat, with `attr(href)` after the label, because
     a button prints as a black pill leading nowhere — and the band came off the
     home page. What reaches paper now is the masthead's Get started button, as a
     pill with no address on it. If a printed page is ever meant to carry the URL
     again, that button is the one to write the rule against. */

  .band {
    padding-block: 1.5rem;
  }

  /* The fill prints as #fff, so without the border this card has no edge at all.
     The lift is a screen device — on paper it is one card hanging off the
     row. */
  .plan--featured {
    border: 1px solid #888;
    margin-block: 0;
    padding-block: clamp(1.25rem, 2vw, 1.75rem);
  }

  /* --fill-ink is #000 on paper, so the badge would print as a solid black
     lozenge. Inverted, it is a bordered white tab. */
  .plan__badge {
    background: #fff;
    color: #000;
    border: 1px solid #888;
  }

  /* The billing toggle prints, because which term a price is quoted on is part
     of the price — and the swap itself still works on paper, so a page printed
     with Annual picked prints $2,200. Same inversion as .plan__badge for the
     same reason: --ink is #000 and --paper #fff here, so the picked pill would
     be a solid black lozenge, and it would vanish outright in a browser set to
     drop backgrounds. It prints as a bordered tab; the un-picked one keeps its
     word and loses its box rather than printing as an empty outline. */
  .billing__opt {
    background: none;
    color: #000;
  }

  #billing-monthly:checked ~ .billing .billing__opt--mo,
  #billing-annual:checked ~ .billing .billing__opt--yr {
    background: #fff;
    color: #000;
    border-color: #000;
  }

  .billing__save {
    background: #fff;
    color: #000;
    border-color: #888;
  }

  /* Whatever the observer has or has not reached, it all prints. */
  .js-motion .reveal {
    opacity: 1;
    transform: none;
  }

  /* ⛔ --ok AND --flag ARE DELIBERATELY ABSENT from the print :root above: the
     drawings keep their green and red on paper, and that is the point, not an
     omission. Nothing needs to restate them for the artefact drawings either —
     there is one palette, so a `.fg { --ok: …; --flag: … }` here would set the
     tokens to themselves. */

  /* A carousel on paper is one slide and three that vanished. On print it goes
     back to being what it is with the script off: four stacked cards, with the
     artwork drawn and the controls gone. */
  .is-reel .reel__stage {
    overflow: visible;
    border: 0;
    box-shadow: none;
  }

  .is-reel .reel__track {
    display: grid;
    gap: 1rem;
    transform: none;
  }

  .is-reel .reel__slide {
    border: 1px solid #bbb;
    border-radius: 8px;
    break-inside: avoid;
  }

  .is-reel .reel__bar {
    display: none;
  }

  .is-reel .rl-draw { stroke-dashoffset: 0; }
  .is-reel .rl-fade { opacity: 1; }
  .is-reel .rl-blk { opacity: 1; transform: none; }
  .is-reel .rl-row { opacity: 1; transform: none; }

  /* A highlighter that prints as a grey block eats the words under it. */
  .hl {
    background: none;
    border-bottom: 2px solid #888;
  }

  a {
    text-decoration: none;
  }
}
