/* ==========================================================================
   QualityLabs — shared stylesheet
   --------------------------------------------------------------------------
   Token-first. Every value below the TOKENS block composes from it.
   No raw px/rem/colour literals past that point (hairlines excepted).
   Zero dependencies, zero third-party requests.
   ========================================================================== */

/* ==========================================================================
   1. TOKENS
   ========================================================================== */

:root {
  /* ---- Type scale — major third (1.25) at 360px → (1.333) at 1280px ---- */
  --step--1: clamp(0.833rem, 0.80rem + 0.15vw, 0.90rem);
  --step-0:  clamp(1.00rem,  0.96rem + 0.19vw, 1.125rem);
  --step-1:  clamp(1.25rem,  1.17rem + 0.36vw, 1.50rem);
  --step-2:  clamp(1.56rem,  1.41rem + 0.66vw, 2.00rem);
  --step-3:  clamp(1.95rem,  1.68rem + 1.19vw, 2.67rem);
  --step-4:  clamp(2.44rem,  1.99rem + 1.99vw, 3.55rem);
  --step-5:  clamp(3.05rem,  2.31rem + 3.27vw, 4.74rem);

  /* ---- Spacing ramp — 1.5 ratio, fluid at the larger end ---- */
  --space-3xs: 0.25rem;
  --space-2xs: 0.5rem;
  --space-xs:  0.75rem;
  --space-s:   1rem;
  --space-m:   1.5rem;
  --space-l:   2.25rem;
  --space-xl:  clamp(3rem, 2.5rem + 2vw, 4rem);
  --space-2xl: clamp(4rem, 3rem + 4vw, 6.5rem);
  --space-3xl: clamp(5.5rem, 4rem + 6vw, 9rem);

  /* ---- Measure ---- */
  --measure:       66ch;
  --measure-tight: 54ch;
  --container:     70rem;
  --container-narrow: 44rem;

  /* ---- Colour — light ---- */
  color-scheme: light;
  --paper:      #fbfaf9;
  --surface:    #ffffff;
  --surface-alt:#f4f2ef;
  --ink:        #16181c;
  --ink-soft:   #474d55;
  /* Darkened from #696f78, which measured 4.43:1 on --accent-wash and so
     missed AA on the CTA panel. Worst case is now 4.70:1, on --accent-wash. */
  --ink-faint:  #656b74;
  --accent:     #0b6b4f;
  --accent-deep:#084f3b;
  --accent-ink: #ffffff;
  --accent-wash:#e8f2ee;
  --rule:       #e3e0db;
  --rule-strong:#cfcbc4;

  /* ---- Non-colour primitives ---- */
  --radius:    6px;
  --radius-lg: 10px;
  --header-h:  3.3rem;

  /* Syntax tinting for the hero comparison. Verified AA on --surface. */
  --code-comment: #5f6673;
  --code-key:     #6f42c1;
  --code-str:     #0a6e4a;

  /* Chart categorical pair. Validated with the dataviz six-checks validator:
     all pass, worst adjacent CVD dE 9.4 (deutan), normal-vision dE 19.2.
     Deliberately NOT --accent: the brand emerald fails the chart chroma
     floor at 0.094, so charts use a fractionally brighter step. */
  --chart-before: #a1520a;
  --chart-after:  #0f7a5a;
  --chart-grid:   #e3e0db;
  --hairline:  1px;
  --ring:      2px;
  --shadow-sm: 0 1px 2px rgb(22 24 28 / 6%), 0 2px 8px rgb(22 24 28 / 4%);

  /* ---- Motion ---- */
  --ease: cubic-bezier(0.2, 0, 0, 1);
  --dur:  160ms;
}

/* Dark values appear twice on purpose. CSS cannot put a media query and an
   attribute selector in the same selector, so one copy answers the system
   preference (unless the reader has explicitly asked for light) and the other
   answers an explicit dark choice. lint.py fails if the two drift apart. */

