/* ===========================================================================
   v2 — Launchpad case study
   Type scale, colour and layout taken from Maxine's refined Figma file
   (kZbRLtd614DBU5cWWJLP8O, frame 4:2). Token names mirror the Figma
   variable collection "Case study tokens" so the two stay legible together.
   =========================================================================== */

:root {
  /* ---- surfaces (Figma: colour/*) --------------------------------- */
  --bg:          #FAFAFA;
  --surface:     #F6F6F8;
  --surface-2:   #F2F4F5;
  --white:       #FFFFFF;
  --line:        #ECECEC;
  --line-strong: #E0E0E0;

  /* ---- ink --------------------------------------------------------- */
  --ink:         #111112;
  --ink-body:    #3D3D40;
  --ink-muted:   #76767B;
  --ink-faint:   #A0A0A6;

  /* ---- accent (Figma: Accent/launchpad) --------------------------- */
  --accent:      #8F4A56;

  /* ---- type -------------------------------------------------------
     Figma text styles, 1:1:
       Eyebrow  12 / Medium    / ls  9%
       H1       26 / Medium    / lh 33 / ls -2%
       H2       20 / Medium    / lh 33 / ls -2%
       H3       18 / Medium    / lh 33 / ls -2%
       body     16 / Regular   / lh 29
       Body 2   14 / Regular
       numbers  38 / Semi Bold
     ---------------------------------------------------------------- */
  --font: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Helvetica, Arial, sans-serif;

  --t-eyebrow: 12px;
  --t-h1:      26px;
  --t-h2:      20px;
  --t-h3:      18px;
  --t-body:    16px;
  --t-body2:   14px;
  --t-small:   15.5px;
  --t-num:     38px;
  --t-label:   14px;   /* the "Launchpad" eyebrow label */

  --lh-head:   33px;
  --lh-body:   29px;
  --ls-tight:  -0.02em;

  /* ---- shape ------------------------------------------------------- */
  --r-sm:   8px;
  --r-md:  12px;
  --r-lg:  16px;
  --r-pill: 40px;

  /* ---- layout — matches the 1440 Figma frame ---------------------- */
  --max:      1440px;
  --content:  1160px;   /* inner content width  */
  --col-text:  370px;   /* sticky text column   */
  --col-gap:    48px;
  --gutter:   clamp(20px, 9.72vw, 140px);

  /* ---- sticky label swap ------------------------------------------------
     Variant B from _prototype/sticky-lab.html: fade + rise at 600ms.
     ---------------------------------------------------------------------- */
  --swap-dur:  600ms;
  --swap-rise: 6px;
  --swap-ease: cubic-bezier(.2, .7, .3, 1);
}

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; scroll-padding-top: 80px; }
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--ink-body);
  font-family: var(--font);
  font-size: var(--t-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  padding-bottom: 112px;   /* clearance for the floating dock */
}

/* width/height attributes on <img> reserve space before load (no layout
   shift, and the scrolly row heights are correct from the first paint).
   height:auto keeps the aspect ratio once CSS sets the width. */
img { max-width: 100%; height: auto; display: block; }
video { max-width: 100%; display: block; }
a { color: inherit; }

h1, h2, h3 {
  margin: 0;
  color: var(--ink);
  font-weight: 500;
  line-height: var(--lh-head);
  letter-spacing: var(--ls-tight);
}
h1 { font-size: var(--t-h1); }
h2 { font-size: var(--t-h2); }
h3 { font-size: var(--t-h3); }
p  { margin: 0; }

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

/* An extra gutter's worth of inset on top of the normal content width --
   e.g. the Phase 2 conflict block and the Impact block inside Approach,
   both measured at 880px against the page's usual 1160px. */
.inset { padding-inline: var(--gutter); }
/* True centred text, distinct from .prose's "narrower column, still
   left-aligned" -- for the rare block that's genuinely centre-aligned
   throughout, like the Phase 2 conflict block. */
.center-block { text-align: center; }
/* 40px vertical rhythm between arbitrary children, for a wrapper that isn't
   itself a .section (which gets this from .section > .sec-head + *) */
