/* ============================================================
   Decart Research — Design System
   Ported from Figma "Decart Research" file (Home page + Article
   page templates). Design tokens below mirror the Figma Variables
   panel 1:1 (Primitives / Semantic / Spacing / Radius collections).
   ============================================================ */

/* --- Roobert (body/UI) — self-hosted, reused from the AdaOr post --- */
@font-face {
  font-family: 'Roobert';
  src: url('fonts/Roobert-Light.woff2') format('woff2');
  font-weight: 300;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Roobert';
  src: url('fonts/Roobert-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Roobert';
  src: url('fonts/Roobert-Medium.woff2') format('woff2');
  font-weight: 500;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Roobert';
  src: url('fonts/Roobert-SemiBold.woff2') format('woff2');
  font-weight: 600;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Roobert';
  src: url('fonts/Roobert-Bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

/* --- Season (display) ---
 * Figma uses "Season VF-TRIAL" — the VARIABLE font — at the Mix named
 * instances. The VF's fvar table defines:
 *   axes:  wght 300–900, SERF 0–100, ital 0–11
 *   Mix Regular = { wght: 420, SERF: 50 }
 *   Mix Medium  = { wght: 580, SERF: 50 }
 * So Figma's weights 420/580 are used HERE verBATim, with SERF pinned to 50
 * (set once on <body>; Roobert has no SERF axis so it is unaffected).
 * The static Mix cuts remain as fallback if the VF fails to load.
 */
@font-face {
  font-family: 'Season VF';
  src: url('fonts/Season/SeasonCollectionVF-TRIAL.woff2') format('woff2-variations');
  font-weight: 300 900;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Season Mix';
  src: url('fonts/SeasonMix-Regular.woff2') format('woff2');
  font-weight: 300 499;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Season Mix';
  src: url('fonts/SeasonMix-RegularItalic.woff2') format('woff2');
  font-weight: 300 499;
  font-style: italic;
  font-display: swap;
}
@font-face {
  font-family: 'Season Mix';
  src: url('fonts/SeasonMix-Medium.woff2') format('woff2');
  font-weight: 500 649;
  font-style: normal;
  font-display: swap;
}
@font-face {
  font-family: 'Season Mix';
  src: url('fonts/SeasonMix-SemiBold.woff2') format('woff2');
  font-weight: 650 900;
  font-style: normal;
  font-display: swap;
}

:root {
  /* ---- Primitives: neutral ---- */
  --neutral-0: #FFFFFF;
  --neutral-50: #FEFEFB;
  /* Darkened from Figma's #F5F5F5: once soft-light blended with the now
     much-brighter paper texture, #F5F5F5 rendered almost indistinguishable
     from the page background. This value is chosen so the blended RESULT
     lands back at roughly the original card's visual weight. */
  --neutral-100: #ECECEC;

  /* Fill for panels that have to READ as a card against the page.
     This is the FINAL rendered value — panels are flat, never blended.

     They used to be authored #E6E6E6 with the grain tile composited over
     them at soft-light, which lands on exactly this #F2F2F2 in Chrome. But
     WebKit does not blend a background-image with the background-COLOR
     beneath it (image↔image blending works; image↔color silently doesn't),
     so on every iPhone the near-white tile painted opaquely over the fill
     and the card became the page — invisible. Verified against the page's
     own grain (which body paints at `normal` blend, rendering 252): 242
     here is the 10-level step the design draws its cards with.

     The grain the blend used to add had a std of 0.15 after compositing —
     nothing anyone can see — so flat costs nothing anywhere. If a panel
     ever needs real texture again it must come from a pre-tinted tile, not
     from blending over a color. */
  --surface-panel: #F2F2F2;
  --neutral-150: #F3F3F3;
  --neutral-200: #E3E3E3;
  --neutral-300: #C5C5C5;
  --neutral-400: #9A9A9A;
  --neutral-500: #6A6A6A;
  --neutral-600: #4D565F;
  /* Warmed slightly off pure gray/black so all copy reads as printed ink
     rather than flat digital black. */
  --neutral-700: #3B3733;
  --neutral-800: #313131;
  --neutral-850: #21222C;
  --neutral-900: #33302B;
  --neutral-950: #000000;
  --ink-rgb: 51, 48, 43; /* rgb triplet matching --neutral-900, for rgba() text colors */

  /* ---- Primitives: brand ---- */
  --brand-blue: #2548F6;
  --brand-navy: #00044C;
  --brand-warm: #FAF7EE;

  /* ---- Semantic ---- */
  --bg-primary: var(--neutral-50);
  --bg-secondary: var(--neutral-100);
  --bg-tertiary: var(--neutral-150);
  --bg-inverse: var(--neutral-850);
  --bg-warm: var(--brand-warm);

  --text-primary: var(--neutral-900);
  --text-secondary: var(--neutral-600);
  --text-tertiary: var(--neutral-500);
  --text-muted: var(--neutral-400);
  --text-on-dark: var(--neutral-0);
  --text-heading: var(--neutral-800);

  --accent-primary: var(--brand-blue);
  --accent-deep: var(--brand-navy);

  --border-default: var(--neutral-200);
  --border-subtle: var(--neutral-300);

  --surface-card: var(--neutral-0);
  --surface-elevated: var(--neutral-100);

  /* ---- Spacing scale ---- */
  --space-2xs: 4px;
  --space-xs: 8px;
  --space-sm: 12px;
  --space-md: 16px;
  --space-lg: 20px;
  --space-xl: 24px;
  --space-2xl: 32px;
  --space-3xl: 48px;
  --space-4xl: 64px;
  --space-5xl: 80px;
  --space-6xl: 120px;

  /* ---- Radius scale ---- */
  --radius-sm: 4px;
  --radius-md: 24px;
  --radius-lg: 32px;
  --radius-full: 100px;

  /* ---- Type families ---- */
  --font-display: 'Season VF', 'Season Mix', 'Roobert', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-body: 'Roobert', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  /* Figma's Season "Mix" instances: Regular = wght 420, Medium = wght 580 */
  --w-mix-regular: 420;
  --w-mix-medium: 580;
  --font-mono: 'SF Mono', 'JetBrains Mono', 'Fira Code', monospace;

  /* ---- Legacy aliases used by ported interactive components ---- */
  --bg: var(--bg-primary);
  --bg-white: var(--surface-card);
  --bg-card: var(--bg-secondary);
  --bg-surface: var(--bg-tertiary);
  --border: var(--border-default);
  --border-light: var(--bg-tertiary);
  --text: var(--text-primary);
  --blue: var(--accent-primary);
  --blue-light: #4a68ff;
  --blue-dim: rgba(37, 72, 246, 0.08);
  --blue-border: rgba(37, 72, 246, 0.2);
  --serif: var(--font-display);
  --sans: var(--font-body);
  --mono: var(--font-mono);

  --max-w: 780px;
  --max-w-wide: 1040px;
  /* Figma frame is 1440 wide with a 64px gutter => 1312px content column.
     Every top-level band uses --page-w + --gutter so all content shares
     ONE left edge at any viewport width. */
  --content-w: 1312px;
  --page-w: 1440px;
  --gutter: 64px;

  /* Exact Figma fills (note: nav/section bg #FFFEFB differs by 1 in the red
     channel from the neutral/50 token #FEFEFB — using Figma's literal value) */
  --nav-bg: #FFFEFB;
  --divider: rgba(0, 0, 0, 0.15);
  --nav-link: #696969;

  /* Paper grain texture — seamless tile, shown at 800px logical width
     (source asset is 2x that for retina sharpness). Layered as an image
     over --nav-bg wherever the page's light background shows. */
  --paper-texture: url('images/bg/paper-texture.webp');
  --paper-tile-size: 800px auto;

}

/* The single shared container. Anything that holds page content uses this so
   the nav, hero, sections and footer all align to the same gutter. */
.container {
  max-width: var(--page-w);
  margin: 0 auto;
  padding-left: var(--gutter);
  padding-right: var(--gutter);
  width: 100%;
}

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

html { scroll-behavior: smooth; font-size: 16px; }

/* The nav is sticky and 92 tall, so a jump to #publications, #events or
   #community-tools would park the heading underneath it. The offset is
   the bar plus a little air, and it belongs on the targets rather than
   on html's scroll-padding so that in-page scrolling is unaffected. */
:target,
#publications,
#events,
#community-tools { scroll-margin-top: 116px; }

body {
  font-family: var(--font-body);
  background-color: var(--nav-bg);
  background-image: var(--paper-texture);
  background-repeat: repeat;
  background-size: var(--paper-tile-size);
  /* scrolls with the page content (not pinned to the viewport), so the
     grain travels as you scroll instead of sitting static behind it */
  background-attachment: scroll;
  color: var(--text-primary);
  line-height: 1.5;
  /* Pin the Season VF to its Mix style (SERF 50). Roobert has no SERF axis,
     so this inherits harmlessly through body text. */
  font-variation-settings: 'SERF' 50;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

a { color: inherit; }
img { max-width: 100%; display: block; }

::selection { background: var(--accent-primary); color: #fff; }

.wrap {
  max-width: var(--content-w);
  margin: 0 auto;
  padding: 0 var(--space-4xl);
}

/* ============================================================
   Eyebrow / tag text
   Figma: Roobert 11px 500, lh 105%, letter-spacing 2.75px (25%),
   uppercase, #313131. Two container variants:
     .eyebrow-badge   — FILLED  cream pill (#FFFEFB, radius 4, pad 8/12)
                        used on stat cards, pillar cards, CTA banner
     .eyebrow-outline — STROKED pill (1px #313131, transparent)
                        used on publication cards + event rows
   ============================================================ */
.eyebrow {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 500;
  line-height: 1.05;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  color: var(--neutral-800);
}

.eyebrow-badge {
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  background: var(--nav-bg);
}

.eyebrow-outline {
  padding: 7px 11px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--neutral-800);
  background: transparent;
}

.eyebrow-accent { color: var(--accent-primary); }

/* ============================================================
   Navbar (shared — Home + Article)
   ============================================================ */
/* Full-bleed band so the background + border span the viewport; the inner
   .container carries the gutter so nav content lines up with page content. */
/* Figma: height 92px; padding 20px 64px; background #FFFEFB (solid);
   backdrop-filter blur(0px); no border. */
.site-nav {
  position: sticky;
  top: 0;
  z-index: 100;
  height: 92px;
  background-color: var(--nav-bg);
  background-image: var(--paper-texture);
  background-repeat: repeat;
  background-size: var(--paper-tile-size);
}

.site-nav > .container {
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.site-logo {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  text-decoration: none;
  color: var(--text-primary);
}

.site-logo img { height: 20px; width: auto; }

/* Figma: Roobert Medium 12px, lh 105%, letter-spacing 25%,
   #FFFFFF on #313131, padding 8px 12px, radius 4.71px */
.site-logo-badge {
  font-family: var(--font-body);
  font-size: 12px;
  font-weight: 500;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  color: #FFFFFF;
  background: var(--neutral-800);
  padding: 8px 12px;
  line-height: 1.05;
  border-radius: 4.71px;
}

.site-nav-links {
  display: flex;
  align-items: center;
  gap: var(--space-2xl);
}

.site-nav-links a {
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 500;
  line-height: 1.5;
  color: var(--nav-link);
  text-decoration: none;
  transition: color 0.15s;
}

.site-nav-links a:hover { color: var(--text-primary); }

.site-nav-cta {
  font-size: 11px !important;
  font-weight: 500 !important;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--accent-primary) !important;
  border: 1px solid var(--accent-primary);
  border-radius: var(--radius-sm);
  padding: 10px 16px;
  transition: background 0.15s, color 0.15s;
}

.site-nav-cta:hover {
  background: var(--accent-primary);
  color: #fff !important;
}

/* ============================================================
   HOME PAGE
   ============================================================ */

/* ---- Hero ---- */
/* Figma: padding 110px 64px 80px; column; gap 90px between the content row
   and the 1312×1px divider. */
/* The divider under the hero sits between two gaps that have to match.
   Above it is .home-hero-grid's padding-bottom; below it is THIS padding
   plus the following section's padding-top (--space-4xl). They were 91 and
   144 — unequal and both loose — so the bottom padding drops to zero and
   the section's own 64 becomes the whole lower gap, matching the 64 above. */
.home-hero {
  padding-top: 110px;
  padding-bottom: 0;
  /* transparent: the body's fixed paper texture shows through continuously */
  background: transparent;
}

/* Figma "Column": row, align-items flex-start, gap 20px, two equal 644px cols */
.home-hero-grid {
  display: flex;
  align-items: flex-start;
  gap: var(--space-lg);
  padding-bottom: var(--space-4xl);
  border-bottom: 1px solid var(--divider);
}

.home-hero-title,
.home-hero-copy { flex: 1 1 0; min-width: 0; }

/* Figma: Season VF 92 / wght 420 / lh 108% / ls -2.76px / #101010 */
.home-hero-title {
  font-family: var(--font-display);
  font-size: 92px;
  font-weight: var(--w-mix-regular);
  line-height: 1.08;
  letter-spacing: -0.03em;
  color: var(--neutral-900);
  margin: 0;
}

/* Figma: Season VF 18 / wght 580 / lh 145% / ls -0.09px / rgba(0,0,0,.9) */
.home-hero-copy h2 {
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: var(--w-mix-medium);
  line-height: 1.45;
  letter-spacing: -0.005em;
  color: rgba(var(--ink-rgb), 0.9);
  margin: 0 0 var(--space-md);
}

/* Figma: Roobert 16 / 400 / lh 152% / ls -0.16px / #333 @ 70% */
.home-hero-copy p {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.52;
  letter-spacing: -0.01em;
  color: var(--neutral-700);
  opacity: 0.7;
  margin: 0;
}

/* ---- Section shell ---- */
/* Figma sections vary: most are 64 top / 80 bottom; the quote band is 80/80;
   community tools is 80 top / 120 bottom; the CTA band is 64/64. */
.home-section {
  max-width: var(--page-w);
  margin: 0 auto;
  padding: var(--space-4xl) var(--gutter) var(--space-5xl);
  width: 100%;
  background: transparent;
}

.home-section--cta { padding-bottom: var(--space-4xl); }
.home-section--quote { padding-top: var(--space-5xl); }
.home-section--community { padding-top: var(--space-5xl); padding-bottom: var(--space-6xl); }

/* Figma: Season VF 40 / wght 420 / lh 108% / ls -0.4px / #101010 */
.home-section-heading {
  font-family: var(--font-display);
  font-size: 40px;
  font-weight: var(--w-mix-regular);
  line-height: 1.08;
  letter-spacing: -0.01em;
  color: var(--neutral-900);
  margin-bottom: var(--space-3xl);
}

/* ---- Publications ---- */
/* Figma: 2 cards of 644, gap 24 */
.publications-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-xl);
}

.pub-card { text-decoration: none; color: inherit; display: block; }

/* Figma: 644×352 (ratio 1.83:1), radius 24, 24 below */
.pub-card-media {
  position: relative;
  aspect-ratio: 644/352;
  border-radius: var(--radius-md);
  background: #F2F6FC;
  overflow: hidden;
  margin-bottom: var(--space-xl);
  display: flex;
  align-items: center;
  justify-content: center;
}

.pub-card-media svg { width: 100%; height: 100%; }

/* On the home page .cover-anim is a canvas HOST nested inside
   .pub-card-media so it can share that space with the slider overlay —
   there it fills its parent. On the article page the element carries
   .cover-anim directly and IS the sized flex item, so it must stay in
   normal flow; only the nested case is taken out of it. */
.cover-anim { width: 100%; height: 100%; }
.pub-card-media .cover-anim { position: absolute; inset: 0; }
.cover-anim canvas { width: 100%; height: 100%; display: block; }

/* Ambient, non-interactive "intensity" demo floating over the AdaOr
   cover — see cover-slider.js. Per the design: a centered white card
   holding tracked ORIGINAL / FULL EDIT labels above a blue slider.
   Purely decorative (aria-hidden), so it never intercepts the card's
   own link. */
.cover-demo-slider {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: min(64%, 400px);
  padding: 18px 22px 20px;
  border-radius: 14px;
  background: var(--nav-bg);
  box-shadow: 0 6px 24px rgba(16, 26, 110, 0.14);
  z-index: 2;
  pointer-events: none;
}
.cover-demo-labels {
  display: flex;
  justify-content: space-between;
  gap: 12px;
  /* Two tracked labels in a card that is only 64% of the cover: once the
     card narrows, "FULL EDIT" wraps, space-between collapses the gap, and
     the two run together as "ORIGINALFULL EDIT". They are two words on one
     line by definition — never let them break. */
  white-space: nowrap;
  margin-bottom: 14px;
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 500;
  line-height: 1.05;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  color: var(--neutral-800);
}
.cover-demo-track {
  position: relative;
  height: 6px;
  border-radius: 3px;
  background: var(--neutral-200);
}
.cover-demo-fill {
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: calc(var(--sv, 0.5) * 100%);
  border-radius: 3px;
  background: var(--accent-primary);
}
.cover-demo-thumb {
  position: absolute;
  top: 50%;
  left: calc(var(--sv, 0.5) * 100%);
  width: 18px;
  height: 18px;
  margin-left: -9px;
  transform: translateY(-50%);
  border-radius: 50%;
  background: var(--accent-primary);
  border: 3px solid #fff;
  box-shadow: 0 0 0 1px var(--accent-primary), 0 2px 6px rgba(37, 72, 246, 0.25);
}

/* Left publication cover — vector-needle field + centered emblem card */
.transport-cover { position: absolute; inset: 0; }
.transport-cover canvas { width: 100%; height: 100%; display: block; }

/* Figma: Season VF 18 / wght 580 / lh 145% / rgba(0,0,0,.9), 8px above desc */
.pub-card-title {
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: var(--w-mix-medium);
  line-height: 1.45;
  letter-spacing: -0.005em;
  color: rgba(var(--ink-rgb), 0.9);
  margin-bottom: var(--space-xs);
}

/* Figma: Roobert 16 / 400 / lh 152% / ls -0.32px / #333 @ 70%, 24 below */
.pub-card-desc {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.52;
  letter-spacing: -0.02em;
  color: var(--neutral-700);
  opacity: 0.7;
  margin-bottom: var(--space-xl);
  max-width: 591px;
}

/* Figma: badge + date, gap 20 */
.pub-card-meta {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
}

/* Figma: Roobert 14 / 400 / lh 130% / ls -0.14px / #9A9A9A */
.pub-card-date {
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.3;
  color: var(--neutral-400);
  letter-spacing: -0.01em;
}

/* ---- CTA banner (Our Goal) ---- */
/* Scroll interaction: the banner lives full-bleed inside the wrapper. A
   clip-path insets it to the 1312 content column with a 32px radius; as
   the section travels, JS drives --p from 0 to 1, opening the mask out to
   the full viewport width with hard edges while the banner grows taller,
   then the page scrolls on normally.
   The wrapper reserves stage-max, a dwell, and a short pinned runway. The
   expansion itself is animated during the LEAD-IN — ordinary scrolling
   while the section rises into view — which costs no page height at all.
   That is what lets the animation run long enough to notice without
   leaving a field of empty page before Research pillars. */
.cta-scroll-wrap {
  --p: 0;
  --cta-pin: 8vh;
  /* Breathing room kept under the card. The bottom edge rides this far
     above the viewport bottom instead of sitting flush on it — edge-to-edge
     read as too immersive. It stays CONSTANT for the whole animation
     (the bottom edge is stationary), so it reads as a margin rather than
     as the growing blank gap the pinned runway used to leave. */
  --cta-gap-bottom: 72px;
  /* Scroll spent at full height AFTER the expansion finishes. Costs nothing
     visually: the grown card covers the screen from the pin down to the
     bottom margin, so the runway underneath it is off-screen throughout. */
  --cta-hold: 200px;
  /* Scroll spent at the resting size BEFORE anything opens.

     Without it the card began widening the instant its bottom cleared the
     bottom margin — p was already 0.28 with the card only halfway up the
     viewport — so the aligned state existed for zero scroll and the card
     was simply "the one element wider than the grid". This buys a stretch
     where it sits at 1312 with its 32px corners, indistinguishable from a
     publication card, before it opens.

     It costs no page height — it is spent during the lead-in, while the
     section travels from the fold to the pin, which the wrapper's own
     height does not govern. What it does cost is that the card's bottom
     edge rides this far above the bottom margin while it dwells, settling
     back onto the margin as it opens. Keep it modest: much past a fifth of
     the viewport and the card reads as floating rather than resting. */
  --cta-dwell: 18vh;
  /* Both are overwritten in px by home-effects.js from the real measured
     geometry; these are the no-JS fallbacks. --cta-h-min has to be the
     banner's natural resting height, because min-height below that is
     ignored and the card then sits frozen until the ramp climbs past it. */
  --cta-h-min: 266px;
  --cta-h-max: 780px;
  /* Exactly the grown card plus the hold, and nothing spare. Any excess
     here is page the card can never reach, which is precisely what shows
     up as blank paper underneath it. */
  height: calc(100vh - var(--cta-pin) - var(--cta-gap-bottom) + var(--cta-hold));
  background: transparent;
}

.cta-sticky {
  position: sticky;
  /* Sits just under the nav so the banner follows Publications closely
     rather than after a run of empty page. */
  top: var(--cta-pin);
  /* NO explicit height — the stage is sized by the banner inside it, so
     it grows as the banner grows and can never leave dead space below
     the card. Height therefore comes from exactly one declaration (the
     banner's min-height), instead of two that have to agree. */
  display: flex;
  flex-direction: column;
  /* TOP-anchored: the banner's top edge stays put and it grows downward.
     Centring made it expand in both directions, which pushed dead space
     above the card and detached it from Publications. */
  justify-content: flex-start;
}

.cta-sticky .cta-banner {
  /* --p is inherited from .cta-scroll-wrap so the stage and the banner
     read the SAME value; it is no longer redeclared here. */
  /* distance from viewport edge to the resting card edge; --vw is the true
     layout width (sans scrollbar), set by home-effects.js */
  --cta-inset: max(var(--gutter), calc((var(--vw, 100vw) - 1312px) / 2));
  width: 100%;
  border-radius: 0;
  clip-path: inset(0 calc(var(--cta-inset) * (1 - var(--p))) round calc(32px * (1 - var(--p))));
  /* The copy migrates onto the page grid as the card opens. At p=0 it sits
     48px inside the resting card's rounded edge, exactly as designed; at
     p=1 that 48px is gone and the text lines up with --cta-inset — the
     same x as the decart logo and the Research pillars heading. Driven by
     the same --p as the mask and the height, so the slide is one motion
     with the expansion rather than a separate move. */
  --cta-pad-x: calc(var(--cta-inset) + 48px * (1 - var(--p)));
  padding-left: var(--cta-pad-x);
  padding-right: var(--cta-pad-x);
  /* the single source of truth for the section's height; the sticky stage
     takes its size from this. Works wherever --p is set on an ancestor.
     The ceiling is already viewport-derived (pin to bottom edge), so it
     needs no extra clamp here. Driven by --p, the SAME linear value as the
     clip-path above, so the card's height and its width are one motion
     rather than two. */
  min-height: calc(
    var(--cta-h-min) + (var(--cta-h-max) - var(--cta-h-min)) * var(--p)
  );
  overflow: hidden;
  /* The content is a GROUP held together, not spread to the card's edges.
     space-between hands every pixel of new height to the gap between the
     badge and the headline — harmless at 266px, but the ceiling is now the
     viewport, so on a tall display that gap opens to around a thousand
     pixels of empty blue. Centring leaves the resting card identical
     (nothing to distribute at natural height) and keeps the copy together
     as the card grows. */
  justify-content: center;
  /* NO transition on either axis. The mask used to be eased over 120ms
     while height was written straight from scroll — so the card's sides
     were always ~7 frames behind its bottom edge, and the two arrived at
     each new size separately. A transition on a scroll-driven property
     fights the scroll rather than smoothing it: the scroll position is
     already continuous, so the only thing easing adds is lag. */
}

/* Figma: column, flex-start, gap 48, padding 52px 48px, radius 32.
   The navy is image 63467109 (a photo) scaled to 2598×3674 and blurred
   11.65px over a #F3F3F3 base — approximated with a blurred-photo-like
   navy gradient until the asset is exported. */
.cta-banner {
  /* The supplied blurred navy photograph is the backdrop, with grain
     soft-light blended over it and the blueprint canvas above both.

     The grain here is the NEUTRAL tile, not --paper-texture. The paper
     tile is near-white (mean 252), and soft-light with a near-white top
     layer pushes a dark backdrop toward sqrt(b) — it was lifting the
     supplied navy from rgb(34,50,97) to rgb(89,112,156), a washed-out
     slate roughly 58 levels light on every channel, while contributing
     under one level of actual grain. The neutral tile is the same grain
     recentred on 128, where soft-light is a no-op on colour, so the
     photograph renders at the value it was delivered at (measured
     rgb(33,50,96)) and the texture still reads.

     The other dark surfaces (.quote-block, .tool-card) deliberately keep
     the near-white tile: their gradients are hand-authored AGAINST that
     lift — the tool card's 6.7:1 contrast was measured as-rendered — so
     swapping their grain would darken them below what was tuned. */
  background-color: #1d2c5e;
  background-image:
    url('images/bg/paper-grain-neutral.webp'),
    url('images/bg/cta-backdrop.webp');
  background-repeat: repeat, no-repeat;
  background-size: var(--paper-tile-size), cover;
  background-position: 0 0, center center;
  background-blend-mode: soft-light, normal;
  border-radius: var(--radius-lg);
  padding: 52px 48px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-3xl);
}

/* On the navy banner the badge stays the FILLED cream pill (Figma) */
.cta-banner .eyebrow-badge { background: var(--nav-bg); color: var(--neutral-800); }

/* Figma "Content": row, align-items flex-end, gap 24px, stretched */
.cta-banner-row {
  display: flex;
  align-items: flex-end;
  gap: var(--space-xl);
  align-self: stretch;
}

/* Figma: Season VF 36 / wght 420 / lh 120% / ls -0.36px / #FFFEFB / w 515 */
.cta-banner h3 {
  font-family: var(--font-display);
  font-size: 36px;
  font-weight: var(--w-mix-regular);
  line-height: 1.2;
  letter-spacing: -0.01em;
  color: var(--nav-bg);
  width: 515px;
  flex: 0 0 auto;
  margin: 0;
}

/* The headline grows with the card, on the same --p as the mask, the
   height and the copy's slide onto the grid — so it is one gesture rather
   than another thing moving on its own clock.
   Both ends are guarded with max(): on a narrow viewport the viewport-
   derived ceiling can fall BELOW the resting value, and an unguarded
   interpolation would then run backwards and shrink the headline as the
   card opened. Clamped this way it simply holds at the resting size. */
.cta-sticky .cta-banner h3 {
  --h3-size-min: 36px;
  --h3-size-max: max(36px, min(64px, 4.6vw));
  --h3-w-min: 515px;
  --h3-w-max: max(515px, min(915px, 68%));
  font-size: calc(
    var(--h3-size-min) + (var(--h3-size-max) - var(--h3-size-min)) * var(--p)
  );
  /* the measure widens with the type so the line count holds instead of
     the headline wrapping into a tall stack as it grows */
  width: calc(var(--h3-w-min) + (var(--h3-w-max) - var(--h3-w-min)) * var(--p));
  max-width: 100%;
}

.cta-banner p {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.52;
  color: rgba(255,255,255,0.7);
  flex: 1;
  text-align: right;
  margin: 0;
}

/* ---- Research pillars ---- */
/* Figma: 3-col grid, gap 24; cards 421×(auto), radius 32, #F5F5F5,
   padding 28px 28px 32px, internal gap 32, NO border. */
/* Figma 535:10296 — two cards of 644 with a 24 gutter, 24 between rows:
   644 + 24 + 644 = 1312, the content column exactly. It was three across
   while there were five pillars; the fifth ("Training & Inference") has
   been retired — Figma keeps its slot as a hidden "Next pillar in
   development" placeholder — so four cards now sit 2x2. */
.pillars-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: var(--space-xl);
}