/* DARK-TOKENS-A */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
        --paper:      #0e1013;
        --surface:    #16191e;
        --surface-alt:#1b1f25;
        --ink:        #eceef1;
        --ink-soft:   #a9b1bb;
        --ink-faint:  #838d99;
        --accent:     #35c491;
        --accent-deep:#5ad6ab;
        --accent-ink: #06251b;
        --accent-wash:#12241e;
        --rule:       #262b32;
        --rule-strong:#39404a;
        --code-comment: #7d8894;
        --code-key:     #c297ff;
        --code-str:     #6ee7b7;

        /* Dark steps chosen against the dark surface, not flipped from light:
           the validator's dark lightness band (L 0.48-0.67) is much narrower
           than light's, so the light pair fails it outright. All checks pass. */
        --chart-before: #c07d2c;
        --chart-after:  #1aa87a;
        --chart-grid:   #262b32;
        --shadow-sm: 0 1px 2px rgb(0 0 0 / 40%), 0 2px 8px rgb(0 0 0 / 25%);
  
  }
}

/* DARK-TOKENS-B */
:root[data-theme="dark"] {
  color-scheme: dark;
      --paper:      #0e1013;
      --surface:    #16191e;
      --surface-alt:#1b1f25;
      --ink:        #eceef1;
      --ink-soft:   #a9b1bb;
      --ink-faint:  #838d99;
      --accent:     #35c491;
      --accent-deep:#5ad6ab;
      --accent-ink: #06251b;
      --accent-wash:#12241e;
      --rule:       #262b32;
      --rule-strong:#39404a;
      --code-comment: #7d8894;
      --code-key:     #c297ff;
      --code-str:     #6ee7b7;

      /* Dark steps chosen against the dark surface, not flipped from light:
         the validator's dark lightness band (L 0.48-0.67) is much narrower
         than light's, so the light pair fails it outright. All checks pass. */
      --chart-before: #c07d2c;
      --chart-after:  #1aa87a;
      --chart-grid:   #262b32;
      --shadow-sm: 0 1px 2px rgb(0 0 0 / 40%), 0 2px 8px rgb(0 0 0 / 25%);
  
}

/* ==========================================================================
   2. BASE
   ========================================================================== */

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

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

@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;
  }
}

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto,
               "Helvetica Neue", Arial, sans-serif;
  font-size: var(--step-0);
  line-height: 1.6;
  font-synthesis-weight: none;
  text-rendering: optimizeLegibility;
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 {
  margin: 0;
  line-height: 1.12;
  letter-spacing: -0.02em;
  font-weight: 650;
  text-wrap: balance;
}

h1 { font-size: var(--step-5); letter-spacing: -0.035em; }
h2 { font-size: var(--step-3); letter-spacing: -0.028em; }
h3 { font-size: var(--step-1); letter-spacing: -0.02em; }
h4 { font-size: var(--step-0); letter-spacing: -0.01em; }

p, ul, ol, blockquote, figure, table { margin: 0; }

p { text-wrap: pretty; }

a {
  color: inherit;
  text-decoration-color: var(--rule-strong);
  text-underline-offset: 0.2em;
  text-decoration-thickness: from-font;
  transition: text-decoration-color var(--dur) var(--ease),
              color var(--dur) var(--ease);
}

a:hover { text-decoration-color: currentColor; }

:where(a, button, summary, [tabindex]):focus-visible {
  outline: var(--ring) solid var(--accent);
  outline-offset: 3px;
  border-radius: var(--radius-lg);
}

:focus:not(:focus-visible) { outline: none; }

code, kbd, pre, samp {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas,
               "Liberation Mono", monospace;
  font-size: 0.9em;
}

code {
  background: var(--surface-alt);
  border: var(--hairline) solid var(--rule);
  border-radius: var(--radius);
  padding: 0.1em 0.35em;
}

hr {
  border: 0;
  border-top: var(--hairline) solid var(--rule);
  margin: 0;
}

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

/* ==========================================================================
   3. LAYOUT PRIMITIVES
   ========================================================================== */

.wrap {
  width: min(100% - var(--space-m) * 2, var(--container));
  margin-inline: auto;
}

.wrap--narrow { --container: var(--container-narrow); }

.section { padding-block: var(--space-2xl); }
.section--tight { padding-block: var(--space-xl); }
.section--flush { padding-block-start: 0; }

.section + .section { border-top: var(--hairline) solid var(--rule); }

.stack > * + * { margin-block-start: var(--flow, var(--space-s)); }
.stack--s  { --flow: var(--space-xs); }
.stack--m  { --flow: var(--space-m); }
.stack--l  { --flow: var(--space-l); }

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

.grid {
  display: grid;
  gap: var(--space-m);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr));
}