.stack-40 > * + * { margin-top: 40px; }
/* two media items side by side, equal width -- e.g. Human-in-the-Loop's
   video + image pairing, below a full-width heading/body rather than the
   usual single text+media feature row */
.media-pair { display: flex; gap: 24px; align-items: flex-start; }
.media-pair > * { flex: 1 1 0; min-width: 0; margin: 0; }
/* the video here is portrait-shaped content forced into a short, wide box
   (see the reveal's own --ar) -- cover would crop it on every edge to fill
   that shape. contain fits the whole frame instead, narrower than its box
   rather than cropped. */
.media-pair .fig .frame > .clip > video { object-fit: contain; object-position: center; }

/* The Phase 2 conflict heading + its rotated "Conflicts" sticker -- a real,
   normal flex row in Figma (not an absolute overlay), which is exactly why
   it's simple to reproduce: the sticker is a same-height flex sibling next
   to the heading, both centred as one row. Only this row is centred --
   the rest of the block reads left-aligned like everywhere else. */
.insight-heading {
  display: flex; align-items: flex-end; justify-content: center;
}
.insight-heading h2 { margin-right: -8px; }
.conflict-sticker-slot {
  position: relative; flex: 0 0 auto; width: 94px; height: 56px;
}
.conflict-sticker {
  position: absolute; left: 0; top: 0;
  display: inline-flex; align-items: center; justify-content: center;
  transform: rotate(16deg);
  background: #FFD8E4; border: 1px solid #FFB7CD; border-radius: var(--r-sm);
  padding: 6px 16px;
  font-size: 14px; font-weight: 500; color: #49454F;
  white-space: nowrap; pointer-events: none;
}