.pillar-card {
  background: var(--surface-panel);
  border-radius: var(--radius-lg);
  padding: 28px 28px 32px;
}

.pillar-card-top {
  display: flex;
  justify-content: space-between;
  align-items: flex-start;
  /* Figma: number row ends at 58, the title starts at 90 */
  margin-bottom: var(--space-2xl);
}

/* Figma: Season VF 28 / wght 420 / lh 108% / ls -0.28px / #00044C */
.pillar-num {
  font-family: var(--font-display);
  font-size: 28px;
  font-weight: var(--w-mix-regular);
  line-height: 1.08;
  letter-spacing: -0.01em;
  color: var(--brand-navy);
}

/* Figma: Roobert 20 / 400 / lh 140% / ls -0.8px / #101010, then a
   1px #E3E3E3 divider 8px below */
.pillar-card h4 {
  font-family: var(--font-body);
  font-size: 20px;
  font-weight: 400;
  line-height: 1.4;
  letter-spacing: -0.04em;
  color: var(--neutral-900);
  padding-bottom: var(--space-xs);
  border-bottom: 1px solid var(--neutral-200);
  /* Figma: the rule sits at 126, the first bullet row at 158 */
  margin-bottom: var(--space-2xl);
}

/* Figma: list gap 10; rows: 4px dot + text, gap 8 */
.pillar-card ul {
  list-style: none;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Figma: Roobert 15 / 400 / lh 140% / #4D565F @ 90% */
.pillar-card li {
  font-family: var(--font-body);
  font-size: 15px;
  color: var(--neutral-600);
  opacity: 0.9;
  line-height: 1.4;
  padding-left: var(--space-sm);
  position: relative;
}

.pillar-card li::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.6em;
  width: 4px;
  height: 4px;
  border-radius: 50%;
  background: var(--neutral-600);
}

/* ---- Quote ---- */
/* Figma: banner radius 40, padding 52px 48px, gap 64 between quote and
   attribution. Background = the avatar photo scaled to 2598×3674 with
   blur(15.2px) over #F3F3F3 — approximated with a blue blurred-photo
   gradient until the asset is exported. */
.quote-block {
  /* The supplied photograph replaces the gradient stack that stood in for
     it. Grain is the NEUTRAL tile (recentred on 128) rather than the
     near-white paper one, which would soft-light this navy several dozen
     levels lighter. Cream copy measures 5.99:1 on the delivered colour. */
  background-color: #465db3;
  background-image:
    url('images/bg/paper-grain-neutral.webp'),
    url('images/bg/quote-backdrop.webp');
  background-repeat: repeat, no-repeat;
  background-size: var(--paper-tile-size), cover;
  background-position: 0 0, center center;
  background-blend-mode: soft-light, normal;
  border-radius: 40px;
  padding: 52px 48px;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-4xl);
}