/* Exactly two columns from tablet up. `auto-fit` is wrong for a four-card set:
   it resolves to three columns at desktop and strands a lone card on row two. */
.grid--2 { grid-template-columns: 1fr; }

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

/* Anchor targets must clear the sticky header, or in-page nav hides the
   heading it just jumped to. */
:where([id]) { scroll-margin-block-start: calc(var(--header-h) + var(--space-m)); }

.cluster {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s) var(--space-m);
  align-items: center;
}

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

.skip-link {
  position: absolute;
  inset-inline-start: var(--space-s);
  inset-block-start: calc(var(--space-s) * -4);
  z-index: 10;
  background: var(--accent);
  color: var(--accent-ink);
  padding: var(--space-2xs) var(--space-s);
  border-radius: var(--radius);
  text-decoration: none;
  transition: inset-block-start var(--dur) var(--ease);
}

.skip-link:focus { inset-block-start: var(--space-s); }

/* ==========================================================================
   4. TYPE HELPERS
   ========================================================================== */

.lede {
  font-size: var(--step-1);
  line-height: 1.45;
  color: var(--ink-soft);
  letter-spacing: -0.012em;
}

.small     { font-size: var(--step--1); }
.soft      { color: var(--ink-soft); }
.faint     { color: var(--ink-faint); }

.eyebrow {
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--accent);
}

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

/* ==========================================================================
   5. COMPONENTS
   ========================================================================== */

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

.site-header {
  border-bottom: var(--hairline) solid var(--rule);
  /* Opaque first, translucent second. An engine without color-mix drops the
     line below and keeps this one, which matters here and nowhere else the
     function is used: the other five are border colours that degrade to
     something harmless, while a sticky bar with no background lets the page
     scroll underneath its own text. */
  background: var(--paper);
  background: color-mix(in srgb, var(--paper) 88%, transparent);
  -webkit-backdrop-filter: blur(8px);
  backdrop-filter: blur(8px);
  position: sticky;
  top: 0;
  z-index: 5;
}

.site-header__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-m);
  padding-block: var(--space-xs);
}

/* Brand left, nav and the theme toggle together on the right. The auto margin
   does that without the nav needing a wrapper on all eleven pages. */
.site-nav { margin-inline-start: auto; }

.theme-toggle {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  flex: none;
  padding: 0;
  border: 0;
  border-radius: 999px;
  background: none;
  color: var(--ink-faint);
  cursor: pointer;
  transition: color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}

.theme-toggle[hidden] { display: none; }

.theme-toggle:hover {
  color: var(--ink);
  background: var(--surface-alt);
}

.theme-toggle svg { width: 1.05rem; height: 1.05rem; }

/* The icon shows the mode you would be switching to, not the one you are in.
   Driven by a data attribute the script sets, because the effective mode can
   come from either the media query or the override and no selector sees both. */
.theme-toggle__sun { display: none; }
.theme-toggle[data-mode="dark"] .theme-toggle__moon { display: none; }
.theme-toggle[data-mode="dark"] .theme-toggle__sun { display: block; }

.brand {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  font-weight: 650;
  letter-spacing: -0.02em;
  text-decoration: none;
  font-size: var(--step-0);
}

.brand__mark {
  width: 1.1em;
  height: 1.1em;
  flex: none;
  color: var(--accent);
}

.site-nav {
  display: flex;
  gap: var(--space-m);
  font-size: var(--step--1);
}

.site-nav a {
  text-decoration: none;
  color: var(--ink-soft);
  font-weight: 500;
}

.site-nav a:hover { color: var(--ink); }

@media (max-width: 33rem) {
  .site-nav { display: none; }
}

/* ---- Buttons ---- */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2xs);
  padding: var(--space-xs) var(--space-m);
  border-radius: var(--radius-lg);
  border: var(--hairline) solid transparent;
  font: inherit;
  font-weight: 600;
  font-size: var(--step-0);
  line-height: 1.25;
  text-decoration: none;
  cursor: pointer;
  transition: background var(--dur) var(--ease),
              border-color var(--dur) var(--ease),
              transform var(--dur) var(--ease);
}

.btn--primary {
  background: var(--accent);
  color: var(--accent-ink);
}

.btn--primary:hover {
  background: var(--accent-deep);
}

.btn--primary:active { transform: translateY(1px); }

.btn--ghost {
  background: transparent;
  color: var(--ink);
  border-color: var(--rule-strong);
}

