/* ==========================================================================
   UCOFL Design System — "Precision, warmly delivered"
   Urological Consultants of Florida · miamiurologyconsultants.com rebuild
   v2.0 · 2026-07-18 · Contract: notes/DESIGN-SYSTEM.md
   One stylesheet sitewide. Link as css/ucofl.css?v=3 — bump ?v on change.
   v2: official brand palette, real logo, editorial
   testimonials, consult panel, interactive buttons, static font weights.
   v3 (2026-11): Concept C "Signal" rebrand — slate / dark slate / orange /
   light blue / U-mark blue, inline wordmark lockup, orange primary CTA.
   ==========================================================================
   CONTENTS
   1. Tokens          2. Base & typography     3. A11y (skip/focus/motion)
   4. Layout          5. Header & nav          6. Buttons
   7. Sections/bands  8. Hero cinema           9. Trust strip
   10. Service rows   11. Cards & team + stats 12. Testimonials
   13. Forms          14. Location/NAP         15. Media & placeholders
   16. Brand lockup   17. Footer               18. Reveal motion
   ========================================================================== */

/* 1. TOKENS ------------------------------------------------------------- */
:root {
  /* color — all AA-verified (ratios computed 2026-11-10, see DESIGN-SYSTEM.md §2). Use tokens, never raw hex. */
  --paper: #ffffff;
  --paper-2: #f5f7f8;
  --ink: #0b0f12;          /* 19.24:1 paper · 17.91:1 paper-2 */
  --ink-2: var(--slate-dark); /* secondary text = brand dark slate — 10.31:1 paper · 9.59:1 paper-2 */
  --ink-3: var(--slate);   /* muted/meta text = brand slate — 6.15:1 paper · 5.72:1 paper-2 (floor, do not lighten) */
  --line: #e3e8eb;
  --line-dark: rgba(255, 255, 255, 0.14);

  /* OFFICIAL BRAND COLORS (Concept C "Signal", 2026-11) + AA-derived companions */
  --slate: #58636C;        /* brand slate (wordmark letters) — 6.15:1 paper · 5.72:1 paper-2 · 5.49:1 #edf3f6 ✓ */
  --slate-dark: #36424B;   /* brand dark slate — dark bands + footer. white 10.31:1 · --mist 6.94:1 · --blue-air 7.12:1 · --amber(dark) 5.75:1 ✓ */
  --midnight: #1E262C;     /* deep slate (same hue as --slate-dark, L 15%) — hero base, cinema, night panels.
                              white 15.34:1 · --mist 10.33:1 · --blue-air 10.60:1 · --amber(dark) 8.56:1 ✓ · --blue 3.01:1 (graphic only) */
  --mist: #CDD5DB;         /* body text on dark — 6.94:1 slate-dark · 10.33:1 midnight ✓ */
  --blue: #3A7496;         /* U-mark blue — links, eyebrows, focus rings, graphics on LIGHT.
                              5.10:1 paper · 4.75:1 paper-2 · 4.55:1 #edf3f6 · 4.68:1 #fdf3f2 · 4.76:1 #f2f8fb ✓ (floor — never lighten)
                              Re-scoped to --blue-air inside dark surfaces (see "dark scopes" below). */
  --blue-text: var(--blue); /* legacy alias — 84 call sites, leave them. NOTE: resolved at :root, so it
                              stays #3A7496 everywhere (it is NOT re-scoped on dark; dark call sites use --blue). */
  --blue-air: #B9DCEC;     /* brand light blue (name line on dark) — eyebrows/labels/focus on dark. 7.12:1 slate-dark · 10.60:1 midnight ✓ */
  --amber: #F0A03C;        /* brand orange (light surfaces) — GRAPHIC / SURFACE accent. 2.14:1 paper ✗ never text-on-light.
                              AS A SURFACE: --ink on it 8.98:1 ✓ (primary CTA). Re-scoped to #F7B55A inside dark surfaces. */
  --amber-hover: #E8932A;  /* primary CTA hover surface — --ink on it 7.92:1 ✓ */
  --amber-line: #B46A12;   /* 2px wipes/hairlines/ticks on light — 4.19:1 paper · 3.90:1 paper-2 (≥3:1 non-text ✓) */
  --amber-text: #8F5410;   /* AA orange for small text/marks on light — 6.10:1 paper · 5.68:1 paper-2 ✓ */
  --signal: var(--amber-line); /* SIGNAL icon square on light — 4.19:1 paper · 3.90:1 paper-2 (≥3:1 non-text ✓) */
  --error: #b3261e;

  /* type */
  --font-sans: "Switzer", -apple-system, BlinkMacSystemFont, "Helvetica Neue",
    "Segoe UI", Arial, sans-serif;
  --fs-display-1: clamp(2.875rem, 8vw, 6.75rem);
  --fs-display-2: clamp(2rem, 4.5vw, 3.625rem);
  --fs-h3: clamp(1.375rem, 2.4vw, 2rem);
  --fs-h4: 1.125rem;
  --fs-lead: clamp(1.125rem, 1.7vw, 1.375rem);
  --fs-body: 1.0625rem;
  --fs-small: 0.9375rem;
  --fs-label: 0.75rem;
  --fs-micro: 0.8125rem;

  /* layout */
  --container: 1280px;
  --container-narrow: 800px;
  --gutter: clamp(1.25rem, 4vw, 2.5rem);
  --grid-gap: clamp(1rem, 2.5vw, 2rem);
  --space-section: clamp(6rem, 12vw, 10rem);
  --header-h: 72px;

  /* misc */
  --radius: 16px;
  --radius-input: 10px;
  --shadow-lift: 0 24px 48px -24px rgba(11, 15, 18, 0.18);
  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);
}

/* DARK SCOPES — the brand's two "on dark" colours. Every element that paints a
   dark surface re-scopes the accent tokens so descendants pick the right pair. */
.band--dark, .band--navy, .site-footer, .hero, .page-hero--cinema, .page-hero--split,
.gfx--night, .gfx__view--night, .vx-creds, .media-band, .press-stage, .fit-band {
  --amber: #F7B55A;        /* 5.75:1 on slate-dark · 8.56:1 on midnight ✓ (large text/graphic); --ink on it 10.73:1 */
  --amber-hover: #F3AE5C;  /* --ink on it 10.10:1 */
  --blue: var(--blue-air); /* links/eyebrows/focus inside dark read light blue */
  --signal: var(--amber);  /* SIGNAL icon square on dark — #F7B55A, 8.56:1 midnight · 5.75:1 slate-dark */
}

/* self-hosted type — owner drops files into assets/fonts/ (Fontshare: Switzer) */
/* Switzer static weights (Fontshare, free commercial license). Design uses ~400–600;
   the variable file can replace these later. Non-standard weights (460/480/560) snap to nearest. */
@font-face { font-family: "Switzer"; src: url("../assets/fonts/Switzer-400.woff2") format("woff2"); font-weight: 400; font-display: swap; font-style: normal; }
@font-face { font-family: "Switzer"; src: url("../assets/fonts/Switzer-500.woff2") format("woff2"); font-weight: 500; font-display: swap; font-style: normal; }
@font-face { font-family: "Switzer"; src: url("../assets/fonts/Switzer-600.woff2") format("woff2"); font-weight: 600; font-display: swap; font-style: normal; }
@font-face { font-family: "Switzer"; src: url("../assets/fonts/Switzer-700.woff2") format("woff2"); font-weight: 700; font-display: swap; font-style: normal; }
/* Outfit Medium — ONE subset (Latin-1, kern), used only for the brand name line in the lockup. OFL. */
@font-face { font-family: "Outfit"; src: url("../assets/fonts/Outfit-500.woff2") format("woff2"); font-weight: 500; font-display: swap; font-style: normal; }

/* 2. BASE & TYPOGRAPHY --------------------------------------------------- */
*,
*::before,
*::after { box-sizing: border-box; }

/* The `hidden` attribute must actually hide. The UA rule is [hidden]{display:none},
   but ANY author rule that sets `display` beats it — so `.form-state{display:flex}`
   silently un-hid the form's error banner, and every visitor saw "We couldn't send
   your message just now" on page load, before typing anything. Found 2026-08-01.
   This is the global guard; do not patch [hidden] per-component again (the one-off
   `.lightbox[hidden]` rule below is the earlier, narrower version of this same bug). */
[hidden] { display: none !important; }

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

body {
  margin: 0;
  font-family: var(--font-sans);
  font-size: var(--fs-body);
  font-weight: 400;
  line-height: 1.65;
  color: var(--ink);
  background: var(--paper);
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, p, ul, ol, figure, blockquote { margin: 0; }

h1 {
  font-size: var(--fs-display-1);
  font-weight: 500; /* snapped to real Switzer-500.woff2 (static weights 400/500/600/700) */
  line-height: 1.02;
  letter-spacing: -0.035em;
  max-width: 14ch;
  text-wrap: balance;
}

h2 {
  font-size: var(--fs-display-2);
  font-weight: 500;
  line-height: 1.08;
  letter-spacing: -0.025em;
  max-width: 24ch;
  text-wrap: balance;
}

h3 {
  font-size: var(--fs-h3);
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: -0.015em;
}

h4 {
  font-size: var(--fs-h4);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.005em;
}

p { max-width: 64ch; }

.lead {
  font-size: var(--fs-lead);
  line-height: 1.5;
  color: var(--ink-2);
  max-width: 52ch;
}

.small { font-size: var(--fs-small); color: var(--ink-3); }
.micro { font-size: var(--fs-micro); color: var(--ink-3); line-height: 1.55; }

.eyebrow {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--blue-text);
  margin-bottom: 1.25rem;
}

a { color: var(--blue-text); text-underline-offset: 3px; }
a:hover { color: var(--ink); }

main a:not([class]) { text-decoration: underline; } /* color never sole signal */

img, video { max-width: 100%; height: auto; display: block; }

::selection { background: var(--blue-air); color: var(--ink); }   /* 13.30:1 */

/* 3. ACCESSIBILITY ------------------------------------------------------- */
.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 200;
  background: var(--ink);
  color: #fff;
  padding: 14px 24px;
  border-radius: 0 0 12px 0;
  text-decoration: none;
  font-size: var(--fs-small);
}
.skip-link:focus {
  left: 0;
  color: #fff;
}

:focus-visible {
  outline: 2px solid var(--blue-text);
  outline-offset: 3px;
  border-radius: 4px;
}
.band--dark :focus-visible,
.band--navy :focus-visible,
.hero :focus-visible,
.site-footer :focus-visible { outline-color: var(--blue); }  /* --blue-air via the dark scope */

.vh { /* visually hidden, present for AT + heading hierarchy */
  position: absolute !important;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .reveal { opacity: 1 !important; transform: none !important; }
}

/* 4. LAYOUT -------------------------------------------------------------- */
.container {
  max-width: var(--container);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.container--narrow { max-width: var(--container-narrow); }

.grid {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: var(--grid-gap);
}
.col-4 { grid-column: span 4; }
.col-5 { grid-column: span 5; }
.col-6 { grid-column: span 6; }
.col-7 { grid-column: span 7; }
.col-8 { grid-column: span 8; }
.col-12 { grid-column: span 12; }

@media (max-width: 900px) {
  .grid > [class^="col-"] { grid-column: span 12; }
}

.section { padding-block: var(--space-section); }
.section--tight { padding-block: calc(var(--space-section) * 0.55); }

.section-head { margin-bottom: clamp(3rem, 6vw, 5rem); }
.section-head .lead { margin-top: 1.5rem; }

/* 5. HEADER & NAV -------------------------------------------------------- */
.site-header {
  position: fixed;
  inset: 0 0 auto 0;
  z-index: 100;
  height: var(--header-h);
  background: rgba(255, 255, 255, 0.82);
  -webkit-backdrop-filter: blur(16px);
  backdrop-filter: blur(16px);
  border-bottom: 1px solid transparent;
  transition: border-color 240ms var(--ease);
}
.site-header.is-scrolled { border-bottom-color: var(--line); background: rgba(255, 255, 255, 0.92); }

.header-bar {
  height: var(--header-h);
  display: flex;
  align-items: center;
  gap: clamp(1rem, 3vw, 3rem);
}


.primary-nav { margin-left: auto; }
.primary-nav > ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  align-items: center;
  gap: clamp(0.25rem, 1.5vw, 1rem);
}
.primary-nav a,
.primary-nav .nav-drop > button {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  min-height: 44px;
  padding: 0 12px;
  font-family: var(--font-sans);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
  text-decoration: none;
  background: none;
  border: 0;
  cursor: pointer;
  border-radius: 8px;
}
.primary-nav a:hover,
.primary-nav .nav-drop > button:hover { color: var(--ink); }

/* orange underline-wipe on desktop nav (decorative accent — text color stays AA ink) */
@media (min-width: 901px) {
  .primary-nav > ul > li > a,
  .primary-nav .nav-drop > button {
    background-image: linear-gradient(var(--amber-line), var(--amber-line));
    background-repeat: no-repeat;
    background-position: 12px calc(100% - 8px);
    background-size: 0 2px;
    transition: background-size 260ms var(--ease);
  }
  .primary-nav > ul > li > a:hover,
  .primary-nav > ul > li > a[aria-current="page"],
  .primary-nav .nav-drop > button:hover,
  .primary-nav .nav-drop > button[aria-expanded="true"] {
    background-size: calc(100% - 24px) 2px;
  }
}

/* Language switch (English <-> Español), the last item of the primary nav on every
   page that has a counterpart in the other language (tools/i18n/i18n.py pair).
   Desktop: a hairline divider sets it apart from the section links. Mobile: it is
   simply the last row of the menu. Footer: .foot-lang, every size. */
.primary-nav > ul > .nav-lang a { letter-spacing: 0.12em; }
/* The desktop header bar has no room left for it (it would squeeze the booking
   button), so on desktop the switch lives in the footer's legal row instead. */
@media (min-width: 901px) {
  .primary-nav > ul > .nav-lang { display: none; }
}
.footer-legal .foot-lang { font-weight: 600; }

.nav-drop { position: relative; }
.nav-drop > button::after {
  content: "";
  width: 7px; height: 7px;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translateY(-2px);
  transition: transform 200ms var(--ease);
}
.nav-drop > button[aria-expanded="true"]::after {
  transform: rotate(-135deg) translateY(-2px);
}

.nav-panel {
  position: absolute;
  top: calc(100% + 10px);
  left: 0;
  min-width: 260px;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lift);
  padding: 8px;
  list-style: none;
  margin: 0;
  display: none;
}
.nav-drop > button[aria-expanded="true"] + .nav-panel { display: block; }
.nav-panel a {
  display: flex;
  min-height: 44px;
  align-items: center;
  padding: 0 16px;
  font-size: var(--fs-small);
  font-weight: 500;
  letter-spacing: 0;
  text-transform: none;
  border-radius: 10px;
}
.nav-panel a:hover { background: var(--paper-2); color: var(--ink); }

.header-phone {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  padding: 0 10px;
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.header-phone:hover { color: var(--blue); }

.nav-toggle {
  display: none;
  width: 44px; height: 44px;
  margin-left: auto;
  background: none;
  border: 0;
  cursor: pointer;
  padding: 10px;
  border-radius: 10px;
}
.nav-toggle .bars,
.nav-toggle .bars::before,
.nav-toggle .bars::after {
  content: "";
  display: block;
  width: 24px; height: 2px;
  background: var(--ink);
  transition: transform 240ms var(--ease), opacity 200ms;
}
.nav-toggle .bars { position: relative; }
.nav-toggle .bars::before { position: absolute; top: -7px; }
.nav-toggle .bars::after { position: absolute; top: 7px; }
body.menu-open .nav-toggle .bars { transform: rotate(45deg); }
body.menu-open .nav-toggle .bars::before { transform: translateY(7px) rotate(-90deg); }
body.menu-open .nav-toggle .bars::after { opacity: 0; }

@media (max-width: 900px) {
  :root { --header-h: 64px; }
  .header-phone { display: none; }
  /* .header-cta stays visible on mobile — persistent conversion CTA (deliberate) */
  .header-cta { padding: 0 16px; }
  .nav-toggle { display: block; }

  .primary-nav {
    position: fixed;
    inset: var(--header-h) 0 0 0;
    background: var(--paper);
    display: none;
    overflow-y: auto;
    padding: 2rem var(--gutter) 4rem;
  }
  body.menu-open .primary-nav { display: block; }
  body.menu-open { overflow: hidden; }
  /* backdrop-filter creates a containing block that would trap the fixed
     overlay inside the 64px header — drop it while the menu is open */
  body.menu-open .site-header {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    background: var(--paper);
  }

  .primary-nav > ul {
    flex-direction: column;
    align-items: stretch;
    gap: 0;
  }
  .primary-nav > ul > li { border-bottom: 1px solid var(--line); }
  .primary-nav a,
  .primary-nav .nav-drop > button {
    width: 100%;
    min-height: 60px;
    font-size: 1.25rem;
    font-weight: 500;
    letter-spacing: -0.01em;
    text-transform: none;
    padding: 0;
    justify-content: space-between;
  }
  .nav-panel {
    position: static;
    border: 0;
    box-shadow: none;
    padding: 0 0 12px;
    min-width: 0;
  }
  .nav-panel a { min-height: 48px; color: var(--ink-2); font-size: 1.0625rem; }
  .menu-extras { display: block; padding-top: 2rem; }
  /* re-assert pill styling — the .primary-nav a rule above outranks .btn--primary */
  .primary-nav .menu-extras a.btn {
    width: 100%;
    min-height: 52px;
    justify-content: center;
    margin-bottom: 1rem;
    padding: 0 28px;
    /* the orange primary pill, re-asserted (ink text on #F0A03C = 8.98:1) */
    background-color: var(--amber);
    color: var(--ink);
    border-radius: 999px;
    font-size: var(--fs-label);
    font-weight: 600;
    letter-spacing: 0.14em;
    text-transform: uppercase;
  }
  .menu-extras .header-phone { display: inline-flex; font-size: 1.125rem; }
}
@media (min-width: 901px) {
  .menu-extras { display: none; }
}

/* 6. BUTTONS ------------------------------------------------------------- */
.btn {
  position: relative;
  overflow: hidden;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 48px;
  padding: 0 28px;
  border-radius: 999px;
  border: 1px solid transparent;
  font-family: var(--font-sans);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color 220ms var(--ease), color 220ms var(--ease),
    border-color 220ms var(--ease), transform 220ms var(--ease),
    box-shadow 220ms var(--ease), background-position 520ms var(--ease);
}
.btn--sm { min-height: 44px; padding: 0 22px; }

/* micro-interaction: a quiet light-sweep on hover (skipped under reduced motion) */
.btn::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(105deg, transparent 38%,
    rgba(255, 255, 255, 0.16) 50%, transparent 62%);
  transform: translateX(-101%);
  pointer-events: none;
}
.btn:hover::after { transform: translateX(101%); transition: transform 650ms var(--ease); }

/* micro-interaction: an orange tick that breathes in from the left edge */
.btn::before {
  content: "";
  position: absolute;
  left: 0;
  top: 50%;
  width: 3px;
  height: 0;
  transform: translateY(-50%);
  background: var(--amber);
  border-radius: 0 2px 2px 0;
  transition: height 220ms var(--ease);
  pointer-events: none;
}
.btn:hover::before, .btn:focus-visible::before { height: 55%; }

/* ── Primary CTA: the orange pill (Concept C, 2026-11) ───────────────────────
   Brand orange surface with --ink text: 8.98:1 on #F0A03C (light), 10.73:1 on
   #F7B55A (inside dark scopes). Hover darkens the orange and lifts. The pill
   beside the wordmark's orange O is an intended rhyme. */
.btn--primary {
  background: var(--amber);           /* #F0A03C on light, #F7B55A inside dark scopes */
  color: var(--ink);                  /* 8.98:1 light · 10.73:1 dark */
  border-color: transparent;
}
.btn--primary:hover, .btn--primary:focus-visible {
  background: var(--amber-hover);     /* 7.92:1 / 10.10:1 */
  color: var(--ink);
  transform: translateY(-2px);
  box-shadow: 0 16px 34px -16px rgba(11, 15, 18, 0.45);
}
.btn--primary:active { transform: translateY(0); box-shadow: none; }
/* ink label must survive context link colours (`.band--dark a { color:#fff }` = 0,1,1
   outranks .btn--primary; white on orange is 1.79:1) */
.btn.btn--primary, .btn.btn--primary:hover, .btn.btn--primary:focus-visible { color: var(--ink); }
/* the 3px left-edge orange tick is the accent for OUTLINE buttons; on the
   orange pill it would vanish */
.btn--primary::before { display: none; }

.btn--ghost { background: transparent; color: var(--ink); border-color: var(--ink); }
.btn--ghost:hover { background: var(--ink); color: #fff; transform: translateY(-2px); }
.btn--ghost:active { transform: translateY(0); }

/* the arrow inside any button nudges + warms on hover */
.btn .arrow { transition: transform 220ms var(--ease), color 220ms var(--ease); }
.btn:hover .arrow { transform: translateX(4px); color: var(--amber); }
/* orange arrow on an orange pill is 1.1:1 — the arrow stays ink there */
.btn--primary .arrow, .btn--primary:hover .arrow { color: var(--ink); }

/* on dark bands the primary stays the orange pill: the dark scope re-points
   --amber to #F7B55A and --ink on it is 10.73:1 (amber as TEXT on white would be
   2.14:1 — it is never text-on-light). Ghost buttons go white-outline. */
.band--dark .btn--ghost,
.hero .btn--ghost { color: #fff; border-color: rgba(255, 255, 255, 0.6); }
.band--dark .btn--ghost:hover,
.hero .btn--ghost:hover { background: #fff; color: var(--ink); }

/* a11y: orange arrow only where the hovered button surface is DARK (ink fill).
   The primary pill is orange and ghost buttons on dark fill white on hover —
   orange there = 1.1–2.14:1 FAIL. Use ink on those fills (≥8.98:1 ✓). */
.band--dark .btn--primary:hover .arrow,
.band--navy .btn--primary:hover .arrow,
.hero .btn--primary:hover .arrow,
.band--dark .btn--ghost:hover .arrow,
.band--navy .btn--ghost:hover .arrow,
.hero .btn--ghost:hover .arrow { color: var(--ink); }

.btn--link {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 44px;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--blue-text);
  text-decoration: none;
  background-image: linear-gradient(var(--amber-line), var(--amber-line));
  background-repeat: no-repeat;
  background-position: 0 calc(100% - 6px);
  background-size: 0 2px;
  transition: background-size 260ms var(--ease), color 200ms var(--ease);
}
.btn--link:hover { background-size: 100% 2px; color: var(--ink); }
.btn--link .arrow { transition: transform 200ms var(--ease), color 200ms var(--ease); }
.btn--link:hover .arrow { transform: translateX(4px); color: var(--amber-text); }
.band--dark .btn--link { color: #fff; }
.band--dark .btn--link:hover { color: #fff; }
.band--dark .btn--link:hover .arrow { color: var(--amber); }

/* 7. SECTIONS / BANDS ---------------------------------------------------- */
.band--soft { background: var(--paper-2); }

.band--dark, .band--navy { background: var(--slate-dark); color: var(--mist); }  /* --navy retired: .band--navy is an alias */
.band--dark h2, .band--dark h3,
.band--navy h2, .band--navy h3 { color: #fff; }
.band--dark .eyebrow, .band--navy .eyebrow { color: var(--blue-air); }
.band--dark .lead, .band--navy .lead { color: var(--mist); }
.band--dark a, .band--navy a { color: #fff; }
.band--dark .micro, .band--navy .micro,
.band--dark .small, .band--navy .small { color: var(--mist); }

.hairline { border: 0; border-top: 1px solid var(--line); margin: 0; }
.band--dark .hairline, .band--navy .hairline { border-top-color: var(--line-dark); }

/* 8. HERO CINEMA --------------------------------------------------------- */
.hero {
  position: relative;
  min-height: min(100svh, 960px);
  display: flex;
  align-items: flex-end;
  color: #fff;
  background: var(--midnight);
  overflow: hidden;
}
.hero-media {
  position: absolute;
  inset: 0;
  margin: 0;
}
.hero-media video,
.hero-media img,
.hero-media .ph {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
}
/* keep the placeholder label out of the H1's way until real media lands */
.hero-media .ph span {
  position: absolute;
  top: calc(var(--header-h) + 1.25rem);
  right: 1.5rem;
  padding: 0;
}
/*  SCRIM STRENGTH, set 2026-08-15 from measurement, not taste.
    The owner asked for 50% brighter, desktop and mobile. On the brightest clip on the
    site that is not available: projected at 0.50 scale it measures 2.81 / 2.80 / 2.86
    against a 4.5 floor — a clear AA failure, not a marginal one. Measured ceiling on
    that clip: 0.75 gives 4.68 (thin), 0.80 gives 5.18 (comfortable).

    So the tiers now diverge instead of moving together, because the limit belongs to
    the footage rather than to the component:
      --bright  x0.80  — the harbour, golf and family clips. 20% lighter; the most the
                         brightest of them can carry with real margin.
      standard  x0.62  — genuinely dark footage. The swimmer measured 14.0 / 11.6 / 12.1
                         under the bright scrim, so it was massively over-protected. It
                         moves back to this tier and gains far more than 50%.
    Anything brighter than this on the bright tier ships text that fails to be readable
    over its own hero, which is the one thing a scrim exists to prevent. */
.hero-media::after {
  /* ── LOCALISED SCRIM (rewritten 2026-08-15) ──────────────────────────────
     The scrim used to be one flat wash over the whole frame, and chasing WCAG
     on bright footage pushed it from 0.28 to 0.60 — at which point the owner
     said, correctly, that the videos looked too dark. Darkening the entire
     picture to protect text that occupies a third of it is the wrong trade:
     §0.3 asks for cinema, and a muddy frame is not cinema.

     So the wash is now aimed. Hero text is left-aligned and bottom-anchored
     (the section is `align-items: flex-end`), so a horizontal gradient carries
     the weight on the left and clears by ~72%, and a much lighter vertical
     grade holds the top edge and the bottom. On these clips the subject sits
     centre-right, which is exactly the part that now stays bright.

     Result: the pixels behind the text are darker than the old flat 0.60,
     while most of the frame is LIGHTER than the original 0.28. Both goals,
     rather than a compromise between them.

     ── Mid-stop moved 40% -> 54% (2026-08-24) ──
     Measured, not guessed. The `.lead` is the widest element in the hero: it
     runs to ~52% of the frame, where the h1 stops at ~40% and the eyebrow at
     ~30%. With the falloff starting at 40% the tail of every lead sat in the
     decayed part of the wash (alpha ~0.23 instead of ~0.44) and three pages
     failed §7 on the lead alone while their h1 and eyebrow passed comfortably.

     Holding the wash flat to 54% covers the full text column. The ENDPOINT IS
     DELIBERATELY UNCHANGED at 72% (74% bright) so nothing in the right quarter
     of the frame moves — that is the part the owner asked to keep bright, and
     it is byte-identical to before. Only the 40-72% ramp is reshaped. */
  content: "";
  position: absolute;
  inset: 0;
  background:
    linear-gradient(90deg, rgba(16, 24, 32, 0.46) 0%,
      rgba(16, 24, 32, 0.44) 54%, rgba(16, 24, 32, 0) 72%),
    linear-gradient(180deg, rgba(16, 24, 32, 0.10) 0%,
      rgba(16, 24, 32, 0.11) 55%, rgba(16, 24, 32, 0.26) 100%);
}
/* On narrow viewports the text runs nearly full-width, so the horizontal wash
   must too — otherwise the right-hand end of every line loses its backing. */
@media (max-width: 860px) {
  .hero-media::after {
    background:
      linear-gradient(90deg, rgba(16, 24, 32, 0.46) 0%,
        rgba(16, 24, 32, 0.41) 60%, rgba(16, 24, 32, 0.32) 100%),
      linear-gradient(180deg, rgba(16, 24, 32, 0.10) 0%,
        rgba(16, 24, 32, 0.11) 55%, rgba(16, 24, 32, 0.26) 100%);
  }
}
.hero-content {
  position: relative;
  z-index: 2;
  width: 100%;
  padding-block: clamp(6rem, 12vh, 9rem) clamp(4rem, 9vh, 7rem);
  animation: heroRise 900ms var(--ease) both;
}
@keyframes heroRise {
  from { opacity: 0; transform: translateY(28px); }
  to { opacity: 1; transform: none; }
}
.hero .eyebrow { color: var(--blue-air); }
/* Home hero on phones: the eyebrow sits in the lightest band of the scrim
   (top of frame), where --blue-air measured 3.98–4.00 against 4.5 over the
   reception footage even on the --bright tier (2026-11-10). White clears it. */
@media (max-width: 860px) {
  .hero .eyebrow { color: #fff; }
}
.hero .lead { color: #fff; opacity: 0.92; margin-top: 1.75rem; }
.hero-ctas {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem;
  margin-top: 2.5rem;
}
.hero-scroll-cue {
  position: absolute;
  z-index: 2;
  left: 50%;
  bottom: 1.75rem;
  transform: translateX(-50%);
  width: 28px; height: 44px;
  border: 0;
  background: none;
  color: rgba(255, 255, 255, 0.8);
  animation: cuePulse 2.4s var(--ease) infinite;
}
@keyframes cuePulse {
  0%, 100% { transform: translate(-50%, 0); opacity: 0.75; }
  50% { transform: translate(-50%, 8px); opacity: 1; }
}
@media (prefers-reduced-motion: reduce) {
  .hero-scroll-cue { display: none; }
  .hero-content { animation: none; }
}

/* 9. TRUST STRIP — "one baseline" (rebuilt 2026-08-01, SIGNAL 2026-11-10) -----
   The section's job: instant proof of legitimacy in the first scroll. One
   continuous hairline — the baseline of §4.17 — draws across the strip; all
   five SIGNAL icons stand on it (32px, top-aligned, internal baseline y=36
   lands at 32 x 36/48 = 24px). "40+ Years" is the display-scale lead (the one fact
   competitors can't match); the other four hold label-caps weight.
   ≤900px the lead spans full width and the four facts drop to a 2x2 grid,
   where the invariant holds per-row (equal, top-aligned icons).

   WHY THIS WAS REBUILT: the old rule was `.trust-strip li { flex: 1 1 220px }`,
   written as a WIDTH basis. The <=700px query flipped the container to
   flex-direction: column, so flex-basis applied to the main axis and 220px
   silently became a MIN-HEIGHT. Five near-empty slabs = 1,157px on a 390px
   phone — 1.37 screens of whitespace for five short labels. Now ~430px. */
.trust-strip {
  background: var(--paper);
  padding-block: clamp(2.5rem, 6vw, 3.75rem) clamp(2.25rem, 5vw, 3.25rem);
}
.trust-line {
  list-style: none;
  margin: 0;
  padding: 0;
  position: relative;
  display: grid;
  grid-template-columns: 1fr 1fr;
  column-gap: var(--grid-gap);
  row-gap: 2rem;
}
/* the drawn baseline — decorative hairline (same exempt status as every --line
   rule); every mark stands on it the way letters stand on a baseline. */
.trust-line::before {
  content: "";
  position: absolute;
  top: 24px;                 /* 32px icon × 36/48 — the baseline every mark stands on */
  left: 0;
  right: 0;
  height: 1px;
  background: var(--line);
  transform-origin: left center;
}
.trust-line.is-in::before { animation: sgSweep 1100ms var(--ease) both; }
@keyframes sgSweep { from { transform: scaleX(0); } to { transform: scaleX(1); } }

/* §4.17 gotcha still applies: flex-start only, never center — centering
   re-seats each column and drops icons off the shared line. */
.trust-line li {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  justify-content: flex-start;
}
.trust-lead { grid-column: 1 / -1; }

.t-cap, .t-label {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}
.t-cap  { color: var(--ink-3); margin-top: 1rem; }    /* 5.80:1 AA */
.t-label {
  color: var(--ink-2);                                 /* 9.09:1 AA */
  margin-top: 1rem;
  max-width: 22ch;
  text-wrap: balance;
}
.t-num {
  display: block;
  font-size: clamp(2.25rem, 3.6vw, 3.25rem);
  font-weight: 500;
  line-height: 1.05;
  letter-spacing: -0.025em;
  color: var(--ink);                                   /* 19.24:1 AA */
  font-variant-numeric: tabular-nums;
  margin-top: 0.375rem;
}

/* labels fade in west-to-east behind the drawing line — once (§0.5) */
.trust-line.is-in .t-cap                   { animation: sgFade 600ms 260ms var(--ease) both; }
.trust-line.is-in .t-num                   { animation: sgFade 600ms 340ms var(--ease) both; }
.trust-line.is-in li:nth-child(2) .t-label { animation: sgFade 600ms 420ms var(--ease) both; }
.trust-line.is-in li:nth-child(3) .t-label { animation: sgFade 600ms 500ms var(--ease) both; }
.trust-line.is-in li:nth-child(4) .t-label { animation: sgFade 600ms 580ms var(--ease) both; }
.trust-line.is-in li:nth-child(5) .t-label { animation: sgFade 600ms 660ms var(--ease) both; }

/* >=901px: one row, five columns — the single ::before now passes under
   EVERY icon's baseline. The set's invariant, finally literal. */
@media (min-width: 901px) {
  .trust-line { grid-template-columns: 1.4fr 1fr 1.2fr 1fr 1fr; row-gap: 0; }
  .trust-lead { grid-column: auto; }
}

@media (prefers-reduced-motion: reduce) {
  .trust-line li:hover .sg-icon .sg-mark,
  .svc-row:hover .sg-icon--svc .sg-mark { animation: none; }
  .trust-line.is-in::before,
  .trust-line.is-in .t-cap,
  .trust-line.is-in .t-num,
  .trust-line.is-in .t-label { animation: none; }
}

/* 9a. SIGNAL ICON SET (§4.17) — slate strokes with square terminals, round
   joins, and ONE orange 6×6 square that terminates the last stroke: the U mark's
   grammar, drawn as icons. Cap line y=12, baseline y=36 in a 48 box. Structure
   inherits currentColor; the signal is --signal (re-scoped on dark). Motion runs
   ONCE on the .reveal observer: strokes draw, the signal rises last. Authored at
   final state, so the reduce guard renders them complete.

   Hover replays need their OWN keyframe name (sgRiseHover). A CSS animation
   restarts only when animation-name changes; the reveal already applied sgRise
   to these exact elements, so re-applying the same name on :hover is a no-op.
   Identical geometry, different identity. Verified 2026-08-01. */
.sg-icon { width: 32px; height: 32px; flex: none; color: var(--ink-2); overflow: visible; }
.sg-icon .sg, .sg-icon .sg-2 { stroke-dasharray: 36; stroke-dashoffset: 0; }
.sg-icon .sg-mark { fill: var(--signal); }
.reveal.is-in .sg-icon .sg      { animation: sgDraw 800ms var(--ease) both; }
.reveal.is-in .sg-icon .sg-2    { animation: sgDraw 800ms 300ms var(--ease) both; }
.reveal.is-in .sg-icon .sg-mark { animation: sgRise 700ms 600ms var(--ease) both; }
.sg-icon path, .sg-icon rect { transition: stroke 320ms var(--ease); }
.trust-line li:hover .sg-icon { color: var(--ink); }
.trust-line li:hover .sg-icon .sg-mark { animation: sgRiseHover 700ms var(--ease); }
@keyframes sgDraw { from { stroke-dashoffset: 36; } to { stroke-dashoffset: 0; } }
@keyframes sgRise { from { transform: translateY(8px); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes sgRiseHover { from { transform: translateY(8px); opacity: 0; } to { transform: none; opacity: 1; } }
@keyframes sgFade { from { opacity: 0; } to { opacity: 1; } }

/* 9b. ABOUT — exterior photo (added 2026-08-01) --------------------------
   Sits under the portrait in the About split. Deliberately a landscape crop
   against the portrait's 4:5 — two different shapes in one column is the
   first break in the page's "every image is the same rounded rectangle"
   sameness. Caption carries the NAP, spelled "Boulevard" per §4.13. */
.about-exterior { margin: var(--grid-gap, 1.5rem) 0 0; }



/* 9c. R3a — hero scroll cue (the signal settling onto the line) ---------------- */
.cue-mark { animation: cueRise 3.2s var(--ease) infinite; }
@keyframes cueRise {
  0%, 100% { transform: translateY(7px); opacity: 0.55; }
  50%      { transform: translateY(0);   opacity: 1; }
}

/* 9d. R3b — consult crest: the U mark standing on a line (SIGNAL) ---------- */
/* The crest sits on the whisper-gradient consult panel, a light surface, so
   --signal resolves to --amber-line. */
.sg-crest { display: block; color: var(--ink-2); margin-bottom: 1rem; overflow: visible; }
.sg-crest .sg, .sg-crest .sg-2 { stroke-dasharray: 36; stroke-dashoffset: 0; }
.sg-crest .sg-mark { fill: var(--signal); }
.reveal.is-in .sg-crest .sg      { animation: sgDraw 800ms var(--ease) both; }
.reveal.is-in .sg-crest .sg-2    { animation: sgDraw 800ms 300ms var(--ease) both; }
.reveal.is-in .sg-crest .sg-mark { animation: sgRise 700ms 900ms var(--ease) both; }

/* 9e. R4 — service-row icons on the midnight band -------------------------- */
/* --signal resolves to #F7B55A via .band--dark (8.56:1 on --midnight). */
.svc-row .num { display: flex; flex-direction: column; align-items: flex-start; gap: 0.75rem; }
.sg-icon--svc { width: 36px; height: 36px; color: var(--mist); }
.svc-row:hover .sg-icon--svc { color: #fff; }
.svc-row:hover .sg-icon--svc .sg-mark { animation: sgRiseHover 700ms var(--ease); }
@media (max-width: 700px) { .sg-icon--svc { width: 28px; height: 28px; } }

/* 9f. R5 — screening-room frame -------------------------------------------
   The facades use object-fit:contain on black (§4.15), so non-16:9 sources
   show bars. Against a white section those read as a mistake; inside a
   deliberate midnight surround they read as a cinema. Gets the dark-frame
   effect without adding a third dark band (§3 caps it at two). */
.screen-frame {
  background: var(--midnight);
  border-radius: var(--radius);
  padding: clamp(0.75rem, 1.8vw, 1.5rem);
}
.screen-frame .media-frame { border-radius: calc(var(--radius) - 8px); }
.screen-cap {
  margin: 0.875rem 0 0;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--mist);
}

/* 9g. R7 — the stat tick draws in ----------------------------------------- */
.reveal.is-in .stat::before { animation: tickDraw 600ms var(--ease) both; }
.reveal.is-in .stat .n      { animation: sgFade 600ms 90ms var(--ease) both; }
@keyframes tickDraw { from { width: 0; } to { width: 28px; } }

/* 9h. About media column — RETIRED asymmetry (2026-08-01) -----------------
   R6 originally did two things here: bled the portrait toward the viewport
   edge, and staggered the exterior photo 14% right off the portrait's line.
   BOTH are now gone, in two steps and for the same underlying reason —
   the whisper-gradient panel (§9i) changed what "asymmetric" reads as:

   1. The bleed went when the panel arrived. A photo escaping a rounded
      container reads as a rendering fault, not an editorial choice.
   2. The 14% stagger went on owner report: inside the panel it did not
      read as an offset, it read as two images that failed to line up
      (427px vs 367px, right edges flush, left edges 60px apart).

   The section is NOT monotonous without them. The break from "every image
   is the same rounded rectangle" now comes from ASPECT CONTRAST — a 4:5
   portrait above a 2.06:1 landscape — which is the more robust version of
   the same idea and survives being put in a box. Keep both images at full
   column width; if you want asymmetry back, change the shapes, not the
   margins. */
@media (min-width: 901px) {
  #welcome .about-exterior { margin-top: calc(var(--grid-gap, 1.5rem) * 1.5); }
}


/* 9i. ABOUT PANEL (added 2026-08-01, owner request) -----------------------
   The same whisper-gradient bubble as the consult band. §2 names that
   gradient "the sanctioned exception" to the no-decorative-gradients rule;
   this extends the exception to a SECOND section, deliberately. It costs a
   little of the consult panel's uniqueness as "the warmest room in the
   house" (§4.8) — but consult keeps the white .consult-card nested inside
   it, which About does not, so it still reads as the place you act.
   Do not extend this to a third section; two is a rhyme, three is wallpaper. */
.about-panel {
  border-radius: 28px;
  background: linear-gradient(135deg, #f8fafb 0%, #edf3f6 100%);
  border: 1px solid var(--line);
  padding: clamp(1.75rem, 4.5vw, 4rem);
}
/* R6's portrait bleed is dropped INSIDE the panel — a photo escaping a
   rounded container reads as a rendering fault, not an editorial choice.
   The exterior photo keeps its stagger, which still breaks the symmetry. */
@media (min-width: 901px) {
  #welcome .col-5 > .media-frame { margin-left: 0; }
}


/* 9j. ABOUT — photo motion (added 2026-08-01) ----------------------------
   The two About photos were the only elements on the page with no life
   after their one fade-rise: the service rows nudge, the icons rise, the
   CTAs carry the orange pill. Two additions, both on-doctrine (§0.5):

   1. ARRIVAL — the exterior now has its own .reveal at delay 2, so the
      portrait lands first and the building follows. Sequence, not a
      simultaneous pop. Still happens once.
   2. HOVER — a slow scale inside the frame's existing overflow:hidden.
      User-initiated and replayable, the same licence the trust-strip
      icons and svc-rows already use. 700ms, no bounce, no spin. Scoped to
      #welcome deliberately: this is NOT a global photo treatment. */
#welcome .media-frame { overflow: hidden; }
#welcome .media-frame img {
  transition: transform 700ms var(--ease);
  will-change: transform;
}
#welcome .media-frame:hover img { transform: scale(1.04); }

/* The exterior additionally warms on hover — a brand-orange glow at the base.
   Orange at 0.16 over a photo is decorative graphic use, not a wash on a
   light surface, and it sits under nothing readable. */
#welcome .about-exterior .media-frame { position: relative; }
#welcome .about-exterior .media-frame::after {
  content: "";
  position: absolute;
  inset: 0;
  background: radial-gradient(80% 60% at 50% 100%,
      rgba(240, 160, 60, 0.16) 0%, rgba(240, 160, 60, 0) 70%);
  opacity: 0;
  transition: opacity 700ms var(--ease);
  pointer-events: none;
}
#welcome .about-exterior .media-frame:hover::after { opacity: 1; }

@media (prefers-reduced-motion: reduce) {
  #welcome .media-frame:hover img { transform: none; }
  #welcome .about-exterior .media-frame:hover::after { opacity: 0; }
}

/* 10. SERVICE INDEX ROWS ------------------------------------------------- */
.svc-index { list-style: none; margin: 0; padding: 0; }
.svc-index li { border-top: 1px solid var(--line-dark); }
.svc-index li:last-child { border-bottom: 1px solid var(--line-dark); }

.svc-row {
  display: grid;
  grid-template-columns: 4rem 1fr auto;
  align-items: center;
  gap: var(--grid-gap);
  padding: clamp(1.75rem, 3.5vw, 2.75rem) clamp(0.5rem, 2vw, 1.5rem);
  text-decoration: none;
  color: inherit;
  transition: background-color 240ms var(--ease);
}
.svc-row:hover { background: rgba(255, 255, 255, 0.05); }

.svc-row .num {
  font-size: var(--fs-small);
  font-weight: 480;
  font-variant-numeric: tabular-nums;
  color: var(--blue-air);
  letter-spacing: 0.1em;
}
.svc-row h3 {
  color: #fff;
  font-size: clamp(1.5rem, 3.2vw, 2.5rem);
  font-weight: 460;
  letter-spacing: -0.02em;
}
.svc-row .svc-desc {
  margin-top: 0.5rem;
  color: var(--mist);
  font-size: var(--fs-small);
  max-width: 56ch;
}
.svc-row .arrow {
  font-size: 1.75rem;
  color: #fff;
  transition: transform 240ms var(--ease);
}
.svc-row:hover .arrow { transform: translateX(6px); color: var(--amber); }
.svc-row:hover .num { color: var(--amber); } /* 6.68:1 on midnight ✓ */
.svc-row .num, .svc-row .arrow { transition: color 240ms var(--ease); }
@media (max-width: 700px) {
  .svc-row { grid-template-columns: 2.5rem 1fr auto; }
}

/* 11. CARDS & TEAM ------------------------------------------------------- */
.card {
  display: flex;
  flex-direction: column;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  overflow: hidden;
  text-decoration: none;
  color: inherit;
  transition: transform 240ms var(--ease), box-shadow 240ms var(--ease);
  /* Fill the grid column. The .col-* grid items stretch to the tallest row
     item, but .card is a block child inside them and only ever took its own
     content height — so in a 3-up team row the two cards with less copy sat
     ~20px short and the bottoms stepped. `height: 100%` makes each card fill
     its stretched column; .card-body already has flex:1 and .btn--link has
     margin-top:auto, so the links then line up too. Harmless where a card
     stands alone (parent height is content height). Fixed 2026-08-02. */
  height: 100%;
}
.card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lift); }
.band--soft .card { border-color: transparent; }

.card-media { aspect-ratio: 4 / 5; overflow: hidden; }
.card-media--wide { aspect-ratio: 3 / 2; }
.card-media img { width: 100%; height: 100%; object-fit: cover; }

.card-body { padding: clamp(1.5rem, 3vw, 2rem); display: flex; flex-direction: column; gap: 0.5rem; flex: 1; }
.card-body .role {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--blue-text);
}
.card-body .btn--link { margin-top: auto; padding-top: 1rem; }

/* 11b. STAT ROW (About) -------------------------------------------------- */
.stat-row {
  display: grid;
  /* auto-fit, not repeat(3,1fr): pages carry two OR three stats, and a hard 3
     leaves a dead column when there are two. Adapts without a per-page override. */
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--grid-gap);
  margin-top: 2.75rem;
}
@media (max-width: 700px) { .stat-row { grid-template-columns: 1fr; } }
.stat {
  border-top: 2px solid var(--ink);
  padding-top: 1rem;
  position: relative;
}
.stat::before {
  /* amber tick riding the rule — brand signal, graphic use */
  content: "";
  position: absolute;
  top: -2px;
  left: 0;
  width: 28px;
  height: 2px;
  background: var(--amber-line);
}
.stat .n {
  display: block;
  font-size: clamp(1.75rem, 3vw, 2.5rem);
  font-weight: 500;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.stat .d {
  display: block;
  margin-top: 0.375rem;
  font-size: var(--fs-small);
  color: var(--ink-3);
  max-width: 24ch;
}

/* 27-dark. COMPARE on a midnight band -------------------------------------
   Added 2026-08-13 for the Peyronie's fork band. The cards keep their light
   --paper background on a dark band, which looks right — but component 7's dark
   context sets the TEXT colour for everything inside the band, so a bare <p> or
   <a> inside a light card inherited white-on-white. Measured before the fix:
   body copy 1.55:1, links 1.00:1. Invisible.

   This is precisely the failure component 36's header warns about — "a second
   dark context is exactly how .page-hero--cinema ended up with invisible ghost
   buttons (see 30)" — and it was found the same way, by computing contrast in a
   live browser rather than by looking at it. `.compare-col h3` and `li` already
   carry their own colours, which is why they looked fine and hid the problem.

   The card is a LIGHT island: inside it, light-band colours apply. Measured
   after: body 9.09:1, links 19.24:1, both on --paper. */
.band--dark .compare-col p { color: var(--ink-2); }
.band--dark .compare-col a { color: var(--ink); }
.band--dark .compare-col li { color: var(--ink-2); }

/* 11b-dark. STAT ROW on a midnight band -----------------------------------
   Added 2026-08-13 for the Xiaflex boxed-warning band. The component was built
   for light bands: `border-top: 2px solid var(--ink)` and `--ink` numerals are
   effectively invisible on --midnight. Rather than invent a second numbers
   component, the existing one gains a dark context — the same move component 36
   documents, and it inherits the rest from component 7 without restating it.
   Contrast on --midnight #1E262C (re-measured 2026-11): #fff 15.34:1,
   --mist 10.33:1, --amber (#F7B55A via dark scope) as non-text graphic 8.56:1. §7 clear.
   The amber tick stays: on midnight it is --amber, not --amber-line. */
.band--dark .stat { border-top-color: var(--mist); }
.band--dark .stat::before { background: var(--amber); }
.band--dark .stat .n { color: #fff; }
.band--dark .stat .d { color: var(--mist); }

/* 11c. MANUFACTURER DESIGNATION — ED / penile-implant pages ONLY -----------
   Deliberately NOT a seal, shield, ribbon, or logo lockup. A manufacturer
   designation is a commercial award from the device vendor, not an
   accreditation — and anything styled like a certification mark creates
   exactly the implied-accreditation problem §9 exists to prevent. So:
   hairline band, conferring company on its own line, scope disclaimer always
   rendered (never behind a toggle, never small-print grey below the fold).
   Amber left rule is graphic-only (--amber-line, 3.99:1, non-text ✓).
   Contrast: --ink-3 on --paper-2 = 5.40:1 ✓ AA. */
.designation {
  border: 1px solid var(--line);
  border-left: 2px solid var(--amber-line);
  padding: clamp(1.5rem, 3vw, 2.25rem);
  background: var(--paper-2);
}
.designation__eyebrow {
  margin: 0 0 0.75rem;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.designation__title {
  margin: 0;
  font-size: var(--fs-h4);
  font-weight: 600;
  line-height: 1.35;
  color: var(--ink);
}
.designation__by {
  margin: 0.5rem 0 0;
  font-size: var(--fs-body);
  color: var(--ink-2);
}
.designation__note {
  margin: 1rem 0 0;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
  font-size: var(--fs-small);
  line-height: 1.6;
  color: var(--ink-3);
  max-width: 62ch;
}

/* 50. .designation--photo — documentary photo beside the designation band ---
   The band's own note (11c) explains why this must never be styled as a seal
   or a vendor logo lockup. A PHOTOGRAPH is not that: it shows the physical
   plaque as the commercial object it is, at the practice, and it lets a
   provider-seeking visitor see that a real office and real people exist —
   which is the gap this page's search data says it has.
   Text column first in the DOM so screen readers and crawlers reach the
   designation and its scope note before the image. Single column on small
   screens (photo below the note); side-by-side from 860px up.
   No frame, no shadow, no rounded "badge" treatment — a hairline only, to
   match the band it sits in. NOT a .media-frame: that component carries the
   editorial cinema treatment and would read as promotional here. */
.designation--photo { display: grid; gap: clamp(1.25rem, 3vw, 2rem); align-items: center; }
@media (min-width: 860px) {
  .designation--photo { grid-template-columns: minmax(0, 1fr) minmax(0, 40%); }
}
/* min-width:0 is load-bearing, not decorative: a grid item's default
   min-width:auto refuses to shrink below its longest unbreakable word, and
   "prosthesis"/"Scientific" in the title would push the track wider than its
   1fr share and squeeze the photo column. */
.designation__body { min-width: 0; }
.designation__figure { margin: 0; min-width: 0; }
.designation__figure img {
  display: block;
  width: 100%;
  height: auto;
  border: 1px solid var(--line);
}
.designation__figure figcaption {
  margin: 0.6rem 0 0;
  font-size: var(--fs-label);
  line-height: 1.5;
  color: var(--ink-3);        /* 5.40:1 on --paper-2 ✓ AA */
}

/* 12. TESTIMONIALS — 3-up editorial quote grid (no JS, nothing hidden) ------
   Was a one-at-a-time carousel with jog arrows; with only three short quotes
   that hid 2/3 of the social proof behind a click. All three now render at
   once — better conversion, better SEO (no display:none content), no controls
   to operate, and the whole carousel JS block is gone. */
.testi { position: relative; }
.testi-slides {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(2rem, 4vw, 3.5rem);
  align-items: stretch;   /* equal-height cells so attributions can bottom-align */
}
@media (max-width: 900px) {
  .testi-slides { grid-template-columns: 1fr; gap: 3rem; }
}
/* quotes vary in length; pin the attribution to the bottom of each cell so the
   names + source badges sit on one line across all three columns */
.testi-slide {
  display: flex;
  flex-direction: column;
  height: 100%;
}

.testi-slide::before {
  /* the amber signal-mark — a single warm hairline (brand accent, graphic use) */
  content: "";
  display: block;
  width: 56px;
  height: 3px;
  border-radius: 2px;
  background: var(--amber);
  margin-bottom: 1.75rem;
}
.testi-slide blockquote {
  /* stepped down from the old full-width featured size — these now set in
     ~1/3-width columns, so display scale would wrap to shreds */
  font-size: clamp(1.125rem, 1.5vw, 1.4375rem);
  font-weight: 400;
  line-height: 1.45;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-wrap: pretty;
}
.testi-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
  margin-top: auto;        /* push to the bottom of the flex column */
  padding-top: 1.75rem;    /* preserve the old visual gap under the quote */
}
@media (max-width: 900px) {
  /* stacked: no sibling to align to, so don't stretch the gap */
  .testi-slide { height: auto; }
  .testi-meta { margin-top: 0; }
}
.testi-meta .who {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.badge {
  display: inline-flex;
  align-items: center;
  gap: 8px;
  min-height: 28px;
  padding: 0 14px;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: var(--fs-micro);
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--ink-3);
}
.badge::before {
  content: "";
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--blue);
}

/* 13. FORMS — the consultation panel is the warmest room in the house ------ */
.consult-band { position: relative; }
.consult-panel {
  border-radius: 28px;
  background: linear-gradient(135deg, #f8fafb 0%, #edf3f6 100%);
  border: 1px solid var(--line);
  padding: clamp(1.75rem, 4.5vw, 4rem);
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: clamp(1.5rem, 3.5vw, 3.5rem);
  align-items: start;
}
.consult-intro { grid-column: 1 / 6; }
.consult-formcol { grid-column: 6 / 13; }
@media (max-width: 900px) {
  .consult-intro, .consult-formcol { grid-column: 1 / -1; }
}

/* 54. CONSULT PANEL — NO-FORM VARIANT (`.consult-panel--direct`) -------------
   The implant OVERVIEW had no consult band at all: no panel, no form, and — the
   part that actually hurt — no `id="consult"`, while THREE buttons on the page
   pointed at `#consult` (both header CTAs and the hero's "Make Appointment").
   The page's primary call to action was a dead anchor.

   A form was not the fix. I-08 records that the consult forms have no backend,
   so shipping one here would add a control that silently swallows submissions —
   worse than no form, and worse specifically for the anxious reader this page
   is written for. The phone works today, so the phone leads.

   Single column rather than the 12-col split, because with the form column gone
   the intro would otherwise sit in a 5-of-12 gutter with half the panel empty.
   Measure is capped so the copy does not run the full panel width. */
.consult-panel--direct { grid-template-columns: 1fr; }
.consult-panel--direct .consult-intro {
  grid-column: 1 / -1;
  max-width: 62ch;
  /* Centred as a BLOCK, text still left-aligned. Without this the copy sits
     hard left and the panel's right half is visibly empty — on the sibling
     pages the form fills it. Centring reads as intentional; left-aligned in a
     full-width panel reads as something failed to load. */
  margin-inline: auto;
}
.consult-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 2rem;
}

.consult-doc {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-top: 2.5rem;
}
.consult-doc img {
  width: 56px; height: 56px;
  border-radius: 50%;
  object-fit: cover;
  object-position: top;
}
.consult-doc .small { max-width: 30ch; }

.consult-card {
  background: var(--paper);
  border-radius: 20px;
  border: 0;
  box-shadow: var(--shadow-lift);
  padding: clamp(1.5rem, 3.5vw, 2.75rem);
}

.form-field { margin-bottom: 1.375rem; }
.form-field label {
  display: block;
  font-size: var(--fs-small);
  font-weight: 600;
  margin-bottom: 0.5rem;
  color: var(--ink);
}
.form-field input[type="text"],
.form-field input[type="email"],
.form-field input[type="tel"],
.form-field textarea {
  width: 100%;
  min-height: 52px;
  padding: 14px 16px;
  font-family: var(--font-sans);
  font-size: 1rem;
  color: var(--ink);
  background: #fbfcfd;
  border: 1px solid var(--line);
  border-radius: 12px;
  transition: border-color 160ms var(--ease), box-shadow 160ms var(--ease),
    background-color 160ms var(--ease);
}
.form-field textarea { min-height: 132px; resize: vertical; }
.form-field input:hover,
.form-field textarea:hover { border-color: var(--slate); }
.form-field input:focus-visible,
.form-field textarea:focus-visible {
  outline: none;
  background: var(--paper);
  border-color: var(--blue-text);
  box-shadow: 0 0 0 4px rgba(58, 116, 150, 0.18);
}
.form-field ::placeholder { color: var(--ink-3); opacity: 1; }

.form-consent {
  display: grid;
  grid-template-columns: 24px 1fr;
  gap: 12px;
  align-items: start;
  margin: 1.5rem 0;
}
.form-consent input[type="checkbox"] {
  width: 24px; height: 24px;
  margin: 2px 0 0;
  accent-color: var(--blue-text);
}
.form-consent label { font-size: var(--fs-micro); color: var(--ink-3); line-height: 1.55; }

/* No-checkbox variant (124 pages, owner 2026-10-11): same type as the consent label. */
.form-terms { margin: 1.5rem 0; font-size: var(--fs-micro); color: var(--ink-3); line-height: 1.55; }
/* Honeypot — off-screen rather than display:none, which some bots detect and skip.
   aria-hidden + tabindex=-1 in the markup keep it away from keyboard and screen readers. */
.form-hp { position: absolute; left: -10000px; top: auto; width: 1px; height: 1px; overflow: hidden; }

.form-submit { width: 100%; }

.form-privacy {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  margin-top: 1.125rem;
  font-size: var(--fs-micro);
  color: var(--ink-3);
}
.form-privacy svg { flex: none; }

.form-error {
  color: var(--error);
  font-size: var(--fs-small);
  margin-top: 0.375rem;
}

/* in-card submit outcome states — icon + text (never color alone), no layout jump */
.form-state {
  display: flex;
  align-items: flex-start;
  gap: 12px;
  padding: 1rem 1.125rem;
  border-radius: 12px;
  font-size: var(--fs-small);
  line-height: 1.5;
  margin-bottom: 1.25rem;
}
.form-state svg { flex: none; margin-top: 2px; }
.form-state a { font-weight: 600; }
.form-state--error {
  background: #fdf3f2;
  border: 1px solid #e8c6c3;
  color: #8c1d16; /* 7.0:1 on #fdf3f2 ✓ */
}
.form-state--error a { color: #8c1d16; }
.form-state--success {
  background: #f1f7f4;
  border: 1px solid #c5ddd1;
  color: #1d5c3f; /* 7.3:1 on #f1f7f4 ✓ */
  align-items: center;
}
.form-success-wrap { text-align: center; padding: 2rem 0; }
.form-success-wrap h4 { margin-bottom: 0.75rem; }
.form-success-wrap p { margin-inline: auto; }

/* 14. LOCATION / NAP ----------------------------------------------------- */
.nap-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.875rem; }
.nap-list .k {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
  display: block;
  margin-bottom: 0.125rem;
}
.band--dark .nap-list .k, .band--navy .nap-list .k { color: var(--blue-air); }

.hours-table { width: 100%; border-collapse: collapse; font-size: var(--fs-small); }
.hours-table td { padding: 0.5rem 0; border-bottom: 1px solid var(--line); }
.hours-table td:last-child { text-align: right; font-variant-numeric: tabular-nums; }
.band--dark .hours-table td, .band--navy .hours-table td { border-bottom-color: var(--line-dark); }

/* 15. MEDIA & PLACEHOLDERS ---------------------------------------------- */
.media-frame {
  border-radius: var(--radius);
  overflow: hidden;
}
.media-frame iframe {
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  border: 0;
}

/* ── YouTube click-to-load facade ───────────────────────────────────────────
   No third-party request until the visitor actually chooses to watch.
   `contain` on black, not `cover`: these videos aren't natively 16:9 (one is a
   vertical clip, one a 4:3 slide deck) and YouTube letter/pillarboxes them —
   `cover` would crop the frame and cut off on-screen text. */
.yt-facade {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 16 / 9;
  background: #000;
  overflow: hidden;
  cursor: pointer;
  border: 0;
  padding: 0;
}
.yt-facade img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  transition: transform 600ms var(--ease), opacity 240ms var(--ease);
}
.yt-facade:hover img { transform: scale(1.03); opacity: 0.88; }
.yt-play { position: absolute; inset: 0; display: grid; place-items: center; }
.yt-play svg {
  width: clamp(56px, 9vw, 76px); height: auto;
  filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.5));
  transition: transform 240ms var(--ease);
}
/* ink disc + white triangle at rest — monochrome-first, not YouTube red.
   Amber is the hover accent, and the disc is a DARK surface, so it's on-doctrine. */
.yt-disc { fill: rgba(11, 15, 18, 0.78); transition: fill 240ms var(--ease); }
.yt-tri  { fill: #fff; transition: fill 240ms var(--ease); }
.yt-facade:hover .yt-disc,
.yt-facade:focus-visible .yt-disc { fill: rgba(11, 15, 18, 0.92); }
.yt-facade:hover .yt-tri,
.yt-facade:focus-visible .yt-tri { fill: var(--amber); }  /* 10.96:1 on ink ✓ */
.yt-facade:hover .yt-play svg { transform: scale(1.07); }
.yt-facade:focus-visible { outline: 2px solid var(--blue-text); outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) {
  .yt-facade:hover img { transform: none; }
  .yt-facade:hover .yt-play svg { transform: none; }
}

/* the location map is itself the directions link */
a.map-link {
  display: block;
  position: relative;
  transition: transform 240ms var(--ease), box-shadow 240ms var(--ease);
}
a.map-link img { display: block; transition: transform 600ms var(--ease); }
a.map-link:hover { box-shadow: 0 18px 40px -20px rgba(11, 15, 18, 0.55); }
a.map-link:hover img { transform: scale(1.03); }   /* quiet push-in, no bounce */
/* the map sits on a dark band — --blue resolves to --blue-air there */
a.map-link:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }
@media (prefers-reduced-motion: reduce) {
  a.map-link:hover img { transform: none; }
}

/* labeled placeholder — owner drops real media in; looks intentional meanwhile */
.ph {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  background:
    repeating-linear-gradient(-45deg, transparent 0 14px, rgba(58, 116, 150, 0.06) 14px 15px),
    linear-gradient(160deg, #252E35 0%, #1A2126 100%);
  color: var(--blue-air);
  overflow: hidden;
}
.ph span {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  text-align: center;
  padding: 1rem;
  opacity: 0.85;
}
.ph--light {
  background:
    repeating-linear-gradient(-45deg, transparent 0 14px, rgba(58, 116, 150, 0.08) 14px 15px),
    linear-gradient(160deg, #eef2f4 0%, #e3e9ec 100%);
  color: var(--blue-text);
}

/* 16. BRAND LOCKUP — inline wordmark + real-text name line (Concept C, 2026-11) */
.brand-logo {
  display: flex; flex-direction: column; justify-content: center; align-items: flex-start;
  gap: 6px; min-height: 44px; flex-shrink: 0; text-decoration: none; color: var(--slate);
}
.brand-wm { display: block; height: 40px; width: auto; }       /* ≈181 px wide */
.brand-wm .wm-l { fill: var(--slate); }                          /* #58636C — exactly ucofl-logo-color.svg */
.brand-wm .wm-o { fill: var(--amber); }                          /* #F0A03C light · #F7B55A in dark scopes */
.brand-name {
  display: block; padding-left: 1px;                              /* optical: the U's stem has 1px side bearing */
  font-family: "Outfit", var(--font-sans); font-weight: 500;
  font-size: 0.59375rem;            /* 9.5px */ letter-spacing: 0.18em; text-transform: uppercase;
  line-height: 1.25; white-space: nowrap; color: var(--slate);   /* 6.15:1 — same colour as the letters */
}
.brand-name > span { display: inline; }
.brand-logo--rev { color: #fff; }
.brand-logo--rev .brand-wm { height: 48px; }
.brand-logo--rev .wm-l { fill: #fff; }
.brand-logo--rev .brand-name { color: var(--blue-air); font-size: 0.6875rem; letter-spacing: 0.2em; margin-top: 2px; } /* 11px, 7.12:1 on slate-dark */

@media (max-width: 1180px) { .brand-wm { height: 36px; } .brand-name { font-size: 0.5625rem; letter-spacing: 0.16em; } } /* 9px */
@media (max-width: 900px)  { .brand-wm { height: 32px; } .brand-name { font-size: 0.53125rem; letter-spacing: 0.14em; } } /* 8.5px */
@media (max-width: 600px)  { .brand-name > span { display: block; } .brand-logo { gap: 4px; } } /* two rows: UROLOGICAL CONSULTANTS / OF FLORIDA */
@media (max-width: 520px)  { .brand-wm { height: 26px; } .brand-name { font-size: 0.5rem; letter-spacing: 0.12em; } .header-bar { gap: 0.5rem; } } /* 8px */
@media (max-width: 400px)  { .header-cta .arrow { display: none; } }
.site-footer .brand-logo--rev .brand-name > span { display: inline; }   /* footer never wraps */
/* `.footer-col a` (inline-flex, align-items:center) outranks .brand-logo — re-assert, or the
   wordmark centres over the wider name line instead of sitting flush left */
.site-footer .brand-logo--rev { display: flex; align-items: flex-start; }

/* mobile icon-only tap-to-call (CRO) — circular icon-button language */
.header-call {
  display: none;
  align-items: center;
  justify-content: center;
  width: 44px; height: 44px;
  flex-shrink: 0;
  border: 1px solid var(--line);
  border-radius: 50%;
  color: var(--ink);
  transition: border-color 200ms, color 200ms;
}
.header-call:hover { border-color: var(--amber-line); color: var(--ink); }
/* HEADER WIDTH BUDGET (measured 2026-11-10 at 1440/1360/1181/1180/1024/920/901/320,
   EN + ES). The lockup is ~60px wider than the old raster and the bar overflowed
   (the CTA pill got squeezed and its label clipped — it already clipped at 901–1200
   before the rebrand). So, desktop:
   - bar gap tightened; the phone NUMBER shows only ≥1361, the 44px call circle
     takes over ≤1360 (the spec said 1181 — not enough room for nav + number + CTA);
   - 901–1360: nav links tighten (8px padding, 4px gap);
   - 901–1180: the name line stacks in two rows (lockup ≈150px wide) and the CTA
     uses its short label.
   Menu-extras phone (mobile panel) is unaffected. */
@media (min-width: 901px) {
  .header-bar { gap: clamp(0.75rem, 1.6vw, 1.5rem); }
}
@media (max-width: 1360px) {
  .header-phone { display: none; }
  .header-call { display: inline-flex; }
}
@media (min-width: 901px) and (max-width: 1360px) {
  .primary-nav > ul { gap: 4px; }
  .primary-nav > ul > li > a,
  .primary-nav .nav-drop > button { padding: 0 8px; background-position: 8px calc(100% - 8px); }
}
@media (min-width: 901px) and (max-width: 1180px) {
  .site-header .brand-name > span { display: block; }
  .site-header .brand-logo { gap: 4px; }
  .site-header .brand-wm { height: 32px; }
}
@media (max-width: 360px) { .header-call { display: none; } }  /* after the ≤1360 show rule, so it wins */

/* header CTA label swaps to a short form on narrow screens (prevents clipping) */
/* the lockup is wider than the old raster: never let the bar squeeze the CTA pill
   (overflow:hidden on .btn would clip its label) */
.header-cta, .header-phone, .header-call { flex-shrink: 0; }
.header-cta .cta-short { display: none; }
@media (max-width: 520px) {
  .header-bar { gap: 0.5rem; }
  .header-cta { padding: 0 16px; letter-spacing: 0.08em; }
  .header-cta .cta-full { display: none; }
  .header-cta .cta-short { display: inline; }
}
/* 901–1180: short CTA label too (see HEADER WIDTH BUDGET above; must follow the base rule) */
@media (min-width: 901px) and (max-width: 1180px) {
  .header-cta .cta-full { display: none; }
  .header-cta .cta-short { display: inline; }
}

/* hero credibility chip (CRO) — one quiet human element near the CTAs */
.hero-cred {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-top: 2.25rem;
}
.hero-cred img {
  width: 48px; height: 48px;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
}
.hero-cred span {
  font-size: var(--fs-micro);
  letter-spacing: 0.04em;
  color: #fff;
  opacity: 0.92;
  max-width: 34ch;
}

/* display-scale links (e.g. "Follow @doc_robbins") get a finer underline */
h2 a { text-decoration-thickness: 2px; text-underline-offset: 6px; }

/* 16a. INSTAGRAM STRIP — local manifest, no third-party scripts ----------- */
.insta-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--grid-gap);
}
.insta-tile {
  position: relative;
  display: block;
  width: 100%;
  /* 4:5, not 1:1. The account posts Reels, which are 9:16 — squeezing those
     into a square threw away 44% of the frame and sliced the caption text in
     half mid-sentence, because these posts carry their message as burned-in
     type rather than in the caption field. 4:5 is Instagram's own portrait
     ratio: it loses about 30% of a Reel instead of 44%, and trims a square
     post only slightly. Measured against the three posts currently in the
     manifest — two 9:16 Reels and one 1:1. */
  aspect-ratio: 4 / 5;
  padding: 0;
  border: 0;
  border-radius: var(--radius);
  overflow: hidden;
  cursor: pointer;
  background: var(--paper-2);
  transition: transform 240ms var(--ease), box-shadow 240ms var(--ease);
}
.insta-tile:hover { transform: translateY(-4px); box-shadow: var(--shadow-lift); }
.insta-tile img,
.insta-tile .ph {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  /* Reels lead with a headline in the upper half. When the crop has to take
     something, take it from the bottom. */
  object-position: center 30%;
  transition: transform 400ms var(--ease);
}
.insta-tile:hover img { transform: scale(1.04); }
.insta-tile[disabled] { cursor: default; }
.insta-tile[disabled]:hover { transform: none; box-shadow: none; }
.insta-tile .play-glyph {
  position: absolute;
  right: 12px;
  top: 12px;
  width: 34px; height: 34px;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(11, 15, 18, 0.62);
  color: #fff;
}
/* responsive count: 1 (mobile) / 2 (tablet) / 3 (desktop) */
@media (max-width: 1000px) {
  .insta-grid { grid-template-columns: repeat(2, 1fr); }
  .insta-grid .insta-tile:nth-child(n+3) { display: none; }
}
@media (max-width: 700px) {
  .insta-grid { grid-template-columns: 1fr; }
  .insta-grid .insta-tile:nth-child(n+2) { display: none; }
}

/* lightbox — on-site modal, no navigation away */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 300;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(1rem, 4vw, 3rem);
  background: rgba(11, 15, 18, 0.74);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
.lightbox[hidden] { display: none; }
.lightbox-panel {
  position: relative;
  width: min(680px, 100%);
  max-height: calc(100vh - 4rem);
  overflow-y: auto;
  background: var(--paper);
  border-radius: 20px;
  padding: clamp(1.25rem, 3vw, 2rem);
  animation: fadeRise 320ms var(--ease) both;
}
/* shared fade-rise (was @keyframes testiIn, defined in the old carousel block) */
@keyframes fadeRise {
  from { opacity: 0; transform: translateY(14px); }
  to { opacity: 1; transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  @keyframes fadeRise { from { opacity: 0; } to { opacity: 1; } }
}
.lightbox-media { border-radius: 12px; overflow: hidden; background: var(--paper-2); }
.lightbox-media img, .lightbox-media video { width: 100%; height: auto; display: block; }
.lightbox-caption { margin-top: 1.25rem; font-size: var(--fs-small); color: var(--ink-2); }
.lightbox-link { margin-top: 1rem; display: inline-flex; }
.lightbox-close {
  position: absolute;
  top: 12px; right: 12px;
  width: 44px; height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  border: 0;
  border-radius: 50%;
  background: var(--paper);
  color: var(--ink);
  font-size: 1.4rem;
  line-height: 1;
  cursor: pointer;
  box-shadow: 0 4px 16px -6px rgba(11,15,18,.25);
}
.lightbox-close:hover { color: var(--amber-text); }

/* 16b. ASSOCIATIONS TICKER — ambient, pausable, reusable ------------------ */
.ticker-wrap { position: relative; }
.ticker {
  --ticker-duration: 42s;   /* slow + calm; tune per logo count */
  position: relative;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent 0, #000 9%, #000 91%, transparent 100%);
  mask-image: linear-gradient(90deg, transparent 0, #000 9%, #000 91%, transparent 100%);
}
.ticker-track {
  display: flex;
  width: max-content;
  animation: tickerScroll var(--ticker-duration) linear infinite;
}
/* seamless loop: margin (not gap) so -50% lands exactly on the duplicate seam */
.ticker-item {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  min-width: 132px;
  min-height: 72px;
  margin-right: clamp(2.5rem, 6vw, 5rem);
  filter: grayscale(1);
  opacity: 0.55;
  transition: filter 240ms var(--ease), opacity 240ms var(--ease),
    transform 240ms var(--ease);
}
.ticker-item:hover {
  filter: none;
  opacity: 1;
  transform: translateY(-4px);
}
.ticker-item img { max-height: 56px; width: auto; }
.ticker-item .ph { width: 132px; height: 64px; border-radius: 8px; }
@keyframes tickerScroll {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}
/* pause: hover anywhere on the belt, any keyboard focus inside, or the button */
.ticker:hover .ticker-track,
.ticker:focus-within .ticker-track,
.ticker.is-paused .ticker-track { animation-play-state: paused; }

/* Pause/play button — restored 2026-10-29 at the owner's request (register I-14):
   WCAG 2.2.2 asks for an on-page way to pause motion that starts by itself. */
.ticker-pause {
  position: absolute;
  right: 0;
  bottom: calc(100% + 0.75rem);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px; height: 44px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--paper);
  color: var(--ink-2);
  cursor: pointer;
  transition: border-color 200ms, color 200ms;
}
.ticker-pause:hover { border-color: var(--amber-line); color: var(--ink); }
html:not(.js) .ticker-pause { display: none; }


/* WCAG 2.2.2 + reduced motion: no auto-scroll — static centered row, dupes hidden */
@media (prefers-reduced-motion: reduce) {
  .ticker { -webkit-mask-image: none; mask-image: none; }
  .ticker-track {
    animation: none;
    width: auto;
    flex-wrap: wrap;
    justify-content: center;
  }
  .ticker-item { filter: none; opacity: 0.8; margin-right: 0; margin: 0.75rem clamp(1rem, 3vw, 2rem); }
  .ticker-item.is-dup { display: none; }
  .ticker-pause { display: none; }
}

/* 17. FOOTER ------------------------------------------------------------- */
.site-footer {
  background: var(--slate-dark);   /* brand-literal, matches ucofl-logo-reversed-on-slate */
  color: var(--mist);
  padding: var(--space-section) 0 3rem;
  font-size: var(--fs-small);
}
.site-footer h2, .site-footer h3 { color: #fff; }
.site-footer .footer-grid {
  display: grid;
  grid-template-columns: 2fr 1fr 1fr 1.4fr;
  gap: var(--grid-gap);
  padding-bottom: 4rem;
}
@media (max-width: 900px) {
  .site-footer .footer-grid { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 560px) {
  .site-footer .footer-grid { grid-template-columns: 1fr; }
}
.footer-col h3 {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--blue-air);
  margin-bottom: 1.25rem;
}
.footer-col ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.25rem; }
.footer-col a {
  color: var(--mist);
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  min-height: 36px;
}
.footer-col a:hover { color: #fff; }

.social-row { display: flex; gap: 0.5rem; margin-top: 1.75rem; }
.social-row a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px; height: 44px;      /* 44px tap targets — a11y contract */
  border: 1px solid var(--line-dark);
  border-radius: 50%;
  color: var(--mist);
  text-decoration: none;
  transition: border-color 200ms, color 200ms, background-color 200ms;
}
.social-row a:hover { border-color: var(--amber); color: #fff; transform: translateY(-2px); }
.social-row a { transition: border-color 200ms, color 200ms, background-color 200ms, transform 200ms var(--ease); }
.social-row svg { width: 18px; height: 18px; fill: currentColor; }

/* light-band variant (e.g. homepage social section) */
.social-row--light a { border-color: var(--line); color: var(--ink-2); }
.social-row--light a:hover { border-color: var(--amber-line); color: var(--ink); }

.footer-legal {
  border-top: 1px solid var(--line-dark);
  padding-top: 2.5rem;
  display: grid;
  gap: 1.25rem;
}
.footer-legal p { max-width: 100ch; }
.footer-legal a { color: var(--mist); }
/* .micro defaults to --ink-3 (slate #58636C) — 1.6:1 on the dark-slate footer. Mist = 6.94:1. */
.site-footer .micro, .site-footer .small { color: var(--mist); }
.footer-legal .row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 2rem;
  align-items: center;
}

/* 18. REVEAL MOTION ------------------------------------------------------ */
/* HIDING IS OPT-IN, and the opt-in is `html.js`.
   .reveal used to start at opacity 0 unconditionally, un-hidden only by JS
   adding .is-in. That meant anything preventing the script from running took
   the entire body copy of the site with it — a very large failure mode for a
   very small animation, and one that fails INVISIBLY: the markup is all there,
   so every HTML-level check still passes.
   Now nothing is hidden unless the inline head script has added `js`, and a
   watchdog in that same script removes it again if the reveal system never
   comes up. Default state is visible; the animation is the exception. */
.reveal {
  transition: opacity 700ms var(--ease), transform 700ms var(--ease);
}
html.js .reveal {
  opacity: 0;
  transform: translateY(24px);
}
/* Belt and braces: covers a browser that supports the media query but where
   the inline script was blocked (a strict CSP without a nonce, say). */
@media (scripting: none) {
  html.js .reveal { opacity: 1; transform: none; }
}
/* Scoped to html.js so it OUT-SPECIFIES the hiding rule above. Unscoped,
   `.reveal.is-in` is (0,2,0) while `html.js .reveal` is (0,2,1) — the hide
   would win and nothing would ever reveal. */
html.js .reveal.is-in { opacity: 1; transform: none; }
.reveal[data-reveal-delay="1"] { transition-delay: 90ms; }
.reveal[data-reveal-delay="2"] { transition-delay: 180ms; }
.reveal[data-reveal-delay="3"] { transition-delay: 270ms; }

/* no-JS safety: if ucofl.js never runs, everything stays visible */
html:not(.js) .reveal { opacity: 1; transform: none; }

/* ==========================================================================
   INTERIOR-PAGE COMPONENTS (added 2026-08-02 with the procedure + blog
   archetypes — build/rezum-for-bph.html, build/blog/…hematuria….html).
   These exist because the ~180 migrated pages are PROSE-CARRYING pages,
   a shape the homepage never needed: a short text hero instead of the
   cinema hero, default vertical rhythm for migrated body copy, a quiet
   FAQ accordion, an E-E-A-T byline for blog posts, and a light-band
   variant of the service index rows. Everything else on interior pages
   reuses the components above unchanged.
   ========================================================================== */

/* 19. PAGE HERO — short interior hero (procedure + blog pages) -------------
   §10 calls for "hero short" on procedure pages. Most legacy pages have no
   cinematic media worth full-bleeding (350px content JPEGs), and §0.3 bars
   faking one — so the interior hero is TYPE: eyebrow, verbatim legacy H1,
   one lead sentence, the two standard exits (Make Appointment / Call).
   Sits on paper with a closing hairline; the fixed header needs explicit
   clearance because, unlike the cinema hero, nothing dark runs behind it.

   TYPE SCALE DECISION (document, don't drift): interior H1s set at
   --fs-display-2, NOT --fs-display-1. §1 marks display-1 "Hero H1 only" —
   that means the homepage cinema hero. Legacy H1s are keyword phrases up to
   ~60 chars ("Blood in Your Urine? Here Is Why You Should Never Ignore It");
   at 6.75rem they shred into 4–5 lines. Display-2 keeps them to 1–2 lines
   and preserves a clear step down to the h3-scale prose h2s below. */
.page-hero {
  padding-top: calc(var(--header-h) + clamp(4rem, 9vw, 6.5rem));
  padding-bottom: clamp(3rem, 6vw, 4.5rem);
  border-bottom: 1px solid var(--line);
}
.page-hero h1 {
  font-size: var(--fs-display-2);
  line-height: 1.06;
  max-width: 22ch;
}
.page-hero .lead { margin-top: 1.5rem; }
.page-hero .hero-ctas { margin-top: 2.25rem; }   /* reuses the hero CTA row */

/* 20. PROSE — migrated body copy (procedure + blog + utility pages) --------
   The legacy pages are h2/p/ul/figure flows. This block gives them the
   system's rhythm without per-element inline styles, so the bulk migration
   is "wrap the cleaned legacy content in .prose and stop".

   HEADING SCALE DECISION: content h2s inside .prose set at --fs-h3 scale.
   Display-2 is for §4.3 component section heads ("one idea per section");
   a migrated article carries 5–10 h2s in a single narrow column, and at
   display scale that reads as shouting (§0.2 is about few words, not many
   big ones). Semantics stay h2 — only the visual scale steps down. */
.prose > * + * { margin-top: 1.25rem; }
.prose h2 {
  font-size: var(--fs-h3);
  line-height: 1.15;
  letter-spacing: -0.015em;
  max-width: 30ch;
}
.prose > h2:not(:first-child) { margin-top: clamp(2.5rem, 5vw, 3.5rem); }
.prose h3 {
  font-size: var(--fs-h4);
  font-weight: 600;
}
.prose > h3:not(:first-child) { margin-top: 2.25rem; }
.prose ul, .prose ol { padding-left: 1.375rem; display: grid; gap: 0.625rem; max-width: 60ch; }
.prose li::marker { color: var(--blue-text); }
.prose figure { margin-block: 2rem; }
.prose figure img { border-radius: var(--radius); }
/* legacy content images are small (300–350px) — render at natural size,
   never upscale to column width (soft blowups read as neglect) */
.prose img { max-width: 100%; height: auto; }
.prose figcaption { margin-top: 0.75rem; font-size: var(--fs-micro); color: var(--ink-3); }
.prose blockquote {
  border-left: 2px solid var(--amber-line);
  padding-left: 1.5rem;
  color: var(--ink-2);
}

/* 21. FAQ ACCORDION — native details/summary (procedure pages) -------------
   Zero JS, keyboard-native, exclusive-open NOT enforced (readers compare
   answers). Hairline rows, quiet plus that turns to a minus — no bounce.
   Pairs with a FAQPage JSON-LD node whose text MUST mirror the visible
   answers (Google requirement; §8). */
.faq { border-top: 1px solid var(--line); }
.faq details { border-bottom: 1px solid var(--line); }
.faq summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  min-height: 44px;                          /* tap target — a11y contract */
  padding: 1.375rem 0;
  cursor: pointer;
  list-style: none;
  font-size: var(--fs-h4);
  font-weight: 600;
  line-height: 1.35;
  color: var(--ink);
  transition: color 200ms var(--ease);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { color: var(--blue-text); }
.faq summary::after {
  /* the plus: two 2px strokes, not a glyph — rotates to an x-free "open"
     state (vertical bar fades) in one calm 260ms move */
  content: "";
  flex: none;
  width: 16px; height: 16px;
  background:
    linear-gradient(var(--ink-2), var(--ink-2)) center / 16px 2px no-repeat,
    linear-gradient(var(--ink-2), var(--ink-2)) center / 2px 16px no-repeat;
  transition: transform 260ms var(--ease);
}
.faq details[open] > summary::after {
  transform: rotate(45deg);                  /* + becomes x: "close me" */
}
.faq .faq-a {
  padding-bottom: 1.75rem;
  color: var(--ink-2);
}
.faq .faq-a > * + * { margin-top: 1rem; }
.faq .faq-a img { border-radius: var(--radius); max-width: 100%; height: auto; }
@media (prefers-reduced-motion: reduce) {
  .faq summary::after { transition: none; }
}

/* 22. BYLINE — blog E-E-A-T header + author box -----------------------------
   Legacy posts shipped an <em> byline or none at all. Google-AI-optimization
   Tier 1 (owner-adopted): visible author byline + published / last-reviewed
   dates on every post. The same block, with .byline--box, closes the post
   as an "About the author" card. */
.byline {
  display: flex;
  align-items: center;
  gap: 1rem;
  margin-top: 2rem;
}
.byline img {
  width: 52px; height: 52px;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
}
.byline .who {
  display: block;
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--ink);
}
.byline .who a { color: inherit; text-decoration: none; }
.byline .who a:hover { color: var(--blue-text); }
.byline .meta {
  display: block;
  margin-top: 2px;
  font-size: var(--fs-micro);
  color: var(--ink-3);
}
.byline--box {
  align-items: flex-start;
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
  padding: clamp(1.25rem, 2.5vw, 1.75rem);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper-2);
}
.byline--box .meta { max-width: 52ch; line-height: 1.55; }

/* 23. LIGHT INDEX ROWS — "In This Section" nav on light bands ---------------
   §4.5's numbered editorial rows, re-tokened for paper. Used for the
   sibling-page lists legacy procedure pages carry ("In This Section").
   Amber on light steps down to --amber-text (5.77:1) per §2 doctrine. */
.svc-index--light li { border-top-color: var(--line); }
.svc-index--light li:last-child { border-bottom-color: var(--line); }
.svc-index--light .svc-row { padding-block: clamp(1.125rem, 2.2vw, 1.625rem); }
.svc-index--light .svc-row:hover { background: var(--paper-2); }
.svc-index--light .svc-row h3 {
  color: var(--ink);
  font-size: clamp(1.125rem, 1.8vw, 1.5rem);
  font-weight: 500;
}
.svc-index--light .svc-row .num { color: var(--blue-text); }
.svc-index--light .svc-row .svc-desc { color: var(--ink-3); }
.svc-index--light .svc-row .arrow { color: var(--ink); font-size: 1.375rem; }
.svc-index--light .svc-row:hover .arrow,
.svc-index--light .svc-row:hover .num { color: var(--amber-text); }
.svc-index--light .svc-row[aria-current="page"] h3 { color: var(--ink-3); }

/* ==========================================================================
   PROSE-PAGE SECTIONING COMPONENTS (added 2026-08-03 with the vasectomy.html
   visual rebuild). The long procedure pages carry content SHAPES the rezum
   archetype never met: emoji-bulleted feature trios, numbered technique
   steps, a two-column pro/con comparison, myth-vs-fact blocks, and an
   h4-level sub-label layer inside prose. Each gets a quiet component here so
   the bulk migration never reaches for inline styles or invents classes.
   All colors are §2 tokens; all motion reuses existing keyframes and the
   .reveal observer; the global reduced-motion guard (§3) covers everything.
   ========================================================================== */

/* 24. PROSE h4 — the sub-label layer -----------------------------------------
   Legacy pages carry h4-level labels ("Before Your Consultation:",
   "About Reversal", "Minimal Trauma Technique") that the first migration
   pass silently dropped — and they ARE the scaffolding that breaks a wall
   of text. Rendered as label-caps eyebrows: visually smaller than the
   h3-scale prose h2s and the h4-size prose h3s above them, so the heading
   HIERARCHY reads correctly even though the type size inverts. Semantics
   untouched — they stay <h4>. */
.prose h4 {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--blue-text);
}
.prose > h4:not(:first-child) { margin-top: 2rem; }

/* .sublabel — the SAME label-caps treatment, applied to whichever tag the
   heading hierarchy actually requires. Several legacy sub-labels sit directly
   under an h2 with no h3 between them; shipping those as <h4> would be an
   h2→h4 level skip (§7.1). So the tag steps up to <h3> and the CLASS carries
   the look. Never solve the reverse way — do not downgrade a real heading's
   tag to fix a visual. Wins over the .prose h3 rule by specificity. */
.prose h3.sublabel,
.prose h4.sublabel {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--blue-text);
}
.prose > h3.sublabel:not(:first-child) { margin-top: 2rem; }

/* 25. STATIC FEATURE CARDS + FACT LINE ---------------------------------------
   For non-link card grids (feature trios, myth-vs-fact). .card assumes an
   <a> wrapper and a -4px lift; a div card gets a gentler, clearly
   decorative response (-2px) so hover never promises navigation.
   .card-fact is the myth-buster's closing line: hairline + amber tick
   (the §4.13 stat gesture) + weight-600 text — emphasis by weight, not
   color, per §1. */
.card--static { cursor: default; }
.card--static:hover { transform: translateY(-2px); box-shadow: var(--shadow-lift); }
.card--static .card-body h3 { font-size: var(--fs-h4); font-weight: 600; }
.card--static .card-body p { color: var(--ink-2); }
.card-fact {
  position: relative;
  margin-top: auto;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--ink);
}
.card-fact::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  width: 28px;
  height: 2px;
  background: var(--amber-line);
}
.card-body .card-fact { margin-top: auto; }
/* feature-card icon slot — SIGNAL marks (§4.17) at 32px, structure ink-2 */
.card-ic { color: var(--ink-2); }
.card-ic .sg-icon { width: 32px; height: 32px; }

/* 26. NUMBERED STEP LIST ------------------------------------------------------
   The technique walk-through ("1 Anesthesia … 4 Completion"). Same visual
   grammar as the stat row: 2px ink top rule, amber tick, display numeral —
   so steps and stats read as one family. <ol> for semantics; the visible
   numeral is aria-hidden decoration beside the h4. */
.steps {
  list-style: none;
  padding: 0;
  margin-top: 2.5rem;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(1.5rem, 3vw, 2.5rem);
}
@media (max-width: 700px) { .steps { grid-template-columns: 1fr; } }
.step {
  position: relative;
  border-top: 2px solid var(--ink);
  padding-top: 1.125rem;
}
.step::before {
  content: "";
  position: absolute;
  top: -2px;
  left: 0;
  width: 28px;
  height: 2px;
  background: var(--amber-line);
}
.step .step-num {
  display: block;
  font-size: clamp(1.75rem, 3vw, 2.25rem);
  font-weight: 500;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
  color: var(--blue-text);
}
/* h3 accepted alongside h4 (2026-08-30): the steps component is used both
   under an h3 (vasectomy: h2 > h3 > h4) and directly under a section h2
   (about-us: h2 > h3). Hard-coding h4 forced a heading-level SKIP on the
   second shape, which §7 forbids — so the selector follows the document,
   rather than the document contorting to follow the selector. */
.step h3,
.step h4 {
  margin-top: 0.5rem;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink);
}
.step p { margin-top: 0.625rem; color: var(--ink-2); }
/* reveal: ticks draw west-to-east, one calm pass (reuses tickDraw/sgFade) */
.reveal.is-in .step::before { animation: tickDraw 600ms var(--ease) both; }
.reveal.is-in .step .step-num { animation: sgFade 600ms 90ms var(--ease) both; }
.reveal.is-in .step:nth-child(2)::before { animation-delay: 120ms; }
.reveal.is-in .step:nth-child(3)::before { animation-delay: 240ms; }
.reveal.is-in .step:nth-child(4)::before { animation-delay: 360ms; }

/* 27. COMPARE — two-column pro/con checklist ---------------------------------
   "Traditional Method" vs "Fellow Partnership". Marks are GLYPHS (– / ✓),
   never color alone (§7.9). The preferred column earns the 2px ink rule +
   amber tick; the baseline column stays hairline — one loud, one quiet,
   same doctrine as the trust strip. */
.compare {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: clamp(1rem, 2.5vw, 2rem);
  align-items: stretch;
}
@media (max-width: 700px) { .compare { grid-template-columns: 1fr; } }
.compare-col {
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: clamp(1.5rem, 3vw, 2rem);
  transition: transform 240ms var(--ease), box-shadow 240ms var(--ease);
}
.compare-col:hover { transform: translateY(-2px); box-shadow: var(--shadow-lift); }
.compare-col h3,
.compare-col h4 {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-2);
}
.compare-col ul {
  list-style: none;
  padding: 0;
  margin-top: 1.25rem;
  display: grid;
  gap: 0.75rem;
}
.compare-col li {
  position: relative;
  padding-left: 1.75rem;
  color: var(--ink-2);
}
.compare-col li::before {
  position: absolute;
  left: 0;
  top: 0;
  font-weight: 600;
}
.compare-col--con li::before { content: "\2013"; color: var(--ink-3); }   /* – */
.compare-col--pro li::before { content: "\2713"; color: var(--blue-text); } /* ✓ */
.compare-col--pro {
  border-top: 2px solid var(--ink);
  border-radius: 0 0 var(--radius) var(--radius);
  position: relative;
}
.compare-col--pro::before {
  content: "";
  position: absolute;
  top: -2px;
  left: 0;
  width: 28px;
  height: 2px;
  background: var(--amber-line);
}
.compare-col--pro h3,
.compare-col--pro h4 { color: var(--ink); }

/* 28. MEDIA-FRAME ZOOM — OPT-IN hover for content imagery --------------------
   §4.6a's slow scale, generalised but deliberately OPT-IN. That block is
   scoped to #welcome with an explicit warning that applying it sitewide would
   make every image on 180 pages twitch under the cursor — so this is a class
   you ADD to a frame, never a global rule. Same gesture: 700ms scale to 1.04,
   clipped by the frame's overflow:hidden. User-initiated and replayable, the
   same licence the svc-rows and SIGNAL icons already hold (§0.5). */
.media-frame--zoom img { transition: transform 700ms var(--ease); }
.media-frame--zoom:hover img,
.media-frame--zoom:focus-within img { transform: scale(1.04); }
@media (prefers-reduced-motion: reduce) {
  .media-frame--zoom img,
  .media-frame--zoom:hover img,
  .media-frame--zoom:focus-within img { transition: none; transform: none; }
}

/* 29. PROSE LINK UNDERLINES — §7.9 on migrated body copy ---------------------
   Colour is never the only signal. Migrated prose carries inline internal and
   outbound links; they get a persistent underline that thickens on hover,
   rather than the label-caps wipe the .btn--link component uses. */
.prose a:not(.btn):not(.btn--link) {
  color: var(--blue-text);
  text-decoration: underline;
  text-underline-offset: 3px;
  text-decoration-thickness: 1px;
  transition: text-decoration-color 200ms var(--ease);
}
.prose a:not(.btn):not(.btn--link):hover { text-decoration-thickness: 2px; }
.band--navy .prose a:not(.btn):not(.btn--link),
.band--dark .prose a:not(.btn):not(.btn--link) { color: var(--blue-air); }

/* 30. PAGE HERO — CINEMA VARIANT (interior pages that have hero footage) ----
   Deliberately NOT `.hero`. That block is min-height:min(100svh,960px) —
   sized for the homepage's single cinematic statement. An interior page must
   not spend a whole viewport before the reader reaches the thing he searched
   for, so this tops out near three-quarters screen and keeps the §1 interior
   H1 scale (display-2, never display-1).

   Reuses `.hero-media`, so the AA scrim (`.hero-media::after`) applies
   unchanged and white type stays >= 4.5:1 over moving footage. ucofl.js §6
   already strips autoplay under prefers-reduced-motion / Save-Data, at which
   point the poster is what the reader sees — so no separate motion guard is
   needed here. */
.page-hero--cinema {
  position: relative;
  min-height: min(72svh, 700px);
  display: flex;
  align-items: flex-end;
  overflow: hidden;
  color: #fff;
  background: var(--midnight);   /* holds the frame before the poster paints */
  border-bottom: 0;
}
.page-hero--cinema > .container {
  position: relative;
  z-index: 2;
  width: 100%;
}
/* ⚠️ WHITE, not --blue-air. Corrected 2026-08-15 after measuring the composited
   pixels behind this element on live pages rather than trusting an earlier reading.
   the old --blue-air (light sky tint, relative luminance ~0.60) over sunlit footage measured 2.72:1
   on prostate-health and 2.44:1 on the penile-implant couple clip — both well under
   the 4.5:1 an eyebrow needs at 12px. It had been failing on prostate-health since
   that hero shipped.

   The original measurement missed it because it computed every hero element as if it
   were white; the h1 and lead ARE white, the eyebrow never was. A measurement is only
   as good as the colour you feed it. White + the deepened --bright scrim below gives
   4.71:1 on the brightest clip on the site. */
.page-hero--cinema .eyebrow { color: #fff; }
.page-hero--cinema h1 { color: #fff; }
.page-hero--cinema .lead { color: #fff; opacity: 0.92; }

/* Dark-context button inversion for the cinema hero.
   §"inverted on dark bands" lists `.band--dark`, `.band--navy` and `.hero` —
   `.page-hero--cinema` is a FOURTH dark context and was not in those selector
   lists, so `.btn--ghost` kept its light-background treatment (--ink text and
   border) and rendered near-invisible over the footage. Same values as the
   existing dark rules, including the a11y note that the hover arrow must be
   --ink and never --amber on a white/mist/orange fill (orange there is 1.1-2.14:1). */
.page-hero--cinema .btn--ghost {
  color: #fff;
  border-color: rgba(255, 255, 255, 0.6);
}
.page-hero--cinema .btn--ghost:hover { background: #fff; color: var(--ink); }
.page-hero--cinema .btn--primary:hover .arrow,
.page-hero--cinema .btn--ghost:hover .arrow { color: var(--ink); }

/* 31. REVIEWS — 6-up variant of the testimonial grid + star marks -----------
   `.testi-slides` is a 3-up row sized for three hand-picked quotes. The Google
   review feed shows six, and six in one row is unreadable — this reflows to
   3x2, then 2x3, then a single column.

   STARS USE --amber-text, NOT --amber. §2 is explicit that --amber is 1.76:1
   on white and is a GRAPHIC/HOVER accent only; a star glyph is TEXT, so it
   needs the 5.77:1 token. The glyphs are aria-hidden and each block carries a
   .vh "Rated N out of 5" so screen readers get the rating as words rather
   than a run of unicode stars. */
.testi-slides--six { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 1100px) {
  .testi-slides--six { grid-template-columns: repeat(2, 1fr); gap: 2.5rem; }
}
@media (max-width: 700px) {
  .testi-slides--six { grid-template-columns: 1fr; gap: 2.5rem; }
}
.review-stars {
  margin: 0 0 0.75rem;
  color: var(--amber-text);
  font-size: var(--fs-small);
  letter-spacing: 0.14em;
  line-height: 1;
}
/* the 6-up cards carry their own star mark, so the component's amber hairline
   would be a second warm accent stacked on the first */
.testi-slides--six .testi-slide::before { display: none; }

/* 32. REVIEWS — "see the rest" CTA + disclaimer -----------------------------
   The link previously lived inside the disclaimer sentence, which made an
   invitation to read more look like fine print. Separated: the CTA gets a real
   button and its own line, the FTC notice drops below it in muted small type
   where a disclaimer belongs. */
.reviews-more {
  margin: 2.5rem 0 0;
  display: flex;
  justify-content: center;
}
.reviews-disclaimer {
  margin: 1rem 0 0;
  text-align: center;
  color: var(--ink-3);
}

/* 33. VIDEO GRID — the gallery's multi-video layout ------------------------
   §4.15 facades were designed one-per-section (a procedure page shows a single
   video). The gallery shows eight at once, and eight full-width frames is a
   very long scroll for a page whose job is "pick one". Two up on desktop, one
   below 780px so the play target and caption stay legible.

   Gap is deliberately generous: these are clickable media tiles that open a
   third-party site on activation, so they need clear separation rather than
   reading as one contiguous filmstrip. */
.video-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(2rem, 4vw, 3rem);
  align-items: start;
}
@media (max-width: 780px) {
  .video-grid { grid-template-columns: 1fr; gap: 2.5rem; }
}
.video-grid .screen-frame { margin-top: 0; }
.yt-more-wrap { margin-top: clamp(2rem, 4vw, 3rem); text-align: center; }


/* 34. TRACT SUMMARY -------------------------------------------------------
   Replaces a copyrighted "causes of hematuria" infographic. It is markup
   rather than a picture on purpose: the text is real, so it is readable by a
   screen reader, translatable into the Spanish pages, indexable by Google and
   selectable by a patient — none of which an image of a list can do. It also
   carries no licensing question.
   The clinical framing (upper tract / lower tract / whole body) is standard
   teaching, and the causes listed are the page's OWN nine sections regrouped,
   not a transcription of the original artwork. */
.tract { display: grid; gap: 1rem; margin-block: 2rem; }
@media (min-width: 720px) { .tract { grid-template-columns: repeat(3, 1fr); } }

.tract-group {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 1.25rem 1.25rem 1.1rem;
  background: var(--paper);
}
.tract-group > p.tract-where {
  font-size: var(--fs-micro);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-3);
  margin: 0 0 0.15rem;
}
.tract-group > p.tract-name { font-weight: 600; margin: 0 0 0.6rem; }
.tract-group ul { margin: 0; padding-left: 1.1rem; }
.tract-group li { margin-block: 0.3rem; }

/* the "looks like blood but is not" caveat reads as an aside, not a fourth cause */
.tract-note {
  border-left: 3px solid var(--blue);
  padding: 0.35rem 0 0.35rem 1rem;
  margin-block: 1.5rem;
  color: var(--ink-2);
}


/* 35. INSTAGRAM FALLBACK --------------------------------------------------
   What sits in the markup before JS swaps in the real tiles — and what a
   visitor sees if that fetch never succeeds. It has to be presentable on its
   own, because it is the failure state as well as the initial state. */
.insta-fallback {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  min-height: 140px;
  padding: 1.5rem;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper);
  color: inherit;
  text-decoration: none;
  text-align: center;
  transition: border-color 240ms var(--ease), transform 240ms var(--ease);
}
.insta-fallback:hover { border-color: var(--blue); transform: translateY(-2px); }
.insta-fallback__mark { display: inline-flex; color: var(--blue); }

/* 36. EVIDENCE LEDGER — sourced findings on a dark band ----------------------
   Built for the shockwave page, where the honest evidence IS the argument and
   has to read as confidence rather than as a disclaimer in fine print.

   Ships on `.section band--dark`, so the midnight background, white headings,
   blue-air eyebrow, mist body, inverted buttons and focus rings all come from
   component 7. Nothing here restates them — a second dark context is exactly
   how `.page-hero--cinema` ended up with invisible ghost buttons (see 30).

   Each item is a SOURCE label, the CLAIM, and the qualification that keeps the
   claim honest. The 28px amber cap rule is the same gesture as .compare-col--pro
   (27), so the two read as one family.

   Contrast on --midnight #1E262C, computed not assumed (2026-11): --blue-air 10.60:1,
   --mist 10.33:1, #fff 15.34:1, --amber (#F7B55A) as a non-text graphic 8.56:1. §7 clear. */
.ev-grid {
  list-style: none;
  margin: clamp(2.5rem, 5vw, 3.5rem) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: clamp(1.5rem, 3vw, 2.5rem);
}
/* Opt-in cap at three columns. With five findings, auto-fit at full width
   lays out 4 + 1, an orphan that reads as an afterthought; 3 + 2 reads as a set.
   Opt-in so the existing pages keep their layouts. */
@media (min-width: 900px) {
  .ev-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
.ev-item {
  position: relative;
  border-top: 2px solid var(--line-dark);
  padding-top: 1.5rem;
}
.ev-item::before {
  content: "";
  position: absolute;
  top: -2px;
  left: 0;
  width: 28px;
  height: 2px;
  background: var(--amber);
}
.ev-src {
  display: block;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--blue-air);
}
.ev-claim {
  margin-top: 0.875rem;
  max-width: 34ch;
  color: #fff;
  font-size: var(--fs-h4);
  font-weight: 600;
  line-height: 1.35;
}
.ev-note {
  margin-top: 0.75rem;
  max-width: 46ch;
  color: var(--mist);
  font-size: var(--fs-small);
}
.ev-close {
  margin-top: clamp(2.5rem, 5vw, 3.5rem);
  max-width: 64ch;
  color: #fff;
  font-size: var(--fs-lead);
  line-height: 1.5;
}

/* 37. PATH STRIP — the pathway spine on prostate-section child pages --------
   The section is entered from search, not from its hub: 180-day GSC gave the
   hub 0 clicks at position 56 while the child pages took 472. So orientation
   cannot live on the hub. This strip is the spine rendered locally on every
   child page — you are here, this came before, this is next — and it REPLACES
   the sitewide trust strip there (one orientation band per hero; the trust
   facts stay on the hub and inside each page's credential band).

   SIGNAL grammar (§4.17): the line is the patient's path; each stage is a
   square that has climbed exactly as far as he has come. Line at y=32 in
   every state.

   State is carried by GLYPH + WEIGHT + aria-current, never by color alone
   (§7.9): is-done = a slate square resting on the line, is-here = the orange
   signal raised on a post plus the amber rest-state underline, is-ahead = a
   short stub on the line with no square. No clip defs needed.

   No entrance animation and NOT wired to .reveal — this is navigation, and
   navigation that fades in is navigation you cannot use yet. */
.path-strip {
  border-block: 1px solid var(--line);
  background: var(--paper);
  padding-block: 1rem 1.125rem;
}
.path-strip__crumb {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.path-strip__crumb a { color: var(--blue-text); text-decoration: none; }
.path-strip__crumb a:hover { text-decoration: underline; }
.path-strip__stops {
  list-style: none;
  margin: 0.875rem 0 0;
  padding: 0;
  display: flex;
  gap: clamp(1.25rem, 4vw, 3rem);
  overflow-x: auto;                 /* mobile: the path scrolls, it never wraps */
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.path-strip__stops::-webkit-scrollbar { display: none; }
.path-strip__stops li { flex: 0 0 auto; }
.path-strip__stops .ps-link,
.path-strip__stops li[aria-current] {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 44px;                 /* §7 tap target */
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-3);
  text-decoration: none;
  white-space: nowrap;
}
.path-strip__stops .ps-link:hover span { text-decoration: underline; }
/* ⚠ Specificity matters here and it bit once already. The shared rule above is
   `.path-strip__stops a, .path-strip__stops li[aria-current]`, so a bare
   `.is-here` (0,2,0) LOSES to `li[aria-current]` (0,2,1) and a bare `.is-done`
   is overridden on the anchor, which carries its own color. Both state rules
   rendered nothing at all until this was caught in the browser: every stage came
   out the same grey. Qualify with `li` and target the anchor where the anchor
   owns the color. */
.path-strip__stops li.is-done > .ps-link { color: var(--ink-2); }   /* passed: legible, not loud */
.path-strip__stops li.is-here { color: var(--ink); }
.path-strip__stops .is-here span {
  border-bottom: 2px solid var(--amber-line);
  padding-bottom: 2px;
}
.path-strip__stops li.is-ahead > .ps-link { color: var(--ink-3); }
.ps-mark { width: 22px; height: 22px; flex: 0 0 auto; }
.ps-mark .sg-mark { fill: var(--signal); }
.path-strip__stops .ps-link:focus-visible {
  outline: 2px solid var(--blue-text);
  outline-offset: 3px;
  border-radius: 4px;
}

/* 38. NEXT STEP — one canonical forward move before the consult band --------
   The rail, not the maze. Exactly ONE title-scale link per page, plus a quiet
   step-back line. Anxious men do not need more choices at the bottom of a
   page; they need to be told where to go.

   Same 2px rule + 28px amber tick as .compare-col--pro (27) and .ev-item (36),
   so the three read as one family.

   Next = the following stage of the path, EXCEPT on the two decision nodes,
   where the honest next move is a consultation rather than another page. */
.path-next { padding-block: clamp(2.5rem, 6vw, 4rem) 0; }
.path-next > .container--narrow {
  position: relative;
  border-top: 2px solid var(--ink);
  padding-top: 1.25rem;
}
.path-next > .container--narrow::before {
  content: "";
  position: absolute;
  top: -2px;
  left: 0;
  width: 28px;
  height: 2px;
  background: var(--amber-line);
}
/* `.path-next` has padding-bottom:0 because on every other page in the system a
   consult band follows and supplies its own top padding. The implant OVERVIEW has
   no consult band (see I-08 — the forms have no backend), so the next-step ran
   straight into the navy locations band with ~12px of air. This restores the
   bottom half of the rhythm for that case only; adding it to the base rule would
   double-space it on the ten prostate pages where the consult band does follow. */
.path-next--spaced { padding-bottom: clamp(2.5rem, 6vw, 4rem); }
.path-next__title {
  margin-top: 0.5rem;
  font-size: var(--fs-h3);
  font-weight: 500;
  letter-spacing: -0.015em;
}
.path-next__title a { color: var(--ink); text-decoration: none; }
.path-next__title a:hover .arrow { transform: translateX(4px); color: var(--amber-text); }
.path-next__title .arrow {
  display: inline-block;
  transition: transform 240ms var(--ease), color 240ms var(--ease);
}
.path-next__title a:focus-visible { outline: 2px solid var(--blue-text); outline-offset: 3px; }
.path-next__alt { margin-top: 0.625rem; font-size: var(--fs-small); color: var(--ink-3); }
.path-next__alt a { color: var(--blue-text); }

/* 39. TX TABLE — the treatment comparison ledger (decision-node pages) ------
   A real <table>, deliberately: translatable, indexable, screen-readable, and
   correctable in place. The alternative the legacy site reached for — baking a
   comparison into an image — is how claims end up invisible to every text check
   on the site (register M-09), and it cannot be translated at all.

   The table is a SUMMARY, never a new source of claims: every cell traces to the
   child page it links to, or to a cited guideline.

   Wide content scrolls inside .table-scroll so the PAGE never scrolls
   horizontally — the house rule the shockwave and path-strip work both held to. */
.table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin-top: 2.5rem;
}
.tx-table { width: 100%; min-width: 720px; border-collapse: collapse; }
.tx-table caption { text-align: left; }
.tx-table thead th {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-2);
  text-align: left;
  padding: 0 1rem 0.75rem 0;
  border-bottom: 2px solid var(--ink);
}
.tx-table tbody th,
.tx-table tbody td {
  padding: 1.125rem 1rem 1.125rem 0;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
  text-align: left;
  font-size: var(--fs-body);
  color: var(--ink-2);
}
.tx-table tbody th { font-weight: 600; color: var(--ink); }
.tx-table tbody th a { color: var(--blue-text); text-decoration: underline; text-underline-offset: 3px; }
.tx-table tbody tr:hover { background: var(--paper-2); }
.tx-table tbody th a:focus-visible { outline: 2px solid var(--blue-text); outline-offset: 2px; }

/* 40. TESTIMONIAL QUOTE — patient words inside prose ------------------------
   Distinct from the 3-up .testi grid (12), which is a homepage layout. This is
   for quotes that sit in the body of a procedure page, where they arrive one
   after another and need to read as quotation rather than as more body copy.

   A blockquote, not a styled <p>: assistive technology announces it as a quote,
   which is the whole point — a reader should know whose voice this is.

   The 28px amber cap rule is the same gesture as .compare-col--pro (27),
   .ev-item (36) and .path-next (38), so the family stays coherent. */
.testi-quote {
  position: relative;
  margin: 2rem 0 0;
  padding: 1.25rem 0 0 1.5rem;
  border-top: 1px solid var(--line);
}
.testi-quote::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  width: 28px;
  height: 1px;
  background: var(--amber-line);
}
.testi-quote p {
  margin: 0;
  color: var(--ink-2);
  font-size: var(--fs-body);
  line-height: 1.65;
}
.testi-quote p + p { margin-top: 0.875rem; }
.testi-quote cite {
  display: block;
  margin-top: 0.75rem;
  font-style: normal;
  font-size: var(--fs-small);
  color: var(--ink-3);
}

/* 41. BRIGHT-FOOTAGE SCRIM — an opt-in deepening of the hero scrim -----------
   The standard .hero-media::after gradient (0.28 -> 0.38 -> 0.68) is tuned for
   the footage the site already had. Measured against the golden-hour golf clip
   on prostate-health, white text came out at 3.79:1 (h1), 3.90:1 (lead) and
   4.25:1 (eyebrow) — the h1 passes as large text, but the lead and eyebrow are
   BODY-size and need 4.5:1. They failed.

   The honest fix is more scrim on the pages that need it, not a quiet exemption
   from §7. Opt in with `.hero-media hero-media--bright` when the footage is
   sunlit; leave it off when it is not, so darker clips are not needlessly muddied.

   Measured after: every hero text element clears 4.5:1 over this clip. Re-measure
   if the footage is ever swapped — that is the check that caught this. */
.hero-media--bright::after {
  /* Same localised shape, a little more weight on the text side, for footage
     that is genuinely bright (sunlit harbour, golden-hour grass). Still far
     lighter across the picture than the flat 0.60 it replaces.
     Mid-stop moved 40% -> 54% on 2026-08-24 for the reason given on
     `.hero-media::after`; the 74% endpoint is unchanged. */
  background:
    linear-gradient(90deg, rgba(16, 24, 32, 0.64) 0%,
      rgba(16, 24, 32, 0.62) 54%, rgba(16, 24, 32, 0) 74%),
    linear-gradient(180deg, rgba(16, 24, 32, 0.18) 0%,
      rgba(16, 24, 32, 0.19) 55%, rgba(16, 24, 32, 0.38) 100%);
}
@media (max-width: 860px) {
  .hero-media--bright::after {
    background:
      linear-gradient(90deg, rgba(16, 24, 32, 0.64) 0%,
        rgba(16, 24, 32, 0.58) 60%, rgba(16, 24, 32, 0.48) 100%),
      linear-gradient(180deg, rgba(16, 24, 32, 0.18) 0%,
        rgba(16, 24, 32, 0.19) 55%, rgba(16, 24, 32, 0.38) 100%);
  }
}

/* 51. `.hero-media--bright-narrow` — the heavy scrim ONLY where it is earned ---
   Added 2026-09-12 for the ED cafe hero, after the owner said it looked too
   dark. He was right, and the measurements said the fix was structural.

   `--bright` exists for SUNLIT footage (see above). Applying it to a dim
   interior does exactly what the `.hero-media::after` comment warns against —
   needlessly muddies a dark clip. But the ED hero could not simply drop to the
   standard tier either, because at 390px the `.lead` wraps to four
   near-full-width lines and runs out into the decayed tail of the horizontal
   wash, measuring 3.60 against a 4.5 requirement.

   The failure is MOBILE-ONLY, so the remedy should be too. This modifier adds
   nothing above 860px — the element keeps the standard `.hero-media::after`
   wash — and applies the bright mobile stops below it. Measured on the ED hero,
   worst case over 8 moments of playback and every glyph pixel:

       desktop  eyebrow 6.10  h1 4.22  lead 5.32   all pass, mean grey 77.1
       mobile   eyebrow 8.32  h1 6.24  lead 6.43   all pass, mean grey 54.7

   versus 54.1 mean grey on desktop under the full `--bright` tier: a 43%
   brighter picture at the viewport where the complaint was, with no §7 loss.

   USE THIS when a clip is too dark for `--bright` but its mobile lead will not
   clear on standard. Do NOT reach for it to rescue a hero that fails on
   desktop — that is a footage problem, and this modifier changes nothing there. */
@media (max-width: 860px) {
  .hero-media--bright-narrow::after {
    background:
      linear-gradient(90deg, rgba(16, 24, 32, 0.64) 0%,
        rgba(16, 24, 32, 0.58) 60%, rgba(16, 24, 32, 0.48) 100%),
      linear-gradient(180deg, rgba(16, 24, 32, 0.18) 0%,
        rgba(16, 24, 32, 0.19) 55%, rgba(16, 24, 32, 0.38) 100%);
  }
}

/* 42. PROTOCOL TIMELINE (`.protocol`) — a treatment cycle drawn as a climb ----
   Built 2026-08-13 for the Xiaflex page's post-injection instructions and the
   at-home stretching routine. Three constraints shaped it, and each one ruled
   out the obvious implementation:

   1) §0.5 AS AMENDED BANS INFINITE LOOPS. The owner asked for a "moving
      graphic"; a looping animation beside text a patient must read is exactly
      what the 2026-08-01 amendment forbids, and WCAG 2.2.2 agrees. So this
      DRAWS ONCE on scroll-in, using the existing `.reveal` -> `.is-in`
      observer. No new JS, no new network requests, no loop.
   2) NOTHING ANATOMICAL. Owner's instruction, and correct: this is a
      patient-facing page about the penis, and an explicit diagram would be
      both unusable in a waiting room and unnecessary. The house iconography is
      the SIGNAL set (§4.17), so the cycle is drawn as a climb: the signal
      square sits low at the first injection and stands at the top by the time
      the cycle completes. The metaphor carries the sequence without
      depicting anything.
   3) THE TEXT IS REAL TEXT. Every instruction is HTML, not paint — readable,
      selectable, translatable, indexable, and legible to a screen reader. The
      SVG is decoration (`aria-hidden`) and carries no meaning of its own. Same
      doctrine as the §34 .tract diagram that replaced manufacturer artwork.

   Reduced motion needs no extra code: the marks are authored at their FINAL
   state and the animation only subtracts (dashoffset, translate), so the
   global reduce guard renders them complete and static. Same trick as .sg-icon.

   SIGNAL grammar (§4.17, 2026-11-10): viewBox 48x48, a post that climbs one
   step per stage with the orange signal square on top (sg-step-{k}of{n});
   butt-capped 3-unit strokes, pathLength="36" on every drawable stroke. */
.protocol { margin: 2.5rem 0 0; }
.protocol__steps {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0;
}
/* A VERTICAL timeline, not a five-column strip. The first attempt used five
   columns and it was wrong twice over: inside a `.prose` measure (~64ch) each
   column got ~110px, so "One to three days later" broke over three lines; and a
   sequence of INSTRUCTIONS reads better down the page than across it, because
   that is the order a man performs them in. Vertical also means the mobile and
   desktop layouts are the same shape, so the thing he reads at home on a phone
   is the thing he saw here. */
.protocol__step {
  position: relative;
  display: grid;
  grid-template-columns: 44px 1fr;
  gap: 1rem;
  padding: 0 0 1.75rem;
}
.protocol__step:last-child { padding-bottom: 0; }
/* the thread between marks — drawn, once, like everything else */
.protocol__step:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 22px;   /* centred under the 44px mark, not 21px — a 1px offset is visible on a hairline */
  top: 44px;
  bottom: 0.5rem;
  width: 1px;
  background: var(--line);
  transform-origin: top;
}
.js .reveal:not(.is-in) .protocol__step::before { transform: scaleY(0); }
.reveal.is-in .protocol__step::before {
  animation: ptThread 500ms cubic-bezier(.22,.61,.36,1) both;
}
@keyframes ptThread { from { transform: scaleY(0); } to { transform: scaleY(1); } }
.protocol__mark { width: 44px; height: 44px; display: block; }
.protocol__body { padding-top: 0.35rem; }
.protocol__when {
  margin: 0 0 0.15rem;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.protocol__step h3 {
  margin: 0 0 0.35rem;
  font-size: var(--fs-body);
  font-weight: 600;
  line-height: 1.3;
}
.protocol__step p:not(.protocol__when) {
  margin: 0;
  font-size: var(--fs-small);
  line-height: 1.6;
  color: var(--ink-2);
}

/* The climb itself. The signal sits low at the first injection and stands at
   the top by the time the cycle closes — the sequence carries the progression,
   so nothing anatomical has to be drawn. Each verb runs ONCE (§0.5 as
   amended); the marks are authored at final state so the reduced-motion guard
   leaves them complete and static with no extra code. The thread ::before
   continues each mark's own post. */
.protocol__mark .sg { stroke-dasharray: 36; stroke-dashoffset: 0; }
.js .reveal:not(.is-in) .protocol__mark .sg { stroke-dashoffset: 36; }
.reveal.is-in .protocol__mark .sg { animation: sgDraw 700ms var(--ease) both; }
.protocol__mark .sg-mark { fill: var(--signal); }
.js .reveal:not(.is-in) .protocol__mark .sg-mark { opacity: 0; }
.reveal.is-in .protocol__mark .sg-mark { animation: sgRise 900ms var(--ease) both; }
.protocol__step:nth-child(1) .protocol__mark .sg { animation-delay: 0ms; }
.protocol__step:nth-child(2) .protocol__mark .sg { animation-delay: 120ms; }
.protocol__step:nth-child(3) .protocol__mark .sg { animation-delay: 240ms; }
.protocol__step:nth-child(4) .protocol__mark .sg { animation-delay: 360ms; }
.protocol__step:nth-child(5) .protocol__mark .sg { animation-delay: 480ms; }
.protocol__step:nth-child(6) .protocol__mark .sg { animation-delay: 600ms; }
.protocol__step:nth-child(1) .protocol__mark .sg-mark { animation-delay: 160ms; }
.protocol__step:nth-child(2) .protocol__mark .sg-mark { animation-delay: 280ms; }
.protocol__step:nth-child(3) .protocol__mark .sg-mark { animation-delay: 400ms; }
.protocol__step:nth-child(4) .protocol__mark .sg-mark { animation-delay: 520ms; }
.protocol__step:nth-child(5) .protocol__mark .sg-mark { animation-delay: 640ms; }
.protocol__step:nth-child(6) .protocol__mark .sg-mark { animation-delay: 760ms; }

.protocol__cadence {
  margin: 2rem 0 0;
  padding: 1.5rem;
  border: 1px solid var(--line);
  border-left: 3px solid var(--blue);
  border-radius: var(--radius);
}
.protocol__cadence h3 { margin: 0 0 0.5rem; font-size: var(--fs-h4, 1.125rem); }
.protocol__cadence p { margin: 0.5rem 0 0; color: var(--ink-2); }
.protocol__cadence .protocol__big {
  display: block;
  font-size: clamp(2rem, 5vw, 2.75rem);
  font-weight: 700;
  line-height: 1.1;
  letter-spacing: -0.02em;
  margin-bottom: 0.25rem;
}

/* 43. ERA TIMELINE (`.era`) — the practice's history drawn as one long climb ---
   About page. Same doctrine as .protocol (42): draws ONCE via the existing
   .reveal observer, nothing loops, text is real HTML, marks are aria-hidden
   decoration authored at FINAL state — so the global reduced-motion guard
   renders them complete and static with zero extra code. Grammar is §4.17
   SIGNAL: viewBox 48x48, a post that climbs one step per milestone
   (sg-step-{k}of6) with the orange signal square on top, pathLength="36" on
   every drawable stroke.

   Not merely .protocol reskinned, because the content differs in kind: a
   protocol step is an INSTRUCTION (small type; you obey it in order); an era
   entry is a MILESTONE — the year is the display element, set in the stat-row
   family (tabular-nums, ink) so timeline and stats read as one language.
   Reuses ptThread/sgDraw/sgRise rather than minting near-duplicates. */
.era { margin: clamp(2.5rem, 5vw, 3.5rem) 0 0; }
.era__list { list-style: none; margin: 0; padding: 0; }
.era__item {
  position: relative;
  display: grid;
  grid-template-columns: 44px clamp(84px, 12vw, 132px) 1fr;
  gap: 0.25rem 1.25rem;
  padding: 0 0 2.25rem;
}
.era__item:last-child { padding-bottom: 0; }
/* the thread between marks — 22px centres a 1px line under a 44px mark (a 1px
   offset is visible on a hairline — learned in component 42) */
.era__item:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 22px;
  top: 48px;
  bottom: 0.5rem;
  width: 1px;
  background: var(--line);
  transform-origin: top;
}
.js .reveal:not(.is-in) .era__item::before { transform: scaleY(0); }
.reveal.is-in .era__item::before { animation: ptThread 500ms var(--ease) both; }
.era__mark { width: 44px; height: 44px; display: block; color: var(--ink-2); }
.era__year {
  margin: 0;
  padding-top: 0.4rem;
  font-size: clamp(1.375rem, 2.4vw, 1.875rem);
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.1;
  font-variant-numeric: tabular-nums;   /* harmless on "Founded"/"Today" */
  color: var(--ink);
}
.era__body { padding-top: 0.55rem; }
.era__body h3 {
  margin: 0 0 0.35rem;
  font-size: var(--fs-h4);
  font-weight: 600;
  line-height: 1.3;
}
.era__body p { margin: 0; color: var(--ink-2); max-width: 56ch; }
.era__by {
  margin: 0.375rem 0 0;
  font-size: var(--fs-small);
  color: var(--ink-3);
}
@media (max-width: 700px) {
  /* year stacks above the body in one column; the mark column persists so the
     thread stays continuous — same shape on phone and desktop, like .protocol */
  .era__item { grid-template-columns: 44px 1fr; }
  .era__year { grid-column: 2; padding-top: 0.55rem; }
  .era__body { grid-column: 2; padding-top: 0; }
}

/* the climb: posts draw, signals rise, entry by entry */
.era__mark .sg { stroke-dasharray: 36; stroke-dashoffset: 0; }
.js .reveal:not(.is-in) .era__mark .sg { stroke-dashoffset: 36; }
.reveal.is-in .era__mark .sg { animation: sgDraw 700ms var(--ease) both; }
.era__mark .sg-mark { fill: var(--signal); }
.js .reveal:not(.is-in) .era__mark .sg-mark { opacity: 0; }
.reveal.is-in .era__mark .sg-mark { animation: sgRise 900ms var(--ease) both; }
.era__item:nth-child(1) .era__mark .sg { animation-delay: 0ms; }
.era__item:nth-child(2) .era__mark .sg { animation-delay: 120ms; }
.era__item:nth-child(3) .era__mark .sg { animation-delay: 240ms; }
.era__item:nth-child(4) .era__mark .sg { animation-delay: 360ms; }
.era__item:nth-child(5) .era__mark .sg { animation-delay: 480ms; }
.era__item:nth-child(6) .era__mark .sg { animation-delay: 600ms; }
.era__item:nth-child(1) .era__mark .sg-mark { animation-delay: 160ms; }
.era__item:nth-child(2) .era__mark .sg-mark { animation-delay: 280ms; }
.era__item:nth-child(3) .era__mark .sg-mark { animation-delay: 400ms; }
.era__item:nth-child(4) .era__mark .sg-mark { animation-delay: 520ms; }
.era__item:nth-child(5) .era__mark .sg-mark { animation-delay: 640ms; }
.era__item:nth-child(6) .era__mark .sg-mark { animation-delay: 760ms; }

/* hover replays the signal's rise — the one repeatable, user-initiated moment.
   🛑 sgRiseHover, never the reveal's sgRise: a CSS animation only restarts when
   animation-name CHANGES (§4.17, learned 2026-08-01). */
.era__item:hover .era__mark { color: var(--ink); }
.era__item:hover .era__mark .sg-mark { animation: sgRiseHover 700ms var(--ease); }
@media (prefers-reduced-motion: reduce) {
  .era__item:hover .era__mark .sg-mark { animation: none; }
}

/* the shared designation scope note — the §4.16 keystone, styled in the
   .designation family (hairline box, amber-line left rule = graphic use,
   --ink-3 on --paper-2 = 5.40:1 AA). NEVER shrink to micro, never collapse
   behind a toggle, never drop "for space" (§4.16). */
.era__scope {
  margin: clamp(2rem, 4vw, 2.75rem) 0 0;
  border: 1px solid var(--line);
  border-left: 2px solid var(--amber-line);
  background: var(--paper-2);
  padding: 1.25rem 1.5rem;
  font-size: var(--fs-small);
  line-height: 1.6;
  color: var(--ink-3);
  max-width: 72ch;
}

/* 44. OFFICE DUO — small photos displayed at native scale, on purpose --------
   The largest office photographs in existence are 500x333 (asset audit
   2026-08-30 — everything else in content/ is a legacy CMS thumbnail). This
   component's entire job is to make small photography look DELIBERATE:
   contained tiles capped near native width, generous gap, label-caps captions.
   max-width holds two tiles to ~508px each — an 8px stretch, invisible; a
   full-bleed treatment would be a 3x upscale and would look exactly as bad as
   it sounds. When the owner supplies larger photos, raise the cap (or remove
   it) and add tiles — auto-fit absorbs 2, 3 or 4 without new CSS. */
.office-duo {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
  gap: var(--grid-gap);
  margin-top: 2.5rem;
  max-width: 1040px;
}
.office-duo figure { margin: 0; }
.office-duo .media-frame img { width: 100%; height: auto; }
.office-cap {
  margin: 0.75rem 0 0;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}

/* 52. PROSE + PORTRAIT (`.prose-portrait`) — text with a face beside it -------
   Added 2026-09-20. The site had no way to put an image BESIDE body copy: the
   only options were a figure stacked in the flow (which interrupts the argument
   it is meant to accompany) or `.office-duo`, which is a tile grid for equals.
   Dropping a 375px portrait between two paragraphs pushed them apart and broke
   the read exactly where it needed to stay tight.

   No `float` is used anywhere on this site and this does not introduce one:
   floats pull out of flow and then need clearfixes, and on a narrow viewport
   they wrap text into 12-character columns. A two-track grid degrades honestly
   — one column on phones, text first, image after.

   The portrait track is capped at 300px rather than a fraction, because the
   largest portrait available is 480px wide and a fractional track would stretch
   it on a wide screen. Same reasoning as §4.x on .office-duo: make small
   photography look deliberate rather than blown up. Raise the cap if a larger
   original ever arrives.

   `align-items: start` so the image sits level with the first line of text and
   does not float in the vertical middle of a long paragraph. */
.prose-portrait {
  display: grid;
  gap: clamp(1.5rem, 3.5vw, 2.75rem);
  align-items: start;
}
@media (min-width: 860px) {
  /* 260px, not 300: inside `container--narrow` a 300px face leaves the copy at
     ~48 characters a line, below the 50–75 comfortable range. 260 buys the text
     back ~40px and raises the portrait's pixel density to 1.85x. */
  .prose-portrait { grid-template-columns: minmax(0, 1fr) minmax(0, 260px); }
}
.prose-portrait__body { min-width: 0; }
.prose-portrait__body > * + * { margin-top: 1.25rem; }
/* Capped and centred so the stacked (single-column) case does not blow the
   portrait up to full column width — the source is 480px wide, so a 460px
   render is 1.04x and goes soft on a retina phone. 300px keeps it at 1.6x.
   On the two-column layout the 260px track already wins, so this is inert. */
.prose-portrait__figure { margin: 0 auto; min-width: 0; max-width: 300px; }
.prose-portrait__figure img { width: 100%; height: auto; }

/* 53. ROUTING NOTE (`.routing-note`) — "you may want a different page" -------
   A signpost OFF the page, set inside a page whose whole job is to argue FOR
   the operation. It has to read as parenthetical or it reads as hedging: a
   plain <p> in the prose column looked exactly like the argument around it,
   so a man skimming could take "different operation from cosmetic enlargement"
   as part of the pitch rather than as a redirect meant for someone else.

   Borrows the panel grammar of `.designation` (11c) on purpose — 1px rule,
   2px amber left edge, paper-2 fill — because that is already the house
   signature for "set apart from the body copy". It is NOT `.designation`:
   that class carries §4.16 meaning (a named manufacturer designation, with
   conferrer and scope) and reusing it here would make a signpost look like a
   credential.

   Held at --fs-small and --ink-2 so it sits BELOW the surrounding prose in
   weight. An aside that shouts is just another section.

   <aside>, not <section>: it is tangential to the document flow, which is the
   element's actual definition, and it keeps the page's section count honest
   for the heading outline. */
.routing-note {
  border: 1px solid var(--line);
  border-left: 2px solid var(--amber-line);
  background: var(--paper-2);
  padding: clamp(1.25rem, 2.6vw, 1.75rem) clamp(1.25rem, 3vw, 2rem);
  max-width: 68ch;
}
.routing-note__eyebrow {
  margin: 0 0 0.5rem;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
/* A heading is allowed inside the aside for the one case where the block was
   CONVERTED from a section and its wording must not change (see the HA block on
   penile-implant-miami). Held near body scale, not h2 scale — promoting it would
   undo the point of making it an aside. */
.routing-note__title {
  margin: 0 0 0.5rem;
  font-size: var(--fs-body);
  font-weight: 600;
  line-height: 1.4;
  letter-spacing: 0;
  color: var(--ink);
  /* The base h2 carries max-width:24ch and text-wrap:balance, both sized for
     display-scale headlines. Inherited at body scale they strangle this one to
     ~235px inside a 553px panel, so it wrapped to three short lines beside
     full-width body copy. Reset both: at this size the measure the panel
     already sets is the right one. */
  max-width: none;
  text-wrap: pretty;
}
.routing-note__title a { color: var(--ink); text-decoration-thickness: 1px; }
.routing-note__title a:hover { color: var(--blue-text); }
.routing-note p {
  margin: 0;
  font-size: var(--fs-small);
  line-height: 1.65;
  color: var(--ink-2);
}
.routing-note strong { color: var(--ink); }
/* A picture may sit inside the aside (the Xiaflex signpost on peyronies-disease
   carries the carton photo). Capped and left-aligned with the copy: unbounded it
   rendered at panel width and turned a quiet signpost into the loudest block on
   the page, which is the opposite of what an aside is for. */
.routing-note figure { margin: 1.25rem 0 0; max-width: 260px; }
.routing-note figure img { width: 100%; height: auto; }
/* The link is the point of the block, so it keeps full contrast and a visible
   underline even though the surrounding text is muted (§7.9). */
.routing-note a {
  color: var(--blue-text);
  text-decoration: underline;
  text-underline-offset: 0.18em;
}
.routing-note a:hover { text-decoration-thickness: 2px; }
.routing-note a:focus-visible { outline: 2px solid var(--blue-text); outline-offset: 3px; }

/* 55. CURVATURE DEMO (`.curve-demo`) — degrees of curvature, on a slider -----
   Built ORIGINAL rather than embedding the manufacturer's version. Two reasons,
   both hard: their widget ships with the ad platform its page runs on (GTM /
   DoubleClick), and §0 bans third-party script outright — doubly so on a page
   where a patient is reading about their own condition, which is the OCR
   tracking-technologies pattern the no-trackers gate exists to prevent. And a
   prescribing relationship is not a copyright licence. Same answer the section
   spec already reached for the manufacturer's three-components figure: rebuild
   it as our own markup (the §34 `.tract` precedent).

   A native <input type="range"> is the whole interaction — keyboard, screen
   reader and touch support come free, which a scroll-driven reveal does not
   have. No canvas, no library, no animation, so nothing to guard under
   prefers-reduced-motion: the redraw is instantaneous by design.

   The shaft is a circular arc: for total deviation θ and length L the radius is
   L/θ, which keeps the LENGTH constant as the angle changes. Scaling a curve
   instead would suggest the condition shortens the penis by the amount it
   bends, which is not what the slider is showing. */
.curve-demo {
  border: 1px solid var(--line);
  border-left: 2px solid var(--amber-line);
  background: var(--paper-2);
  padding: clamp(1.25rem, 3vw, 2rem);
}
.curve-demo__fig { margin: 0; }
/* Capped and centred: at full panel width the diagram rendered ~800px wide and
   dominated the section. The viewBox is deliberately FIXED to the shape's
   bounds across the whole 0-90 range, not fitted per angle — a viewBox that
   retracked would rescale the shaft while dragging and destroy the very thing
   the arc model exists to show, that the length is not changing. */
.curve-demo__svg {
  display: block;
  width: 100%;
  max-width: 420px;
  height: auto;
  margin-inline: auto;
  color: var(--ink-2);
}
.curve-demo__axis { stroke: var(--ink-3); stroke-width: 2; stroke-dasharray: 6 7; opacity: 0.55; }
.curve-demo__shaft { stroke: currentColor; stroke-width: 26; stroke-linecap: round; fill: none; }
.curve-demo__controls { margin-top: 1.25rem; }
.curve-demo__row { display: flex; align-items: baseline; gap: 0.75rem; flex-wrap: wrap; }
.curve-demo__label {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.curve-demo__out {
  font-size: clamp(1.5rem, 3.2vw, 2.25rem);
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;   /* stops the readout jittering while dragging */
}
.curve-demo__range { width: 100%; margin-top: 0.75rem; accent-color: var(--amber-line); }
.curve-demo__range:focus-visible { outline: 2px solid var(--blue-text); outline-offset: 4px; }
.curve-demo__scale {
  display: flex;
  justify-content: space-between;
  margin-top: 0.25rem;
  font-size: var(--fs-micro);
  color: var(--ink-3);
}
.curve-demo__note {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
  font-size: var(--fs-small);
  line-height: 1.6;
  color: var(--ink-3);
}

/* 56. ANATOMY EXPLORER (`.anat`) — the structures of the penis, and what a
   plaque does to them. Peyronie's page, under the paragraph it illustrates.
   Two linked SVG views (cross-section + side) driven by ONE state: a native
   range for blood flow and a native radio group for the plaque. Native
   controls again, for component 55's reason — keyboard, screen reader and
   touch come free.

   Motion budget, per §0 ("it happens once"): a single fill from rest to rigid
   the first time the diagram is half in view, and a 650ms settle when the
   plaque option changes. Both cancel the moment the reader touches a control,
   and neither runs under prefers-reduced-motion — the static markup is
   already the rigid + top-plaque state, which carries the whole point.
   Nothing loops.

   Colour: sky is the blood (graphic use, allowed at 2.87:1), amber-line is the
   plaque (≥3:1 non-text). The plaque's number badge uses --amber-text so its
   white numeral clears AA (5.77:1). Highlighting dims everything else, so the
   state is never carried by colour alone. */
.anat {
  margin-top: clamp(2rem, 4vw, 3rem);
  border: 1px solid var(--line);
  border-left: 2px solid var(--amber-line);
  background: var(--paper-2);
  padding: clamp(1.25rem, 3vw, 2rem);
}
.anat__kicker {
  margin: 0 0 1rem;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--blue-text);
}
.anat__views { display: grid; gap: 1rem; }
@media (min-width: 720px) { .anat__views { grid-template-columns: 1fr 1fr; } }
.anat__view {
  margin: 0;
  background: var(--paper);
  border: 1px solid var(--line);
  padding: 0.75rem 0.75rem 0.625rem;
}
.anat__svg { display: block; width: 100%; height: auto; }
.anat__cap {
  margin-top: 0.375rem;
  text-align: center;
  font-size: var(--fs-micro);
  color: var(--ink-3);
}

/* SVG parts. Geometry lives in ucofl.js; only paint lives here. */
.anat__orient { font-size: 10px; font-weight: 600; letter-spacing: 0.16em; text-anchor: middle; fill: var(--ink-3); }
.anat__skin { fill: var(--paper); stroke: var(--ink-3); stroke-opacity: 0.4; stroke-width: 1.5; }
.anat__spong { fill: var(--paper-2); stroke: var(--ink-3); stroke-width: 1.6; }
.anat__urethra { fill: var(--paper); stroke: var(--ink-2); stroke-width: 1.4; }
.anat__corp { fill: var(--paper); }
.anat__blood { fill: var(--blue); }
.anat__tun { fill: none; stroke: var(--ink-2); stroke-linejoin: round; }
.anat__sep { stroke: var(--ink-2); stroke-dasharray: 3 3; }
.anat__plaque { fill: none; stroke: var(--amber-line); stroke-width: 8; stroke-linecap: round; stroke-linejoin: round; }
.anat__lead { stroke: var(--ink-3); stroke-width: 1; stroke-opacity: 0.55; }
.anat__mk circle { fill: var(--ink); }
.anat__mk--plaque circle { fill: var(--amber-text); }
.anat__mk text { fill: #fff; font-size: 10px; font-weight: 600; text-anchor: middle; }
.anat__wall { stroke: var(--ink-3); stroke-width: 1.5; stroke-opacity: 0.45; }
.anat__ref { stroke: var(--ink-3); stroke-width: 1.5; stroke-dasharray: 5 6; }
.anat__tube { fill: var(--blue); }
.anat__uline { fill: none; stroke: var(--ink-3); stroke-width: 1.4; stroke-dasharray: 4 4; }

/* Highlight: a key names a part, everything else steps back. data-part sits on
   <g> wrappers, never on the elements ucofl.js writes `opacity` onto — a CSS
   opacity on those would override the plaque's own fade. */
.anat__svg [data-part] { transition: opacity 240ms var(--ease); }
.anat[data-hl] .anat__svg [data-part] { opacity: 0.2; }
.anat[data-hl="corpora"] .anat__svg [data-part="corpora"],
.anat[data-hl="tunica"] .anat__svg [data-part="tunica"],
.anat[data-hl="urethra"] .anat__svg [data-part="urethra"],
.anat[data-hl="plaque"] .anat__svg [data-part="plaque"] { opacity: 1; }

/* Controls — hidden without JS, where they could not do anything. */
html:not(.js) .anat__controls { display: none; }
.anat__controls { margin-top: 1.25rem; }
.anat__row { display: flex; align-items: baseline; gap: 0.75rem; flex-wrap: wrap; }
.anat__label {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.anat__out {
  font-size: clamp(1.375rem, 2.8vw, 1.875rem);
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.anat__range { width: 100%; margin-top: 0.625rem; accent-color: var(--amber-line); }
.anat__range:focus-visible { outline: 2px solid var(--blue-text); outline-offset: 4px; }
.anat__scale {
  display: flex;
  justify-content: space-between;
  margin-top: 0.25rem;
  font-size: var(--fs-micro);
  color: var(--ink-3);
}
.anat__set {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin: 1.25rem 0 0;
  padding: 0;
  border: 0;
  min-width: 0;
}
.anat__set legend { float: left; margin-right: 0.5rem; padding: 0; }
.anat__opt {
  position: relative;
  display: inline-flex;
  align-items: center;
  gap: 0.3em;
  min-height: 44px;                                   /* §7 tap target */
  padding: 0 1rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: var(--paper);
  font-size: var(--fs-small);
  color: var(--ink-2);
  cursor: pointer;
  transition: border-color 220ms var(--ease), color 220ms var(--ease);
}
.anat__opt input { position: absolute; inset: 0; margin: 0; opacity: 0; cursor: pointer; }
.anat__opt:hover { border-color: var(--ink-3); }
.anat__opt.is-on { border-color: var(--ink); color: var(--ink); font-weight: 600; box-shadow: inset 0 -2px 0 var(--amber-line); }
.anat__opt:focus-within { outline: 2px solid var(--blue-text); outline-offset: 2px; }
.anat__hint { font-weight: 400; color: var(--ink-3); }
.anat__state {
  margin-top: 1.25rem;
  min-height: 3.2em;                                  /* no reflow as the sentence changes */
  font-size: var(--fs-small);
  line-height: 1.6;
  color: var(--ink-2);
}

/* Key — every entry always visible (nothing paginated behind a control, §5);
   pressing one pins its highlight, hover and focus preview it. */
.anat__keys {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.375rem;
}
@media (min-width: 720px) { .anat__keys { grid-template-columns: 1fr 1fr; } }
.anat__key {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  width: 100%;
  min-height: 44px;
  padding: 0.625rem 0.75rem;
  border: 1px solid transparent;
  border-radius: var(--radius-input);
  background: transparent;
  font: inherit;
  font-size: var(--fs-small);
  line-height: 1.5;
  text-align: left;
  color: var(--ink-2);
  cursor: pointer;
  transition: border-color 240ms var(--ease), background-color 240ms var(--ease);
}
.anat__key:hover,
.anat__key[aria-pressed="true"] { background: var(--paper); border-color: var(--line); }
.anat__key[aria-pressed="true"] { border-color: var(--ink-3); }
.anat__key:focus-visible { outline: 2px solid var(--blue-text); outline-offset: 2px; }
.anat__key strong { display: block; font-weight: 600; color: var(--ink); }
.anat__num {
  flex: none;
  display: grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  margin-top: 0.1rem;
  border-radius: 50%;
  background: var(--ink);
  color: #fff;
  font-size: 0.75rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.anat__num--plaque { background: var(--amber-text); }
.anat__note {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
  font-size: var(--fs-small);
  line-height: 1.6;
  color: var(--ink-3);
}
@media (prefers-reduced-motion: reduce) {
  .anat__svg [data-part], .anat__opt, .anat__key { transition: none; }
}

/* 57. PLAQUE TIMELINE (`.heal`) — how a Peyronie's plaque forms, over time.
   Peyronie's page, under the "What causes" paragraph. Component 56 answers
   WHERE and WHAT (the anatomy, and the bend); this answers HOW and WHEN: a
   small tear in the tunica during a bend, healing that lays down disorganized
   scar instead of orderly repair, a firm plaque — and how that maps onto the
   acute and chronic phases the next section describes. Built original, for
   component 55's reasons: the manufacturer's mechanism video is copyrighted,
   HCP-only and domain-locked, and §0 bans third-party script anyway.

   Same family as 56 on purpose — panel, kicker, two white views, native
   range, key list — so the page reads as one hand. Same vocabulary too:
   tunica = ink stroke, blood/inflammation = sky (graphic use, allowed at
   2.87:1), scar/plaque = --amber-line (3.99:1, ≥3:1 non-text). Stage labels
   are always visible (§5: nothing paginates behind a control); the current
   stage is marked by border, weight AND a filled numeral, never colour alone.

   Motion budget (§0, "it happens once"): a single pass from healthy to
   chronic the first time the views are half in view, cancelled by any input,
   nothing under prefers-reduced-motion. The static SVG is the acute phase —
   healing gone wrong — generated by the same geometry ucofl.js runs. */
.heal {
  margin-top: clamp(2rem, 4vw, 3rem);
  border: 1px solid var(--line);
  border-left: 2px solid var(--amber-line);
  background: var(--paper);          /* the section band is paper-2, so the panel inverts 56's pairing */
  padding: clamp(1.25rem, 3vw, 2rem);
}
.heal__kicker {
  margin: 0 0 1rem;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--blue-text);
}
.heal__views { display: grid; gap: 1rem; }
@media (min-width: 720px) { .heal__views { grid-template-columns: 1fr 1fr; } }
.heal__view {
  margin: 0;
  background: var(--paper-2);
  border: 1px solid var(--line);
  padding: 0.75rem 0.75rem 0.625rem;
}
.heal__svg { display: block; width: 100%; height: auto; }
.heal__cap {
  margin-top: 0.375rem;
  text-align: center;
  font-size: var(--fs-micro);
  color: var(--ink-3);
}

/* SVG parts. Geometry lives in ucofl.js; only paint lives here. */
.heal__orient { font-size: 10px; font-weight: 600; letter-spacing: 0.16em; text-anchor: middle; fill: var(--ink-3); }
.heal__tag { font-size: 10px; font-weight: 600; letter-spacing: 0.14em; text-anchor: middle; fill: var(--ink-2); }
.heal__tag--plaque { fill: var(--amber-text); }
.heal__body { fill: var(--blue); fill-opacity: 0.13; }
.heal__blood { fill: var(--blue); }
.heal__wall { fill: var(--paper); }
.heal__edge { fill: none; stroke: var(--ink-2); stroke-width: 2.2; stroke-linejoin: round; }
.heal__fib { fill: none; stroke: var(--ink-2); stroke-width: 0.9; stroke-opacity: 0.75; stroke-linecap: round; }
.heal__scar { fill: var(--amber-line); stroke: var(--amber-line); stroke-width: 1.5; }
.heal__tangle { fill: none; stroke: var(--amber-line); stroke-width: 1.4; stroke-linecap: round; }
.heal__zoom { fill: none; stroke: var(--ink-3); stroke-width: 1; stroke-dasharray: 3 3; opacity: 0.7; }
.heal__arrow { fill: none; stroke: var(--ink-2); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.heal__ring { fill: none; stroke: var(--ink-3); stroke-width: 1.5; }
.heal__mfib { fill: none; stroke: var(--ink-2); stroke-width: 2.2; stroke-opacity: 0.85; stroke-linecap: round; }
.heal__mtangle { fill: none; stroke: var(--amber-line); stroke-width: 2; stroke-linecap: round; }

/* Controls — hidden without JS, where they could not do anything. */
html:not(.js) .heal__controls { display: none; }
.heal__controls { margin-top: 1.25rem; }
.heal__row { display: flex; align-items: baseline; gap: 0.75rem; flex-wrap: wrap; }
.heal__label {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.heal__out {
  font-size: clamp(1.375rem, 2.8vw, 1.875rem);
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--ink);
}
.heal__range { width: 100%; margin-top: 0.625rem; accent-color: var(--amber-line); }
.heal__range:focus-visible { outline: 2px solid var(--blue-text); outline-offset: 4px; }
/* Four ticks for four stages: space-between puts them at 0, 1/3, 2/3 and 1,
   which is exactly where t = 0, 1, 2, 3 sit on the range. */
.heal__scale {
  display: flex;
  justify-content: space-between;
  margin-top: 0.25rem;
  font-size: var(--fs-micro);
  color: var(--ink-3);
}
.heal__scale span:first-child { text-align: left; }
.heal__scale span:last-child { text-align: right; }
.heal__state {
  margin-top: 1.25rem;
  min-height: 4.8em;                                  /* no reflow as the sentence changes */
  font-size: var(--fs-small);
  line-height: 1.6;
  color: var(--ink-2);
}

/* Stages — every one always visible; each is a button that moves the
   timeline to it. aria-current="step" marks where the timeline is. */
.heal__steps {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  display: grid;
  gap: 0.375rem;
}
@media (min-width: 720px) { .heal__steps { grid-template-columns: 1fr 1fr; } }
.heal__step {
  display: flex;
  align-items: flex-start;
  gap: 0.75rem;
  width: 100%;
  min-height: 44px;                                   /* §7 tap target */
  padding: 0.625rem 0.75rem;
  border: 1px solid transparent;
  border-radius: var(--radius-input);
  background: transparent;
  font: inherit;
  font-size: var(--fs-small);
  line-height: 1.5;
  text-align: left;
  color: var(--ink-2);
  cursor: pointer;
  transition: border-color 240ms var(--ease), background-color 240ms var(--ease), box-shadow 240ms var(--ease);
}
html:not(.js) .heal__step { cursor: default; }
.heal__step:hover { background: var(--paper-2); border-color: var(--line); }
.heal__step[aria-current] { background: var(--paper-2); border-color: var(--ink-3); box-shadow: inset 0 -2px 0 var(--amber-line); }
.heal__step:focus-visible { outline: 2px solid var(--blue-text); outline-offset: 2px; }
.heal__step strong { display: block; font-weight: 600; color: var(--ink); }
.heal__step small { display: block; font-size: var(--fs-micro); color: var(--ink-3); }
.heal__num {
  flex: none;
  display: grid;
  place-items: center;
  width: 1.5rem;
  height: 1.5rem;
  margin-top: 0.1rem;
  border: 1px solid var(--ink-3);
  border-radius: 50%;
  background: transparent;
  color: var(--ink);
  font-size: 0.75rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.heal__step[aria-current] .heal__num { background: var(--ink); border-color: var(--ink); color: #fff; }
.heal__note {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
  font-size: var(--fs-small);
  line-height: 1.6;
  color: var(--ink-3);
}
@media (prefers-reduced-motion: reduce) {
  .heal__step { transition: none; }
}

/* 58. CANDOR BOXES (`.candor`) — the before-any-prescription band ------------
   Low-testosterone hub centerpiece (notes/SPEC-testosterone-section.md §6.1;
   markup and rules carried verbatim from SPEC-low-testosterone.md §6, where
   this was drafted as 48 — never built, renumbered because 48–57 are taken).
   Three boxed honest gates (fertility, cardiovascular, differential) shown
   BEFORE the clinical prose — the page's whole competitive argument is that a
   urologist tells you these first, so the design says it first. Same doctrine
   as .spectrum__note (46) and .designation__note (§4.16): claim-gating text
   ships at FULL body size, boxed, never fine print, never collapsed, never
   demoted "for space."

   Boxes are white on the soft band, 1px hairline, with a 2px left rule; the
   KEY item (fertility) warms that rule to --amber-line (3.99:1 non-text ✓ on
   white) — one warm accent for the band, the era/spectrum precedent for
   light-surface sets. Marks follow §4.17 SIGNAL grammar (cap line y=12,
   baseline y=36, one orange signal square per mark).

   Motion reuses fadeRise + sgDraw/sgRise + sgRiseHover — ZERO new keyframes,
   zero new JS. sgRiseHover is safe as the hover replay because the reveal
   applies sgRise: animation-name changes, so the restart actually fires
   (§4.17 silent-no-op trap). Marks are authored at final state, and every
   hiding rule is scoped to `.js`, so no-JS and the global reduced-motion guard
   both render them complete and static with no extra code.

   Contrast, computed not assumed: --ink h3 19.24:1, --ink-2 body 9.09:1 on
   white; --blue-text link 5.76:1; --amber-line rules 3.99:1 (non-text). */
.candor {
  list-style: none;
  margin: clamp(2.5rem, 5vw, 3.5rem) 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1.5rem, 3vw, 2.5rem);
  align-items: stretch;
}
.candor__item {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  background: var(--paper);
  border: 1px solid var(--line);
  border-left: 2px solid var(--line);
  padding: clamp(1.5rem, 3vw, 2rem);
}
.candor__item--key { border-left-color: var(--amber-line); }
.candor__mark { width: 44px; height: 44px; display: block; color: var(--ink-2); }
.candor__item h3 {
  margin: 1rem 0 0.5rem;
  font-size: var(--fs-h4);
  font-weight: 600;
  line-height: 1.3;
  color: var(--ink);
}
.candor__item p {
  margin: 0;
  font-size: var(--fs-body);      /* FULL body size — the §9/hub-FDA doctrine.
                                     Never --fs-small here: these are claim
                                     gates, not captions. */
  line-height: 1.6;
  color: var(--ink-2);
  max-width: 46ch;
}
.candor__link {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  min-height: 44px;               /* §7 tap target */
  margin-top: 0.875rem;
  font-size: var(--fs-small);
  font-weight: 500;
  color: var(--blue-text);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.candor__link:hover { color: var(--ink); }
.candor__link:hover .arrow { transform: translateX(4px); color: var(--amber-text); }
.candor__link .arrow { transition: transform 240ms var(--ease), color 240ms var(--ease); }
.candor__link:focus-visible { outline: 2px solid var(--blue-text); outline-offset: 3px; }

/* choreography: boxes rise west→east, then each mark draws its strokes and
   raises its signal square.
   One .reveal on the UL — everything staggers off nth-child, no per-item
   observer targets. */
.js .reveal:not(.is-in) .candor__item { opacity: 0; transform: translateY(24px); }
.reveal.is-in .candor__item {
  animation: fadeRise 700ms var(--ease) both;
}
.reveal.is-in .candor__item:nth-child(1) { animation-delay: 0ms; }
.reveal.is-in .candor__item:nth-child(2) { animation-delay: 120ms; }
.reveal.is-in .candor__item:nth-child(3) { animation-delay: 240ms; }

.candor__mark .sg, .candor__mark .sg-2 { stroke-dasharray: 36; }
.js .reveal:not(.is-in) .candor__mark .sg,
.js .reveal:not(.is-in) .candor__mark .sg-2 { stroke-dashoffset: 36; }
.reveal.is-in .candor__mark .sg,
.reveal.is-in .candor__mark .sg-2 { animation: sgDraw 700ms var(--ease) both; }
.candor__mark .sg-mark { fill: var(--signal); }
.js .reveal:not(.is-in) .candor__mark .sg-mark { opacity: 0; }
.reveal.is-in .candor__mark .sg-mark { animation: sgRise 900ms var(--ease) both; }
.candor__item:nth-child(1) .candor__mark .sg { animation-delay: 250ms; }
.candor__item:nth-child(2) .candor__mark .sg { animation-delay: 430ms; }
.candor__item:nth-child(3) .candor__mark .sg { animation-delay: 610ms; }
.candor__item:nth-child(1) .candor__mark .sg-mark { animation-delay: 420ms; }
.candor__item:nth-child(2) .candor__mark .sg-mark { animation-delay: 600ms; }
.candor__item:nth-child(3) .candor__mark .sg-mark { animation-delay: 780ms; }

/* hover: the whole mark acknowledges the cursor; only the signal replays */
.candor__item:hover .candor__mark { color: var(--ink); }
.candor__item:hover .candor__mark .sg-mark { animation: sgRiseHover 700ms var(--ease); }
@media (prefers-reduced-motion: reduce) {
  .candor__item:hover .candor__mark .sg-mark { animation: none; }
}

@media (max-width: 900px) {
  .candor { grid-template-columns: 1fr; }
  .candor__item p { max-width: 64ch; }
}

/* 59. T-SCALE (`.t-scale`) — what counts as low, on a slider -----------------
   Symptoms page (SPEC-testosterone-section.md §6.2). Its one job is to make
   300 ng/dL legible as what the AUA calls it — a cut-off IN SUPPORT OF a
   diagnosis, made only after two early-morning draws — and never as a verdict
   on the reader's own number. That is why the note under the slider carries
   the whole meaning at text weight, and why the state line changes WORDS, not
   only colour, as the value crosses 300.

   Sibling of 55: same panel grammar (hairline, 2px --amber-line left edge,
   --paper-2 fill), native <input type="range"> as the whole interaction, no
   canvas, no library, nothing loops. The picture is a SIGNAL gauge (§4.17):
   a post grows from the line and the signal square rides on top of it — flat
   on the line at 100, 12 units up at 300, the full 48 at 900.

   No-JS: the markup IS the 300 state and `.t-scale__controls` ships `hidden`
   (ucofl.js removes it), so a reader without script sees a correct, complete
   diagram and the note, not a dead slider. The only motion is a 240ms ease on
   the post and square; reduced motion removes it, and the redraw is instantaneous.

   The SVG root sets fill="none" for the strokes, so the tick text MUST set its
   own fill or it renders invisible — and stroke:none, or it inherits the 2px
   stroke and bloats. */
.t-scale {
  border: 1px solid var(--line);
  border-left: 2px solid var(--amber-line);
  background: var(--paper-2);
  padding: clamp(1.25rem, 3vw, 2rem);
}
.t-scale__kicker {
  margin: 0 0 1rem;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--blue-text);          /* 5.36:1 on paper-2 */
}
.t-scale__fig { margin: 0; }
.t-scale__svg {
  display: block;
  width: 100%;
  max-width: 420px;
  height: auto;
  margin-inline: auto;
  color: var(--ink-2);
}
.t-scale__line { stroke-width: 2; }
.t-scale__post { stroke: var(--ink-2); stroke-width: 3; transition: d 240ms var(--ease); }
.t-scale__mark { fill: var(--signal); stroke: none; transition: y 240ms var(--ease); }  /* the ONE
  amber element; --amber-line 3.90:1 on paper-2. Where d/y do not transition
  (older engines) the gauge jumps — acceptable; no JS animation */
.t-scale__tick {
  fill: var(--ink-3);
  stroke: none;
  font-size: 11px;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
}
.t-scale__cap {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 0.75rem;
  margin-top: 1rem;
}
.t-scale__label {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.t-scale__controls .t-scale__label { display: block; margin-top: 1.25rem; }
.t-scale__out {
  font-size: clamp(1.5rem, 3.2vw, 2.25rem);
  font-weight: 500;
  letter-spacing: -0.02em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;   /* stops the readout jittering while dragging */
}
.t-scale__state {
  flex-basis: 100%;
  margin: 0.375rem 0 0;
  font-size: var(--fs-body);
  color: var(--ink);
}
.t-scale__range { width: 100%; margin-top: 0.75rem; accent-color: var(--amber-line); }
.t-scale__range:focus-visible { outline: 2px solid var(--blue-text); outline-offset: 4px; }
.t-scale__scale {
  display: flex;
  justify-content: space-between;
  margin-top: 0.25rem;
  font-size: var(--fs-micro);
  color: var(--ink-3);
}
.t-scale__note {
  margin-top: 1rem;
  padding-top: 1rem;
  border-top: 1px solid var(--line);
  font-size: var(--fs-small);
  line-height: 1.6;
  color: var(--ink-3);
}
@media (prefers-reduced-motion: reduce) {
  .t-scale__post, .t-scale__mark { transition: none; }
}

/* 60. REGULATORY NOTE (`.reg-note`) — what the FDA label says ---------------
   Options + fertility pages (SPEC-testosterone-section.md §6.3). For a
   labeling fact the reader must not miss: the age-related-hypogonadism
   Limitation of Use every testosterone label carries, and what "off-label"
   means for clomiphene / anastrozole / hCG in men. A true sentence in fine
   print is still a deceptive net impression, so this sits AT prose weight:
   --fs-body, boxed, never --fs-small, never collapsed into a <details>.

   Why not 53 `.routing-note`: that one means "you may want a different page"
   and is held deliberately BELOW prose weight. Why not `.advisory` (46): that
   box is a single small-type paragraph; this one carries an eyebrow, its own
   heading in the outline, several body paragraphs and a sources line, and
   must be body size. Same border grammar as both, so it reads as family.

   White on a band--soft section (the default); on a white section add
   `.reg-note--tint` for the --paper-2 fill. Static: the section's .reveal is
   the only motion, and the global reduced-motion guard covers it. */
.reg-note {
  border: 1px solid var(--line);
  border-left: 2px solid var(--amber-line);
  background: var(--paper);
  padding: clamp(1.5rem, 3vw, 2rem);
  max-width: 64ch;
}
.reg-note--tint { background: var(--paper-2); }
.reg-note__eyebrow {
  margin: 0;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--blue-text);          /* 5.76:1 white, 5.36:1 paper-2 */
}
.reg-note__title {
  margin: 0.5rem 0 0.75rem;
  font-size: var(--fs-h4);
  font-weight: 600;
  line-height: 1.3;
  color: var(--ink);
}
.reg-note p {
  margin: 0 0 0.75rem;
  font-size: var(--fs-body);        /* NEVER --fs-small: this is a claim gate */
  line-height: 1.6;
  color: var(--ink-2);              /* 9.09:1 white */
}
/* specificity (0,2,0) beats `.reg-note p` (0,1,1) — both the eyebrow and the
   sources line are <p>s that must keep their own metrics */
.reg-note p.reg-note__eyebrow { margin: 0; font-size: var(--fs-label); line-height: inherit; color: var(--blue-text); }
.reg-note p.reg-note__src {
  margin: 0;
  font-size: var(--fs-micro);
  line-height: 1.55;
  color: var(--ink-3);              /* 5.80:1 white, 5.40:1 paper-2 */
}

/* 45. SVC SUB-LINKS — second-level routing under a .svc-row (hub pages) ------
   Solves the hub problem the men's-health section was rebuilt for: 17
   destinations cannot be a flat list, and they cannot nest INSIDE .svc-row
   because the row is one <a> and NESTED ANCHORS ARE INVALID HTML. So each li
   carries an optional <nav class="svc-sub"> AFTER the row anchor: small pill
   links, indented to the .svc-main column so they read as children of the
   title above them.

   Pills, not text links, for two reasons: (1) §7 tap targets — inline text
   links in a wrapping row cannot guarantee 44px; a bordered pill with
   min-height can. (2) On the midnight band a hairline border is the calmest
   possible affordance — no fill, no colour shout, consistent with "colour is
   an event".

   Contrast on --midnight #1E262C (re-measured 2026-11): --mist text 10.33:1, hover #fff
   15.34:1, orange border (#F7B55A) = non-text graphic 8.56:1, focus ring --blue-air 10.60:1. */
.svc-sub {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  /* left inset = row padding + the 4rem .num column + the grid gap, so chips
     align under the h3, not under the numeral */
  padding: 0 clamp(0.5rem, 2vw, 1.5rem) 1.75rem
             calc(clamp(0.5rem, 2vw, 1.5rem) + 4rem + var(--grid-gap));
  /* pull up toward the row it belongs to; the li border separates GROUPS,
     proximity binds chip to parent */
  margin-top: -0.5rem;
}
.svc-sub a,
.svc-sub__link {
  /* NOTE the explicit class: the sitewide prose rule `main a:not([class])` is
     specificity (0,1,2) and outranks `.svc-sub a` (0,1,1), so an unclassed chip
     inherits the prose underline. That rule is deliberately scoped to UNCLASSED
     links — carrying a class IS the sanctioned opt-out, so the markup classes
     each chip rather than escalating specificity here. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;                      /* §7 tap target */
  padding: 0.4rem 1.05rem;
  border: 1px solid var(--line-dark);
  border-radius: 999px;
  font-size: var(--fs-small);
  font-weight: 500;
  color: var(--mist);
  text-decoration: none;
  transition: color 240ms var(--ease), border-color 240ms var(--ease);
}
.svc-sub a:hover,
.svc-sub__link:hover { color: #fff; border-color: var(--amber); }
.svc-sub a:focus-visible,
.svc-sub__link:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }
@media (max-width: 700px) {
  /* .svc-row's num column drops to 2.5rem at this width — track it */
  .svc-sub { padding-left: calc(clamp(0.5rem, 2vw, 1.5rem) + 2.5rem + var(--grid-gap)); }
}
@media (max-width: 480px) {
  /* at phone width the indent costs too much line length; proximity alone
     carries the parent relationship */
  .svc-sub { padding-left: clamp(0.5rem, 2vw, 1.5rem); }
}

/* 46. TREATMENT SPECTRUM (`.spectrum`) — ED options as one rising line -------
   Men's-health hub. The section's argument ("you are never out of options")
   drawn in §4.17 SIGNAL grammar: four stations on ONE hairline, the post and
   its signal square higher at every station, at the top at the definitive
   option. Same doctrine as .era
   (43): draws ONCE via the existing .reveal observer, marks authored at FINAL
   state so the global reduced-motion guard renders them complete and static,
   one signal square per mark in --signal (--amber-line on light).

   Layout is the trust strip's, not the era's: a shared ::before hairline
   crosses the row at y=31.5px, the foot of every stop's post (y=32).
   <=900px drops to 2x2 and the shared line switches off — a shared line
   cannot cross rows, and each mark carries its own post, so the
   invariant holds per-mark.

   Reuses sgSweep/sgDraw/sgRise + sgRiseHover — ZERO new keyframes.
   sgRiseHover is safe as the hover replay because the reveal applies sgRise:
   the animation-name CHANGES, so the restart actually fires (§4.17 gotcha). */
.spectrum { margin-top: clamp(2.5rem, 5vw, 3.5rem); }
.spectrum__list {
  list-style: none;
  margin: 0; padding: 0;
  position: relative;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: var(--grid-gap);
}
/* the shared hairline at the foot of every stop's post, y=32 (48px marks at cell top) */
.spectrum__list::before {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 31.5px;
  height: 1px;
  background: var(--line);
  transform-origin: left;
}
.js .reveal:not(.is-in) .spectrum__list::before { transform: scaleX(0); }
.reveal.is-in .spectrum__list::before { animation: sgSweep 1100ms var(--ease) both; }
.spectrum__stop {
  display: flex;
  flex-direction: column;
  align-items: flex-start;   /* §4.17: center would float short columns and
                                break the fused hairline */
}
.spectrum__mark { width: 48px; height: 48px; display: block; color: var(--ink-2); }
.spectrum__stop h3 {
  margin: 1rem 0 0.35rem;
  font-size: var(--fs-h4);
  font-weight: 600;
  line-height: 1.3;
}
.spectrum__stop h3 a { color: inherit; text-decoration: none; }
.spectrum__stop h3 a:hover { color: var(--blue-text); }
.spectrum__stop h3 a:focus-visible { outline: 2px solid var(--blue-text); outline-offset: 3px; }
.spectrum__stop p { margin: 0; font-size: var(--fs-small); color: var(--ink-2); max-width: 34ch; }
.spectrum__links { list-style: none; margin: 0.75rem 0 0; padding: 0; }
.spectrum__links a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;               /* §7 tap target on stacked small links */
  font-size: var(--fs-small);
  font-weight: 500;
  color: var(--blue-text);        /* 5.36:1 on paper-2 */
  text-decoration: underline;
  text-underline-offset: 3px;
}
.spectrum__links a:hover { color: var(--ink); }

/* choreography: posts draw, signals rise, west to east */
.spectrum__mark .sg { stroke-dasharray: 36; stroke-dashoffset: 0; }
.js .reveal:not(.is-in) .spectrum__mark .sg { stroke-dashoffset: 36; }
.reveal.is-in .spectrum__mark .sg { animation: sgDraw 700ms var(--ease) both; }
.spectrum__mark .sg-mark { fill: var(--signal); }
.js .reveal:not(.is-in) .spectrum__mark .sg-mark { opacity: 0; }
.reveal.is-in .spectrum__mark .sg-mark { animation: sgRise 900ms var(--ease) both; }
.spectrum__stop:nth-child(1) .spectrum__mark .sg { animation-delay: 100ms; }
.spectrum__stop:nth-child(2) .spectrum__mark .sg { animation-delay: 280ms; }
.spectrum__stop:nth-child(3) .spectrum__mark .sg { animation-delay: 460ms; }
.spectrum__stop:nth-child(4) .spectrum__mark .sg { animation-delay: 640ms; }
.spectrum__stop:nth-child(1) .spectrum__mark .sg-mark { animation-delay: 260ms; }
.spectrum__stop:nth-child(2) .spectrum__mark .sg-mark { animation-delay: 440ms; }
.spectrum__stop:nth-child(3) .spectrum__mark .sg-mark { animation-delay: 620ms; }
.spectrum__stop:nth-child(4) .spectrum__mark .sg-mark { animation-delay: 800ms; }

/* hover: the whole mark acknowledges the cursor; only the signal replays */
.spectrum__stop:hover .spectrum__mark { color: var(--ink); }
.spectrum__stop:hover .spectrum__mark .sg-mark { animation: sgRiseHover 700ms var(--ease); }
@media (prefers-reduced-motion: reduce) {
  .spectrum__stop:hover .spectrum__mark .sg-mark { animation: none; }
}

/* the FDA note — the §9/FDA keystone of this band. Body-size, boxed like
   .era__scope so it reads as substance, never as fine print. NEVER shrink it
   to micro, collapse it behind a toggle, or drop it "for space". */
/* `.advisory` is the general form of this box — the same regulatory-note
   treatment, usable outside a .spectrum. Added 2026-09-07 when the FDA status
   had to go onto the ED flagship and the PRP/exosome child pages, none of which
   contain a spectrum. Same rule, two names; do NOT fork a second style. */
.advisory,
.spectrum__note {
  margin: clamp(2rem, 4vw, 2.75rem) 0 0;
  border: 1px solid var(--line);
  border-left: 2px solid var(--amber-line);
  background: var(--paper);
  padding: 1.25rem 1.5rem;
  font-size: var(--fs-small);
  line-height: 1.6;
  color: var(--ink-2);          /* 9.09:1 — deliberately darker than
                                   era__scope; this one is a claim gate */
  max-width: 72ch;
}
.spectrum__close { margin: 1.75rem 0 0; color: var(--ink-2); }

@media (max-width: 900px) {
  .spectrum__list { grid-template-columns: repeat(2, 1fr); row-gap: 2.5rem; }
  /* accepted trade: per-mark posts carry the line; a shared
     hairline cannot cross grid rows */
  .spectrum__list::before { display: none; }
}
@media (max-width: 480px) {
  .spectrum__list { grid-template-columns: 1fr; }
}

/* 47. HERO STILL SETTLE — the photographic counterpart of fade-rise ---------
   Opt-in, for cinema heroes using a STILL instead of footage. The frame
   arrives fractionally large and settles to rest over 2.6s — the picture
   takes one breath and stands still. §0.5 compliant: same gesture as
   fade-rise, finite, HAPPENS ONCE (no loop, no scroll linkage, and nothing
   the reader is reading moves — the type sits on top, static). Reduced
   motion kills it, leaving the finished frame.

   ⚠️ The REST state is the finished frame. The initial scale lives only in
   the keyframe's `from`, and the animation is applied under `.js` — so a
   no-JS visitor (or one whose reveal-guard timeout strips `.js`) sees
   scale(1), never a permanently over-scaled image. Authoring the base at
   scale(1.035) would ship a broken crop to exactly those readers.
   transform-origin favours the upper frame so the settle reads as the
   horizon easing down, not a zoom-out. .page-hero--cinema already clips. */
.hero-media--settle img {
  transform: none;
  transform-origin: 50% 35%;
}
.js .hero-media--settle img { animation: heroSettle 2600ms var(--ease) both; }
@keyframes heroSettle {
  from { transform: scale(1.035); }
  to   { transform: none; }
}
@media (prefers-reduced-motion: reduce) {
  .hero-media--settle img { animation: none; transform: none; }
}

/* 49. SPECTRUM MAP (`.spectrum--map`) — the ED flagship's five-stop ladder ----
   A VARIANT of 46, not a sibling: same anatomy (stops on one fused hairline,
   the signal higher at every station, the boxed FDA note at full weight),
   with two differences only.

   (1) FIVE columns. The hub compresses lifestyle+oral into one stop because it
       is a map of a map; the flagship separates them because its sections do.
       46 hard-codes repeat(4,1fr) and staggers four stops; this extends both.
   (2) Destinations are SAME-PAGE anchors — the stops are this page's table of
       contents. Anchor targets therefore need scroll-margin for the fixed
       header, or every jump buries its own heading under the bar. The rule is
       scoped to `main [id]` rather than the five ladder targets because
       #consult and the FAQ heading suffer the identical clipping today; it
       fixes the class of bug, is purely additive, and cannot affect a page
       with no in-page anchors.

   Motion, keyframes, hover replay, reduced-motion and the <=900px shared-line
   trade are ALL inherited from 46 — zero new keyframes, zero new JS. */
main [id] { scroll-margin-top: calc(72px + 1.5rem); }
.spectrum--map .spectrum__list { grid-template-columns: repeat(5, 1fr); }
.spectrum--map .spectrum__stop:nth-child(5) .spectrum__mark .sg { animation-delay: 820ms; }
.spectrum--map .spectrum__stop:nth-child(5) .spectrum__mark .sg-mark { animation-delay: 980ms; }
/* the stop titles are same-page links and are the primary nav on this page —
   guarantee the §7 tap target rather than relying on line-box height */
.spectrum--map .spectrum__stop h3 a {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
@media (max-width: 900px) {
  .spectrum--map .spectrum__list { grid-template-columns: repeat(2, 1fr); }
  /* the 5th stop would sit alone on its row at 2-col — let it span */
  .spectrum--map .spectrum__stop:nth-child(5) { grid-column: 1 / -1; }
}


/* 61. FIT (`.fit`) — where the Himplant sits: a static cross-section ----------
   notes/SPEC-himplant-redesign.md §5.19. Replaces a third-party implant
   illustration of unknown licence (D5b) with a house diagram that says one
   thing: the implant sits under the skin, around the shaft, outside the
   erectile bodies. Circles, not anatomy drawn figuratively — clinical and
   non-explicit. Monochrome structure (--ink-2, 9.09:1) with exactly ONE warm
   element, the implant arc in --amber-line (3.99:1 on white, ≥3:1 non-text).
   A11y: the SVG is aria-hidden; the ordered list carries every label, and
   colour is never the only signal (the implant key is also the thick ring,
   and is named). Motion: the arc DRAWS once on reveal (sgDraw, §4.17 verb).
   Authored complete — the hiding rule is scoped to `.js .reveal:not(.is-in)`,
   so no-JS and the global reduced-motion guard render it finished.
   Spec deviation: the draft's leader tick (M160 24v8) pointed at no label and
   was dropped; the urethra key is a smaller ring so the key maps to sizes. */
.fit { max-width: 420px; margin: 2.5rem auto; }
.fit__svg { display: block; width: 100%; height: auto; color: var(--ink-2); }
.fit__implant { stroke: var(--amber-line); stroke-dasharray: 36; }
.fit__skin { stroke-width: 2; }
.fit__corp, .fit__ure { stroke-width: 1.5; }
.fit__cap { margin-top: 1rem; }
.js .fit.reveal:not(.is-in) .fit__implant { stroke-dashoffset: 36; }
.fit.reveal.is-in .fit__implant { animation: sgDraw 800ms var(--ease) 300ms both; }
.fit__keys { list-style: none; padding: 0; margin: 1rem 0 0; font-size: var(--fs-small); color: var(--ink-2); }
.prose .fit__keys li { display: flex; gap: 0.6rem; align-items: baseline; margin: 0.35rem 0; padding: 0; }
.prose .fit__keys { padding-left: 0; display: block; max-width: none; }
.fit__k { width: 14px; height: 14px; border: 1.5px solid currentColor; border-radius: 50%; flex: none; transform: translateY(2px); }
.fit__k--implant { border-color: var(--amber-line); border-width: 4px; }
.fit__k--sm { width: 9px; height: 9px; margin-inline: 2.5px; }
.fit__note { font-size: var(--fs-micro); color: var(--ink-3); margin: 0.75rem 0 0; }

/* 62. SISTER (`.sister`) — the same-owner practice band ----------------------
   SPEC-himplant-redesign §5.19. A prose-weight outbound panel to INTIMÉ Miami:
   distinct from 53 .routing-note (held BELOW prose weight, "you may be on the
   wrong page") and 60 .reg-note (a regulatory fact). This one says "you are on
   the right page; the practice page is over there". The same-owner disclosure
   is the sentence itself. One instance per page. Hairline + a 2px
   --amber-line left rule (3.99:1 non-text on --paper-2 ≈ 3.7:1, ≥3:1 ✓).
   Text: --ink title 19.24:1, --ink-2 body ~8.4:1 on --paper-2, eyebrow
   --blue-text 5.36:1 on --paper-2. Motion: .reveal only + the house arrow
   nudge (≤240ms, killed by the reduced-motion guard). */
.sister {
  border: 1px solid var(--line);
  border-left: 2px solid var(--amber-line);
  background: var(--paper-2);
  padding: clamp(1.5rem, 3vw, 2rem);
  max-width: 64ch;
  margin: 0;              /* left-aligned with .path-next above it (narrow container) */
  border-radius: 4px;
}
.sister__eyebrow {
  font-size: var(--fs-label); font-weight: 600; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--blue-text); margin: 0;
}
.sister__title { font-size: var(--fs-h4); font-weight: 600; line-height: 1.35; margin: 0.5rem 0 0.75rem; }
.sister__title a { color: var(--ink); text-decoration: none; border-bottom: 2px solid var(--amber-line); }
.sister__title a .arrow { display: inline-block; transition: transform 240ms var(--ease); }
.sister__title a:hover .arrow,
.sister__title a:focus-visible .arrow { transform: translateX(4px); }
.sister__title a:focus-visible { outline: 2px solid var(--blue-text); outline-offset: 3px; }
.sister p:not(.sister__eyebrow) { font-size: var(--fs-body); color: var(--ink-2); margin: 0; }

/* 61–62 support: `.candor__kicker` — a page-scoped label inside component 58.
   SPEC-himplant-redesign §12: the legacy page's three orphan top headings
   ("Medical-Grade Solution" / "Natural Results" / "Board-Certified Urologist")
   migrate as the candor boxes' kickers; their heading rank does not. 58 has no
   kicker element, so this adds one without touching 58's own rules.
   --blue-text 5.76:1 on white. */
.candor__item .candor__kicker {
  margin: 1rem 0 0;
  font-size: var(--fs-label); font-weight: 600; letter-spacing: 0.16em;
  text-transform: uppercase; color: var(--blue-text); line-height: 1.3;
}
.candor__kicker + h3 { margin-top: 0.4rem; }

/* 63–66 shared: `.gfx` — the panel the four vasectomy-reversal graphics sit in.
   notes/SPEC-vasectomy-reversal-redesign.md §8.0. Lifted from the owner-approved
   prototype (2026-10-08) with renames only: the prototype's bare `.steps`,
   `.chips`, `.note`, `.out`, `.scale` and `.cap-row` became `gfx__*` because
   `.steps` is already component 26 (numbered step list) and the rest are too
   generic to ship unscoped. Geometry, colour and timing unchanged.
   Panel grammar is the sibling of 55/59: 1px --line, a 2px --amber-line left
   edge (3.99:1 non-text), --paper-2 fill. `.gfx--night` is design-system
   exception E1 (midnight window panel, max two per page, never adjacent to a
   dark band): --mist text 10.33:1, --blue-air kicker 10.60:1, --amber edge
   8.56:1, white caption 15.34:1 (2026-11 palette). Controls are native (button aria-pressed,
   radio fieldsets, range), ≥44px, with --blue focus rings (--blue-air inside night panels).
   Panel paragraphs drop the global 64ch cap so caption and note run the full
   panel width, as approved. */
.gfx { border: 1px solid var(--line); border-left: 2px solid var(--amber-line); background: var(--paper-2); padding: clamp(1.25rem, 3vw, 2rem); }
.gfx--night { background: var(--midnight); border-color: var(--line-dark); border-left-color: var(--amber); color: var(--mist); }
.gfx p { max-width: none; }
.gfx figure { margin: 0; }
.gfx svg { display: block; width: 100%; height: auto; }
.gfx .svg-text { font-family: "Switzer", sans-serif; }
.gfx__kicker { margin: 0 0 1rem; font-size: var(--fs-label); font-weight: 600; letter-spacing: 0.16em; text-transform: uppercase; color: var(--blue-text); }
.gfx--night .gfx__kicker { color: var(--blue-air); }
.gfx__cap { margin-top: 1rem; font-size: var(--fs-body); color: var(--ink); }
.gfx--night .gfx__cap { color: #fff; }
.gfx__state { margin: 0.375rem 0 0; font-size: var(--fs-body); min-height: 1.65em; }
.gfx--night .gfx__state { color: var(--mist); }
.gfx__label { font-size: var(--fs-label); font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-3); }
.gfx--night .gfx__label { color: var(--mist); }
.gfx__controls { margin-top: 1.25rem; }
.gfx__controls[hidden] { display: none !important; }
.gfx__note { margin-top: 1rem; padding-top: 1rem; border-top: 1px solid var(--line); font-size: var(--fs-small); line-height: 1.6; color: var(--ink-3); }
.gfx--night .gfx__note { border-top-color: var(--line-dark); color: var(--mist); }
/* stepper buttons */
.gfx__steps { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.75rem 0 0; padding: 0; list-style: none; }
.gfx__steps button { min-height: 44px; padding: 0 1rem; border-radius: 999px; border: 1px solid var(--line-dark); background: transparent; color: var(--mist); font: 600 var(--fs-label)/1 "Switzer", sans-serif; letter-spacing: 0.14em; text-transform: uppercase; cursor: pointer; transition: background 240ms var(--ease), color 240ms var(--ease), border-color 240ms var(--ease); }
.gfx__steps button[aria-pressed="true"] { background: #fff; color: var(--ink); border-color: #fff; }
.gfx__steps button:focus-visible { outline: 2px solid var(--blue); outline-offset: 3px; }
.gfx:not(.gfx--night) .gfx__steps button { border-color: var(--ink-3); color: var(--ink-2); }
.gfx:not(.gfx--night) .gfx__steps button[aria-pressed="true"] { background: var(--ink); color: #fff; border-color: var(--ink); }
.gfx:not(.gfx--night) .gfx__steps button:focus-visible { outline-color: var(--blue-text); }
/* radio chips */
.gfx__chips { display: flex; flex-wrap: wrap; gap: 0.5rem; margin: 0.5rem 0 0; padding: 0; border: 0; }
.gfx__chips legend { padding: 0; margin: 0 0 0.25rem; }
.gfx__chips label { display: inline-flex; align-items: center; min-height: 44px; padding: 0 1rem; border-radius: 999px; border: 1px solid var(--line-dark); color: var(--mist); font: 600 var(--fs-label)/1 "Switzer", sans-serif; letter-spacing: 0.14em; text-transform: uppercase; cursor: pointer; transition: background 240ms var(--ease), color 240ms var(--ease); }
.gfx__chips input { position: absolute; opacity: 0; width: 1px; height: 1px; }
.gfx__chips input:checked + label { background: #fff; color: var(--ink); border-color: #fff; }
.gfx__chips input:focus-visible + label { outline: 2px solid var(--blue); outline-offset: 3px; }
.gfx:not(.gfx--night) .gfx__chips label { border-color: var(--ink-3); color: var(--ink-2); }
.gfx:not(.gfx--night) .gfx__chips input:checked + label { background: var(--ink); color: #fff; border-color: var(--ink); }
.gfx:not(.gfx--night) .gfx__chips input:focus-visible + label { outline-color: var(--blue-text); }
/* range */
.gfx input[type=range] { width: 100%; margin-top: 0.75rem; accent-color: var(--amber-line); }
.gfx--night input[type=range] { accent-color: var(--amber); }
.gfx input[type=range]:focus-visible { outline: 2px solid var(--blue-text); outline-offset: 4px; }
.gfx--night input[type=range]:focus-visible { outline-color: var(--blue); }
.gfx__scale { display: flex; justify-content: space-between; margin-top: 0.25rem; font-size: var(--fs-micro); color: var(--ink-3); }
.gfx__out { font-size: clamp(1.5rem, 3.2vw, 2.25rem); font-weight: 500; letter-spacing: -0.02em; color: var(--ink); font-variant-numeric: tabular-nums; }
.gfx--night .gfx__out { color: #fff; }
.gfx__cap-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.25rem 0.75rem; margin-top: 1rem; }
.gfx__cap-row .gfx__state { flex-basis: 100%; }
/* section spacing: a panel that follows prose, and prose that follows a panel */
.prose + .gfx, .gfx + .prose { margin-top: clamp(2rem, 4vw, 3rem); }

/* 63. SPERM PATHWAY (`.vr-path`) — testis → epididymis → vas, three states ----
   SPEC-vasectomy-reversal §8.1 (graphic D), night panel. Before / after
   vasectomy / after reversal cross-fade 420ms; one SMIL pulse runs the route
   once per state (restarted from JS, never loops). Reveal: before → after
   (3.1s) → reversal (5.6s), cancelled by any reader input. No-JS: the
   "before" picture, controls hidden. Reduced motion: the "after reversal"
   picture, no sequence, no pulse. The figure is a two-column grid (SVG |
   caption) that stacks at ≤760px. */
.vr-path figure { display: grid; grid-template-columns: minmax(0, 360px) 1fr; gap: clamp(1.5rem, 3vw, 3rem); align-items: center; }
@media (max-width: 760px) { .vr-path figure { grid-template-columns: 1fr; } }
.vr-path figcaption .gfx__cap { margin-top: 0; font-size: clamp(1.125rem, 1.7vw, 1.375rem); line-height: 1.45; }
.vr-path .gfx__controls { margin-top: 1.5rem; }
.vr-path__svg { max-width: 380px; margin-inline: auto; aspect-ratio: 380 / 520; }
.vr-path .st { transition: opacity 420ms var(--ease); }
.vr-path .st-after, .vr-path .st-rev { opacity: 0; }
.vr-path[data-state="after"] .st-before { opacity: 0; }
.vr-path[data-state="after"] .st-after { opacity: 1; }
.vr-path[data-state="rev"] .st-before { opacity: 0; }
.vr-path[data-state="rev"] .st-rev { opacity: 1; }
.vr-path[data-state="rev"] .st-after-only { opacity: 0; }
/* Defect fix (post-approval, builder): an animateMotion target with begin="indefinite"
   rests at the SVG origin, so with no JS / reduced motion the pulse showed as a stray dot in
   the top-left corner. It is hidden until JS has actually started it. */
.vr-path:not(.is-live) [data-path-pulse] { visibility: hidden; }
.vr-path .svg-text { fill: var(--mist); font-size: 11px; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; }
.vr-path .svg-text.soft { fill: #8A959D; text-transform: none; letter-spacing: 0; font-weight: 500; font-size: 11.5px; }
@media (prefers-reduced-motion: reduce) { .vr-path * { transition: none !important; } }

/* 64. YEARS SINCE VASECTOMY (`.vr-years`) — published outcomes, on a slider --
   §8.2 (graphic B), light panel. Two glass gauges fill to the Vasovasostomy
   Study Group's figures for the chosen interval (Belker 1991, PMID 1997700);
   the band under the rail brightens; the state sentence changes WORDS. The
   citation note is part of the component, never optional (§9 keystone).
   White 34-unit numerals sit ≥44 units below the liquid surface, so the
   lightest stop is never under them. Reveal: one sweep 1→4→10→16→1 at 900ms,
   cancelled by input. No-JS: the under-3-years picture; the .vh text carries
   the whole table. Reduced motion: no transitions, no sweep. */
.vr-years__svg { max-width: 720px; margin-inline: auto; aspect-ratio: 640 / 380; }
.vr-years .liq rect, .vr-years .liq ellipse { transition: all 420ms var(--ease); }
.vr-years .mark { transition: transform 320ms var(--ease); }
.vr-years .band { transition: opacity 320ms var(--ease); }
.vr-years .pct { font-weight: 500; font-size: 34px; letter-spacing: -0.02em; font-variant-numeric: tabular-nums; }
.vr-years .lbl { font-size: 11.5px; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; }
.vr-years .tick { font-size: 11px; font-weight: 600; fill: var(--ink-3); font-variant-numeric: tabular-nums; }
@media (prefers-reduced-motion: reduce) { .vr-years * { transition: none !important; } }

/* 65. THE RECONNECTION (`.vr-recon`) — a reversal under the microscope -------
   §8.3 (graphic A, the lead piece), night panel. Four steps (ends · fluid ·
   sutures · flow) and two modes (vas-to-vas | vas-to-epididymis). The zoom is
   one CSS transform on `.scene` (transform-box: view-box); sutures draw by
   dash-offset on pathLength=1, 120ms apart; the pulse is SMIL, once. Cut
   faces fade when the ends meet; in VE mode the vas travels −116px to the
   tubule and the zoom centres on the join. The VE mode is EDUCATIONAL: the
   panel note says this practice refers for it (owner D2, 2026-10-09).
   Reveal: 1 → 2 (1.2s) → 3 → 4 (1.7s apart), cancelled by any input.
   No-JS: step 1. Reduced motion: step 4, no sequence, no pulse. */
.vr-recon__svg { max-width: 760px; margin-inline: auto; aspect-ratio: 640 / 400; }
.vr-recon .scene { transform-box: view-box; transform-origin: 320px 200px; transition: transform 1000ms var(--ease); }
.vr-recon[data-step="2"] .scene, .vr-recon[data-step="3"] .scene { transform: scale(2.15); }
.vr-recon .vasL, .vr-recon .vasR { transition: transform 900ms var(--ease); }
.vr-recon[data-step="3"] .vasL, .vr-recon[data-step="4"] .vasL { transform: translateX(25px); }
.vr-recon[data-step="3"] .vasR, .vr-recon[data-step="4"] .vasR { transform: translateX(-25px); }
.vr-recon[data-step="3"] .face, .vr-recon[data-step="4"] .face { opacity: 0; }
.vr-recon[data-mode="ve"][data-step="3"] .vasR, .vr-recon[data-mode="ve"][data-step="4"] .vasR { transform: translateX(-116px); }
.vr-recon[data-mode="ve"] .scene { transform-origin: 300px 200px; }
.vr-recon .fade { transition: opacity 500ms var(--ease); }
.vr-recon .fluid, .vr-recon .sut, .vr-recon .pulse { opacity: 0; }
.vr-recon[data-step="2"] .fluid { opacity: 1; }
.vr-recon[data-step="3"] .sut, .vr-recon[data-step="4"] .sut { opacity: 1; }
.vr-recon[data-step="4"] .pulse { opacity: 1; }
.vr-recon .sut path { stroke-dasharray: 1; stroke-dashoffset: 1; }
.vr-recon[data-step="3"] .sut path, .vr-recon[data-step="4"] .sut path { stroke-dashoffset: 0; transition: stroke-dashoffset 420ms var(--ease); }
.vr-recon[data-step="3"] .sut-in path:nth-child(1) { transition-delay: 0ms; }
.vr-recon[data-step="3"] .sut-in path:nth-child(2) { transition-delay: 120ms; }
.vr-recon[data-step="3"] .sut-in path:nth-child(3) { transition-delay: 240ms; }
.vr-recon[data-step="3"] .sut-in path:nth-child(4) { transition-delay: 360ms; }
.vr-recon[data-step="3"] .sut-out path:nth-child(1) { transition-delay: 520ms; }
.vr-recon[data-step="3"] .sut-out path:nth-child(2) { transition-delay: 640ms; }
.vr-recon[data-step="3"] .sut-out path:nth-child(3) { transition-delay: 760ms; }
.vr-recon[data-step="3"] .sut-out path:nth-child(4) { transition-delay: 880ms; }
.vr-recon[data-step="3"] .sut-out path:nth-child(5) { transition-delay: 1000ms; }
.vr-recon[data-step="3"] .sut-out path:nth-child(6) { transition-delay: 1120ms; }
.vr-recon .mode-ve { display: none; }
.vr-recon[data-mode="ve"] .mode-ve { display: block; }
.vr-recon[data-mode="ve"] .mode-vv { display: none; }
.vr-recon .step-lbl { opacity: 0; transition: opacity 400ms var(--ease); }
.vr-recon .step-lbl.is-on { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .vr-recon * { transition: none !important; } }

/* 66. TWO ROADS (`.vr-fork`) — reversal, or sperm retrieval with IVF ---------
   §8.4 (graphic C), light panel. Four factor radio groups light lamps on the
   road each factor leans toward (a half-lit pair = either road). The visible
   list IS the text alternative and is rewritten only when a sentence changes.
   No totals, no verdict — it organises a conversation. Lamps fade in once,
   160ms per row, via a class (the lamps' SVG transform attribute is never
   touched). Halo is --amber (graphic only, never text); lit core is
   --amber-line (3.99:1 non-text). No-JS: the default lean, controls hidden. */
.vr-fork__svg { max-width: 760px; margin-inline: auto; aspect-ratio: 640 / 430; }
.vr-fork .lamp { transition: opacity 360ms var(--ease); }
.vr-fork.js-pending .lamp { opacity: 0; }
.vr-fork.is-in .lamp { opacity: 1; }
.vr-fork.is-in .lamp:nth-child(2) { transition-delay: 160ms; }
.vr-fork.is-in .lamp:nth-child(3) { transition-delay: 320ms; }
.vr-fork.is-in .lamp:nth-child(4) { transition-delay: 480ms; }
.vr-fork .halo { opacity: 0; transition: opacity 360ms var(--ease); }
.vr-fork .lamp.is-lit .halo { opacity: 1; }
.vr-fork .lamp .core { fill: none; stroke: var(--ink-3); stroke-width: 1.5; }
.vr-fork .lamp.is-lit .core { fill: var(--amber-line); stroke: var(--amber-line); }
.vr-fork .lamp.is-half .core { fill: none; stroke: var(--amber-line); stroke-width: 2; }
.vr-fork .lamp.is-half .halo { opacity: 0.45; }
.vr-fork .factor-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 1rem 2rem; margin-top: 1rem; }
.vr-fork fieldset { border: 0; padding: 0; margin: 0; min-width: 0; }
.vr-fork .lamp-list { margin: 1rem 0 0; padding: 0; list-style: none; display: grid; gap: 0.5rem; }
.vr-fork .lamp-list li { font-size: var(--fs-small); color: var(--ink-2); padding-left: 1.25rem; position: relative; }
.vr-fork .lamp-list li::before { content: ""; position: absolute; left: 0; top: 0.55em; width: 0.6em; height: 0.6em; border-radius: 50%; border: 1.5px solid var(--ink-3); }
.vr-fork .lamp-list li.lean-rev::before, .vr-fork .lamp-list li.lean-ivf::before { background: var(--amber-line); border-color: var(--amber-line); }
.vr-fork .svg-text { fill: var(--ink-2); font-size: 12px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; }
.vr-fork .road-title { font-size: 15px; font-weight: 600; letter-spacing: -0.005em; text-transform: none; fill: var(--ink); }
.vr-fork .road-sub { font-size: 11px; font-weight: 500; letter-spacing: 0; text-transform: none; fill: var(--ink-3); }
@media (prefers-reduced-motion: reduce) { .vr-fork * { transition: none !important; } }

/* 63–66 phone legibility (≤700px) — part of the approved set (§8.8).
   A 640-unit viewBox renders ~310px wide on a 390px phone, so 10–12-unit SVG
   text lands at 5–6px. Labels the figcaption already carries are hidden;
   labels that must stay are enlarged in viewBox units. Desktop untouched. */
@media (max-width: 700px) {
  .vr-recon .step-lbl, .vr-recon .svg-text[text-anchor="end"] { display: none; }
  .vr-years .lbl { font-size: 21px; }
  .vr-years .lbl[style] { font-size: 18px; }
  .vr-years .tick { font-size: 19px; }
  .vr-fork .road-title { font-size: 21px; }
  .vr-fork .road-sub { display: none; }
  .vr-fork .svg-text:not(.road-title) { font-size: 15px; }
  .vr-fork svg > text.svg-text[y="34"] { display: none; }
  .vr-path .svg-text { font-size: 14px; }
  .vr-path .svg-text.soft { font-size: 13px; }
}

/* 63–66 support: the hero credibility chip (§4.4) on a LIGHT type-only
   .page-hero. Its base rule sets the label #fff for the homepage cinema hero,
   which would be invisible on paper. --ink-2 is 9.09:1 on white. */
.page-hero .hero-cred span { color: var(--ink-2); opacity: 1; }
/* ...and back to white on a CINEMA .page-hero (GreenLight photo hero, 2026-10-10), where the scrim is dark. */
.page-hero--cinema .hero-cred span { color: #fff; opacity: 0.92; }

/* 67. ALTERNATIVES COMPARISON (`.alt-compare`) — Himplant page, frozen copy ---
   Added 2026-10-09 at the owner's request ("more stimulating and modern"). The
   block's WORDS are frozen by T-40 (settlement review deferred to pre-launch),
   so this is styling only: every visible text node is byte-identical to the
   migrated block and was diffed before/after. Nothing here adds text, imagery
   or emphasis that the words did not already carry — the ✅/❌ glyphs stay as
   the text itself, set in a badge, never replaced by drawn icons.

   One featured card (the first group, the procedure the page is about) on
   midnight with an amber cap rule; the alternatives as quiet paper cards in an
   auto-fit grid. Each list item is a row with a round badge, so a long list
   scans as a ledger rather than as a bullet column. Cards lift 4px on hover —
   user-initiated, replayable (§0.5) — and fade in once with the standard
   stagger. Contrast on --midnight: #fff 17.89:1, --mist 11.57:1, --blue-air
   10.88:1 (§2 pre-verified). Not a gradient anywhere (§2 do-not list). */
.alt-compare__head { margin-bottom: clamp(1.5rem, 3vw, 2.25rem); }
.alt-compare__head h2 { font-size: var(--fs-display-2); line-height: 1.08; letter-spacing: -0.025em; max-width: 24ch; }
/* 300px floor, not 260: five alternatives in a 1200px container would fit four
   across and orphan the fifth on its own row; 300 gives 3 + 2 on desktop and
   2-up on tablets. */
.alt-compare__grid {
  display: grid;
  gap: clamp(1rem, 2vw, 1.5rem);
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
}
.alt-card {
  position: relative;
  background: var(--paper);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: clamp(1.25rem, 2.5vw, 1.75rem);
  overflow: hidden;
  transition: transform 240ms var(--ease), box-shadow 240ms var(--ease);
}
.alt-card::before {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 3px;
  background: var(--ink-3);
  opacity: 0.35;
}
.alt-card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lift); }
.alt-card h3 { margin: 0; font-size: var(--fs-h4); font-weight: 600; color: var(--ink); }
.alt-card__label {
  margin: 0.35rem 0 0;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.alt-card__list { list-style: none; margin: 0.9rem 0 0; padding: 0; }
.alt-card__list li {
  display: flex;
  align-items: flex-start;
  gap: 0.65rem;
  padding: 0.55rem 0;
  border-top: 1px solid var(--line);
  font-size: var(--fs-small);
  line-height: 1.45;
  color: var(--ink-2);
}
.alt-card__mark {
  flex: none;
  display: grid;
  place-items: center;
  width: 1.75rem;
  height: 1.75rem;
  border-radius: 50%;
  font-size: 0.8rem;
  line-height: 1;
}
.alt-yes .alt-card__mark { background: rgba(58, 116, 150, 0.12); }
.alt-no .alt-card__mark { background: rgba(11, 15, 18, 0.05); }

/* The featured card: the page's own procedure, set apart by ground and scale. */
.alt-card--feature {
  grid-column: 1 / -1;
  background: var(--midnight);
  border-color: var(--midnight);
  padding: clamp(1.5rem, 3.5vw, 2.5rem);
}
.alt-card--feature::before { background: var(--amber); opacity: 1; height: 4px; }
.alt-card--feature h3 { font-size: var(--fs-h3); font-weight: 500; letter-spacing: -0.015em; color: #fff; }
.alt-card--feature .alt-card__label { color: var(--blue-air); }
.alt-card--feature .alt-card__list li { color: var(--mist); border-top-color: var(--line-dark); font-size: var(--fs-body); }
.alt-card--feature .alt-yes .alt-card__mark { background: rgba(247, 181, 90, 0.16); }
.alt-card--feature .alt-no .alt-card__mark { background: rgba(255, 255, 255, 0.08); }
@media (min-width: 760px) {
  .alt-card--feature .alt-card__list { columns: 2; column-gap: 2.5rem; }
  .alt-card--feature .alt-card__list li { break-inside: avoid; }
}
@media (prefers-reduced-motion: reduce) {
  .alt-card { transition: none; }
  .alt-card:hover { transform: none; }
}

/* 68. MEDIA BAND (`.media-band`) — full-bleed footage BETWEEN sections ---------
   Added 2026-10-09 for the Himplant device animation, placed by the owner. The
   footage carries no text (cropped between the promo's burned-in captions); the
   captions are an <ol> of real text instead, timed by ucofl.js to the shots.

   Two states, so nothing depends on JS or motion:
   - Static (no JS, reduced motion, Save-Data): the poster, then every caption as
     a quiet list beneath it. All six facts are read, nothing moves.
   - Live (`.is-live`, set by ucofl.js): one caption at a time, lower-left over a
     functional scrim (the same idea as the hero's, not decoration), fading in as
     its shot begins. White on the scrim's dark end, as the hero measures it.
   Height is capped so the band never pushes the next section off a laptop. */
.media-band { position: relative; margin: 0; }
.media-band video {
  display: block;
  width: 100%;
  aspect-ratio: 1920 / 640;
  max-height: 480px;
  min-height: 240px;
  object-fit: cover;
  background: var(--midnight);
}
.media-band__captions {
  list-style: none;
  margin: 0;
  padding: 1rem clamp(1.25rem, 4vw, 2.5rem);
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem 1.5rem;
  background: var(--midnight);
  color: var(--mist);
  font-size: var(--fs-small);
  line-height: 1.45;
}
.media-band__credit {
  padding: 0.5rem clamp(1.25rem, 4vw, 2.5rem) 0;
  font-size: var(--fs-micro);
  color: var(--ink-3);
}
.media-band.is-live::after {
  content: "";
  position: absolute;
  left: 0; right: 0; top: 0;
  height: min(480px, max(240px, calc(100vw * 640 / 1920)));
  background: linear-gradient(to top, rgba(16, 24, 32, 0.82) 0%, rgba(16, 24, 32, 0.35) 38%, rgba(16, 24, 32, 0) 62%);
  pointer-events: none;
}
.media-band.is-live .media-band__captions {
  position: absolute;
  z-index: 1;
  left: clamp(1.25rem, 4vw, 2.5rem);
  top: calc(min(480px, max(240px, calc(100vw * 640 / 1920))) - clamp(1rem, 3vw, 2rem));
  padding: 0;
  background: none;
  display: block;
  pointer-events: none;
}
.media-band.is-live .media-band__captions li {
  position: absolute;
  left: 0;
  bottom: 0;
  width: max-content;
  max-width: min(34rem, calc(100vw - 2.5rem));
  opacity: 0;
  transform: translateY(8px);
  transition: opacity 500ms var(--ease), transform 500ms var(--ease);
  font-size: clamp(1.05rem, 2.2vw, 1.6rem);
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: 1.25;
  color: #fff;
}
.media-band.is-live .media-band__captions li.is-on { opacity: 1; transform: none; }

/* 69. IN THE PRESS (`.press`) — assemble, dissolve, then rotate ---------------
   Added 2026-10-10 for the Himplant page. Spec: notes/SPEC-himplant-press.md
   (+ OWNER CHANGE 2026-10-10: continuous rotation, one quote per outlet).
   One midnight WINDOW PANEL (§2 E1) inside a light section. The opening plays
   ONCE on first view with Skip; then the five quotes cross-fade on a loop with
   the matching wordmark lit, and the control is Pause/Play (WCAG 2.2.2).
   Reduced motion / Save-Data (`.is-step`): one quote, Previous/Next, nothing
   moves on its own. No JS: logos row, signal line, all five quotes listed. The wordmarks are white monochrome SVGs
   (sources: notes/PRESS-LOGO-SOURCES.md), sized by optical weight, not box. */
.press-stage {
  --press-fg: #fff;
  position: relative;
  isolation: isolate;
  overflow: hidden;
  display: grid;
  grid-template-columns: minmax(0, 1fr);   /* the column may never outgrow the stage (a wide foot row once pushed the quote off-centre) */
  grid-template-rows: auto auto auto;      /* STATIC layout: logos / line / quote */
  gap: clamp(1.5rem, 3vw, 2.5rem);
  background: var(--midnight);
  color: var(--press-fg);
  border: 1px solid var(--line-dark);
  border-left: 2px solid var(--amber);     /* the E1 panel signature */
  border-radius: var(--radius);
  padding: clamp(2rem, 5vw, 3.5rem) clamp(1.25rem, 5vw, 4rem);
}
.press-stage :focus-visible { outline-color: var(--blue); }

/* lighting (graphic, aria-hidden): a low light-blue stage light (~22%) from the foot of the panel,
   fading to nothing by mid-panel. The orange "dawn" layer was retired with HORIZON (2026-11-10). */
.press-light {
  position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background:
    radial-gradient(70% 55% at 50% 108%, rgba(185,220,236,.22) 0%, rgba(185,220,236,.07) 45%, rgba(185,220,236,0) 72%);
  opacity: .9;
}

/* the line: a hairline in --mist that draws left→right and is closed by ONE signal square at its
   right end — the SIGNAL grammar (§4.17): slate stroke, square terminal, one orange square. */
.press-line {
  display: block; position: relative; height: 2px; width: 100%;
  background: linear-gradient(90deg, rgba(205,213,219,0) 0%, rgba(205,213,219,.55) 24%, rgba(205,213,219,.55) 100%);
  transform-origin: left center;
}
.press-line::after {
  content: ""; position: absolute; right: 0; bottom: 0; width: 8px; height: 8px;
  background: var(--signal);
}

/* ---- the outlets: a real list of names, drawn as wordmarks ---- */
.press-logos {
  --wm-h: clamp(16px, 1.6vw, 22px);        /* base wordmark height; each outlet scales it by --wm-k */
  list-style: none; padding: 0; margin: 0;
  display: flex; flex-wrap: wrap; justify-content: center; align-items: center;
  column-gap: clamp(1.75rem, 4.5vw, 4rem); row-gap: 1.5rem;
}
.press-wm {
  /* §7.3: each wordmark is a link, so the hit area is 44px tall in EVERY state
     (animated, done, reduced motion, no JS); the mark is centred inside it. */
  display: inline-flex; align-items: center; min-height: 44px;
  color: var(--press-fg); text-decoration: none; line-height: 1; white-space: nowrap;
  padding: 0 .1rem; border-bottom: 2px solid transparent; transition: border-color 220ms var(--ease);
}
.press-wm:hover, .press-wm:focus-visible { border-bottom-color: var(--amber); color: var(--press-fg); }
.press-wm img { display: block; width: auto; height: calc(var(--wm-h) * var(--wm-k, 1)); max-width: none; }
/* equal OPTICAL weight, not equal boxes: the compact heavy marks (GQ, VICE) and the condensed
   Men's Health stand taller; the long all-caps lockups (New York Post, HuffPost) stand shorter. */
.wm--gq   { --wm-k: 1.12; }
.wm--mh   { --wm-k: 1.2; }
.wm--vice { --wm-k: 1.08; }
.wm--nyp  { --wm-k: .86; }
.wm--hp   { --wm-k: .78; }

/* ---- the quote: live HTML text, large, white on midnight (17.89:1) ---- */
.press-quotes { display: grid; gap: clamp(2.5rem, 5vw, 3.5rem); }   /* no JS: all five listed */
.press-quote { display: grid; justify-items: center; text-align: center; }
.press-qmark {
  display: block; width: clamp(2rem, 4vw, 3rem); height: auto; margin-bottom: clamp(.75rem, 2vw, 1.25rem);
  color: var(--amber);  /* 10.19:1 — graphic on dark */
}
.press-quote blockquote p {
  font-size: clamp(1.5rem, 1rem + 2.2vw, 2.625rem);
  font-weight: 500; line-height: 1.22; letter-spacing: -0.02em;
  max-width: 28ch; text-wrap: balance; color: var(--press-fg);
}
.press-quote .w { display: inline-block; }
.press-quote figcaption { margin-top: clamp(1.25rem, 3vw, 2rem); font-size: var(--fs-small); color: var(--mist); /* 11.0:1 */
  display: flex; flex-wrap: wrap; justify-content: center; align-items: baseline; gap: .15em 0; }
.press-quote figcaption a { white-space: nowrap; }
.press-quote cite { font-style: normal; font-weight: 600; color: var(--blue-air); /* 10.88:1 */ }
.press-quote figcaption a { color: var(--press-fg); text-decoration: underline; text-underline-offset: 4px; text-decoration-color: var(--amber); }
.press-quote figcaption a:hover { text-decoration-thickness: 2px; }
.press-quote .sep { margin-inline: .5em; opacity: .55; }

/* the controls: Skip while the opening plays, then Pause/Play; Previous/Next under reduced motion.
   44px targets, ghost on dark. */
.press-ctl {
  position: absolute; right: 1rem; bottom: 1rem; z-index: 2;
  min-height: 44px; min-width: 44px; padding: 0 1rem;
  font: 600 var(--fs-label)/1 var(--font-sans); letter-spacing: .16em; text-transform: uppercase;
  color: var(--press-fg); background: transparent; border: 1px solid rgba(255,255,255,.35); border-radius: 999px;
  cursor: pointer; transition: border-color 220ms var(--ease), background 220ms var(--ease), opacity 220ms var(--ease);
}
.press-ctl:hover { border-color: var(--amber); }
.press-ctl { display: inline-flex; align-items: center; justify-content: center; gap: .55em; }
.press-ctl svg { width: 10px; height: 10px; flex: none; color: var(--amber); }
/* reduced motion: Previous · n of 5 · Next, in the flow under the quote */
.press-nav { display: flex; align-items: center; justify-content: center; gap: 1rem; }
.press-ctl--nav { position: static; }
.press-count { font-size: var(--fs-small); color: var(--mist); min-width: 4.5em; text-align: center; font-variant-numeric: tabular-nums; }
.press.is-step .press-wm { opacity: .62; transition: opacity 220ms var(--ease), border-color 220ms var(--ease); }
.press.is-step .press-wm.is-current,
.press.is-step .press-wm:hover,
.press.is-step .press-wm:focus-visible { opacity: 1; }
.press.is-step .press-wm.is-current { border-bottom-color: var(--amber); }

/* motes: twelve 3px points that rise as the wordmarks dissolve (graphic, aria-hidden) */
.press-motes { position: absolute; inset: 0; pointer-events: none; z-index: 1; }
.press-mote { position: absolute; left: var(--x); top: var(--y); width: 3px; height: 3px; border-radius: 50%; background: var(--c); opacity: 0; }

/* ---- LIVE (animated) MODE — only when ucofl.js will run the sequence (`is-live`).
   Everything stacks in ONE cell; the foot row is where the logos settle after the quote lands. */
.press.is-live .press-stage {
  grid-template-rows: 1fr auto; grid-template-areas: "main" "foot"; gap: 0;
  min-height: clamp(300px, 36vw, 480px);
  padding-bottom: calc(clamp(2rem, 5vw, 3.5rem) + 44px);  /* room for the control row */
}
.press.is-live .press-logos,
.press.is-live .press-quotes { grid-area: main; align-self: center; }
/* every quote in ONE cell (the cell is as tall as the longest, so nothing jumps); one is on at a time.
   visibility keeps the off quotes' links out of the tab order and the accessibility tree. */
.press.is-live .press-quotes { gap: 0; }
.press.is-live .press-quote {
  grid-area: 1 / 1; opacity: 0; visibility: hidden;
  transition: opacity 700ms var(--ease), visibility 0s linear 700ms;
}
.press.is-live .press-quote.is-on {
  opacity: 1; visibility: visible;
  transition: opacity 900ms var(--ease) 350ms, visibility 0s linear 0s;   /* the outgoing quote clears first */
}
.press.is-live .press-line { position: absolute; left: 0; right: 0; bottom: calc(44px + clamp(2rem, 5vw, 3.5rem) - .75rem); width: auto; transform: scaleX(0); }
.press.is-live .press-wm { opacity: 0; will-change: transform, opacity, filter; }
.press.is-live .press-quote .press-qmark,
.press.is-live .press-quote .w,
.press.is-live .press-quote figcaption { opacity: 0; }
.press.is-live .press-light { opacity: 0; }

/* phase timings (ms). One place to tune. */
.press-stage {
  --t-line: 0ms;      --d-line: 1100ms;
  --t-in0: 250ms;     --gap-in: 190ms;  --d-in: 950ms;     /* assemble: 250 → ~1960, settles by ~2.2s  */
  --t-out0: 4300ms;   --gap-out: 70ms;  --d-out: 800ms;    /* hold ~2s, dissolve 4300 → ~5380         */
  --t-mote: 4350ms;
  --t-qm: 5350ms;     --t-w0: 5650ms;   --gap-w: 55ms;  --d-w: 480ms;  /* quote words from 5.65s      */
  --t-cap: 7100ms;    /* overwritten by JS: t-w0 + words*gap-w + 420ms */
}
.press.is-playing .press-light { animation: pressGlow 1400ms var(--ease) var(--t-line) both; }
.press.is-playing .press-line  { animation: pressLine var(--d-line) var(--ease) var(--t-line) both; }
.press.is-playing .press-wm {
  animation:
    wmIn  var(--d-in)  var(--ease)                     calc(var(--t-in0)  + var(--i) * var(--gap-in))  both,
    wmOut var(--d-out) cubic-bezier(.4, 0, .7, 1)       calc(var(--t-out0) + var(--i) * var(--gap-out)) forwards;
}
.press.is-playing .press-mote { animation: moteRise 1500ms var(--ease) calc(var(--t-mote) + var(--d)) both; }
.press.is-playing .press-quote .press-qmark { animation: qmIn 650ms var(--ease) var(--t-qm) both; }
.press.is-playing .press-quote .w { animation: wIn var(--d-w) var(--ease) calc(var(--t-w0) + var(--wi) * var(--gap-w)) both; }
.press.is-playing .press-quote figcaption { animation: wIn 600ms var(--ease) var(--t-cap) both; }

@keyframes pressGlow { from { opacity: 0; transform: translateY(12%); } to { opacity: .9; transform: none; } }
@keyframes pressLine { from { transform: scaleX(0); } to { transform: scaleX(1); } }
@keyframes wmIn {
  from { opacity: 0; transform: translate(var(--dx), var(--dy)) scale(.5) rotate(var(--tilt)); filter: blur(8px); }
  55%  { filter: blur(0); }
  to   { opacity: 1; transform: none; filter: blur(0); }
}
/* transform/filter/opacity ONLY — no size change, which would reflow the row mid-dissolve. */
@keyframes wmOut {
  from { opacity: 1; transform: none; filter: blur(0); }
  to   { opacity: 0; transform: translateY(-18px) scale(1.16); filter: blur(12px); }
}
@keyframes moteRise {
  0%   { opacity: 0; transform: translateY(0) scale(.6); }
  30%  { opacity: .85; }
  100% { opacity: 0; transform: translateY(-70px) scale(1); }
}
@keyframes qmIn { from { opacity: 0; transform: scale(.6) translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes wIn  { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }

/* done: the quote stays; the outlets return as a quiet row on the foot line */
.press.is-done .press-light { opacity: .9; }
.press.is-done .press-line { transform: scaleX(1); }
.press.is-done .press-quote .press-qmark,
.press.is-done .press-quote .w,
.press.is-done .press-quote figcaption { opacity: 1; }
.press.is-done .press-logos {
  /* 15px floor (was 11): at 390 the smallest mark (HuffPost, x0.78) rendered ~9px —
     unreadable, and these are LINKS. See the tap-target rule below. */
  --wm-h: clamp(15px, 1.1vw, 16px);
  grid-area: foot; align-self: end; justify-content: flex-start;
  column-gap: clamp(1.25rem, 3vw, 2.25rem); row-gap: .75rem;
  margin-top: 1.5rem;      /* the control sits BELOW the signal line, so no right padding is needed */
  animation: wIn 700ms var(--ease) both;
}
.press.is-done .press-wm {
  opacity: .62; transition: opacity 500ms var(--ease), border-color 500ms var(--ease);
}
.press.is-done .press-wm:hover, .press.is-done .press-wm:focus-visible { opacity: 1; }
/* the outlet whose quote is showing: full strength + the amber underline */
.press.is-done .press-wm.is-current { opacity: 1; border-bottom-color: var(--amber); }
.press.is-live .press-ctl[hidden] { display: none; }

/* phone */
@media (max-width: 640px) {
  .press-stage { padding: 1.75rem 1.25rem; }
  .press.is-live .press-stage { min-height: 440px; padding-bottom: calc(1.75rem + 52px); }
  .press-logos { display: grid; grid-template-columns: 1fr 1fr; justify-items: center; column-gap: 1rem; row-gap: 1.25rem; }
  .press-logos li:last-child:nth-child(odd) { grid-column: 1 / -1; }   /* five outlets: the odd one centres */
  .press.is-done .press-logos { display: flex; justify-content: flex-start; column-gap: 1.25rem; row-gap: .6rem; }
  .press-ctl { bottom: .75rem; right: .75rem; }
}

/* 70. AQUABLATION PANEL GRAMMAR (`.gfx--aq`) — the six Aquablation graphics --
   notes/SPEC-aquablation-redesign.md §9.0, lifted from the owner-approved
   prototype (2026-10-09; all six approved 2026-10-10). A modifier of the 63–66
   `.gfx` panel, not a fork: same 1px --line border and 2px --amber-line left
   edge (3.99:1 non-text), but a --paper fill, as approved. The prototype's
   bare `.gfx__steps/.gfx__step/.gfx__num` collided with the 63–66 stepper, so
   they ship as `.aq-steps/.aq-step/.aq-step__num`; the other new sub-elements
   (`__views/__view/__svg/__row/__range/__keys/__key/__swatch/__fs/__radios/
   __radio`) did not exist and keep the prototype names. Every control is
   native and ≥44px with a --blue-text focus ring; state lines sit OUTSIDE the
   controls so the no-JS reader still gets the final sentence. */
.gfx--aq { background: var(--paper); margin-top: clamp(2rem, 4vw, 3rem); }
.gfx--aq .gfx__cap { margin-top: 0.5rem; text-align: center; font-size: var(--fs-micro); color: var(--ink-3); }
.gfx--aq .gfx__state { margin-top: 1.25rem; min-height: 4.8em; font-size: var(--fs-small); line-height: 1.6; color: var(--ink-2); max-width: 72ch; }
.gfx--aq .gfx__out { font-size: clamp(1.375rem, 2.8vw, 1.875rem); }
.gfx__views { display: grid; gap: 1rem; }
@media (min-width: 760px) { .gfx__views { grid-template-columns: 1fr 1fr; } }
.gfx__view { margin: 0; border: 1px solid var(--line); background: var(--paper-2); padding: 0.75rem 0.75rem 0.625rem; }
.gfx__view--plain { background: var(--paper); }
.gfx__view--night { background: var(--midnight); border-color: var(--line-dark); }
.gfx__view--flush { padding: 0; overflow: hidden; }
.gfx__view--night .gfx__cap { color: var(--mist); }
.gfx__view--flush .gfx__cap { padding: 0.5rem 0.75rem 0.625rem; margin-top: 0; }
.gfx__svg { display: block; width: 100%; height: auto; }
.gfx__row { display: flex; align-items: baseline; gap: 0.75rem; flex-wrap: wrap; }
.gfx__range { width: 100%; margin-top: 0.625rem; accent-color: var(--amber-line); }
.gfx__range:focus-visible { outline: 2px solid var(--blue-text); outline-offset: 4px; }
.aq-steps { list-style: none; margin: 1rem 0 0; padding: 0; display: grid; gap: 0.375rem; }
@media (min-width: 720px) { .aq-steps { grid-template-columns: repeat(auto-fit, minmax(14rem, 1fr)); } }
.aq-step { display: flex; align-items: flex-start; gap: 0.75rem; width: 100%; min-height: 44px; padding: 0.625rem 0.75rem; border: 1px solid transparent; border-radius: var(--radius-input); background: transparent; font: inherit; font-size: var(--fs-small); line-height: 1.5; text-align: left; color: var(--ink-2); cursor: pointer; transition: border-color 240ms var(--ease), background-color 240ms var(--ease), box-shadow 240ms var(--ease); }
.aq-step:hover { background: var(--paper-2); border-color: var(--line); }
.aq-step[aria-current] { background: var(--paper-2); border-color: var(--ink-3); box-shadow: inset 0 -2px 0 var(--amber-line); }
.aq-step:focus-visible { outline: 2px solid var(--blue-text); outline-offset: 2px; }
.aq-step strong { display: block; font-weight: 600; color: var(--ink); }
.aq-step small { display: block; font-size: var(--fs-micro); color: var(--ink-3); }
.aq-step__num { flex: none; display: grid; place-items: center; width: 1.5rem; height: 1.5rem; margin-top: 0.1rem; border: 1px solid var(--ink-3); border-radius: 50%; color: var(--ink); font-size: 0.75rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.aq-step[aria-current] .aq-step__num { background: var(--ink); border-color: var(--ink); color: #fff; }
.gfx__keys { list-style: none; margin: 1rem 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 0.375rem; }
.gfx__key { min-height: 44px; padding: 0.5rem 0.875rem; border: 1px solid var(--line); border-radius: 999px; background: var(--paper); font: inherit; font-size: var(--fs-small); color: var(--ink-2); cursor: pointer; display: inline-flex; align-items: center; gap: 0.5rem; text-align: left; transition: border-color 240ms var(--ease), background-color 240ms var(--ease); }
.gfx__key:hover { border-color: var(--ink-3); }
.gfx__key[aria-pressed="true"] { border-color: var(--ink); color: var(--ink); box-shadow: inset 0 -2px 0 var(--amber-line); }
.gfx__key:focus-visible { outline: 2px solid var(--blue-text); outline-offset: 2px; }
.gfx__swatch { width: 0.75rem; height: 0.75rem; border-radius: 50%; display: inline-block; background: var(--ink-3); flex: none; }
fieldset.gfx__fs { border: 0; padding: 0; margin: 1rem 0 0; min-width: 0; }
.gfx__fs legend { font-size: var(--fs-label); font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-3); padding: 0; margin-bottom: 0.5rem; }
.gfx__radios { display: flex; flex-wrap: wrap; gap: 0.375rem; }
.gfx__radio { position: relative; display: inline-flex; align-items: center; gap: 0.5rem; min-height: 44px; padding: 0.5rem 0.875rem; border: 1px solid var(--line); border-radius: 999px; background: var(--paper); font-size: var(--fs-small); color: var(--ink-2); cursor: pointer; }
.gfx__radio input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.gfx__radio:has(input:checked) { border-color: var(--ink); color: var(--ink); box-shadow: inset 0 -2px 0 var(--amber-line); }
.gfx__radio:has(input:focus-visible) { outline: 2px solid var(--blue-text); outline-offset: 2px; }
@media (prefers-reduced-motion: reduce) { .aq-step, .gfx__key { transition: none; } }

/* 71. CONTOUR PLANNING (`.aq-contour`) — the real HYDROS screen, walked through.
   The overlay SVG shares the image's 1179x874 space so markers land on the real
   points. Overlay colours are E1-style marks on a dark instrument image:
   --amber 10.19:1 / --blue-air / a light green, each with a dark paint-order
   halo so they read over speckle. The image never carries meaning alone —
   the keys and the state line are the text. */
.ct-frame { position: relative; aspect-ratio: 1179 / 874; background: #161D22; }
.ct-frame picture, .ct-frame img { position: absolute; inset: 0; width: 100%; height: 100%; display: block; object-fit: cover; }
.gfx .ct-frame svg { position: absolute; inset: 0; width: 100%; height: 100%; }
.ct-plan { fill: none; stroke: var(--amber); stroke-width: 5; stroke-linejoin: round; stroke-linecap: round; filter: url(#ctGlow); }
.ct-plan-under { fill: none; stroke: #000; stroke-opacity: 0.45; stroke-width: 9; stroke-linejoin: round; stroke-linecap: round; }
.ct-pt { fill: var(--amber); stroke: #161D22; stroke-width: 2.5; }
.ct-ring { fill: none; stroke: var(--amber); stroke-width: 2.5; }
.ct-num { font-family: var(--font-sans); font-size: 30px; font-weight: 600; fill: #fff; text-anchor: middle; paint-order: stroke; stroke: #161D22; stroke-width: 6; stroke-linejoin: round; }
.ct-zone-bn { fill: var(--blue); fill-opacity: 0.32; stroke: var(--blue-air); stroke-width: 2; }
.ct-zone-vpz { fill: none; stroke: var(--amber); stroke-width: 2.5; stroke-dasharray: 8 6; }
.ct-zone-vpz-fill { fill: #2fd37a; fill-opacity: 0.28; }
.ct-urethra { fill: none; stroke: #fff; stroke-opacity: 0.85; stroke-width: 3; stroke-dasharray: 14 10; }
.ct-depth { fill: none; stroke: var(--amber); stroke-width: 2.5; }
.ct-tag { font-family: var(--font-sans); font-size: 26px; font-weight: 600; letter-spacing: 0.06em; fill: #fff; paint-order: stroke; stroke: #161D22; stroke-width: 7; stroke-linejoin: round; }
.ct-tag--amber { fill: var(--amber); }
.ct-tag--sky { fill: var(--blue-air); }
.ct-tag--green { fill: #7ff0b0; }
.ct-hl { fill: none; stroke: #fff; stroke-width: 3; stroke-dasharray: 10 8; opacity: 0; }
.ct-keys .gfx__key[data-k="bn"] .gfx__swatch { background: var(--blue); }
.ct-keys .gfx__key[data-k="vpz"] .gfx__swatch { background: #2fd37a; }
.ct-keys .gfx__key[data-k="plan"] .gfx__swatch { background: var(--amber); }
.ct-keys .gfx__key[data-k="depth"] .gfx__swatch { background: #fff; border: 1px solid var(--ink-3); }

/* 72. WATERJET SWEEP (`.aq-sweep`) — own illustration, matched to the
   manufacturer's animation (bladder left, probe below, handpiece along the
   urethra). Anatomical shading inside the diagram is §2 exception E3. */
.sw-fat { fill: url(#gFat); }
.sw-fat-tex { fill: #d9c48a; opacity: 0.35; filter: url(#fFatTex); }
.sw-bladder { fill: url(#gBladderL); }
.sw-mucosa { fill: url(#gMucosa); }
.sw-capsule { fill: none; stroke: #b8655e; stroke-width: 3; }
.sw-gland { fill: url(#gLobe); }
.sw-gland--far { filter: brightness(0.82); }
.sw-gland-hi { fill: url(#gLobeHi); }
.sw-veru { fill: #c9665f; stroke: #8e3a38; stroke-width: 1; }
.sw-vessel { fill: none; stroke: #c9585a; stroke-width: 1.6; stroke-opacity: 0.6; stroke-linecap: round; }
.sw-vessel2 { fill: none; stroke: #d97a7a; stroke-width: 0.9; stroke-opacity: 0.5; stroke-linecap: round; }
.sw-cavity { fill: url(#gCavity); }
.sw-cavity-rim { fill: none; stroke: #8e3a38; stroke-width: 2.5; stroke-opacity: 0.8; }
.sw-bound { fill: none; stroke: #2fd37a; stroke-width: 4; stroke-dasharray: 1 11; stroke-linecap: round; filter: url(#fDotGlow); }
.sw-shaft, .sw-tip { fill: url(#gShaft); stroke: #2b333a; stroke-width: 1.2; }
.sw-shaft-hi { fill: #fff; fill-opacity: 0.55; }
.sw-jet { fill: none; stroke: #bfe9ff; stroke-width: 6; stroke-opacity: 0.95; stroke-linecap: round; }
.sw-jet-core { fill: none; stroke: #fff; stroke-width: 2.6; stroke-linecap: round; }
.sw-jet-glow { fill: none; stroke: var(--blue); stroke-width: 26; stroke-opacity: 0.55; stroke-linecap: round; filter: url(#fJetGlow); }
.sw-mist { fill: #eaf8ff; fill-opacity: 0.9; }
.sw-rectum { fill: url(#gRectumL); }
.sw-rectum-fib { fill: none; stroke: #b86a66; stroke-width: 1; stroke-opacity: 0.5; }
.sw-probe { fill: url(#gProbeL); }
.sw-probe-hi { fill: #fff; fill-opacity: 0.35; }
.sw-probe-handle { fill: #f1f3f5; stroke: #9aa4ad; stroke-width: 1; }
.sw-tag { font-family: var(--font-sans); font-size: 15px; font-weight: 600; letter-spacing: 0.1em; text-transform: uppercase; fill: #fff; paint-order: stroke; stroke: #3a1e1c; stroke-width: 4; stroke-linejoin: round; }
.sw-tag--soft { font-weight: 500; letter-spacing: 0.06em; text-transform: none; font-size: 14px; }
.sw-lead { fill: none; stroke: #fff; stroke-width: 1.5; stroke-opacity: 0.9; stroke-dasharray: 3 4; }
.sw-front { fill: none; stroke: #fff; stroke-width: 1.5; stroke-dasharray: 6 6; opacity: 0.9; }

/* 73. FIVE YEARS, MEASURED (`.aq-ipss`) — the symptom-score chart, rebuilt
   from PubMed abstracts. Series strokes ≥3:1 on white at 2.4px. The two
   dashed reference levels (owner D10) are --ink-3 hairlines with a † that
   points at the PROCEPT attribution in the sources list. */
.ch-band { fill: var(--ink); }
.ch-band--severe { fill-opacity: 0.07; }
.ch-band--moderate { fill-opacity: 0.035; }
.ch-band-t { font-family: var(--font-sans); font-size: 11px; font-weight: 600; letter-spacing: 0.16em; text-transform: uppercase; fill: var(--ink-3); }
.ch-axis { stroke: var(--line); stroke-width: 1; }
.ch-axis-main { stroke: var(--ink-2); stroke-width: 1.2; }
.ch-tick { font-family: var(--font-sans); font-size: 11px; fill: var(--ink-3); text-anchor: middle; font-variant-numeric: tabular-nums; }
.ch-tick--y { text-anchor: end; }
.ch-axis-lab { font-family: var(--font-sans); font-size: 11px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; fill: var(--ink-2); }
.ch-ref { stroke: var(--ink-3); stroke-width: 1.2; stroke-dasharray: 6 5; }
.ch-ref-t { font-family: var(--font-sans); font-size: 11px; font-weight: 500; letter-spacing: 0.02em; fill: var(--ink-3); paint-order: stroke; stroke: var(--paper); stroke-width: 4; stroke-linejoin: round; }
.ch-line { fill: none; stroke-width: 2.4; stroke-linejoin: round; stroke-linecap: round; }
.ch-dot { stroke-width: 2; fill: var(--paper); }
.ch-cursor { stroke: var(--amber-line); stroke-width: 1.5; stroke-dasharray: 3 3; }
.ch-read { margin-top: 1rem; display: grid; gap: 0.5rem; }
@media (min-width: 720px) { .ch-read { grid-template-columns: 1fr 1fr; } }
.ch-read div { border-top: 1px solid var(--line); padding-top: 0.5rem; font-size: var(--fs-small); color: var(--ink-2); display: grid; grid-template-columns: auto 1fr; gap: 0.5rem; align-items: start; }
.ch-read b { font-weight: 500; color: var(--ink); font-variant-numeric: tabular-nums; font-size: 1.25rem; line-height: 1.2; }
.ch-read small { display: block; color: var(--ink-3); font-size: var(--fs-micro); }
.ch-read .gfx__swatch { margin-top: 0.5rem; }
.ch-src { list-style: none; margin: 1rem 0 0; padding: 0; font-size: var(--fs-micro); color: var(--ink-3); display: grid; gap: 0.25rem; }
.ch-src li { display: grid; grid-template-columns: auto 1fr; gap: 0.5rem; align-items: start; }
.ch-src .gfx__swatch { margin-top: 0.3rem; }
.ch-src .ch-ref-sw { background: transparent; border-top: 2px dashed var(--ink-3); border-radius: 0; height: 0; margin-top: 0.55rem; }

/* 74. HEAT VS WATERJET (`.aq-heat`) — two panels, one geometry. Halo and
   tissue shading are diagram lighting (E3). Fact tiles reuse 80 `.aq-facts`. */
.ht-tissue { fill: url(#gTissue); }
.ht-cells { fill: none; stroke: #b98a86; stroke-width: 0.8; stroke-opacity: 0.5; }
.ht-margin { fill: none; stroke: var(--ink-2); stroke-width: 1.5; }
.ht-cavity { fill: var(--paper-2); }
.ht-heat { fill: url(#gHeat); mix-blend-mode: multiply; }
.ht-char { fill: none; stroke: #6b3b16; stroke-width: 3; stroke-opacity: 0.75; }
.ht-seal { fill: #6b3b16; fill-opacity: 0.85; }
.ht-vessel { fill: none; stroke: #b3474a; stroke-width: 4; stroke-linecap: round; }
.ht-vessel-open { fill: #b3474a; }
.ht-bleed { fill: #b3474a; fill-opacity: 0.55; }
.ht-protect { fill: var(--blue); fill-opacity: 0.18; stroke: var(--blue-text); stroke-width: 1.2; stroke-dasharray: 3 3; }
.ht-duct { fill: none; stroke: var(--blue-text); stroke-width: 2; stroke-linecap: round; }
.ht-duct-dot { fill: var(--blue-text); }
.ht-ruler { fill: none; stroke: var(--amber-line); stroke-width: 1.5; }
.ht-ruler-t { font-family: var(--font-sans); font-size: 10px; font-weight: 600; letter-spacing: 0.12em; fill: var(--amber-text); }
.ht-tool { fill: url(#gSteel); stroke: var(--ink); stroke-width: 1; }
.ht-beam { fill: none; stroke: #7bc96f; stroke-width: 3; stroke-linecap: round; }
.ht-beam-glow { fill: none; stroke: #7bc96f; stroke-width: 10; stroke-opacity: 0.25; stroke-linecap: round; }
.ht-jet { fill: none; stroke: var(--blue); stroke-width: 3; stroke-linecap: round; }
.ht-jet-glow { fill: none; stroke: var(--blue); stroke-width: 10; stroke-opacity: 0.22; stroke-linecap: round; }
.ht-balloon { fill: var(--paper); fill-opacity: 0.9; stroke: var(--ink-2); stroke-width: 1.2; }
.an-tag { font-family: var(--font-sans); font-size: 10.5px; font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; fill: var(--ink-2); }
.an-tag--soft { fill: var(--ink-3); font-weight: 500; letter-spacing: 0.1em; }
.an-tag--protect { fill: var(--blue-text); }
.an-spray { fill: var(--blue); fill-opacity: 0.55; }

/* 75. BPH FIT (`.bph-fit`) — every BPH procedure by prostate size. Proposed
   shared cluster component (spec §9.5): real DOM rows, linked names; solid band
   = guideline-stated range, dashed = studied or labelled beyond it. The amber
   marker is the reader's setting (--amber-line 3.99:1 non-text). */
.fit-grid { display: grid; gap: 1.25rem; margin-top: 1rem; }
@media (min-width: 900px) { .fit-grid { grid-template-columns: 5fr 7fr; } }
.fit-axis { margin-top: 1rem; }
.fit-row { display: grid; grid-template-columns: minmax(7.5rem, 10rem) 1fr; align-items: center; gap: 0.75rem; min-height: 44px; padding: 0.375rem 0; border-top: 1px solid var(--line); }
.fit-row__name { font-size: var(--fs-small); font-weight: 600; color: var(--ink); }
.fit-row__name small { display: block; font-weight: 400; color: var(--ink-3); font-size: var(--fs-micro); }
.fit-row__name a { color: inherit; text-decoration: underline; text-decoration-color: var(--line); text-underline-offset: 3px; display: inline-block; min-height: 24px; }
.fit-row__name a:hover { text-decoration-color: currentColor; }
.fit-track { position: relative; height: 28px; background: var(--paper-2); border-radius: 4px; }
.fit-band { position: absolute; top: 6px; bottom: 6px; border-radius: 3px; background: var(--mist); opacity: 0.9; }
.fit-band--soft { background: repeating-linear-gradient(90deg, var(--mist) 0 6px, transparent 6px 10px); opacity: 0.8; }
.fit-row.is-in .fit-band { background: var(--slate-dark); }
.fit-row.is-in .fit-band--soft { background: repeating-linear-gradient(90deg, var(--slate-dark) 0 6px, transparent 6px 10px); }
.fit-row.is-out .fit-row__name { color: var(--ink-3); }
.fit-marker { position: absolute; top: -6px; bottom: -6px; width: 2px; background: var(--amber-line); transform: translateX(-1px); }
.fit-marker::after { content: ""; position: absolute; top: -4px; left: -4px; width: 10px; height: 10px; border-radius: 50%; background: var(--amber-line); }
.fit-ticks { display: grid; grid-template-columns: minmax(7.5rem, 10rem) 1fr; gap: 0.75rem; height: 1.25rem; font-size: var(--fs-micro); color: var(--ink-3); }
.fit-ticks div { position: relative; }
.fit-ticks span { position: absolute; transform: translateX(-50%); top: 0; white-space: nowrap; }
.fit-ticks span:last-child { transform: translateX(-100%); }
.fit-flag { font-size: var(--fs-micro); color: var(--ink-3); }
.fit-row.is-in .fit-flag { color: var(--ink); }
.fit-row__right { display: grid; grid-template-columns: 1fr minmax(9rem, auto); gap: 0.75rem; align-items: center; }
@media (max-width: 700px) {
  .fit-row__right, .fit-row, .fit-ticks { grid-template-columns: 1fr; }
  .fit-ticks > div:first-child { display: none; }
  .fit-ticks { margin-bottom: 0.75rem; }
}
.fit-out { margin-top: 1.25rem; font-size: var(--fs-small); line-height: 1.6; color: var(--ink-2); min-height: 4.8em; max-width: 72ch; }
.fit-check { position: relative; display: inline-flex; align-items: center; gap: 0.6rem; min-height: 44px; padding: 0.5rem 0.875rem; border: 1px solid var(--line); border-radius: 999px; background: var(--paper); font-size: var(--fs-small); color: var(--ink-2); cursor: pointer; }
.fit-check input { position: absolute; inset: 0; opacity: 0; margin: 0; cursor: pointer; }
.fit-check:has(input:checked) { border-color: var(--ink); color: var(--ink); box-shadow: inset 0 -2px 0 var(--amber-line); }
.fit-check:has(input:focus-visible) { outline: 2px solid var(--blue-text); outline-offset: 2px; }
.fit-legend { display: flex; gap: 1.25rem; flex-wrap: wrap; margin-top: 0.75rem; font-size: var(--fs-micro); color: var(--ink-3); }
.fit-legend i { display: inline-block; width: 1.25rem; height: 0.5rem; border-radius: 2px; background: var(--slate-dark); vertical-align: middle; margin-right: 0.35rem; }
.fit-legend i.fit-legend__soft { background: repeating-linear-gradient(90deg, var(--slate-dark) 0 4px, transparent 4px 7px); }
.fit-legend__you { color: var(--amber-text); }

/* 76. HOSPITAL TO HOME (`.aq-days`) — SIGNAL grammar on a timeline: a signal
   square on a post that grows as the reader scrubs (user-driven, the set's one
   repeatable motion), the pooled IPSS curve drawn once. */
.rc-line { stroke: var(--ink-2); stroke-width: 1.5; stroke-linecap: butt; }
.rc-post { fill: none; stroke: var(--ink-2); stroke-width: 3; stroke-linecap: butt; }
.rc-signal { fill: var(--signal); }
.rc-track { stroke: var(--ink-3); stroke-width: 1; stroke-dasharray: 2 4; }
.rc-yaxis { stroke: var(--line); stroke-width: 1; }
.rc-station { fill: var(--paper); stroke: var(--ink-2); stroke-width: 1.5; }
.rc-station.is-on { fill: var(--amber); stroke: var(--ink); }
.rc-station.is-past { fill: var(--ink-2); }
.rc-tag { font-family: var(--font-sans); font-size: 10.5px; font-weight: 600; letter-spacing: 0.12em; text-transform: uppercase; fill: var(--ink-2); text-anchor: middle; }
.rc-tag--soft { fill: var(--ink-3); font-weight: 500; letter-spacing: 0.06em; text-transform: none; }
.rc-curve { fill: none; stroke: var(--ink); stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.rc-curve-area { fill: var(--ink); fill-opacity: 0.06; }
.rc-pt { fill: var(--paper); stroke: var(--ink); stroke-width: 2; }
.rc-pt-t { font-family: var(--font-sans); font-size: 11px; font-weight: 600; fill: var(--ink); text-anchor: middle; font-variant-numeric: tabular-nums; }
.rc-axis { font-family: var(--font-sans); font-size: 9.5px; letter-spacing: 0.08em; fill: var(--ink-3); }
.rc-hospital, .rc-home { fill: var(--paper); stroke: var(--ink-2); stroke-width: 1.4; }
.rc-cath { fill: none; stroke: var(--ink-2); stroke-width: 1.6; stroke-linecap: round; }
.rc-bag { fill: var(--blue); fill-opacity: 0.25; stroke: var(--ink-2); stroke-width: 1.2; }
.rc-cursor { stroke: var(--amber-line); stroke-width: 1.5; stroke-dasharray: 3 3; }
.rc-src { fill: var(--ink-3); font-family: var(--font-sans); font-size: 9px; letter-spacing: 0.04em; }

/* 71–76 phones: SVG text scales with the viewBox, so sizes are raised here
   rather than letting 11px chart ticks become 4px (found in review). Timeline
   station captions hide — the range's own scale carries them. */
@media (max-width: 700px) {
  .ct-tag { font-size: 34px; stroke-width: 9; }
  .ct-num { font-size: 40px; }
  .ct-plan { stroke-width: 7; }
  .sw-tag { font-size: 24px; stroke-width: 6; }
  .sw-tag--soft { font-size: 22px; }
  .ch-tick { font-size: 22px; }
  .ch-axis-lab { font-size: 19px; letter-spacing: 0.08em; }
  .ch-band-t { font-size: 15px; letter-spacing: 0.04em; }
  .ch-ref-t { font-size: 18px; stroke-width: 6; }
  .ch-line { stroke-width: 4; }
  .ch-dot { r: 7; stroke-width: 3; }
  .ch-cursor { stroke-width: 2.5; }
  .rc-tag, .rc-src { display: none; }
  .rc-pt-t { font-size: 24px; }
  .rc-axis { font-size: 20px; }
  .rc-curve { stroke-width: 3.5; }
  .rc-pt { r: 9; stroke-width: 3; }
  .rc-station { transform-box: fill-box; transform-origin: center; transform: scale(2); }
  .an-tag { font-size: 15px; }
  .ht-ruler-t { font-size: 14px; }
}

/* 77. SPLIT HERO (`.page-hero--split`) — owner D9 (2026-10-10): the HYDROS
   product photo BESIDE the heading on a dark panel, never full-bleed (the
   source is 447x687; upscaling it is a soft blow-up). Midnight band: eyebrow
   --blue-air 10.88:1, h1 white 17.89:1, lead --mist 11.0:1 — all measured
   against the brightest pixel behind each glyph (a flat field; the photo never
   sits behind text). The photo is a white-ground product shot, so it sits on
   its own white plate rather than being cut out. Counts as dark band 1 of 2
   (locations is 2). */
.page-hero--split { background: var(--midnight); color: var(--mist); border-bottom: 0; }
.page-hero--split .eyebrow { color: var(--blue-air); }
.page-hero--split h1 { color: #fff; }
.page-hero--split .lead { color: var(--mist); max-width: 52ch; }
.page-hero--split :focus-visible { outline-color: var(--blue); }
/* primary stays the orange pill here (dark scope: #F7B55A, --ink on it 10.73:1) */
.page-hero--split .btn--ghost { color: #fff; border-color: rgba(255, 255, 255, 0.6); }
.page-hero--split .btn--ghost:hover { background: #fff; color: var(--ink); }
.page-hero--split .btn--primary:hover .arrow,
.page-hero--split .btn--ghost:hover .arrow { color: var(--ink); }
.split-hero { display: grid; gap: clamp(2rem, 5vw, 4rem); align-items: center; }
@media (min-width: 900px) { .split-hero { grid-template-columns: minmax(0, 7fr) minmax(0, 4fr); } }
.split-hero__media { margin: 0; justify-self: center; width: 100%; max-width: 360px; }
.split-hero__plate { background: #fff; border-radius: var(--radius); padding: clamp(1rem, 2.5vw, 1.5rem); }
.split-hero__plate img { display: block; width: 100%; max-width: 300px; height: auto; margin-inline: auto; }
.split-hero__media figcaption { margin-top: 0.625rem; font-size: var(--fs-micro); color: var(--mist); text-align: center; }
@media (max-width: 899px) { .split-hero__media { max-width: 260px; } .split-hero__plate img { max-width: 200px; } }

/* 78. OWN CASE (`.aq-case`) — Dr. Robbins' own before / plan / after triptych.
   The after slot is positioned over the triptych's AFTER panel (measured on
   the 640x360 source: x 449–617, y 50–336) so a short muted loop can drop in
   later without re-cutting the image; empty today, so the still shows. */
.aq-case { max-width: 760px; /* 640px source: never blown up past ~1.2x */ margin: clamp(2rem, 4vw, 3rem) 0 0; border: 1px solid var(--line); border-left: 2px solid var(--amber-line); background: var(--paper); padding: clamp(1rem, 2.5vw, 1.5rem); }
.aq-case__frame { position: relative; aspect-ratio: 640 / 360; background: #000; overflow: hidden; }
.aq-case__frame video, .aq-case__frame picture, .aq-case__frame > picture img { position: absolute; inset: 0; width: 100%; height: 100%; display: block; object-fit: cover; }
.aq-case__after { position: absolute; left: 70.16%; top: 13.89%; width: 26.25%; height: 79.44%; }
.aq-case__after video { width: 100%; height: 100%; object-fit: cover; display: block; }
.aq-case figcaption { margin-top: 0.875rem; font-size: var(--fs-small); color: var(--ink-2); max-width: 72ch; }
.aq-case figcaption strong { color: var(--ink); font-weight: 600; }

/* 79. PATIENT REEL CARD (`.aq-reel`) — D8: the patient's own account lives on
   Instagram; this is a plain outbound link, never a Meta embed (§4.12, no
   third-party request). Quote slot is held for the verbatim transcript. */
.aq-reel { margin-top: clamp(2rem, 4vw, 3rem); display: grid; gap: 1.25rem; align-items: center; border: 1px solid var(--line); border-radius: var(--radius); background: var(--paper); padding: clamp(1.25rem, 3vw, 2rem); }
@media (min-width: 760px) { .aq-reel { grid-template-columns: auto 1fr; } }
.aq-reel__mark { width: 72px; height: 72px; border-radius: 50%; background: var(--ink); display: grid; place-items: center; }
.aq-reel__mark svg { width: 28px; height: 28px; margin-left: 4px; }
.aq-reel__mark path { fill: var(--amber); }
.aq-reel__kicker { font-size: var(--fs-label); font-weight: 600; letter-spacing: 0.16em; text-transform: uppercase; color: var(--blue-text); margin: 0; }
.aq-reel__title { margin: 0.375rem 0 0; font-size: var(--fs-h4); font-weight: 600; color: var(--ink); }
.aq-reel__link { display: inline-flex; align-items: center; gap: 0.4rem; min-height: 44px; margin-top: 0.25rem; }

/* 80. FACT TILES (`.aq-facts`) — two quotable numbers with their sources.
   Trial figures, never practice figures (§4.13's stat row is for those). */
.aq-facts { display: grid; gap: 1rem; margin-top: 1.25rem; }
@media (min-width: 760px) { .aq-facts { grid-template-columns: 1fr 1fr; } }
.aq-facts > div { border-top: 1px solid var(--line); padding-top: 0.875rem; position: relative; }
.aq-facts > div::before { content: ""; position: absolute; top: -1px; left: 0; width: 28px; height: 1px; background: var(--amber-line); }
.aq-facts b { display: block; font-size: clamp(1.5rem, 3vw, 2.25rem); font-weight: 500; letter-spacing: -0.02em; color: var(--ink); font-variant-numeric: tabular-nums; line-height: 1.1; }
.aq-facts b small { font-size: 0.6em; color: var(--ink-3); font-weight: 500; }
.aq-facts span { display: block; font-size: var(--fs-small); color: var(--ink-3); margin-top: 0.25rem; }

/* 81. FIGURE FRAME (`.aq-figure`) — a still at its own aspect inside a light
   section, credit line under it. Not `.media-frame` (that is the cinema
   treatment); the planning screen and the animation still are instruments,
   not photos. */
.aq-figure { margin: clamp(2rem, 4vw, 3rem) 0 0; max-width: 760px; }
.aq-figure img { display: block; width: 100%; height: auto; border-radius: var(--radius); border: 1px solid var(--line); }
.aq-figure figcaption { margin-top: 0.625rem; font-size: var(--fs-micro); color: var(--ink-3); }
.aq-split { display: grid; gap: clamp(1.5rem, 4vw, 3rem); align-items: start; }
@media (min-width: 900px) { .aq-split { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); } }
.aq-split .aq-figure { margin-top: 0; }
.aq-dl { display: grid; grid-template-columns: auto 1fr; gap: 0.5rem 1.25rem; margin: 1.5rem 0 0; font-size: var(--fs-body); max-width: 64ch; }
.aq-dl dt { font-weight: 600; color: var(--ink); }
.aq-dl dd { margin: 0; color: var(--ink-2); }
@media (max-width: 520px) { .aq-dl { grid-template-columns: 1fr; } .aq-dl dd { margin-bottom: 0.5rem; } }
/* 74/76 JS hooks: `.ht-halo` (the heat halo group whose opacity animates once)
   and `.rc-stn` (a timeline station group read by ucofl.js). Structural only. */
.ht-halo, .rc-stn { pointer-events: none; }

/* 82. AQUABLATION — HYDROS photo relocated from the hero (owner 2026-10-10) ----
   The split-hero plate now sits on a LIGHT band in "The system in the room", so
   the caption leaves --mist (tuned for midnight) for --ink-3. */
.aq-hydros { margin: clamp(1.5rem, 4vw, 2.5rem) auto 0; }
.aq-hydros .split-hero__plate { box-shadow: 0 0 0 1px var(--line, rgba(0,0,0,.08)); }
.aq-hydros figcaption { color: var(--ink-3); }
/* Trust section (moved up 2026-10-10): on one column the team photo leads, so
   "first in South Florida" is seen with its picture, not after 30 lines of prose. */
@media (max-width: 899px) { .aq-split--photo-first > .aq-figure { order: -1; } }

/* 83. AQUABLATION PATIENT TESTIMONIAL (`.aq-testi`) — owner 2026-10-10 --------
   A vertical phone video beside the patient's verbatim words (component 40).
   The video is capped so a 9:16 frame never becomes a full-screen wall on desktop;
   on one column it sits first, then the quote. Click-to-play only: preload none,
   native controls, captions on by default — no autoplay, so §0's "no loops beside
   text" does not apply. Supersedes the component 79 Instagram link card. */
.aq-testi { display: grid; gap: clamp(1.5rem, 4vw, 3rem); align-items: center; }
@media (min-width: 760px) { .aq-testi { grid-template-columns: minmax(0, 320px) minmax(0, 1fr); } }
.aq-testi__video { margin: 0; justify-self: center; width: 100%; max-width: 320px; }
.aq-testi__video video { display: block; width: 100%; height: auto; aspect-ratio: 9 / 16; border-radius: var(--radius); background: #0b0f12; }
.aq-testi__words .testi-quote { margin-top: 0; }
.aq-testi__words .testi-quote p { font-size: clamp(1.0625rem, 1.6vw, 1.25rem); color: var(--ink); }
.aq-testi__words .micro { margin-top: 1rem; color: var(--ink-3); }

/* ==========================================================================
   84–88  GREENLIGHT LASER PAGE (`greenlight-laser-surgery.html`)
   notes/SPEC-greenlight-redesign.md §6, built 2026-10-10 from the approved
   prototype (scratchpad/greenlight-graphics-prototype.html). All four sit in
   the 63–66 `.gfx` panel grammar; nothing here restyles `.gfx` itself.
   Renames from the prototype, so nothing collides with shipped classes:
   prototype `.gfx__cap` (flex row) -> the existing `.gfx__cap-row`;
   `.gfx__ctl` -> the existing `.gfx__controls` (its [hidden] guard applies);
   `.no-js` / `.js-only` -> a per-graphic `is-live` class that ucofl.js adds
   once the graphic is wired, so a page whose script fails or is blocked
   shows the complete still and the text fallbacks — never an empty frame.

   Palette exception E4 (spec §6, owner-approved with the graphics): the
   clinical-illustration tones and the 532-nm green live ONLY inside these
   frames, scoped to the graphic roots below — never in UI, never on a panel.
   ========================================================================== */
.gl-channel, .gl-fiber {
  --gl-tissue-1: #efc6ba; --gl-tissue-2: #d6978a; --gl-tissue-3: #a86459; --gl-tissue-4: #6e3b35;
  --gl-cavity: #3a1b18; --gl-lumen: #e6f4fb; --gl-532: #3ddc84; --gl-532-glow: #a7f5c6;
}
/* A light panel that lands on a --paper-2 band takes a --paper fill, or the
   panel and the band are the same grey and the frame dissolves. */
.band--soft .gl-panel { background: var(--paper); }
.gl-panel { margin-top: clamp(2rem, 4vw, 3rem); }
.gl-panel .gfx__cap-row .gfx__state { color: var(--ink); }

/* 84. THE CHANNEL (`.gl-channel`, graphic G1) — why the stream got weak, and
   what the laser changes. One state t (0–1) drives four path strings through a
   Catmull-Rom helper in ucofl.js: cavity, flow, the green vaporization front
   (0.02 < t < 0.985) and the --amber-line sealed rim (t >= 0.985, the ONE
   amber element). A dashed white ghost of the t=0 slit appears past t=0.15 so
   the eye compares. The MARKUP ships at t=1 with the ghost visible: that is
   the no-JS and reduced-motion picture, and the whole argument in one still.
   Plays once 0 -> 1 (3 s) at 50% visibility; any input cancels it.
   Labels: --ink-2 9.09:1 / --ink-3 5.40:1 on paper-2. */
.gl-channel svg { max-width: 560px; margin-inline: auto; overflow: visible; }
.gl-channel .lbl { font-size: 12px; font-weight: 600; fill: var(--ink-2); letter-spacing: 0.02em; }
.gl-channel .lbl--muted { fill: var(--ink-3); font-weight: 500; }
.gl-channel .lead-line { stroke: var(--ink-3); stroke-width: 1; fill: none; opacity: 0.7; }
.gl-channel .front { stroke: var(--gl-532); stroke-width: 3; fill: none; filter: url(#glGlow); transition: opacity 300ms var(--ease); }
.gl-channel .seal { stroke: var(--amber-line); stroke-width: 2.5; fill: none; transition: opacity 300ms var(--ease); }
.gl-channel .ghost { stroke: #fff; stroke-opacity: 0.85; stroke-width: 1.5; stroke-dasharray: 4 5; fill: none; transition: opacity 300ms var(--ease); }
.gl-channel .capsule-tag { transition: opacity 300ms var(--ease); }
@media (max-width: 700px) {
  /* 560 units render ~305px wide on a 390px phone: 12-unit labels would land
     at ~6.5px. Raised in viewBox units; the leader lines carry the pointing. */
  .gl-channel .lbl { font-size: 18px; }
}

/* 85. GREEN LIGHT AT WORK (`.gl-fiber`, graphic G2) — night panel (E1; the
   page's only midnight window, never adjacent to its dark band). Cut-away of
   the prostatic urethra: beam -> crater -> bubbles carried by the irrigation
   -> the sealed layer (--amber, 10.19:1 on midnight) draws along the crater.
   DEFAULT STATE IS THE FINISHED STILL: every part visible. ucofl.js adds
   `is-armed` (reset to the start) and then `is-live` (the CSS sequence plays,
   3.2 s, once); it removes both afterwards, so the still remains. Replay is
   the reader's: arm, reflow, live — a class change, so it truly restarts
   (§4.17 lesson). Keys dim the other parts to 0.28 via `data-hl`.
   No JS / reduced motion: never armed, so the still is all there is. */
.gl-fiber svg { max-width: 720px; margin-inline: auto; }
.gl-fiber .lbl { font-size: 12px; font-weight: 600; fill: #fff; letter-spacing: 0.02em; }
.gl-fiber .lbl--sub { fill: var(--mist); font-weight: 500; }
.gl-fiber .lead-line { stroke: var(--mist); stroke-width: 1; fill: none; opacity: 0.7; }
.gl-fiber [data-part] { transition: opacity 260ms var(--ease); }
.gl-fiber[data-hl] [data-part] { opacity: 0.28; }
.gl-fiber[data-hl="beam"] [data-part="beam"],
.gl-fiber[data-hl="vapor"] [data-part="vapor"],
.gl-fiber[data-hl="seal"] [data-part="seal"] { opacity: 1; }
.gl-fiber .beam-core { stroke: var(--gl-532-glow); stroke-width: 2; fill: none; stroke-dasharray: 1; stroke-dashoffset: 0; }
.gl-fiber .sealband { stroke: var(--amber); stroke-width: 5; fill: none; stroke-linecap: round; stroke-dasharray: 1; stroke-dashoffset: 0; }
.gl-fiber .bubble { transform: translate(70px, 14px); opacity: 0.55; }
/* armed = the first frame of the sequence */
.gl-fiber.is-armed .beam, .gl-fiber.is-armed .crater, .gl-fiber.is-armed .bubble,
.gl-fiber.is-armed .flowtag, .gl-fiber.is-armed .vaportag, .gl-fiber.is-armed .sealtag { opacity: 0; }
.gl-fiber.is-armed .bubble { transform: translate(0, 0); }
.gl-fiber.is-armed .beam-core, .gl-fiber.is-armed .sealband { stroke-dashoffset: 1; }
/* live = the sequence, each verb once, by animation-delay */
.gl-fiber.is-live .beam { animation: glFade 700ms var(--ease) 200ms forwards; }
.gl-fiber.is-live .beam-core { animation: glDraw 700ms var(--ease) 250ms forwards; }
.gl-fiber.is-live .crater { animation: glFade 500ms var(--ease) 900ms forwards; }
.gl-fiber.is-live .bubble { animation: glBubble 1600ms var(--ease) forwards; }
.gl-fiber.is-live .bubble:nth-child(1) { animation-delay: 1000ms; }
.gl-fiber.is-live .bubble:nth-child(2) { animation-delay: 1120ms; }
.gl-fiber.is-live .bubble:nth-child(3) { animation-delay: 1240ms; }
.gl-fiber.is-live .bubble:nth-child(4) { animation-delay: 1360ms; }
.gl-fiber.is-live .bubble:nth-child(5) { animation-delay: 1480ms; }
.gl-fiber.is-live .bubble:nth-child(6) { animation-delay: 1600ms; }
.gl-fiber.is-live .bubble:nth-child(7) { animation-delay: 1720ms; }
.gl-fiber.is-live .bubble:nth-child(8) { animation-delay: 1840ms; }
.gl-fiber.is-live .flowtag { animation: glFade 500ms var(--ease) 1300ms forwards; }
.gl-fiber.is-live .vaportag { animation: glFade 500ms var(--ease) 1500ms forwards; }
.gl-fiber.is-live .sealband { animation: glDraw 900ms var(--ease) 1900ms forwards; }
.gl-fiber.is-live .sealtag { animation: glFade 500ms var(--ease) 2500ms forwards; }
@keyframes glFade { from { opacity: 0; } to { opacity: 1; } }
@keyframes glDraw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
@keyframes glBubble { 0% { opacity: 0; transform: translate(0, 0); } 20% { opacity: 0.95; } 100% { opacity: 0.55; transform: translate(70px, 14px); } }
.gl-fiber__bar { display: flex; flex-wrap: wrap; gap: 0.5rem 1rem; align-items: center; margin-top: 1rem; }
.gl-fiber__bar[hidden] { display: none !important; }
.gl-fiber .gfx__keys { margin-top: 0; }
/* the shared .gfx__key (70) is drawn for light panels; on midnight it inverts */
.gl-fiber .gfx__key { background: transparent; border-color: var(--line-dark); color: var(--mist); }
.gl-fiber .gfx__key:hover { border-color: var(--mist); color: #fff; }
.gl-fiber .gfx__key[aria-pressed="true"] { border-color: var(--amber); color: #fff; box-shadow: inset 0 0 0 1px var(--amber); }
.gl-fiber :focus-visible { outline-color: var(--blue); }
.gl-fiber .gfx__cap-row .gfx__state { color: #fff; }
@media (max-width: 700px) {
  .gl-fiber .lbl { font-size: 20px; }
  .gl-fiber .lbl--sub { display: none; }
}

/* 86. WHICH FITS A MAN LIKE ME? (`.gl-fit`, graphic G3) — reader-driven,
   nothing plays. Four native radio groups drive four cards; each card says,
   IN WORDS, what the AUA guideline says for that profile. Every guideline
   sentence the cards quote is read from the no-JS table in the markup
   (`[data-aua]`), so the guideline wording lives in ONE place and is swapped
   there when the 2026 statements are verified. Fit is tri-state and SHAPE-
   coded (signal raised on a post / resting on the line / dropped below it) as well as worded
   — never colour alone. No-JS: the form stays hidden and that table is the
   graphic. Pills: --ink-2 on white 9.09:1, checked rim --amber-line 3.99:1. */
.gl-fit fieldset { border: 0; padding: 0; margin: 0 0 1rem; min-width: 0; }
.gl-fit legend { font-size: var(--fs-label); font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-3); padding: 0; margin-bottom: 0.5rem; }
.gl-fit__form[hidden] { display: none !important; }
.gl-fit__opts { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.gl-fit__opt { position: relative; }
.gl-fit__opt input { position: absolute; opacity: 0; inset: 0; width: 100%; height: 100%; margin: 0; cursor: pointer; }
.gl-fit__opt span { display: inline-flex; align-items: center; min-height: 44px; padding: 0.55rem 1rem; border: 1px solid var(--line); border-radius: 999px; background: #fff; font-size: var(--fs-small); font-weight: 500; color: var(--ink-2); transition: border-color 200ms var(--ease), color 200ms var(--ease); }
.gl-fit__opt:hover span { border-color: var(--ink-3); color: var(--ink); }
.gl-fit__opt input:checked + span { border-color: var(--amber-line); box-shadow: inset 0 0 0 1px var(--amber-line); color: var(--ink); }
.gl-fit__opt input:focus-visible + span { outline: 2px solid var(--blue-text); outline-offset: 3px; }
.gl-fit__grid, .gl-fit__sum, .gl-fit__others { display: none; }
.gl-fit.is-live .gl-fit__grid { display: grid; }
.gl-fit.is-live .gl-fit__sum, .gl-fit.is-live .gl-fit__others { display: block; }
.gl-fit.is-live .gl-fit__table { display: none; }
.gl-fit__grid { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1rem; margin: 1.5rem 0 0; list-style: none; padding: 0; }
@media (max-width: 1000px) { .gl-fit__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 560px) { .gl-fit__grid { grid-template-columns: 1fr; } }
.gl-fit__card { background: #fff; border: 1px solid var(--line); border-radius: var(--radius); padding: 1.25rem; display: flex; flex-direction: column; gap: 0.5rem; min-width: 0; transition: border-color 300ms var(--ease); }
.band--soft .gl-fit__card { background: var(--paper-2); }
.gl-fit__card[data-fit="in"] { border-color: var(--amber-line); }
.gl-fit__card h3 { margin: 0; font-size: var(--fs-h4); font-weight: 600; line-height: 1.3; letter-spacing: -0.005em; max-width: none; }
.gl-fit__card h3 a { color: inherit; text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: 0.18em; }
.gl-fit__card h3 a:hover { color: var(--blue-text); }
.gl-fit__card .setting { font-size: var(--fs-micro); color: var(--ink-3); letter-spacing: 0.01em; }
.gl-fit__mark { display: block; width: 48px; height: 32px; margin: 0 0 0.25rem; color: var(--ink-2); overflow: visible; }
.gl-fit__mark .s { fill: var(--signal); transition: transform 650ms var(--ease); }
.gl-fit__mark .p { transform-box: fill-box; transform-origin: center bottom; transform: scaleY(0); transition: transform 650ms var(--ease); }
.gl-fit__card[data-fit="in"] .gl-fit__mark .s { transform: translateY(-10px); }   /* raised on its post */
.gl-fit__card[data-fit="in"] .gl-fit__mark .p { transform: scaleY(1); }
.gl-fit__card[data-fit="out"] .gl-fit__mark .s { transform: translateY(8px); }    /* dropped below the line */
.gl-fit__status { font-size: var(--fs-small); color: var(--ink); line-height: 1.5; margin: 0; }
.gl-fit__status b { font-weight: 600; }
.gl-fit__why { font-size: var(--fs-micro); color: var(--ink-3); line-height: 1.55; margin: 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 0.35rem; }
.gl-fit__why b { color: var(--ink-2); font-weight: 600; }
.gl-fit__sum { margin: 1.25rem 0 0; font-size: var(--fs-body); color: var(--ink); }
.gl-fit__others { margin: 0.75rem 0 0; font-size: var(--fs-small); color: var(--ink-3); }
.gl-fit__table { width: 100%; border-collapse: collapse; font-size: var(--fs-small); margin-top: 0.5rem; }
.gl-fit__table caption { text-align: left; font-size: var(--fs-label); font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-3); padding-bottom: 0.5rem; }
.gl-fit__table th, .gl-fit__table td { text-align: left; padding: 0.6rem 0.5rem; border-top: 1px solid var(--line); vertical-align: top; color: var(--ink-2); }
.gl-fit__table th { color: var(--ink); font-weight: 600; }
@media (max-width: 560px) { .gl-fit__table th, .gl-fit__table td { display: block; padding-inline: 0; } .gl-fit__table td { border-top: 0; padding-top: 0; } }

/* 87. THE WEEKS AFTER (`.gl-recovery`, graphic G4) — SIGNAL grammar on a
   timeline: six stops, and the --signal square slides along the line and
   lifts one small step per stop the reader selects. Stops are a real WAI-ARIA tablist (arrow keys / Home / End,
   roving tabindex) wired by ucofl.js; the panel is the text alternative and
   reads its words from the <dl> below it, which is also the no-JS view — one
   source for the words. Reveal: the line draws once via the .reveal
   observer. Not component 42 (.protocol): that is a vertical, static list;
   this needs selectable stops (spec §6.4 allowed either). */
.gl-recovery svg { max-width: 860px; margin-inline: auto; overflow: visible; }
.gl-recovery .line { stroke: var(--ink-2); stroke-width: 2; stroke-linecap: butt; fill: none; stroke-dasharray: 1; stroke-dashoffset: 0; transition: stroke-dashoffset 900ms var(--ease); }
.js .gl-recovery.reveal:not(.is-in) .line { stroke-dashoffset: 1; }
.gl-recovery .tick { stroke: var(--ink-3); stroke-width: 2; }
.gl-recovery .sgp { fill: var(--signal); }
.gl-recovery .sgpost { fill: none; stroke: var(--ink-2); stroke-width: 3; stroke-linecap: butt; }
.gl-recovery .sg-g { transition: transform 700ms var(--ease); }
.gl-recovery .stop-lbl { font-size: 12px; font-weight: 600; fill: var(--ink-2); }
.gl-recovery .stop-sub { font-size: 11px; font-weight: 500; fill: var(--ink-3); }
.gl-recovery__controls { display: none; }
.gl-recovery.is-live .gl-recovery__controls { display: block; }
.gl-recovery.is-live .gl-recovery__all { display: none; }
.gl-recovery__stops { display: grid; grid-template-columns: repeat(6, minmax(0, 1fr)); gap: 0.5rem; margin: 1rem 0 0; padding: 0; list-style: none; }
.gl-recovery__stop { width: 100%; min-height: 44px; padding: 0.6rem 0.5rem; border: 1px solid var(--line); border-radius: 12px; background: #fff; color: var(--ink-2); font: inherit; font-size: var(--fs-micro); font-weight: 600; letter-spacing: 0.04em; cursor: pointer; text-align: center; transition: border-color 200ms var(--ease), color 200ms var(--ease); }
.gl-recovery__stop:hover { border-color: var(--ink-3); color: var(--ink); }
.gl-recovery__stop[aria-selected="true"] { border-color: var(--amber-line); box-shadow: inset 0 0 0 1px var(--amber-line); color: var(--ink); }
.gl-recovery__panel { margin-top: 1.25rem; padding: 1.25rem 1.5rem; background: #fff; border: 1px solid var(--line); border-radius: var(--radius); }
.band--soft .gl-recovery__panel { background: var(--paper-2); }
.gl-recovery__panel h3 { margin: 0 0 0.35rem; font-size: var(--fs-h4); font-weight: 600; max-width: none; }
.gl-recovery__panel p { margin: 0; color: var(--ink-2); }
.gl-recovery__all { margin: 1rem 0 0; }
.gl-recovery__all dt { font-weight: 600; color: var(--ink); margin-top: 0.75rem; }
.gl-recovery__all dd { margin: 0; color: var(--ink-2); }
@media (max-width: 760px) {
  .gl-recovery__stops { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .gl-recovery .stop-lbl, .gl-recovery .stop-sub { display: none; }
}

/* 88. GREENLIGHT PAGE SUPPORT — three small pieces the four graphics do not
   cover. (a) `.gl-strip`: legacy photos are 268–483px CMS thumbnails, so they
   ship as one contained strip at near-native width (the 44 `.office-duo`
   doctrine — small photography made deliberate, never blown up); it carries
   the four teaching photos and, as `--pair`, the endoscopic before/after.
   (b) `.gl-watch`: a legacy "Watch:" h2 kept beside the facade it labels,
   held at h4 scale — a label for a video, not a section heading (§1: a visual
   scale rule, the tag stays h2). (c) the hero lead's measure on this page. */
.gl-strip { margin: clamp(2rem, 4vw, 3rem) 0 0; max-width: 1100px; }
.gl-strip__grid { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1rem; align-items: end; }
.gl-strip--pair { max-width: 640px; }
.gl-strip--pair .gl-strip__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
@media (max-width: 760px) { .gl-strip__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
.gl-strip__grid > * { margin: 0; min-width: 0; }
.gl-strip img { display: block; width: 100%; height: auto; border-radius: var(--radius); }
.gl-strip__label { margin: 0.5rem 0 0; font-size: var(--fs-micro); color: var(--ink-3); }
.gl-strip > figcaption { margin-top: 0.875rem; font-size: var(--fs-label); font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-3); line-height: 1.5; }
.gl-strip--pair > figcaption { text-transform: none; letter-spacing: 0.01em; font-weight: 400; font-size: var(--fs-small); }
.gl-watch { margin-top: clamp(2.5rem, 5vw, 3.5rem); max-width: 860px; }
.gl-watch h2 { margin: 0 0 0.25rem; font-size: var(--fs-h4); font-weight: 600; line-height: 1.35; letter-spacing: -0.005em; max-width: none; }
.gl-watch > p { margin: 0 0 1rem; font-size: var(--fs-small); color: var(--ink-3); }
.gl-voice__quote { margin-top: clamp(1.5rem, 3vw, 2.25rem); }
.gl-voice__quote p { font-size: var(--fs-lead); line-height: 1.45; color: var(--ink); }
.gl-src { margin-top: clamp(2rem, 4vw, 2.75rem); max-width: 72ch; font-size: var(--fs-small); color: var(--mist); }
.gl-src p + p { margin-top: 0.5rem; }
@media (prefers-reduced-motion: reduce) {
  .gl-channel .front, .gl-channel .seal, .gl-channel .ghost, .gl-channel .capsule-tag,
  .gl-fiber [data-part], .gl-fit__mark .s, .gl-fit__mark .p, .gl-recovery .sg-g, .gl-recovery .line { transition: none; }
}
/* 88 (d) — the testimonials split: quotes beside the one patient still. Own
   names rather than reusing 81's `.aq-split`, so the two pages never couple. */
.gl-split { display: grid; gap: clamp(1.5rem, 4vw, 3rem); align-items: start; margin-top: clamp(2.5rem, 5vw, 3.5rem); }
@media (min-width: 900px) { .gl-split { grid-template-columns: minmax(0, 7fr) minmax(0, 4fr); } }
.gl-split p { max-width: 64ch; }
.gl-split p + p { margin-top: 0.75rem; }
.gl-figure { margin: 0; max-width: 340px; justify-self: center; }
.gl-figure img { display: block; width: 100%; height: auto; border-radius: var(--radius); }
.gl-figure figcaption { margin-top: 0.625rem; font-size: var(--fs-micro); color: var(--ink-3); }
/* 84 JS hook: `.ure-flow` (the stream leaving the urethra, resized by ucofl.js). Structural only. */
.gl-channel .ure-flow { pointer-events: none; }
/* 86 fix: the shared `.gfx svg { width: 100% }` (63–66) outranks a bare class,
   which drew the fit marks at card width. */
.gfx .gl-fit__mark { width: 48px; height: 32px; }
/* 83b. A second testimonial row (owner 2026-10-10) — spaced like a new paragraph of evidence,
   no rule between them: the quote's own amber cap line already marks each one. */
.aq-testi + .aq-testi { margin-top: clamp(2.5rem, 5vw, 4rem); }

/* 89. GREENLIGHT TEACHING MAP (`.gl-map`) — owner 2026-10-10 --------------------
   The Americas (Natural Earth 1:50m, baked into the SVG) with the cities where he
   has taught GreenLight and the places patients have travelled from. Monochrome
   land; the only accents are amber (taught there, a filled square — the site's
   SIGNAL mark) and blue (patients from there, an open ring). Shape carries the
   meaning, colour only reinforces it, and the figcaption lists every place in
   words — the SVG labels are a convenience, hidden where they would be too small.
   Motion (§0, once, on the site .reveal): arcs draw out from Miami one after another, then the dots
   and rings settle. Reduced motion / no JS: the finished map, nothing hidden. */
.gl-map-grid { display: grid; gap: clamp(1.5rem, 4vw, 3rem); align-items: start; }
@media (min-width: 960px) { .gl-map-grid { grid-template-columns: minmax(0, 7fr) minmax(0, 4fr); } }
.gl-map { margin: 0; min-width: 0; }
.gl-map__svg { display: block; width: 100%; height: auto; }
.glm-land { fill: #dfe5ea; stroke: #b9c4cc; stroke-width: 0.6; stroke-linejoin: round; }
.glm-arc { fill: none; stroke: var(--amber-line); stroke-width: 1.4; stroke-linecap: round; stroke-dasharray: 1; stroke-dashoffset: 0; opacity: 0.85; }
.glm-teach { fill: var(--amber-text); stroke: #fff; stroke-width: 1.5; }
.glm-from { fill: none; stroke: var(--blue-text); stroke-width: 1.6; }
.glm-home { fill: var(--midnight); stroke: #fff; stroke-width: 2; }
.glm-labels text { font: 600 12px/1 var(--font-sans, system-ui, sans-serif); fill: var(--ink); paint-order: stroke; stroke: rgba(245, 247, 248, 0.9); stroke-width: 3px; stroke-linejoin: round; }
.glm-labels .glm-sub { font-weight: 500; fill: var(--ink-3); }
.glm-labels .glm-home-t { font-weight: 700; }
@media (max-width: 560px) { .glm-labels { display: none; } }   /* < 7px rendered: the legend carries them */
.gl-map__legend { list-style: none; margin: 1rem 0 0; padding: 0; display: grid; gap: 0.75rem; font-size: var(--fs-small); color: var(--ink-2); line-height: 1.55; }
.gl-map__legend li { display: grid; grid-template-columns: 18px minmax(0, 1fr); gap: 0.6rem; align-items: start; }
.glm-key { width: 12px; height: 12px; margin-top: 0.3rem; border-radius: 50%; }
.glm-key--teach { border-radius: 0; }   /* the signal square (SIGNAL, §4.17), as drawn on the map */
.glm-key--teach { background: var(--amber-text); }
.glm-key--from { border: 2px solid var(--blue-text); }
.gl-agenda { margin: 0; min-width: 0; }
.gl-agenda img { display: block; width: 100%; height: auto; border: 1px solid var(--line); border-radius: 6px; background: #fff; }
@media (max-width: 959px) { .gl-agenda { max-width: 440px; justify-self: center; } }
.gl-agenda figcaption { margin-top: 0.625rem; font-size: var(--fs-micro); color: var(--ink-3); line-height: 1.5; }
.gl-map__more { margin-top: clamp(1.25rem, 3vw, 2rem); }
.gl-map__more a { display: inline-flex; align-items: center; min-height: 44px; color: var(--blue-text); font-weight: 600; }

/* animation — rides the site reveal: hidden only while <html> has `js` and the figure has
   not yet been revealed (the head watchdog strips `js` if scripts fail, so nothing stays hidden) */
html.js .gl-map:not(.is-in) .glm-arc { stroke-dashoffset: 1; }
html.js .gl-map:not(.is-in) .glm-teach,
html.js .gl-map:not(.is-in) .glm-from { opacity: 0; }
.gl-map.is-in .glm-arc { animation: glmDraw 900ms var(--ease) both; animation-delay: calc(var(--i) * 160ms); }
.gl-map.is-in .glm-teach { animation: glmPop 420ms var(--ease) both; animation-delay: calc(var(--i) * 160ms + 760ms); }
.gl-map.is-in .glm-from { animation: glmFade 600ms var(--ease) both; animation-delay: calc(var(--j) * 90ms + 1900ms); }
@keyframes glmDraw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
@keyframes glmPop { from { opacity: 0; } to { opacity: 1; } }
@keyframes glmFade { from { opacity: 0; } to { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .gl-map .glm-arc, .gl-map .glm-teach, .gl-map .glm-from { animation: none !important; opacity: 1 !important; stroke-dashoffset: 0 !important; }
}
.glm-arcs, .glm-dots, .glm-rings { pointer-events: none; }   /* groups: structure for the stagger only */
.trust-line .t-label a { color: inherit; text-decoration: underline; text-decoration-color: var(--line); text-underline-offset: 3px; }

/* 90. GREENLIGHT OR LOOP (`.gl-or-loop`) — owner 2026-10-10 ---------------------
   A square 720px clip shown at (at most) its own size — a soft source stretched
   wide reads as low quality exactly where trust is being built. Plays only in
   view via the component 68 [data-media-band] script; poster otherwise. */
.gl-or-loop { margin: clamp(2rem, 4vw, 3rem) auto 0; max-width: 520px; }
.gl-or-loop video { display: block; width: 100%; height: auto; aspect-ratio: 1 / 1; border-radius: var(--radius); background: #0b0f12; object-fit: cover; }
.gl-or-loop figcaption { margin-top: 0.625rem; font-size: var(--fs-micro); color: var(--ink-3); line-height: 1.5; text-align: center; }

/* 91. VASECTOMY REVERSAL HERO ART (`.vrh`) — owner 2026-10-10 --------------------
   An original SVG in the cinema hero slot until the owner's loupes photo arrives.
   Two shaded tube ends (the vas) drift together, five stitches draw across the seam,
   an amber pulse runs through the rejoined channel, and the signal square rises
   onto the line (SIGNAL, §4.17). ONCE (§0), on load, CSS only. Every element's FINAL state is its resting
   style; the animations run from the start state with fill `both`, so with reduced
   motion (animation: none) or before CSS animation support the finished frame shows.
   xMaxYMid slice keeps the junction on the right, away from the left-aligned text. */
.hero-media .vrh-art { position: absolute; inset: 0; width: 100%; height: 100%; display: block; }
.vrh-face { fill: #2b3843; stroke: #a9b8c2; stroke-width: 1.5; }
.vrh-lumen { fill: #161D22; }
.vrh-st { fill: none; stroke: #eef3f6; stroke-width: 2; stroke-linecap: round; stroke-dasharray: 1; stroke-dashoffset: 0; }
.vrh-line { stroke: var(--mist); stroke-width: 1.5; stroke-linecap: butt; opacity: .5; }
.vrh-mark { fill: var(--signal); }
.vrh-glow { opacity: 1; }
.vrh-pulse { opacity: 0; }
html.js .vrh-left  { animation: vrhInL 1600ms var(--ease) 300ms both; }
html.js .vrh-right { animation: vrhInR 1600ms var(--ease) 300ms both; }
html.js .vrh-st    { animation: vrhDraw 420ms var(--ease) both; animation-delay: calc(1900ms + var(--i) * 140ms); }
html.js .vrh-pulse { animation: vrhPulse 2600ms cubic-bezier(.45,.05,.55,.95) 2700ms both; }
html.js .vrh-glow  { animation: vrhFade 1400ms var(--ease) 4600ms both; }
html.js .vrh-mark  { animation: vrhRise 1800ms var(--ease) 4400ms both; }
@keyframes vrhInL { from { transform: translateX(-90px); } to { transform: none; } }
@keyframes vrhInR { from { transform: translateX(90px); } to { transform: none; } }
@keyframes vrhDraw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
@keyframes vrhPulse { 0% { opacity: 0; transform: translateX(0); } 12% { opacity: 1; } 88% { opacity: 1; } 100% { opacity: 0; transform: translateX(-1100px); } }
@keyframes vrhFade { from { opacity: 0; } to { opacity: 1; } }
@keyframes vrhRise { from { transform: translateY(44px); } to { transform: none; } }
.vrh-left, .vrh-right, .vrh-pulse, .vrh-mark { transform-box: view-box; }
@media (prefers-reduced-motion: reduce) {
  .vrh-left, .vrh-right, .vrh-st, .vrh-pulse, .vrh-glow, .vrh-mark { animation: none !important; }
}
.vrh-stitches { pointer-events: none; }
/* Narrow screens: the text column IS the whole hero, so the art can only sit behind it.
   Dimmed to a hint; contrast measured at 390px after this rule (2026-10-10). */
@media (max-width: 759px) { .hero-media .vrh-art { opacity: 0.3; } }

/* 92. OWNER REEL (`.vx-reel`) — vasectomy page, owner 2026-10-10 ------------------
   A vertical 9:16 talking reel in the "Meet Your Expert" column. Capped so it never
   becomes a full-height wall; click-to-play with native controls. The reel carries
   burned-in (open) captions; the transcript below is the text alternative. */
.vx-reel { margin: 0 auto; max-width: 340px; }
.vx-reel video { display: block; width: 100%; height: auto; aspect-ratio: 9 / 16; border-radius: var(--radius); background: #0b0f12; }
.vx-reel__transcript { margin-top: 0.75rem; font-size: var(--fs-small); color: var(--ink-2); }
.vx-reel__transcript summary { cursor: pointer; min-height: 44px; display: flex; align-items: center; color: var(--blue-text); font-weight: 600; }
.vx-reel__transcript p { margin: 0.5rem 0 0; line-height: 1.6; }

/* 93. HERO GOOGLE RATING (`.hero-rating`) — owner 2026-10-10 ---------------------
   One line under the hero CTAs: five amber stars, the score, the review count, all
   one link to the Google profile so a reader can verify it. White text on the cinema
   scrim (measured with ucofl-hero-contrast); 44px tap target; stars are decorative —
   the words carry the meaning. */
.hero-rating { margin: 1.25rem 0 0; }
.hero-rating a { display: inline-flex; align-items: center; gap: 0.5rem; min-height: 44px; color: #fff; text-decoration: none; font-size: var(--fs-small); }
.hero-rating a:hover span:not(.hero-rating__stars), .hero-rating a:focus-visible span:not(.hero-rating__stars) { text-decoration: underline; text-underline-offset: 3px; }
.hero-rating a:focus-visible { outline: 2px solid #fff; outline-offset: 3px; }
.hero-rating strong { font-size: 1.125rem; font-weight: 700; }
.hero-rating__stars { display: inline-flex; gap: 2px; }
.hero-rating__stars svg { width: 16px; height: 16px; fill: var(--amber); }
.page-hero:not(.page-hero--cinema) .hero-rating a { color: var(--ink); }

/* 94. "THIS PAGE" ROW (`.svc-row--here`) — owner 2026-10-10 --------------------------
   The first row of the vasectomy page's options band points DOWN into the page
   (anchor to #nnns-h) instead of away from it. A thin amber rule on its leading edge
   and a down arrow mark it as "continue here", so the band no longer reads as a
   gate the reader must pick an exit from. */
.svc-row--here { box-shadow: inset 3px 0 0 var(--amber); }
.svc-row--here .arrow { color: var(--amber); }

/* 95. VASECTOMY CREDENTIALS BAND (`.vx-creds`) — rev 2026-10-10 (Option A, "over the top") ---
   Midnight band directly under the hero: three checkable numbers, the people he
   treats, and where they travel from. Every fact is owner-confirmed (see the page
   comment). Owner chose prototype Option A on 2026-10-10.

   🛑 §0.5 EXCEPTION — OWNER-APPROVED 2026-10-10. DESIGN-SYSTEM §0.5 says motion
   happens once and nothing loops beside text. This band deliberately breaks that:
   stars twinkle, comets ride the routes to Miami, Miami pulses, the region glows
   and U.S. dots breathe, and the DAD tiles keep a slow glow. The exception is
   NARROW: it covers `.vx-creds` on vasectomy.html only and is logged in
   notes/CONCERNS.md (I-25). It does not license loops anywhere else.
   Guard rails that still hold: every loop pauses while the band is off-screen
   (`.vx-creds.is-offscreen`, set by ucofl.js §95b), none runs under reduced motion,
   none runs without JS, and the text itself never moves once it has landed.

   Resting state = final state. No JS: the board renders as a plain list of the
   real words beside their icons; reduced motion: JS builds the tiles already
   landed. The board's words are real DOM text (`.vxb-word`), visually hidden only
   once JS has drawn the aria-hidden tiles over them. */
.vx-creds { position: relative; overflow: hidden; isolation: isolate; background:
  radial-gradient(1100px 600px at 78% 40%, rgba(247,181,90,.08), transparent 60%), var(--midnight); }
.vx-creds > .container { position: relative; z-index: 1; }
.vx-creds__grid { display: grid; gap: clamp(2rem, 5vw, 4rem); align-items: start; }
@media (min-width: 980px) { .vx-creds__grid { grid-template-columns: minmax(0, 6fr) minmax(0, 5fr); } }
.vx-creds__text { min-width: 0; }
.vx-creds h2 { color: #fff; margin-top: .25rem; }
.vx-creds .eyebrow { color: var(--amber); }
.vx-stats { list-style: none; margin: clamp(1.5rem, 3vw, 2.25rem) 0 0; padding: 0; display: grid; gap: 1.25rem; grid-template-columns: repeat(3, minmax(0, 1fr)); }
@media (max-width: 640px) { .vx-stats { grid-template-columns: 1fr; gap: 1rem; } }
.vx-stats li { border-top: 1px solid rgba(255,255,255,.14); padding-top: 1rem; }
.vx-stat__num { display: flex; align-items: baseline; gap: 0; font-size: clamp(2.5rem, 5vw, 3.75rem); line-height: 1; font-weight: 700; color: #fff; font-variant-numeric: tabular-nums; letter-spacing: -.02em; transform-origin: left center; transition: text-shadow 600ms var(--ease); }
.vx-stat__num--word { font-size: clamp(1.9rem, 3.6vw, 2.6rem); }
.vx-star { width: .6em; height: .6em; fill: var(--amber); align-self: center; margin-left: .3rem; }
.vx-stat__lab { display: block; margin-top: .55rem; font-size: var(--fs-small); color: var(--mist); line-height: 1.45; }
.vx-stat__lab a { color: var(--mist); text-decoration: underline; text-decoration-color: rgba(255,255,255,.35); text-underline-offset: 3px; display: inline-flex; min-height: 44px; align-items: center; }
.vx-stat__lab a:hover { color: #fff; text-decoration-color: var(--amber); }
.vx-who__lead { display: flex; align-items: center; gap: .6rem; margin: clamp(1.75rem, 3.5vw, 2.5rem) 0 .75rem; font-size: var(--fs-label); letter-spacing: .16em; text-transform: uppercase; color: var(--mist); font-weight: 600; }
.vx-creds__map { margin: 0; min-width: 0; }
.vxm { display: block; width: 100%; height: auto; max-height: 640px; }
.vxm-land { fill: #28323A; stroke: #3A4650; stroke-width: .6; }
.vxm-glow { fill: url(#vxmGlow); transform-box: fill-box; transform-origin: center; }
.vxm-us { fill: var(--amber); opacity: .75; }
.vxm-arc { fill: none; stroke: var(--amber); stroke-width: 1.4; stroke-linecap: round; opacity: .7; stroke-dasharray: 1; stroke-dashoffset: 0; }
.vxm-halo { fill: url(#vxmHome); transform-box: fill-box; transform-origin: center; }
.vxm-home { fill: #fff; stroke: var(--amber); stroke-width: 2.5; }
.vxm-label { font: 600 13px/1 var(--font-sans); fill: #e8eef2; paint-order: stroke; stroke: var(--midnight); stroke-width: 4px; stroke-linejoin: round; }
.vxm-label--home { font-weight: 700; fill: #fff; }
.vx-creds__map figcaption { margin-top: .75rem; font-size: var(--fs-micro); color: var(--mist); line-height: 1.5; }
@media (max-width: 520px) { .vxm-label { font-size: 17px; } }
.vx-facts { list-style: none; margin: clamp(2rem, 4vw, 3rem) 0 0; padding: 1.25rem 0 0; border-top: 1px solid rgba(255,255,255,.12); display: flex; flex-wrap: wrap; gap: .5rem 2rem; color: var(--mist); font-size: var(--fs-small); }
.vx-facts li::before { content: ""; display: inline-block; width: 6px; height: 6px; border-radius: 50%; background: var(--amber); margin-right: .6rem; vertical-align: middle; }
.vxm-glows, .vxm-usdots, .vxm-arcs, .vxm-labels, .vxm-comets { pointer-events: none; }

/* map: once-only draw on the site reveal, then (JS adds .is-ambient) it goes live */
html.js .vx-creds__grid:not(.is-in) .vxm-label,
html.js .vx-creds__grid:not(.is-in) .vxm-us,
html.js .vx-creds__grid:not(.is-in) .vxm-glow { opacity: 0; }
html.js .vx-creds__grid:not(.is-in) .vxm-arc { stroke-dashoffset: 1; }
.vx-creds__grid.is-in .vxm-glow { animation: vxBloom 1100ms var(--ease) both; animation-delay: calc(500ms + var(--i) * 260ms); }
.vx-creds__grid.is-in .vxm-arc { animation: vxDraw 1200ms var(--ease) both; animation-delay: calc(700ms + var(--i) * 260ms); }
.vx-creds__grid.is-in .vxm-label { animation: vxIn 600ms var(--ease) both; animation-delay: calc(1300ms + var(--i) * 260ms); }
.vx-creds__grid.is-in .vxm-us { animation: vxIn 400ms var(--ease) both; animation-delay: calc(600ms + var(--j) * 60ms); }
.vx-creds__grid.is-in .vxm-halo { animation: vxHalo 2400ms var(--ease) 2600ms both; }
@keyframes vxIn { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes vxBloom { from { opacity: 0; transform: scale(.4); } to { opacity: 1; transform: none; } }
@keyframes vxDraw { from { stroke-dashoffset: 1; } to { stroke-dashoffset: 0; } }
@keyframes vxHalo { 0% { transform: scale(.6); opacity: .2; } 50% { transform: scale(1.5); opacity: 1; } 100% { transform: none; opacity: 1; } }

/* ---- ambient layer (§0.5 exception — see header) ---- */
.vxb-aurora { position: absolute; inset: -20% -10%; z-index: 0; pointer-events: none; background:
  radial-gradient(600px 420px at 70% 30%, rgba(247,181,90,.16), transparent 65%),
  radial-gradient(500px 500px at 20% 80%, rgba(185,220,236,.10), transparent 65%);
  transform: translate3d(calc(var(--px, 0) * -28px), calc(var(--py, 0) * -20px + var(--sy, 0px)), 0); transition: transform 1200ms var(--ease); }
.vxb-stars { position: absolute; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.vxb-stars span { position: absolute; width: 2px; height: 2px; border-radius: 50%; background: #fff; opacity: 0; animation: vxTwinkle var(--t, 4s) ease-in-out infinite; animation-delay: var(--dl, 0s); }
@keyframes vxTwinkle { 0%, 100% { opacity: 0; transform: translateY(0); } 50% { opacity: var(--o, .5); transform: translateY(-6px); } }
/* pointer parallax (mouse only; JS sets --px/--py on the section) */
.vx-creds .vx-creds__map { transform: translate3d(calc(var(--px, 0) * 14px), calc(var(--py, 0) * 10px), 0); transition: transform 1200ms var(--ease); }
.vx-creds .vxb-board { transform: translate3d(calc(var(--px, 0) * -6px), calc(var(--py, 0) * -4px), 0); transition: transform 1200ms var(--ease); }
.vx-creds .vx-stats { transform: translate3d(calc(var(--px, 0) * -3px), calc(var(--py, 0) * -2px), 0); transition: transform 1200ms var(--ease); }
/* numbers: glow on landing, then a faint resting glow */
.vx-stat__num.is-lit { animation: vxNumGlow 1400ms var(--ease) both; text-shadow: 0 0 22px rgba(247,181,90,.28); }
@keyframes vxNumGlow { 0% { text-shadow: 0 0 0 rgba(247,181,90,0); transform: scale(1); } 35% { text-shadow: 0 0 34px rgba(247,181,90,.9), 0 0 70px rgba(247,181,90,.45); transform: scale(1.05); } 100% { text-shadow: 0 0 22px rgba(247,181,90,.28); transform: scale(1); } }
/* map goes live: Miami pulses, glows / dots / halo breathe, comets ride the routes (JS-driven, no SMIL) */
.vxm-pulse { fill: none; stroke: var(--amber); stroke-width: 1.5; opacity: 0; transform-box: fill-box; transform-origin: center; }
.vx-creds__grid.is-ambient .vxm-pulse { animation: vxPulse 2.4s ease-out infinite; }
.vx-creds__grid.is-ambient .vxm-pulse--2 { animation-delay: 1.2s; }
@keyframes vxPulse { 0% { transform: scale(.6); opacity: .9; } 100% { transform: scale(5); opacity: 0; } }
.vx-creds__grid.is-ambient .vxm-glow { animation: vxGlowBreathe 5s ease-in-out infinite alternate; animation-delay: calc(var(--i) * -.9s); }
@keyframes vxGlowBreathe { from { opacity: 1; transform: scale(1); } to { opacity: .55; transform: scale(1.12); } }
.vx-creds__grid.is-ambient .vxm-us { animation: vxUsTwinkle 3s ease-in-out infinite; animation-delay: calc(var(--j) * -.37s); }
@keyframes vxUsTwinkle { 0%, 100% { opacity: .35; } 50% { opacity: 1; } }
.vx-creds__grid.is-ambient .vxm-halo { animation: vxHaloBreathe 2.4s ease-in-out infinite; }
@keyframes vxHaloBreathe { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.25); } }
.vxm-comets { opacity: 0; transition: opacity 600ms var(--ease); }
.vx-creds__grid.is-ambient .vxm-comets { opacity: 1; }
.vxm-comet { opacity: 0; }
.vxm-comet circle { fill: #fff; }
/* off-screen: every loop in the band holds still */
.vx-creds.is-offscreen *, .vx-creds.is-offscreen *::before, .vx-creds.is-offscreen *::after { animation-play-state: paused !important; }

/* ---- the people board (split-flap). Rows: icon + word. JS adds tiles. ---- */
.vxb-board { --cols: 14; --icw: 32px; position: relative; max-width: 33rem; font-size: clamp(.8rem, 3.4vw, 1.125rem); font-weight: 600; letter-spacing: .04em; }
@media (max-width: 560px) { .vxb-board { --icw: 22px; } }
.vxb-rows { display: grid; gap: .45em; margin: 0; padding: 0; list-style: none; }
.vxb-row { display: grid; grid-template-columns: var(--icw) minmax(0, 1fr); column-gap: .6rem; align-items: center; min-height: 1.55em; }
/* no-JS / pre-build look: the real word, set like a board label */
.vxb-word { color: #fff; text-transform: uppercase; letter-spacing: .08em; font-size: .9em; line-height: 1.3; }
.vxb-row--dad .vxb-word { color: var(--amber); }
.vxb-board.is-built .vxb-word { position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; border: 0; }
.vxb-cells { display: grid; grid-template-columns: repeat(var(--cols), minmax(0, 1fr)); gap: 3px; }
.vxb-cl { position: relative; height: 1.55em; perspective: 240px; }
.vxb-cl > b { position: absolute; left: 0; right: 0; height: 50%; overflow: hidden; background: #0b1218; border: 1px solid rgba(255,255,255,.07); font-weight: inherit; }
.vxb-cl i { position: absolute; left: 0; right: 0; height: 200%; display: flex; align-items: center; justify-content: center; font-style: normal; color: #fff; line-height: 1; }
.vxb-cl > b:nth-child(odd) { top: 0; border-radius: 3px 3px 0 0; border-bottom-color: rgba(0,0,0,.6); }
.vxb-cl > b:nth-child(even) { bottom: 0; border-radius: 0 0 3px 3px; border-top: 0; background: #0e161e; }
.vxb-cl > b:nth-child(odd) i { top: 0; }
.vxb-cl > b:nth-child(even) i { bottom: 0; }
.vxb-cl > b:nth-child(3) { transform-origin: bottom center; z-index: 2; backface-visibility: hidden; }
.vxb-cl > b:nth-child(4) { transform-origin: top center; z-index: 2; backface-visibility: hidden; transform: rotateX(90deg); }
.vxb-cl.is-blank > b { background: #0d141b; }
.vxb-row--dad.is-on .vxb-cl.has-char > b { background: #1F1A10; border-color: rgba(247,181,90,.35); transition: background 300ms var(--ease), border-color 300ms var(--ease); }
.vxb-row--dad.is-on .vxb-cl.has-char i { color: var(--amber); transition: color 300ms var(--ease); }
.vxb-board.is-done .vxb-row--dad .vxb-cl.has-char > b { animation: vxTileBreathe 3.2s ease-in-out infinite; }
@keyframes vxTileBreathe { 0%, 100% { box-shadow: 0 0 0 rgba(247,181,90,0); } 50% { box-shadow: 0 0 18px rgba(247,181,90,.45); } }
.vxb-dads { margin: .35em 0 0 calc(var(--icw) + .6rem); color: var(--amber); font-weight: 600; letter-spacing: .02em; opacity: 0; transform: translateY(6px); transition: opacity 700ms var(--ease), transform 700ms var(--ease); }
.vxb-board.is-done .vxb-dads { opacity: 1; transform: none; }
/* title tiles ("HIS PATIENTS INCLUDE") replace the lead's text on wide screens once built */
.vxb-title { --cols: 22; flex: 1 1 auto; max-width: 30rem; font-size: clamp(.8rem, 1.1vw, .82rem); letter-spacing: .04em; }
.vx-who__lead.is-built .vxb-lead-text { position: absolute; width: 1px; height: 1px; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap; }
.vxb-lead-text { display: inline; }
@media (max-width: 560px) {
  .vxb-title { display: none; }
  .vx-who__lead.is-built .vxb-lead-text { position: static; width: auto; height: auto; margin: 0; overflow: visible; clip: auto; clip-path: none; }
}

/* row icons — thin line, one amber element each; drawn at rest, drawn-in once when a row lands.
   SAFARI: the draw uses stroke-dasharray only DURING the animation (fill: backwards), so the
   resting stroke has no dash. Where pathLength on <circle>/<rect> is unsupported the draw is
   merely uneven for 0.7s; the finished icon is always complete. */
.vxb-ic { width: var(--icw); height: var(--icw); display: block; color: var(--mist); fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
.vxb-am { stroke: var(--amber); }
.vxb-am-fill { fill: var(--amber); stroke: none; transform-box: fill-box; transform-origin: center; }
.vxb-board.is-armed .vxb-row:not(.is-on) .vxb-ic :is(path, circle, rect) { stroke-dasharray: 36; stroke-dashoffset: 36; }
.vxb-board.is-armed .vxb-row:not(.is-on) .vxb-am-fill { opacity: 0; }
.vxb-board.is-armed .vxb-row.is-on .vxb-ic :is(path, circle, rect) { animation: vxIcDraw 720ms var(--ease) backwards; animation-delay: calc(var(--d, 0) * 140ms); }
.vxb-board.is-armed .vxb-row.is-on .vxb-am-fill { animation: vxIcFill 500ms var(--ease) both 420ms; }
@keyframes vxIcDraw { from { stroke-dasharray: 36; stroke-dashoffset: 36; } to { stroke-dasharray: 36; stroke-dashoffset: 0; } }
@keyframes vxIcFill { from { opacity: 0; transform: scale(.4); } to { opacity: 1; transform: none; } }

/* SIGNAL crest beside "His patients include" — a post on a line with the signal
   on top; the square rises once when the board wakes */
.vxb-crest { flex: none; width: 36px; height: 24px; overflow: visible; fill: none; stroke: var(--mist); stroke-width: 3; stroke-linecap: butt; }
.vxb-mark { fill: var(--signal); stroke: none; }
html.js .vx-creds__grid:not(.is-in) .vxb-mark { transform: translateY(9px); }
.vx-creds__grid.is-in .vxb-mark { animation: vxCrestRise 1000ms var(--ease) both 250ms; }
@keyframes vxCrestRise { from { transform: translateY(9px); } to { transform: translateY(0); } }

/* DAD burst (one-shot, JS-built) */
.vxb-burst { position: absolute; left: 0; top: 0; width: 0; height: 0; pointer-events: none; z-index: 5; }
.vxb-burst i { position: absolute; left: -3px; top: -3px; width: 6px; height: 6px; border-radius: 50%; background: var(--amber); }
.vxb-burst svg { position: absolute; left: -140px; top: -140px; width: 280px; height: 280px; overflow: visible; }
.vxb-burst circle { fill: none; stroke: var(--amber); stroke-width: 2; }
.vxb-flash { position: absolute; border-radius: 6px; background: radial-gradient(closest-side, rgba(247,181,90,.35), transparent); opacity: 0; pointer-events: none; }

/* reduced motion: the finished band, nothing moving */
@media (prefers-reduced-motion: reduce) {
  .vx-creds *, .vx-creds *::before, .vx-creds *::after { animation: none !important; transition: none !important; }
  .vxm-glow, .vxm-arc, .vxm-label, .vxm-us, .vxm-halo { opacity: 1 !important; stroke-dashoffset: 0 !important; transform: none !important; }
  .vxm-us { opacity: .75 !important; }
  .vxb-stars, .vxm-comets, .vxm-pulse, .vxb-burst, .vxb-flash, .vxb-aurora { display: none !important; }
  .vxb-mark, .vx-creds .vx-creds__map, .vx-creds .vxb-board, .vx-creds .vx-stats { transform: none !important; }
  .vxb-ic :is(path, circle, rect) { stroke-dasharray: none !important; }
  .vxb-am-fill, .vxb-dads { opacity: 1 !important; transform: none !important; }
  .vxb-cl > b:nth-child(4) { transform: none !important; }
}
/* options band now sits on the light style: icons and the "this page" arrow need light-band colours */
.svc-index--light .sg-icon--svc { color: var(--ink-3); }
.svc-index--light .svc-row--here .arrow { color: var(--amber-text); }
.svc-index--light .svc-row--here { box-shadow: inset 3px 0 0 var(--amber-line); }

/* 96. BLOG ARTICLE v2 ---------------------------------------------------------
   One block, ten small components, built only from §1–§3 tokens. The blog
   posts are the site's widest-read pages (the kidney-stone diet post alone
   drew 206k impressions) and the only ones still shipped as a bare .prose
   column. These pieces give a post the procedure pages' rhythm — the 2px ink
   rule + 28px amber tick ledger family (11b, 26, 27, 36, 38), the hairline
   panel family (53, 60), the light index rows (23) — without inventing a
   second visual language.

   Reuses, unchanged: .page-hero (19), .prose (20), .byline / .byline--box
   (22), .svc-index--light (23), .table-scroll (39), .card (11), .btn*, .eyebrow,
   .reveal (18), the global reduced-motion guard (3).

   Contents
     96.0 .post-grid       — the article's two-column frame (rail + 720px main)
     96.1 .takeaways       — key takeaways ledger under the byline
     96.2 .toc             — "On this page", sticky in the rail ≥1100px
     96.3 .callout         — clinician's note / when-to-call-us / editor's update
     96.4 .related         — mid-article "Related care" card
     96.5 .prose--v2       — reading rhythm: hairline h2s, scroll margins
     96.6 .data-table      — a real <table> for food lists and comparisons
     96.7 .pullquote       — one verbatim sentence at lead scale
     96.8 .post-facts      — quotable numbers lifted from the article itself
     96.9 .post-figure     — a still with a credit line (not .media-frame)
     96.10 .sources        — numbered references + inline .cite markers
     96.11 .post-cta       — end-of-article "Related care" rail (the blog's 38)
     96.12 .more-posts     — "More from the blog", three text cards
     96.13 index: .featured, .topic-filter, .topic, .post-list
   Dark-band variants are deliberately absent: every piece sits on paper or
   paper-2. A post never carries a midnight band of its own; the consult band
   and the navy NAP band that follow supply the page's dark beats.
   -------------------------------------------------------------------------- */

/* 96.0 POST GRID — the frame everything else sits in.
   Single centred column of 720px (what .container--narrow already yields at
   desktop gutters). With .post-grid--rail, ≥1100px opens a 220px rail on the
   left for the TOC. The HERO uses the same grid so its H1 and the body copy
   share one left edge; its rail cell simply stays empty. Short posts (no TOC)
   drop the modifier and both hero and body collapse to the centred column. */
.post-grid {
  display: grid;
  grid-template-columns: minmax(0, 720px);
  justify-content: center;
  column-gap: clamp(2.5rem, 5vw, 5rem);
  row-gap: 0;
}
.post-grid__main { grid-column: 1; min-width: 0; }
@media (min-width: 1100px) {
  .post-grid--rail { grid-template-columns: 220px minmax(0, 720px); }
  .post-grid--rail > .post-grid__main { grid-column: 2; grid-row: 1; }
  .post-grid--rail > .post-grid__rail {
    grid-column: 1;
    grid-row: 1;
    align-self: start;
    position: sticky;
    top: calc(var(--header-h) + 2rem);
  }
}
/* the hero keeps its 19 metrics; only the eyebrow learns to carry links */
.post-eyebrow a { color: inherit; text-decoration: none; }
.post-eyebrow a:hover,
.post-eyebrow a:focus-visible { text-decoration: underline; text-underline-offset: 3px; }
.post-eyebrow .sep { margin-inline: 0.5em; color: var(--ink-3); }
/* the article body section: tighter than .section--tight at the top because
   the hero's bottom hairline already closed the header */
.post-body { padding-block: clamp(2.5rem, 5vw, 4rem) clamp(3rem, 6vw, 5rem); }

/* 96.1 KEY TAKEAWAYS — the ledger grammar (2px ink rule + 28px amber tick),
   because these are the post's checkable facts, not a decoration. Lives at
   the foot of the hero, directly under the byline, so a reader who gets no
   further still leaves with the point. Numerals are the index-row numerals. */
.takeaways {
  position: relative;
  margin-top: clamp(2rem, 4vw, 2.75rem);
  padding-top: 1.125rem;
  border-top: 2px solid var(--ink);
  max-width: 64ch;
}
.takeaways::before {
  content: "";
  position: absolute;
  top: -2px;
  left: 0;
  width: 28px;
  height: 2px;
  background: var(--amber-line);
}
.takeaways__title {
  margin: 0;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
  max-width: none;
  line-height: 1.2;
}
.takeaways ol {
  list-style: none;
  margin: 0.5rem 0 0;
  padding: 0;
  counter-reset: tk;
}
.takeaways li {
  position: relative;
  counter-increment: tk;
  padding: 0.75rem 0 0.75rem 2.25rem;
  border-top: 1px solid var(--line);
  color: var(--ink-2);
  font-size: var(--fs-body);
  line-height: 1.55;
}
.takeaways li:first-child { border-top: 0; }
.takeaways li::before {
  content: counter(tk, decimal-leading-zero);
  position: absolute;
  left: 0;
  top: 0.95rem;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.1em;
  font-variant-numeric: tabular-nums;
  color: var(--blue-text);
}
.takeaways strong { color: var(--ink); font-weight: 600; }

/* 96.2 ON THIS PAGE — a <nav> of the post's h2s. Plain hairline list on
   phones and tablets; in the rail ≥1100px it is sticky and the script marks
   the section in view with aria-current="location" (the amber segment on the
   rail). Every link clears 44px so it is a target on touch, and the list
   splits into two columns between 600 and 1099px so nine entries do not cost
   a full screen before the article starts. */
.toc {
  font-size: var(--fs-small);
  margin-top: clamp(1.5rem, 3vw, 2rem);
  padding-block: 1.25rem;
  border-top: 1px solid var(--line);
  border-bottom: 1px solid var(--line);
}
.toc__title {
  margin: 0 0 0.5rem;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.toc ol {
  list-style: none;
  margin: 0;
  padding: 0;
  border-left: 1px solid var(--line);
}
.toc li { break-inside: avoid; }
.toc a {
  display: block;
  min-height: 44px;
  padding: 0.7rem 0.75rem 0.7rem 1rem;
  margin-left: -1px;
  border-left: 2px solid transparent;
  color: var(--ink-2);
  text-decoration: none;
  line-height: 1.4;
  transition: color 200ms var(--ease), border-color 200ms var(--ease);
}
.toc a:hover { color: var(--ink); border-left-color: var(--ink-3); }
.toc a:focus-visible { outline-offset: -2px; border-radius: 0; }
.toc a[aria-current="location"] {
  color: var(--ink);
  font-weight: 600;
  border-left-color: var(--amber-line);
}
@media (min-width: 600px) and (max-width: 1099px) {
  .toc ol { columns: 2; column-gap: 2.5rem; border-left: 0; }
  .toc li { border-left: 1px solid var(--line); }
}
@media (min-width: 1100px) {
  .toc { margin-top: 0; padding-block: 0; border: 0; }
  .toc a { min-height: 40px; padding-block: 0.55rem; }
}

/* 96.3 CALLOUTS — the hairline-panel grammar of 53/60 (1px rule, 2px left
   edge, quiet fill), always at BODY size: each of these is a claim gate and
   §60's rule stands — a true sentence in fine print is still a bad net
   impression. Three edges, each announced by its eyebrow TEXT, never by the
   colour alone (§7.9):
     --note    amber-line edge, paper-2  : Dr. Robbins speaking in his own voice
     --urgent  --error edge, error tint   : when to call us / go to the ER
     --update  --blue-text edge, paper    : dated editor's note on an older post
   Error tint pair is the form-state pair from §4.8 (#8c1d16 on #fdf3f2),
   already AA-computed; --blue-text on #fdf3f2 measures 5.29:1. */
.callout {
  border: 1px solid var(--line);
  border-left: 2px solid var(--amber-line);
  background: var(--paper-2);
  padding: clamp(1.25rem, 2.6vw, 1.75rem) clamp(1.25rem, 3vw, 2rem);
  max-width: 64ch;
}
.callout__eyebrow {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.25rem 0.625rem;
  margin: 0 0 0.625rem;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--blue-text);
  max-width: none;
}
.callout__eyebrow svg { flex: none; width: 16px; height: 16px; }
.callout__title {
  margin: 0 0 0.5rem;
  font-size: var(--fs-h4);
  font-weight: 600;
  line-height: 1.3;
  color: var(--ink);
  max-width: none;
  letter-spacing: 0;
}
.callout p {
  margin: 0;
  font-size: var(--fs-body);          /* never --fs-small — see 60 */
  line-height: 1.6;
  color: var(--ink-2);
}
.callout p + p,
.callout ul + p { margin-top: 0.75rem; }
.callout ul {
  margin: 0.75rem 0 0;
  padding-left: 1.25rem;
  display: grid;
  gap: 0.5rem;
  color: var(--ink-2);
}
.callout li::marker { color: var(--ink-3); }
.callout strong { color: var(--ink); font-weight: 600; }
.callout a {
  color: var(--blue-text);
  text-decoration: underline;
  text-underline-offset: 0.18em;
}
.callout a:hover { text-decoration-thickness: 2px; }
/* who is speaking: a 36px portrait + name line, only on --note */
.callout__by {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-top: 1rem;
  font-size: var(--fs-small);
  color: var(--ink-3);
}
.callout__by img {
  width: 36px; height: 36px;
  border-radius: 50%;
  object-fit: cover;
  flex: none;
}
.callout__by strong { color: var(--ink); font-weight: 600; }
/* --urgent */
.callout--urgent {
  border-left-color: var(--error);
  background: #fdf3f2;
}
.callout--urgent .callout__eyebrow { color: #8c1d16; }
.callout--urgent li::marker { color: #8c1d16; }
.callout--urgent .callout__phone {
  display: inline-block;
  margin-top: 0.875rem;
  font-weight: 600;
  color: var(--ink);
  text-decoration: none;
  min-height: 44px;
  line-height: 44px;
}
.callout--urgent .callout__phone:hover { color: var(--blue-text); }
/* --update */
.callout--update {
  border-left-color: var(--blue-text);
  background: var(--paper);
}
.callout--update .callout__eyebrow time { color: var(--ink-3); font-weight: 500; letter-spacing: 0.1em; }
/* inside .prose the generic 1.25rem rhythm is too tight around a panel */
.prose > .callout { margin-top: 2.25rem; }
.prose > .callout + * { margin-top: 2.25rem; }

/* 96.4 RELATED CARE — mid-article signpost to the service page. A hairline
   card (11) rather than the ledger rule, so mid-flow it reads as "set apart",
   not as a new section. One link by default (.btn--link); the pill CTAs live
   at the end in 96.11, keeping to one filled button per region (§4.2). */
.related {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 0.75rem 2rem;
  align-items: center;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper-2);
  padding: clamp(1.25rem, 2.6vw, 1.75rem) clamp(1.25rem, 3vw, 2rem);
}
.related__text { min-width: 0; }
.related__eyebrow {
  margin: 0 0 0.375rem;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--blue-text);
  max-width: none;
}
.related__title {
  margin: 0;
  font-size: var(--fs-h4);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: 0;
  color: var(--ink);
  max-width: none;
  text-wrap: pretty;
}
.related__title a { color: inherit; text-decoration: none; }
.related__title a:hover { color: var(--blue-text); }
.related__why {
  margin: 0.375rem 0 0;
  font-size: var(--fs-small);
  line-height: 1.55;
  color: var(--ink-2);
  max-width: 52ch;
}
.related__act { display: flex; flex-wrap: wrap; gap: 0.75rem; align-items: center; }
.related__act .btn--link { white-space: nowrap; }
@media (max-width: 700px) {
  .related { grid-template-columns: 1fr; }
}
.prose > .related { margin-top: 2.5rem; }
.prose > .related + * { margin-top: 2.5rem; }

/* 96.5 PROSE v2 — reading rhythm. A hairline above every h2 after the first
   gives a long article the section-band cadence of a procedure page without
   bands; scroll margins keep TOC jumps from landing under the fixed header. */
.prose--v2 > h2:not(:first-child) {
  margin-top: clamp(3rem, 6vw, 4.25rem);
  padding-top: clamp(1.75rem, 3.5vw, 2.5rem);
  border-top: 1px solid var(--line);
}
/* the hairline must run the full column while the words still wrap at the
   30ch measure 20 sets: swap the max-width for right padding (clamped to 0
   on narrow columns, where the measure is the column anyway) */
.prose--v2 h2 {
  max-width: none;
  padding-right: max(0px, calc(100% - 30ch));
  text-wrap: pretty;
}
.prose--v2 > h3:not(:first-child) { margin-top: 2.5rem; }
.prose--v2 [id],
.sources [id] { scroll-margin-top: calc(var(--header-h) + 1.5rem); }
.prose--v2 > ul, .prose--v2 > ol { gap: 0.75rem; }
.prose--v2 li > strong:first-child { color: var(--ink); font-weight: 600; }
/* a hanging note under a heading: "Updated …" one-liners, source lines */
.prose--v2 .note { font-size: var(--fs-small); color: var(--ink-3); }

/* 96.6 DATA TABLE — a real <table>, same reasoning as 39 (translatable,
   indexable, correctable). Unlike .tx-table there is no 720px floor: a food
   list is two or three columns and should fit a phone. Wrap in .table-scroll
   anyway so a wide table scrolls inside itself, never the page. */
.data-table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--fs-body);
}
.data-table caption {
  caption-side: top;
  text-align: left;
  padding-bottom: 0.875rem;
  font-size: var(--fs-h4);
  font-weight: 600;
  line-height: 1.3;
  color: var(--ink);
}
.data-table caption .note { display: block; margin-top: 0.25rem; font-size: var(--fs-small); font-weight: 400; color: var(--ink-3); }
.data-table thead th {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-2);
  text-align: left;
  padding: 0 1rem 0.75rem 0;
  border-bottom: 2px solid var(--ink);
  vertical-align: bottom;
}
.data-table tbody th,
.data-table tbody td {
  padding: 0.875rem 1rem 0.875rem 0;
  border-bottom: 1px solid var(--line);
  vertical-align: top;
  text-align: left;
  color: var(--ink-2);
  line-height: 1.5;
}
.data-table tbody th { font-weight: 600; color: var(--ink); width: 11em; }
.data-table tbody th:last-child,
.data-table tbody td:last-child { padding-right: 0; }
.data-table ul { margin: 0; padding-left: 1.1rem; display: grid; gap: 0.35rem; max-width: none; }
.data-table li::marker { color: var(--ink-3); }
.data-table .num { font-variant-numeric: tabular-nums; white-space: nowrap; }
.prose--v2 > .table-scroll { margin-top: 2.25rem; }
.prose--v2 > .table-scroll + * { margin-top: 2.25rem; }
@media (max-width: 520px) {
  .data-table { font-size: var(--fs-small); }
  .data-table tbody th { width: auto; }
  .data-table tbody th, .data-table tbody td { padding-right: 0.75rem; }
}

/* 96.7 PULL QUOTE — one sentence the article actually contains, lifted to
   lead scale under the 56×3 signal-mark (the testimonial rule of 12). Not the
   .prose blockquote (that is a quotation of someone else). It never says
   anything the body does not — it is emphasis, not a new claim. */
.prose .pullquote,
.pullquote {
  margin: clamp(2.5rem, 5vw, 3.5rem) 0;
  padding: 0;
  border: 0;
  max-width: 32ch;
  color: var(--ink);
}
.pullquote::before {
  content: "";
  display: block;
  width: 56px;
  height: 3px;
  margin-bottom: 1.25rem;
  background: var(--amber-line);
}
.pullquote p {
  margin: 0;
  max-width: none;
  font-size: var(--fs-lead);
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: -0.01em;
  color: var(--ink);
  text-wrap: pretty;
}
.pullquote cite {
  display: block;
  margin-top: 0.875rem;
  font-style: normal;
  font-size: var(--fs-small);
  color: var(--ink-3);
}
.prose > .pullquote + * { margin-top: 0; }

/* 96.8 POST FACTS — the 80 fact-tile grammar for numbers that already appear
   in the article (never new figures). Each tile names where the number comes
   from in the shared source line. Three tiles fit ≥760px, one column below. */
.post-facts {
  display: grid;
  gap: 1rem 2rem;
  margin-top: 2.25rem;
  grid-template-columns: repeat(auto-fit, minmax(170px, 1fr));
  max-width: none;
}
.post-facts > div {
  position: relative;
  border-top: 1px solid var(--line);
  padding-top: 0.875rem;
}
.post-facts > div::before {
  content: "";
  position: absolute;
  top: -1px;
  left: 0;
  width: 28px;
  height: 1px;
  background: var(--amber-line);
}
.post-facts b {
  display: block;
  font-size: clamp(1.5rem, 3vw, 2.125rem);
  font-weight: 500;
  letter-spacing: -0.02em;
  line-height: 1.1;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.post-facts b small { font-size: 0.55em; font-weight: 500; color: var(--ink-3); letter-spacing: 0; }
.post-facts span { display: block; margin-top: 0.375rem; font-size: var(--fs-small); color: var(--ink-3); line-height: 1.45; }
.post-facts__src {
  grid-column: 1 / -1;
  margin: 0;
  font-size: var(--fs-micro);
  color: var(--ink-3);
}
.prose > .post-facts + * { margin-top: 2.5rem; }

/* 96.9 POST FIGURE — a still at its own aspect, hairline frame, credit line.
   Mirrors 81; not .media-frame (cinema treatment, reads promotional). Legacy
   post images are 300–350px wide: they render at natural size, never blown
   up to the column (20's rule) — the frame is what makes a small image look
   placed rather than neglected. */
.post-figure { margin: clamp(2rem, 4vw, 3rem) 0 0; }
.post-figure img {
  display: block;
  max-width: 100%;
  height: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--paper-2);
}
.post-figure figcaption {
  margin-top: 0.625rem;
  font-size: var(--fs-micro);
  color: var(--ink-3);
  line-height: 1.5;
  max-width: 64ch;
}
.post-figure--full img { width: 100%; }
.prose > .post-figure + * { margin-top: 2.5rem; }

/* 96.10 SOURCES — a numbered ledger of what the article rests on. Inline
   markers (.cite) jump to the entry; the entry lights up on :target so the
   reader sees where they landed. External links open where the browser
   decides (no target=_blank), and carry rel="noopener". */
.sources {
  margin-top: clamp(3rem, 6vw, 4.5rem);
  padding-top: 1.25rem;
  border-top: 2px solid var(--ink);
  position: relative;
}
.sources ol { max-width: 68ch; }
.sources::before {
  content: "";
  position: absolute;
  top: -2px;
  left: 0;
  width: 28px;
  height: 2px;
  background: var(--amber-line);
}
.sources__title {
  margin: 0;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink);
  max-width: none;
  line-height: 1.2;
}
.sources__note { margin: 0.5rem 0 0; font-size: var(--fs-small); color: var(--ink-3); }
.sources ol {
  list-style: none;
  margin: 1rem 0 0;
  padding: 0;
  counter-reset: src;
}
.sources li {
  position: relative;
  counter-increment: src;
  padding: 0.625rem 0.5rem 0.625rem 2.25rem;
  margin-left: -0.5rem;
  font-size: var(--fs-small);
  line-height: 1.55;
  color: var(--ink-2);
}
.sources li::before {
  content: counter(src) ".";
  position: absolute;
  left: 0.5rem;
  top: 0.625rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  color: var(--ink-3);
}
.sources li:target { background: var(--paper-2); }
.sources a {
  color: var(--blue-text);
  text-decoration: underline;
  text-underline-offset: 3px;
  overflow-wrap: anywhere;
}
.sources a:hover { text-decoration-thickness: 2px; }
.sources__meta { color: var(--ink-3); }
.cite {
  font-size: 0.72em;
  line-height: 0;
  vertical-align: super;
  margin-left: 1px;
}
.cite a {
  display: inline-block;
  padding: 0 0.35em;
  border: 1px solid var(--line);
  border-radius: 999px;
  color: var(--blue-text);
  font-weight: 600;
  text-decoration: none;
  line-height: 1.5;
}
.cite a:hover { border-color: var(--blue-text); }

/* 96.11 POST CTA — the blog's "next step" (38): one forward move, ledger
   rule, title at h3 scale, the two standard exits as pills, and a quiet
   "or read first" line. Sits in the main column above the consult band —
   NOT a second full-width band; the consult panel is the band. */
.post-cta {
  position: relative;
  margin-top: clamp(3rem, 6vw, 4.5rem);
  padding-top: 1.25rem;
  border-top: 2px solid var(--ink);
}
.post-cta::before {
  content: "";
  position: absolute;
  top: -2px;
  left: 0;
  width: 28px;
  height: 2px;
  background: var(--amber-line);
}
.post-cta .eyebrow { margin-bottom: 0.625rem; }
.post-cta__title {
  margin: 0;
  font-size: var(--fs-h3);
  font-weight: 500;
  line-height: 1.15;
  letter-spacing: -0.015em;
  max-width: 24ch;
}
.post-cta__why {
  margin-top: 0.875rem;
  color: var(--ink-2);
  max-width: 56ch;
}
.post-cta__ctas {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  margin-top: 1.5rem;
}
.post-cta__alt { margin-top: 1rem; font-size: var(--fs-small); color: var(--ink-3); }
.post-cta__alt a { color: var(--blue-text); }

/* 96.12 MORE FROM THE BLOG — three text cards on a soft band. Posts carry no
   hero art, so the card is type: topic label, title, meta, Read link. Extends
   .card (lift, radius, height:100%); .band--soft already drops the border. */
.more-posts__grid {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
  gap: var(--grid-gap);
}
.post-card { padding: clamp(1.5rem, 3vw, 2rem); gap: 0.5rem; }
.post-card__topic {
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--blue-text);
}
.post-card h3 {
  font-size: var(--fs-h4);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: 0;
  color: var(--ink);
  text-wrap: pretty;
}
.post-card p { color: var(--ink-2); font-size: var(--fs-small); line-height: 1.55; }
.post-card__meta { font-size: var(--fs-micro); color: var(--ink-3); }
.post-card .btn--link { margin-top: auto; padding-top: 0.75rem; }
.post-card:focus-visible { outline: 2px solid var(--blue-text); outline-offset: 3px; }
/* lead variant for the index's featured slot */
.post-card--lead { background: var(--paper-2); border-color: transparent; }
.post-card--lead h3 { font-size: var(--fs-h3); font-weight: 500; letter-spacing: -0.015em; line-height: 1.15; max-width: 22ch; }
.post-card--lead p { font-size: var(--fs-body); max-width: 44ch; }

/* 96.13 BLOG INDEX — featured trio, topic filter, topic sections. ----------
   The index is read by someone deciding what to read, so it leads with three
   chosen posts and lets the reader cut the list by service line. Rows reuse
   23 (.svc-index--light); the numeral slot carries the YEAR, which tells a
   reader something a running count never did. */
.featured {
  display: grid;
  gap: var(--grid-gap);
  grid-template-columns: minmax(0, 1fr);
}
.featured__side { display: grid; gap: var(--grid-gap); }
@media (min-width: 900px) {
  .featured { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); align-items: stretch; }
  .featured__side { grid-template-rows: 1fr 1fr; }
}

/* filter bar: anchor links to the topic sections (works with no script);
   the script turns them into in-place filters. Sticky under the header only
   on desktop, where it is one row tall. */
.topic-filter { border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); background: var(--paper); }
@media (min-width: 900px) {
  .topic-filter { position: sticky; top: var(--header-h); z-index: 5; }
}
.topic-filter__row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 0.5rem;
  padding-block: 0.75rem;
}
.topic-filter__label {
  margin: 0 0.5rem 0 0;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-3);
}
.topic-filter ul { list-style: none; margin: 0; padding: 0; display: contents; }
.topic-filter a {
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
  min-height: 40px;
  padding: 0 1rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  font-size: var(--fs-small);
  font-weight: 500;
  color: var(--ink-2);
  text-decoration: none;
  background: var(--paper);
  transition: background-color 200ms var(--ease), color 200ms var(--ease), border-color 200ms var(--ease);
}
.topic-filter a .n { font-size: var(--fs-micro); color: var(--ink-3); font-variant-numeric: tabular-nums; }
.topic-filter a:hover { border-color: var(--ink-3); color: var(--ink); }
.topic-filter a[aria-current="true"] { background: var(--ink); border-color: var(--ink); color: #fff; }
.topic-filter a[aria-current="true"] .n { color: var(--mist); }
@media (max-width: 899px) {
  .topic-filter a { min-height: 44px; }
}
/* status line for assistive tech + sighted readers once a filter is on */
.topic-filter__status { margin: 0; padding-bottom: 0.75rem; font-size: var(--fs-small); color: var(--ink-3); }
.topic-filter__status:empty { display: none; }

/* topic sections */
.topic .section-head { margin-bottom: clamp(1.5rem, 3vw, 2.5rem); }
.topic .section-head h2 { font-size: var(--fs-h3); }
.topic .section-head .lead { font-size: var(--fs-body); }
.topic__svc { margin-top: 0.75rem; }
.post-list .svc-row { grid-template-columns: 4.5rem 1fr auto; }
.post-list .svc-row .num { letter-spacing: 0.04em; font-weight: 500; }
.post-list .svc-row .svc-desc { margin-top: 0.375rem; }
@media (max-width: 700px) {
  .post-list .svc-row { grid-template-columns: 3.25rem 1fr auto; }
}
/* `hidden` wins over .section display thanks to the global [hidden] guard (§4.8a) */

/* motion: everything above is static. The TOC's current marker and the filter
   pills change colour over 200ms; the global reduced-motion guard (3) zeroes
   those, and the script takes its own cue from the same media query. */
/* 96 fixes (lead, 2026-10-10): filter pills held to the 44px target at every width
   (they measured 40px at 1280); and the two class names the spec documents but never
   styled now resolve to the default callout / plain article, so builders following the
   spec cannot trip verify's orphan-class gate. */
.topic-filter a { min-height: 44px; }
.callout--note { }
article.post { display: block; }

/* 97. BLOG MASTHEAD (`.blog-mast`) — owner 2026-10-10 ------------------------------
   Editorial "from the desk of" hero for blog.html: eyebrow, the verbatim legacy H1,
   a dek in the author's voice, a credentials line and a columnist-scale portrait.
   No booking CTAs here; the topic filter (component 96) follows immediately and pins. */
.blog-mast { padding-bottom: clamp(2rem, 4vw, 3rem); }
.blog-mast__grid { display: grid; gap: clamp(1.5rem, 4vw, 3rem); align-items: end; }
@media (min-width: 860px) { .blog-mast__grid { grid-template-columns: minmax(0, 1fr) 260px; } }
.blog-mast__text { min-width: 0; }
.blog-mast__dek { margin: .75rem 0 0; max-width: 30ch; font-size: clamp(1.5rem, 2.6vw, 2.1rem); line-height: 1.25; color: var(--ink); letter-spacing: -.01em; }
.blog-mast__cred { margin: 1.25rem 0 0; font-size: var(--fs-small); color: var(--ink-3); letter-spacing: .02em; }
.blog-mast__portrait { margin: 0; justify-self: start; width: 100%; max-width: 260px; }
.blog-mast__portrait img { display: block; width: 100%; height: auto; aspect-ratio: 4 / 5; object-fit: cover; border-radius: var(--radius); }
@media (max-width: 859px) { .blog-mast__portrait { max-width: 160px; order: -1; } }

/* 98. SECTION BACK-LINK (`.section-back`) — owner 2026-10-11 -------------------------
   A slim breadcrumb bar at the top of every vasectomy subpage: "← Vasectomy › This page".
   The parent link is the obvious, generous target (44px); the current page is plain text
   with aria-current. Paired with BreadcrumbList JSON-LD. Reusable for any section. */
/* The site header is position:fixed and every hero pads its top to clear it, so the bar sits
   directly under the header, inside that padding (heroes start their content ~176px down). */
.section-back { position: absolute; top: var(--header-h); left: 0; right: 0; z-index: 3;
  background: rgba(245, 247, 248, 0.94); border-bottom: 1px solid var(--line);
  -webkit-backdrop-filter: blur(6px); backdrop-filter: blur(6px); }
.section-back ol { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: nowrap; align-items: center; gap: 0 .6rem; min-height: 52px; font-size: var(--fs-small); }
.section-back li { display: flex; align-items: center; color: var(--ink-3); min-width: 0; }
.section-back li + li::before { content: "\203A"; margin-right: .6rem; color: var(--ink-3); }
.section-back a { display: inline-flex; align-items: center; gap: .45rem; min-height: 44px; padding: 0 .25rem; color: var(--blue-text); font-weight: 600; text-decoration: none; }
.section-back a:hover, .section-back a:focus-visible { text-decoration: underline; text-underline-offset: 3px; }
.section-back__arrow { font-size: 1.15em; transition: transform 200ms var(--ease); }
.section-back a:hover .section-back__arrow { transform: translateX(-3px); }
@media (prefers-reduced-motion: reduce) { .section-back a:hover .section-back__arrow { transform: none; } }
.section-back li { flex: none; }
.section-back li[aria-current] { flex: 1 1 auto; min-width: 0; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.section-back li[aria-current]::before { display: inline; }
/* phones: heroes reserve less top space, so give the hero content room below the bar */
@media (max-width: 859px) { .section-back + .page-hero { padding-top: calc(var(--header-h) + 52px + 1.75rem); } }
/* Long button labels on phones (found 2026-10-11: "Learn about our vasectomy procedure →"
   ran 25px past a 390px screen). Let any button wrap and stay inside its column. */
@media (max-width: 480px) { .btn { max-width: 100%; white-space: normal; text-align: center; } }
/* 97 rev (2026-10-11): the masthead now sits on the cinema hero (writing footage), so the
   dek and credentials switch to light text; the portrait gets a hairline so it reads as a card. */
.page-hero--cinema.blog-mast .blog-mast__dek { color: #fff; }
.page-hero--cinema.blog-mast .blog-mast__cred { color: var(--mist); }
.page-hero--cinema.blog-mast .blog-mast__portrait img { box-shadow: 0 0 0 1px rgba(255,255,255,.18), 0 18px 40px rgba(0,0,0,.35); }
.page-hero--cinema.blog-mast .container { position: relative; z-index: 1; }

/* 99. FOLLOWING SECTION NAV (`.pfollow` desktop / `.pfollow-m` phone) — owner 2026-10-11 ------
   Built by ucofl.js §99 from an opted-in `.path-strip[data-follow]`. Hidden (and inert) until
   the full strip scrolls off; then:
     ≥900px  a slim tab bar pinned directly under the fixed header (z below the header).
     <900px  a bottom bar: ‹ prev · "n of N · page" · next ›; the middle opens the full list.
   44px targets throughout; reduced motion drops the slide. */
.pfollow, .pfollow-m { display: none; }
@media (min-width: 900px) {
  .pfollow { display: block; position: fixed; left: 0; right: 0; top: var(--header-h); z-index: 90;
    background: rgba(255,255,255,.94); -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px);
    border-bottom: 1px solid var(--line); transform: translateY(-110%); opacity: 0;
    transition: transform 260ms var(--ease), opacity 260ms var(--ease); pointer-events: none; }
  .pfollow.is-on { transform: none; opacity: 1; pointer-events: auto; }
  .pfollow .container { display: flex; align-items: center; gap: 1.5rem; min-height: 48px; }
  .pfollow__name { font-size: var(--fs-label); letter-spacing: .14em; text-transform: uppercase; color: var(--ink-3); font-weight: 600; white-space: nowrap; }
  .pfollow ol { list-style: none; margin: 0; padding: 0; display: flex; gap: .25rem; flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .pfollow li { flex: none; }
  .pfollow a, .pfollow [aria-current] { display: inline-flex; align-items: center; min-height: 44px; padding: 0 .85rem;
    font-size: var(--fs-small); font-weight: 500; color: var(--ink-2); text-decoration: none; border-bottom: 2px solid transparent; }
  .pfollow a:hover, .pfollow a:focus-visible { color: var(--ink); border-bottom-color: var(--line); }
  .pfollow [aria-current] { color: var(--ink); font-weight: 600; border-bottom-color: var(--amber-line); }
}
@media (max-width: 899px) {
  .pfollow-m { display: block; position: fixed; left: 0; right: 0; bottom: 0; z-index: 90;
    padding-bottom: env(safe-area-inset-bottom); background: rgba(255,255,255,.96);
    -webkit-backdrop-filter: blur(12px); backdrop-filter: blur(12px); border-top: 1px solid var(--line);
    box-shadow: 0 -8px 24px rgba(11,15,18,.08); transform: translateY(110%); transition: transform 260ms var(--ease); }
  .pfollow-m.is-on { transform: none; }
  .pfollow-m.is-on.is-tucked { transform: translateY(110%); }
  .pfollow-m__bar { display: grid; grid-template-columns: 52px minmax(0,1fr) 52px; align-items: center; min-height: 56px; }
  .pfollow-m__step { display: flex; align-items: center; justify-content: center; min-height: 56px; font-size: 1.75rem; line-height: 1; color: var(--blue-text); text-decoration: none; }
  .pfollow-m__step.is-off { color: var(--line); }
  .pfollow-m__mid { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 2px; min-height: 56px; width: 100%;
    padding: 0 .5rem; background: none; border: 0; border-inline: 1px solid var(--line); font: inherit; color: var(--ink); cursor: pointer; }
  .pfollow-m__count { font-size: var(--fs-micro); letter-spacing: .12em; text-transform: uppercase; color: var(--ink-3); }
  .pfollow-m__label { font-size: var(--fs-small); font-weight: 600; max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .pfollow-m__label::after { content: " \25B4"; color: var(--ink-3); font-size: .8em; }
  .pfollow-m.is-open .pfollow-m__label::after { content: " \25BE"; }
  .pfollow-m__panel { border-bottom: 1px solid var(--line); padding: .75rem var(--gutter) .5rem; max-height: 60vh; overflow-y: auto; }
  .pfollow-m__title { margin: 0 0 .25rem; font-size: var(--fs-label); letter-spacing: .14em; text-transform: uppercase; color: var(--ink-3); font-weight: 600; }
  .pfollow-m__panel ol { list-style: none; margin: 0; padding: 0; }
  .pfollow-m__panel a, .pfollow-m__panel [aria-current] { display: flex; align-items: center; gap: .75rem; min-height: 48px; color: var(--ink); text-decoration: none; font-size: var(--fs-small); border-top: 1px solid var(--line); }
  .pfollow-m__panel li:first-child a, .pfollow-m__panel li:first-child [aria-current] { border-top: 0; }
  .pfollow-m__panel [aria-current] { font-weight: 700; color: var(--blue-text); }
  .pfollow-m__n { width: 1.5rem; height: 1.5rem; display: inline-grid; place-items: center; border-radius: 50%; border: 1px solid var(--line); font-size: var(--fs-micro); color: var(--ink-3); flex: none; }
  .pfollow-m__panel [aria-current] .pfollow-m__n { border-color: var(--amber-line); color: var(--amber-text); }
  /* keep the page's last content clear of the bar */
  body:has(.pfollow-m.is-on) footer { padding-bottom: calc(56px + env(safe-area-inset-bottom)); }
}
@media (prefers-reduced-motion: reduce) { .pfollow, .pfollow-m { transition: none; } }
/* 99: in-page anchors must clear the pinned tab bar too (header 72 + bar 48) */
@media (min-width: 900px) { body:has(.pfollow.is-on) main [id] { scroll-margin-top: calc(72px + 48px + 1.5rem); } }
/* 99: a care-path stage with no single page (shown, not linked) */
.pfollow .pfollow__stage { display: inline-flex; align-items: center; min-height: 44px; padding: 0 .85rem; font-size: var(--fs-small); color: var(--ink-3); }
.pfollow-m__panel .pfollow__stage { display: flex; align-items: center; gap: .75rem; min-height: 48px; font-size: var(--fs-small); color: var(--ink-3); border-top: 1px solid var(--line); }

/* 100. PAGE TRANSITIONS (cross-document View Transitions) — owner 2026-10-11 ----------------
   Supporting browsers cross-fade between same-site pages instead of flashing white; the fixed
   header and the section nav bars are named so they hold still across the change. Opt-in only
   when the visitor has not asked for reduced motion. Unsupported browsers ignore all of this. */
@media (prefers-reduced-motion: no-preference) {
  @view-transition { navigation: auto; }
  ::view-transition-old(root), ::view-transition-new(root) { animation-duration: 220ms; animation-timing-function: cubic-bezier(.22,.61,.36,1); }
  .site-header { view-transition-name: site-header; }
  .pfollow { view-transition-name: section-nav; }
  .pfollow-m { view-transition-name: section-nav-m; }
}

/* 101. AT A GLANCE — answer-first summary on service pages (AI-search readiness,
   owner 2026-10-11). Reuses 96.1 .takeaways (relabelled "At a glance") and the
   22 .byline unchanged; the wrapper is a plain .section--tight, so the only new
   rule zeroes the top margin both components carry for their blog-hero context. */
.glance .byline:first-child,
.glance .takeaways:first-child { margin-top: 0; }

/* 102. WORDMARK DRAW — the orange O draws itself once, then the letters settle
   (owner-approved 2026-11-10; 404 + About/Sobre nosotros ONLY — never the header
   or the homepage). Markup: <svg class="wm-draw">, a copy of the header lockup
   paths. The O wears a luminance mask whose white stroke (r 56, width 36 → covers
   radii 38–74; the ring spans ~42.6–69.3) runs 380° so the start/end overlap and
   leave no seam: at rest the mask is solid white over the whole ring, so the end
   frame is pixel-identical to the static logo. Authored at FINAL state; the motion
   only exists under html.js (no-JS / watchdog-stripped = finished logo). Inside a
   .reveal it waits (paused) until the observer adds .is-in. Reduced motion: none. */
.wm-draw { display: block; width: auto; height: 64px; overflow: visible; }
.wm-draw--lg { height: clamp(56px, 9vw, 96px); margin-bottom: clamp(1.5rem, 3vw, 2.25rem); }
.wm-draw--sm { height: clamp(40px, 5vw, 52px); margin-bottom: 1.5rem; }
.wm-draw .wm-l { fill: var(--slate); }
.wm-draw .wm-o { fill: var(--amber); }
.wm-draw .wm-ink { fill: none; stroke: #fff; stroke-width: 36; stroke-dasharray: 100; stroke-dashoffset: 0; }
@media (prefers-reduced-motion: no-preference) {
  html.js .wm-draw .wm-ink { animation: wmDraw 900ms cubic-bezier(0.22, 0.61, 0.36, 1) 150ms both; }
  html.js .wm-draw .wm-l   { transform-box: fill-box; animation: wmSettle 520ms var(--ease) 650ms both; }
  html.js .wm-draw .wm-l--near { animation-delay: 560ms; }
  html.js .reveal:not(.is-in) .wm-draw * { animation-play-state: paused; }
}
@media (prefers-reduced-motion: reduce) { .wm-draw * { animation: none !important; } }
@keyframes wmDraw   { from { stroke-dashoffset: 100; } to { stroke-dashoffset: 0; } }
@keyframes wmSettle { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