/* Figma: Roobert Regular 32px, lh 128%, ls -0.64px, #FFFEFB — the quote is
   body-font, NOT the display face. Words rest at white @ 35% and fill to
   full white typewriter-style as the block scrolls through the viewport
   (the Figma frame shows this animation mid-state). */
.quote-block blockquote {
  font-family: var(--font-body);
  font-size: 32px;
  font-weight: 400;
  line-height: 1.28;
  letter-spacing: -0.02em;
  color: rgba(255,255,255,0.35);
  max-width: 923px;
  margin: 0;
}

.quote-block blockquote .qw {
  color: rgba(255,255,255,0.35);
  transition: color 0.3s ease-out;
}
.quote-block blockquote .qw.on { color: var(--nav-bg); }

.quote-attribution { display: flex; align-items: center; gap: var(--space-md); }

/* Figma: 72×72 photo. The gradient stays as the backdrop behind the
   portrait, so a slow-loading or missing image still reads as a plate
   rather than a hole. */
.quote-avatar {
  width: 72px;
  height: 72px;
  border-radius: 50%;
  background: linear-gradient(140deg, rgba(255,255,255,0.5), rgba(255,255,255,0.15));
  flex-shrink: 0;
  overflow: hidden;
}

.quote-avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  border-radius: 50%;
}

/* Figma: Season VF 20 / wght 580 / lh 145% / ls -0.1px / #FFFFFF 90% */
.quote-name {
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: var(--w-mix-medium);
  line-height: 1.45;
  letter-spacing: -0.005em;
  color: rgba(255,255,255,0.9);
}

/* Figma: Roobert 16 / 400 / lh 150% / #FFFEFB @ 80% */
.quote-role {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.5;
  color: var(--nav-bg);
  opacity: 0.8;
}

/* ---- Stats ---- */
/* Figma heading row: 40px heading (664 wide) left, 18px note right,
   bottom-aligned; 48 below. */
.stats-heading-row {
  display: flex;
  justify-content: space-between;
  align-items: flex-end;
  gap: 28px;
  margin-bottom: var(--space-3xl);
}

.stats-heading-row .home-section-heading { margin-bottom: 0; max-width: 664px; }

/* Figma: Roobert 18 / 400 / lh 152% / ls -0.36px / #333 @ 70% */
.stats-heading-row p {
  font-family: var(--font-body);
  font-size: 18px;
  line-height: 1.52;
  letter-spacing: -0.02em;
  color: var(--neutral-700);
  opacity: 0.7;
  text-align: right;
}

.stats-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-xl);
}

/* Figma: radius 28, #F5F5F5, padding 28px 24px 32px, no border;
   badge → 32 → value → 24 → caption */
.stat-card {
  background: var(--surface-panel);
  border-radius: 28px;
  padding: 28px 24px 32px;
}

.stat-card .eyebrow-badge { margin-bottom: var(--space-2xl); }

/* Figma: Season VF 36 / wght 420 / lh 108% / ls +0.36px / #00044C */
.stat-value {
  font-family: var(--font-display);
  font-size: 36px;
  font-weight: var(--w-mix-regular);
  line-height: 1.08;
  letter-spacing: 0.01em;
  color: var(--brand-navy);
  margin-bottom: var(--space-xl);
}

/* Figma: Roobert 16 / 400 / lh 152% / ls -0.16px / #333 @ 70% */
.stat-caption {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.52;
  letter-spacing: -0.01em;
  color: var(--neutral-700);
  opacity: 0.7;
}

/* ---- Events ---- */
/* Figma rows: [date pill 252] [name 129] [desc 414] [badge] [location],
   centered vertically, 1px #E3E3E3 dividers between rows. */
.events-list { display: flex; flex-direction: column; }

/* One grid, five fixed columns, one gutter — because alignment ACROSS rows
   is the whole point and flex cannot give it. The old row was flex with
   `margin-left:auto` on the location, so every location sat wherever its own
   text ended: the pin icons landed anywhere from x=1220 to x=1267, and the
   gap after the tag was ~160 against 32 before it.

   Widths are measured, not guessed: 176 fits the longest title
   ("NVIDIA GTC EMEA", 170) on one line, 144 fits the longest location
   ("Sydney | Australia", 141 including pin and gap), 104 fits the tag. The
   description takes what is left.

   The 56 gutter is chosen rather than round: it is what makes every gap
   equal while leaving the description at 412 — a hair under the 414 it had,
   and still above the ~391 the longest description needs to hold two lines.
   Equalising the gaps reclaims that old 160px of dead space, and it has to
   land somewhere; spending it on the gutter rather than the description is
   what keeps the row from turning into one wide block of grey text. */
.event-row {
  display: grid;
  grid-template-columns: 252px 176px minmax(0, 1fr) 104px 144px;
  gap: 56px;
  align-items: center;
  padding: var(--space-lg) 0;
  text-decoration: none;
  color: inherit;
}

.event-row + .event-row { border-top: 1px solid var(--neutral-200); }

/* Figma: 252 wide, padding 24, radius 20, #F5F5F5, centered */
.event-date {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-sm);
  padding: var(--space-xl);
  border-radius: 20px;
  background: var(--surface-panel);
  /* Figma: Season VF 22 / wght 420 / lh 108% / ls +0.22px / #00044C */
  font-family: var(--font-display);
  font-size: 22px;
  font-weight: var(--w-mix-regular);
  line-height: 1.08;
  letter-spacing: 0.01em;
  color: var(--brand-navy);
  white-space: nowrap;
}

/* Figma: Season VF 20 / wght 580 / lh 145% / ls -0.1px / rgba(0,0,0,.9) */
.event-title {
  /* Never wrap: a two-line conference name pushed its row taller than the
     rest and broke the rhythm of the list. The column above is sized to the
     longest current title — a longer one would need that number raised. */
  white-space: nowrap;
  font-family: var(--font-display);
  font-size: 20px;
  font-weight: var(--w-mix-medium);
  line-height: 1.45;
  letter-spacing: -0.005em;
  color: rgba(var(--ink-rgb), 0.9);
}

/* Figma: Roobert 16 / 400 / lh 152% / ls -0.16px / #333 @ 70%, w 414 */
.event-desc {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.52;
  letter-spacing: -0.01em;
  color: var(--neutral-700);
  opacity: 0.7;
}

.event-row .eyebrow-outline { justify-self: center; white-space: nowrap; }

/* Figma: pin icon + Roobert 14 / 400 / lh 150% / #101010 @ 70% */
/* Left-aligned inside its column, not right-aligned against the page edge:
   the request was that the pins line up, and the column is sized to the
   longest location so that shared x is as far right as it can go — its
   right edge is the content column's right edge. */
.event-location {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--font-body);
  font-size: 14px;
  line-height: 1.5;
  color: var(--neutral-900);
  opacity: 0.7;
  white-space: nowrap;
}

.event-location svg { width: 16px; height: 16px; flex-shrink: 0; }

/* The five-column row is sized for the full 1312 content column. Below that
   the fixed columns eat the description before anything else, so the row
   gives up the grid and wraps instead — earlier than the rest of the page,
   which holds together down to 900. */
@media (max-width: 1200px) {
  .event-row {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-md) var(--space-xl);
  }
  .event-title { flex: 0 0 auto; }
  .event-desc { flex: 1 1 240px; }
  .event-row .eyebrow-outline { justify-self: auto; }
  .event-location { margin-left: auto; }
}

@media (max-width: 640px) {
  /* No room to sit anything beside anything: one item per line. */
  .event-desc, .event-location { flex: 1 1 100%; }
  .event-location { margin-left: 0; }
}

/* ---- Community tools ---- */
.community-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: var(--space-3xl);
}

/* The section heading changes TEXT with the viewport, not just size: the
   1440 frame says "Built by researchers, for researchers" (399:5549), the
   360 frame says "Community tools" (350:565). Both live in the markup and
   each width shows its own — same trick as the card's title and tag. */
.community-title-mobile { display: none; }

/* Hidden while Unfold is the only tool — arrows that cannot go anywhere
   read as broken. Restore to `flex` when a second card lands. */
.carousel-nav { display: none; gap: var(--space-sm); }

/* Figma: outlined circles with arrow glyphs */
.carousel-btn {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 1px solid var(--neutral-200);
  background: transparent;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  font-size: 16px;
  color: var(--neutral-900);
  transition: border-color .15s;
}
.carousel-btn:hover { border-color: var(--neutral-400); }

/* Figma: radius 40, padding 52, row gap 24, align center. Background =
   image 63467111 blown up + blur(26.25px) over #F3F3F3 — approximated
   with a soft periwinkle blurred-photo gradient. */
.tool-card {
  /* The supplied photograph, with the neutral grain over it so it renders
     at the delivered value. It is LIGHT (mean rgb 188,202,226), which
     inverts this card: the slate gradient it replaces existed only to make
     cream copy legible, and cream on this measures 1.64:1 — the same
     unreadable state flagged before. The copy below is therefore ink
     rather than cream, which lands at 11.5:1. */
  background-color: #bccae2;
  background-image:
    url('images/bg/paper-grain-neutral.webp'),
    url('images/bg/tools-backdrop.webp');
  background-repeat: repeat, no-repeat;
  background-size: var(--paper-tile-size), cover;
  background-position: 0 0, center center;
  background-blend-mode: soft-light, normal;
  border-radius: 40px;
  padding: 52px;
  display: flex;
  align-items: center;
  gap: var(--space-xl);
}

.tool-card-body { flex: 0 0 568px; max-width: 568px; }

/* TOOL badge: filled cream pill, 13px variant of the eyebrow */
.tool-card .eyebrow-badge {
  font-size: 13px;
  letter-spacing: 0.22em;
  margin-bottom: var(--space-3xl);
  display: inline-flex;
}

/* Figma: Season VF 52 / wght 420 / lh 108% / ls -1.56px / #FFFEFB */
.tool-card h4 {
  font-family: var(--font-display);
  font-size: 52px;
  font-weight: var(--w-mix-regular);
  line-height: 1.08;
  letter-spacing: -0.03em;
  /* ink, not cream — the backdrop is now the light supplied photo */
  color: var(--neutral-900);
  margin-bottom: var(--space-xl);
}

/* Figma: Roobert 18 / 400 / lh 152% / ls -0.18px / #FFFEFB @ 85%, w 568 */
.tool-card p {
  font-family: var(--font-body);
  font-size: 18px;
  line-height: 1.52;
  letter-spacing: -0.01em;
  color: var(--neutral-800);
  opacity: 0.9;
  margin-bottom: var(--space-xl);
}

/* Figma: cream pill button, Roobert 16 / 500 / lh 150% / #000 */
.tool-card-cta {
  display: inline-flex;
  align-items: center;
  padding: 12px 20px;
  border-radius: var(--radius-full);
  background: var(--nav-bg);
  color: #000;
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 500;
  line-height: 1.5;
  text-decoration: none;
}