:where(a, button):focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; border-radius: 6px; }
.skip { position: absolute; left: -9999px; }
.skip:focus { left: 12px; top: 12px; z-index: 300; background: var(--ink); color: #fff; padding: 10px 18px; border-radius: var(--r-sm); }

/* ===========================================================================
   Top bar
   =========================================================================== */
.topbar {
  position: sticky; top: 0; z-index: 90;
  background: rgba(250,250,250,.85);
  backdrop-filter: saturate(180%) blur(14px);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
}
.topbar .wrap {
  display: flex; align-items: center; justify-content: space-between;
  height: 55px;
}
.topbar a { text-decoration: none; font-weight: 500; color: var(--ink); }
.topbar .tb-name  { font-size: 16px; letter-spacing: var(--ls-tight); }
/* design has About at full ink, not muted */
.topbar .tb-right { font-size: var(--t-body2); color: var(--ink); }
.topbar .tb-right:hover { color: var(--ink-body); }

/* ===========================================================================
   Shared type helpers
   =========================================================================== */
.eyebrow {
  display: block;
  font-size: var(--t-eyebrow); font-weight: 500;
  letter-spacing: .09em; text-transform: uppercase;
  color: var(--ink-faint);
}
.body   { font-size: var(--t-body);  line-height: var(--lh-body); color: var(--ink-body); }
/* "Body 2" in the Figma styles: 14 / 33 / -2%  */
.body-2 { font-size: var(--t-body2); line-height: var(--lh-head); letter-spacing: var(--ls-tight); color: var(--ink-body); }
/* "small" = the 15.5 / 25 pairing used for the label descriptions */
.small  { font-size: var(--t-small); line-height: 25px;           color: var(--ink-body); }
/* the quiet parenthetical under a heading, e.g. "(In a typical month)" */
.note   { font-size: var(--t-body2); line-height: var(--lh-head);
          letter-spacing: var(--ls-tight); color: var(--ink-muted); }
/* emphasis carries the Launchpad accent, per the design */
strong  { color: var(--accent); font-weight: 600; }

/* ===========================================================================
   Pills — two variants, as in the refined hero
   =========================================================================== */
.pills { display: flex; flex-wrap: wrap; gap: 7px; list-style: none; margin: 0; padding: 0; }
.pills li {
  border: 1px solid var(--line-strong); border-radius: var(--r-pill);
  padding: 4px 11px;
  font-size: 11.5px; line-height: normal; font-weight: 500; letter-spacing: .055em;
  color: var(--ink-body); white-space: nowrap;
}
/* emphasised: category tags carry the ink border */
.pills li.is-key { border-color: var(--ink); color: var(--ink); }

/* ===========================================================================
   Hero
   =========================================================================== */
.hero { padding-top: 92px; padding-bottom: 44px; }
.hero > * + * { margin-top: 22px; }
.hero .project-label {
  font-size: var(--t-label); font-weight: 500;
  color: var(--ink); letter-spacing: var(--ls-tight);
}
.hero h1 { max-width: 560px; }
.hero .hero-sub {
  max-width: 620px;
  font-size: var(--t-body); line-height: var(--lh-body); color: var(--ink-body);
}

/* ===========================================================================
   Sections
   =========================================================================== */
.section { padding-top: 56px; }
.section.pad-lg  { padding-top: 96px; }
/* trailing space -- only where the next section (typically a full-bleed one)
   doesn't supply enough of its own leading space to read as one beat */
.section.pad-b   { padding-bottom: 56px; }
/* .collapse-panel and .inset (and any future plain "div" wrapper block) sit
   between a section and its real content, invisible to a `.section > `
   selector -- so every one of these direct-child rules is written to reach
   through ONE such wrapper too. Add a new wrapper class here the day one
   doesn't show up in this list; that's the only way this silently breaks. */
.section > * + *,
.section > :is(.collapse-panel, .inset) > * + * { margin-top: 56px; }
.section.gap-lg > * + * { margin-top: 112px; }

/* Inside one head group (eyebrow, heading, an intro line that belongs to
   it) everything sits 12px apart -- confirmed against Figma's own metadata
   across every section. The 40px rule is only for what comes AFTER the
   whole head group. */
.sec-head > * + * { margin-top: 12px; }
.sec-head .body { max-width: 680px; }
/* 40px from a section heading block to whatever body follows it */
.section > .sec-head + *,
.section > :is(.collapse-panel, .inset) > .sec-head + * { margin-top: 40px; }
/* ...and 40px (not the general 56px) from a list to whatever follows it,
   e.g. the Reflect section's numbered list straight into its features */
.section > :is(.numbered, .bullets) + *,
.section > :is(.collapse-panel, .inset) > :is(.numbered, .bullets) + * { margin-top: 40px; }

/* ===========================================================================
   Sticky split — text left, media scrolls right
   Figma: flex row, gap 48, align-items start, left col 370, right col 742
   =========================================================================== */
.split {
  display: grid;
  grid-template-columns: var(--col-text) minmax(0, 1fr);
  gap: var(--col-gap);
  align-items: start;
}
.split-text {
  position: sticky; top: 80px;
  padding-bottom: 40px;
}
.split-text > * + * { margin-top: 14px; }
.split-text .body { font-size: var(--t-body); }

.split-media { display: grid; gap: 16px; min-width: 0; }

@media (max-width: 1040px) {
  .split { grid-template-columns: 1fr; gap: 24px; }
  .split-text { position: static; padding-top: 0; padding-bottom: 0; }
}

/* a labelled sub-block inside a text column ("Pipeline View" + description) */
.def > * + * { margin-top: 6px; }
/* design has these labels at ink-body, a step softer than a section heading */
.def > h3 { color: var(--ink-body); }
.def .small { color: var(--ink-body); }
/* a heading followed by a group of question chips needs more air than a
   heading followed by a single line of body copy */
.def > h3 + .q-list { margin-top: 22px; }

/* ===========================================================================
   Scrolly split — one sticky text block per media item, each COVERING the last
   ---------------------------------------------------------------------------
   Every .st spans from its own row down to the last row of the grid, so all
   of their sticky ranges overlap. Each one is opaque and later in the DOM, so
   as media item N reaches the top of the viewport its text slides up over the
   previous one — and reverses cleanly on the way back up.

   Source order is interleaved (text, media, text, media…) so the single-column
   mobile layout reads in the right order with no duplicated markup.
   Set --pairs on the container to the number of pairs: style="--pairs:2"
   =========================================================================== */
.scrolly {
  display: grid;
  grid-template-columns: var(--col-text) minmax(0, 1fr);
  grid-template-rows: repeat(var(--pairs, 2), auto);
  column-gap: var(--col-gap);
  row-gap: 16px;
}

.scrolly > .st {
  grid-column: 1;
  align-self: start;
  position: sticky;
  /* Offset the pin rather than padding the box: at rest the first line of
     text sits flush with the top of its paired image (alignment), and when
     pinned it clears the 55px sticky top bar. */
  top: 80px;
  z-index: 1;
  padding-bottom: 32px;
}
.scrolly > .st > * + * { margin-top: 14px; }

/* Only ONE block is ever visible. An opaque backdrop was not enough: the
   blocks pin and unpin at different moments, so scrolling either way could
   briefly expose the one behind. JS marks the active block and the rest are
   hidden outright, which makes bleed-through impossible in both directions.
   The .swap class is added by JS, so with no JS the blocks simply all show.

   Fade + rise, 600ms — chosen from the variants in sticky-lab.html. The
   labels are parallel (same heading offset, so 0px drift at the swap), which
   is what stops it reading as a jump; the rise then gives the change a
   direction rather than a dead crossfade. */
.scrolly.swap > .st {
  opacity: 0;
  pointer-events: none;
  transform: translateY(var(--swap-rise));
  transition: opacity var(--swap-dur) var(--swap-ease),
              transform var(--swap-dur) var(--swap-ease);
}
.scrolly.swap > .st.is-on {
  opacity: 1;
  transform: none;
  pointer-events: auto;
}
@media (prefers-reduced-motion: reduce) {
  .scrolly.swap > .st { transition: opacity 1ms linear; transform: none; }
}

.scrolly > figure { grid-column: 2; }

/* pair placement — text spans to the end of the grid, media sits in its row */
.scrolly > div.st:nth-of-type(1)  { grid-row: 1 / -1; }
.scrolly > div.st:nth-of-type(2)  { grid-row: 2 / -1; }
.scrolly > div.st:nth-of-type(3)  { grid-row: 3 / -1; }
.scrolly > div.st:nth-of-type(4)  { grid-row: 4 / -1; }
.scrolly > figure:nth-of-type(1)  { grid-row: 1; }
.scrolly > figure:nth-of-type(2)  { grid-row: 2; }
.scrolly > figure:nth-of-type(3)  { grid-row: 3; }
.scrolly > figure:nth-of-type(4)  { grid-row: 4; }

/* later blocks paint over earlier ones */
.scrolly > div.st:nth-of-type(2) { z-index: 2; }
.scrolly > div.st:nth-of-type(3) { z-index: 3; }
.scrolly > div.st:nth-of-type(4) { z-index: 4; }

/* collapse: stack in source order, release the sticky */
@media (max-width: 1040px) {
  .scrolly {
    grid-template-columns: 1fr;
    grid-template-rows: none;
    row-gap: 24px;
  }
  .scrolly > .st,
  .scrolly > figure { grid-column: 1; grid-row: auto !important; }
  .scrolly > .st {
    position: static;
    padding-bottom: 0;
  }
  /* stacked layout shows every block in source order — no swapping */
  .scrolly.swap > .st,
  .scrolly.swap > .st.is-on { opacity: 1; pointer-events: auto; }
}

/* stacked variant: narrow intro text, then a full-width figure */
.stack > * + * { margin-top: 32px; }
.stack .stack-text { max-width: 556px; }
.stack .stack-text > * + * { margin-top: 12px; }

/* ===========================================================================
   Figures
   =========================================================================== */
figure { margin: 0; }
.fig > * + * { margin-top: 12px; }
.fig figcaption {
  font-size: var(--t-body2); line-height: var(--lh-head);
  letter-spacing: var(--ls-tight);
  color: var(--ink-muted); max-width: 62ch;
}
.fig .frame {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 20px;
}
/* ---------------------------------------------------------------------------
   The black corner, properly.

   The source screenshots and recordings have a rounded corner baked into the
   pixels, with black outside the curve. A border-radius on the media cannot
   remove it: the baked radius is larger than the mask, so the dark arc sits
   just inside and shows through.

   Fix: clip in a wrapper at 16px and scale the media up by 2.1%, which pushes
   the baked corner outside the clip. Measured against the real assets — a 7px
   inset is the smallest that leaves zero dark pixels in all four corners of
   all five videos; 1.021 gives that at the rendered 702px width. Costs 2% of
   each edge, which on a UI screenshot is nothing.
   --------------------------------------------------------------------------- */
.fig .frame > .clip {
  border-radius: var(--r-lg);
  overflow: hidden;
  background: var(--surface-2);
  line-height: 0;
}
.fig .frame > .clip > img,
.fig .frame > .clip > video {
  width: 100%;
  border-radius: 0;
  transform: scale(1.021);
  transform-origin: center center;
}

/* ---------------------------------------------------------------------------
   Video framing — this is what removes the black corner.

   The source recordings carry black letterbox bars along the top (53-83px at
   1500px wide) and a rounded corner baked into the pixels. Two things were
   going wrong:
     - a 16px mask cannot hide a black BAR, it just rounds it;
     - object-fit:contain letterboxes the media inside the box, so the box's
       border-radius never reaches the media's own corners at all.

   Fix: give each video a box whose aspect ratio matches its CONTENT (frame
   minus the bar) and use object-fit:cover with object-position:bottom, so the
   overflow is taken entirely off the top — exactly the bar. The media then
   fills the box edge to edge, so the 16px radius does clip its corners.

   --ar and --op are set per video in the markup from measured pixel values.
   --------------------------------------------------------------------------- */
.fig .frame > .clip > video {
  aspect-ratio: var(--ar, 702 / 439);
  object-fit: cover;
  object-position: center var(--op, center);
}

/* an empty slot Maxine left in the design with no source yet */
.slot-empty {
  aspect-ratio: 702 / 439;
  border: 1px dashed var(--line-strong);
  border-radius: var(--r-sm);
  background: repeating-linear-gradient(45deg, #F1F1F4 0 10px, var(--surface) 10px 20px);
  display: grid; place-items: center;
  font-size: var(--t-body2); color: var(--ink-faint);
}

/* ===========================================================================
   Stats
   =========================================================================== */
.stats {
  display: grid; gap: 12px;
  grid-template-columns: repeat(2, 1fr);
}
@media (min-width: 900px) { .stats { grid-template-columns: repeat(4, 1fr); } }
.stat {
  background: var(--surface); border-radius: var(--r-lg);
  padding: 26px;
}
.stat .n {
  display: block;
  font-size: var(--t-num); font-weight: 600; line-height: 40px;
  letter-spacing: -0.04em; color: var(--ink);
}
.stat .l {
  display: block; margin-top: 9px;
  font-size: 13.5px; line-height: 20px; color: var(--ink-muted);
}

/* ===========================================================================
   Overview — label / value rows
   =========================================================================== */
.overview { display: grid; gap: 28px; }
@media (min-width: 900px) { .overview { grid-template-columns: repeat(3, 1fr); align-items: start; } }
.overview .ov h3 { margin-bottom: 8px; }
.overview .ov ul, .overview .ov ol { margin: 0; padding-left: 18px; }
.overview .ov li { font-size: var(--t-body); line-height: 26px; }
.overview .ov li::marker { color: var(--accent); }

/* ===========================================================================
   Numbered lists
   =========================================================================== */
/* numbered findings — the number reads inline, no hanging indent */
.findings { margin: 0; padding: 0; list-style: none; max-width: 760px; counter-reset: f; }
.findings li {
  counter-increment: f;
  font-size: var(--t-body); line-height: var(--lh-body); color: var(--ink-body);
}
.findings li + li { margin-top: 28px; }
.findings li::before { content: counter(f) ". "; color: var(--ink-body); }

/* Design direction — cards side by side, each with a large muted numeral.
   auto-fit + minmax reflows to however many cards there are (3 here, 4 on
   Launchpad's own usage) rather than assuming a fixed count. */
.step-cards {
  display: grid; gap: 16px; margin: 0; padding: 0; list-style: none;
  grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
}

.step-cards li {
  background: var(--surface);
  border-radius: var(--r-lg);
  padding: 28px;
}
.step-cards .n {
  display: block;
  font-size: var(--t-num); font-weight: 600; line-height: 40px;
  letter-spacing: -0.04em; color: var(--ink-faint);
}
/* an optional title between the numeral and the body text -- real <h3>,
   so it's black (--ink) rather than the muted body colour below it */
.step-cards h3 { margin-top: 12px; }
.step-cards h3 + .t { margin-top: 8px; }
.step-cards .t {
  display: block; margin-top: 12px;
  font-size: var(--t-body); line-height: var(--lh-body); color: var(--ink-body);
}
/* a step card that carries its own screenshot -- bleeds to the card's own
   edges, rounded on the top corners only, sitting above the numeral */
.step-cards .step-media {
  margin: -28px -28px 20px;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  overflow: hidden;
  background: var(--surface-2);
}
.step-cards .step-media img { width: 100%; height: auto; display: block; }

/* ===========================================================================
   Before / after reveal — full-bleed sticky stack, crossfade
   ---------------------------------------------------------------------------
   Two full-viewport frames. Both pin at the top; the AFTER frame is later in
   the DOM with a higher stacking order and fades in as it rises, so it covers
   the BEFORE frame and uncovers it again on the way back up.

   Chosen from _prototype/reveal-lab.html: the "fade" variant at 100vh.
   --reveal-p (0 -> 1) is set per section in case-study.js from scroll
   position. Everything else is CSS; both panes release together at the end of
   the section so the reader is never scroll-trapped.
   =========================================================================== */
.reveal { position: relative; }
.reveal .pane {
  position: sticky; top: 0;
  height: 100vh;
  overflow: hidden;
  display: flex; align-items: center;
}
.reveal .pane.is-before { z-index: 1; background: #F4F4F6; }
.reveal .pane.is-after  { z-index: 2; opacity: var(--reveal-p, 0); background: var(--bg); }

/* Each pane is a full frame from the design, not a bare screenshot: a label,
   a short caption and the media, laid out at the page's own content width so
   it reads as a real screen rather than a cropped photo. */
.reveal .pane-inner {
  width: 100%; max-width: var(--content); margin: 0 auto;
  padding: 56px var(--gutter);
  display: flex; flex-direction: column; gap: 24px;
  height: 100%;
}
.reveal .pane-head {
  flex: 0 0 auto;
  display: flex; gap: var(--col-gap); align-items: baseline;
}
.reveal .pane-head h3 {
  flex: 0 0 var(--col-text);
  color: var(--ink-body);
}
.reveal .pane-body {
  flex: 1 1 auto; max-width: 526px;
  font-size: var(--t-small); line-height: 25px; color: var(--ink-body); margin: 0;
}
.reveal .pane-media {
  flex: 1 1 auto; min-height: 0;
  display: flex;
}
.reveal .pane-media .frame,
.reveal .pane-media .clip { flex: 1; min-height: 0; }
.reveal .pane-media .frame { display: flex; }
.reveal .pane-media .clip { display: block; }
.reveal .pane-media .clip img {
  width: 100%; height: 100%; object-fit: cover; object-position: top center;
  transform: none; /* this media has no baked-in corner to compensate for */
}

/* Static fallback: both frames stacked at their natural height.
   Used in two situations, and they need the same treatment:

   1. prefers-reduced-motion — no scroll-driven motion at all.
   2. Narrow viewports. A 100vh pane on a phone is portrait (390x844,
      ratio 0.46) while these frames are landscape desktop UI (1.6). With
      object-fit: cover that shows a ~29% vertical slice of the screenshot
      and crops the rest away, which is unreadable. The reveal is a
      wide-viewport idea; below the same 1040px the scrolly collapses at,
      it becomes two plain stacked frames.

   The panes stay in source order, so it still reads before-then-after. */
@media (prefers-reduced-motion: reduce), (max-width: 1040px) {
  .reveal .pane {
    position: relative;
    height: auto;
    display: block;
  }
  .reveal .pane-inner { height: auto; }
  .reveal .pane-head { flex-direction: column; gap: 8px; }
  .reveal .pane-head h3 { flex: 0 0 auto; }
  .reveal .pane-media .clip img { height: auto; }
  .reveal .pane.is-after { opacity: 1; }
  .reveal .pane + .pane { margin-top: 0; }
}
@media (max-width: 640px) {
  .reveal .pane-head { flex-direction: column; gap: 8px; }
  .reveal .pane-head h3 { flex-basis: auto; }
}

/* ===========================================================================
   Chapter + feature row — the Huxe pattern
   ---------------------------------------------------------------------------
   Measured from jasonspielman.com/huxe: a dated chapter heading acting as a
   quiet divider, then feature rows of text 384 / gap 64 / media 520, centred
   on the cross axis, 48px apart. Note the deliberate inversion — the feature
   heading is LARGER than the chapter heading, because the chapter is a marker
   and the feature is the content.

   Sizes use Maxine's own tokens rather than Huxe's 28/17, so the page stays
   inside her design system: feature heading = --t-h1 (26), body = --t-body.
   =========================================================================== */
.chapter {
  font-size: var(--t-h2);
  color: var(--ink-faint);
  letter-spacing: .01em;
}
.chapter + * { margin-top: 40px; }

.features { display: grid; gap: 48px; }
/* DS migration's Approach specifically measures 96px between feature rows in
   Figma (uniform across all four phases) -- scoped by id rather than changed
   globally, since Launchpad's own spacing hasn't been re-measured against it. */
#approach .features { gap: 96px; }

.feature {
  display: grid;
  /* 384 / 520 exactly where there is room, but minmax lets both tracks shrink
     rather than overflow between the two-column breakpoint and the width at
     which 384 + 64 + 520 actually fits inside the gutters. */
  grid-template-columns: minmax(0, 384px) minmax(0, 520px);
  column-gap: 64px;
  align-items: center;
  justify-content: center;
}
.feature-text > * + * { margin-top: 24px; }
/* higher specificity than .numbered/.bullets's own margin:0 reset, so the
   24px rhythm above always wins inside a feature -- Figma measures 24px
   from every phase heading to its list, with no exceptions. */
.feature-text > :is(.numbered, .bullets) { margin-top: 24px; }
.feature-text p  { font-size: var(--t-body); line-height: var(--lh-body); color: var(--ink-body); }
.feature figure  { margin: 0; }

/* media-first row: same two tracks, sizes swapped to match, DOM order swapped too */
.feature.rev { grid-template-columns: minmax(0, 520px) minmax(0, 384px); }

/* below the two-column threshold, stack and let the media fill */
@media (max-width: 1040px) {
  .feature { grid-template-columns: 1fr; row-gap: 24px; }
}

/* plain bullet list — for problems, deliverables, learnings */
.bullets { margin: 0; padding-left: 22px; max-width: 760px; }
.bullets li {
  font-size: var(--t-body); line-height: var(--lh-body); color: var(--ink-body);
}
.bullets li + li { margin-top: 14px; }
.bullets li::marker { color: var(--accent); }

/* numbered list — same weight as .bullets, plain numerals instead of an
   accent marker, for steps and deliverables that are genuinely ordered */
.numbered { margin: 0; padding-left: 22px; max-width: 760px; }
.numbered li {
  font-size: var(--t-body); line-height: var(--lh-body); color: var(--ink-body);
}
.numbered li + li { margin-top: 14px; }

/* callout — an aside that steps out of the main narrative */
.callout {
  background: var(--surface);
  border-radius: var(--r-lg);
  padding: 32px;
  max-width: 760px;
}
.callout > * + * { margin-top: 20px; }
.callout h3 { font-size: var(--t-h3); }
.callout .label {
  display: block;
  font-size: var(--t-eyebrow); font-weight: 600;
  letter-spacing: .08em; text-transform: uppercase;
  color: var(--accent);
}
.callout .label + * { margin-top: 8px; }

/* question chips */
.q-list { list-style: none; margin: 0; padding: 0; display: grid; gap: 8px; }
.q-list li {
  background: var(--white); border: 1px solid var(--line);
  border-radius: var(--r-md); padding: 12px 16px;
  font-size: var(--t-body2); line-height: 20px;
  letter-spacing: var(--ls-tight); color: var(--ink);
}

/* ===========================================================================
   Pull quote — 26px Medium, per the refined file
   =========================================================================== */
.pull {
  font-size: var(--t-h1); font-weight: 500;
  line-height: var(--lh-head); letter-spacing: var(--ls-tight);
  color: var(--ink);
}

/* a plain reading column of body copy */
.prose { max-width: 680px; }
.prose > * + * { margin-top: 28px; }

/* the "Why Back button dominance?" block: heading and quote centred, the
   body a narrower centred column that is still left-aligned for reading */
.centred { text-align: center; }
.centred .sec-head h2 { max-width: none; }
.centred > * + * { margin-top: 40px; }
.centred .prose {
  max-width: 680px; margin-inline: auto; text-align: left;
}
.centred .prose > * + * { margin-top: 28px; }

/* ===========================================================================
   Floating dock
   =========================================================================== */
.dock {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 95;
  padding: 24px var(--gutter) 32px;
  pointer-events: none;
}
.dock-inner {
  max-width: var(--content); margin-inline: auto;
  display: flex; align-items: center; justify-content: space-between; gap: 12px;
}
.dock a { pointer-events: auto; }

.dock-pill {
  display: inline-flex; align-items: center; gap: 7px;
  background: var(--white); border: 1px solid var(--line);
  border-radius: var(--r-pill); padding: 10px 16px;
  font-size: 13px; font-weight: 500; color: var(--ink);
  text-decoration: none; white-space: nowrap;
  box-shadow: 0 2px 6px rgba(17,17,18,.05), 0 10px 24px -8px rgba(17,17,18,.14);
  transition: transform .18s ease;
}
.dock-pill:hover { transform: translateY(-2px); }

.dock-links {
  display: flex; align-items: center; gap: 2px;
  background: var(--white); border: 1px solid var(--line);
  border-radius: var(--r-pill); padding: 7px 6px;
  box-shadow: 0 2px 6px rgba(17,17,18,.05), 0 10px 24px -8px rgba(17,17,18,.14);
  overflow-x: auto; scrollbar-width: none; max-width: 100%;
}
.dock-links::-webkit-scrollbar { display: none; }
.dock-links a {
  padding: 5px 12px; border-radius: var(--r-pill);
  font-size: 13px; font-weight: 500; color: var(--ink-muted);
  text-decoration: none; white-space: nowrap;
  transition: color .16s ease, background .16s ease;
}
.dock-links a:hover { color: var(--ink); }
.dock-links a.active { color: var(--ink); background: var(--surface); }

@media (max-width: 860px) {
  .dock-inner { justify-content: center; }
  .dock-inner .dock-pill { display: none; }
}

/* Collapsible section — e.g. "My reflects". Collapsed by default; the
   toggle reuses .dock-pill's look since that's the same component, just
   inline in the flow rather than fixed to the viewport. */
.collapse-toggle {
  display: flex; margin: 0 auto; cursor: pointer;
  font-family: inherit;
}
.collapse-toggle .chevron {
  width: 16px; height: 16px; transition: transform .18s ease;
}
.collapse-toggle[aria-expanded="true"] .chevron { transform: rotate(180deg); }
.collapse-panel[hidden] { display: none; }

/* ===========================================================================
   Reveal
   =========================================================================== */
.rv { opacity: 0; transform: translateY(14px); transition: opacity .55s ease, transform .55s cubic-bezier(.2,.7,.3,1); }
.rv.in { opacity: 1; transform: none; }
@media (prefers-reduced-motion: reduce) { .rv { opacity: 1; transform: none; } }

/* Media fades in but never MOVES. A translate here would leave every image
   and video sitting 14px below the top of its paired sticky text until the
   reveal fires — i.e. misaligned exactly while it is being read. */
figure.rv { transform: none; }

/* Same for section headings: the body they introduce (a .scrolly, a stat
   grid) has no reveal of its own, so a heading that slides would change the
   40px gap while it animates. Fade only. */
.sec-head.rv { transform: none; }

@media print {
  .dock, .topbar { display: none !important; }
  .rv { opacity: 1; transform: none; }
  body { padding-bottom: 0; }
}