.btn--ghost:hover { border-color: var(--ink-faint); }

/* ---- Contact block (mailto + copyable address) ---- */

.contact {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-s) var(--space-m);
}

.addr {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  font-size: var(--step--1);
  color: var(--ink-soft);
  flex-wrap: wrap;
}

.addr__label {
  color: var(--ink-faint);
  white-space: nowrap;
}

.addr__value {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.95em;
  color: var(--ink);
  user-select: all;
  word-break: keep-all;
}

.copy {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  background: var(--surface);
  border: var(--hairline) solid var(--rule);
  border-radius: 999px;
  padding: 0.35em 0.75em;
  font: inherit;
  font-size: 0.95em;
  font-weight: 550;
  line-height: 1;
  color: var(--ink-soft);
  cursor: pointer;
  transition: border-color var(--dur) var(--ease),
              color var(--dur) var(--ease),
              background-color var(--dur) var(--ease);
}

.copy:hover {
  border-color: var(--rule-strong);
  color: var(--ink);
}

.copy__icon {
  width: 1em;
  height: 1em;
  flex: none;
}

.copy__icon--done { display: none; }

/* Confirmation is carried by colour and a tick, not by the word alone, so it
   reads at a glance without anyone having to re-read the button. */
.copy.is-done {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 38%, transparent);
  background: var(--accent-wash);
}

.copy.is-done .copy__icon { display: none; }
.copy.is-done .copy__icon--done { display: block; }

/* The label swaps between Copy, Copied and Select, so the button has to be
   as wide as the longest of them from the outset or the address next to it
   jumps. Stacking a hidden sizer in the same grid cell reserves exactly that
   width. The old fix was a min-width guess, which was too wide and left the
   short label stranded against the left edge. */
.copy__label {
  display: inline-grid;
  justify-items: center;
}

.copy__label > span { grid-area: 1 / 1; }

.copy__label::after {
  content: "Copied";
  grid-area: 1 / 1;
  height: 0;
  visibility: hidden;
  pointer-events: none;
}

/* ---- Hero ---- */

/* Deliberately short. The carousel sits directly underneath and has to be
   visible without scrolling, so the hero gets the headline, one lede and the
   call to action, and nothing else. The before/after comparison that used to
   fill the right half is now the first slide. */
.hero {
  padding-block: var(--space-l) var(--space-m);
}

.hero__title {
  font-size: var(--step-4);
  max-width: 20ch;
}

.hero__lede {
  max-width: var(--measure-tight);
  margin-block-start: var(--space-s);
}

.hero__cta { margin-block-start: var(--space-m); }

/* ---- Before/after code comparison ---- */

.code-compare {
  display: grid;
  gap: var(--space-s);
}

.code-panel {
  margin: 0;
  background: var(--surface);
  border: var(--hairline) solid var(--rule);
  border-radius: var(--radius-lg);
  overflow: hidden;
}

.code-panel__label {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  flex-wrap: wrap;
  gap: var(--space-3xs) var(--space-s);
  padding: var(--space-2xs) var(--space-s);
  border-bottom: var(--hairline) solid var(--rule);
  background: var(--surface-alt);
  font-size: var(--step--1);
  color: var(--ink-faint);
}

.code-panel__tag {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.92em;
  white-space: nowrap;
}

.code-panel--after .code-panel__label { color: var(--accent); }

.code-panel pre {
  margin: 0;
  padding: var(--space-s);
  overflow-x: auto;
  font-size: clamp(0.71rem, 0.64rem + 0.3vw, 0.8rem);
  line-height: 1.7;
}

.code-panel code {
  background: none;
  border: 0;
  padding: 0;
  font-size: inherit;
}

.tok-c { color: var(--code-comment); }
.tok-k { color: var(--code-key); }
.tok-s { color: var(--code-str); }

/* ---- Wordmark strip ---- */

.wordmarks {
  padding-block: var(--space-l);
  border-block: var(--hairline) solid var(--rule);
}

.wordmarks__label {
  font-size: var(--step--1);
  color: var(--ink-faint);
  margin-block-end: var(--space-s);
}

.wordmarks__list {
  list-style: none;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-s) var(--space-l);
  align-items: baseline;
}

.wordmarks__list li {
  font-weight: 600;
  font-size: var(--step-0);
  letter-spacing: -0.01em;
  color: var(--ink-soft);
  white-space: nowrap;
}

