/* ==========================================================================
   Cotesty design tokens
   Derived from Archive-UI-Component-DESIGN.md. Palette, radii and the three
   shadow recipes are closed sets: nothing outside them ships.
   ========================================================================== */

:root {
  color-scheme: light;

  /* ---- palette, from Archive-UI-Component-DESIGN.md ---- */
  --bg:            #FFFFFF;
  --surface:       #FFFAF0;
  --surface-veil:  rgba(255, 250, 240, 0.7);
  --ink:           #1D1B16;
  --ink-2:         #676258;              /* = color-mix(in srgb,#7C766B 78%,#1D1B16). 6.06:1 on --bg, 5.83:1 on --surface */
  /* NON-TEXT ONLY. 4.33:1 on --surface fails AA outright, and 4.51:1 on --bg
     clears it by 0.01, which is a coincidence rather than a margin.
     Verified by scripts/assert-contrast.mjs. */
  --ink-mute:      #7C766B;
  --rule:          rgba(29, 27, 22, 0.12);
  --rule-hard:     #1D1B16;
  --go:            #9EE66E;
  --flag:          #FFD96A;
  --flag-2:        #EECB69;
  --alarm:         #FF5B4F;

  /* ---- severity, four redundant channels: word, glyph, edge weight, fill mode.
         Hue never carries the level alone (WCAG 1.4.1). ---- */
  --sev-crit-bg:   #FF5B4F;   --sev-crit-fg: var(--ink);  --sev-crit-edge: var(--ink);
  --sev-high-bg:   #FFD7CD;   --sev-high-fg: var(--ink);  --sev-high-edge: var(--alarm);
  --sev-med-bg:    #FFD96A;   --sev-med-fg:  var(--ink);  --sev-med-edge:  var(--ink);
  --sev-low-bg:    #F7E5B3;   --sev-low-fg:  var(--ink);  --sev-low-edge:  var(--flag-2);
  --sev-pass-bg:   #D8F2BC;   --sev-pass-fg: var(--ink);  --sev-pass-edge: var(--ink);

  /* ---- type ---- */
  /* The *-Fallback families are metric-matched aliases of Arial and Courier
     New, generated by scripts/measure-font-fallback.py. They sit between the
     webfont and the generic so the swap does not move a single line box. */
  --ff-display: "Bricolage Grotesque", "Bricolage Grotesque Fallback", Arial, sans-serif;
  --ff-mono:    "JetBrains Mono", "JetBrains Mono Fallback", ui-monospace, "SF Mono", Menlo, monospace;

  --fs-h1:          clamp(2.5rem, 1.6rem + 3.85vw, 4.75rem);      /* 40 -> 76px */
  --fs-h2:          clamp(1.875rem, 1.35rem + 2.24vw, 3.25rem);   /* 30 -> 52px */
  --fs-h3:          clamp(1.375rem, 1.18rem + 0.83vw, 1.875rem);  /* 22 -> 30px */
  --fs-h4:          clamp(1.0625rem, 1rem + 0.27vw, 1.25rem);     /* 17 -> 20px */
  --fs-body:        clamp(1rem, 0.96rem + 0.17vw, 1.0625rem);     /* 16 -> 17px */
  --fs-small:       0.875rem;                                     /* 14px */
  --fs-label:       0.75rem;                                      /* 12px */
  --fs-mono-report: clamp(0.8125rem, 0.78rem + 0.15vw, 0.9375rem);/* 13 -> 15px */
  --fs-mono-label:  0.6875rem;                                    /* 11px */

  --lh-h1: 0.88; --lh-h2: 0.95; --lh-h3: 1.12; --lh-h4: 1.25;
  --lh-body: 1.6; --lh-mono: 1.6;
  --tr-h1: -0.045em; --tr-h2: -0.035em; --tr-h3: -0.02em;
  --tr-label: 0.08em; --tr-mono-label: 0.1em;

  /* ---- space, 5px rhythm from the md ---- */
  --s-1: 5px;  --s-2: 10px; --s-3: 12px; --s-4: 13px;
  --s-5: 15px; --s-6: 18px; --s-7: 24px; --s-8: 28px;
  --pad-card-xs: 11.5px; --pad-card-sm: 13.5px;
  --pad-card-md: 24px;   --pad-card-lg: 30px;  --pad-card-xl: 26.5px;
  --pad-section-x: clamp(20px, 4vw, 60px);
  --pad-section-y: clamp(60px, 8vw, 120px);
  --gap-1: 9px; --gap-2: 12px; --gap-3: 18px; --gap-4: 20px;
  --measure: 62ch;
  --grid-max: 1240px;
  --grid-cols: 12;

  /* The sticky header's height, asserted with min-height on .site-header rather
     than left to fall out of its contents. Anchor scroll-padding and the demo's
     sticky device both derive from it, so a change to the header can never
     silently misalign a deep link. */
  --header-h: 59px;

  /* ---- radii, closed family ---- */
  --r-1: 22px; --r-2: 28px; --r-3: 34px; --r-4: 36px; --r-pill: 100px;

  /* ---- elevation, the three md recipes only ---- */
  --shadow-1: rgba(60, 49, 27, 0.10) 0 18px 45px 0;
  --shadow-2: rgba(60, 49, 27, 0.12) 0 16px 34px 0;
  --shadow-3: rgba(60, 49, 27, 0.08) 0 24px 70px 0;
  --border-1: 1px solid var(--rule);
  --border-hard: 1px solid var(--rule-hard);
  --border-accent: 2px solid var(--alarm);
  /* #FF5B4F on #FFFAF0 is 2.96:1 and misses the 3:1 non-text floor, so the ink
     band carries compliance and the alarm band carries identity. */
  --focus-ring: 0 0 0 2px var(--surface), 0 0 0 4px var(--ink), 0 0 0 6px var(--alarm);

  /* ---- motion. transform and opacity only. ---- */
  --dur-1: 350ms;
  --dur-2: 650ms;
  --ease:      cubic-bezier(0.2, 0.8, 0.2, 1);
  --ease-soft: ease;
  --tilt-max: 6deg;
}

@property --tilt-x { syntax: "<angle>"; inherits: true; initial-value: 0deg; }
@property --tilt-y { syntax: "<angle>"; inherits: true; initial-value: 0deg; }

/* Dark mode is deliberately not implemented: the whole page is paper on a desk,
   and #FFFAF0 has no honest dark translation. The only concession is stronger
   elevation, because dark-adapted eyes lose the subtle shadows. */