/* Figma: warm white panel, radius 24, ~616×380, holds the graph art */
.tool-card-media {
  flex: 1 1 auto;
  height: 380px;
  /* cool near-white sheet — the graph sits on paper, and the contrast now
     comes from the muted slate card around it rather than a tinted panel */
  background: #FBFCFD;
  border-radius: var(--radius-md);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.tool-card-media svg { width: 100%; height: 100%; }

.unfold-graph { width: 100%; height: 100%; }
.unfold-graph canvas { width: 100%; height: 100%; display: block; }

/* ============================================================
   Footer (shared)
   Figma: single black bar — padding 24px 64px, space-between, centered.
   Left: white decart logo + social icons. Right: 11px tracked caps links
   separated by a 4px dot.
   ============================================================ */
.site-footer { background: #000; }

.site-footer-inner {
  padding-top: var(--space-xl);
  padding-bottom: var(--space-xl);
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.site-footer-logo { display: flex; align-items: center; gap: var(--space-xl); }
.site-footer-logo img { height: 22px; filter: brightness(0) invert(1); }

.site-footer-social { display: flex; align-items: center; gap: var(--space-lg); }
.site-footer-social a { color: #fff; display: inline-flex; opacity: 0.9; }
.site-footer-social a:hover { opacity: 1; }
.site-footer-social svg { width: 18px; height: 18px; }

/* Figma: Roobert 11 / 500 / lh 105% / ls 2.75px / uppercase / #FFF */
.site-footer-legal {
  display: flex;
  align-items: center;
  gap: var(--space-xl);
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 500;
  line-height: 1.05;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  color: #fff;
}
.site-footer-legal a { text-decoration: none; color: #fff; }
.site-footer-legal .dot { width: 4px; height: 4px; border-radius: 50%; background: #fff; }

/* ============================================================
   ARTICLE PAGE
   ============================================================ */

/* Figma article accent — a softer blue than the brand blue */
:root {
  --accent-article: #3D5ECA;

  /* Reading type on the article runs 15% over the Figma spec, on review.
     One knob so every size on the page moves together and the vertical
     rhythm between them is preserved. Component chrome — slider pills,
     axis labels, mono readouts — is deliberately NOT on this scale: those
     sit inside fixed-width widgets where 15% is a collision, not a size. */
  --tscale: 1.15;

  /* Title and lede share one measure so their right edges line up. */
  --article-measure: 644px;

  /* The sticky quick-links rail runs 10% under the body's scale, on
     review. It is navigation sitting beside the reading, so it should
     be findable without competing with it. Applied on top of --tscale
     rather than instead of it, so the rail keeps tracking the body if
     that ever moves again. The 266px column is untouched — the grid
     (978 + 68 + 266) still lands on the content column exactly. */
  --rail-scale: 0.9;

  /* How far a publication cover zooms on hover — the one knob for how
     loud that hover is.

     It started at 1.05, which is the usual figure for a card hover and was
     invisible here. These covers are sparse line-work on a flat field, so a
     5% scale moves a few thin strokes by 16px and leaves the other ~90% of
     the frame, which is flat colour, completely unchanged. Photographs
     carry texture to every corner and can sell 5%; line art cannot.
     1.14 moves the cover's edge by 45px instead of 16.

     The canvases are drawn at devicePixelRatio, so a 14% upscale still
     leaves them sharper than CSS pixels — there is no softening cost. */
  --pub-cover-zoom: 1.14;
}

/* Figma 437:7933 — header pt 20, then the breadcrumb row, then a 72 gap
   to the content. The breadcrumb was removed on design review, so what
   is left of that stack is 20 + 72. */
.article-shell {
  padding-top: 92px;
  position: relative;
}

/* Figma header: [title column 754] + gap 24 + [hero demo 533],
   with a hairline divider closing the fold */
.article-header {
  display: flex;
  /* The hero is taller than the title column, and top-aligning them dumped
     all of that difference into one dead band under the lede. Centred, the
     leftover is split above and below, so neither side reads as a gap. */
  align-items: center;
  gap: var(--space-xl);
  justify-content: space-between;
  /* Figma 437:7939 — gap 80 between the header row and the rule. The
     matching 80 below it is on .article-main. */
  padding-bottom: var(--space-5xl);
  border-bottom: 1px solid var(--divider);
}

/* Figma 437:7947 — title column 754 */
.article-header-main { flex: 1 1 auto; max-width: 754px; }

/* Figma 437:7942 — gap 12, then 24 down to the title.
   The two halves are deliberately NOT the same face: the date is Roobert,
   the byline is Season. That mismatch is the design, not an oversight —
   it is what the "wrong font" review note was pointing at. */
.article-meta {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  margin-bottom: var(--space-xl);
}

/* Figma 437:7943 — Roobert Regular 15 / 145% / -0.075px / rgba(0,0,0,.55) @ 80% */
.article-meta .article-date {
  font-family: var(--font-body);
  font-size: calc(15px * var(--tscale));
  line-height: 1.45;
  letter-spacing: -0.005em;
  color: rgba(0, 0, 0, 0.55);
  opacity: 0.8;
}

/* Figma 437:7945 — Season VF Mix Medium 16 / 145% / -0.08px / rgba(0,0,0,.7) */
.article-meta .article-byline {
  font-family: var(--font-display);
  font-weight: var(--w-mix-medium);
  font-size: calc(16px * var(--tscale));
  line-height: 1.45;
  letter-spacing: -0.005em;
  color: rgba(0, 0, 0, 0.7);
}

/* Figma 437:7944 — 4px, #4D4C4B */
.article-meta .dot { width: 4px; height: 4px; border-radius: 50%; background: #4D4C4B; }

/* Figma 437:7948 — Season VF Mix Regular / 116% / -1.68px / #101010.
   Size is 64 rather than the frame's 56, per the review note asking for
   64, then 1.2x on top of that on the second review. It takes the 1.2
   only, not 1.2 x --tscale: the headline was called out by itself, and
   compounding the two would put it at 88px, which stops being a headline
   and starts being a wordmark.

   The measure is shared with the lede so the two blocks end on the same
   right edge — an unconstrained title overhangs the paragraph under it,
   which is what the review was pointing at. */
.article-title {
  font-family: var(--font-display);
  font-size: calc(64px * 1.2);
  font-weight: var(--w-mix-regular);
  line-height: 1.16;
  letter-spacing: -0.03em;
  color: #101010;
  max-width: var(--article-measure);
  margin-bottom: var(--space-xl);
}

/* Headlines are one colour on design review, so .accent no longer tints
   the title — only section headings inside the body still take it. The
   span itself is left in place: it marks the emphasised phrase, and
   restoring the tint is one selector if that call is reversed. */
.article-body h2 .accent { color: var(--accent-article); }

/* Figma 437:7949 — Roobert Regular 16 / 152% / #4f4f4f @ 90% / w 644 */
.article-lede {
  font-family: var(--font-body);
  font-size: calc(16px * var(--tscale));
  line-height: 1.52;
  color: #4f4f4f;
  opacity: 0.9;
  max-width: var(--article-measure);
}

/* Figma 437:7950 — 533 x 307, radius 32 */
.article-hero-media {
  flex: 0 0 533px;
  max-width: 533px;
  aspect-ratio: 533/307;
  border-radius: var(--radius-lg);
  background: #E3E0F2;
  overflow: hidden;
}

.article-hero-media svg,
.article-hero-media canvas { width: 100%; height: 100%; display: block; }

/* ------------------------------------------------------------
   Hero interactive slider — Figma 503:2849
   ------------------------------------------------------------
   This component is the article's cover: it sits in the header slot
   where the decorative canvas used to be. Figma draws it 720 wide as a
   standalone spec; the header slot is 533, so the chrome values below
   (26 inset, 48 radius, 33 slider inset) are kept literal and the width
   flows — scaling them down with the slot would have taken the 16px
   prompt copy with it.
   ------------------------------------------------------------ */
.article-hero-demo {
  flex: 0 0 533px;
  max-width: 533px;
}

/* Figma 503:2850 — a #f5f5f5 pill, no border, text inset 24 */
.article-hero-demo .demo-prompt-bar {
  display: block;
  background: #f5f5f5;
  border: 0;
  border-radius: 48px;
  padding: 20px 24px;
  margin-bottom: var(--space-xl);
}

/* Figma 503:2851 — Roobert Regular 16 / 152% / #0a0a0a at 80%.
   Upright, not italic: the quotation marks already mark it as quoted. */
.article-hero-demo .prompt-text {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.52;
  font-style: normal;
  color: #0a0a0a;
  opacity: 0.8;
}

/* ---- The cover chrome, shared with the "Try it yourself" cards ----

   Everything from here down is written for two components at once: the
   hero cover and each .example-card in the demo grid. They are the same
   object — a grey card holding a video with two readout pills over it and
   an intensity track beneath — so they are styled from one place rather
   than described twice and left to drift apart.

   The literal Figma chrome (48 radius, 26 inset, 12 track, 24 thumb)
   carries over without scaling because the two cards are within a few
   percent of the same width: the hero slot is 533, and an example card in
   the two-column grid inside the 1010 body is (1010 - 32) / 2 = 489. */

/* Figma 503:2852 — 26 around the video, 41 under the slider */
.article-hero-demo .video-slider-wrapper,
.example-card {
  background: #f5f5f5;
  border: 0;
  border-radius: 48px;
  padding: 26px 26px 41px;
}

/* The layers swap instantly. A crossfade would put two half-opaque copies
   over the card fill at once, which reads as the picture washing out on
   every step; with the clips frame-aligned there is nothing for a fade to
   smooth over anyway. */
/* Opacity, z-order and transition on the hero's sixteen layers are set
   inline per swap by article-interactive.js — a dissolve when the sweep
   drives it, a hard cut under a hand. The stylesheet only has to stay out
   of the way, so the shared .15s default below is cancelled here. */
.article-hero-demo .hero-video-layer { transition: none; }

.article-hero-demo .video-display,
.example-card .video-display {
  aspect-ratio: 668/374;
  border-radius: 16px;
  margin-bottom: 30px;
}
/* The hero's placeholder is keyed to the first frame of the bunny clip so
   the swap-in is invisible. The demo grid runs six unrelated clips, so it
   keeps the neutral well instead of flashing beige under a snow scene. */
.article-hero-demo .video-display { background-color: #F5F1E5; }

/* Both readouts ride on the video. Figma 503:2871 sets the right-hand one;
   the intensity pill mirrors it on the left, which is where the reading
   went when the labels row lost its middle slot. */
/* Only the hero carries these. The demo cards dropped them: at six cards
   the pills repeated the slider's own position twelve times over, and the
   card is small enough that they crowded the picture. */
.article-hero-demo .video-alpha-badge,
.article-hero-demo .video-intensity-badge {
  position: absolute;
  top: 24px;
  display: flex;
  align-items: center;
  height: 28px;
  padding: 0 12px;
  border: 0;
  border-radius: 70px;
  background: var(--nav-bg);
  backdrop-filter: none;
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 500;
  line-height: 1.05;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  color: var(--neutral-800);
  white-space: nowrap;
}
/* Uppercasing this pill turns the lowercase alpha into a capital one,
   which renders as a Latin A — and alpha is the quantity the whole
   article is about. The rest of the string is digits and an equals sign,
   so nothing is lost by opting it out. */
.article-hero-demo .video-alpha-badge {
  right: 24px;
  text-transform: none;
  letter-spacing: 0.18em;
}
.article-hero-demo .video-intensity-badge { left: 24px; }
.article-hero-demo .video-intensity-badge strong {
  margin-left: 0.4em;
  font-weight: 500;
  color: var(--accent-primary);
}

/* The slider sits 7 further in than the video — Figma insets it 33 from
   the card edge against the video's 26. */
.article-hero-demo .slider-control,
.example-card .slider-control { padding: 0 7px; }

/* Figma 503:2873/2874 — Roobert Medium 12 / 1.8px tracking / #5d5d5d */
.article-hero-demo .slider-labels,
.example-card .slider-labels {
  align-items: center;
  font-size: 12px;
  font-weight: 500;
  line-height: 1.05;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: #5d5d5d;
  margin-bottom: 25px;
}

/* A 12px track with a 24px thumb. The track is painted by the wrapper's
   ::before and the progress by .slider-fill, with the input itself left
   transparent — giving the input a background would bury the fill, since
   the fill has to stay under the thumb the input draws. */
.article-hero-demo .slider-track-wrapper,
.example-card .slider-track-wrapper {
  height: 24px;
}
.article-hero-demo .slider-track-wrapper::before,
.example-card .slider-track-wrapper::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  height: 12px;
  border-radius: 62px;
  background: var(--nav-bg);
}
.article-hero-demo .styled-slider,
.example-card .styled-slider {
  height: 24px;
  background: transparent;
}
.article-hero-demo .slider-fill,
.example-card .slider-fill {
  height: 12px;
  border-radius: 62px;
  background: var(--accent-primary);
}
.article-hero-demo .styled-slider::-webkit-slider-thumb,
.example-card .styled-slider::-webkit-slider-thumb {
  width: 24px;
  height: 24px;
  border: 3.6px solid #fff;
  background: var(--accent-primary);
  box-shadow: 0 0 0 1px #d4d4d4, 0 0 5.2px rgba(255, 255, 255, 0.2);
}
.article-hero-demo .styled-slider::-moz-range-thumb,
.example-card .styled-slider::-moz-range-thumb {
  width: 24px;
  height: 24px;
  border: 3.6px solid #fff;
  background: var(--accent-primary);
  box-shadow: 0 0 0 1px #d4d4d4;
}

/* ---- What is specific to a .example-card ----
   Only the grid it sits in and the prompt line above the video; the hero
   carries its prompt in the article's own copy, the cards each need their
   own. Set to the hero's prompt-bar spec (Figma 503:2851 — Roobert 16 /
   152% / #0a0a0a at 80%, upright rather than italic, since the quotation
   marks already mark it as quoted), reserving two lines so six cards of
   uneven prompt length still line their videos up across the row. */
/* Two up while there is room for it, one up when there isn't, rather than
   two up until the mobile breakpoint. The body column is squeezed by the
   quick-links rail, so a hard two-column grid was still drawing 196px
   cards at a 901px viewport.

   The floor is set by the prompts: the longest of them sets 356px of text,
   which needs 408px of card once the 26px insets are paid for. Below that
   a prompt wraps to two lines and drops its video below its neighbour's,
   so the grid goes one-up instead — where every prompt has room to spare.
   Shorten or lengthen a prompt and this number is what has to move. */
.examples-grid {
  display: grid;
  /* min() keeps the 360 floor from forcing a track wider than the column
     itself on a narrow phone, which would push the page sideways. */
  grid-template-columns: repeat(auto-fit, minmax(min(410px, 100%), 1fr));
  gap: var(--space-2xl);
}
.example-prompt {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.52;
  font-style: normal;
  color: #0a0a0a;
  opacity: 0.8;

  /* Every prompt is written to fit one line at the two-column width, so
     one line is all that is reserved — reserving two put a visible hole
     between the text and the video in every card.

     The reserve and the bottom alignment are still here rather than being
     dropped for a plain margin, because they are what keeps the videos on
     a shared line: if a prompt does wrap at some width, the text grows
     upward into the card's top padding and the gap beneath it stays 18px,
     instead of pushing that card's video below its neighbour's. */
  min-height: 1.52em;
  display: flex;
  align-items: flex-end;
  margin-bottom: 18px;
}

/* ---- Second fold: body + quick-links rail ---- */
/* The rail is NOT part of the headline/cover fold — it enters with the
   article body and sticks from there on. */
/* Figma grid: body column ≈ 960 on the left edge, ~176 gap, 177 rail right.
   Body widened to 1010 to hold a sensible measure at the larger type, paid
   for by the rail shrinking: 1010 + 48 + 239 = 1297, inside the 1312
   content column. */
/* Figma 437:7952 — the body runs from the 64 gutter to x=1042 (978 wide),
   the rail sits at 1110 and is 266 wide, which lands its right edge on the
   opposite gutter. 978 + 68 + 266 = 1312, the content column exactly.
   The 80 top is the other half of the pair around the header rule. */
.article-main {
  display: grid;
  grid-template-columns: minmax(0, 978px) 266px;
  gap: 68px;
  align-items: start;
  padding-top: var(--space-5xl);
}

/* Figma 437:7871 "Table of content" — a 266 frame whose rows are only 177
   wide; the divider is the one thing that spans the full 266. The rail was
   previously scaled 1.5x off this spec and is now back on it, which is what
   the "decrease font size / darker" review notes were asking for.
   Vertical rhythm is a flat 12 between title, list, rule and Share, with
   each row carrying its own 8 top and bottom. */
.quick-links {
  position: sticky;
  top: 120px;
  width: 266px;
}

.quick-links-title {
  width: 177px;
  padding: 6px 0;
  font-family: var(--font-body);
  font-size: calc(14px * var(--tscale) * var(--rail-scale));
  font-weight: 500;
  line-height: 1.05;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  color: #313131;
  opacity: 0.7;
  margin-bottom: var(--space-sm);
}

.quick-links-list {
  display: flex;
  flex-direction: column;
  padding-bottom: var(--space-sm);
  border-bottom: 1px solid var(--divider);
  margin-bottom: var(--space-sm);
}

.quick-links-list a,
.quick-links-share {
  display: flex;
  align-items: center;
  gap: var(--space-xs);
  width: 177px;
  padding: var(--space-xs) 0;
  font-family: var(--font-body);
  font-size: calc(16px * var(--tscale) * var(--rail-scale));
  line-height: 1.5;
  color: #101010;
  opacity: 0.9;
  text-decoration: none;
}

.quick-links-share {
  background: none;
  border: none;
  cursor: pointer;
  text-align: left;
}

.quick-links-list a:hover,
.quick-links-share:hover { color: var(--accent-article); opacity: 1; }

/* Figma icons are 24 square and #585857, a step lighter than the label */
.quick-links .ql-icon {
  flex: 0 0 calc(24px * var(--rail-scale));
  width: calc(24px * var(--rail-scale));
  height: calc(24px * var(--rail-scale));
  color: #585857;
}
.quick-links .ql-icon svg { display: block; width: 100%; height: 100%; }

/* ---- Article body / text blocks ---- */
/* Figma: the body column runs ~960 wide; the header title stays at 754 */
.article-body {
  max-width: 1010px;
}

/* Figma 437:7953 spaces subheadered groups by 48. Opened to 64 on review —
   the type ran 15% larger than the frame after the same review, and 48 was
   sized against the smaller setting, so the sections had closed up.

   That fixes the article's vertical rhythm at a clean 16 / 32 / 64: between
   paragraphs, around anything embedded in them, between sections. Each step
   is double the last, so the page reads as three levels of separation and
   not as a dozen unrelated gaps. */
.text-block { margin-bottom: var(--space-4xl); }

/* The middle step of that rhythm, applied in one place.

   These components arrived from three different places — the block kit,
   the ported AdaOr demos, the figure styles — each carrying its own idea
   of how far to sit from the copy: 48 for figures, 32 for equations and
   comparisons, 24 for the filmstrip and callouts, and nothing at all for
   the examples grid, which left it at the paragraph's own 16. Five
   intervals doing one job. */
.article-body .figure,
.article-body .figure-grid,
.article-body .callout,
.article-body .no-knob-demo,
.article-body .equation-block,
.article-body .comparison-block,
.article-body .tech-cards,
.article-body .surface-container,
.article-body .examples-grid {
  margin-top: var(--space-2xl);
  margin-bottom: var(--space-2xl);
}

/* The template carries no numbered chip beside its subheaders. The build
   still counts sections, so restoring them is one span in build.js. */
.section-chip { display: none; }

/* Figma 437:7955 — Season VF Mix Medium 22 / 145% / -0.11px / rgba(0,0,0,.9),
   16 down to the copy it introduces */
.article-body h2 {
  font-family: var(--font-display);
  font-size: calc(22px * var(--tscale));
  font-weight: var(--w-mix-medium);
  line-height: 1.45;
  letter-spacing: -0.005em;
  color: rgba(0, 0, 0, 0.9);
  margin-bottom: var(--space-md);
}

/* Every heading in the body is one size, on review. h3 used to sit a step
   under h2, which meant the single "###" in the AdaOr article read as a
   smaller heading among six larger ones — as a mistake rather than as a
   level. Its top margin is the only thing left distinguishing it, and that
   is the same 32 as everything else embedded in the prose.

   The consequence worth knowing: "###" and "##" now look identical, so the
   subordinate level exists in the markup only. If it ever needs to show
   again, the lever is weight or colour — not size, which is what created
   this. */
.article-body h3 {
  font-family: var(--font-display);
  font-size: calc(22px * var(--tscale));
  font-weight: var(--w-mix-medium);
  line-height: 1.45;
  letter-spacing: -0.005em;
  color: rgba(0, 0, 0, 0.9);
  margin: var(--space-2xl) 0 var(--space-md);
}

.article-body .section-eyebrow {
  display: block;
  margin-bottom: var(--space-md);
}

/* Figma: Roobert 16 / 152% / #333 */
/* Figma 437:7956 — Roobert Regular 16 / 152% / -0.32px / #4f4f4f @ 70% */
.article-body p {
  font-family: var(--font-body);
  font-size: calc(16px * var(--tscale));
  line-height: 1.52;
  letter-spacing: -0.02em;
  color: #4f4f4f;
  opacity: 0.7;
  margin-bottom: var(--space-md);
}

.article-body p:last-child { margin-bottom: 0; }

/* Lists take the paragraph's whole voice — same face, size, colour, tracking
   and opacity — so a bulleted passage reads as prose that happens to be
   itemised rather than as a different kind of text. They had been left on
   the browser default, which put them at 16 against the body's 18.4 and
   hung the markers outside the text column, because the default `ul` has
   no padding for them to sit in. The authoring template offers lists, so
   they have to arrive as finished as the paragraphs around them. */
.article-body ul,
.article-body ol {
  margin: 0 0 var(--space-md);
  padding-left: 1.4em;
  color: #4f4f4f;
  opacity: 0.7;
}

.article-body li {
  font-family: var(--font-body);
  font-size: calc(16px * var(--tscale));
  line-height: 1.52;
  letter-spacing: -0.02em;
  margin-bottom: var(--space-xs);
}

.article-body li:last-child { margin-bottom: 0; }
.article-body ul:last-child,
.article-body ol:last-child { margin-bottom: 0; }

/* Figma: in-text links use the article accent blue */
.article-body a:not(.article-closing-cta) {
  color: var(--accent-article);
  text-decoration: none;
  border-bottom: 1px solid rgba(61,94,202,0.3);
}
.article-body a:not(.article-closing-cta):hover { border-color: var(--accent-article); }

.article-body strong { color: var(--text-primary); font-weight: 600; }
.article-body em { color: var(--text-primary); font-style: italic; }

.article-body code {
  font-family: var(--font-mono);
  font-size: 0.85em;
  background: var(--bg-tertiary);
  padding: 2px 6px;
  border-radius: var(--radius-sm);
  color: var(--accent-primary);
}

/* Figma 437:7980 — media placeholders are radius 28 */
.figure { margin: var(--space-3xl) 0; }
.figure img, .figure video { width: 100%; border-radius: 28px; }
.figure-caption {
  margin-top: var(--space-sm);
  font-family: var(--font-body);
  font-size: calc(16px * var(--tscale));
  color: var(--text-muted);
  line-height: 1.5;
}

/* Same button as Unfold on the home page (.tool-card-cta): pill, Roobert
   16/500, sentence case, and the same dark inversion on hover.

   One deviation, and it is forced: Unfold's resting fill is --nav-bg, which
   is also the page background here — on the article that pill would be the
   paper it sits on. The hairline gives it an edge, drawn in the same ink the
   hover fills with, so the two states are one idea rather than two. Unfold
   needs no border because it sits on the blue tool card. */
.article-closing-cta {
  display: inline-flex;
  align-items: center;
  padding: 12px 20px;
  border-radius: var(--radius-full);
  background: var(--nav-bg);
  border: 1px solid var(--neutral-800);
  color: #000;
  font-family: var(--font-body);
  font-size: calc(16px * var(--tscale));
  font-weight: 500;
  line-height: 1.5;
  text-decoration: none;
  margin-top: var(--space-2xl);
  transition: background-color 0.25s ease, color 0.25s ease;
}

/* The CTA is inline-flex, so its top margin does not collapse with the
   block above it — the two added up to 40 where everything else embedded
   in the prose sits at 32. Zeroing whatever precedes it puts the gap
   wholly under the rule above, whether the CTA follows a paragraph or a
   figure. */
.article-body *:has(+ .article-closing-cta) { margin-bottom: 0; }

.article-closing-cta:hover {
  background-color: var(--neutral-800);
  color: var(--nav-bg);
}
.article-closing-cta:hover { background: var(--accent-deep); }

/* ============================================================
   Ported interactive components (AdaOr demos)
   restyled onto the new tokens above — logic lives in
   article-interactive.js and is unchanged.
   ============================================================ */

.no-knob-demo { margin: var(--space-xl) 0; }
.no-knob-strip { display: flex; align-items: flex-start; gap: var(--space-xl); }
.no-knob-item { flex: 1; min-width: 0; }
.no-knob-input { flex: 0 0 auto; width: 22%; }
.no-knob-edited-group { flex: 1; display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-sm); }
.no-knob-video-wrap {
  width: 100%; aspect-ratio: 16/9; border-radius: 12px;
  overflow: hidden; border: none; background: var(--bg-secondary);
}
.no-knob-video-wrap video { width: 100%; height: 100%; object-fit: cover; display: block; }
.no-knob-label { margin-top: var(--space-sm); font-size: calc(16px * var(--tscale)); color: var(--text-secondary); text-align: center; font-style: italic; line-height: 1.4; }
.no-knob-input .no-knob-label { font-style: normal; font-weight: 500; color: var(--text-muted); }

/* Prompt pill — Figma 612:3969: #F5F5F5, 56 tall, r16, copy 16 upright */
.demo-prompt-bar, .surface-prompt-bar {
  display: flex; align-items: center; gap: var(--space-sm);
  background: #f5f5f5; border: 0;
  border-radius: 16px; min-height: 56px; padding: 8px 28px;
  margin-bottom: var(--space-lg);
}
.prompt-text { font-size: 16px; line-height: 1.52; color: #0a0a0a; opacity: 0.8; }
/* the frame's pill spans only the plot column (477 of 978) */
.surface-prompt-bar { width: calc(50% - var(--space-xl) / 2); }

.video-slider-wrapper {
  background: var(--surface-card); border: 1px solid var(--border-default);
  border-radius: var(--radius-md); padding: var(--space-xl); overflow: hidden;
}
.video-display {
  position: relative; width: 100%; aspect-ratio: 16/9; background: var(--bg-secondary);
  border-radius: var(--radius-sm); overflow: hidden; margin-bottom: var(--space-lg);
}
.hero-video-layer, .example-video-layer {
  position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity .15s ease; pointer-events: none;
}
.hero-video-layer.active, .example-video-layer.active { opacity: 1; }
.video-alpha-badge {
  position: absolute; top: var(--space-md); right: var(--space-md);
  background: rgba(255,255,255,0.9); backdrop-filter: blur(10px);
  color: var(--accent-primary); font-family: var(--font-mono); font-size: 13px;
  font-weight: 500; padding: 5px 10px; border-radius: var(--radius-sm);
  border: 1px solid var(--border-default);
}

.slider-control { padding: 0 var(--space-xs); }
.slider-labels {
  display: flex; justify-content: space-between; font-size: 10px; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: .1em; font-weight: 500; margin-bottom: var(--space-sm);
}
.slider-track-wrapper, .example-slider-wrapper { position: relative; height: 36px; display: flex; align-items: center; }
.styled-slider {
  -webkit-appearance: none; appearance: none; width: 100%; height: 3px;
  background: var(--border-subtle); outline: none; cursor: pointer; position: relative; z-index: 2;
}
.styled-slider::-webkit-slider-thumb {
  -webkit-appearance: none; appearance: none; width: 20px; height: 20px;
  background: var(--accent-primary); border-radius: 50%; cursor: grab; border: 3px solid var(--surface-card);
  box-shadow: 0 0 0 1px var(--accent-primary), 0 2px 6px rgba(37,72,246,0.25);
}
.styled-slider::-moz-range-thumb {
  width: 20px; height: 20px; background: var(--accent-primary); border-radius: 50%; cursor: grab;
  border: 3px solid var(--surface-card); box-shadow: 0 0 0 1px var(--accent-primary);
}
.slider-fill {
  position: absolute; left: 0; top: 50%; transform: translateY(-50%); height: 3px;
  background: var(--accent-primary); pointer-events: none; z-index: 1; width: 0%;
}
.slider-value-display { text-align: center; font-size: 13px; color: var(--text-muted); margin-top: var(--space-sm); }
.slider-value-display strong { color: var(--accent-primary); font-family: var(--font-mono); font-weight: 500; }

/* Figma: equation panels are #F5F5F5 rounded cards, no border, with a tiny
   tracked blue label and mono formula with colored tokens */
.equation-block {
  margin: var(--space-2xl) 0; background: var(--surface-panel); border: none;
  border-radius: 16px; padding: var(--space-2xl); text-align: center;
}
.equation-block-wide { max-width: var(--max-w-wide); }
.equation { display: flex; flex-direction: column; align-items: center; gap: var(--space-sm); }
.eq-label { font-size: 10px; font-weight: 500; text-transform: uppercase; letter-spacing: .2em; color: var(--accent-article); }
.eq-content { font-family: var(--font-mono); font-size: 15px; color: var(--text-primary); padding: var(--space-sm) 0; }
.equation-caption { margin-top: var(--space-md); font-size: calc(12px * var(--tscale)); color: var(--text-muted); line-height: 1.6; }
.eq-highlight-origin { color: var(--accent-article); }
.eq-highlight-cond { color: #d97706; }
.eq-highlight-id { color: #059669; }
.equation-annotations {
  margin-top: var(--space-lg); display: flex; flex-direction: column; gap: var(--space-sm);
  align-items: flex-start; max-width: 360px; margin-left: auto; margin-right: auto;
}
.eq-annotation { display: flex; align-items: center; gap: var(--space-sm); font-size: 13px; color: var(--text-secondary); line-height: 1.4; }
.eq-annotation strong { font-family: var(--font-mono); font-size: 12px; color: var(--text-primary); }
.eq-dot { width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0; }
.eq-dot-origin { background: var(--accent-primary); }
.eq-dot-cond { background: #d97706; }
.eq-dot-w { background: var(--text-primary); }

/* Figma filmstrip panels: "Standard CFG" on #F5F5F5, "AdaOr (Ours)" on a
   light blue panel, radius 16, name left + caption right, 5-up strip */
.comparison-block { margin-top: var(--space-2xl); display: flex; flex-direction: column; gap: var(--space-md); }
.comparison-row { background: var(--surface-panel); border: none; border-radius: 16px; padding: var(--space-md); }
.comparison-row-highlight { background-color: #E0E9FB; }
.comparison-header { display: flex; align-items: baseline; justify-content: space-between; gap: var(--space-md); margin-bottom: var(--space-sm); padding: 2px 4px 0; }
.comparison-icon { display: none; }
.comparison-header h4 { font-family: var(--font-body); font-size: calc(14px * var(--tscale)); font-weight: 500; color: var(--text-primary); }
.comparison-row-highlight .comparison-header h4 { color: var(--accent-article); }
.comparison-strip { display: flex; gap: var(--space-xs); }
.comparison-strip img { flex: 1; width: 0; aspect-ratio: 1; object-fit: cover; object-position: center 22%; border-radius: 8px; border: none; }
.comparison-axis { display: flex; justify-content: space-between; margin-top: var(--space-xs); padding: 0 4px; font-size: 9px; font-weight: 500; color: var(--text-muted); text-transform: uppercase; letter-spacing: .12em; }
.filmstrip-arrow { color: var(--border-subtle); }
.comparison-caption { font-size: calc(12px * var(--tscale)); color: #585857; line-height: 1.4; text-align: right; margin: 0; }
.comparison-caption em { color: var(--text-primary); font-style: italic; }

/* Figma: 3 gray cards with small colored number chips, mono term title
   over a hairline divider, then the description */
.tech-cards { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--space-md); margin-top: var(--space-xl); }
.tech-card { background: var(--surface-panel); border: none; border-radius: 16px; padding: var(--space-lg); }
.tech-card-num {
  width: 22px; height: 22px; border-radius: var(--radius-sm); background: var(--accent-article); color: #fff;
  display: flex; align-items: center; justify-content: center; font-size: 11px; font-weight: 600; margin-bottom: var(--space-md);
}
.tech-card h4 { font-family: var(--font-mono); font-size: calc(13px * var(--tscale)); font-weight: 500; margin-bottom: var(--space-sm); padding-bottom: var(--space-sm); border-bottom: 1px solid var(--neutral-200); color: var(--text-primary); }
.tech-card p { font-size: calc(13px * var(--tscale)); color: #585857; line-height: 1.5; }

/* ---- "Why you need both knobs" — Figma 588:114 ----
   Two 477-wide columns on a 978 row, 24 gap. The plot is a grey card
   whose inner drawing area is 314x284; every placement below is that
   frame's number as a percentage, so the whole picture scales as one.
   All eleven apples crop the same sprite (images/apples2/plot.webp) via
   background-position — the strip is one element, not six. */
.surface-container { max-width: var(--max-w-wide); margin: var(--space-2xl) auto 0; }
.surface-layout { display: flex; gap: var(--space-xl); align-items: stretch; }

/* The plot card. 477x360, #F5F5F5 flat (Figma parity — see the panels
   note: no blending anywhere). */
.surface-grid-side {
  flex: 1;
  min-width: 0;
  background: #f5f5f5;
  border-radius: 32px;
  aspect-ratio: 477 / 360;
  display: flex;
  align-items: center;
  justify-content: center;
}
.surface-grid {
  position: relative;
  /* 314x284 inside the 477x360 card */
  width: 65.8%;
  aspect-ratio: 314 / 284;
  cursor: crosshair;
}
.surface-paths { position: absolute; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 1; overflow: visible; }
.path-cfg { stroke: #0a0a0a; stroke-width: 1; vector-effect: non-scaling-stroke; }
.path-adaor { stroke: #3d5eca; stroke-width: 1; vector-effect: non-scaling-stroke; }

/* The apples. Sprite geometry: the source image maps to a 312x312 box;
   the strip window is 312x54 at (2,0), each diagonal window 48x55. The
   background-size/position pairs below are those Figma crops converted
   to percentages, so they hold at any rendered size. */
.surface-strip,
.surface-apple {
  position: absolute;
  background-image: url('images/apples2/plot.webp');
  background-repeat: no-repeat;
  pointer-events: none;
  z-index: 3;
}
.surface-strip {
  left: 0.64%; top: 0; width: 99.36%; height: 19.01%;
  background-size: 100% 577.78%;
  background-position: 0 9.69%;
}
.surface-apple { width: 15.29%; height: 19.37%; background-size: 650% 567.27%; }
.surface-apple-d1 { left: 0;      top: 80.63%; background-position: 0       97.67%; }
.surface-apple-d2 { left: 19.11%; top: 63.38%; background-position: 20.94%  84.61%; }
.surface-apple-d3 { left: 38.22%; top: 46.13%; background-position: 42.47%  69.18%; }
.surface-apple-d4 { left: 57.32%; top: 28.87%; background-position: 63.98%  52.41%; }
.surface-apple-d5 { left: 76.43%; top: 11.62%; background-position: 82.19%  32.68%; }

/* Path tags: white chips, CFG ink / AdaOr blue (588:3811/3813) */
.surface-path-tag {
  position: absolute;
  z-index: 2;
  pointer-events: none;
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  background: var(--nav-bg);
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  line-height: 1.05;
  color: #313131;
}
.surface-path-tag-cfg { left: 0.64%; top: 21.13%; }
.surface-path-tag-adaor { left: 78.34%; top: 85.21%; color: #3d5eca; text-transform: none; }

/* Axis labels live inside the plot now (588:3823/3824) */
.surface-y-label,
.surface-x-label {
  position: absolute;
  z-index: 2;
  pointer-events: none;
  font-size: 10px;
  font-weight: 500;
  letter-spacing: 0.08em;
  color: #888;
  white-space: nowrap;
}
.surface-y-label {
  left: 0.64%;
  top: 65.4%;
  writing-mode: vertical-rl;
  transform: translateY(-50%) rotate(180deg);
}
.surface-x-label { left: 17.2%; top: 88.5%; text-transform: uppercase; }

/* Knob: 30px, #2548F6, white ring, soft shadow (609:3218) */
.surface-knob {
  position: absolute;
  width: 30px; height: 30px;
  transform: translate(-50%,-50%);
  z-index: 10;
  cursor: grab;
  left: 50%; top: 50%;
  border-radius: 50%;
  background: var(--accent-primary);
  border: 2.4px solid #fff;
  box-shadow: 5px 5px 7px rgba(0,0,0,.2);
  transition: transform .1s;
}
.surface-knob:active { cursor: grabbing; transform: translate(-50%,-50%) scale(1.12); }

/* Right column: source / output cards over their readout cards.
   Figma: cards 228x233 r28, readouts 107 tall r16, gaps 20/21. */
.surface-preview-side {
  flex: 1;
  min-width: 0;
  display: grid;
  /* minmax(0,·), not 1fr: the canvas carries an intrinsic 400px, and a
     bare 1fr track honors it — off the edge of a phone. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
  gap: 20px 21px;
  align-content: space-between;
}
.surface-preview-item {
  position: relative;
  min-width: 0;
  background: #f5f5f5;
  border-radius: 28px;
  aspect-ratio: 228 / 233;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}
.surface-preview-img { display: block; border: 0; background: none; }
.surface-preview-item img.surface-preview-img { width: 98%; height: auto; }
canvas.surface-preview-img { width: 79%; max-width: 100%; aspect-ratio: 1; height: auto; }
.surface-preview-label {
  position: absolute;
  right: 16px; bottom: 16px;
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  background: var(--nav-bg);
  font-family: var(--font-body);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.25em;
  text-transform: uppercase;
  line-height: 1.05;
  color: #313131;
}
.surface-readout-card {
  background: #f5f5f5;
  border-radius: 16px;
  min-height: 107px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 12px 28px;
}
.readout-label { font-family: var(--font-body); font-size: 20px; line-height: 1.4; letter-spacing: -0.04em; color: var(--neutral-900); white-space: nowrap; }
.readout-value { font-family: var(--font-body); font-size: 20px; line-height: 1.4; letter-spacing: -0.04em; color: #4f4f4f; opacity: 0.7; }

/* Verdict cards (588:3845/3857): grey r32 cards, a 32px colored square,
   "CFG path / top edge" title row over a hairline, then the reading. */
.insight-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-xl); margin-top: var(--space-xl); }
.insight-card {
  background: #f5f5f5;
  border: 0;
  border-radius: 32px;
  padding: 28px 28px 32px;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: var(--space-2xl);
}
.insight-marker {
  width: 32px; height: 32px;
  border-radius: 3px;
  display: flex; align-items: center; justify-content: center;
  font-size: 16px; font-weight: 700; color: #fff;
  flex-shrink: 0;
}
.insight-marker-x { background: #f57474; }
.insight-marker-check { background: #8de9ae; }
.insight-marker-check svg { width: 32px; height: 32px; }
.insight-head {
  display: flex;
  gap: 4px;
  font-size: 20px;
  line-height: 1.4;
  letter-spacing: -0.04em;
  padding-bottom: var(--space-sm);
  border-bottom: 1px solid var(--neutral-200);
  /* head sits 32 under the marker, description 32 under the head — the
     card's own gap covers both */
}
.insight-head strong { color: var(--neutral-900); font-weight: 400; }
.insight-head span { color: #4f4f4f; opacity: 0.7; }
.insight-card p { font-size: 15px; color: #4d565f; opacity: 0.9; line-height: 1.4; }

/* The demo cards are styled with the hero cover, up in the ARTICLE COVER
   section — they are the same component and share its rules. */

/* ============================================================
   Blueprint underlay (blue cards)
   A drafting-table diagram — dashed grid, diagonals, long arcs — that
   draws itself in as the card scrolls up. See blueprint.js. Sits behind
   the card's own content, never intercepts pointer events.
   ============================================================ */
.blueprint {
  position: absolute;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  /* Copy on these cards is set left-aligned, so the underlay is faded out
     across the left column and reaches full strength on the open right
     side. Keeps the drawing readable as a drawing without ever competing
     with the text sitting on top of it. */
  -webkit-mask-image: linear-gradient(to right, rgba(0,0,0,0.72) 0%, rgba(0,0,0,0.85) 38%, #000 66%);
  mask-image: linear-gradient(to right, rgba(0,0,0,0.72) 0%, rgba(0,0,0,0.85) 38%, #000 66%);
}
.blueprint canvas { width: 100%; height: 100%; display: block; }

/* The CTA banner changes height on every scroll frame. Sizing its underlay
   to the live box meant ResizeObserver fired each of those frames, and each
   notification reallocated the canvas and re-derived every arc span from
   scratch — enough per-frame work to make the expansion stall in steps.
   Pinning the underlay to the banner's FULL height makes it a fixed
   surface that the growing card simply reveals more of. The diagram's
   geometry then stays put as the card opens instead of being re-solved
   against a box that is still moving. */
.cta-banner .blueprint {
  bottom: auto;
  height: var(--cta-h-max, 780px);
  /* Left/right come from the base rule's inset:0 and must stay there. An
     absolutely positioned child resolves against the PADDING box, whose
     edges are the BORDER edges — padding sits inside it, so the animated
     padding neither moves nor resizes this. Offsetting left by that
     padding (an earlier attempt at a problem that did not exist) shunted
     the whole diagram sideways by --cta-inset and left exactly that much
     dead band down the right edge of the card. */
}

/* the blue cards become the positioning context, and their real content
   is lifted above the underlay */
.cta-banner, .quote-block { position: relative; }
.quote-block { overflow: hidden; }
.cta-banner > *:not(.blueprint),
.quote-block > *:not(.blueprint) { position: relative; z-index: 1; }

/* ============================================================
   Interactive cards
   Hover is reserved for things that actually do something: publication
   cards (link through to the article) and the Unfold panel (has its own
   CTA). Research pillars, stat cards and event rows are inert content,
   so they stay static — a hover state there just promises a click that
   never happens.
   ============================================================ */
/* Unfold: the only thing that responds is the button, and only when the
   pointer is actually on it. The card-wide lift and its two drop shadows
   are gone — the panel is not clickable, so the shadow was advertising a
   hit area that does not exist, and on a light backdrop it read as a
   bright halo rather than depth. */
.tool-card-cta { transition: background-color 0.25s ease, color 0.25s ease; }
.tool-card-cta:hover {
  background-color: var(--neutral-800);
  color: var(--nav-bg);
}

/* Publications: the artwork grows inside a frame that does not move, and
   nothing else changes.

   The card used to lift on a drop shadow and turn its headline blue. Both
   are gone on review: on this paper background the shadow read as a halo
   rather than depth, and recolouring the headline pulled the eye away from
   the cover it was meant to be advertising. What is left says the same
   thing more quietly — there is more of this behind the edge.

   The scale goes on the cover's CONTENTS, not on .pub-card-media, so the
   frame keeps its size and its 24px corner and does the clipping. The demo
   slider is left out of it: that is a UI overlay lying on the artwork, not
   part of the picture, and a control that zooms looks like a bug. */
.pub-card-media > *:not(.cover-demo-slider) {
  transform-origin: center;
  /* Half a second on a plain ease-out. Long enough that the eye can follow
     the movement the whole way — a faster curve reads as a pop and is over
     before it registers — and short enough not to lag the pointer. */
  transition: transform 0.5s cubic-bezier(0.22, 0.61, 0.36, 1);
}

.pub-card:not(.is-upcoming):hover .pub-card-media > *:not(.cover-demo-slider) {
  transform: scale(var(--pub-cover-zoom));
}

/* A publication with no page yet. build.js renders it as a div rather than
   an anchor, so there is no cursor or focus stop to remove; this only has
   to make sure the lift and the blue headline stay out of it, since both
   would read as "this opens" on something that does not. */
.pub-card.is-upcoming { cursor: default; }

/* ---- An unreleased publication (Figma 588:3350) ----

   The cover runs its real animation with a Coming soon chip in the corner;
   only the copy below it is withheld, as skeleton bars with a sheen
   travelling across them. So the card reads as a finished piece of work
   whose writing has not landed yet — which is the actual situation.

   The bars are not a blur over the real text. build.js writes no copy into
   this card at all (see renderCard), so there is nothing underneath them
   to reveal; styling is never trusted with a secret. The cover is the one
   deliberate exception, and it is deliberate. */
.pub-skel-line {
  background-color: var(--skel-base);
  /* A wide, shallow falloff — the sheen occupies most of the gradient
     rather than a narrow band in the middle of it, so there is no visible
     leading or trailing edge to track. What passes is a change in tone,
     not a stripe. */
  background-image: linear-gradient(
    100deg,
    transparent 8%,
    var(--skel-sheen) 50%,
    transparent 92%
  );
  background-repeat: no-repeat;
  background-size: 300% 100%;
  /* Slow enough to read as breathing rather than as something loading
     against a clock. Paired with the soft falloff, a longer pass is what
     keeps it from pulling the eye away from the published card. */
  animation: pub-skel-sweep 4.4s ease-in-out infinite;
}

/* Blue, not grey: the cover directly above these bars is in the site's
   light blues, so the bars are built from the same family. Grey ones read
   as dead UI pasted under a live picture. */
.pub-card.is-upcoming {
  --skel-base: #DEE7F7;
  --skel-sheen: #F2F6FD;
}

/* Left to right. With the gradient at 300% width the sheen sits 1.5 card
   widths along it, so 100% → 0% carries it from just off the left edge to
   just off the right — no visible start or stop. */
@keyframes pub-skel-sweep {
  from { background-position: 100% 0; }
  to { background-position: 0 0; }
}

/* ---- "Coming soon" chip (Figma 588:3404) ----

   Figma builds this from the very same component as the Research chip in
   the meta row below — identical 8/12 padding and 4px corner. Only the
   fill, the ink and one step up in size differ, so it is a modifier on
   .eyebrow rather than a new object. */
.eyebrow-solid {
  padding: 8px 12px;
  border-radius: var(--radius-sm);
  background: var(--accent-primary);
  color: #fff;
  /* 13px with 2.86px of tracking, straight off the frame — a touch larger
     and a touch tighter than the 11px/0.25em the other chips use. */
  font-size: 13px;
  letter-spacing: 0.22em;
}

/* Figma insets it 32 from the top and right of a 644-wide cover. */
.pub-soon-tag {
  position: absolute;
  top: 32px;
  right: 32px;
  z-index: 3;
}

/* The bars stand in for lines of type, so they are measured in em off the
   text they replace: they track the real type scale for free, and the two
   blocks keep the exact height their prose had — the card does not resize
   when the paper lands. */
.pub-skel-line {
  display: block;
  height: 0.66em;
  border-radius: 999px;
}

.pub-card.is-upcoming .pub-card-title,
.pub-card.is-upcoming .pub-card-desc {
  display: flex;
  flex-direction: column;
  justify-content: center;
}
/* One line of the title, three of the summary — the same count the real
   card sets at this width. */
.pub-card.is-upcoming .pub-card-title { height: 1.45em; }
.pub-card.is-upcoming .pub-card-desc { height: 4.56em; }
.pub-card.is-upcoming .pub-card-desc .pub-skel-line + .pub-skel-line {
  margin-top: 0.86em;
}

/* The sheen goes first when motion is turned down. The bars stay: they are
   the message, the movement is only the manner. (The cover's own animation
   already opts itself out — every one of these renderers checks
   prefers-reduced-motion before it starts.) */
@media (prefers-reduced-motion: reduce) {
  .pub-skel-line {
    animation: none;
    background-image: none;
  }
}

/* ============================================================
   Panels are FLAT. There used to be a rule here blending the paper grain
   over every card's background-color at soft-light, and it is gone for a
   reason worth keeping: WebKit does not apply background-blend-mode
   between a background-image and the background-COLOR under it. Blending
   between two image layers works (which is why .tool-card, .quote-block
   and .cta-banner — grain over photo — survive on iOS), but a lone tile
   over a color falls back to painting the tile opaquely. The tile is mean
   252, i.e. the page itself, so on every iPhone all sixteen panels in
   that list rendered as bare page — the "invisible grey cards" QA note,
   twice.

   Chrome DID blend, which made this bug invisible to any measurement made
   in it. The panels below are now authored at the value Chrome used to
   arrive at, so Chrome looks identical and Safari finally agrees:

     old authored → composite under soft-light grain → now authored flat
     #E6E6E6 (--surface-panel members)  → 241.9 → #F2F2F2 (the token)
     #E7EDF9 (.pub-card-media)          →        #F2F6FC
     #F7F8FB (.tool-card-media)         →        #FBFCFD
     #CBC7E6 (.article-hero-media)      →        #E3E0F2
     #ECE4CF (hero video placeholder)   →        #F5F1E5
     #ECECEC (--bg-secondary members)   → 245.1 → flat below
     white   (--surface-card members)   → white — blend was a no-op

   The grain the blend added measured std 0.15 after compositing; its loss
   is not visible. Do not reintroduce a grain layer over these unless it
   is a pre-tinted tile per panel color. */
.video-display,
.example-video-display,
.no-knob-video-wrap { background-color: #F5F5F5; }

/* ---- Responsive ---- */
@media (max-width: 900px) {
  /* Figma 382:3035 — every band on the mobile frame is px-20 */
  :root { --gutter: 20px; }
  .home-hero-grid { flex-direction: column; }
  .home-hero-title { font-size: 56px; }
  .publications-grid, .pillars-grid, .stats-grid, .examples-grid, .tech-cards, .insight-row { grid-template-columns: 1fr; }
  .cta-banner, .tool-card { flex-direction: column; align-items: flex-start; }
  .cta-banner p, .quote-block blockquote { text-align: left; }
  .article-header { flex-direction: column; }
  .article-hero-media { flex: none; width: 100%; max-width: none; }
  .article-main { grid-template-columns: 1fr; }
  .quick-links { position: static; width: auto; justify-self: stretch; margin: 0 0 var(--space-2xl); order: -1; }
  /* No CTA overrides needed any more. Both ends of the ramp are derived
     rather than authored — the floor from the banner's measured resting
     height (which at this width rewraps to roughly 423px) and the ceiling
     from the viewport — so the same rule adapts to every screen. */
  .article-title { font-size: calc(48px * 1.2); }
  .no-knob-strip { flex-direction: column; }
  .no-knob-input { width: 60%; margin: 0 auto; }
  .surface-layout { flex-direction: column; }

  /* ---- QA: article page, mobile ---- */

  /* Panels that were still washing out on a phone: authored #F5F5F5 (or
     white — .insight-card was LIGHTER than the page it sits on). 242 puts
     each of them 10 below the page's rendered 252, the same step every
     other panel uses. All flat — everything composites the same now that
     the blend rule is gone, so these two groups are one value. */
  .example-card,
  .article-hero-demo .video-slider-wrapper { background-color: #F2F2F2; }
  .insight-card,
  .no-knob-video-wrap { background-color: var(--surface-panel); }

  /* The caption carries the argument of the comparison, but at 18.4px it was
     set larger than the body copy around it. */
  .article-body .comparison-caption { font-size: 15px; }

  /* The plot card scales itself (aspect-ratio on the card, percentages
     inside), so stacking needs nothing beyond the column direction above.
     The knobs cards are authored #F5F5F5 for Figma parity; on the phone
     that is the 7-level step Gal flagged, so they join the example cards
     at 242. The prompt pill spans only its column on desktop — stacked,
     it takes the full measure. */
  .surface-grid-side,
  .surface-preview-item,
  .surface-readout-card,
  .insight-card,
  .surface-prompt-bar { background-color: #F2F2F2; }
  .surface-prompt-bar { width: 100%; }


  /* Every prompt on one line at this measure — at 18.4px four of the six
     wrapped, so they no longer agreed with each other even though the type
     size did. */
  .article-body .example-prompt { font-size: 14px; }
  .tool-card { flex-direction: column; align-items: flex-start; }
  .tool-card-body { flex: 1 1 auto; }
  .tool-card-media { width: 100%; height: 240px; }
  .tool-card h4 { font-size: 36px; }
  .home-section-heading { font-size: 32px; }
  .stats-heading-row { flex-direction: column; align-items: flex-start; }
  .stats-heading-row p { text-align: left; }
  .site-footer-inner { flex-direction: column; gap: var(--space-md); text-align: center; }
  .site-nav-links { gap: var(--space-md); }
}


/* ============================================================
   Mobile navigation
   Below 900px the five nav links collapse behind a three-bar toggle and
   open as a sheet under the bar. Above it nothing changes: the toggle is
   display:none and the links stay the same flex row.
   ============================================================ */
.nav-toggle {
  display: none;
  flex-direction: column;
  justify-content: center;
  gap: 5px;
  width: 44px;              /* smallest comfortable touch target */
  height: 44px;
  padding: 0 10px;
  margin-right: -10px;      /* keeps the bars optically on the grid edge */
  background: none;
  border: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

.nav-toggle span {
  display: block;
  width: 100%;
  height: 1.5px;
  background: var(--text-primary);
  border-radius: 2px;
  transition: transform 0.28s ease, opacity 0.2s ease;
}

/* open: the outer bars cross, the middle one drops out */
.nav-open .nav-toggle span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
.nav-open .nav-toggle span:nth-child(2) { opacity: 0; }
.nav-open .nav-toggle span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }

@media (max-width: 900px) {
  .nav-toggle { display: flex; }

  /* the bar becomes the positioning context for the sheet */
  .site-nav { position: relative; }

  /* Sheet is measured off the nav's own height rather than a guessed
     constant, so it sits flush under the bar at any bar height. Collapsed
     with max-height rather than display:none so it can animate, and with
     visibility so the links are not tab-focusable while it is shut. */
  .site-nav-links {
    position: absolute;
    top: 100%;
    left: 0;
    right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    padding: 0 var(--gutter);
    background-color: var(--nav-bg);
    background-image: var(--paper-texture);
    background-repeat: repeat;
    background-size: var(--paper-tile-size);
    /* No border while shut. max-height:0 collapses the box but the border
       still paints, leaving a 1px hairline ruled across the screen under
       the nav with the menu closed. */
    border-bottom: 0 solid var(--divider);
    max-height: 0;
    overflow: hidden;
    visibility: hidden;
    transition: max-height 0.32s ease, visibility 0s linear 0.32s;
  }

  .nav-open .site-nav-links {
    max-height: 70vh;
    overflow-y: auto;
    visibility: visible;
    border-bottom-width: 1px;
    transition: max-height 0.32s ease, visibility 0s;
  }

  .site-nav-links a {
    padding: 16px 0;
    font-size: 18px;
    border-bottom: 1px solid var(--divider);
  }
  .site-nav-links a:last-child { border-bottom: 0; }

  /* stop the page scrolling behind an open drawer */
  body.nav-open { overflow: hidden; }
}


/* ============================================================
   Mobile relayout — two blocks whose desktop geometry does not survive
   a narrow viewport.
   ============================================================ */
@media (max-width: 900px) {
  /* "Our goal": the headline row is a two-column layout on desktop, with
     a 515px fixed headline and the standfirst taking the remainder. At
     phone widths the headline eats the full row and the standfirst is
     squeezed into a column about one character wide, then clipped. Stack
     them instead. */
  .cta-banner-row {
    flex-direction: column;
    align-items: flex-start;
    gap: var(--space-lg);
  }
  .cta-sticky .cta-banner h3 {
    width: 100%;
    font-size: 32px;
  }
  .cta-banner p {
    flex: none;
    width: 100%;
    text-align: left;
  }
  /* The card's own 48px inner inset is a desktop measure; on a 375px
     screen it plus the gutter eats a third of the width. */
  .cta-sticky .cta-banner {
    --cta-pad-x: calc(var(--cta-inset) + 20px * (1 - var(--p)));
  }

  /* Chaim Baskin quote: 924px tall on an 812px screen — taller than the
     viewport. Two compounding causes, both desktop measures left intact:
     the 48px side padding cut a 375px screen down to a 279px column, and
     32px display type in 279px wraps to sixteen lines. Widening the column
     and dropping the type to 22px takes it to roughly half a screen. */
  .quote-block {
    padding: 36px 24px;
    gap: var(--space-2xl);
    border-radius: 24px;
  }
  .quote-block blockquote {
    font-size: 22px;
    line-height: 1.3;
  }

  /* AdaOr cover: give the floating slider card more of the cover to sit
     in, and take the tracking down so both labels fit on one line. */
  .cover-demo-slider {
    width: min(88%, 320px);
    padding: 14px 16px 16px;
  }
  .cover-demo-labels {
    font-size: 9px;
    letter-spacing: 0.12em;
    margin-bottom: 11px;
  }
  /* Scaled with the cover it sits on: the 32px inset and 13px type are
     sized against a 644-wide cover, and the phone's is barely half that. */
  .pub-soon-tag {
    top: 16px;
    right: 16px;
  }
  .eyebrow-solid {
    padding: 6px 10px;
    font-size: 11px;
    letter-spacing: 0.18em;
  }
}

/* ============================================================
   CONTENT KIT
   Styling for the blocks the build (build.js) generates from
   content/publications/*.md. Everything here extends the existing
   article language rather than introducing a second one — the
   figure, comparison, equation and tech-card styles above are
   reused as-is, and only these three shapes were missing.
   ============================================================ */

/* A cover supplied as a file rather than a canvas animation. The
   card and the article hero are both fixed-ratio boxes, so the
   media has to crop to fill rather than letterbox inside them. */
.pub-card-media img,
.pub-card-media video,
.article-hero-media img,
.article-hero-media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* :::grid — 2-4 media across, each with its own label. Shares the
   ratio and radius of the article's existing filmstrips. */
/* Figma 437:7979 — a row of tiles 220 tall, radius 28, gap 24 */
.figure-grid {
  margin: var(--space-3xl) 0;
  display: grid;
  grid-template-columns: repeat(var(--cols, 3), 1fr);
  gap: var(--space-xl);
}
.figure-grid-media {
  height: 220px;
  border-radius: 28px;
  overflow: hidden;
  background: #f5f5f5;
}
.figure-grid-media img,
.figure-grid-media video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
.figure-grid-label {
  margin-top: var(--space-sm);
  font-size: 16px;
  line-height: 1.4;
  color: var(--text-secondary);
  text-align: center;
}

/* :::note — an aside that has to be read. Marked by a rule in the
   article accent rather than a tinted panel: the body column already
   sits on textured paper, and a second fill there reads as a card
   the reader can skip. */
.callout {
  margin: var(--space-xl) 0;
  padding: var(--space-md) 0 var(--space-md) var(--space-lg);
  border-left: 2px solid var(--accent-article);
}
.callout .eyebrow-badge { margin-bottom: var(--space-sm); }
.callout p {
  margin-bottom: 0;
  color: var(--text-primary);
}

@media (max-width: 900px) {
  .figure-grid { grid-template-columns: 1fr 1fr; }
}

/* ============================================================
   MOBILE ARTICLE — Figma 382:3035
   The desktop article rules above are the base; this block is only
   where the mobile frame genuinely diverges from them, so the two
   stay one design rather than two implementations.
   ============================================================ */
@media (max-width: 900px) {

  /* Bar is 72 rather than 92, and the logo lock-up comes down with it */
  .site-nav { height: 72px; }
  .site-logo img { height: 20px; }  /* was 16 — reads too small on a phone */
  .site-logo-badge {
    font-size: 9px;
    padding: 6px 9px;
    border-radius: 3.6px;
  }

  /* Header: 40 in, 32 between the copy and the cover, 40 down to the rule */
  .article-shell { padding-top: 40px; }
  .article-header {
    gap: var(--space-2xl);
    padding-bottom: 40px;
  }

  /* The two halves stack and the dot goes — with one item per line there
     is nothing left for a separator to separate. */
  .article-meta {
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
    margin-bottom: var(--space-lg);
  }
  .article-meta .dot { display: none; }
  .article-meta .article-date { font-size: calc(14px * var(--tscale)); }
  .article-meta .article-byline { font-size: calc(15px * var(--tscale)); }

  .article-title {
    font-size: calc(36px * 1.2);
    margin-bottom: var(--space-lg);
  }

  .article-lede {
    color: #333;
    opacity: 0.7;
    max-width: none;
  }

  /* 320 x 175 at this width, and a tighter corner than the desktop 32 */
  .article-hero-media {
    aspect-ratio: 320/175;
    border-radius: 16px;
  }

  /* Body: the rail is already ordered above the copy by the block above */
  .article-main {
    padding-top: 40px;
    padding-bottom: 40px;
  }
  /* The 100px of air under Share was not a margin at all — .article-main is
     a grid, and once it stacks its 68px COLUMN gap becomes the row gap
     between the rail and the copy. Fixing the margin could never have
     reached it. The gap owns this spacing now, so the rail carries none. */
  .article-main { row-gap: var(--space-2xl); }

  /* "Our goal": starts too late, then happens too fast. Both are the same
     number. start = vh - gapBottom - stageMin - dwell, and total = start -
     stickyTop, so every vh of dwell delays the beginning AND takes a vh off
     the distance the growth has to happen over. Cutting it to 4vh starts the
     card moving much sooner and spreads the same growth across ~14vh more
     scroll. */
  .cta-scroll-wrap { --cta-dwell: 4vh; }
  .quick-links { margin-bottom: 0; }

  /* On mobile Share is simply the sixth row of the list — the rule that
     separates it on desktop is not in the frame. */
  .quick-links-list {
    gap: 2px;
    padding-bottom: 0;
    border-bottom: 0;
    margin-bottom: 2px;
  }
  .quick-links .ql-icon {
    flex-basis: calc(28px * var(--rail-scale));
    width: calc(28px * var(--rail-scale));
    height: calc(28px * var(--rail-scale));
  }

  /* Subheads drop to 18 and sit 8 above their copy; groups 40 apart */
  .article-body h2 {
    font-size: calc(18px * var(--tscale));
    margin-bottom: var(--space-xs);
  }
  /* Same one-size rule as desktop — see .article-body h3 above. */
  .article-body h3 { font-size: calc(18px * var(--tscale)); }
  .article-body p { color: #333; }
  .text-block { margin-bottom: var(--space-3xl); }
}

/* ------------------------------------------------------------
   Hero slider at mobile
   ------------------------------------------------------------
   Figma has no mobile frame for this component — 382:3035 shows the
   cover slot as a plain placeholder. At 320 the card's desktop chrome
   (26 inset, 48 radius) eats the video, and the two readout pills, sized
   for a 668-wide frame, run into each other and merge into one. Both
   readouts are worth keeping — one is the plain-language reading, the
   other the symbol the article argues about — so they shrink rather
   than one being dropped.
   ------------------------------------------------------------ */
@media (max-width: 900px) {
  .article-hero-demo {
    flex: none;
    width: 100%;
    max-width: none;
  }

  .article-hero-demo .demo-prompt-bar {
    padding: 16px 20px;
    border-radius: 32px;
    margin-bottom: var(--space-md);
  }

  .article-hero-demo .video-slider-wrapper,
  .example-card {
    padding: 16px 16px 28px;
    border-radius: 32px;
  }

  .article-hero-demo .video-display,
  .example-card .video-display { margin-bottom: 20px; }

  .article-hero-demo .video-alpha-badge,
  .article-hero-demo .video-intensity-badge {
    top: 14px;
    height: 24px;
    padding: 0 9px;
    font-size: 10px;
    letter-spacing: 0.12em;
  }
  .article-hero-demo .video-alpha-badge { right: 14px; letter-spacing: 0.08em; }
  .article-hero-demo .video-intensity-badge { left: 14px; }

  .article-hero-demo .slider-control,
  .example-card .slider-control { padding: 0 4px; }
  .article-hero-demo .slider-labels,
  .example-card .slider-labels {
    font-size: 11px;
    margin-bottom: 16px;
  }

  /* One column below 900, so the prompts no longer have to line up across
     a row and can take the height they need. */
  .example-prompt {
    min-height: 0;
    margin-bottom: 16px;
  }
}

/* ============================================================
   TECHNION–DECART EXCELLENCE PROGRAM  (technion.html)
   Figma 535:10389. Reuses the existing tokens and .home-section /
   .home-section-heading / .eyebrow-badge wherever the frame already
   agrees with them — the 40px section heading and the 11px badge are
   the same objects the home page uses, so they are not restyled here.
   ============================================================ */

/* ---- Hero (Figma 544:3016) — pt 110, pb 92, then a 92 gap to the rule ---- */
.tech-hero { padding: 110px 0 0; }

.tech-hero .container { padding-bottom: 92px; }

.tech-hero-grid {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
  padding-bottom: 92px;
  border-bottom: 1px solid var(--divider);
}

/* 64 rather than the home page's 92: this is a page title inside a section
   stack, not the site's masthead. */
.tech-hero-title {
  flex: 0 0 644px;
  max-width: 644px;
  font-family: var(--font-display);
  font-size: 64px;
  font-weight: var(--w-mix-regular);
  line-height: 1.08;
  letter-spacing: -0.03em;
  color: #101010;
}

.tech-hero-aside {
  flex: 1 1 auto;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 28px;
}

.tech-hero-aside p {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.52;
  letter-spacing: -0.01em;
  color: #333;
  opacity: 0.7;
}

/* Figma 551:10764 — h48, px32, radius 100, 1px black on the paper.
   Same shape as the Unfold and Read-the-Paper buttons, so it inverts on
   hover the same way rather than inventing a third behaviour. */
.tech-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  height: 48px;
  padding: 12px 32px;
  border: 1px solid #000;
  border-radius: var(--radius-full);
  background: var(--nav-bg);
  font-family: var(--font-body);
  font-size: 16px;
  font-weight: 500;
  line-height: 1.5;
  color: #000;
  text-decoration: none;
  transition: background-color 0.25s ease, color 0.25s ease;
}

.tech-btn:hover { background: var(--neutral-800); color: var(--nav-bg); }

/* ---- About the program (Figma 539:2972) ---- */
.tech-about { padding-top: 0; padding-bottom: var(--space-5xl); }

.tech-about-grid {
  display: grid;
  grid-template-columns: minmax(0, 624px) minmax(0, 1fr);
  gap: var(--space-4xl);
  align-items: center;
}

/* Figma 548:10715 — a 624x400 #f5f5f5 panel, radius 24, holding the seal */
.tech-seal {
  aspect-ratio: 624/400;
  border-radius: var(--radius-md);
  background: #f5f5f5;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

/* Figma 601:3873 — the seal sits at 136 of a 320 panel (42.5%), a much
   roomier inset than the 68% it launched with. */
.tech-seal img { width: 42.5%; max-width: 265px; height: auto; display: block; }

.tech-about-copy {
  display: flex;
  flex-direction: column;
  gap: var(--space-xl);
}

.tech-about-copy .home-section-heading { margin-bottom: 0; }

.tech-about-copy p {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.52;
  letter-spacing: -0.02em;
  color: #333;
  opacity: 0.7;
}

/* ---- The Program at a Glance (Figma 544:3060) ---- */
.tech-glance { padding-top: var(--space-5xl); padding-bottom: var(--space-5xl); }

/* Heading left, standfirst right-aligned against it — the same shape as the
   stats section on the home page. */
.tech-heading-row {
  display: flex;
  align-items: flex-start;
  gap: 28px;
  margin-bottom: 52px;
}

.tech-heading-row .home-section-heading { flex: 1 1 auto; margin-bottom: 0; }

.tech-heading-row p {
  flex: 0 1 620px;
  padding-top: var(--space-xs);
  font-family: var(--font-body);
  font-size: 18px;
  line-height: 1.52;
  letter-spacing: -0.02em;
  color: #333;
  opacity: 0.7;
  text-align: right;
}

.tech-glance-grid {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: var(--space-xl);
}

/* Figma 544:3068 — #f5f5f5, radius 28, pt 28 / px 24 / pb 32, badge then a
   129-tall block with the figure at the top and the caption at the bottom */
.tech-stat {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: var(--space-2xl);
  padding: 28px 24px 32px;
  border-radius: 28px;
  background: #f5f5f5;
}

.tech-stat-body {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--space-xl);
  min-height: 129px;
  width: 100%;
}

.tech-stat-value {
  font-family: var(--font-display);
  font-size: 40px;
  font-weight: var(--w-mix-regular);
  line-height: 1.08;
  letter-spacing: -0.01em;
  color: #2d2d2d;
  white-space: nowrap;
}

.tech-stat p {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.52;
  letter-spacing: -0.01em;
  color: #333;
  opacity: 0.7;
}

/* ---- Partnership Pillars carousel (Figma 553:298) ----
   The frame replaced the earlier accordion-plus-photo with a 2646-wide
   track of four 643/644 slides on 24 gutters, inside the 1312 column —
   which is exactly two slides visible (643 + 24 + 644 = 1311). So the
   carousel is not a decision, it is what those numbers describe. */
.tech-pillars { padding-top: var(--space-5xl); padding-bottom: 120px; }

/* Figma 553:299 — the heading and the two arrows share one 48-tall row */
.tech-pillars-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-xl);
  min-height: 48px;
  margin-bottom: 49px;
}

.tech-pillars-head .home-section-heading { margin-bottom: 0; }

.tech-carousel-nav { display: flex; gap: var(--space-sm); flex: 0 0 auto; }

/* Figma 553:304/306 — two 48px round buttons */
.tech-carousel-btn {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  border: 1px solid var(--neutral-800);
  background: var(--nav-bg);
  color: var(--neutral-800);
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
  transition: background-color 0.25s ease, color 0.25s ease, opacity 0.25s ease;
}

.tech-carousel-btn svg { width: 20px; height: 20px; display: block; }
.tech-carousel-btn[data-dir="-1"] svg { transform: scaleX(-1); }
.tech-carousel-btn:hover:not(:disabled) { background: var(--neutral-800); color: var(--nav-bg); }

/* Disabled rather than hidden: the control keeps its place in the row, so
   the heading does not shift as you page through. */
.tech-carousel-btn:disabled { opacity: 0.3; cursor: default; }

/* A real scrolling track, not a transform. Trackpad, touch, keyboard and
   find-in-page all work on it before technion.js loads; the arrows are an
   addition to that, not the mechanism. */
.tech-slides {
  list-style: none;
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: calc((100% - var(--space-xl)) / 2);
  gap: var(--space-xl);
  overflow-x: auto;
  scroll-snap-type: x mandatory;
  scroll-behavior: smooth;
  scrollbar-width: none;
  /* the track bleeds to the page edge on mobile, so it needs its own end
     padding to stop the last slide butting against the viewport */
  padding-bottom: var(--space-xs);
}

.tech-slides::-webkit-scrollbar { display: none; }

.tech-slide { scroll-snap-align: start; min-width: 0; }

/* Figma 553:310 — 643x352, the same 1.827 the publication covers use */
.tech-slide-media {
  aspect-ratio: 643/352;
  border-radius: var(--radius-md);
  overflow: hidden;
  background: #f5f5f5;
  margin-bottom: var(--space-xl);
}

.tech-slide-media img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* Figma 553:313 — 26 tall at 1.45 => 18px, then 8 down to the copy */
.tech-slide-title {
  font-family: var(--font-display);
  font-size: 18px;
  font-weight: var(--w-mix-medium);
  line-height: 1.45;
  color: rgba(0, 0, 0, 0.9);
  margin-bottom: var(--space-xs);
}

.tech-slide-body {
  font-family: var(--font-body);
  font-size: 16px;
  line-height: 1.52;
  letter-spacing: -0.02em;
  color: #333;
  opacity: 0.7;
}

/* ---- Technion page at narrow widths ----
   No mobile frame exists for this page, so it follows the conventions the
   rest of the site already set: columns stack, the hero title drops to the
   mobile heading size, and the right-aligned standfirst goes left. */
@media (max-width: 1100px) {
  .tech-glance-grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .tech-pillars-head { flex-wrap: wrap; }
}

@media (max-width: 900px) {
  .tech-hero { padding-top: var(--space-4xl); }
  .tech-hero .container { padding-bottom: var(--space-4xl); }
  .tech-hero-grid {
    flex-direction: column;
    align-items: stretch;
    gap: var(--space-2xl);
    padding-bottom: var(--space-4xl);
  }
  .tech-hero-title { flex: none; max-width: none; font-size: 43.2px; }
  .tech-about-grid { grid-template-columns: 1fr; gap: var(--space-2xl); }
  .tech-heading-row { flex-direction: column; gap: var(--space-md); margin-bottom: var(--space-2xl); }
  .tech-heading-row p { flex: none; text-align: left; padding-top: 0; }
  .tech-glance-grid { grid-template-columns: 1fr; }
  .tech-pillars { padding-bottom: var(--space-4xl); }
  /* One slide at a time, and the track runs to the page edge so the next
     one peeks in — that is what tells you it scrolls. */
  .tech-slides { grid-auto-columns: 82%; }
}

/* ============================================================
   MOBILE, ALIGNED TO FIGMA 326:7986
   ============================================================
   The home page previously reached mobile by inheriting desktop and
   patching a handful of values. This block takes its numbers from the
   360-wide frame instead, which turns out to differ from desktop in more
   than width: the cards carry their own padding, the section rhythm is a
   flat 40 top and bottom rather than 64/80, and several sizes step DOWN
   less than a naive scale would suggest (the pillar number actually goes
   UP, 28 -> 32).

   Everything here is measured off that frame. Where the frame and desktop
   already agree the rule is not repeated.
   ============================================================ */
@media (max-width: 900px) {

  /* Figma: every section is pt 40 / pb 40, not the desktop 64 / 80 */
  .home-section { padding-top: 40px; padding-bottom: 40px; }

  /* ---- Hero (326:7978) — title 52 tall at lh 1.08 => 48px ---- */
  .home-hero-title { font-size: 48px; }
  .home-hero { padding-top: 40px; padding-bottom: 40px; }
  .home-hero-grid { gap: var(--space-lg); }          /* title -> copy: 20 */
  .home-hero-copy h2 { margin-bottom: var(--space-xs); }   /* h2 -> p: 8 */

  /* Section heading sits 32 above whatever follows it (326:8040 -> 8045) */
  .home-section-heading { margin-bottom: var(--space-2xl); }

  /* ---- Publications (326:8039) ---- */
  /* 52 between stacked cards, against 24 on the desktop grid */
  .publications-grid { gap: 52px; }
  .pub-card-media { margin-bottom: var(--space-xl); }      /* cover -> title: 24 */
  .pub-card-desc { margin-bottom: var(--space-xl); }       /* desc -> meta: 24 */

  /* ---- Research pillars (326:8159) ---- */
  /* Card padding is 24 at the sides here, not the desktop 28, and the
     bottom is 28 rather than 32. Radius opens to 32. */
  .pillar-card { padding: 28px 24px; border-radius: 32px; }
  .pillar-num { font-size: 32px; }                          /* up from 28 */
  .pillar-card-top { margin-bottom: var(--space-xl); }      /* 24, was 32 */
  .pillar-card h4 { margin-bottom: var(--space-xl); }       /* 24, was 32 */

  /* ---- The benchmark keeps moving (350:230) ---- */
  .stats-heading-row { gap: var(--space-sm); }              /* heading -> standfirst: 12 */
  .stat-card { padding: 28px 24px 32px; }
  .stat-card .eyebrow-badge { margin-bottom: var(--space-2xl); }  /* badge -> figure: 32 */
  .stat-value { font-size: 28px; margin-bottom: var(--space-md); } /* 30 tall at 1.08; -> caption 16 */

  /* ---- Quote (326:8487) ---- */
  .quote-block { padding: 32px 24px; }
  .quote-avatar { width: 52px; height: 52px; flex: 0 0 52px; }

  /* ---- QA: spacing, contrast and layout notes on the mobile build ---- */

  /* Every section on the mobile frame is padded 40 top AND bottom, so a
     boundary between two of them is 80. The CTA banner is not a
     .home-section, so the publications -> goal boundary was coming out at
     40 — half of every other gap on the page, which is what read as
     cramped. Paid on the publications side because the CTA's sticky
     expansion measures its own offsets and should not be reparented. */
  #publications { padding-bottom: 80px; }

  /* Quote: up from 22, and the blueprint comes off. Its dashed grid runs
     straight through the copy, and at this measure the lines cost more in
     legibility than they add in texture — the frame does not draw them
     over the quote on mobile either. */
  .quote-block blockquote { font-size: 24px; line-height: 1.36; }
  .quote-block .blueprint { display: none; }

  /* ---- Events (Figma 350:476) ----
     Mobile reorders the row rather than just stacking it: the date sits in
     a chip, and the location and the tag SHARE the last line, tag flush
     right. Done with grid areas so the source order — which desktop's
     five-column layout depends on — is left alone. */
  .event-row {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas:
      "date date"
      "title title"
      "desc desc"
      "loc  tag";
    gap: 20px 12px;
    align-items: center;
  }
  .event-date {
    grid-area: date;
    justify-self: start;
    /* 16px in a 12px-radius chip on 16/12 padding — the chip was reading as
       a heading at 22px on 24px padding. */
    padding: 12px 16px;
    border-radius: 12px;
    font-size: 16px;
  }
  .event-title { grid-area: title; font-size: 20px; }
  .event-desc { grid-area: desc; margin-top: -8px; }
  .event-location { grid-area: loc; justify-self: start; }
  .event-row .eyebrow-outline { grid-area: tag; justify-self: end; }

  /* ---- Community tools (Figma 350:560/350:598) ----
     The mobile card is not the desktop card squeezed — it is its own
     design: heading "Community tools" (swapped by the title spans above),
     media first, then "Unfold" with a small TOOL tag beside it, WHITE copy,
     and a full-width pill button. The long title and the "Research " half
     of the tag are desktop-only spans, hidden here. */
  .tool-name-long, .tool-badge-long { display: none; }
  .community-title-desktop { display: none; }
  .community-title-mobile { display: inline; }

  /* The frame's card is a deep blurred blue (the section backdrop image
     darkened), not the desktop's powder photo — that depth is what makes
     the white copy legible. Same photo, tinted with the site's indigo at
     45%, which lands within a couple of levels of the frame's midtone
     (#8798CC vs the design's ~#93A8D8). Plain alpha compositing — no
     blend modes; see the panels note for why Safari gets those wrong —
     but the desktop rule's soft-light still has to be reset. */
  .tool-card {
    padding: 24px;
    border-radius: 32px;
    background-color: #8798CC;
    background-image:
      linear-gradient(rgba(70, 93, 179, 0.45), rgba(70, 93, 179, 0.45)),
      url('images/bg/tools-backdrop.webp');
    background-repeat: no-repeat, no-repeat;
    background-size: cover, cover;
    background-position: center, center;
    background-blend-mode: normal;
  }
  .tool-card-media {
    order: -1;
    width: 100%;
    align-self: stretch;
    flex: none;
    height: 200px;                /* 272x200 in the frame */
    border-radius: 16px;
  }
  .tool-card-body {
    display: grid;
    grid-template-columns: auto 1fr;
    grid-template-areas:
      "title tag"
      "body  body"
      "cta   cta";
    align-items: center;          /* tag centered on the one-line "Unfold" */
    /* 14 title->copy per the frame; 16 between title and tag */
    gap: 14px 16px;
    width: 100%;
  }
  .tool-card h4 { font-size: 28px; color: #FFFEFB; }
  .tool-card p { font-size: 16px; line-height: 1.44; color: #FFFEFB; opacity: 0.9; }
  .tool-card-body h4 { grid-area: title; margin: 0; }
  .tool-card-body .eyebrow-badge { grid-area: tag; justify-self: start; margin: 0; }
  .tool-card-body p { grid-area: body; margin: 0; }
  .tool-card-body .tool-card-cta {
    grid-area: cta;
    justify-self: stretch;
    justify-content: center;
    height: 48px;
    /* copy->button is 24 in the frame; the grid's row gap gives 14 */
    margin-top: 10px;
  }
  .quote-attribution { gap: var(--space-sm); }              /* avatar -> name: 12 */
}