/* The sectors line is what turns the names above from decoration into
   evidence: most of them are crypto, one is a real-estate marketplace. */
.wordmarks__sectors {
  margin-block-start: var(--space-s);
  max-width: var(--measure);
  font-size: var(--step--1);
  color: var(--ink-faint);
}

/* ---- Cards ---- */

.card {
  background: var(--surface);
  border: var(--hairline) solid var(--rule);
  border-radius: var(--radius-lg);
  padding: var(--space-m);
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

.card__title { font-size: var(--step-1); }

.card--link {
  text-decoration: none;
  transition: border-color var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}

.card--link:hover {
  border-color: var(--rule-strong);
  box-shadow: var(--shadow-sm);
}

/* Underline on hover only. A persistent rule under a two-line card title
   reads as a rendering artefact; the border, the arrow and the hover state
   already carry the affordance. */
.card--link .card__title {
  text-decoration: underline;
  text-decoration-color: transparent;
  text-underline-offset: 0.18em;
  transition: text-decoration-color var(--dur) var(--ease);
}

.card--link:hover .card__title,
.card--link:focus-visible .card__title { text-decoration-color: var(--accent); }

.card__more {
  margin-block-start: auto;
  padding-block-start: var(--space-xs);
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--accent);
}

/* ---- Numbered steps ---- */

.steps {
  list-style: none;
  padding: 0;
  counter-reset: step;
  display: grid;
  gap: var(--space-m);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
}

.steps > li {
  counter-increment: step;
  border-top: var(--ring) solid var(--accent);
  padding-block-start: var(--space-s);
}

.steps > li::before {
  content: counter(step, decimal-leading-zero);
  display: block;
  font-size: var(--step--1);
  font-weight: 700;
  color: var(--accent);
  letter-spacing: 0.06em;
  margin-block-end: var(--space-3xs);
}

.steps h3 { margin-block-end: var(--space-3xs); }

/* ---- Tool strip ---- */

.tools {
  list-style: none;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs) var(--space-xs);
}

.tools li {
  font-size: var(--step--1);
  font-weight: 500;
  color: var(--ink-soft);
  border: var(--hairline) solid var(--rule);
  border-radius: 999px;
  padding: 0.2em 0.7em;
  white-space: nowrap;
}

/* ---- Callout / CTA panel ---- */

.panel {
  background: var(--accent-wash);
  border: var(--hairline) solid color-mix(in srgb, var(--accent) 22%, transparent);
  border-radius: var(--radius-lg);
  padding: var(--space-l);
}

.panel__title { max-width: 24ch; }

/* ---- Footer ---- */

.site-footer {
  border-top: var(--hairline) solid var(--rule);
  padding-block: var(--space-xl) var(--space-l);
  color: var(--ink-soft);
  font-size: var(--step--1);
}

/* Grid rather than flex: with four columns, space-between wrapped 3 + 1 and
   stranded the last block on its own row. */
.site-footer__grid {
  display: grid;
  gap: var(--space-l) var(--space-xl);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
  align-items: start;
}

.made {
  display: inline-flex;
  align-items: baseline;
  gap: 0.3em;
  white-space: nowrap;
}

/* Decorative: the accessible name is carried by the visually-hidden "love". */
.made__heart {
  color: var(--accent);
  font-size: 1.05em;
  line-height: 1;
}

.site-footer__legal {
  margin-block-start: var(--space-l);
  padding-block-start: var(--space-s);
  border-top: var(--hairline) solid var(--rule);
  color: var(--ink-faint);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs) var(--space-m);
  justify-content: space-between;
}

/* ==========================================================================
   6. ARTICLE (notes/*.html)
   ========================================================================== */

.article { padding-block: var(--space-2xl) var(--space-xl); }

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

.article__meta {
  font-size: var(--step--1);
  color: var(--ink-faint);
  margin-block-start: var(--space-s);
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2xs) var(--space-s);
}

.prose {
  max-width: var(--measure);
  margin-block-start: var(--space-xl);
}

.prose > * + * { margin-block-start: var(--space-m); }

.prose h2 {
  font-size: var(--step-2);
  margin-block-start: var(--space-xl);
}

.prose h3 {
  font-size: var(--step-1);
  margin-block-start: var(--space-l);
}