@media (prefers-color-scheme: dark) {
  :root {
    --shadow-1: rgba(60, 49, 27, 0.16) 0 18px 45px 0;
    --shadow-2: rgba(60, 49, 27, 0.18) 0 16px 34px 0;
    --shadow-3: rgba(60, 49, 27, 0.14) 0 24px 70px 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  :root { --dur-1: 0.01ms; --dur-2: 0.01ms; --tilt-max: 0deg; }
}

/* ==========================================================================
   Base: fonts, reset, elements, surfaces, focus.
   ========================================================================== */

/* ---- webfonts -------------------------------------------------------- */
/* One file per family, subset once to ASCII + Turkish + the punctuation the
   page actually uses. See scripts/fetch-fonts.sh and the coverage assertion
   in scripts/assert-font-coverage.py, which is what stops a silent
   fallback-glyph swap on the Turkish page. */

@font-face {
  font-family: "Bricolage Grotesque";
  src: url("/assets/fonts/bricolage-var-tr-en.woff2") format("woff2");
  font-weight: 200 800;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: "JetBrains Mono";
  src: url("/assets/fonts/jetbrains-mono-var-tr-en.woff2") format("woff2");
  font-weight: 100 800;
  font-style: normal;
  font-display: swap;
}

/* ---------------------------------------------------------------
   Metric-matched fallbacks. GENERATED by scripts/measure-font-fallback.py
   Do not hand-tune these numbers. Re-run the script if a font changes.
   --------------------------------------------------------------- */

@font-face {
  font-family: "Bricolage Grotesque Fallback";
  src: local("Arial");
  size-adjust: 95.19%;
  ascent-override: 97.70%;
  descent-override: 28.37%;
  line-gap-override: 0.00%;
}
/* bricolage-var-tr-en.woff2: avg advance 0.5392em vs Arial 0.5665em */

@font-face {
  font-family: "JetBrains Mono Fallback";
  src: local("Courier New");
  size-adjust: 99.98%;
  ascent-override: 102.02%;
  descent-override: 30.00%;
  line-gap-override: 0.00%;
}
/* jetbrains-mono-var-tr-en.woff2: avg advance 0.6000em vs Courier New 0.6001em */

/* ---- reset ----------------------------------------------------------- */

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

html {
  -webkit-text-size-adjust: 100%;
  /* No scroll-behavior: smooth. This document is about 19,000px tall, so a
     footer link to #faq animated through 16,600px and took 1.5 seconds to
     arrive, measured. Smooth scrolling is pleasant over a screen or two and
     is motion sickness over sixteen. Anchor jumps are instant. */

  /* Anchors land clear of the sticky header. Derived from --header-h rather
     than hand-tuned, so the two cannot drift apart. */
  scroll-padding-top: calc(var(--header-h) + var(--s-8));
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font-family: var(--ff-display);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  font-variation-settings: "opsz" 16;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

h1, h2, h3, h4, p, figure, blockquote, dl, dd, ul, ol { margin: 0; }
ul, ol { padding: 0; list-style: none; }

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

button, input, select, textarea { font: inherit; color: inherit; }

table { border-collapse: collapse; }

/* ---- headings -------------------------------------------------------- */
/* opsz is set per level so one file serves 76px display and 17px body.
   Bricolage is narrower than Inter, so the source system's -0.075em is
   reduced to -0.045em: at -0.075em the terminals collide. */

h1, .h1 {
  font-size: var(--fs-h1);
  line-height: var(--lh-h1);
  letter-spacing: var(--tr-h1);
  font-weight: 500;
  font-variation-settings: "opsz" 76;
  text-wrap: balance;
}

@media (min-width: 1024px) {
  h1, .h1 { letter-spacing: -0.06em; }
}

h2, .h2 {
  font-size: var(--fs-h2);
  line-height: var(--lh-h2);
  letter-spacing: var(--tr-h2);
  font-weight: 500;
  font-variation-settings: "opsz" 52;
  text-wrap: balance;
}

h3, .h3 {
  font-size: var(--fs-h3);
  line-height: var(--lh-h3);
  letter-spacing: var(--tr-h3);
  font-weight: 500;
  font-variation-settings: "opsz" 30;
}

h4, .h4 {
  font-size: var(--fs-h4);
  line-height: var(--lh-h4);
  letter-spacing: -0.01em;
  font-weight: 600;
  font-variation-settings: "opsz" 20;
}

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

a { color: inherit; text-underline-offset: 0.18em; }

/* ---- mono, the instrument layer -------------------------------------- */

.mono {
  font-family: var(--ff-mono);
  font-size: var(--fs-mono-report);
  line-height: var(--lh-mono);
  font-variant-numeric: tabular-nums;
}

.eyebrow,
.label {
  font-family: var(--ff-mono);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--ink-2);
  margin: 0;
}

.mono-label {
  font-family: var(--ff-mono);
  font-size: var(--fs-mono-label);
  font-weight: 600;
  letter-spacing: var(--tr-mono-label);
  text-transform: uppercase;
  line-height: 1.1;
}

/* ---- surfaces -------------------------------------------------------- */
/* Four recipes, all from Archive-UI-Component-DESIGN.md. S0 is the inverted
   instrument ground: a printout taped to the desk, not dark mode. */

.s0 {
  background: var(--ink);
  color: var(--surface);
  border-radius: var(--r-1);
  padding: var(--pad-card-md);
  font-family: var(--ff-mono);
}

.s1 {
  background: var(--bg);
  border: var(--border-1);
  border-radius: var(--r-1);
  padding: var(--pad-card-xl);
}

.s2 {
  background: var(--surface);
  border: var(--border-1);
  border-radius: var(--r-2);
  padding: var(--pad-card-lg);
  box-shadow: var(--shadow-1);
}

.s3 {
  background: var(--surface-veil);
  border: var(--border-1);
  border-radius: var(--r-3);
  padding: var(--pad-card-md);
  box-shadow: var(--shadow-3);
}

/* ---- focus ----------------------------------------------------------- */
/* Double ring on purpose. #FF5B4F on #FFFAF0 is 2.96:1 and misses the 3:1
   non-text floor, so the ink band carries compliance and the alarm band
   carries identity. An outline is declared first so the ring survives
   forced-colors mode, where box-shadow is dropped. */

:focus-visible {
  outline: 3px solid var(--ink);
  outline-offset: 3px;
}

@supports (color: color-mix(in srgb, red, blue)) {
  @media not (forced-colors: active) {
    :focus-visible {
      outline-color: var(--alarm);
      box-shadow: var(--focus-ring);
    }
  }
}

/* ---- utilities ------------------------------------------------------- */

.visually-hidden:not(:focus):not(:active) {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.skip-link {
  position: absolute;
  top: var(--s-2);
  left: var(--s-2);
  z-index: 100;
  padding: var(--s-2) var(--s-5);
  background: var(--go);
  color: var(--ink);
  border: var(--border-hard);
  border-radius: var(--r-pill);
  font-family: var(--ff-mono);
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  text-decoration: none;
  transform: translateY(-300%);
  transition: transform var(--dur-1) var(--ease);
}

.skip-link:focus {
  transform: translateY(0);
}

/* ==========================================================================
   Layout: the page frame, grid, header, footer.

   Full-bleed substrate with contained content. That is how the source
   system's "full bleed + strong grid" survives on a marketing page: the
   background runs edge to edge, the text never does.
   ========================================================================== */

.wrap {
  width: 100%;
  max-width: var(--grid-max);
  margin-inline: auto;
  padding-inline: var(--pad-section-x);
}

.section {
  padding-block: var(--pad-section-y);
}

/* The 12-column grid only activates at 900px. Below that every asymmetric
   split collapses to one column in source order, and the deliberate vertical
   offsets go to zero rather than becoming a cramped two-column layout. */
.grid {
  display: grid;
  gap: var(--gap-4);
  grid-template-columns: 1fr;
}

@media (min-width: 900px) {
  .grid {
    grid-template-columns: repeat(var(--grid-cols), minmax(0, 1fr));
    column-gap: var(--gap-4);
  }
}

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

.site-header {
  position: sticky;
  top: 0;
  z-index: 50;
  min-height: var(--header-h);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--gap-3);
  padding: var(--s-4) var(--pad-section-x);
  background: var(--surface-veil);
  backdrop-filter: blur(10px);
  border-bottom: var(--border-1);
}

.site-header__brand {
  text-decoration: none;
  display: inline-flex;
  align-items: center;
}

.wordmark {
  font-family: var(--ff-display);
  font-size: 1.25rem;
  font-weight: 700;
  letter-spacing: -0.03em;
  font-variation-settings: "opsz" 20;
}

.site-header__nav {
  display: flex;
  gap: var(--s-1);
  align-items: center;
}

.site-header__nav a {
  font-family: var(--ff-mono);
  font-size: var(--fs-mono-label);
  font-weight: 600;
  letter-spacing: var(--tr-mono-label);
  text-decoration: none;
  color: var(--ink-2);
  padding: var(--s-1) var(--s-2);
  border-radius: var(--r-pill);
  border: 1px solid transparent;
}

.site-header__nav a[aria-current] {
  color: var(--ink);
  border-color: var(--rule-hard);
  background: var(--bg);
}

/* ---- section nav (header, middle) ------------------------------------ */
/* One row at every width: brand, the section links, the language switch. The
   links scroll sideways inside their own strip on narrow screens, so the header
   stays exactly one --header-h tall (anchor scroll-padding depends on that) and
   the page body never scrolls horizontally. Plain links: works with no JS. */

.site-header__brand,
.site-header__nav { flex: 0 0 auto; }

.site-nav {
  flex: 0 1 auto;
  min-width: 0;
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scrollbar-width: none;
}
.site-nav::-webkit-scrollbar { display: none; }

.site-nav__list {
  display: flex;
  gap: var(--s-1);
  align-items: center;
  list-style: none;
  width: max-content;
  margin: 0;
  padding: 0;
}

.site-nav__link {
  display: inline-block;
  white-space: nowrap;
  font-family: var(--ff-mono);
  font-size: var(--fs-mono-label);
  font-weight: 600;
  letter-spacing: var(--tr-mono-label);
  text-decoration: none;
  color: var(--ink-2);
  padding: var(--s-1) var(--s-2);
  border-radius: var(--r-pill);
  border: 1px solid transparent;
}
.site-nav__link:hover { color: var(--ink); }
.site-nav__link[aria-current="page"] {
  color: var(--ink);
  border-color: var(--rule-hard);
  background: var(--bg);
}

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

.site-footer {
  border-top: var(--border-hard);
  padding-block: var(--pad-section-y);
  background: var(--surface);
}

.site-footer__grid {
  display: grid;
  gap: var(--gap-4) clamp(24px, 5vw, 72px);
  grid-template-columns: 1fr;
}

@media (min-width: 720px) {
  .site-footer__grid { grid-template-columns: 1.4fr 2fr; align-items: start; }
}

.site-footer__brandcol .wordmark { display: block; margin-bottom: var(--s-4); }
.site-footer__product { color: var(--ink); margin-bottom: var(--s-2); }
.site-footer__studio { color: var(--ink-2); font-size: var(--fs-small); }

.site-footer__links {
  display: grid;
  gap: var(--gap-4);
  grid-template-columns: repeat(2, minmax(0, 1fr));
}
@media (min-width: 520px) {
  .site-footer__links { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.site-footer__group p { margin-bottom: var(--s-3); color: var(--ink-2); }
.site-footer__group ul { display: grid; gap: var(--s-2); }
.site-footer__group a {
  font-size: var(--fs-small);
  color: var(--ink);
  text-decoration: none;
}
.site-footer__group a:hover { text-decoration: underline; }

.site-footer__base {
  margin-top: var(--pad-card-lg);
  padding-top: var(--pad-card-md);
  border-top: var(--border-1);
  display: grid;
  gap: var(--s-3);
}
.site-footer__sources {
  font-size: var(--fs-mono-report);
  color: var(--ink);
  max-width: 78ch;
}
.site-footer__contact { font-size: var(--fs-small); color: var(--ink-2); max-width: 78ch; }
.site-footer__bottom { color: var(--ink-2); }

/* ---- overflow discipline --------------------------------------------- */
/* Wide content scrolls inside its own container. The page body never scrolls
   sideways, at any width. */

.scroll-x {
  overflow-x: auto;
  overscroll-behavior-x: contain;
}

/* ==========================================================================
   Components: reusable pieces used across sections.
   CTA control, buttons, severity chips, finding rows, report frame.
   ========================================================================== */

/* ---- CTA composite control ------------------------------------------- */
/* Input and button read as one 100px-radius object on white, the button
   inlined at the right edge. Below a small width it stacks. */

.cta { width: 100%; max-width: 34rem; }
.cta--center { margin-inline: auto; }

.cta__control {
  display: flex;
  gap: var(--s-1);
  padding: var(--s-1);
  background: var(--bg);
  border: var(--border-hard);
  border-radius: var(--r-pill);
}

.cta__input {
  flex: 1 1 auto;
  min-width: 0;
  border: 0;
  background: transparent;
  padding: var(--s-3) var(--s-5);
  font-family: var(--ff-mono);
  font-size: var(--fs-mono-report);
  color: var(--ink);
}
.cta__input::placeholder { color: var(--ink-2); opacity: 1; }
.cta__input:focus { outline: none; }
.cta__control:focus-within {
  box-shadow: var(--focus-ring);
  outline: 3px solid transparent;
}

.cta__button {
  flex: 0 0 auto;
  border: 0;
  border-radius: var(--r-pill);
  padding: var(--s-3) var(--s-6);
  background: var(--go);
  color: var(--ink);
  font-family: var(--ff-mono);
  font-size: var(--fs-mono-label);
  font-weight: 700;
  letter-spacing: var(--tr-mono-label);
  text-transform: uppercase;
  cursor: pointer;
  transition: transform var(--dur-1) var(--ease);
}
.cta__button:hover { transform: translateY(-1px); }
.cta__button:active { transform: translateY(0); }
.cta__button:disabled { cursor: progress; opacity: 0.7; }

.cta__help {
  margin: var(--s-3) 0 0 var(--s-5);
  font-family: var(--ff-mono);
  font-size: var(--fs-mono-label);
  letter-spacing: var(--tr-mono-label);
  text-transform: uppercase;
  color: var(--ink-2);
}
.cta--center .cta__help { margin-left: 0; text-align: center; }

/* Reserve one line so an appearing status message never shifts layout. */
.cta__status {
  margin: var(--s-2) 0 0 var(--s-5);
  min-height: 1.4em;
  font-family: var(--ff-mono);
  font-size: var(--fs-mono-report);
  line-height: 1.4;
}
.cta--center .cta__status { margin-left: 0; text-align: center; }
.cta__status[data-kind="error"] { color: var(--alarm); }
.cta__status[data-kind="success"] { color: var(--ink); }
.cta__status[data-kind="busy"] { color: var(--ink-2); }

@media (max-width: 460px) {
  .cta__control { flex-direction: column; border-radius: var(--r-2); }
  .cta__button { width: 100%; }
}

/* ---- severity chips -------------------------------------------------- */
/* Four redundant channels, so a monochrome or fully colorblind reader loses
   nothing: (1) the printed word, always; (2) an ASCII glyph; (3) left-edge
   bar weight; (4) fill vs tint vs dotted. Glyphs are ASCII on purpose, both
   because Bricolage lacks the check/cross/triangle marks and because ASCII
   survives HTML-to-text extraction by an AI crawler. */

.sev {
  display: inline-flex;
  align-items: center;
  gap: var(--s-1);
  flex: 0 0 auto;
  font-family: var(--ff-mono);
  font-size: var(--fs-mono-label);
  font-weight: 700;
  letter-spacing: var(--tr-mono-label);
  line-height: 1;
  padding: 5px 10px 5px 8px;
  border-radius: var(--r-pill);
  color: var(--ink);
  border: 1px solid var(--ink);
}
.sev__glyph {
  font-weight: 700;
  opacity: 0.85;
}

.sev--crit {
  background: var(--sev-crit-bg);
  border-left: 6px solid var(--ink);
}
.sev--high {
  background: var(--sev-high-bg);
  border-left: 6px solid var(--alarm);
}
.sev--med {
  background: var(--sev-med-bg);
  border-left: 3px solid var(--ink);
}
.sev--low {
  background: var(--sev-low-bg);
  border-color: var(--rule);
  border-left: 3px solid var(--flag-2);
}
.sev--pass {
  background: var(--sev-pass-bg);
  border-style: dotted;
  border-left: 1px dotted var(--ink);
}

/* ---- hero card: interactive sample report ---------------------------- */
/* Persona tabs switch the visible panel through radio :checked + :has(), so it
   works with no JavaScript. All three panels are always present and reserved
   to the same height, so switching shifts nothing and the H1 keeps LCP. */

.hero-card {
  margin: 0;
  max-width: 30rem;
  background: var(--surface);
  border: var(--border-1);
  border-radius: var(--r-2);
  box-shadow: var(--shadow-1);
  padding: var(--pad-card-md);
}
.hero-card__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--gap-2);
  padding-bottom: var(--s-3);
  border-bottom: var(--border-hard);
}
.hero-card__tag { color: var(--ink); }
.hero-card__app { color: var(--ink-2); }

.hero-card__tabs {
  border: 0;
  margin: var(--s-4) 0;
  padding: 0;
  display: flex;
  gap: var(--s-2);
}
.hero-card__legend { float: left; width: 100%; margin-bottom: var(--s-3); color: var(--ink-2); }
/* No boxed chrome: the tab is just the round avatar and the age under it, so it
   reads as one control, not a box wrapped around a circle. Selection shows as a
   green ring on the avatar, not a filled background. */
.hero-card__tab {
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  min-width: 48px;
  min-height: 44px;
  padding: 2px;
  border: 0;
  background: transparent;
  color: var(--ink);
  cursor: pointer;
  font-family: var(--ff-mono);
  font-weight: 700;
  transition: transform var(--dur-1) var(--ease);
}
/* The round avatar badge holds the persona photo. overflow:hidden crops the
   image into the circle; the 1px ink edge frames it against the cream card. */
.hero-card__avatar-badge {
  width: 44px;
  height: 44px;
  border-radius: 50%;
  background: var(--surface);
  border: 1px solid var(--rule-hard);
  overflow: hidden;
}
.hero-card__avatar { display: block; width: 100%; height: 100%; object-fit: cover; }
.hero-card__age { font-size: 12px; line-height: 1; color: var(--ink-2); }
.hero-card__tab:hover { transform: translateY(-1px); }
.hero-card__radio:checked + .hero-card__tab .hero-card__avatar-badge {
  box-shadow: 0 0 0 3px var(--go);
}
.hero-card__radio:checked + .hero-card__tab .hero-card__age { color: var(--ink); }
.hero-card__radio:focus-visible + .hero-card__tab .hero-card__avatar-badge {
  outline: 3px solid var(--ink);
  outline-offset: 3px;
}

/* All three panels occupy the same grid cell, so the card is always as tall as
   the tallest panel no matter which is shown. This makes the persona switch
   zero layout shift by construction, with no reserved magic number to keep in
   sync with the copy. Inactive panels are visibility:hidden, so they hold their
   space but leave the tab order and the accessibility tree. The text is still
   in the served HTML for crawlers either way. */
.hero-card__panels { display: grid; }
.hero-card__panel { grid-area: 1 / 1; visibility: hidden; }
.hero-card:has(#hp14:checked) .hero-card__panel[data-persona="14"],
.hero-card:has(#hp35:checked) .hero-card__panel[data-persona="35"],
.hero-card:has(#hp68:checked) .hero-card__panel[data-persona="68"] { visibility: visible; }

.hero-card__persona { color: var(--ink-2); margin-bottom: var(--s-3); max-width: none; }
.hero-card__list { display: grid; gap: var(--s-2); }
.hero-find {
  display: grid;
  justify-items: start;            /* the sev pill hugs its text, not the column */
  gap: var(--s-2);
  padding: var(--s-3) 0;
  border-top: var(--border-1);
}
.hero-find:first-child { border-top: 0; }
.hero-find__text { color: var(--ink); max-width: none; }
.hero-card__tally {
  margin-top: var(--s-4);
  padding-top: var(--s-3);
  border-top: var(--border-1);
  color: var(--ink-2);
}

/* ==========================================================================
   Sections: per-section layout. One block per section, in page order.
   The rhythm rule: substrate alternates white / cream / black, density
   alternates airy prose / dense mono, and no two consecutive sections share
   a dominant element type.
   ========================================================================== */

/* ---- S0 full-bleed band (proof strip, final CTA) --------------------- */

.s0-band {
  background: var(--ink);
  color: var(--surface);
}
.s0-band .eyebrow,
.s0-band .mono-label { color: color-mix(in srgb, var(--surface) 72%, var(--ink)); }
.s0-band a { color: var(--surface); }

/* ---- 1. Hero --------------------------------------------------------- */
/* 7 / 5 left-weighted split, not centered. The H1 is still the LCP element:
   its entrance is transform-only with opacity kept at 1, so it paints at the
   same moment it always did and stays the largest contentful paint. The report
   frame is real DOM, not an <img>. The intro is defined at the bottom of this
   block and is disabled entirely under prefers-reduced-motion. */

.hero { background: var(--bg); padding-block: clamp(48px, 7vw, 96px); overflow-x: clip; }

.hero__grid {
  display: grid;
  gap: var(--gap-4);
  align-items: center;
  grid-template-columns: 1fr;
  position: relative;              /* positioning context for the intro flash */
}

.hero__title {
  margin: var(--s-5) 0 var(--s-6);
}
.hero__subhead {
  font-size: var(--fs-h4);
  line-height: 1.5;
  color: var(--ink-2);
  margin-bottom: var(--s-8);
  max-width: 46ch;
}

.hero__frame { margin: 0; }

@media (min-width: 900px) {
  .hero__grid {
    grid-template-columns: 7fr 5fr;
    gap: clamp(24px, 5vw, 72px);
  }
}

/* ---- hero intro (first paint): TEST IT takeover ---------------------- */
/* A giant "TEST IT!" fills the hero first, then the two columns rush in from the
   far left and far right and crush it between them (scaleX -> 0 at the moment
   they meet). This is a deliberate design choice that trades the strict
   "LCP element is the H1" budget: at this size the takeover text is the largest
   paint on home, so measure-lcp is relaxed to accept it and to allow up to
   Google's "good" 2500ms LCP. .hero clips the overflow so the off-screen panels
   never add a scrollbar; everything is transform + opacity, so CLS stays 0; and
   the whole intro is skipped under prefers-reduced-motion. The flash text lives
   in CSS, so crawlers never read a stray "TEST IT!" and it localises via :lang. */
.hero__flash {
  position: absolute;
  inset-inline: 0;
  top: 50%;
  z-index: 5;
  pointer-events: none;
  opacity: 0;
  transform: translateY(-50%);
}
/* Composed like the landing itself: the same eyebrow, then a full-bleed TEST IT,
   both left-aligned to the content edge (not a floating centred word). The block
   spans the content width so the exit squeeze collapses it to the middle where the
   two columns meet. */
.hero__flash::before {
  content: "AGE-MATCHED PRODUCT VALIDATION";
  display: block;
  font-family: var(--ff-mono);
  font-size: clamp(11px, 1.35vw, 18px);
  font-weight: 500;
  letter-spacing: var(--tr-label);
  text-transform: uppercase;
  color: var(--ink-2);
  margin-bottom: clamp(6px, 1.4vw, 18px);
  padding-inline-start: 0.12em;
}
.hero__flash::after {
  content: "TEST IT!";
  display: block;
  font-family: var(--ff-display);
  font-weight: 800;
  font-size: clamp(72px, 16vw, 232px);
  line-height: 0.86;
  letter-spacing: -0.03em;
  color: var(--ink);
  white-space: nowrap;
}
:lang(tr) .hero__flash::before { content: "YAŞ GRUBUNA GÖRE ÜRÜN DOĞRULAMA"; }
:lang(tr) .hero__flash::after  { content: "TEST ET!"; }

/* The columns settle at the sides and stay BEHIND the flash (z-index above): they
   never ride on top of the text. All three intro animations are gated on
   `scripting: enabled`, so WITHOUT JavaScript (or on a browser lacking the feature)
   the hero paints straight into its final resting state -- there is no takeover
   that could freeze mid-play and cover the content. With JS on, intro.js also
   force-clears the takeover (see .intro-done below) on animationend, on a
   wall-clock timeout, and on the first reveal if the tab was loaded in the
   background, so a throttled half-crushed "TEST IT" can never sit stuck on the
   hero. The flash still paints its 0% frame at first paint (media query resolved
   at parse time), so there is no flash-of-final-content. */
@media (scripting: enabled) {
  .hero__flash { animation: hero-flash 3100ms var(--ease) both; }
  .hero__lede  { animation: hero-lede-in  3100ms var(--ease) both; }
  .hero__frame { animation: hero-frame-in 3100ms var(--ease) both; }
}

/* Guaranteed teardown, added by intro.js the instant the takeover is done: the
   flash is removed from the box model (not merely faded), so a throttled,
   half-dissolved "TEST IT" can never sit stuck on the hero. .hero__flash is
   position:absolute, so display:none shifts nothing and CLS stays 0.
   The columns are deliberately NOT reset here: their `both` fill already holds
   them at the final position, and forcing `animation: none` would repaint the
   report card late, registering an avatar <img> as a fresh (and, by a hair,
   largest) LCP candidate at teardown time -- pushing home LCP from ~1.6s to
   ~4.7s. Leaving the fill in place keeps the early wordmark paint as LCP. */
.hero__grid.intro-done .hero__flash { display: none; }

/* Sequence (timing matters, and the two phases never overlap): TEST IT eases IN
   (~0.5s), holds ALONE in the centre (~0.75s), then SMOOTHLY DISSOLVES -- a gentle
   opacity fade with a slight scale up, no squeeze -- and is fully gone by 56%.
   ONLY THEN do the two columns slide in from the far left and right (58% -> 100%).
   Both columns wait fully off-screen (own width + 40vw) until it is their turn, so
   the takeover has the stage to itself and the entrance has the stage to itself. */
@keyframes hero-flash {
  0%   { opacity: 0; transform: translateY(-50%) scale(0.92); }
  16%  { opacity: 1; transform: translateY(-50%) scale(1); }
  40%  { opacity: 1; transform: translateY(-50%) scale(1); }
  56%  { opacity: 0; transform: translateY(-50%) scale(1.04); }
  100% { opacity: 0; transform: translateY(-50%) scale(1.04); }
}
@keyframes hero-lede-in {
  0%   { transform: translateX(calc(-100% - 40vw)) rotate(-3deg); }
  58%  { transform: translateX(calc(-100% - 40vw)) rotate(-3deg); }
  100% { transform: translateX(0) rotate(0); }
}
@keyframes hero-frame-in {
  0%   { transform: translateX(calc(100% + 40vw)) rotate(3deg); }
  58%  { transform: translateX(calc(100% + 40vw)) rotate(3deg); }
  100% { transform: translateX(0) rotate(0); }
}

@media (prefers-reduced-motion: reduce) {
  .hero__flash { display: none; }
  .hero__lede,
  .hero__frame { animation: none; }
}

/* ---- final CTA (S0), the mirror of the proof strip ------------------- */

.final-cta__inner { text-align: center; }
.final-cta__title {
  max-width: 20ch;
  margin-inline: auto;
}
.final-cta__subhead {
  margin: var(--s-6) auto var(--s-8);
  max-width: 52ch;
  color: color-mix(in srgb, var(--surface) 82%, var(--ink));
  font-size: var(--fs-h4);
  line-height: 1.5;
}
.final-cta .cta__control {
  background: var(--surface);
  border-color: var(--surface);
}
.final-cta__note {
  margin: var(--s-8) auto 0;
  max-width: 60ch;
  color: color-mix(in srgb, var(--surface) 70%, var(--ink));
  font-size: var(--fs-mono-report);
}

/* On the dark band, the CTA help and status need light ink. */
.final-cta .cta__help { color: color-mix(in srgb, var(--surface) 72%, var(--ink)); }
.final-cta .cta__status[data-kind="success"],
.final-cta .cta__status[data-kind="busy"] { color: var(--surface); }
.final-cta .cta__status[data-kind="error"] { color: var(--alarm); }


/* ---- 2. Proof strip ---------------------------------------------------
   A hard instrument band straight after the warm demo. Three mono cells
   separated by hairlines, not cards. Dominant element: one large numeral. */

.proof__h2 { color: color-mix(in srgb, var(--surface) 72%, var(--ink)); margin-bottom: var(--gap-3); }
.proof__grid {
  display: grid;
  gap: var(--gap-4);
  grid-template-columns: 1fr;
}
@media (min-width: 900px) {
  .proof__grid { grid-template-columns: 1.1fr 1fr 1fr; gap: 0; }
  .proof__cell + .proof__cell {
    border-left: 1px solid color-mix(in srgb, var(--surface) 26%, var(--ink));
    padding-left: var(--pad-card-lg);
    margin-left: var(--pad-card-lg);
  }
}
.proof__big {
  font-size: clamp(2.75rem, 2rem + 3vw, 3.25rem);
  line-height: 1;
  font-weight: 700;
  color: var(--go);
  margin-bottom: var(--s-4);
}
.proof__head { color: var(--surface); margin-bottom: var(--s-4); }
.proof__body { color: color-mix(in srgb, var(--surface) 82%, var(--ink)); max-width: 42ch; }
.proof__src {
  margin-top: var(--s-5);
  color: color-mix(in srgb, var(--surface) 62%, var(--ink));
  text-transform: none;
  letter-spacing: 0;
}
.proof__src a { color: var(--go); }

/* ---- 3. The problem ---------------------------------------------------
   Warm, prose-led, vertically scrolling. The H2 sticks while the evidence
   scrolls past it. Distinguished from the band above by being the opposite
   in every dimension. */

.problem { background: var(--surface); }
.problem__grid { display: grid; gap: var(--gap-4); grid-template-columns: 1fr; }
@media (min-width: 900px) {
  .problem__grid { grid-template-columns: 5fr 7fr; gap: clamp(24px, 5vw, 72px); }
  .problem__aside { position: sticky; top: 120px; align-self: start; }
}
.problem__lede { font-size: var(--fs-h4); line-height: 1.5; max-width: 54ch; }
.problem__body { margin-top: var(--gap-3); color: var(--ink-2); }
.problem__src { margin-top: var(--s-3); color: var(--ink-2); text-transform: none; letter-spacing: 0; }
.problem__src a { color: var(--ink); }
.problem__card { margin-top: var(--gap-4); }
.problem__card h3 { margin-bottom: var(--s-4); }
.problem__card p { color: var(--ink-2); }
.problem__note {
  margin-top: var(--s-5);
  padding-left: var(--s-5);
  border-left: var(--border-accent);
  color: var(--ink);
}
.problem__closing {
  margin-top: var(--gap-4);
  font-size: var(--fs-h3);
  line-height: 1.25;
  letter-spacing: var(--tr-h3);
  max-width: 24ch;
}

/* ---- 4. How it works --------------------------------------------------
   A vertical numbered sequence on a single rule. Never a three-card grid:
   that layout is the clearest tell of an unedited framework default. */

.how { background: var(--bg); }
.how__subhead { color: var(--ink-2); font-size: var(--fs-h4); margin: var(--s-5) 0 var(--pad-card-lg); }
.how__steps {
  position: relative;
  display: grid;
  gap: var(--pad-card-lg);
  padding-left: 0;
}
.step { display: grid; gap: var(--s-3); }
.step__num { color: var(--ink-2); }
.step__head { margin-bottom: var(--s-3); }
.step p { color: var(--ink-2); }

@media (min-width: 900px) {
  .how__steps { padding-left: 26%; }
  /* The rule the sequence hangs from. */
  .how__steps::before {
    content: "";
    position: absolute;
    left: calc(26% - 1px);
    top: 8px; bottom: 8px;
    width: 1px;
    background: var(--rule-hard);
  }
  .step {
    grid-template-columns: 1fr;
    position: relative;
    padding-left: var(--pad-card-lg);
  }
  .step__num {
    position: absolute;
    left: calc(-26% + var(--pad-card-lg));
    top: 2px;
    width: calc(26% - var(--pad-card-lg) - var(--gap-4));
    text-align: right;
  }
  /* A slight walk, so the sequence reads as a sequence and not a list.
     The offset moves the body only. Shifting the whole .step would carry the
     absolutely-positioned number with it and collide it with the rule. */
  .step:nth-child(even) .step__body { margin-left: 40px; }
}

/* ---- 5. The age axis --------------------------------------------------
   Edge to edge, image-dominant, the widest section on the page. The
   argument is made by the annotated pair, with no borrowed statistics. */

.age { background: var(--surface); }
.age__h2 { max-width: 24ch; }
.age__subhead { font-size: var(--fs-h4); color: var(--ink-2); margin: var(--s-5) 0 var(--gap-3); }
.age__opening { max-width: 58ch; }
.age__block-head { margin: var(--pad-card-lg) 0 var(--gap-3); }
.age__pair { display: grid; gap: var(--gap-4); grid-template-columns: 1fr; }
@media (min-width: 820px) { .age__pair { grid-template-columns: 1fr 1fr; } }
.age__col-label { color: var(--ink-2); padding-bottom: var(--s-3); border-bottom: var(--border-hard); }
.age__notes { display: grid; gap: var(--s-3); margin-top: var(--s-5); }
.age__note {
  padding-left: var(--s-5);
  border-left: 3px solid var(--rule);
  color: var(--ink);
}
.age__col--b .age__note { border-left-color: var(--flag); }
/* Two classes, so it beats the .age__col--b descendant rule above. The stop
   is the whole point of the pair and has to read as the stop. */
.age__col--b .age__note--stop { border-left-color: var(--alarm); border-left-width: 6px; }
.age__after { margin-top: var(--gap-4); max-width: 58ch; color: var(--ink-2); }
.age__human { margin-top: var(--gap-4); }
.age__human h3 { margin-bottom: var(--s-4); }
.age__closing {
  margin-top: var(--gap-4);
  font-size: var(--fs-h3);
  line-height: 1.25;
  letter-spacing: var(--tr-h3);
  max-width: 34ch;
}

/* ---- 6. What the report covers ---------------------------------------
   The artifact itself, on the instrument ground. Real excerpt structure,
   not feature bullets. */

.report { background: var(--bg); }
.report__subhead { font-size: var(--fs-h4); color: var(--ink-2); margin: var(--s-5) 0 var(--s-4); }
.report__note {
  color: var(--ink-2);
  text-transform: none;
  letter-spacing: 0;
  max-width: 62ch;
  padding-left: var(--s-5);
  border-left: 3px solid var(--flag);
}
.report__list { display: grid; gap: var(--gap-3); margin-top: var(--pad-card-lg); }
.ex { border-radius: var(--r-1); }
.ex__meta { display: grid; gap: var(--s-3); margin-bottom: var(--s-5); }
@media (min-width: 720px) { .ex__meta { grid-template-columns: auto 1fr 1fr; gap: var(--gap-3); align-items: start; } }
.ex__meta dt { color: color-mix(in srgb, var(--surface) 62%, var(--ink)); margin-bottom: var(--s-1); }
.ex__meta dd { margin: 0; color: color-mix(in srgb, var(--surface) 88%, var(--ink)); }
.ex__body { color: var(--surface); max-width: 76ch; }
.ex__ev {
  margin-top: var(--s-5);
  padding-top: var(--s-3);
  border-top: 1px solid color-mix(in srgb, var(--surface) 26%, var(--ink));
  color: color-mix(in srgb, var(--surface) 62%, var(--ink));
  text-transform: none;
  letter-spacing: 0;
}
.report__footer { margin-top: var(--gap-4); color: var(--ink-2); max-width: 66ch; }

/* ---- 7. What we do not catch -----------------------------------------
   No card, no shadow, no radius. The one section that refuses the material
   system, which is exactly what makes it read as honest. */

.limits { background: var(--bg); }
.limits__inner {
  max-width: 68ch;
  border-top: var(--border-hard);
  border-bottom: var(--border-hard);
  padding-block: var(--pad-card-lg);
}
.limits__label { color: var(--ink-2); margin-bottom: var(--s-5); }
.limits h2 { font-size: var(--fs-h3); letter-spacing: var(--tr-h3); }
.limits__lede { margin: var(--s-5) 0 var(--pad-card-lg); font-size: var(--fs-h4); line-height: 1.5; }
.limits h3 {
  font-size: var(--fs-h4);
  margin-top: var(--pad-card-lg);
  margin-bottom: var(--s-4);
  padding-top: var(--s-5);
  border-top: var(--border-1);
}
.limits p { color: var(--ink-2); }
.limits p + p { margin-top: var(--s-5); }
.limits__src { color: var(--ink-2); text-transform: none; letter-spacing: 0; }
.limits__src a { color: var(--ink); }
.limits__closing {
  margin-top: var(--pad-card-lg);
  padding-top: var(--s-5);
  border-top: var(--border-hard);
  color: var(--ink) !important;
  font-weight: 600;
}

/* ---- 9. Comparison ----------------------------------------------------
   The densest, coldest, most orthogonal block on the page. Question rows,
   not feature rows. Only the Cotesty column is filled. */

.compare { background: var(--surface); }
.compare__subhead { font-size: var(--fs-h4); color: var(--ink-2); margin: var(--s-5) 0 var(--s-4); }
.compare__hint { color: var(--ink-2); text-transform: none; letter-spacing: 0; }
@media (min-width: 1024px) { .compare__hint { display: none; } }
.compare__scroll { margin-top: var(--gap-3); }
.compare__table { width: 100%; min-width: 900px; border-collapse: collapse; }
.compare__table th, .compare__table td {
  border: 1px solid var(--rule-hard);
  padding: var(--s-5);
  text-align: left;
  vertical-align: top;
  font-size: var(--fs-mono-report);
  font-family: var(--ff-mono);
  color: var(--ink-2);
}
.compare__table thead th {
  font-family: var(--ff-display);
  font-size: var(--fs-small);
  font-weight: 600;
  color: var(--ink);
  background: var(--bg);
}
.compare__table tbody th {
  font-family: var(--ff-display);
  font-size: var(--fs-h4);
  font-weight: 500;
  color: var(--ink);
  width: 16%;
}
.compare__table .cmp--us {
  background: var(--bg);
  color: var(--ink);
  box-shadow: inset 2px 0 0 var(--alarm), inset -2px 0 0 var(--alarm);
}
.compare__table thead .cmp--us { box-shadow: inset 2px 2px 0 var(--alarm), inset -2px 0 0 var(--alarm); }
.compare__table tbody tr:last-child .cmp--us { box-shadow: inset 2px 0 0 var(--alarm), inset -2px -2px 0 var(--alarm); }
.compare__footer { margin-top: var(--gap-3); color: var(--ink-2); max-width: 62ch; }

/* ---- 10. FAQ ----------------------------------------------------------
   Native disclosure, no cards, no surface recipe. The answer is always in
   the HTML whether or not the item is open, which is what makes it the
   highest-yield retrieval surface on the page. */

.faq { background: var(--bg); }
.faq__inner { max-width: 78ch; }
.faq__hint { color: var(--ink-2); margin: var(--s-5) 0 var(--gap-3); }
.faq__list { border-top: var(--border-hard); }
.faq__item { border-bottom: var(--border-1); }
.faq__q {
  display: flex;
  align-items: baseline;
  gap: var(--s-5);
  padding: var(--s-6) 0;
  cursor: pointer;
  font-size: var(--fs-h4);
  font-weight: 500;
  list-style: none;
}
.faq__q::-webkit-details-marker { display: none; }
.faq__q::before {
  content: "+";
  font-family: var(--ff-mono);
  font-size: var(--fs-h4);
  color: var(--ink-2);
  flex: 0 0 auto;
  width: 1ch;
}
.faq__item[open] .faq__q::before { content: "-"; }
.faq__q:focus-visible { outline: 3px solid var(--ink); outline-offset: -3px; }
.faq__a { padding: 0 0 var(--s-6) calc(1ch + var(--s-5)); }
.faq__a p { color: var(--ink-2); max-width: 66ch; }

/* ---- 404 --------------------------------------------------------------
   Deliberately plain. Its job is to get someone to a real page fast. */

.notfound { max-width: 62ch; }
.notfound h1 { margin: var(--s-5) 0 var(--gap-3); font-size: var(--fs-h2); }
.notfound__body { color: var(--ink-2); }
.notfound__body + .notfound__body { margin-top: var(--s-4); }
.notfound__label { color: var(--ink-2); margin: var(--pad-card-lg) 0 var(--s-4); }
.notfound__links { display: grid; gap: var(--s-3); border-top: var(--border-hard); padding-top: var(--s-5); }
.notfound__links a { font-size: var(--fs-h4); }
.notfound__note {
  margin-top: var(--pad-card-lg);
  padding-top: var(--s-5);
  border-top: var(--border-1);
  color: var(--ink-2);
}

/* ==========================================================================
   The interactive demo.

   Two hard rules encoded here:
   1. With no JavaScript the section is fully usable: the radio :checked
      selector drives which persona track shows, and the compare checkbox
      swaps in the matrix. JS only hides lines and reveals them on a timer.
   2. CLS = 0 by construction: every pane has a reserved height before
      anything plays, so revealing a line or switching persona shifts nothing.
   ========================================================================== */

.demo-section { background: var(--bg); }
/* No content-visibility here. It was set to auto with contain-intrinsic-size
   900px against a real height of ~2700px, which made every deep link that
   targets a section BELOW the demo (#report, #limits, #compare, #faq, all of
   them linked from the footer and the 404) land about 2000px short on a phone:
   the browser scrolls using the 900px estimate, then the section expands and
   carries the target away. Measured: 2011px error with it, 353px without.
   It was also not buying anything. LCP is 1344-1420ms without it and 1460ms
   with it, because the H1 sits above the demo and never waited on it. */

.demo__h2 { margin: var(--s-4) 0 var(--s-5); max-width: 20ch; }
.demo__intro { color: var(--ink-2); font-size: var(--fs-h4); line-height: 1.5; max-width: 54ch; }

.demo { margin-top: var(--pad-card-lg); }

/* ---- persona picker -------------------------------------------------- */

.demo-personas {
  border: 0;
  margin: 0 0 var(--gap-3);
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--s-2);
  align-items: center;
}
.demo-personas legend { float: left; width: 100%; margin-bottom: var(--s-3); color: var(--ink-2); }

.demo-persona {
  display: inline-flex;
  align-items: center;
  min-height: 44px;                /* was 43px: one pixel under the target size */
  cursor: pointer;
  padding: var(--s-2) var(--s-5);
  border: var(--border-hard);
  border-radius: var(--r-pill);
  background: var(--bg);
  font-family: var(--ff-mono);
  font-size: var(--fs-mono-report);
  font-weight: 600;
  transition: transform var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
}
.demo-persona__chip { pointer-events: none; }
.demo-persona-input:checked + .demo-persona { background: var(--go); }
.demo-persona-input:focus-visible + .demo-persona {
  outline: 3px solid var(--ink);
  outline-offset: 3px;
}
.demo-persona:hover { transform: translateY(-1px); }

/* ---- controls -------------------------------------------------------- */

.demo-controls {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s-3) var(--gap-3);
  margin-bottom: var(--gap-3);
}
/* The single control for the run. It is primary at rest, because at rest the
   only thing worth doing is starting the test, and quiet once the run owns the
   screen. Same element throughout, so nothing moves between states. */
.demo-play {
  border: var(--border-hard);
  background: var(--go);
  border-radius: var(--r-pill);
  padding: var(--s-2) var(--s-6);
  min-height: 44px;
  font-family: var(--ff-mono);
  font-size: var(--fs-mono-label);
  font-weight: 700;
  letter-spacing: var(--tr-mono-label);
  text-transform: uppercase;
  cursor: pointer;
  transition: transform var(--dur-1) var(--ease), background var(--dur-1) var(--ease);
}
.demo-play:hover { transform: translateY(-1px); }
.demo-play:active { transform: translateY(0); }
.demo[data-state="running"] .demo-play,
.demo[data-state="complete"] .demo-play { background: var(--bg); }
.demo-compare {
  display: inline-flex;
  align-items: center;
  min-height: 44px;                /* the label is the hit area, not the 18px box */
  gap: var(--s-2);
  cursor: pointer;
  font-size: var(--fs-small);
  color: var(--ink);
}
.demo-compare::before {
  content: "";
  width: 18px; height: 18px;
  border: var(--border-hard);
  border-radius: 6px;
  background: var(--bg);
}
.demo-compare-input:checked + .demo-compare::before {
  background: var(--go);
}
.demo-compare-input:focus-visible + .demo-compare { outline: 3px solid var(--ink); outline-offset: 3px; }
.demo-skip { font-size: var(--fs-small); color: var(--ink-2); }

/* ---- stage: 3-column grid on desktop, stacked on mobile -------------- */

/* Breakpoints are set by the content, not by device names. The phone is a
   fixed 390px object, so the only question at each width is whether 390px plus
   a readable stream fits side by side.

   below 860px  one column, phone centred at its natural size
   860 and up   phone 390 + stream in the remainder. 860 is the width at which
                the stream still clears ~380px beside a 390px phone, which is
                about 45 monospace characters. The old layout only split at
                1024, which stranded the phone in a 750px column with 360px of
                dead air beside it; splitting earlier than this instead squeezes
                the findings into an unreadable 230px ribbon. */
.demo-stage {
  display: grid;
  gap: var(--gap-4);
  grid-template-columns: 1fr;
  justify-items: center;           /* centres the phone on one column */
  min-height: 460px;               /* reserved, so persona swap shifts nothing */
}
.demo-tracks { display: contents; }
.demo-runner { width: 100%; }

@media (min-width: 860px) {
  .demo-stage {
    grid-template-columns: 390px minmax(0, 1fr);
    gap: clamp(20px, 3vw, 44px);
    align-items: start;
    justify-items: stretch;
  }
  /* Sticky only where the phone is short enough to leave the stream usable
     room. On one column it is most of the viewport, so pinning it would bury
     the findings it is supposed to explain. */
  .demo-device {
    grid-row: 1 / span 2;
    position: sticky;
    top: calc(var(--header-h) + var(--s-4));
  }
  .demo-track[data-persona] { grid-column: 2; }
}

/* Track visibility is pure CSS. The radios sit inside a <fieldset>, so a
   sibling combinator cannot reach .demo-stage; :has() on the container can,
   independent of nesting. Baseline since 2023, and crawlers do not run CSS
   anyway, so a no-JS human on a 2026 browser always has it. */
.demo-track { display: none; }
.demo:has(#p14:checked) .demo-track[data-persona="14"],
.demo:has(#p35:checked) .demo-track[data-persona="35"],
.demo:has(#p68:checked) .demo-track[data-persona="68"] { display: grid; gap: var(--gap-4); }

@media (min-width: 860px) {
  .demo:has(#p14:checked) .demo-track[data-persona="14"],
  .demo:has(#p35:checked) .demo-track[data-persona="35"],
  .demo:has(#p68:checked) .demo-track[data-persona="68"] {
    grid-column: 2;
    align-content: start;
  }
}

/* Compare view: hide the stage, show the matrix. No JS. */
.demo-matrix-wrap { display: none; margin-top: var(--gap-3); }
.demo:has(#cotesty-compare:checked) .demo-stage { display: none; }
.demo:has(#cotesty-compare:checked) .demo-matrix-wrap { display: block; }

/* ---- runner: meter + stream + resting state -------------------------- */

.demo-runner { display: grid; gap: var(--s-3); align-content: start; }

.demo-meter {
  display: flex;
  align-items: center;
  gap: var(--s-3);
  flex-wrap: wrap;
  min-height: 44px;                /* reserved: the button appears only with JS */
  padding-bottom: var(--s-2);
  border-bottom: var(--border-1);
}

/* Solid at rest, pulsing while the run is live. The pulse is the only thing on
   the page that says "this is happening now", so it is opacity on a 10px dot
   and nothing more. */
.demo-meter__dot {
  width: 10px; height: 10px;
  border-radius: 50%;
  background: var(--ink-mute);
  flex: 0 0 auto;
}
.demo[data-state="running"] .demo-meter__dot {
  background: var(--alarm);
  animation: demo-pulse 1.4s var(--ease-soft) infinite;
}
.demo[data-state="complete"] .demo-meter__dot { background: var(--go); }

@keyframes demo-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.25; }
}

.demo-meter__status { color: var(--ink); }
.demo-meter__count { color: var(--ink-2); font-variant-numeric: tabular-nums; }
.demo-meter .demo-play { margin-left: auto; }

.demo-streams { position: relative; }

.demo-idle {
  position: absolute;
  inset: 0;
  display: grid;
  align-content: center;
  gap: var(--s-3);
  padding: var(--pad-card-md);
  background: var(--surface);
  border: var(--border-1);
  border-radius: var(--r-2);
  text-align: left;
}
.demo-idle[hidden] { display: none; }
.demo-idle__head { font-size: var(--fs-h3); letter-spacing: var(--tr-h3); max-width: 22ch; }
.demo-idle__body { color: var(--ink-2); max-width: 46ch; }
.demo-idle__meta { color: var(--ink-2); }

/* ---- the loading row ------------------------------------------------- */

.demo-skel { display: none; }
.demo[data-state="running"] .demo-skel {
  display: grid;
  gap: var(--s-2);
  padding-left: var(--s-5);
}
.demo-skel__bar {
  height: 10px;
  border-radius: 6px;
  background: var(--rule);
  animation: demo-pulse 1.4s var(--ease-soft) infinite;
}
.demo-skel__bar:nth-child(1) { width: 72%; }
.demo-skel__bar:nth-child(2) { width: 48%; animation-delay: 0.2s; }
.demo-skel__label { color: var(--ink-mute); }

@media (prefers-reduced-motion: reduce) {
  .demo-meter__dot,
  .demo-skel__bar { animation: none; }
}

/* ---- the stream ------------------------------------------------------ */
/* One list per persona, in time order. Narration is quiet, findings are cards,
   both in the same column, because a finding only means something next to the
   action that produced it. */

.demo-stream__head { color: var(--ink-2); margin-bottom: var(--s-3); }

.demo-stream {
  display: flex;
  flex-direction: column;
  gap: var(--s-3);
}

/* Bounded only when the script is driving it, where it behaves as a console
   that scrolls itself. Left unbounded without JavaScript, so a reader who
   never gets the timed run gets all 47 lines laid out in the document instead
   of a 47-line list crammed into a 500px scroller they have to discover.
   The bound matters: unbounded, the scripted section grows past two and a half
   viewports, and the autostart observer below can never see enough of it. */
.demo.is-scripted .demo-stream {
  height: clamp(320px, 52vh, 560px);
  overflow-y: auto;
  overscroll-behavior: contain;
}

/* ---- narration lines ------------------------------------------------- */

.demo-line {
  font-family: var(--ff-mono);
  font-size: var(--fs-mono-report);
  line-height: 1.55;
  color: var(--ink);
}
.demo-line time {
  color: var(--ink-mute);
  font-variant-numeric: tabular-nums;
}
.demo-line--act,
.demo-line--end { padding-left: var(--s-5); }
.demo-line--act .demo-line__text,
.demo-line--end .demo-line__text { color: var(--ink-2); }
.demo-line--act time,
.demo-line--end time { margin-right: var(--s-2); }
.demo-line--end { font-weight: 600; }
.demo-line--end .demo-line__text { color: var(--ink); }

/* ---- finding cards, inline in the stream ----------------------------- */

.fnd {
  border: var(--border-1);
  border-radius: var(--r-1);
  padding: var(--pad-card-xs) var(--s-5);
  background: var(--bg);
  border-left-width: 6px;
}
.fnd--crit { border-left-color: var(--alarm); }
.fnd--high { border-left-color: var(--alarm); border-left-style: solid; }
.fnd--med  { border-left-color: var(--flag); }
.fnd--low  { border-left-color: var(--flag-2); }
.fnd__head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s-2);
  margin-bottom: var(--s-2);
}
.fnd__code { font-family: var(--ff-mono); font-size: var(--fs-mono-label); color: var(--ink-mute); }
.fnd__text { display: block; color: var(--ink); max-width: none; }

/* The final tally, held back until the run has actually produced it. Visible
   from the start without JavaScript, where there is no run to spoil, but
   hidden while a scripted run is in flight: showing "9 critical, 3 high" next
   to a meter reading "4/12 checks" states the answer before the demo has
   finished arguing it. Height is reserved either way, so revealing it moves
   nothing. */
.demo-score {
  min-height: 44px;
  color: var(--ink-2);
  align-content: center;
}
.demo.is-scripted .demo-score { visibility: hidden; }
.demo.is-scripted[data-state="complete"] .demo-score { visibility: visible; }

/* ---- device: inert flawed sample app --------------------------------- */

.demo-device {
  position: relative;
  /* Exactly 390px where it fits, so the 32x28px chips render at their true
     size and the demo's measurements are not a lie. It shrinks below 390 only
     on narrower screens, and the probe overlays are anchored to elements, so
     they follow. aspect-ratio 390/844 is the real iPhone 12 the agent log
     names, which is why screens 2 to 4 carry an order summary: without it the
     taller frame would just be more empty space. */
  width: min(390px, 100%);
  aspect-ratio: 390 / 844;
  border: 0;
  border-radius: var(--r-3);
  background: var(--surface);
  overflow: hidden;
  /* The dark iPhone bezel is a solid box-shadow ring drawn OUTSIDE the 390x844
     box, so the device stays exactly true size and every internal measurement
     plus the fold geometry are untouched by the frame. Two stacked rings read
     as a titanium edge; the soft recipe underneath lifts it off the page. */
  box-shadow:
    0 0 0 6px var(--ink),
    0 0 0 7.5px var(--ink-2),
    var(--shadow-3);
}

/* An iOS-style status bar so the frame reads as a real phone, not a floating
   card. The Dynamic Island is absolutely centred, so the time and the
   signal/wifi/battery icons keep the two edges of the SAME 34px band the browser
   bar used to occupy. The screen inset below stays 34px, so the fold geometry
   verify-fold.mjs measures is unchanged. */
.dv-chrome {
  position: absolute;
  top: 0; left: 0; right: 0;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 0 16px;
  background: var(--bg);
  z-index: 3;
}
.dv-status__time { font-size: 12px; font-weight: 700; color: var(--ink); letter-spacing: 0.02em; }
.dv-status__icons { display: inline-flex; align-items: center; gap: 5px; }
.dv-ico { display: block; fill: var(--ink); color: var(--ink); }
.dv-island {
  position: absolute;
  top: 8px; left: 50%;
  transform: translateX(-50%);
  width: 76px; height: 20px;
  border-radius: 100px;
  background: var(--ink);
  display: grid;
  place-items: center;
}
.dv-island__cam { width: 6px; height: 6px; border-radius: 50%; background: rgba(255, 255, 255, 0.16); }

/* The home indicator, pinned bottom centre. Deliberately faint so it never
   competes with the flaw evidence that legitimately sits low on the screen. */
.dv-home {
  position: absolute;
  bottom: 6px; left: 50%;
  transform: translateX(-50%);
  width: 36%; height: 4px;
  border-radius: 100px;
  background: var(--ink);
  opacity: 0.22;
  z-index: 4;
}

/* Drop-in push notifications. demo.js builds one per finding as it is hit, from
   the finding line already in the DOM, so no notification text ships in the HTML
   and no reconciliation count is touched. Absolutely positioned (out of flow) so
   they never shift layout: CLS stays zero. Animated with transform + opacity
   only, per the house rule. */
.dv-toasts {
  position: absolute;
  top: 42px; left: 10px; right: 10px;   /* below the status bar, over the app */
  z-index: 5;
  display: flex;
  flex-direction: column;
  gap: 8px;
  pointer-events: none;
}
.dv-toast {
  display: none;                   /* one shows at a time, per active screen */
  grid-template-columns: auto 1fr;
  grid-template-rows: auto auto;
  column-gap: 10px;
  padding: 10px 12px;
  border-radius: 8px;
  background: var(--bg);
  border: 1px solid var(--rule);
  box-shadow: var(--shadow-2);
  font-family: var(--ff-display);
  will-change: transform, opacity;
  animation: dv-toast-in 320ms var(--ease) both;
}
/* No JS: the cart (screen 1) is the shown screen, so is its notification. */
.dv-toast[data-screen="1"] { display: grid; }
/* With the script running, paintStage tags the layer with the active screen;
   show only that screen's notification, so the banner swaps as the run moves. */
.demo.is-scripted .dv-toast { display: none; }
.demo.is-scripted .dv-toasts[data-screen="1"] .dv-toast[data-screen="1"],
.demo.is-scripted .dv-toasts[data-screen="2"] .dv-toast[data-screen="2"],
.demo.is-scripted .dv-toasts[data-screen="3"] .dv-toast[data-screen="3"],
.demo.is-scripted .dv-toasts[data-screen="4"] .dv-toast[data-screen="4"] { display: grid; }
.dv-toast__icon {
  grid-column: 1; grid-row: 1 / span 2;
  width: 26px; height: 26px;
  border-radius: 8px;
  background: var(--go);
  border: 1px solid var(--rule-hard);
  display: grid; place-items: center;
  align-self: start;
}
.dv-toast__icon svg { display: block; width: 15px; height: 15px; color: var(--ink); }
.dv-toast__head {
  grid-column: 2; grid-row: 1;
  display: flex; align-items: center; gap: 7px;
  font-size: 10px; color: var(--ink-2);
}
.dv-toast__app { font-weight: 800; color: var(--ink); font-size: 12px; letter-spacing: -0.01em; }
.dv-toast__time { margin-left: auto; font-variant-numeric: tabular-nums; }
.dv-toast__body {
  grid-column: 2; grid-row: 2;
  margin-top: 3px;
  font-size: 12px; line-height: 1.35; color: var(--ink);
  display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden;
}
.dv-toast .sev { margin-bottom: 0; }

@keyframes dv-toast-in {
  from { opacity: 0; transform: translateY(-140%); }
  to   { opacity: 1; transform: translateY(0); }
}
@media (prefers-reduced-motion: reduce) {
  .dv-toast { animation: none; }
}

.dv-screen {
  position: absolute;
  inset: 34px 0 0;                 /* clears the chrome bar */
  padding: 14px 20px;
  font-family: var(--ff-display);  /* a real shopping app, not a wireframe */
  font-size: 13px;
  letter-spacing: -0.01em;
  overflow: hidden;
  opacity: 0;
}
.dv-screen--1 { opacity: 1; }
.demo.is-scripted .dv-screen { transition: opacity 650ms var(--ease); }
.demo.is-scripted .dv-screen[data-active] { opacity: 1; }
.demo.is-scripted .dv-screen:not([data-active]) { opacity: 0; }

.dv-top { display: flex; justify-content: space-between; align-items: baseline; }
.dv-top b { font-family: var(--ff-display); font-size: 18px; }
.dv-hold { font-size: 11px; color: #7C766B; }          /* F08: 11px silent timer */
.dv-tagline { font-size: 10px; color: #ADA69B; margin-top: 2px; } /* low contrast */
/* The product photo. The tint stays as the box background, so if the image is
   ever missing the store still reads. The height is fixed, so the image (cover)
   cannot shift layout when it decodes. Sized so the CTA and the ID-number
   helper genuinely fall past the bottom of the 844px viewport, which is the
   fold claim the copy makes. */
.dv-photo {
  margin-top: 12px;
  height: 455px;                   /* a real 4:5 product shot at 354px wide */
  border: 1px solid var(--rule);
  border-radius: 8px;
  background: color-mix(in srgb, var(--flag-2) 22%, var(--surface));
  position: relative;
  overflow: hidden;
}
.dv-photo__img { width: 100%; height: 100%; object-fit: cover; display: block; }
.dv-blurb { font-size: 10px; color: #7C766B; margin-top: 12px; line-height: 1.5; }
.dv-also__head { font-size: 11px; margin-top: 16px; }
.dv-also { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 8px; }
.dv-also__item { font-size: 9px; color: var(--ink-2); }
.dv-also__tile {
  display: block;
  aspect-ratio: 1;
  margin-bottom: 4px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  overflow: hidden;
  background: color-mix(in srgb, var(--flag-2) 14%, var(--surface));
}
.dv-also__img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* The fold: the real bottom edge of the phone viewport. Everything the store
   put below this needs a scroll to reach, and both F02 and F09 depend on that
   being true rather than annotated. */
.dv-fold {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  border-top: 1px dashed var(--alarm);
  font-size: 8px;
  color: var(--alarm);
  padding: 2px 18px 4px;
  text-align: right;
  background: var(--surface);
}

.dv-card { margin-top: 14px; }
.dv-item { font-size: 13px; }
.dv-price { font-weight: 700; margin: 2px 0 10px; }
.dv-size-label { font-size: 11px; color: #7C766B; }
.dv-sizes { display: flex; gap: 8px; margin-top: 6px; flex-wrap: wrap; }
.dv-size {                                              /* F01: 32x28. F04: div */
  width: 32px; height: 28px;
  display: grid; place-items: center;
  border: 1px solid #1D1B16;
  font-size: 12px;
}
.dv-size[data-selected] { background: var(--go); }      /* colour-only cue */
.dv-cta {
  margin-top: 22px;
  height: 52px; display: grid; place-items: center;
  background: var(--go); color: var(--ink);
  border-radius: 100px; font-weight: 700; font-size: 15px;
  letter-spacing: -0.01em;
}
.dv-cta--fake { background: #E6E1D6; color: #FFFFFF; }  /* F07: 1.2:1 fake disabled */
.dv-helper { font-size: 11px; color: #ADA69B; margin-top: 12px; } /* below fold, low contrast */

.dv-title {
  font-size: 24px; font-weight: 800; letter-spacing: -0.03em;
  line-height: 1.05; margin: 6px 0 22px;
}
.dv-form { display: grid; gap: 4px; }
.dv-label { font-size: 11px; color: #ADA69B; margin-top: 14px; } /* F02: 2.3:1 */
.dv-label--flag { color: var(--alarm); font-weight: 600; }       /* audit highlight */
/* Real, airy rounded inputs. The planted flaws (no autofill, type=text, the
   unlabelled CVV) are untouched: only the chrome around them got realistic. */
.dv-input {
  height: 46px; border: 1px solid var(--rule); border-radius: 8px;
  padding: 0 14px; font-family: var(--ff-display); font-size: 14px;
  background: color-mix(in srgb, var(--surface) 40%, var(--bg));
  color: var(--ink);
}
.dv-input::placeholder { font-family: var(--ff-mono); color: #ADA69B; }
.dv-input[aria-invalid] {                                /* F06: border only */
  border-color: var(--alarm);
  background: color-mix(in srgb, var(--alarm) 7%, var(--bg));
}
/* Flagged field wrapper: the circled "!" sits inside the input, the measurement
   pill hangs under it (see .dv-probe below), matching a native invalid state. */
.dv-inputwrap { position: relative; display: block; margin-bottom: 18px; }
.dv-inputwrap .dv-input { width: 100%; padding-right: 44px; }
.dv-warn {
  position: absolute; right: 14px; top: 14px;
  color: var(--alarm); pointer-events: none;
}
.dv-consent {
  display: flex; gap: 10px; align-items: flex-start;
  margin-top: 20px; font-size: 11px; line-height: 1.5; color: #7C766B;
  font-family: var(--ff-mono);           /* a technical texture, per the ref */
}
.dv-consent input { width: 20px; height: 20px; flex: 0 0 auto; }
.dv-payrow { display: grid; grid-template-columns: 1fr 1fr; gap: 6px; margin-top: 6px; }
/* Payment card field: a generic card mark sits at the right edge for realism.
   It changes no measurement the probe cites: the number is still unmasked, the
   label is still there. */
.dv-cardfield { position: relative; display: block; }
.dv-cardfield .dv-input { width: 100%; padding-right: 42px; }
.dv-cardmark { position: absolute; right: 8px; top: 50%; transform: translateY(-50%); color: var(--ink-2); }
/* Extras step controls. */
.dv-xhead { font-size: 11px; margin-top: 16px; }
.dv-opts { display: grid; gap: 6px; margin-top: 6px; }
.dv-opt {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 10px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  font-size: 11px;
}
.dv-opt[data-selected] { border-color: var(--rule-hard); }
.dv-opt__dot {
  width: 12px; height: 12px;
  border-radius: 50%;
  border: 1px solid var(--rule-hard);
  flex: 0 0 auto;
}
.dv-opt[data-selected] .dv-opt__dot { background: var(--go); }
.dv-opt__price { margin-left: auto; color: var(--ink-2); }
.dv-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
.dv-chip {
  padding: 8px 10px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  font-size: 11px;
}
.dv-chip[data-selected] { background: var(--go); border-color: var(--rule-hard); }
.dv-note {
  margin-top: 6px;
  padding: 10px;
  height: 110px;
  border: 1px solid var(--rule);
  border-radius: 8px;
  font-size: 11px;
  color: #ADA69B;
}

/* Straddles the fold by construction. The extras above it are sized so this
   row starts at the very bottom edge of the 844px viewport: visible enough
   that the demo can point at it, low enough that F09's claim, that Hüsamettin
   never scrolls to it, is literally true on the screen. Verified by
   scripts/verify-fold.mjs rather than trusted. */
.dv-upsell { display: flex; gap: 6px; align-items: flex-start; font-size: 12px; color: #EECB69; margin: 80px 0 10px; }
.dv-total { font-weight: 700; }
.dv-recap__head { font-size: 11px; margin-top: 20px; }
.dv-recap {
  display: grid;
  gap: 3px;
  margin-top: 6px;
  font-size: 10px;
  color: var(--ink-2);
}
/* Pinned to the bottom of the screen. The selector is deliberately more
   specific than .dv-mark, which sets position: relative to anchor a probe and
   would otherwise cancel this one and drop the line back into the flow. */
.dv-screen > .dv-devtools {
  position: absolute; left: 18px; right: 18px; bottom: 14px;
}
/* The clipping lives on the inner code, not the wrapper. When overflow:hidden
   sat on the same element that anchored the probe, it clipped the probe's own
   dashed outline (inset: -5px) out of existence. */
.dv-devtools__code {
  display: block;
  font-family: var(--ff-mono);          /* it is literal code, keep it mono */
  font-size: 9px; color: var(--alarm);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

/* What the shopper is actually being charged. The gift-wrap line is the
   pre-checked upsell from the previous screen, which is the whole of F09: by
   the time it is visible here the decision was made three screens ago. */
.dv-bill { margin-top: 18px; border-top: 1px solid var(--rule); padding-top: 10px; }
.dv-bill__row {
  display: flex;
  justify-content: space-between;
  gap: var(--s-2);
  font-size: 10px;
  color: var(--ink-2);
  padding: 3px 0;
}
.dv-bill__row--total {
  font-size: 12px;
  color: var(--ink);
  font-weight: 700;
  border-top: 1px solid var(--rule);
  margin-top: 6px;
  padding-top: 8px;
}

/* Running-total band pinned to the bottom of the shorter screens. */
.dv-summary {
  position: absolute;
  left: 0; right: 0; bottom: 0;
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px 20px calc(14px + env(safe-area-inset-bottom, 0px));
  border-top: 1px solid var(--rule);
  background: var(--bg);
  font-size: 12px;
}
.dv-summary__thumb {
  flex: 0 0 auto;
  width: 40px; height: 40px;
  border-radius: 8px;
  overflow: hidden;
  background: color-mix(in srgb, var(--flag-2) 18%, var(--surface));
}
.dv-summary__img { width: 100%; height: 100%; object-fit: cover; display: block; }
.dv-summary__item { color: var(--ink-2); flex: 1 1 auto; }
.dv-summary__total {
  flex: 0 0 auto; font-weight: 800; color: var(--ink);
  font-size: 16px; letter-spacing: -0.02em;
}

/* ---- probes: what the agent measured, on the element it measured ----- */

.dv-mark { position: relative; }

/* The probe is now a quiet positioning wrapper, not a loud dashed box. The
   flagged element carries its own red affordance (input border + circled "!"),
   and the measurement rides in a clean red pill anchored just under it. */
.dv-probe {
  position: absolute;
  inset: -4px;
  border-radius: 8px;
  pointer-events: none;
}
/* The measurement pill. A small solid-red chip like a native inline error,
   replacing the black uppercase chip that read as an engineering annotation. */
.dv-probe__tag {
  position: absolute;
  left: -1px;
  top: 100%;
  margin-top: 5px;
  padding: 3px 8px;
  background: var(--alarm);
  color: var(--bg);
  border-radius: 6px;
  font-family: var(--ff-display);
  font-size: 10px;
  font-weight: 600;
  letter-spacing: 0;
  text-transform: none;
  white-space: nowrap;
  box-shadow: var(--shadow-2);
}
/* Probes near an edge flip their pill inward so it cannot be clipped by the
   frame's overflow: hidden. */
.dv-probe--right .dv-probe__tag { left: auto; right: -1px; }
/* Pill above the element, for a probe sitting at the fold where the space below
   it is clipped away. */
.dv-probe--up .dv-probe__tag { top: auto; bottom: 100%; margin: 0 0 5px; }
/* The devtools probe sits at the very bottom, so its pill goes above it. */
.dv-devtools .dv-probe__tag { top: auto; bottom: 100%; margin: 0 0 5px; }

/* At rest the probes are quiet outlines. During a run the active screen's
   probes light up, which is the moment that reads as "measuring this now". */
.demo.is-scripted .dv-probe { opacity: 0.35; }
.demo.is-scripted .dv-screen[data-active] .dv-probe { opacity: 1; }

/* The scan ring: a pulse centred on the element under test. Replaces the old
   free-floating cursor that followed a canned path unrelated to any finding.
   Because it lives inside the probe, it is always exactly on the measured
   element at any scale, with no coordinate maths. */
.dv-scan {
  position: absolute;
  left: 50%; top: 50%;
  width: 26px; height: 26px;
  margin: -13px 0 0 -13px;
  border-radius: 50%;
  border: 2px solid var(--alarm);
  opacity: 0;
}
.demo.is-scripted .dv-screen[data-active] .dv-scan {
  animation: demo-scan 1.6s var(--ease-soft) infinite;
}
@keyframes demo-scan {
  0%   { opacity: 0.9; transform: scale(0.5); }
  70%  { opacity: 0;   transform: scale(1.6); }
  100% { opacity: 0;   transform: scale(1.6); }
}

@media (prefers-reduced-motion: reduce) {
  .dv-scan { display: none; }
}

/* ---- matrix ---------------------------------------------------------- */

.demo-matrix { width: 100%; border-collapse: collapse; min-width: 720px; }
.demo-matrix caption {
  text-align: left; color: var(--ink-2);
  font-size: var(--fs-small); margin-bottom: var(--s-3);
}
.demo-matrix th, .demo-matrix td {
  border: 1px solid var(--rule-hard);
  padding: var(--s-3);
  vertical-align: top;
  text-align: left;
}
.demo-matrix thead th { background: var(--surface); font-family: var(--ff-display); }
.mx-age { font-size: 1.4em; font-weight: 700; }
.mx-code { display: block; color: var(--ink-mute); }
.mx-flaw { display: block; font-size: var(--fs-small); }
.mx-cell { font-size: var(--fs-mono-report); font-family: var(--ff-mono); }
.mx-cell--none { color: var(--ink-mute); background: var(--surface); }
.mx-sev { margin-bottom: var(--s-2); }
.mx-text { display: block; margin-top: var(--s-2); }

/* ---- handoff / scorecard --------------------------------------------- */

.demo-handoff {
  margin-top: var(--gap-3);
  border-radius: var(--r-1);
}
.demo-handoff__head { color: color-mix(in srgb, var(--surface) 72%, var(--ink)); margin-bottom: var(--s-4); }
.demo-handoff__score { width: 100%; border-collapse: collapse; }
.demo-handoff__score th {
  text-align: left; padding: var(--s-2) 0;
  font-family: var(--ff-display); font-weight: 500;
  color: var(--surface);
  white-space: nowrap; padding-right: var(--gap-3);
}
.demo-handoff__score td { padding: var(--s-2) 0; }
.hs-cell {
  display: inline-block;
  margin-right: var(--gap-3);
  font-family: var(--ff-mono);
  font-size: var(--fs-mono-report);
  color: color-mix(in srgb, var(--surface) 82%, var(--ink));
}
.hs-cell b { color: var(--surface); }
.demo-handoff__meta { margin-top: var(--s-5); color: color-mix(in srgb, var(--surface) 82%, var(--ink)); }
.demo-handoff__meta b { color: var(--go); }
.demo-handoff__callout {
  margin-top: var(--s-4);
  padding-top: var(--s-4);
  border-top: 1px solid color-mix(in srgb, var(--surface) 30%, var(--ink));
  color: color-mix(in srgb, var(--surface) 78%, var(--ink));
  max-width: 74ch;
}
.demo-handoff__line {
  margin-top: var(--s-5);
  color: var(--surface);
  font-size: var(--fs-h4);
  max-width: 46ch;
}

/* ---- no-JS / scripted reveal ---------------------------------------- */
/* DEFAULT = everything visible. JS opts in to hiding via .is-scripted,
   added by demo.js at the end of body. If the script never runs, the page
   degrades to fully visible rather than blank. */

.demo-line { opacity: 1; transform: none; }

/* display:none, not opacity:0, for lines that have not played yet.
   Transparent lines still occupy their full height, so the stream was already
   as tall as all 47 items on the first frame. It then ran scrollTop =
   scrollHeight and parked at the bottom, where every line is invisible: the
   run played correctly into a box that looked permanently empty. This is what
   made the section read as two dead rectangles. */
.demo.is-scripted .demo-line { display: none; }
.demo.is-scripted .demo-line[data-played] {
  display: block;
  /* An animation rather than a transition: a transition cannot run on an
     element arriving from display:none, and animating opacity and transform
     keeps it off the compositor's layout path. */
  animation: demo-line-in 350ms var(--ease) both;
}

@keyframes demo-line-in {
  from { opacity: 0; transform: translate3d(0, 4px, 0); }
  to   { opacity: 1; transform: none; }
}

@media (prefers-reduced-motion: reduce) {
  .demo.is-scripted .dv-screen { transition: none; }
  .demo-cursor { display: none; }
}

/* motion.css — built in a later phase */