.prose ul, .prose ol { padding-inline-start: var(--space-m); }
.prose li + li { margin-block-start: var(--space-2xs); }

.prose a { text-decoration-color: var(--accent); }

.prose blockquote {
  border-inline-start: var(--ring) solid var(--accent);
  padding-inline-start: var(--space-m);
  color: var(--ink-soft);
  font-size: var(--step-1);
  line-height: 1.45;
}

.prose figure {
  margin-block: var(--space-l);
}

.prose figcaption {
  font-size: var(--step--1);
  color: var(--ink-faint);
  margin-block-start: var(--space-2xs);
}

/* Source/citation list */
.sources {
  margin-block-start: var(--space-xl);
  padding-block-start: var(--space-m);
  border-top: var(--hairline) solid var(--rule);
  font-size: var(--step--1);
  color: var(--ink-soft);
}

.sources ol { padding-inline-start: var(--space-m); }
.sources li + li { margin-block-start: var(--space-2xs); }

/* Data table inside prose */
.prose table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--step--1);
}

.prose th,
.prose td {
  text-align: start;
  padding: var(--space-2xs) var(--space-xs);
  border-bottom: var(--hairline) solid var(--rule);
  vertical-align: top;
}

.prose th {
  font-weight: 650;
  color: var(--ink);
  border-bottom-color: var(--rule-strong);
}

.table-scroll { overflow-x: auto; }

/* ==========================================================================
   6b. TEARDOWNS (case studies)
   ========================================================================== */

.case-art {
  width: 100%;
  aspect-ratio: 3 / 1;
  object-fit: cover;
  border-radius: var(--radius-lg);
  border: var(--hairline) solid var(--rule);
  display: block;
  background: var(--surface-alt);
}

/* Facts panel: who published it, when, and the shape of the suite. */
.case-facts {
  margin: 0;
  margin-block-start: var(--space-l);
  padding: var(--space-m);
  background: var(--surface);
  border: var(--hairline) solid var(--rule);
  border-radius: var(--radius-lg);
  display: grid;
  gap: var(--space-s) var(--space-l);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 12rem), 1fr));
}

.case-facts div { min-width: 0; }

.case-facts dt {
  font-size: var(--step--1);
  color: var(--ink-faint);
  margin-block-end: var(--space-3xs);
}

.case-facts dd {
  margin: 0;
  font-weight: 600;
  line-height: 1.35;
  overflow-wrap: break-word;
}

/* Provenance badge: measured vs estimated vs vendor claim. */
.provenance {
  display: inline-flex;
  align-items: center;
  gap: 0.4em;
  font-size: var(--step--1);
  font-weight: 600;
  border-radius: 999px;
  padding: 0.1em 0.6em;
  border: var(--hairline) solid var(--rule-strong);
  color: var(--ink-soft);
  white-space: nowrap;
}

.provenance--measured {
  color: var(--accent);
  border-color: color-mix(in srgb, var(--accent) 40%, transparent);
  background: var(--accent-wash);
}

/* Our own read on the case, visually separated from the reported facts. */
.takeaway {
  margin-block-start: var(--space-xl);
  padding: var(--space-m) var(--space-m) var(--space-m) var(--space-l);
  border-inline-start: var(--ring) solid var(--accent);
  background: var(--surface-alt);
  border-radius: 0 var(--radius-lg) var(--radius-lg) 0;
}

.takeaway h2 { font-size: var(--step-1); margin-block-end: var(--space-2xs); }

.chart {
  margin-block: var(--space-l);
}

.chart img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: var(--radius);
  border: var(--hairline) solid var(--rule);
}

/* Charts are inline SVG, not raster: they follow prefers-color-scheme through
   the --chart-* tokens, stay crisp at any width, and cost no extra request.
   Font sizes are in SVG user units, so they scale with the viewBox. */
.chart__svg {
  width: 100%;
  height: auto;
  display: block;
  font-family: inherit;
  overflow: visible;
}

.chart__title { font-size: 15px; font-weight: 650; fill: var(--ink); letter-spacing: -0.2px; }
.chart__sub   { font-size: 12px; fill: var(--ink-faint); }
.chart__lbl   { font-size: 13px; fill: var(--ink-soft); }
.chart__val   { font-size: 13px; font-weight: 650; fill: var(--ink); }
.chart__note  { font-size: 11.5px; fill: var(--ink-faint); }

.chart__bar--before { fill: var(--chart-before); }
.chart__bar--after  { fill: var(--chart-after); }

.chart__axis { stroke: var(--chart-grid); stroke-width: 1; }

/* Next-reads block at the end of an article */
.next-reads {
  margin-block-start: var(--space-2xl);
  padding-block-start: var(--space-l);
  border-top: var(--hairline) solid var(--rule);
}

/* Line series reuse the validated categorical pair. The tokens are named
   before/after because that is where they were first used; here they are
   simply series A and series B, still the only pair that clears the CVD
   separation check. */
.chart__line {
  fill: none;
  stroke-width: 2.5;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.chart__line--before { stroke: var(--chart-before); }
.chart__line--after  { stroke: var(--chart-after); }

.chart__swatch--before { fill: var(--chart-before); }
.chart__swatch--after  { fill: var(--chart-after); }

.chart--flush { margin-block: 0; }

/* ---- Carousel ----
   Scroll-snap does the work, so the track is swipeable and keyboard-scrollable
   with JavaScript off. The controls are hidden until the script upgrades them,
   which is why the markup carries `hidden` rather than the stylesheet. */

.carousel__viewport {
  overflow-x: auto;
  overscroll-behavior-inline: contain;
  scroll-snap-type: x mandatory;
  scrollbar-width: none;
  border-radius: var(--radius-lg);
}

.carousel__viewport::-webkit-scrollbar { display: none; }

.carousel__track {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  gap: var(--space-s);
}

/* Every slide is the height of the tallest, so advancing never reflows the
   page below. Layout shift on a rotating panel is the usual way carousels
   wreck CLS. */
.carousel__slide {
  flex: 0 0 100%;
  min-width: 0;
  scroll-snap-align: start;
  display: flex;
}

.slide {
  flex: 1;
  background: var(--surface);
  border: var(--hairline) solid var(--rule);
  border-radius: var(--radius-lg);
  padding: var(--space-l);
  display: grid;
  gap: var(--space-l);
  align-content: start;
}

/* Narrow screens: a slide is taller than the viewport, so the dots sit below
   the fold and cannot be the affordance. The next card peeking in at the edge
   is what tells you the thing swipes. */
@media (max-width: 57.99rem) {
  .carousel__slide { flex-basis: 90%; }
  .slide { padding: var(--space-m); gap: var(--space-m); }
}

/* Slides stay stretched to the tallest rather than sized to their content.
   Sizing to content was tried and is worse: a flex container is still the
   height of its tallest item, so a short card does not shorten the carousel,
   it just opens a few hundred pixels of nothing between the card and the
   dots, and leaves the peeked next card towering over it. */

@media (min-width: 58rem) {
  .slide {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.04fr);
    gap: var(--space-xl);
    align-items: center;
    align-content: center;
    padding: var(--space-xl);
  }
}

.slide__copy > * + * { margin-block-start: var(--space-m); }

.slide__index {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: var(--step--1);
  letter-spacing: 0.08em;
  color: var(--ink-faint);
}

.slide__title {
  font-size: var(--step-2);
  max-width: 20ch;
  margin-block-start: var(--space-xs);
}

.slide__art { min-width: 0; }

.slide-facts {
  margin: 0;
  display: grid;
  gap: var(--space-m);
}

.slide-facts > div {
  border-top: var(--hairline) solid var(--rule);
  padding-block-start: var(--space-xs);
}

.slide-facts dt {
  font-size: var(--step--1);
  font-weight: 650;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--accent);
}

.slide-facts dd {
  margin: 0;
  margin-block-start: var(--space-3xs);
  color: var(--ink-soft);
}

/* The controls sit above the track, on the heading row, not under it. The
   first slide is taller than the space left below a hero, so controls placed
   after it land a full screen down and nobody ever learns the thing moves. */
.carousel__head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--space-s) var(--space-m);
  margin-block-end: var(--space-m);
}

.carousel__title {
  font-size: var(--step-2);
  max-width: 22ch;
}

.carousel__controls {
  display: flex;
  align-items: center;
  gap: var(--space-2xs);
}

.carousel__controls[hidden] { display: none; }

.carousel__btn {
  width: 2.25rem;
  height: 2.25rem;
  display: grid;
  place-items: center;
  border: var(--hairline) solid var(--rule);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-soft);
  cursor: pointer;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}

.carousel__btn svg { width: 1rem; height: 1rem; }

.carousel__btn:hover {
  border-color: var(--rule-strong);
  color: var(--ink);
}

/* Play/pause sits in the control cluster as a third circle, the same size and
   shape as the arrows. It exists because rotation starts on its own and runs
   longer than five seconds, which needs a way to stop it that does not depend
   on being able to hover or to tab. An earlier version carried a text label
   out at the right edge of the row, which read as loose chrome. */
.carousel__toggle svg { width: 0.9rem; height: 0.9rem; }

.carousel__icon--play { display: none; }

.carousel.is-static .carousel__icon--pause { display: none; }
.carousel.is-static .carousel__icon--play  { display: block; }

.carousel__dots {
  display: flex;
  align-items: center;
  gap: var(--space-3xs);
  margin-inline: var(--space-2xs);
}

/* The button carries the tap target, the pseudo-element carries the dot, so the
   target is larger than the dot it draws. Measured at 24x36px, which clears the
   24x24 minimum in WCAG 2.5.8 and falls short of the 44x44 in 2.5.5. This
   comment used to claim 44px, which these dimensions have never produced. */
.carousel__dot {
  width: 1.5rem;
  height: 2.25rem;
  display: grid;
  place-items: center;
  padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
}

/* The active dot doubles as a progress bar: it widens into a track and fills
   across the dwell, so the reader can see a change coming instead of being
   surprised by one. The fill is a real element rather than a pseudo so the
   animation can be paused and restarted independently of the track. */
.carousel__dot-track {
  display: block;
  width: 0.5rem;
  height: 0.5rem;
  border-radius: 999px;
  background: var(--rule-strong);
  overflow: hidden;
  transition: width var(--dur) var(--ease), background-color var(--dur) var(--ease);
}

.carousel__dot:hover .carousel__dot-track { background: var(--ink-faint); }

.carousel__dot[aria-current="true"] .carousel__dot-track {
  width: 1.6rem;
  background: color-mix(in srgb, var(--accent) 24%, transparent);
}

.carousel__dot-fill {
  display: block;
  width: 100%;
  height: 100%;
  background: var(--accent);
  transform: scaleX(0);
  transform-origin: left center;
}

.carousel__dot[aria-current="true"] .carousel__dot-fill {
  animation: carousel-dwell var(--dwell, 6000ms) linear forwards;
}

/* Hover, focus, a hidden tab or the carousel scrolling off screen: the bar
   holds where it is rather than resetting. */
.carousel.is-paused .carousel__dot-fill { animation-play-state: paused; }

/* Once the reader has taken over there is no dwell left to show, so the dot
   goes solid. Reduced-motion users land here too, via the global rule that
   collapses animation duration. */
.carousel.is-static .carousel__dot[aria-current="true"] .carousel__dot-fill {
  animation: none;
  transform: scaleX(1);
}

@keyframes carousel-dwell {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}

@media (prefers-reduced-motion: reduce) {
  .carousel__viewport { scroll-behavior: auto; }
}

/* A featured note spans the pair. Used for the piece that carries the
   argument the rest of the page rests on. */
.card--wide {
  grid-column: 1 / -1;
  background: var(--accent-wash);
  border-color: color-mix(in srgb, var(--accent) 22%, transparent);
}

@media (min-width: 58rem) {
  .card--wide .card__title { font-size: var(--step-2); max-width: 28ch; }
  .card--wide .small { max-width: var(--measure); }
}

/* ---- Thesis block ----
   The page's core argument. Same two-column shape as a carousel slide, but a
   plain section: it is the one thing on the page that should not be behind an
   interaction. */
.thesis {
  display: grid;
  gap: var(--space-l);
}

.thesis__copy > * + * { margin-block-start: var(--space-m); }
.thesis__copy .section-title { margin-block-start: var(--space-xs); }
.thesis__art { min-width: 0; }

@media (min-width: 58rem) {
  .thesis {
    grid-template-columns: minmax(0, 1fr) minmax(0, 0.92fr);
    gap: var(--space-xl);
    align-items: start;
  }

  /* The ledger is the visual argument, so it starts level with the prose
     rather than with the heading above it. */
  .thesis__art { padding-block-start: var(--space-2xs); }
}

.thesis__more { margin-block-start: var(--space-m); }
