/* ==========================================================================
   PORTFOLIO UNIFIED STYLESHEET
   Tactile, print-inspired design with single ink and yellow accent marker.
   Shared across Intro Section and Case Study Section.
   ========================================================================== */

/* ============ 1. Design Tokens ============ */
:root {
  /* Layout */
  --rail-w: clamp(56px, 5vw, 66px);
  /* The bottom offset a wide-canvas composition must stay above to clear the
     fixed privacy link (.legal): the link's inset, its 16.5px line (11px x 1.5),
     and a gap. The rail's counterpart on the bottom edge. */
  --legal-clear: calc(clamp(20px, 3vh, 32px) + 16.5px + clamp(18px, 3vh, 28px));

  /* Light mode palette */
  --paper: #f8f7ff;
  --ink: #111316;
  --ghost: #484848;        /* reading copy — 8.60:1 vs #f8f7ff (WCAG AAA) */
  --ghost-link: #2E2E2E;   /* link at rest — 12.76:1 vs #f8f7ff (WCAG AAA) */
  --rule-link: #8C8C8C;    /* hairlines, step lines, thread — 3.16:1 vs #f8f7ff (WCAG AA UI) */
  --rule-deco: #E2E1E8;    /* ultra-pale decorative dividers (even paler & thinner) — 1.22:1 */
  --rule-faint: #DCDCDC;   /* decorative ground rules only — 1.30:1, carries no information */
  --tag: #717171;          /* mono metadata, captions, hints — 4.59:1 vs #f8f7ff (WCAG AA) */
  --woke: #2E2E2E;         /* non-link hover ceiling — 12.76:1 vs #f8f7ff (WCAG AAA) */
  --marker: linear-gradient(101deg,
    rgba(255,214,10,0) 0%, rgba(255,214,10,.75) 2.5%,
    rgba(255,214,10,.92) 34%, rgba(255,214,10,.86) 68%,
    rgba(255,214,10,.7) 97.5%, rgba(255,214,10,0) 100%);
  --marker-v: linear-gradient(191deg,
    rgba(255,214,10,0) 0%, rgba(255,214,10,.75) 2.5%,
    rgba(255,214,10,.92) 34%, rgba(255,214,10,.86) 68%,
    rgba(255,214,10,.7) 97.5%, rgba(255,214,10,0) 100%);
  --marker-ink: #111316;   /* text struck by marker locks to #111316 for 13.5:1 contrast */

  /* The system's only shadow, and it replaces a border rather than decorating one:
     a photographic export carries its own edge, so it takes no frame, but it then
     has nothing holding it off the paper either. Two stops -- a tight contact
     shadow and a soft ambient one -- so it reads as a sheet lying on the page
     rather than a card floating above it. Never on a drawn diagram: a schematic is
     inked onto the paper, so there is no sheet there to cast one. */
  --shadow-art: 0 1px 2px rgba(17,19,22,.05), 0 10px 28px -6px rgba(17,19,22,.10);

  /* The sheet a photographic export is printed on. Deliberately NOT redefined in
     dark mode: these are screenshots of light-ground products and they carry
     transparent backgrounds, so on the dark backing every other shot uses, their
     own dark type would fall onto a near-black ground and disappear. The sheet
     stays paper in both themes and the shadow is what seats it on the canvas. */
  --art-sheet: #f8f7ff;

  /* The counterpart sheet, for a product whose own canvas is carbon. --art-sheet
     above is fixed at paper because it was written for light-ground products; a
     dark-ground product inverts every term of it -- white type, neon tuned to
     bloom on black -- so paper erases exactly what carbon erases in the other
     case. Not redefined in dark mode for the same reason --art-sheet is not: the
     sheet belongs to the artwork, so it may not follow the reader's theme. */
  --art-sheet-dark: #181B1F;

  /* The same shadow cast from an image's own alpha rather than from its box, for
     an export that already is a sheet: it follows a rounded corner instead of
     squaring it off, and shows through the transparent margin around the picture.
     Blur runs tighter than --shadow-art's because a silhouette shadow has no
     spread to pull it back in. */
  --shadow-cut: drop-shadow(0 1px 1px rgba(17,19,22,.06)) drop-shadow(0 7px 14px rgba(17,19,22,.11));
}

:root[data-theme="dark"],
[data-theme="dark"],
[data-theme="dark"] body {
  /* Dark mode palette (photographic negative) */
  --paper: #111316;
  --ink: #FFFFFF;
  --ghost: #C5C7CC;        /* reading copy — 11.0:1 WCAG AAA */
  --ghost-link: #E8E8E8;   /* link at rest — 15.2:1 WCAG AAA */
  --rule-link: #5F6368;    /* step lines & borders — 3.1:1 WCAG AA */
  --rule-deco: #24272C;    /* ultra-pale decorative dividers (dark mode) — 1.24:1 */
  --rule-faint: #2A2D33;   /* decorative ground rules only — 1.34:1 */
  --tag: #8E929A;          /* mono metadata — 5.96:1 WCAG AA */
  --woke: #E8E8E8;         /* 15.5:1 WCAG AAA */
  --marker-ink: #111316;
  /* Deeper than its light counterpart: the same weight against a carbon ground is
     invisible, and what separates a bright export from that ground is the falloff
     around it rather than the tint of the shadow itself. */
  --shadow-art: 0 1px 3px rgba(0,0,0,.4), 0 12px 32px -6px rgba(0,0,0,.55);
  --shadow-cut: drop-shadow(0 1px 2px rgba(0,0,0,.45)) drop-shadow(0 8px 18px rgba(0,0,0,.6));
}

/* ============ 2. Base & Reset ============ */
* { box-sizing: border-box; }
body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  -webkit-font-smoothing: antialiased;
  transition: background-color .3s ease, color .3s ease;
}
img, svg { max-width: 100%; }
a { color: inherit; }
:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

/* ============ Accessible Skip Link ============ */
.skip-link {
  position: fixed;
  top: -999px;
  left: clamp(20px, 3vw, 40px);
  z-index: 100;
  padding: 8px 16px;
  background: var(--ink);
  color: var(--paper);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .12em;
  text-transform: uppercase;
  text-decoration: none;
  border-radius: 2px;
  transition: top .2s ease;
}
.skip-link:focus-visible {
  top: clamp(20px, 3vh, 32px);
  outline: 2px solid var(--paper);
  outline-offset: 3px;
}
[data-theme="dark"] .skip-link {
  background: #FFFFFF;
  color: #111316;
}
[data-theme="dark"] .skip-link:focus-visible {
  outline: 2px solid #FFFFFF;
}

/* ============ 3. Theme Toggle Component ============ */
.theme-toggle {
  position: fixed; z-index: 10;
  top: clamp(20px, 3vh, 32px); left: clamp(20px, 3vw, 40px);
  width: 32px; height: 32px;
  display: grid; place-items: center;
  padding: 0; border: none; border-radius: 4px;
  background: transparent; color: var(--tag);
  cursor: pointer;
  transition: color .2s cubic-bezier(.2,.7,.3,1),
             transform .2s cubic-bezier(.2,.7,.3,1);
}
.theme-toggle:hover {
  color: var(--ink);
  transform: scale(1.15);
}
.theme-toggle:focus-visible {
  outline: 2px solid var(--ink); outline-offset: 3px;
}
.theme-toggle__icon {
  width: 16px; height: 16px;
  stroke: currentColor; fill: none; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
  pointer-events: none;
}
.theme-toggle__icon--sun { display: none; }
.theme-toggle__icon--moon { display: block; }
[data-theme="dark"] .theme-toggle__icon--sun { display: block; }
[data-theme="dark"] .theme-toggle__icon--moon { display: none; }

/* ============ 4. Persistent Navigation Rail (.rail & .ia__rail) ============ */
.rail,
.ia__rail {
  position: fixed; z-index: 5;
  top: 0; bottom: 0; right: 0; width: var(--rail-w);
  display: grid; grid-template-rows: 1fr auto 1fr; justify-items: center;
  background: var(--ink); color: var(--paper);
  padding: clamp(18px, 3vh, 30px) clamp(17px, 1.6vw, 23px);
  isolation: isolate;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
}
[data-theme="dark"] .rail,
[data-theme="dark"] .ia__rail {
  background: #0D0E11; color: #FFFFFF;
  border-left: 1px solid var(--rule-link, #5F6368);
}
.rail__main,
.ia__rail-main {
  grid-row: 2; align-self: center;
  display: flex; flex-direction: column; align-items: center;
  gap: clamp(14px, 2.2vh, 22px);
}
.rail__cv,
.ia__rail-cv {
  grid-row: 3; align-self: end;
  display: flex; flex-direction: column; align-items: center; gap: 9px;
}
.rail__cv:focus-visible,
.ia__rail-cv:focus-visible { outline-color: var(--paper); }
[data-theme="dark"] .rail__cv:focus-visible,
[data-theme="dark"] .ia__rail-cv:focus-visible { outline-color: #FFFFFF; }
.rail__cv-label,
.ia__rail-cv-label { margin-right: -.2em; }
.rail__cv-icon,
.ia__rail-cv-icon { transition: transform .26s cubic-bezier(.2,.7,.3,1); }
.rail__cv:hover .rail__cv-icon, .rail__cv:focus-visible .rail__cv-icon,
.ia__rail-cv:hover .ia__rail-cv-icon, .ia__rail-cv:focus-visible .ia__rail-cv-icon {
  transform: translateY(2px);
}

.rail__link, .rail__cv,
.ia__rail-link, .ia__rail-cv {
  position: relative;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 13px; font-weight: 700; letter-spacing: .2em; text-transform: uppercase;
  color: var(--paper); text-decoration: none;
  transition: color .14s ease;
}
[data-theme="dark"] .rail__link,
[data-theme="dark"] .rail__cv,
[data-theme="dark"] .ia__rail-link,
[data-theme="dark"] .ia__rail-cv {
  color: #FFFFFF;
}
.rail__link,
.ia__rail-link { writing-mode: vertical-rl; }
.rail__link::before, .rail__cv::before,
.ia__rail-link::before, .ia__rail-cv::before {
  content: ""; position: absolute; z-index: -1; inset: -3px -5px;
  background: var(--marker-v);
  border-radius: 3px 9px 4px 7px;
  transform-origin: bottom;
  transform: rotate(-.45deg) skewY(-2.5deg) scaleY(0);
  transition: transform .3s cubic-bezier(.2,.7,.3,1);
}
.rail__link:hover, .rail__link:focus-visible,
.rail__cv:hover, .rail__cv:focus-visible,
.ia__rail-link:hover, .ia__rail-link:focus-visible,
.ia__rail-cv:hover, .ia__rail-cv:focus-visible {
  color: var(--marker-ink, #111316);
  transition: color .1s ease .18s;
}
.rail__link:hover::before, .rail__link:focus-visible::before,
.rail__cv:hover::before, .rail__cv:focus-visible::before,
.ia__rail-link:hover::before, .ia__rail-link:focus-visible::before,
.ia__rail-cv:hover::before, .ia__rail-cv:focus-visible::before {
  transform: rotate(-.45deg) skewY(-2.5deg) scaleY(1);
}
.rail__link:focus-visible,
.ia__rail-link:focus-visible { outline-color: var(--paper); }
[data-theme="dark"] .rail__link:focus-visible,
[data-theme="dark"] .ia__rail-link:focus-visible { outline-color: #FFFFFF; }

.rail__rule,
.ia__rail-rule {
  width: 1px; height: clamp(18px, 3vh, 30px); background: rgba(255,255,255,.4);
}

/* ============ 5. Intro Section (.ia) ============ */
.ia {
  min-height: 100vh; display: grid; position: relative; overflow: hidden;
  background: var(--paper); color: var(--ink);
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  align-content: center;
  padding: clamp(56px, 9vh, 110px) clamp(18px, 3vw, 56px);
  padding-right: clamp(92px, 7vw, 124px);   /* clearance for fixed rail */
  transition: background-color .3s ease, color .3s ease;
}
.ia__stage {
  position: relative; z-index: 2;
  width: 100%; max-width: 1360px; margin: 0 auto;
  min-height: clamp(460px, 68vh, 660px);
  display: grid; place-items: center;
}
.ia__core {
  position: relative; z-index: 3; text-align: center; max-width: min(920px, 90vw);
}
.ia__core::before {
  content: ""; position: absolute; inset: -34% -16%; z-index: -1; pointer-events: none;
  background: radial-gradient(closest-side, var(--paper) 54%, rgba(248,247,255,0) 100%);
}
[data-theme="dark"] .ia__core::before {
  background: radial-gradient(closest-side, #111316 54%, rgba(17,19,22,0) 100%);
}
.ia__lead {
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif; font-weight: 800;
  font-size: clamp(2rem, 3.4vw, 3.15rem); line-height: 1.08; letter-spacing: -.03em;
  margin: 0; color: var(--ink); text-wrap: balance;
}
.ia__lead-line { display: block; white-space: nowrap; }
.ia__nb { white-space: nowrap; }
.ia__name {
  display: inline-block;
  margin: clamp(10px, 1.6vw, 16px) 0 0; padding: .1em 0 .06em;
  font-size: clamp(.95rem, 1.3vw, 1.06rem); font-weight: 400; color: var(--ink);
  text-wrap: balance;
  transition: opacity .3s;
}
.ia__who {
  position: relative; cursor: help;
  border-bottom: 1px dashed var(--rule-link); padding-bottom: 2px;
  transition: color .3s, border-color .3s;
}
.ia__who:hover, .ia__who:focus { color: var(--ink); border-bottom-color: var(--ink); }
.ia__real {
  position: absolute; top: 100%; left: 50%; margin-top: 9px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12px; letter-spacing: .16em; text-transform: uppercase; white-space: nowrap;
  padding-left: .16em; color: var(--tag); pointer-events: none;
  opacity: 0; transform: translate(-50%, -5px);
  transition: opacity .3s, transform .3s;
}
.ia__who:hover .ia__real, .ia__who:focus .ia__real { opacity: 1; transform: translate(-50%, 0); }
.ia__core:has(.ia__word:hover) .ia__name,
.ia__core:has(.ia__word:focus) .ia__name { opacity: 0; }

@keyframes iaWordSlideUp {
  from {
    opacity: 0;
    transform: translateY(16px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.ia__word {
  position: relative; cursor: help; white-space: nowrap;
  display: inline-block; vertical-align: baseline;
  border-bottom: 2px dashed var(--rule-link);
  transition: border-color .3s;
  animation: iaWordSlideUp 1.2s cubic-bezier(.2,.7,.3,1) both;
}
.ia__word:nth-of-type(1) { animation-delay: .35s; }
.ia__word:nth-of-type(2) { animation-delay: .9s; }
.ia__word:hover, .ia__word:focus { border-bottom-color: var(--ink); }

.ia__hint {
  position: absolute; top: clamp(26px, 5vh, 52px); left: 50%; transform: translateX(-50%); z-index: 4;
  margin: 0;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; letter-spacing: .2em; text-transform: uppercase; color: var(--tag);
}
.ia__slot { position: absolute; left: var(--x); top: var(--y); transform: translate(-50%, -50%); z-index: 2; }
.ia__slot:hover, .ia__slot:focus-within { z-index: 4; }
.ia__drift {
  animation: iaDriftA var(--dur, 15s) var(--delay, 0s) ease-in-out infinite alternate;
  will-change: transform;
}
.ia__drift--b { animation-name: iaDriftB; }
.ia__drift--c { animation-name: iaDriftC; }
.ia__drift:hover, .ia__drift:has(:focus-visible) { animation-play-state: paused; }

.ia__idea {
  position: relative; display: block; text-decoration: none; white-space: nowrap; cursor: default;
  color: var(--ghost); font-weight: 300;
  font-size: calc(var(--s, 1) * clamp(.95rem, 1.5vw, 1.2rem)); line-height: 1.2;
  transition: color .3s, transform .3s;
}
.ia__idea:hover { color: var(--woke); transform: scale(1.05); }
.ia__idea--link { cursor: pointer; color: var(--ghost-link); font-weight: 400; }
.ia__idea--link:hover, .ia__idea--link:focus-visible { color: var(--ink); transform: scale(1.05); }

.ia__label { border-bottom: 1px solid transparent; padding-bottom: 3px; transition: border-color .3s; }
.ia__idea--link .ia__label {
  border-bottom-color: var(--rule-link);
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
  background-image: var(--marker); background-repeat: no-repeat;
  background-position: right center; background-size: 0% 86%;
  transition: border-color .3s, background-size .3s cubic-bezier(.2,.7,.3,1);
}
.ia__idea--link:hover .ia__label,
.ia__idea--link:focus-visible .ia__label {
  background-size: 100% 86%;
  border-bottom-color: var(--marker-ink, #111316);
  color: var(--marker-ink, #111316);
}
.ia__idea--link:hover .ia__tag,
.ia__idea--link:focus-visible .ia__tag { color: var(--ink); }

.ia__tag {
  position: absolute; top: 100%; left: 50%; margin-top: 8px;
  font-family: "IBM Plex Mono", ui-monospace, monospace; font-style: normal;
  font-size: 12px; letter-spacing: .2em; text-transform: uppercase; white-space: nowrap;
  padding-left: .2em; color: var(--tag); opacity: 0; transform: translate(-50%, -5px);
  transition: opacity .3s, transform .3s;
}
.ia__idea:hover .ia__tag, .ia__idea:focus-visible .ia__tag { opacity: 1; transform: translate(-50%, 0); }

.ia__spark {
  position: absolute;
  font-family: "IBM Plex Sans", system-ui, sans-serif; font-weight: 300;
  font-size: 14px; line-height: 1.1; letter-spacing: 0; white-space: nowrap;
  color: var(--tag); opacity: 0; pointer-events: none;
  transition: opacity .34s ease, transform .34s cubic-bezier(.2,.7,.3,1);
}
.ia__spark--a {
  left: 50%; bottom: 100%; margin-bottom: 9px;
  transform: translate(calc(-50% + var(--sx, 0px)), calc(8px - var(--sy, 0px)));
}
.ia__spark--b {
  left: 50%; top: 100%; margin-top: 9px;
  transform: translate(calc(-50% + var(--sx, 0px)), calc(var(--sy, 0px) - 8px));
}
.ia__idea .ia__spark--a { margin-bottom: 14px; }
.ia__idea .ia__spark--b { margin-top: 30px; }
.ia__spark--l {
  right: 100%; top: 50%; margin-right: 11px;
  transform: translate(calc(8px - var(--sx, 0px)), calc(-50% + var(--sy, 0px)));
}
.ia__spark--r {
  left: 100%; top: 50%; margin-left: 11px;
  transform: translate(calc(var(--sx, 0px) - 8px), calc(-50% + var(--sy, 0px)));
}
.ia__word:hover .ia__spark, .ia__word:focus .ia__spark,
.ia__idea:hover .ia__spark, .ia__idea:focus-visible .ia__spark {
  opacity: 1; transition-delay: var(--d, 0s);
}
.ia__word:hover .ia__spark--a, .ia__word:focus .ia__spark--a,
.ia__idea:hover .ia__spark--a, .ia__idea:focus-visible .ia__spark--a {
  transform: translate(calc(-50% + var(--sx, 0px)), calc(0px - var(--sy, 0px)));
}
.ia__word:hover .ia__spark--b, .ia__word:focus .ia__spark--b,
.ia__idea:hover .ia__spark--b, .ia__idea:focus-visible .ia__spark--b {
  transform: translate(calc(-50% + var(--sx, 0px)), var(--sy, 0px));
}
.ia__word:hover .ia__spark--l, .ia__word:focus .ia__spark--l,
.ia__idea:hover .ia__spark--l, .ia__idea:focus-visible .ia__spark--l {
  transform: translate(calc(0px - var(--sx, 0px)), calc(-50% + var(--sy, 0px)));
}
.ia__word:hover .ia__spark--r, .ia__word:focus .ia__spark--r,
.ia__idea:hover .ia__spark--r, .ia__idea:focus-visible .ia__spark--r {
  transform: translate(var(--sx, 0px), calc(-50% + var(--sy, 0px)));
}

@keyframes iaDriftA {
  from { transform: translate3d(-11px, -7px, 0) rotate(-.6deg); }
  to { transform: translate3d(13px, 10px, 0) rotate(.8deg); }
}
@keyframes iaDriftB {
  from { transform: translate3d(10px, -12px, 0) rotate(.9deg); }
  to { transform: translate3d(-12px, 8px, 0) rotate(-.7deg); }
}
@keyframes iaDriftC {
  from { transform: translate3d(-8px, 10px, 0) rotate(.5deg); }
  to { transform: translate3d(11px, -10px, 0) rotate(-1deg); }
}
/* Only ever runs below the breakpoint, where a name too long for the column
   travels rather than wrapping. It alternates rather than looping, so the name
   returns to its own left edge and is read from the start each pass; the
   distance is per-instance, measured against the column the name has to cross. */
@keyframes iaMarquee {
  from { transform: translate3d(0, 0, 0); }
  to { transform: translate3d(var(--marq-shift, 0px), 0, 0); }
}

/* ============ 6. Case Study Section (.cs) ============ */
.cs {
  --pad-x: clamp(40px, 6vw, 116px);
  --pad-y: clamp(44px, 9vh, 112px);

  height: 100dvh;
  display: flex; align-items: stretch;
  overflow-x: auto; overflow-y: hidden;
  overscroll-behavior-x: contain;
  background: var(--paper); color: var(--ghost);
  font-family: "IBM Plex Sans", system-ui, sans-serif; font-weight: 300;
  transition: background-color .3s ease, color .3s ease;
}
.cs__hook, .cs__block, .cs__flow, .gt {
  position: relative; z-index: 2;
  flex: 0 0 auto; height: 100%;
  display: grid; align-content: center;
  padding: var(--pad-y) var(--pad-x);
}
.cs__block, .cs__flow, .gt { border-left: 0.5px solid var(--rule-deco); }
.cs > :last-child { margin-right: var(--rail-w); }

/* shared voices */
.cs__eyebrow, .cs__kicker, .cs__meta-key, .cs__symptom-key, .cs__metric-label,
.cs__step-num, .cs__step-tags, .cs__shot-cap, .cs__flow-hint,
.cs__scroll-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  text-transform: uppercase; letter-spacing: .2em;
  font-size: 12px;
}
.cs__prose, .cs__step-text, .cs__symptom-val, .cs__meta-val { color: var(--ghost); font-weight: 300; }
.cs__prose, .cs__step-text, .cs__symptom-val, .cs__hook-sub { text-wrap: pretty; }
.cs__meta-val, .cs__step-num, .cs__metric-num { font-variant-numeric: tabular-nums; }
.cs__kicker, .cs__flow-hint { margin: 0; color: var(--tag); font-weight: 400; }

/* 1. hook */
.cs__hook {
  width: min(92vw, 980px);
  /* Lifted clear of the privacy link, the scroll hint now stands in the band
     the hook's centred copy used to reach into on a short window. So that band
     -- the hint's offset, its 16px line and the same gap again -- is reserved
     here, and only takes over from --pad-y where it is the larger of the two. */
  padding-bottom: max(var(--pad-y), calc(var(--legal-clear) + 16px + clamp(18px, 3vh, 28px)));
}
.cs__eyebrow { margin: 0 0 clamp(22px, 3.6vh, 38px); color: var(--tag); font-weight: 400; }
.cs__hook-line {
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif; font-weight: 800;
  font-size: clamp(2.1rem, 4.6vw, 4.1rem); line-height: 1.02; letter-spacing: -.03em;
  margin: 0; color: var(--ink); text-wrap: balance;
}
.cs__hook-sub {
  margin: clamp(24px, 4vh, 42px) 0 0; max-width: 56ch;
  font-size: clamp(1rem, 1.5vw, 1.2rem); line-height: 1.55; color: var(--ghost);
}
.cs__meta, .cs__facts {
  display: flex; flex-wrap: wrap; gap: clamp(24px, 3vw, 58px);
  margin: clamp(36px, 6vh, 64px) 0 0; padding: clamp(22px, 3vh, 32px) 0 0;
  border-top: 0.5px solid var(--rule-deco);
}
.cs__meta-pair { margin: 0; }
.cs__meta-key { margin: 0 0 7px; color: var(--tag); }
.cs__meta-val { margin: 0; font-size: clamp(.95rem, 1.2vw, 1.05rem); line-height: 1.4; }
/* Collision handling (DESIGN 4): the fixed privacy link owns the bottom-left
   band of the window, and at the old offset this hint sat directly on top of it.
   So the hint stands on --legal-clear and the two can never meet. The hint
   belongs to the hook's column and the link to the page's corner, which the
   different left edges already say; the gap keeps them from reading as a pair. */
.cs__scroll {
  position: absolute; left: var(--pad-x);
  bottom: var(--legal-clear);
  display: flex; align-items: center; gap: 12px; margin: 0; color: var(--tag);
}
.cs__scroll-rule { display: block; width: clamp(34px, 5vw, 64px); height: 0.5px; background: var(--rule-deco); }
.cs__scroll-arrow { font-size: 13px; line-height: 1; margin-left: -6px; }

/* 2 & 3. client, problem, impact */
.cs__block { width: min(88vw, 680px); }
.cs__block--problem { width: min(88vw, 740px); }
.cs__block--impact { width: min(88vw, 720px); }
.cs__block .cs__kicker { margin-bottom: clamp(20px, 3.2vh, 34px); }
.cs__lede { margin: 0; font-size: clamp(1.15rem, 1.7vw, 1.4rem); line-height: 1.5; color: var(--ghost); }
.cs__prose { margin: clamp(17px, 2.4vh, 26px) 0 0; font-size: clamp(1rem, 1.2vw, 1.08rem); line-height: 1.65; }

.cs__link { color: var(--ghost-link); font-weight: 400; text-decoration: none; cursor: pointer; }
.cs__link-label {
  display: inline;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
  border-bottom: 1px solid var(--rule-link); padding-bottom: 2px;
  background-image: var(--marker); background-repeat: no-repeat;
  background-position: right center; background-size: 0% 92%;
  transition: border-color .3s, background-size .34s cubic-bezier(.2,.7,.3,1);
}
.cs__link:hover, .cs__link:focus-visible { color: var(--marker-ink, #111316); transition: color .1s ease .18s; }
.cs__link:hover .cs__link-label,
.cs__link:focus-visible .cs__link-label { background-size: 100% 92%; border-bottom-color: var(--marker-ink, #111316); }
.cs__link:hover .cs__row,
.cs__link:focus-visible .cs__row {
  background-size: 100% 92%;
  border-bottom-color: var(--marker-ink, #111316);
  color: var(--marker-ink, #111316);
  transition: border-color .3s var(--d, 0s),
             color .1s ease calc(var(--d, 0s) + .18s),
             background-size .34s cubic-bezier(.2,.7,.3,1) var(--d, 0s);
}
.cs__link-arrow {
  display: inline-block;
  transition: transform .26s cubic-bezier(.2,.7,.3,1);
}
.cs__link:hover .cs__link-arrow,
.cs__link:focus-visible .cs__link-arrow {
  transform: translateX(4px);
}

.cs__statement {
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif; font-weight: 800;
  font-size: clamp(1.45rem, 2.6vw, 2.15rem); line-height: 1.08; letter-spacing: -.02em;
  margin: 0; color: var(--ink); text-wrap: balance;
}
.cs__symptoms {
  list-style: none; margin: clamp(30px, 4.6vh, 52px) 0 0; padding: 0;
  border-top: 0.5px solid var(--rule-deco);
}
.cs__symptom {
  display: grid; grid-template-columns: minmax(0, clamp(92px, 10vw, 132px)) minmax(0, 1fr);
  gap: clamp(14px, 2vw, 26px);
  padding: clamp(15px, 2.2vh, 23px) 0; border-bottom: 0.5px solid var(--rule-deco);
}
.cs__symptom-key { color: var(--tag); padding-top: .28em; }
.cs__symptom-val { font-size: clamp(.96rem, 1.15vw, 1.04rem); line-height: 1.55; }

/* 5. metrics */
.cs__metrics {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: clamp(16px, 2.4vw, 36px);
  margin: clamp(28px, 4.2vh, 48px) 0 clamp(24px, 3.6vh, 40px);
  padding: clamp(20px, 2.8vh, 30px) 0 0;
  border-top: 0.5px solid var(--rule-deco);
}
.cs__metric { margin: 0; }
.cs__metric-num {
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif; font-weight: 800;
  font-size: clamp(2rem, 3.5vw, 3.2rem); line-height: 1; letter-spacing: -.03em;
  color: var(--ink); margin: 0 0 6px;
}
.cs__metric-label { margin: 0; color: var(--tag); line-height: 1.4; }

/* 4. timeline */
.cs__flow {
  --step-w: clamp(280px, 28vw, 400px);
  --step-gap: clamp(40px, 4.6vw, 88px);
  --art-h: min(calc(var(--step-w) * .7), 34vh);
  --art-zone: calc(var(--art-h) + 84px);
  align-content: center;
}
.cs__flow-header {
  display: flex; align-items: center; gap: 14px;
  margin: 0 0 clamp(28px, 4.5vh, 52px);
}
.cs__flow-rule { width: 22px; height: 0.5px; background: var(--rule-deco); }

.cs__steps {
  list-style: none; margin: 0; padding: 0;
  position: relative; display: flex; align-items: stretch;
  gap: var(--step-gap);
}
.cs__steps::before {
  content: ""; position: absolute; top: var(--art-zone); height: 1px;
  left: 0; right: calc(-1 * clamp(20px, 3vw, 56px));
  background: var(--rule-link);
}
.cs__step {
  position: relative; flex: 0 0 auto; width: var(--step-w);
  padding-top: calc(var(--art-zone) + clamp(24px, 3.8vh, 40px));
  cursor: help;
}
.cs__step:hover, .cs__step:focus-visible, .cs__step.is-active { z-index: 4; }
.cs__step::before {
  content: ""; position: absolute; left: 0; top: var(--art-zone); width: 7px; height: 7px; margin-top: -3px;
  border-radius: 50%; background: var(--rule-link);
  transition: background-color .3s;
}
.cs__step:hover::before, .cs__step:focus-visible::before, .cs__step.is-active::before { background: var(--ink); }

.cs__step-num {
  display: inline-block; color: var(--tag);
  border-bottom: 1px dashed var(--rule-link);
  padding: .34em .25em .3em .45em; margin: -.34em -.25em -.3em -.45em;
  background-image: var(--marker); background-repeat: no-repeat;
  background-position: right center; background-size: 0% 100%;
  transition: color .14s ease, border-color .3s,
             background-size .34s cubic-bezier(.2,.7,.3,1);
}
.cs__step:hover .cs__step-num,
.cs__step:focus-visible .cs__step-num,
.cs__step.is-active .cs__step-num {
  color: var(--marker-ink, #111316); border-bottom-color: var(--marker-ink, #111316); background-size: 100% 100%;
  transition: color .1s ease .18s, border-color .3s,
             background-size .34s cubic-bezier(.2,.7,.3,1);
}
.cs__step-body { margin-top: clamp(16px, 2.6vh, 26px); }
.cs__step-title {
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif; font-weight: 800;
  font-size: clamp(1.15rem, 1.7vw, 1.5rem); line-height: 1.08; letter-spacing: -.02em;
  margin: 0; color: var(--ink); text-wrap: balance;
}
.cs__step-text { margin: clamp(9px, 1.4vh, 14px) 0 0; font-size: clamp(.95rem, 1.1vw, 1.02rem); line-height: 1.6; }
.cs__step-tags { margin: clamp(11px, 1.7vh, 16px) 0 0; color: var(--tag); letter-spacing: .16em; }

.cs__shot {
  position: absolute; z-index: 6;
  left: var(--sticky-x, 0px); top: 0; width: 100%; margin: 0;
  opacity: 0; transform: translateY(8px);
  pointer-events: none;
  transition: opacity .26s ease, transform .26s cubic-bezier(.2,.7,.3,1);
}
.cs__step:hover .cs__shot,
.cs__step:focus-visible .cs__shot,
.cs__step.is-active .cs__shot {
  opacity: 1; transform: translateY(0);
}
.cs__shot--span-3 {
  width: calc(3 * var(--step-w) + 2 * var(--step-gap));
}
.cs__shot-img {
  display: block; width: 100%; height: var(--art-h);
  object-fit: contain;
  background: var(--paper);
  border: 1px solid var(--rule-link);
}
[data-theme="dark"] .cs__shot-img {
  background: #181B1F;
}
.cs__shot-diagram {
  border: none;
  background: transparent;
}
[data-theme="dark"] .cs__shot-diagram {
  background: transparent;
}
.cs__shot-cap { margin: 11px 0 0; color: var(--tag); letter-spacing: .16em; line-height: 1.5; }
.cs__shot--span-3 .cs__shot-cap { text-align: center; padding-left: .16em; margin-top: 13px; }

/* Photographic artwork, where the shared art zone is too short to read.
   A schematic placeholder survives being shrunk to 280px because it carries a
   dozen shapes; a screenshot of a shipped page carries body copy, and at that
   height its type falls under the legible floor. So these grow upward: the box
   hangs above the art zone by the height it gained, which leaves its bottom edge
   -- and therefore the caption and the timeline rule below it -- exactly where
   every other step's sits. Only the space overhead is borrowed, and only the
   section header occupies it.

   The box is sized from the export itself, passed in as --nat-h and --nat-ratio,
   rather than taking the shared three-step span: height is capped at the source's
   own pixel height so a 1100px-wide export is never stretched past its
   resolution, and width follows from its ratio. A box wider than its picture
   would letterbox it inside a visible frame, and would also hand the centring
   script the wrong number to measure -- it positions the figure, so a figure
   twice the width of the picture inside it clamps the picture off the canvas. */
.cs__shot--tall {
  --shot-h: min(calc(var(--art-h) + 120px), 46vh);
  --shot-real-h: min(var(--shot-h), var(--nat-h, 100vh));
  width: calc(var(--shot-real-h) * var(--nat-ratio, 1.6));
  top: calc(var(--art-h) - var(--shot-real-h));
}
/* A diagram is drawn at a fixed unit width, so its figure takes that width rather
   than the three-step span the photographic shots borrow. A lane wider than the
   picture inside it hands the centring script the wrong box to position -- it
   would centre the lane and let the picture ride out from the middle of it -- and
   at three steps that lane is wider than the canvas the rail leaves. */
.cs__shot--drawn { width: calc(var(--art-h) * var(--nat-ratio, 4)); }

/* Centred under its picture. On the pages where a tall shot also carries
   --span-3 this came free with that class; a page whose tall shots stand alone
   would otherwise set its captions flush left and read differently. */
.cs__shot--tall .cs__shot-cap { text-align: center; padding-left: .16em; }
.cs__shot--tall .cs__shot-img {
  height: var(--shot-real-h);
  /* No frame: the schematic placeholders need a rule to separate an empty
     wireframe from the paper behind it, but these exports carry their own edge
     -- a border around one reads as a second, competing line. The shadow takes
     over what the border was doing, which is holding the sheet off the paper. */
  border: none;
  background: var(--art-sheet);
  box-shadow: var(--shadow-art);
}

/* An export whose opaque area *is* the picture, with transparency only outside its
   own edge. It needs no paper behind it -- it brought its own -- so it casts from
   its alpha instead: the shadow follows the rounded corner the artwork actually
   has, and the page shows through the margin around it. The other exports cannot
   do this: their design sits on a canvas they do not carry, so tracing their alpha
   would halo every text block separately and leave the dark type with nothing to
   sit on. Those keep the sheet, and the shadow belongs to the sheet's edge. */
.cs__shot--cut .cs__shot-img {
  background: none;
  box-shadow: none;
  filter: var(--shadow-cut);
}

/* Step 04's export is cropped mid-content at its foot, so it reads as a panel
   that carries on past the edge of the picture. Seating it on the timeline rule
   lets the rule finish that crop, instead of leaving it to hang in the gap above.
   Positioned from its bottom edge rather than its top, so the figure grows
   upward by whatever its caption measures and the picture still lands exactly on
   the rule when that caption wraps to another line. The caption moves above the
   picture because the band it used to sit in is picture now.

   Scoped to the wide canvas: below the breakpoint there is no rule to sit on,
   and the caption belongs under its picture like every other step's. */
@media (min-width: 961px) {
  .cs__shot--seated {
    top: auto; bottom: calc(100% - var(--art-zone));
    display: flex; flex-direction: column-reverse;
  }
  .cs__shot--seated .cs__shot-cap { margin: 0 0 13px; }
}

/* Collision handling (DESIGN 4): the header sits in the band a tall shot grows
   into, so it clears out rather than being overlapped. These are the same three
   states that reveal the shot, is-active included -- the shot stays up after the
   pointer leaves, so the header has to stay down for exactly as long. Opacity
   rather than display, because the h2 still labels the section.

   The is-active branch waits for .is-browsing, which the step script sets on the
   first real hover or focus. A page seeds one step as active in its markup so the
   art zone is not empty at rest; if that seeded step carries a tall shot, the
   unguarded rule would take the heading -- and the hint telling the reader to
   hover -- away before they had done anything at all.

   Scoped to the wide canvas: below the breakpoint the shots are blocks in flow
   under the step that names them, so nothing is ever overlapped and there is
   nothing to clear -- and a tap, which lands as focusin there, would otherwise
   take the section's heading away for no reason the reader can see. */
.cs__flow-header { transition: opacity .26s ease; }
@media (min-width: 961px) {
  .cs__flow:has(.cs__step:hover .cs__shot--tall) .cs__flow-header,
  .cs__flow:has(.cs__step:focus-visible .cs__shot--tall) .cs__flow-header,
  .cs__flow.is-browsing:has(.cs__step.is-active .cs__shot--tall) .cs__flow-header {
    opacity: 0; pointer-events: none;
  }
}

/* Step 01 Horizontal Conversion Diagram */
.cs__diag-tag-rect {
  fill: var(--paper);
  stroke: var(--rule-link);
  stroke-width: 1px;
  transition: stroke .2s ease, fill .2s ease;
}
[data-theme="dark"] .cs__diag-tag-rect {
  fill: #181B1F;
}

.cs__diag-tag-dot {
  fill: var(--tag);
}

.cs__diag-tag-text {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12.5px;
  font-weight: 400;
  letter-spacing: .04em;
  fill: var(--ghost-link);
}

.cs__diag-feeder {
  stroke: var(--rule-link);
  stroke-width: 1.2px;
  stroke-dasharray: 4 3;
  fill: none;
}

.cs__step:hover .cs__diag-feeder,
.cs__step:focus-visible .cs__diag-feeder,
.cs__step.is-active .cs__diag-feeder {
  animation: csDiagFlow 1.6s linear infinite;
}

@keyframes csDiagFlow {
  to { stroke-dashoffset: -14; }
}

@media (prefers-reduced-motion: reduce) {
  .cs__step:hover .cs__diag-feeder,
  .cs__step:focus-visible .cs__diag-feeder,
  .cs__step.is-active .cs__diag-feeder {
    animation: none;
  }
}

.cs__diag-funnel {
  stroke: var(--rule-link);
  stroke-width: 1.4px;
  fill: var(--paper);
}
[data-theme="dark"] .cs__diag-funnel {
  fill: #181B1F;
}

.cs__diag-funnel-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: .16em;
  text-transform: uppercase;
  fill: var(--tag);
}

.cs__diag-core-line {
  stroke: var(--rule-link);
  stroke-width: 1px;
  stroke-dasharray: 2 2;
}

.cs__diag-arrow-shaft {
  stroke: var(--ink);
  stroke-width: 2.5px;
}

.cs__diag-arrow-head {
  fill: var(--ink);
}

.cs__diag-block {
  fill: var(--ink);
  stroke: var(--ink);
  stroke-width: 1px;
}

.cs__diag-block-eyebrow {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: .18em;
  text-transform: uppercase;
  fill: var(--paper);
  opacity: .72;
}

.cs__diag-block-headline {
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 800;
  font-size: 32px;
  letter-spacing: -.02em;
  fill: var(--paper);
}

.cs__diag-block-sub {
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 400;
  font-size: 13.5px;
  line-height: 1.4;
  fill: var(--paper);
  opacity: .9;
}

.cs__diag-block-rule {
  stroke: var(--paper);
  stroke-opacity: .25;
  stroke-width: 0.5px;
}

.cs__diag-block-meta {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: .16em;
  text-transform: uppercase;
  fill: var(--paper);
  opacity: .75;
}

/* Step 02 Venn Intersection Diagram */
.cs__venn-ring {
  fill: none;
  stroke: var(--rule-link);
  stroke-width: 1.5px;
  transition: stroke .2s ease;
}

.cs__venn-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: .18em;
  text-transform: uppercase;
  fill: var(--tag);
}

.cs__venn-title {
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 800;
  font-size: 18px;
  letter-spacing: -.02em;
  fill: var(--ink);
}

.cs__venn-item-dot {
  fill: var(--tag);
}

.cs__venn-item-text {
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 400;
  font-size: 13.5px;
  fill: var(--ghost-link);
}

.cs__venn-core-card {
  fill: var(--ink);
  stroke: var(--ink);
  stroke-width: 1px;
}

.cs__venn-core-eyebrow {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: .18em;
  text-transform: uppercase;
  fill: var(--paper);
  opacity: .72;
}

.cs__venn-core-pill {
  stroke: var(--paper);
  stroke-opacity: .3;
  stroke-width: 0.8px;
  fill: none;
}

.cs__venn-core-pill-text {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9.5px;
  letter-spacing: .14em;
  fill: var(--paper);
}

.cs__venn-core-text {
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 800;
  font-size: 14px;
  letter-spacing: -.01em;
  fill: var(--paper);
}

.cs__venn-core-amp {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-weight: 700;
  font-size: 12px;
  fill: var(--paper);
  opacity: .6;
}

.cs__venn-core-meta {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9.5px;
  letter-spacing: .18em;
  text-transform: uppercase;
  fill: var(--paper);
  opacity: .7;
}

/* Step 02 Session Replay Horizontal Timeline Diagram */
.cs__timeline-card {
  fill: var(--paper);
  stroke: var(--rule-link);
  stroke-width: 1px;
  transition: stroke .2s ease, fill .2s ease;
}
[data-theme="dark"] .cs__timeline-card {
  fill: #181B1F;
}

.cs__timeline-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: .18em;
  text-transform: uppercase;
  fill: var(--tag);
}

.cs__timeline-title {
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 800;
  font-size: 17px;
  letter-spacing: -.02em;
  fill: var(--ink);
}

.cs__timeline-pill {
  stroke: var(--rule-link);
  stroke-width: 0.8px;
  fill: var(--paper);
}
[data-theme="dark"] .cs__timeline-pill {
  fill: #181B1F;
}

.cs__timeline-pill-text {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9px;
  letter-spacing: .14em;
  text-anchor: middle;
  dominant-baseline: central;
  fill: var(--tag);
}

.cs__timeline-dot {
  fill: var(--tag);
}

.cs__timeline-text {
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 400;
  font-size: 12.5px;
  fill: var(--ghost-link);
}

.cs__timeline-feeder {
  stroke: var(--rule-link);
  stroke-width: 1.2px;
  stroke-dasharray: 4 3;
  fill: none;
}

.cs__step:hover .cs__timeline-feeder,
.cs__step:focus-visible .cs__timeline-feeder,
.cs__step.is-active .cs__timeline-feeder {
  animation: csDiagFlow 1.6s linear infinite;
}

@media (prefers-reduced-motion: reduce) {
  .cs__step:hover .cs__timeline-feeder,
  .cs__step:focus-visible .cs__timeline-feeder,
  .cs__step.is-active .cs__timeline-feeder {
    animation: none;
  }
}

.cs__timeline-arrow-head {
  fill: var(--ink);
}

.cs__timeline-meta {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9px;
  letter-spacing: .16em;
  text-transform: uppercase;
  fill: var(--tag);
}

.cs__timeline-rule {
  stroke: var(--rule-link);
  stroke-opacity: 0.35;
  stroke-width: 0.5px;
}

.cs__timeline-block {
  fill: var(--ink);
  stroke: var(--ink);
  stroke-width: 1px;
}

.cs__timeline-block-eyebrow {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: .18em;
  text-transform: uppercase;
  fill: var(--paper);
  opacity: .75;
}

.cs__timeline-block-headline {
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 800;
  font-size: 17px;
  letter-spacing: -.02em;
  fill: var(--paper);
}

.cs__timeline-block-rule {
  stroke: var(--paper);
  stroke-opacity: .25;
  stroke-width: 0.5px;
}

/* The inverted stage carries its own pill rather than borrowing the venn one, whose
   text is left-anchored: reusing it here pushed the label out past the pill's right
   edge. Metrics track .cs__timeline-pill-text so all three pills set identically. */
.cs__timeline-block-pill {
  stroke: var(--paper);
  stroke-opacity: .45;
  stroke-width: 0.8px;
  fill: none;
}

.cs__timeline-block-pill-text {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9px;
  letter-spacing: .14em;
  text-anchor: middle;
  dominant-baseline: central;
  fill: var(--paper);
}

.cs__timeline-block-dot {
  fill: var(--paper);
  opacity: .6;
}

/* Matches .cs__timeline-text so the three stages share one baseline grid. */
.cs__timeline-block-sub {
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 400;
  font-size: 12.5px;
  fill: var(--paper);
  opacity: .85;
}

/* Step 04 Unit Economics Ledger. Reuses the step 02 diagram primitives -- card, block,
   label, pill, rule, feeder, arrow head, meta -- so the two diagrams read as one set,
   and adds only what a ledger needs on top: a right-anchored figure column and a
   summed total. Right-anchored monospace is nudged by its full trailing letter-space
   (DESIGN.md 3.8), which would otherwise hold the column short of its own edge. */
.cs__unit-value {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12px;
  letter-spacing: .16em;
  text-anchor: end;
  fill: var(--woke);
}

.cs__unit-block-value {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12px;
  letter-spacing: .16em;
  text-anchor: end;
  fill: var(--paper);
}

/* Heavier than .cs__timeline-rule: this one closes a column of figures, so it has to
   read as a sum line rather than as another section divider. */
.cs__unit-sum-rule {
  stroke: var(--rule-link);
  stroke-opacity: .55;
  stroke-width: 1px;
}

.cs__unit-block-sum-rule {
  stroke: var(--paper);
  stroke-opacity: .4;
  stroke-width: 1px;
}

.cs__unit-sum-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: .18em;
  text-transform: uppercase;
  fill: var(--tag);
}

.cs__unit-block-sum-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: .18em;
  text-transform: uppercase;
  fill: var(--paper);
  opacity: .75;
}

/* These cards carry no title line, so the total is the card's primary heading and
   takes --ink under rule 2 rather than the --woke ceiling the figures above it use. */
.cs__unit-sum-figure {
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 800;
  font-size: 20px;
  letter-spacing: -.02em;
  text-anchor: end;
  fill: var(--ink);
}

.cs__unit-block-sum-figure {
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 800;
  font-size: 20px;
  letter-spacing: -.02em;
  text-anchor: end;
  fill: var(--paper);
}

/* 6. next project */
.cs__next {
  position: relative; z-index: 2;
  flex: 0 0 auto; height: 100%;
  display: grid; align-content: center;
  padding: var(--pad-y) var(--pad-x);
  border-left: 0.5px solid var(--rule-deco);
  width: min(88vw, 560px);
}
.cs__next .cs__eyebrow { margin-bottom: clamp(16px, 2.8vh, 28px); }
.cs__next-link {
  display: inline-block;
  color: var(--ink); text-decoration: none;
  margin: 0; line-height: 1.32;
}
.cs__next-title {
  display: inline;
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif; font-weight: 800;
  font-size: clamp(1.6rem, 2.8vw, 2.5rem); line-height: 1.28; letter-spacing: -.02em;
}
.cs__row {
  display: inline;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
  border-bottom: 1px solid var(--rule-link); padding-bottom: 2px;
  padding-inline: .16em .1em; margin-inline: -.16em -.1em;
  background-image: var(--marker); background-repeat: no-repeat;
  background-position: right center; background-size: 0% 86%;
  transition: border-color .3s, color .14s ease, background-size .34s cubic-bezier(.2,.7,.3,1);
}
.cs__next-link:hover .cs__row,
.cs__next-link:focus-visible .cs__row {
  background-size: 100% 86%;
  border-bottom-color: var(--marker-ink, #111316);
  color: var(--marker-ink, #111316);
  transition: border-color .3s var(--d, 0s),
             color .1s ease calc(var(--d, 0s) + .18s),
             background-size .34s cubic-bezier(.2,.7,.3,1) var(--d, 0s);
}
.cs__next-title:not(:has(.cs__row)) {
  border-bottom: 1px solid var(--rule-link); padding-bottom: 2px;
  padding-inline: .16em .1em; margin-inline: -.16em -.1em;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
  background-image: var(--marker); background-repeat: no-repeat;
  background-position: right center; background-size: 0% 86%;
  transition: border-color .3s, background-size .34s cubic-bezier(.2,.7,.3,1);
}
.cs__next-link:hover .cs__next-title:not(:has(.cs__row)),
.cs__next-link:focus-visible .cs__next-title:not(:has(.cs__row)) {
  background-size: 100% 86%; border-bottom-color: var(--marker-ink, #111316); color: var(--marker-ink, #111316);
}
.cs__next-arrow {
  display: inline-block;
  font-size: clamp(1.4rem, 2.2vw, 2rem); line-height: 1;
  margin-left: clamp(10px, 1.4vw, 16px);
  vertical-align: baseline;
  transition: transform .3s cubic-bezier(.2,.7,.3,1);
}
.cs__next-link:hover .cs__next-arrow,
.cs__next-link:focus-visible .cs__next-arrow {
  transform: translateX(6px);
}

/* ============ 7. About Me Section (.am) ============ */
:root {
  --marble-base: #EDEBF4;
  --marble-light: #F7F6FB;
  --marble-shade: #DAD7E4;
  --marble-deep: #C3BFD2;
  --marble-vein: rgba(17, 19, 22, 0.16);
  --marble-glow: rgba(255, 214, 10, 0.28);
}
:root[data-theme="dark"],
[data-theme="dark"] {
  --marble-base: #1C1E24;
  --marble-light: #2A2D36;
  --marble-shade: #14161A;
  --marble-deep: #0D0E11;
  --marble-vein: rgba(255, 255, 255, 0.16);
  --marble-glow: rgba(255, 214, 10, 0.42);
}

.am-viewport {
  height: 100vh;
  height: 100dvh;
  overflow-y: scroll;
  scroll-snap-type: y mandatory;
  scroll-behavior: smooth;
  overscroll-behavior-y: contain;
  background: var(--paper);
  color: var(--ink);
  position: relative;
}

.am__screen {
  height: 100vh;
  height: 100dvh;
  min-height: 100vh;
  scroll-snap-align: start;
  scroll-snap-stop: always;
  display: grid;
  /* Zero floors, not 270/320/270: the fixed minimums exceeded the available
     track space between 961px and ~1045px, and `overflow: hidden` clipped the
     professional column off the left edge instead of letting it narrow. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.22fr) minmax(0, 1fr);
  align-items: center;
  justify-content: center;
  padding: clamp(24px, 4vh, 44px) clamp(24px, 3.8vw, 56px);
  padding-right: calc(var(--rail-w) + clamp(20px, 3vw, 44px));
  gap: clamp(20px, 3.2vw, 52px);
  position: relative;
  box-sizing: border-box;
  overflow: hidden;
}

/* Left (Professional) & Right (Personal) Columns */
.am__col {
  display: flex;
  flex-direction: column;
  justify-content: center;
  position: relative;
  z-index: 2;
}
.am__pro { text-align: left; }
.am__personal { text-align: left; }

.am__kicker {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--tag);
  margin: 0 0 clamp(8px, 1.6vh, 14px);
  display: flex;
  align-items: center;
  gap: 8px;
}
.am__kicker-dot {
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--rule-link);
  display: inline-block;
}

.am__title {
  font-family: "Bricolage Grotesque", ui-sans-serif, sans-serif;
  font-weight: 800;
  font-size: clamp(1.4rem, 2.2vw, 2.15rem);
  line-height: 1.06;
  letter-spacing: -0.025em;
  text-wrap: balance;
  margin: 0 0 clamp(12px, 2vh, 18px);
  color: var(--ink);
}

.am__body {
  font-family: "IBM Plex Sans", ui-sans-serif, sans-serif;
  font-weight: 300;
  font-size: clamp(.88rem, 1.02vw, 1.01rem);
  line-height: 1.62;
  color: var(--ghost);
  margin: 0 0 clamp(14px, 2.2vh, 22px);
}
.am__body p { margin: 0 0 10px; }
.am__body p:last-child { margin-bottom: 0; }

.am__link {
  font-weight: 400;
  color: var(--ghost-link);
  text-decoration: underline;
  text-decoration-color: var(--rule-link);
  text-underline-offset: 3px;
  position: relative;
  cursor: pointer;
  display: inline;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
  background-image: var(--marker);
  background-repeat: no-repeat;
  background-position: right center;
  background-size: 0% 86%;
  transition: color .14s ease, background-size .34s cubic-bezier(.2,.7,.3,1), text-decoration-color .2s ease;
}
.am__link:hover,
.am__link:focus-visible {
  background-size: 100% 86%;
  text-decoration-color: transparent;
  color: var(--marker-ink, #111316);
}

/* Stage dateline, centred above the sculpture. One range per stage rather than
   one per column: the professional and personal columns narrate the same span
   of time from two angles, so the date belongs to the stage, not to a column.
   Static-px mono metadata, the sole exception to fluid scaling (DESIGN §3.7). */
.am__year {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12px;
  font-weight: 700;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--tag);
  text-align: center;
  /* Tracking leaves a trailing space after the final glyph; nudge right to
     restore optical centring (DESIGN §3.8). */
  padding-left: .18em;
  margin: 0 0 clamp(10px, 1.8vh, 18px);
}

/* Personal Vignette / Situation Card */
.am__card {
  border-left: 2px solid var(--rule-link);
  padding: 10px 14px;
  margin-top: clamp(8px, 1.4vh, 14px);
  background: rgba(128, 128, 128, 0.04);
  border-radius: 0 3px 3px 0;
  transition: border-color .2s ease;
}
.am__card:hover {
  border-left-color: var(--ink);
}
.am__card-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--tag);
  margin-bottom: 5px;
  display: block;
}
.am__card-quote {
  font-family: "IBM Plex Sans", ui-sans-serif, sans-serif;
  font-style: italic;
  font-size: clamp(.82rem, .94vw, .92rem);
  line-height: 1.5;
  color: var(--ghost);
  margin: 0;
}

/* Center Column: Marble Sculpture Stage */
.am__sculpture {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  position: relative;
  z-index: 2;
}
.am__sculpture-box {
  position: relative;
  width: 100%;
  max-width: 420px;
  display: flex;
  align-items: center;
  justify-content: center;
}
.am__sculpture-box picture {
  display: contents;
}
.am__sculpture-img {
  width: 100%;
  height: auto;
  max-height: clamp(300px, 54vh, 480px);
  object-fit: contain;
  display: block;
  filter: drop-shadow(0 14px 28px rgba(0,0,0,0.08));
  transition: transform .4s cubic-bezier(.2,.7,.3,1), filter .4s ease;
  user-select: none;
  -webkit-user-drag: none;
}
[data-theme="dark"] .am__sculpture-img {
  filter: drop-shadow(0 16px 36px rgba(0,0,0,0.65)) brightness(.96) contrast(1.04);
}
.am__sculpture-box:hover .am__sculpture-img {
  transform: scale(1.025);
}

/* Continuous Flowing Yellow Line (Layered for 3D Depth) */
.am__continuous-thread {
  position: absolute;
  top: 0;
  bottom: 0;
  left: clamp(24px, 3.8vw, 56px);
  right: calc(var(--rail-w) + clamp(20px, 3vw, 44px));
  width: calc(100% - clamp(24px, 3.8vw, 56px) - var(--rail-w) - clamp(20px, 3vw, 44px));
  height: 400vh;
  height: 400dvh;
  pointer-events: none;
  overflow: visible;
}
.am__continuous-thread--behind {
  z-index: 1;
}
.am__continuous-thread--front {
  z-index: 3;
}

.am__thread-base {
  fill: none;
  stroke: #FFD60A;
  stroke-width: 1.4;
  stroke-linecap: round;
  stroke-linejoin: round;
  filter: drop-shadow(0 0 5px rgba(255, 214, 10, 0.65));
  will-change: transform;
}

/* Stage Metadata Label */
.am__stage-meta {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--tag);
  margin-top: clamp(10px, 1.8vh, 18px);
  text-align: center;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
}
.am__stage-pill {
  border: 1px solid var(--rule-link);
  padding: 2px 7px;
  border-radius: 2px;
  font-weight: 700;
  color: var(--ink);
}

/* Scroll Hint (Bottom Right) */
.am__scroll-hint {
  position: fixed;
  z-index: 4;
  bottom: clamp(20px, 3.2vh, 32px);
  right: calc(var(--rail-w) + clamp(16px, 2.5vw, 32px));
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--tag);
  display: flex;
  align-items: center;
  gap: 6px;
  pointer-events: none;
  opacity: .85;
  transition: opacity .4s ease;
}
.am__scroll-hint-arrow {
  display: inline-block;
  animation: amHintBounce 1.8s ease-in-out infinite;
}
@keyframes amHintBounce {
  0%, 100% { transform: translateY(0); }
  50% { transform: translateY(4px); }
}

/* ============ 8. Not Found Section (.nf) ============ */
/* The 404 page. A d20 is the one place in the system where a *random* reveal is
   the point, so the fact panel is a live region rather than a hover disclosure:
   the roll is committed by click, and its result has to reach a screen reader. */
.nf {
  position: relative; z-index: 2;
  min-height: 100vh;
  display: grid; align-content: center;
  gap: clamp(38px, 6vh, 62px);
  padding: clamp(60px, 10vh, 110px) clamp(28px, 7vw, 96px);
  /* Reserve the rail's own width on the trailing edge (DESIGN §Layout Tokens). */
  padding-right: calc(var(--rail-w) + clamp(28px, 7vw, 96px));
  font-family: "IBM Plex Sans", system-ui, sans-serif;
}
.nf__core { max-width: 46ch; }

.nf__eyebrow {
  margin: 0 0 clamp(14px, 2.4vh, 22px);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12px; font-weight: 700; letter-spacing: .18em; text-transform: uppercase;
  color: var(--tag);
}
.nf__title {
  margin: 0 0 clamp(16px, 2.6vh, 26px);
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 800;
  font-size: clamp(2.1rem, 5.4vw, 4rem);
  line-height: 1.04; letter-spacing: -.03em;
  text-wrap: balance;
  color: var(--ink);
}
.nf__body {
  margin: 0;
  font-weight: 300;
  font-size: clamp(1rem, 1.5vw, 1.12rem);
  line-height: 1.6;
  color: var(--ghost);
}

/* Resting affordance: one step darker than body copy, one weight heavier, and
   underlined before interaction. The marker then strikes it left-ward on hover.
   Shared with the privacy page's inline links, which sit in the same reading
   prose and so take the same treatment rather than a copy of it. */
.nf__link,
.pp__link {
  font-weight: 400;
  color: var(--ghost-link);
  text-decoration: underline;
  text-decoration-color: var(--rule-link);
  text-underline-offset: 3px;
  cursor: pointer;
  display: inline;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
  background-image: var(--marker);
  background-repeat: no-repeat;
  background-position: right center;
  background-size: 0% 86%;
  transition: background-size .34s cubic-bezier(.2,.7,.3,1),
              color .14s ease,
              text-decoration-color .14s ease;
}
.nf__link:hover,
.nf__link:focus-visible,
.pp__link:hover,
.pp__link:focus-visible {
  background-size: 100% 86%;
  text-decoration-color: transparent;
  color: var(--marker-ink, #111316);
  transition: background-size .34s cubic-bezier(.2,.7,.3,1),
              color .1s ease .18s,
              text-decoration-color .1s ease .18s;
}

/* --- the die --- */
.nf__roller {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  align-items: center;
  gap: clamp(22px, 4vw, 46px);
  max-width: 62ch;
}
.nf__die {
  /* A button, not a link: it commits a roll rather than routing (DESIGN §4). */
  cursor: pointer;
  display: grid; justify-items: center; gap: 13px;
  padding: 6px; border: none; background: transparent;
  /* Resting affordance (DESIGN §1.3): the die is the page's primary control,
     so it rests at the interactive token rather than the hairline token —
     --rule-link's 3:1 is a floor for borders, not for something to click. */
  color: var(--ghost-link);
  transition: color .3s ease;
}
.nf__die:hover,
.nf__die:focus-visible {
  /* --woke is the ceiling for non-navigational hover; --ink stays reserved
     for routes and headings (DESIGN §1.2). */
  color: var(--woke);
}
.nf__die:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; }

.nf__die-svg {
  display: block;
  width: clamp(96px, 13vw, 132px); height: auto;
  transform-origin: 50% 50%;
  will-change: transform;
  transition: transform .3s cubic-bezier(.2,.7,.3,1);
}
.nf__die:hover .nf__die-svg,
.nf__die:focus-visible .nf__die-svg { transform: scale(1.05); }
/* A rolling die reads as a tumble, so the spin runs at the accent tempo and
   the face number is swapped mid-flight by script. */
.nf__die[data-rolling="true"] .nf__die-svg { animation: nfTumble .34s cubic-bezier(.2,.7,.3,1); }
@keyframes nfTumble {
  from { transform: rotate(0deg) scale(1); }
  60%  { transform: rotate(228deg) scale(1.05); }
  to   { transform: rotate(360deg) scale(1); }
}
.nf__die-face { stroke: currentColor; fill: none; stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round; }
.nf__die-path { fill: currentColor; }
.nf__die-num {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 46px; font-weight: 700; letter-spacing: .16em;
  fill: currentColor;
}

.nf__die-label {
  position: relative;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px; font-weight: 700; letter-spacing: .18em; text-transform: uppercase;
  color: var(--tag);
  /* Mono tracking leaves trailing space after the last glyph; half of it back
     on the left re-centres the label under the die (DESIGN §3.8). */
  padding-left: .18em;
  white-space: nowrap;
  transition: color .14s ease;
}
.nf__die-label::before {
  content: ""; position: absolute; z-index: -1; inset: -3px -6px;
  background: var(--marker);
  border-radius: 3px 9px 4px 7px;
  transform-origin: right;
  transform: rotate(-.45deg) skewX(-2.5deg) scaleX(0);
  transition: transform .34s cubic-bezier(.2,.7,.3,1);
}
.nf__die:hover .nf__die-label,
.nf__die:focus-visible .nf__die-label {
  color: var(--marker-ink, #111316);
  transition: color .1s ease .18s;
}
.nf__die:hover .nf__die-label::before,
.nf__die:focus-visible .nf__die-label::before {
  transform: rotate(-.45deg) skewX(-2.5deg) scaleX(1);
}

/* --- the fact --- */
/* The stage reserves its height up front and the fact is absolutely positioned
   inside it, so swapping a short fact for a long one never reflows the page
   (DESIGN §4, zero layout shift). Keep facts under ~130 characters: past that
   they outgrow the reserved box on a narrow viewport. */
.nf__stage {
  position: relative;
  isolation: isolate;
  min-height: clamp(96px, 15vh, 124px);
  border-left: 1px solid var(--rule-link);
  padding-left: clamp(16px, 2.4vw, 26px);
  display: flex; align-items: center;
}
.nf__fact {
  position: absolute;
  left: clamp(16px, 2.4vw, 26px); right: 0;
  margin: 0;
  font-weight: 300;
  font-size: clamp(1rem, 1.45vw, 1.14rem);
  line-height: 1.5;
  color: var(--ghost);
  opacity: 1; transform: translateY(0);
  transition: opacity .26s ease, transform .26s cubic-bezier(.2,.7,.3,1);
}
/* Script flips this while the die is mid-air, so the outgoing fact clears the
   stage before the incoming one travels in. */
.nf__fact[data-swapping="true"] { opacity: 0; transform: translateY(7px); }
.nf__fact--prompt { color: var(--tag); }

/* ============ 9. Responsive Adaptations ============ */
/* ============ 9. Gantt Track Chart (.gt) ============ */
/* The case study spine. Three tracks share one lane: branding and marketing are
   press-to-open routes either side of the product design line, which carries the
   weight and holds the lane by default. Opening an aside folds the product group
   away and unfolds its own steps in the same reserved stage.
   No box is filled at rest — the marker stroke stays the only fill on the page,
   so hierarchy is carried by box height, stroke weight and typeface rather than
   by colour. */
.gt {
  --lane-cols: 12;
  --art-w: clamp(276px, 28.8vw, 408px);
  --art-h: min(51.5vh, 419px);
  /* A drawn panel figure sets its own height, where an export arrives at
     whatever proportion it was shot in. --art-h is the box an export has to be
     made to fit; this is the taller ceiling a drawing may spend, and it is the
     room the section actually has -- the chart column runs shorter than the art
     column, and the section reserves a full viewport. The vh term is what keeps
     a short window from pushing the figure past the fold; 468px is where the
     ceiling stops being useful, since the column is only 408 wide. The floor is
     not decorative: a bare vh term resolves to zero wherever the viewport does
     -- a hidden pane, a thumbnail capture -- and the figure would size itself
     out of existence rather than merely small. */
  --art-h-drawn: clamp(336px, 58.8vh, 468px);
  /* Boxes carry their own name, so they are sized to hold a line of type */
  --bar-h: clamp(30px, 4.2vh, 40px);
  --row-gap: clamp(6px, 1vh, 12px);
  width: min(94vw, 1340px);
}

/* mono voice, shared with the case study metadata scale */
.gt__num, .gt__shot-cap, .gt__toggle-mark {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  text-transform: uppercase; letter-spacing: .16em;
  font-size: 11px;
}
.gt__num { font-variant-numeric: tabular-nums; }

.gt__frame {
  display: grid;
  grid-template-columns: minmax(0, 1fr) var(--art-w);
  column-gap: clamp(26px, 3.2vw, 60px);
  align-items: center;
}
.gt__chart {
  grid-column: 1;
  display: grid;
  grid-template-columns: repeat(var(--lane-cols), minmax(0, 1fr));
  row-gap: var(--row-gap);
  margin: 0; padding: 0; list-style: none;
  /* Three ground rules, set in --rule-faint. They cross every box in the chart,
     so at structural weight they read as part of a hoverable row rather than as
     the ground behind it; carrying no information of their own, they are exempt
     from the 3:1 floor a real divider has to clear. */
  background-image: repeating-linear-gradient(to right,
    var(--rule-faint) 0 1px, transparent 1px calc(100% / 3));
  background-repeat: no-repeat;
}

/* ---- the stage: one region, three mutually exclusive groups ---- */
/* Every group sits in the same single grid cell, so the stage is always as tall
   as the tallest of them and a swap can never reflow the chart around it. This
   is the exception the zero-layout-shift rule is written for: the fold is a
   deliberate, click-initiated disclosure, and it is contained. */
.gt__stage {
  grid-column: 1 / -1;
  display: grid;
  margin: clamp(11px, 1.7vh, 19px) 0;
  padding: clamp(12px, 1.8vh, 20px) 0;
  border-top: 0.5px solid var(--rule-deco);
  border-bottom: 0.5px solid var(--rule-deco);
}
.gt__group {
  grid-area: 1 / 1;
  display: grid;
  grid-template-columns: repeat(var(--lane-cols), minmax(0, 1fr));
  row-gap: var(--row-gap);
  align-content: start;
  margin: 0; padding: 0; list-style: none;
  visibility: hidden; pointer-events: none;
  transform: scaleY(0);
  transition: transform .3s cubic-bezier(.2, .7, .3, 1), visibility 0s linear .3s;
}
/* An open group must resolve to transform: none, not scaleY(1) — a transformed
   element becomes the containing block for anything absolutely positioned inside
   it, and that is why the artwork lives outside the chart entirely. */
.gt__group.is-open {
  visibility: visible; pointer-events: auto;
  transform: none;
  transition: transform .3s cubic-bezier(.2, .7, .3, 1), visibility 0s;
}
/* A group hinges on the edge nearest the row that opens it: branding sits above
   the stage, so the product line folds down out of its way and branding unfolds
   downward into the space; marketing sits below and the pair reverses. */
.gt__group--branding { transform-origin: top; }
.gt__group--marketing { transform-origin: bottom; }
.gt__group--product { transform-origin: bottom; }
.gt__stage[data-open="marketing"] .gt__group--product { transform-origin: top; }

/* rows stagger into view and collapse together */
.gt__group .gt__track { opacity: 0; transition: opacity .26s ease; }
.gt__group.is-open .gt__track { opacity: 1; transition-delay: var(--d, 0s); }

.gt__track {
  grid-column: 1 / -1;
  display: grid; grid-template-columns: subgrid;
  align-items: center;
}

/* ---- the name a box carries ---- */
.gt__num { flex: 0 0 auto; color: var(--tag); transition: color .14s ease; }
.gt__name {
  min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  font-size: clamp(.95rem, 1.15vw, 1.05rem); line-height: 1.3;
  /* --woke, not --ghost: the name is the entire content of an unfilled box, and
     --ghost reads as a whisper against the dark ground. This is the ceiling the
     system allows here — --ink stays reserved for links and headings, and at
     15.5:1 (dark) / 13.64:1 (light) --woke is a step off it, not a compromise. */
  color: var(--woke); font-weight: 300;
  transition: color .14s ease;
}

/* ---- bars ---- */
.gt__bar {
  grid-column: var(--from) / var(--to);
  display: flex; align-items: center; gap: clamp(8px, .9vw, 12px);
  height: var(--bar-h);
  padding: 0 clamp(10px, 1.1vw, 15px);
  /* 4px keeps two consecutive boxes from fusing into one continuous rule */
  margin-right: 4px;
  border: 1px solid var(--rule-link);
  color: var(--tag);
  /* the marker reveals right-to-left, counter to the reading axis */
  background-image: var(--marker); background-repeat: no-repeat;
  background-position: right center; background-size: 0% 100%;
  transition: color .14s ease, border-color .3s, box-shadow .3s,
              background-size .34s cubic-bezier(.2, .7, .3, 1);
}
/* The phases outweigh the two asides by stroke and height alone */
.gt__bar--phase { box-shadow: inset 0 -3px 0 var(--rule-link); }
.gt__bar--aside { height: calc(var(--bar-h) * .82); border-style: dashed; }
.gt__bar--aside .gt__name { font-size: clamp(.88rem, 1.05vw, .97rem); color: var(--ghost); }
.gt__bracket {
  grid-column: var(--from) / var(--to);
  height: clamp(9px, 1.2vh, 13px); margin-right: 4px;
  border: 1px solid var(--rule-link); border-bottom: 0;
}

/* the lead row heads its group above the bracket */
.gt__track--lead .gt__name {
  grid-column: 1 / -1; margin-bottom: clamp(7px, 1.1vh, 11px);
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif; font-weight: 800;
  font-size: clamp(1.15rem, 1.7vw, 1.5rem); line-height: 1.08; letter-spacing: -.02em;
  color: var(--ink);
}
/* A phase only reveals its artwork, so it is not a click target (DESIGN.md §4).
   The cursor rides the box rather than the row for the same reason the hover
   does: the row is a full-lane grid cell, and most of it is the empty lane the
   box is positioned within, not the box itself. */
.gt__track--phase .gt__bar { cursor: help; }
.gt__track--aside { cursor: default; }

/* ---- the two aside tracks are disclosure buttons ---- */
.gt__toggle {
  appearance: none; -webkit-appearance: none;
  font: inherit; color: inherit; text-align: left;
  background-color: transparent; border-radius: 0;
  /* a control that commits an action on click, so it takes the pointer */
  cursor: pointer;
}
.gt__toggle-mark {
  margin-left: auto; flex: 0 0 auto; color: var(--tag);
  transition: color .14s ease;
}
.gt__toggle-mark::after { content: "+"; }
.gt__toggle[aria-expanded="true"] .gt__toggle-mark::after { content: "\2212"; }
/* an open track holds the lane, so it drops the dashed outline it wore closed */
.gt__toggle[aria-expanded="true"] {
  border-style: solid; border-color: var(--woke);
  box-shadow: inset 0 -3px 0 var(--woke);
}

/* ---- hover / focus ---- */
/* A row that has never been pointed at lifts only to the contrast ceiling. This
   is the state the seeded row rests in before the reader has done anything. */
.gt__track.is-active .gt__bar { border-color: var(--woke); }
.gt__track.is-active .gt__bar--phase { box-shadow: inset 0 -3px 0 var(--woke); }
.gt__track.is-active .gt__num,
.gt__track.is-active .gt__name { color: var(--woke); }

/* Struck by the marker, the box floods and every glyph in it locks to
   --marker-ink for 13.5:1 against the yellow core. Ordered after the resting
   rules so it wins the tie on equal specificity.

   The stroke holds on the active row after the pointer leaves, rather than
   retracting with it. The chart's artwork is a reserved stage that keeps showing
   the last row pointed at, so the stroke is what says which row the panel on the
   right belongs to -- let it retract and the chart is left displaying an answer
   with nothing marking the question. This is the marker acting as a selection
   indicator rather than a hover indicator (DESIGN 1.4), and it is gated on
   .is-browsing so a page's seeded row is never struck before the reader has
   pointed at anything. */
.gt.is-browsing .gt__track--phase.is-active .gt__bar,
.gt.is-browsing .gt__track--aside.is-active .gt__bar,
.gt__track--phase .gt__bar:hover,
.gt__track--phase:focus-visible .gt__bar,
.gt__track--aside .gt__bar:hover,
.gt__toggle:focus-visible {
  background-size: 100% 100%;
  border-color: var(--marker-ink, #111316);
  transition: color .1s ease .18s, border-color .3s, box-shadow .3s,
              background-size .34s cubic-bezier(.2, .7, .3, 1);
}
.gt.is-browsing .gt__track--phase.is-active .gt__bar--phase,
.gt.is-browsing .gt__track--aside.is-active .gt__toggle,
.gt__bar--phase:hover,
.gt__track--phase:focus-visible .gt__bar--phase,
.gt__toggle:hover,
.gt__toggle:focus-visible {
  box-shadow: inset 0 -3px 0 var(--marker-ink, #111316);
}
.gt.is-browsing .gt__track--phase.is-active .gt__num,
.gt.is-browsing .gt__track--phase.is-active .gt__name,
.gt.is-browsing .gt__track--aside.is-active .gt__name,
.gt.is-browsing .gt__track--aside.is-active .gt__toggle-mark,
.gt__bar:hover .gt__num,
.gt__track--phase:focus-visible .gt__num,
.gt__bar:hover .gt__name,
.gt__track--phase:focus-visible .gt__name,
.gt__toggle:hover .gt__toggle-mark,
.gt__toggle:focus-visible .gt__name,
.gt__toggle:focus-visible .gt__toggle-mark {
  color: var(--marker-ink, #111316);
  transition: color .1s ease .18s;
}

/* ---- the art layer ---- */
/* Every panel occupies the same grid cell, so the column is as tall as the
   tallest of them and one reveal can never shift the chart beside it. */
.gt__art { grid-column: 2; display: grid; }
.gt__shot {
  grid-area: 1 / 1; margin: 0;
  opacity: 0; transform: translateX(8px);
  pointer-events: none; /* never blocks the chart underneath it */
  /* An inactive panel leaves the accessibility and tab order, not just the eye
     (DESIGN 4, Reserved Stages). It cost nothing while every panel was a
     picture; one of them now holds a tablist, and a control inside a panel that
     is not on screen must not be a tab stop. Visibility is stepped, not eased,
     so it waits out the fade on the way down and switches immediately on the
     way up. */
  visibility: hidden;
  transition: opacity .26s ease, transform .26s cubic-bezier(.2, .7, .3, 1),
              visibility 0s linear .26s;
}
.gt__shot.is-active {
  opacity: 1; transform: none; visibility: visible;
  transition: opacity .26s ease, transform .26s cubic-bezier(.2, .7, .3, 1),
              visibility 0s;
}
.gt__shot-img {
  display: block; width: 100%; height: var(--art-h);
  object-fit: contain;
  /* The picture is inset from its own edge rather than meeting it, so the sheet
     reads as a mount around the artwork instead of a crop of it. Inside the box
     (border-box), so the lane height is unchanged. */
  padding: 8px;
  background: var(--paper); border: 1px solid var(--rule-link);
}
/* The placeholders are 800x560; the lane is taller than that ratio, so a fixed
   height would letterbox the wireframe and leave the frame drawn around two
   bands of empty paper. The frame exists to separate an empty wireframe from the
   page, so it has to end where the wireframe does -- these take their own ratio
   and sit shorter than the lane. Scaffolding only: every one is replaced by a
   real export, which fills the lane.

   Wide canvas only: below the breakpoint every panel is already sized from its
   own ratio, and the cap would be the one thing holding a placeholder back. */
@media (min-width: 961px) {
  .gt__shot:not(.gt__shot--dark):not(.gt__shot--cut) .gt__shot-img:not(.gt__shot-diagram) {
    height: auto; aspect-ratio: 800 / 560; max-height: var(--art-h);
  }
}
[data-theme="dark"] .gt__shot-img:not(.gt__shot-diagram) { background: #181B1F; }

/* A drawn schematic in the panel rather than an export. It carries neither the
   placeholder's frame nor a sheet: a diagram is inked *onto* the paper and
   inverts with it (rule 6), so the sheet tokens -- which exist to stop a
   photographic export's own ground from fighting the page's -- have nothing to
   do here. Sized from its own viewBox, not from the placeholder ratio.

   The class sits on the svg rather than on the figure so it survives being
   cloned into the zoom reader, where there is no figure above it to match.

   These are drawn at the panel's own unit width, so nothing is being rendered
   below its own resolution and no magnifier is wanted: the loupe cannot read an
   inline svg at all and quietly stays shut, which is the correct outcome here
   rather than a gap. */
/* The metadata tier sits at the floor inside a panel schematic. The timeline
   diagrams set it at 9px, which is under the 10px minimum §3 gives monospace but
   reads at that size because those are drawn on a 900-1020 unit lane and painted
   at roughly 1:1. A panel drawing is a third of that lane, so the same 9px would
   land visibly under the floor. Scoped here rather than raised on the primitives
   themselves, whose own diagrams are laid out around the narrower measure. */
.gt__shot-diagram .cs__timeline-meta,
.gt__shot-diagram .cs__timeline-pill-text,
.gt__shot-diagram .cs__timeline-block-pill-text {
  font-size: 10px;
}

.gt__shot-diagram {
  /* Sized from its own height rather than from the column, so the box is always
     exactly the picture: --art-h-drawn is the ceiling, the ratio gives the width
     it implies, and the column caps that on a narrow canvas. Letting width lead
     instead would clamp the height on a short window and letterbox the drawing
     inside a box wider than itself -- dead ground the reader reads as a margin
     that only appears at some window sizes. */
  width: min(100%, calc(var(--art-h-drawn) * var(--nat-ratio, 1)));
  height: auto;
  padding: 0; border: none; background: none;
}

/* A real export drops the placeholder's frame -- that rule is there to separate
   an empty wireframe from the page, and around artwork it reads as a second,
   competing line -- but keeps a ground. --dark is the carbon sheet: these boards
   were composed on the product's own dark canvas, and the transparency in them
   *is* that canvas, so any type left outside an opaque panel is light type that a
   paper ground would erase. The sheet therefore holds in both themes.

   The radius is the one softening in the system, and it is 2px: enough to stop
   the sheet reading as a hard-cut rectangle now that no rule finishes its edge,
   small enough not to become a card corner. */
.gt__shot--dark .gt__shot-img {
  background: var(--art-sheet-dark);
  border: none; border-radius: 2px;
}

/* --cut is the alpha cast, for an export whose opaque area is the whole picture
   -- a phone frame, a rendered mark -- transparent only outside its own edge. It
   brings its own edge, so a frame around it reads as a second competing line and
   a sheet behind it as a slab it never sat on. */
.gt__shot--cut .gt__shot-img {
  background: none; border: none;
  filter: var(--shadow-cut);
}

/* --screen is --cut for a capture taken off a phone: the export is a flat
   rectangle because the grab was, but the thing pictured has rounded corners, and
   at this size a hard-cut rectangle reads as a cropped region of a screen rather
   than as the screen. Rounding it puts the corner back.

   It is the one place a radius larger than the sheet's 2px is right, and it is not
   decoration -- it is the subject's own geometry. It scales with how large the
   picture is drawn: ~4.7% of the width, the proportion a phone screen actually
   carries, which is 8px in the --art-w panel and about twice that once the reader
   opens the file nearer its own resolution. The drop-shadow rasterises after the
   clip, so --cut's shadow follows the corner rather than the box. */
.gt__shot--screen .gt__shot-img {
  /* The box has to *be* the picture for any of this to land. The shared rule is
     `width: 100%` with `object-fit: contain`, which letterboxes a 9:20 capture
     inside a box twice its width -- a radius on that box rounds transparent
     ground 100px clear of the artwork, and the drop-shadow casts from a corner
     the picture does not have. So this one takes its width from its own ratio off
     the lane height, the way .gt__shot-diagram and .cs__shot--tall already do, and
     drops the 8px mount: that inset exists to hold a sheet's edge off the artwork,
     and a cut export has no sheet to hold. */
  width: auto; max-width: 100%;
  padding: 0;
  margin-inline: auto;
  /* One ratio, three sizes. A phone screen's corner is about 4.7% of its width,
     and this picture is drawn at three widths -- ~193px in the --art-w panel, the
     full column below the breakpoint, and its authored 590px in the reader -- so a
     single px value would read as a screen in one of them and as a hairline or a
     card corner in the others. The value is set per regime instead, each one
     4.7% of the width that regime actually paints. */
  border-radius: 9px;
}
@media (max-width: 960px) {
  /* Stacked, the picture is the column rather than a panel inside it: ~337px. */
  .gt__shot--screen .gt__shot-img { border-radius: 15px; }
}

.gt__shot-cap { margin: 11px 0 0; color: var(--tag); line-height: 1.5; }
.gt__shot-note {
  display: block; margin-top: 8px;
  font-family: "IBM Plex Sans", system-ui, sans-serif; font-weight: 300;
  text-transform: none; letter-spacing: 0;
  font-size: .92rem; line-height: 1.5; color: var(--ghost);
}

/* ============ the brand voice matrix (.vm) ============ */
/* A live card in the chart panel, in place of a static picture. It manifests the
   system thesis: one brand voice across two physical human situations.
   Presented as a dual-register visual split card:
   - Dry hands: on paper ground (qualifier and analytical reasoning).
   - Wet hands: on inverted carbon ground (the number and nothing else).

   Sized to --art-h-drawn as a floor, matching the schematics in the stage.
   Zero layout shifts: self-contained and fluid across all viewport widths. */
.vm {
  display: flex; flex-direction: column;
  min-height: var(--art-h-drawn);
  padding: 0;
  border: 1px solid var(--rule-link);
  /* 6px, matching the rx the schematic cards are drawn with */
  border-radius: 6px;
  background: var(--paper);
  overflow: hidden;
}
[data-theme="dark"] .vm { background: #181B1F; }

/* ---- Upper Register: Dry hands (Analysis) ---- */
.vm__pane--desk {
  display: flex; flex-direction: column;
  justify-content: space-between;
  flex: 1 1 50%;
  padding: clamp(16px, 2vw, 24px) clamp(16px, 2.2vw, 24px);
  background: var(--paper);
}
[data-theme="dark"] .vm__pane--desk {
  background: #181B1F;
}

.vm__meta {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px;
  margin-bottom: clamp(10px, 1.4vw, 16px);
}
.vm__hands {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; font-weight: 400;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--tag);
}
.vm__grade {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; font-weight: 400;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--ghost);
}

.vm__body {
  margin-top: auto;
}
.vm__line {
  margin: 0;
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 300;
  font-size: clamp(14px, 1.45vw, 16px);
  line-height: 1.5;
  color: var(--ghost);
}
.vm__action {
  margin: clamp(6px, 0.8vw, 10px) 0 0;
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 600;
  font-size: clamp(14px, 1.45vw, 16px);
  line-height: 1.5;
  color: var(--ink);
}

/* ---- Lower Register: Wet hands (Workout HUD on Inverted Carbon) ---- */
.vm__pane--gym {
  display: flex; flex-direction: column;
  justify-content: space-between;
  flex: 1 1 50%;
  padding: clamp(16px, 2vw, 24px) clamp(16px, 2.2vw, 24px);
  background: #181B1F;
  border-top: 1px solid var(--rule-link);
}
[data-theme="dark"] .vm__pane--gym {
  background: #0D0E11;
  border-top-color: var(--rule-link);
}

.vm__pane--gym .vm__hands {
  color: #8E929A;
}
.vm__pane--gym .vm__grade {
  color: #C5C7CC;
}

.vm__killed {
  margin: 0 0 clamp(10px, 1.4vw, 16px);
}
.vm__killed s {
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 300;
  font-size: clamp(12px, 1.15vw, 13px);
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.55);
  text-decoration-line: line-through;
  text-decoration-thickness: 1px;
}

.vm__hud {
  display: flex; gap: clamp(24px, 3.5vw, 42px);
  margin-top: auto;
}
.vm__hud-metric {
  display: flex; flex-direction: column;
}
.vm__hud-metric .vm__grade {
  margin-bottom: 5px;
  color: #8E929A;
}
.vm__figure {
  margin: 0;
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 800;
  font-size: clamp(1.35rem, 1.9vw, 1.65rem);
  line-height: 1.04; letter-spacing: -.02em;
  color: #FFFFFF;
}
.vm__figure--clock {
  font-variant-numeric: tabular-nums;
}

/* ---- the panel that holds it ---- */
.gt__shot--card .vm,
.gt__shot--card .pq,
.gt__shot--card .mp,
.gt__shot--card .oc,
.gt__shot--card .qc,
.gt__shot--card .fn { width: 100%; }

/* ============ the pull quote card (.pq) ============ */
/* A live card in Product Step 01 replacing the placeholder image with a
   high-impact pull quote from athlete interviews.
   Floored at --art-h-drawn, matching the schematics in the stage. */
.pq {
  display: flex; flex-direction: column;
  justify-content: space-between;
  min-height: var(--art-h-drawn);
  padding: clamp(20px, 2.6vw, 30px) clamp(18px, 2.4vw, 26px);
  border: 1px solid var(--rule-link);
  border-radius: 6px;
  background: var(--paper);
  box-sizing: border-box;
}
[data-theme="dark"] .pq { background: #181B1F; }

.pq__meta {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px;
}
.pq__tag {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; font-weight: 400;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--tag);
}
.pq__pill {
  display: inline-flex; align-items: center;
  padding: 2px 7px;
  border: 0.8px solid var(--rule-link);
  border-radius: 3px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9px; font-weight: 400;
  letter-spacing: .14em; text-transform: uppercase;
  color: var(--tag);
  line-height: 1.2;
}

.pq__body {
  position: relative;
  margin: auto 0;
  padding: clamp(16px, 2vw, 24px) 0;
}
.pq__glyph {
  position: absolute;
  top: -12px; left: -6px;
  font-family: "Bricolage Grotesque", Georgia, serif;
  font-size: 52px; line-height: 1;
  color: var(--rule-link);
  opacity: 0.22;
  pointer-events: none;
  user-select: none;
}
.pq__quote {
  position: relative;
  margin: 0;
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 800;
  font-size: clamp(1.32rem, 1.85vw, 1.68rem);
  line-height: 1.18;
  letter-spacing: -.025em;
  color: var(--ink);
  text-wrap: balance;
}

.pq__author {
  display: flex; align-items: center;
  margin-top: auto;
  padding-top: clamp(12px, 1.5vw, 16px);
  border-top: 1px solid var(--rule-deco);
}
.pq__author-line {
  display: inline-flex; align-items: center; gap: 8px;
}
.pq__author-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: var(--rule-link);
  flex-shrink: 0;
}
.pq__author-name {
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 600;
  font-size: clamp(13px, 1.25vw, 14px);
  color: var(--ink);
  font-style: normal;
  letter-spacing: 0;
}

/* ============ the market positioning card (.mp) ============ */
/* A live card in Marketing Step 01 replacing the placeholder image with a
   dual-register visual comparison of the fragmented stack vs. Kynso antidote.
   - Upper register (Paper ground): 4 disconnected pills representing the user's
     fragmented stack (Cardio, Lifting, Diet, Mobility) with Siloed data tag.
   - Lower register (Inverted carbon ground): The Kynso Antidote, grouping all into
     a unified node with the Correlated Intelligence tag.
   Sized to --art-h-drawn as a floor, fluid across viewport widths, zero layout shift. */
.mp {
  display: flex; flex-direction: column;
  justify-content: space-between;
  min-height: var(--art-h-drawn);
  border: 1px solid var(--rule-link);
  border-radius: 6px;
  background: var(--paper);
  overflow: hidden;
  box-sizing: border-box;
}
[data-theme="dark"] .mp { background: #181B1F; }

/* ---- Upper Register: Fragmented Stack (Paper ground) ---- */
.mp__pane--fragmented {
  display: flex; flex-direction: column;
  justify-content: space-between;
  flex: 1 1 50%;
  padding: clamp(14px, 1.8vw, 20px) clamp(16px, 2vw, 22px);
  background: var(--paper);
}
[data-theme="dark"] .mp__pane--fragmented {
  background: #181B1F;
}

.mp__meta {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px;
}
.mp__label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px; font-weight: 400;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--tag);
}
.mp__tag {
  display: inline-flex; align-items: center;
  padding: 2px 7px;
  border-radius: 3px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; font-weight: 400;
  letter-spacing: .14em; text-transform: uppercase;
  line-height: 1.2;
}
.mp__tag--siloed {
  border: 0.8px solid var(--rule-link);
  color: var(--tag);
}
.mp__tag--correlated {
  border: 0.8px solid rgba(255, 255, 255, 0.28);
  color: #C5C7CC;
}

/* 4 Disconnected pills */
.mp__silos {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(6px, 0.9vw, 9px);
  margin-top: auto;
  padding-top: clamp(10px, 1.3vw, 14px);
}
.mp__pill--siloed {
  display: inline-flex; align-items: center;
  gap: 8px;
  padding: clamp(5px, 0.7vw, 7px) clamp(8px, 1vw, 12px);
  border: 1px dashed var(--rule-link);
  border-radius: 999px;
  background: var(--paper);
  box-sizing: border-box;
}
[data-theme="dark"] .mp__pill--siloed {
  background: #181B1F;
}
.mp__pill-dot {
  width: 5px; height: 5px; border-radius: 50%;
  border: 1px solid var(--rule-link);
  background: transparent;
  flex-shrink: 0;
}
.mp__pill-name {
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 400;
  font-size: clamp(13px, 1.25vw, 14.5px);
  line-height: 1.2;
  color: var(--ghost);
  letter-spacing: -.01em;
}

/* ---- Lower Register: The Kynso Antidote (Inverted carbon ground) ---- */
.mp__pane--antidote {
  display: flex; flex-direction: column;
  justify-content: space-between;
  flex: 1 1 50%;
  padding: clamp(14px, 1.8vw, 20px) clamp(16px, 2vw, 22px);
  background: #181B1F;
  border-top: 1px solid var(--rule-link);
}
[data-theme="dark"] .mp__pane--antidote {
  background: #0D0E11;
  border-top-color: var(--rule-link);
}

.mp__title {
  margin: 0;
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 800;
  font-size: clamp(1.08rem, 1.36vw, 1.28rem);
  line-height: 1.12;
  letter-spacing: -.02em;
  color: #FFFFFF;
}

/* Unified Node Card */
.mp__node {
  margin-top: auto;
  padding: clamp(10px, 1.2vw, 13px) clamp(12px, 1.4vw, 15px);
  border: 1px solid rgba(255, 255, 255, 0.22);
  border-radius: 5px;
  background: rgba(255, 255, 255, 0.05);
  box-sizing: border-box;
}
.mp__node-domains {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: 4px 6px;
}
.mp__node-pill {
  display: inline-flex; align-items: center;
  padding: 2px 7px;
  border: 1px solid rgba(255, 255, 255, 0.28);
  border-radius: 3px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: clamp(10px, 0.98vw, 11px);
  font-weight: 500;
  letter-spacing: .06em;
  color: #FFFFFF;
  line-height: 1.2;
  background: rgba(255, 255, 255, 0.08);
}
.mp__node-link {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px;
  color: #8E929A;
  user-select: none;
}
.mp__node-body {
  display: flex; align-items: center; gap: 9px;
  margin-top: clamp(8px, 1vw, 10px);
  padding-top: clamp(7px, 0.9vw, 9px);
  border-top: 1px solid rgba(255, 255, 255, 0.12);
}
.mp__node-core-dot {
  width: 6px; height: 6px; border-radius: 50%;
  background: #FFFFFF;
  flex-shrink: 0;
  box-shadow: 0 0 6px rgba(255, 255, 255, 0.5);
}
.mp__node-core-text {
  display: flex; flex-direction: column;
}
.mp__node-core-headline {
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 600;
  font-size: clamp(13px, 1.2vw, 14px);
  line-height: 1.25;
  color: #FFFFFF;
  letter-spacing: -.01em;
}
.mp__node-core-sub {
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 300;
  font-size: clamp(12px, 1.1vw, 13px);
  line-height: 1.35;
  color: #C5C7CC;
  margin-top: 1px;
}

/* ============ the pre-beta funnel cascade (.fn) ============ */
/* The Marketing track's own panel -- the one the aside bar shows before any step
   is pointed at -- so it has to say what the whole track was, not what one step
   produced. Three crops of the waitlist page: the hook, the conversion point, and
   the success state, cascading bottom-left to top-right so the funnel reads as one
   movement rather than as three pictures set side by side.

   Drawn, not exported. A diagram is inked onto the paper and inverts with it
   (DESIGN 5/6), so these crops are greyscale on --paper in both themes rather
   than reproductions of the product's own neon-on-carbon canvas -- which would be
   the one thing on the page holding colour it had not earned.

   Depth is a rule, not a shadow (rule 5): each crop separates from the one behind
   it by carrying its own border and an opaque ground, which is what the flat print
   premise has in place of elevation. */
.fn {
  display: flex; flex-direction: column;
  min-height: var(--art-h-drawn);
  padding: clamp(16px, 2.2vw, 24px);
  border: 1px solid var(--rule-link);
  border-radius: 6px;
  background: var(--paper);
  box-sizing: border-box;
  gap: clamp(12px, 1.6vw, 18px);
  /* Sized against the card, not the window: the panel is a fixed --art-w box that
     can be 288px wide inside a 1000px viewport. Same reasoning as .oc / .qc. */
  container-type: inline-size;
}
[data-theme="dark"] .fn { background: #181B1F; }

/* ---- Meta bar ---- */
/* The opening line every live card in this stage shares. No display title: the
   cascade is the content, the figcaption already names the track, and a heading
   here would only repeat it into space the crops need. */
.fn__meta {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px;
}
.fn__label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px; font-weight: 400;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--tag);
}
.fn__tag {
  display: inline-flex; align-items: center;
  padding: 2px 7px;
  border-radius: 3px;
  border: 0.8px solid var(--rule-link);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; font-weight: 400;
  letter-spacing: .14em; text-transform: uppercase;
  line-height: 1.2;
  color: var(--tag);
  white-space: nowrap;
}

/* ---- The cascade ---- */
/* The step is a fixed offset, not a share of whatever height is left. Anchoring
   the first crop to the bottom and the last to the top would make the overlap a
   residue of the stage -- it opens into a gap on a tall window and swallows a
   whole crop on a short one. Stepping instead keeps the overlap bounded at both
   ends: --ch and --sy scale on the same viewport height --art-h-drawn does, and
   their difference is the overlap, which stays between 20 and 34px throughout.

   --tuck is what that overlap is allowed to cover: a bottom reserve, held clear of
   copy, always at least as deep as the overlap can get. Every crop is therefore
   read in full, and what tucks under the crop in front of it is empty ground. A
   crop has no natural bottom edge anyway -- it is a cut, not a card. */
/* The stage is the room; the cascade is the figure inside it, and it is centred
   rather than stretched. The two are sized on different axes -- the stage takes
   its height from --art-h-drawn (a viewport height) while the crops are sized by
   the panel's width -- so on a tall window with a narrow panel there is height the
   cascade cannot spend without either breaking the overlap or blowing the type up
   past what the column can carry. Air is the right thing to do with it. */
.fn__stage {
  flex: 1 1 auto;
  display: flex; align-items: center;
  --ch: clamp(96px, 21.5vh, 146px);
  /* The step is derived from the crop height, so the overlap is exactly --ov at
     every viewport rather than a difference between two independent clamps. */
  --ov: clamp(24px, 5vh, 30px);
  --sy: calc(var(--ch) - var(--ov));
  /* The bottom reserve the overlap is allowed to cover, held clear of copy. One
     step deeper than the overlap, so a crop whose copy grows past --ch still tucks
     empty ground rather than a line of text. */
  --tuck: calc(var(--ov) + 6px);
}
.fn__cascade {
  position: relative;
  width: 100%;
  height: calc(var(--sy) * 2 + var(--ch));
  /* Local stacking context, so the crops order among themselves without reaching
     into the global scale (DESIGN 6). */
  isolation: isolate;
}
.fn__card {
  position: absolute;
  display: flex; flex-direction: column;
  gap: clamp(6px, 0.9vw, 9px);
  width: var(--cw, 66%);
  min-height: var(--ch);
  padding: clamp(9px, 1.2vw, 13px) clamp(10px, 1.3vw, 14px) var(--tuck);
  border: 1px solid var(--rule-link);
  border-radius: 5px;
  background: var(--paper);
  box-sizing: border-box;
}
[data-theme="dark"] .fn__card { background: #181B1F; }

/* Front-to-back runs bottom-left forward, the way a fanned deck lies: each crop
   tucks its foot under the one in front of it. A crop has no natural bottom edge
   anyway -- it is a cut, not a card -- so the covered edge reads as intended. */
.fn__card--hook    { left: 0;                        bottom: 0;                        z-index: 3; }
.fn__card--action  { left: var(--sx, 17%);           bottom: var(--sy);                z-index: 2; }
.fn__card--confirm { left: calc(var(--sx, 17%) * 2); bottom: calc(var(--sy) * 2);      z-index: 1; }

.fn__step {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; font-weight: 400;
  letter-spacing: .15em; text-transform: uppercase;
  color: var(--tag);
  line-height: 1.4;
}

/* A crop's lead is the headline it was cut around, but it is neither this page's
   heading nor a link, so it stops at the --woke ceiling rule 2 allows. */
.fn__lead {
  margin: 0;
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 800;
  font-size: clamp(13px, 1.25vw, 15px);
  line-height: 1.18;
  letter-spacing: -.02em;
  color: var(--woke);
  text-wrap: balance;
}
.fn__lead--confirm {
  display: flex; align-items: center;
  gap: 6px;
}
.fn__check { flex-shrink: 0; color: var(--rule-link); }

.fn__badge {
  display: inline-flex; align-items: center;
  align-self: flex-start;
  gap: 5px;
  padding: 3px 7px;
  border: 1px solid var(--rule-link);
  border-radius: 999px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 9.5px; font-weight: 400;
  letter-spacing: .06em; text-transform: uppercase;
  line-height: 1.3;
  color: var(--tag);
  white-space: nowrap;
}
.fn__glyph { flex-shrink: 0; }

/* The conversion point itself: the field and the button that commits it. Drawn as
   inert marks -- this is a picture of a form, not a form, so nothing here is
   focusable and nothing carries a hover (rule 3: affordance is established at
   rest, and there is no action behind these to afford). */
.fn__form {
  display: flex; align-items: stretch;
  gap: 5px;
}
.fn__field {
  flex: 1 1 auto;
  min-width: 0;
  display: flex; align-items: center;
  padding: 5px 8px;
  border: 1px solid var(--rule-link);
  border-radius: 4px;
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 300;
  font-size: clamp(10.5px, 1vw, 11.5px);
  line-height: 1.3;
  color: var(--tag);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* The one filled mark in the cascade, and it is ink rather than accent: the
   marker is reserved for interaction (rule 4) and this button is a drawing. Its
   label inverts to --paper on the ink ground, per rule 8. */
.fn__cta {
  display: inline-flex; align-items: center;
  padding: 5px 8px;
  border: 1px solid var(--ink);
  border-radius: 4px;
  background: var(--ink);
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 400;
  font-size: clamp(10.5px, 1vw, 11.5px);
  line-height: 1.3;
  color: var(--paper);
  white-space: nowrap;
}

.fn__hint {
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 300;
  font-size: clamp(10.5px, 1vw, 11.5px);
  line-height: 1.35;
  color: var(--ghost);
  letter-spacing: -.01em;
}

/* Narrow panel: widen each crop and shorten the step, so the cascade holds its
   diagonal without dropping any crop under the legible floor. Below ~150px a crop
   is too narrow to carry a headline fragment at all. */
@container (max-width: 330px) {
  .fn { padding: 14px 16px; gap: 11px; }
  /* Each crop widens to hold its headline and the step shortens to match, so the
     diagonal survives a column too narrow for the wide proportions. */
  .fn__stage { --ch: 108px; --ov: 24px; }
  .fn__card {
    --cw: 76%;
    --sx: 12%;
    padding: 8px 10px var(--tuck);
    gap: 5px;
  }
  .fn__lead { font-size: 12.5px; }
}

/* The narrowest band, where the panel is a 288px box beside a wide chart rather
   than a phone column. Side by side, the field and the button need ~180px and the
   crop has ~164px: the address truncates mid-domain, which reads as a clipped
   element rather than as a cropped picture. Stacked, both are whole -- and --ch is
   raised, not lowered, to pay for the second row and for the headline wrapping.
   A crop that outgrows --ch deepens the overlap on the join under it by exactly
   what it grew, so that height has to be given rather than taken. */
@container (max-width: 272px) {
  .fn__stage { --ch: 130px; }
  .fn__form { flex-direction: column; align-items: stretch; }
  .fn__cta { justify-content: center; }
}

/* ============ the onboarding calibration card (.oc) ============ */
/* A live card in Marketing Step 03 (Onboarding copy) replacing the placeholder image.
   Shows the biological sex choice user has to make before starting Kynso,
   the rationale explaining why fitness algorithms fail when defaulting to a static
   24-hour male baseline, and the downstream math modules that dynamically calibrate.
   Floored at --art-h-drawn, fluid across viewport widths, zero layout shift. */
.oc {
  display: flex; flex-direction: column;
  justify-content: space-between;
  min-height: var(--art-h-drawn);
  padding: clamp(16px, 2.2vw, 24px) clamp(16px, 2.2vw, 24px);
  border: 1px solid var(--rule-link);
  border-radius: 6px;
  background: var(--paper);
  box-sizing: border-box;
  gap: clamp(10px, 1.4vw, 16px);
  /* The panel is a fixed --art-w box (276-408px), so what the copy has to fit is
     the card's own width, not the viewport's -- at a 1000px window the panel is
     288px wide while every viewport-keyed clamp above is still near its maximum. */
  container-type: inline-size;
}
[data-theme="dark"] .oc { background: #181B1F; }

/* ---- Meta Bar ---- */
.oc__header {
  display: flex; flex-direction: column;
  gap: clamp(4px, 0.6vw, 8px);
}
.oc__meta {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px;
}
.oc__label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px; font-weight: 400;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--tag);
}
.oc__tag {
  display: inline-flex; align-items: center;
  padding: 2px 7px;
  border-radius: 3px;
  border: 0.8px solid var(--rule-link);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; font-weight: 400;
  letter-spacing: .14em; text-transform: uppercase;
  line-height: 1.2;
  color: var(--tag);
}
.oc__title {
  margin: 0;
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 800;
  font-size: clamp(1.1rem, 1.4vw, 1.32rem);
  line-height: 1.15;
  letter-spacing: -.02em;
  color: var(--ink);
}

/* ---- Biological Sex Choice Selector ---- */
.oc__selector {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(8px, 1vw, 12px);
}
.oc__choice {
  display: flex; align-items: flex-start;
  gap: 10px;
  padding: clamp(10px, 1.2vw, 12px) clamp(10px, 1.2vw, 14px);
  border: 1px solid var(--rule-link);
  border-radius: 5px;
  background: var(--paper);
  text-align: left;
  cursor: pointer;
  box-sizing: border-box;
  transition: border-color .18s ease, background-color .18s ease, box-shadow .18s ease;
  font: inherit;
  color: inherit;
}
[data-theme="dark"] .oc__choice {
  background: rgba(255, 255, 255, 0.03);
}
.oc__choice:hover {
  border-color: var(--ghost);
}
.oc__choice:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}
/* Rest -> hover -> selected is one ladder of the same greyscale step:
   --rule-link, --ghost, --woke. It stops at the --woke ceiling rather than
   reaching --ink, which rule 2 reserves for links and headings. */
.oc__choice.is-selected {
  border-color: var(--woke);
  background: rgba(17, 19, 22, 0.04);
}
[data-theme="dark"] .oc__choice.is-selected {
  background: rgba(255, 255, 255, 0.08);
}
.oc__radio {
  width: 14px; height: 14px;
  border-radius: 50%;
  border: 1.5px solid var(--rule-link);
  background: transparent;
  flex-shrink: 0;
  margin-top: 2px;
  position: relative;
  transition: border-color .18s ease;
}
.oc__choice.is-selected .oc__radio {
  border-color: var(--woke);
}
.oc__choice.is-selected .oc__radio::after {
  content: "";
  position: absolute;
  top: 50%; left: 50%;
  transform: translate(-50%, -50%);
  width: 6px; height: 6px;
  border-radius: 50%;
  background: var(--woke);
}
.oc__choice-info {
  display: flex; flex-direction: column;
  gap: 2px;
}
.oc__choice-name {
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 600;
  font-size: clamp(13px, 1.2vw, 14.5px);
  line-height: 1.2;
  color: var(--woke);
}
.oc__choice-sub {
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 300;
  font-size: clamp(11px, 1vw, 12px);
  line-height: 1.35;
  color: var(--ghost);
}

/* ---- Body Copy ---- */
.oc__body {
  margin: 0;
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 300;
  font-size: clamp(12.5px, 1.15vw, 13.5px);
  line-height: 1.52;
  color: var(--ghost);
  letter-spacing: -.01em;
}

/* ---- Downstream Connected Math Modules ---- */
.oc__downstream {
  margin-top: auto;
  padding-top: clamp(10px, 1.2vw, 14px);
  border-top: 1px solid var(--rule-deco);
}
.oc__downstream-header {
  display: flex; align-items: center;
  gap: 7px;
  margin-bottom: clamp(8px, 1vw, 10px);
}
.oc__downstream-dot {
  width: 5px; height: 5px;
  border-radius: 50%;
  background: var(--rule-link);
}
.oc__downstream-title {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; font-weight: 400;
  letter-spacing: .15em; text-transform: uppercase;
  color: var(--tag);
}
/* Two fixed columns rather than a wrap: left to wrap, the four modules break
   one-per-row in a narrow panel and the "four connected modules" read -- the
   whole point of the block -- collapses into a ragged list. */
.oc__pills {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px 8px;
}
.oc__pill {
  display: inline-flex; align-items: center; justify-content: center;
  text-align: center;
  padding: clamp(3px, 0.5vw, 5px) clamp(7px, 1vw, 10px);
  border: 1px solid var(--rule-link);
  border-radius: 999px;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: clamp(10px, 0.95vw, 11px);
  font-weight: 400;
  letter-spacing: .04em;
  color: var(--woke);
  background: rgba(17, 19, 22, 0.03);
  line-height: 1.25;
}
[data-theme="dark"] .oc__pill {
  background: rgba(255, 255, 255, 0.04);
}

/* Narrow panel: stack the two choices and tighten the rhythm. Two columns hold
   while the card's content box is ~358px (the panel at its widest) and the
   sub-label breaks over two lines; below that each column falls under ~150px,
   the label breaks into three ragged lines, and stacking costs less height than
   the wrap does. Keyed to the card, not the window: the panel is 288px wide at a
   1000px viewport and 304px at a 375px one, so the window says nothing useful. */
@container (max-width: 330px) {
  .oc { padding: 14px 16px; gap: 9px; }
  .oc__selector { grid-template-columns: 1fr; gap: 7px; }
  .oc__choice { padding: 9px 11px; }
  .oc__body { font-size: 12.5px; line-height: 1.45; }
  .oc__downstream { padding-top: 10px; }
  .oc__downstream-header { margin-bottom: 7px; }
  /* Hold each module on one line: a two-line pill in a one-line pill's grid row
     reads as a wrapped label rather than as one of four equal modules. */
  .oc__pill { padding: 4px 5px; letter-spacing: .01em; }
  .oc__pills { gap: 5px 6px; }
}

/* ============ the beta qualification scorecard (.qc) ============ */
/* A scorecard in Marketing Step 04 (Beta outreach) showing how beta applicants
   were qualified (Discipline mix, Active stack, Friction point).

   The three criteria are rule-separated rows rather than bordered boxes. A box
   inside the card frame repeats the frame's own stroke at the same weight and
   colour, so the container reads at the same level as its contents and the three
   criteria arrive unranked; hierarchy is carried instead by the mono numeral,
   the mono label and the sans reading line, which is how the chart beside it
   ranks its own tracks (DESIGN .gt).

   The rows take the slack (flex: 1) and a closing line holds the bottom edge, so
   the card is composed to --art-h-drawn rather than running out a third of the
   way down it. Floored at that height, not fixed to it: live copy reflows where
   a drawing does not, so the vertical rhythm tightens with the container. */
.qc {
  display: flex; flex-direction: column;
  min-height: var(--art-h-drawn);
  padding: clamp(16px, 2.2vw, 24px);
  border: 1px solid var(--rule-link);
  border-radius: 6px;
  background: var(--paper);
  box-sizing: border-box;
  gap: clamp(12px, 1.6vw, 18px);
  /* The panel is a fixed --art-w box, so the card's own width is what the copy
     has to fit -- not the viewport, which can be wide while the panel is narrow. */
  container-type: inline-size;
}
[data-theme="dark"] .qc { background: #181B1F; }

/* ---- Meta bar ---- */
.qc__header {
  display: flex; flex-direction: column;
  gap: clamp(4px, 0.6vw, 8px);
}
.qc__meta {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 10px;
}
.qc__label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px; font-weight: 400;
  letter-spacing: .16em; text-transform: uppercase;
  color: var(--tag);
}
.qc__tag {
  display: inline-flex; align-items: center;
  padding: 2px 7px;
  border-radius: 3px;
  border: 0.8px solid var(--rule-link);
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; font-weight: 400;
  letter-spacing: .14em; text-transform: uppercase;
  line-height: 1.2;
  color: var(--tag);
  white-space: nowrap;
}
.qc__title {
  margin: 0;
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 800;
  font-size: clamp(1.15rem, 1.5vw, 1.35rem);
  line-height: 1.15;
  letter-spacing: -.02em;
  color: var(--ink);
  text-wrap: balance;
}

/* ---- Criteria ---- */
.qc__list {
  flex: 1 1 auto;
  display: flex; flex-direction: column;
  margin: 0; padding: 0;
  list-style: none;
}
.qc__item {
  /* Numeral in its own column so the label and the reading line share one left
     edge; the rows share the slack the card has left over. */
  flex: 1 1 auto;
  display: grid;
  grid-template-columns: auto 1fr;
  align-content: center;
  column-gap: clamp(9px, 1.1vw, 12px);
  row-gap: 3px;
  padding: clamp(9px, 1.2vw, 13px) 0;
}
.qc__item + .qc__item {
  border-top: 1px solid var(--rule-deco);
}
.qc__item-num {
  grid-row: 1 / span 2;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px; font-weight: 400;
  letter-spacing: .08em;
  color: var(--tag);
  line-height: 1.45;
}
.qc__item-label {
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; font-weight: 400;
  letter-spacing: .15em; text-transform: uppercase;
  color: var(--tag);
  line-height: 1.5;
}
.qc__item-value {
  /* A criterion is not a link or a heading, so it stops at the --woke ceiling
     rule 2 allows a non-navigational element rather than reaching --ink. */
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 400;
  font-size: clamp(12.5px, 1.15vw, 13.5px);
  line-height: 1.42;
  color: var(--woke);
  letter-spacing: -.01em;
}
.qc__item-detail {
  display: block;
  font-weight: 300;
  color: var(--ghost);
}

/* ---- Closing line ---- */
.qc__foot {
  margin: 0;
  padding-top: clamp(10px, 1.2vw, 14px);
  border-top: 1px solid var(--rule-link);
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 300;
  font-size: clamp(12px, 1.1vw, 13px);
  line-height: 1.45;
  color: var(--ghost);
  letter-spacing: -.01em;
}

/* Narrow panel: the fluid rhythm above is keyed to the viewport, which can be
   wide while the --art-w box is not. Tighten against the card's own width. */
@container (max-width: 300px) {
  .qc { padding: 15px 16px; gap: 11px; }
  .qc__item { padding: 8px 0; }
  .qc__foot { padding-top: 10px; }
}

/* ---- the loupe ---- */
/* The panel is a fixed box that a portrait export lands inside at a fraction of
   its resolution. On a pointer device the reader gets the detail in place
   instead of being sent to a full-canvas reader and back: hovering the picture
   opens a magnified window beside it that tracks the cursor, so the gesture that
   asks the question and the surface that answers it are the same movement.

   It opens over the chart because there is nowhere else -- the panel sits in the
   last column before the rail. That is the right thing to cover: the reader is
   pointing at the picture, not at the rows, and it clears the moment they leave.

   Sits on the hint tier (DESIGN 6) so it clears the chart but stays under the
   rail, and is pointer-events:none so it can never eat the movement driving it. */
.loupe {
  position: fixed; z-index: 4;
  pointer-events: none;
  /* A pre-script fallback only. The real box is written back per figure: the
     smaller of the gap the panel leaves beside it and the magnified picture
     itself, so it never pads a small picture out with dead ground and never
     spills past the space there is. No viewport unit can express that -- it
     depends on where the panel landed and on the export's own proportions. */
  width: clamp(320px, 40vw, 900px); height: min(68vh, 560px);
  /* A rule rather than a shadow: the canvas is flat, and rules are what separate
     one plane from another (DESIGN 5). It is the same hairline the rail uses to
     hold a dark panel off a dark page. */
  border: 1px solid var(--rule-link); border-radius: 2px;
  background-color: var(--paper);
  background-repeat: no-repeat;
  opacity: 0; transform: translateX(8px);
  transition: opacity .26s ease, transform .26s cubic-bezier(.2, .7, .3, 1);
}
/* The ground follows the figure it is magnifying, for the reason rule 6 gives:
   the sheet belongs to the artwork, so the window onto it cannot use a different
   one than the panel does. */
.loupe--dark { background-color: var(--art-sheet-dark); }
.loupe--cut { background-color: transparent; border-color: transparent; }
.loupe.is-open { opacity: 1; transform: none; }

/* The picture answers a hover and commits nothing on click, so it takes the same
   cursor the track boxes take for the same reason (DESIGN 4). */
@media (hover: hover) and (pointer: fine) and (min-width: 961px) {
  .gt__shot-img { cursor: help; }
}

/* Tracking must not be eased -- an interpolated background-position lags the
   cursor and the window stops reading as attached to it. Only the reveal is. */
@media (prefers-reduced-motion: reduce) {
  .loupe { transition-duration: .001s; }
}


/* ============ 10. Contact Section (.ct) ============ */
/* The intro's floating canvas, reused as a set of routes: the five channels
   drift around a centred core the same way the ideas do on Home. Every item is
   a real destination, so each carries the intro's link treatment rather than
   its caption treatment — marker stroke across the label, and the handle
   revealed beneath it on hover. The drift keyframes are the shared motion
   primitive declared with the intro section (iaDriftA/B/C), so the cadence and
   the ±13px envelope stay the system's rather than this section's (DESIGN §5). */
.ct {
  min-height: 100vh; display: grid; position: relative; overflow: hidden;
  background: var(--paper); color: var(--ink);
  font-family: "IBM Plex Sans", system-ui, sans-serif;
  align-content: center;
  padding: clamp(56px, 9vh, 110px) clamp(18px, 3vw, 56px);
  padding-right: clamp(92px, 7vw, 124px);   /* clearance for fixed rail */
  transition: background-color .3s ease, color .3s ease;
}
.ct__hint {
  position: absolute; top: clamp(26px, 5vh, 52px); left: 50%; transform: translateX(-50%); z-index: 4;
  margin: 0;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; letter-spacing: .2em; text-transform: uppercase; color: var(--tag);
}
.ct__stage {
  position: relative; z-index: 2;
  width: 100%; max-width: 1360px; margin: 0 auto;
  min-height: clamp(460px, 68vh, 660px);
  display: grid; place-items: center;
}
.ct__core {
  position: relative; z-index: 3; text-align: center; max-width: min(700px, 100%);
}
/* Diffuses any channel that drifts under the display type. The wash follows the
   ground rather than staying white, or it would burn a spotlight into dark mode
   (DESIGN §9.3). */
.ct__core::before {
  content: ""; position: absolute; inset: -34% -16%; z-index: -1; pointer-events: none;
  background: radial-gradient(closest-side, var(--paper) 54%, rgba(248,247,255,0) 100%);
}
[data-theme="dark"] .ct__core::before {
  background: radial-gradient(closest-side, #111316 54%, rgba(17,19,22,0) 100%);
}
.ct__lead {
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif; font-weight: 800;
  font-size: clamp(2.15rem, 4.9vw, 3.5rem); line-height: 1.02; letter-spacing: -.03em;
  margin: 0; color: var(--ink); text-wrap: balance;
}
.ct__sub {
  margin: clamp(10px, 1.6vw, 16px) 0 0;
  font-size: clamp(.95rem, 1.3vw, 1.06rem); font-weight: 300; line-height: 1.5;
  color: var(--ghost); text-wrap: balance;
}

.ct__slot { position: absolute; left: var(--x); top: var(--y); transform: translate(-50%, -50%); z-index: 2; }
.ct__slot:hover, .ct__slot:focus-within { z-index: 4; }
.ct__drift {
  animation: iaDriftA var(--dur, 15s) var(--delay, 0s) ease-in-out infinite alternate;
  will-change: transform;
}
.ct__drift--b { animation-name: iaDriftB; }
.ct__drift--c { animation-name: iaDriftC; }
.ct__drift:hover, .ct__drift:has(:focus-visible) { animation-play-state: paused; }

.ct__channel {
  position: relative; display: block; text-decoration: none; white-space: nowrap; cursor: pointer;
  color: var(--ghost-link); font-weight: 400;
  font-size: calc(var(--s, 1) * clamp(.95rem, 1.5vw, 1.2rem)); line-height: 1.2;
  transition: color .3s, transform .3s;
}
.ct__channel:hover, .ct__channel:focus-visible { color: var(--ink); transform: scale(1.05); }

.ct__label {
  border-bottom: 1px solid var(--rule-link); padding-bottom: 3px;
  -webkit-box-decoration-break: clone; box-decoration-break: clone;
  background-image: var(--marker); background-repeat: no-repeat;
  background-position: right center; background-size: 0% 86%;
  transition: border-color .3s, background-size .3s cubic-bezier(.2,.7,.3,1);
}
.ct__channel:hover .ct__label,
.ct__channel:focus-visible .ct__label {
  background-size: 100% 86%;
  border-bottom-color: var(--marker-ink, #111316);
  color: var(--marker-ink, #111316);
}
/* The handle is the destination rather than decoration, so it reveals beneath
   the label on an absolute box — the composition never reflows to make room
   for it (DESIGN §4). Lowercase in the markup and uppercased here, so a reader
   copying the address out of the page gets the address, not the styling. */
.ct__tag {
  position: absolute; top: 100%; left: 50%; margin-top: 8px;
  font-family: "IBM Plex Mono", ui-monospace, monospace; font-style: normal;
  font-size: 12px; letter-spacing: .2em; text-transform: uppercase; white-space: nowrap;
  padding-left: .2em; color: var(--tag); opacity: 0; transform: translate(-50%, -5px);
  transition: opacity .3s, transform .3s;
}
.ct__channel:hover .ct__tag,
.ct__channel:focus-visible .ct__tag {
  opacity: 1; transform: translate(-50%, 0); color: var(--ink);
}

/* ============ 11. Back To Top (.totop) ============ */
/* The wide canvas keeps navigation permanently in the reader's path on the right
   edge. Stacked, that rail sits at the very top of a column several screens long,
   so the return trip needs its own control within thumb reach. It therefore has no
   wide-canvas counterpart and is display:none above the breakpoint entirely. */
.totop { display: none; }

/* ============ 11b. Privacy Link (.legal) ============ */
/* The one route every page carries that is not navigation: it answers a question
   about the site rather than about the work, so it stays out of the rail instead
   of becoming a fifth rail link. Above the breakpoint it is fixed to the
   bottom-left corner at the theme toggle's own inset, so the two frameless
   controls bracket the canvas's left edge and neither enters the composition
   between them -- every section's vertical padding already clears that band.
   It sits on the rail's tier (z-index 5): it is page chrome, not content. */
.legal {
  position: fixed; z-index: 5;
  left: clamp(20px, 3vw, 40px);
  /* The link's 14px of vertical padding is hit area rather than spacing, so it
     is taken back here: the glyphs sit at the toggle's inset from the edge. */
  bottom: calc(clamp(20px, 3vh, 32px) - 14px);
}
.legal__link {
  display: inline-block;
  /* 11px x 1.5 line + 2 x 14px = 44.5px, the touch minimum with no media query. */
  padding: 14px 0;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px; font-weight: 400; letter-spacing: .18em; text-transform: uppercase;
  line-height: 1.5; white-space: nowrap;
  /* Resting affordance (DESIGN §1.3): the interactive grey and an underline,
     not the --tag of the metadata it otherwise resembles. */
  color: var(--ghost-link);
  text-decoration: underline;
  text-decoration-color: var(--rule-link);
  text-underline-offset: 3px;
  cursor: pointer;
  /* Sized in em rather than as a share of the box: the box is mostly padding,
     and a percentage would strike the whole tap target instead of the line. */
  background-image: var(--marker); background-repeat: no-repeat;
  background-position: right center; background-size: 0% 1.5em;
  transition: background-size .34s cubic-bezier(.2,.7,.3,1),
              color .14s ease,
              text-decoration-color .14s ease;
}
.legal__link:hover,
.legal__link:focus-visible {
  background-size: 100% 1.5em;
  text-decoration-color: transparent;
  color: var(--marker-ink, #111316);
  transition: background-size .34s cubic-bezier(.2,.7,.3,1),
              color .1s ease .18s,
              text-decoration-color .1s ease .18s;
}

/* ============ 11c. Privacy Policy (.pp) ============ */
/* A reading page, so it borrows the 404's plain-document footing rather than a
   composed canvas. Two columns on the wide canvas: the title and the plain
   summary on the left, the numbered sections on the right. The split is what
   keeps the fixed privacy link clear: the left column is short, so once the
   page scrolls there is nothing left in it to pass beneath the corner, and the
   long column that does scroll starts well to the right of it. */
.pp {
  position: relative; z-index: 2;
  min-height: 100vh;
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  align-items: start; align-content: center;
  column-gap: clamp(40px, 6vw, 112px);
  padding: clamp(72px, 12vh, 128px) clamp(28px, 7vw, 96px);
  /* Reserve the rail's own width on the trailing edge (DESIGN §Layout Tokens). */
  padding-right: calc(var(--rail-w) + clamp(28px, 7vw, 96px));
  font-family: "IBM Plex Sans", system-ui, sans-serif;
}
.pp__title {
  margin: 0;
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 800;
  font-size: clamp(2.1rem, 4.4vw, 3.6rem);
  line-height: 1.04; letter-spacing: -.03em;
  text-wrap: balance;
  color: var(--ink);
}
.pp__meta {
  margin: clamp(14px, 2.4vh, 22px) 0 0;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12px; font-weight: 400; letter-spacing: .18em; text-transform: uppercase;
  color: var(--tag);
}
.pp__lede {
  margin: clamp(26px, 4vh, 40px) 0 0; max-width: 40ch;
  font-weight: 300;
  font-size: clamp(1.08rem, 1.5vw, 1.25rem); line-height: 1.55;
  color: var(--ghost);
  text-wrap: pretty;
}

/* The sections take the case study's symptom rows: a mono numeral in a key
   column, the heading and prose beside it, and --rule-deco hairlines between.
   A section is read, not clicked, so no row carries a hover. */
.pp__sections {
  list-style: none; margin: 0; padding: 0;
  border-top: 0.5px solid var(--rule-deco);
}
.pp__section {
  display: grid;
  grid-template-columns: minmax(0, clamp(40px, 4vw, 64px)) minmax(0, 1fr);
  gap: clamp(12px, 1.6vw, 22px);
  padding: clamp(22px, 3.4vh, 34px) 0;
  border-bottom: 0.5px solid var(--rule-deco);
}
.pp__num {
  /* Dropped to the heading's cap height so the numeral reads as its key. */
  padding-top: .5em;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 12px; font-weight: 400; letter-spacing: .18em;
  font-variant-numeric: tabular-nums;
  color: var(--tag);
}
.pp__heading {
  margin: 0;
  font-family: "Bricolage Grotesque", "IBM Plex Sans", system-ui, sans-serif;
  font-weight: 800;
  font-size: clamp(1.2rem, 1.7vw, 1.45rem);
  line-height: 1.08; letter-spacing: -.02em;
  text-wrap: balance;
  color: var(--ink);
}
.pp__prose {
  margin: clamp(12px, 1.8vh, 16px) 0 0; max-width: 62ch;
  font-weight: 300;
  font-size: clamp(1rem, 1.2vw, 1.06rem); line-height: 1.65;
  color: var(--ghost);
  text-wrap: pretty;
}

/* ============ 12. Figure Zoom (.zoom) ============ */
/* The trigger wraps artwork in place. display:contents dissolves its box, so the
   wide layout measures exactly what it did before the wrapper existed; the button
   is disabled there as well, so it adds no tab stop and swallows no clicks. */
.zoom__trigger {
  display: contents;
  padding: 0; border: 0; background: none; font: inherit; color: inherit;
}
.zoom { display: none; }

@media (max-width: 960px) {

  /* One page gutter for every section and for the horizontal rail, so the bar
     shares a left edge with the copy beneath it on Home, About and Case Study
     alike. Previously each section set its own (18 / 20 / 22.5px) against a
     rail margin of 16px, so the menu sat at a different inset on every page. */
  :root { --gutter: clamp(18px, 5vw, 40px); }

  /* Rail layout */
  .rail,
  .ia__rail {
    /* Spans the full gutter width, so the bar shares both edges with the copy
       beneath it rather than sitting as a shrink-wrapped block on the left. */
    position: static; width: auto;
    max-width: 100%;
    display: flex; flex-direction: row; flex-wrap: wrap;
    align-items: center;
    gap: clamp(10px, 2.5vw, 16px);
    /* Top clearance tracks the theme toggle's own offset plus its 44px touch
       target and a gap, so the toggle's tap area never lands on the bar — a
       flat 8.5vh let them collide at 667px-tall viewports. */
    margin: calc(clamp(20px, 3vh, 32px) + 52px) var(--gutter) clamp(20px, 3vh, 32px);
    padding: clamp(9px, 1.5vh, 13px) clamp(12px, 3vw, 18px);
  }
  [data-theme="dark"] .rail,
  [data-theme="dark"] .ia__rail {
    border-left: none;
    border: 1px solid var(--rule-link, #5F6368);
  }
  .rail__link,
  .ia__rail-link {
    writing-mode: horizontal-tb;
    font-size: clamp(11px, 2.8vw, 13px);
  }
  /* Takes the whole bar and spreads the routes across it, so the run of links
     reads edge to edge instead of clustering in the middle. */
  .rail__main,
  .ia__rail-main {
    flex: 1;
    flex-direction: row; flex-wrap: wrap; justify-content: space-between;
    gap: clamp(10px, 2.5vw, 16px);
  }
  /* Mono tracking reserves trailing space after the final glyph; letting these
     shrink would clip it, so they hold their intrinsic width and wrap instead. */
  .rail__link, .rail__rule,
  .ia__rail-link, .ia__rail-rule { flex-shrink: 0; }
  /* The CV download is dropped on mobile at the owner's request. Note this
     leaves no route to the CV below 960px — the desktop rail is its only home. */
  .rail__cv,
  .ia__rail-cv { display: none; }
  .rail__link::before, .rail__cv::before,
  .ia__rail-link::before, .ia__rail-cv::before {
    background: var(--marker);
    inset: -4px -6px;
    transform-origin: right;
    transform: rotate(-.45deg) skewX(-2.5deg) scaleX(0);
  }
  .rail__link:hover::before, .rail__link:focus-visible::before,
  .rail__cv:hover::before, .rail__cv:focus-visible::before,
  .ia__rail-link:hover::before, .ia__rail-link:focus-visible::before,
  .ia__rail-cv:hover::before, .ia__rail-cv:focus-visible::before {
    transform: rotate(-.45deg) skewX(-2.5deg) scaleX(1);
  }
  .rail__rule,
  .ia__rail-rule { width: clamp(10px, 2vw, 18px); height: 1px; }
  /* The intro's rail is nested inside .ia, so the section padding already
     supplies its inset; its own side margin on top of that would inset it a
     second time and break alignment with the body-level .rail. */
  .ia__rail { margin-inline: 0; }

  /* Intro Section */
  /* min-width:0 because the names below hold one line: a grid item's automatic
     minimum size is its min-content width, and an unbreakable name makes that
     the width of the name itself. Without this the stage widens the page past
     the screen rather than letting a name overrun its own row. */
  .ia__stage { min-height: 0; min-width: 0; display: block; justify-items: start; }
  .ia__core { max-width: none; text-align: left; margin: 0 0 clamp(30px, 6vh, 46px); }
  .ia__core::before { display: none; }
  .ia__hint { display: none; }
  .ia {
    padding-inline: var(--gutter);
    /* The rail's own top margin supplies the toggle clearance here, so the
       section's centring padding would only stack a second empty band. */
    padding-top: 0;
    /* The ideas become a document-flow list that outgrows the 100vh minimum;
       centring it then bleeds the rail off the top edge, where overflow:hidden
       would clip it. Anchor the flow to the top instead. */
    align-content: start;
  }
  /* nowrap exists to keep the spark reveals from breaking mid-trigger; the
     sparks are suppressed below, so the display lead may wrap freely instead
     of overflowing a 375px canvas. */
  .ia__nb { white-space: normal; }
  .ia__lead-line { display: inline; white-space: normal; }
  .ia__word {
    white-space: normal;
    -webkit-box-decoration-break: clone; box-decoration-break: clone;
  }
  .ia__lead { font-size: clamp(1.75rem, 7.4vw, 2.6rem); line-height: 1.06; }
  /* justify-items:start on the stage aligns these rows left, and in block
     layout that also shrink-wraps each one to its own text. A name then has no
     column to overrun, so nothing could ever be measured as too long for the
     screen. Stretching the row back to full width restores the track without
     moving a single glyph -- every row is left-aligned either way. */
  .ia__slot { position: static; transform: none; margin: 0 0 26px; justify-self: stretch; }
  .ia__drift { animation: none; }
  .ia__idea {
    /* One size for every name. The per-instance --s scale exists to compose the
       wide canvas's scatter, where a name's size is read against its neighbours'
       and its distance from the centre; stacked into a single-file list with all
       of that gone, the same variation reads as inconsistent type rather than as
       hierarchy. */
    font-size: 1.06rem;
    /* A name too long for the column travels instead of wrapping: a two-line
       name would sit taller than its neighbours and break the even rhythm of
       the list, and the marker stroke would have to be struck row by row on a
       row that is never hovered. .ia__idea is the track the name travels in. */
    white-space: nowrap; overflow: hidden;
  }
  /* Inline elements ignore transforms, and the label is what moves. nowrap has
     already ruled out the multi-line highlight this would otherwise cost. */
  .ia__label { display: inline-block; }
  /* Set only on names measured to overrun the column, and paced by the distance
     each one has to cross, so every name travels at the same speed rather than
     in the same time. The drift these replace is disabled at this width, so no
     two ambient animations run on one element. */
  .ia__idea.is-marquee .ia__label {
    animation: iaMarquee var(--marq-dur, 12s) ease-in-out infinite alternate;
    will-change: transform;
  }
  .ia__tag { position: static; display: block; margin-top: 6px; opacity: 1; transform: none; }
  .ia__spark { display: none; }
  .ia__real { left: 0; transform: translate(0, -5px); }
  .ia__who:hover .ia__real, .ia__who:focus .ia__real { transform: translate(0, 0); }

  /* Case Study Section */
  .cs {
    height: auto; display: block; overflow: visible;
    overscroll-behavior-x: auto;
  }
  .cs > :last-child { margin-right: 0; }
  .cs__hook, .cs__block, .cs__flow, .cs__next, .gt {
    width: auto; height: auto; display: block;
    padding: clamp(62px, 10vh, 104px) var(--gutter);
  }
  .cs__scroll { display: none; }
  .cs__block, .cs__flow, .cs__next, .gt { border-left: 0; border-top: 0.5px solid var(--rule-deco); }
  .cs__symptom { grid-template-columns: minmax(0, 1fr); gap: 6px; }
  .cs__symptom-key { padding-top: 0; }
  .cs__metrics { grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); gap: 24px; }
  .cs__flow-header { margin-bottom: clamp(20px, 3vh, 32px); }
  .cs__flow-hint, .cs__flow-rule { display: none; }
  .cs__steps { display: block; padding: 0; position: relative; }
  .cs__steps::before {
    top: .5em; bottom: .5em; left: 0; right: auto; width: 1px; height: auto;
  }
  .cs__step {
    width: auto; padding: 0 0 clamp(52px, 7.5vh, 84px) clamp(22px, 5vw, 34px);
  }
  .cs__step:last-child { padding-bottom: 0; }
  .cs__step::before { top: .55em; margin-top: 0; margin-left: -3px; }
  .cs__shot,
  .cs__shot--span-3 {
    position: static; width: 100%; max-width: 520px; height: auto;
    margin: clamp(16px, 2.4vh, 22px) 0 0;
    opacity: 1; transform: none;
  }
  .cs__shot-img { height: auto; }
  /* The tall shots' fixed height is a wide-canvas measure -- stacked, they are
     full-width blocks and must take their height from their own ratio again. */
  .cs__shot--tall .cs__shot-img { height: auto; }

  /* Gantt Track Chart */
  /* Each box carries its own name, and the narrowest of them is three of twelve
     columns — about 75px on a 375px canvas, too narrow to hold a word. So the
     proportional lane is what gives way here: boxes take the full width and the
     chart reads as an ordered sequence, which is the part of it a phone can
     actually show. The ground rules go with it — stacked, they would cross the
     artwork sitting beneath the chart. */
  .gt { --row-gap: clamp(9px, 1.6vh, 15px); }
  .gt__frame { display: block; }
  .gt__chart { background-image: none; }
  .gt__bar, .gt__bracket { grid-column: 1 / -1; margin-right: 0; }
  .gt__bar--aside .gt__name { font-size: clamp(.95rem, 1.15vw, 1.05rem); }
  /* The art layer drops below the chart rather than beside it. It still shows one
     panel at a time — a tap on a row swaps it, and the first phase holds it from
     the start, so there is artwork at rest where there is no pointer to hover. */
  .gt__art { margin-top: clamp(20px, 3.4vh, 30px); max-width: min(100%, 624px); }
  .gt__shot-img { height: auto; }

  /* About Me Section */
  .am-viewport {
    height: auto;
    overflow-y: visible;
    scroll-snap-type: none;
    scroll-behavior: auto;
    position: relative;
    -webkit-overflow-scrolling: touch;
  }
  .am__screen {
    height: auto;
    min-height: 0;
    scroll-snap-align: none;
    scroll-snap-stop: normal;
    display: flex;
    flex-direction: column;
    padding: clamp(40px, 6vh, 64px) var(--gutter);
    gap: clamp(24px, 3.5vh, 36px);
  }
  .am__sculpture {
    order: -1;
    width: 100%;
  }
  .am__sculpture-box {
    max-width: 280px;
  }
  .am__scroll-hint {
    display: none;
  }
  /* The thread's path is authored to weave between the three desktop columns.
     Squashed to one column it collapses into a ~100px squiggle laid straight
     over the body copy, so it is dropped rather than downscaled. */
  .am__continuous-thread { display: none; }

  /* Not Found Section */
  .nf {
    min-height: 0;
    padding-inline: var(--gutter);
    /* The rail lies down into a bar at the top of the document here, so the
       trailing clearance the wide canvas reserved is no longer needed. */
    padding-right: var(--gutter);
    /* The rail's own top margin already supplies the theme-toggle clearance. */
    padding-top: 0;
    align-content: start;
    gap: clamp(30px, 5vh, 44px);
  }
  /* The die stops sitting beside its fact: in a phone-width column the fact
     would be squeezed too narrow to read, so it drops beneath the die. */
  .nf__roller { grid-template-columns: minmax(0, 1fr); gap: clamp(20px, 4vh, 30px); }
  .nf__die { justify-items: start; }
  /* The same fact wraps to more rows in a narrow column, so the reserved box
     grows with it — the stage must still never reflow when a fact swaps. A
     132-character fact measures 72px at 375px wide, so this clears the
     documented ceiling with room to spare without stranding empty rule. */
  .nf__stage { min-height: clamp(88px, 13vh, 112px); }

  /* Contact Section */
  /* Same reflow as the intro: the spatial scatter becomes a document-flow list
     and the drift stops, because neither survives a 375px column. */
  .ct {
    min-height: 0;
    padding-inline: var(--gutter);
    /* The rail lies down into a bar at the top of the document here, so the
       trailing clearance the wide canvas reserved is no longer needed. */
    padding-right: var(--gutter);
    /* The rail's own top margin already supplies the theme-toggle clearance. */
    padding-top: 0;
    align-content: start;
  }
  .ct__stage { min-height: 0; display: block; justify-items: start; }
  .ct__core { max-width: none; text-align: left; margin: 0 0 clamp(30px, 6vh, 46px); }
  .ct__core::before { display: none; }
  .ct__hint { display: none; }
  .ct__lead { font-size: clamp(1.75rem, 7.4vw, 2.6rem); line-height: 1.06; }
  .ct__slot { position: static; transform: none; margin: 0 0 26px; }
  .ct__drift { animation: none; }
  .ct__channel { white-space: normal; font-size: calc(var(--s, 1) * 1.06rem); }
  /* The handle is no longer a hover reveal here — it is the row's second line,
     so it takes its place in flow. The longest is a 29-character address, which
     at this tracking outruns a 375px column, and mono is allowed to wrap below
     the breakpoint (DESIGN §7). */
  .ct__tag {
    position: static; display: block; margin-top: 6px;
    opacity: 1; transform: none;
    white-space: normal; overflow-wrap: anywhere;
  }

  /* Privacy Link */
  /* The corner is a wide-canvas position: stacked, a fixed link would float
     over the column of copy for the whole length of the page. So it lies down
     at the foot of the document, behind a hairline like every stacked section.
     The bottom padding matches .totop's own inset, so at the end of the page
     the link and the disc share one row -- link at the left, disc at the right. */
  .legal {
    position: static;
    margin: 0 var(--gutter);
    padding: 8px 0 var(--gutter);
    border-top: 0.5px solid var(--rule-deco);
  }

  /* Privacy Policy */
  .pp {
    min-height: 0;
    grid-template-columns: minmax(0, 1fr);
    align-content: start;
    padding-inline: var(--gutter);
    /* The rail lies down into a bar at the top of the document here, so the
       trailing clearance the wide canvas reserved is no longer needed. */
    padding-right: var(--gutter);
    /* The rail's own top margin already supplies the theme-toggle clearance. */
    padding-top: 0;
  }
  .pp__head { margin-bottom: clamp(30px, 5vh, 44px); }
  .pp__title { font-size: clamp(1.75rem, 7.4vw, 2.6rem); line-height: 1.06; }

  /* Back To Top */
  /* Sits at the page gutter from both edges so it lines up with the column of
     copy it floats over rather than hugging the bezel. 44px square meets the
     touch minimum without an expanded hit box. */
  .totop {
    position: fixed; z-index: 6;
    right: var(--gutter); bottom: var(--gutter);
    display: grid; place-items: center;
    width: 44px; height: 44px;
    padding: 0; border: none; border-radius: 50%;
    background: var(--ink); color: var(--paper);
    cursor: pointer;
    /* Absent until the reader is a screen deep, so it never covers the opening
       hook. visibility keeps it out of the tab order while it is invisible. */
    opacity: 0; visibility: hidden; transform: translateY(8px);
    transition: opacity .26s ease,
                transform .26s cubic-bezier(.2,.7,.3,1),
                visibility 0s linear .26s;
  }
  .totop.is-visible {
    opacity: 1; visibility: visible; transform: translateY(0);
    transition: opacity .26s ease,
                transform .26s cubic-bezier(.2,.7,.3,1),
                visibility 0s;
  }
  /* Matches the rail: the disc keeps a dark ground in both themes rather than
     inverting to white, and takes a hairline boundary to hold its edge. */
  [data-theme="dark"] .totop {
    background: #0D0E11; color: #FFFFFF;
    border: 1px solid var(--rule-link, #5F6368);
  }
  .totop:hover { transform: translateY(0) scale(1.05); }
  .totop:focus-visible { outline-color: var(--paper); }
  [data-theme="dark"] .totop:focus-visible { outline-color: #FFFFFF; }
  .totop__icon {
    width: 16px; height: 16px;
    stroke: currentColor; fill: none; stroke-width: 1.6;
    stroke-linecap: round; stroke-linejoin: round;
    pointer-events: none;
  }

  /* Figure Zoom — the parts that belong to the stacked layout alone */
  /* The trigger takes its own box here so it can carry the tap target. The
     shots it wraps are painted with pointer-events:none on the wide canvas so
     the floating art never blocks the steps beneath it; stacked, they are
     ordinary blocks in flow and have to take taps again. */
  .zoom__trigger {
    display: block; width: 100%; cursor: pointer;
    -webkit-tap-highlight-color: transparent;
  }
  .cs__shot { pointer-events: auto; }
}

/* ============ 12b. Figure Zoom — the reader itself ============ */
/* Outside the breakpoint block: the overlay is a full-canvas surface that
   the Gantt panel opens at every width (see the gate below). */

.zoom {
  /* Its own gutter: the page token is declared only below the breakpoint,
     and the reader is a full-canvas surface at every width. */
  --gutter: clamp(18px, 5vw, 40px);
  position: fixed; z-index: 6; inset: 0;
  display: grid; grid-template-rows: 1fr auto;
  background: var(--paper);
  opacity: 0; transform: translateY(8px);
  transition: opacity .26s ease, transform .26s cubic-bezier(.2,.7,.3,1);
}
.zoom[hidden] { display: none; }
/* Resolves to none rather than translateY(0): a transformed ancestor becomes
   the containing block for the absolutely positioned close control. */
.zoom.is-open { opacity: 1; transform: none; }

/* The pane pans in both axes: a diagram opens at its authored width, which is
   wider than the screen by design, and a screenshot opens taller. */
.zoom__pane {
  overflow: auto; -webkit-overflow-scrolling: touch;
  display: grid; place-items: center;
  padding: calc(var(--gutter) + 44px) var(--gutter) var(--gutter);
}
.zoom__frame > * {
  display: block; max-width: none;
  background: var(--paper); border: 1px solid var(--rule-link);
}
[data-theme="dark"] .zoom__frame > * { background: #181B1F; }
/* Diagrams are drawn on the ground itself, so a frame around them would read
   as a second, competing rule — same exemption they take in flow. */
.zoom__frame > .cs__shot-diagram { border: none; background: transparent; }
[data-theme="dark"] .zoom__frame > .cs__shot-diagram { background: transparent; }
/* Photographic exports carry their own edge; a frame around one reads as a
   second, competing line, in the reader as much as in the flow. */
.zoom.is-photo .zoom__frame > * {
  border: none;
  background: var(--art-sheet);
  box-shadow: var(--shadow-art);
}
/* Same exemption the flow grants a cut export, and for the same reason: it is
   its own edge, so the frame rule would box a transparent field around the
   picture and land nowhere near the artwork's actual silhouette. */
.zoom.is-cut .zoom__frame > * {
  background: none;
  border: none;
  box-shadow: none;
  filter: var(--shadow-cut);
}
/* A dark-ground export carries its sheet into the reader too: the ground is a
   property of the artwork, not of the surface it happens to be shown on, so it
   stays carbon here in both themes exactly as it does in the panel. The frame
   goes, as it does in the panel. */
.zoom.is-dark .zoom__frame > * {
  background: var(--art-sheet-dark);
  border: none; border-radius: 2px;
}
/* The corner is the subject's, not the surface's, so it comes into the reader
   with the picture -- larger here only because the reader opens the file at its
   authored 590px, and 4.7% of that is 28. */
.zoom.is-screen .zoom__frame > * { border-radius: 28px; }

.zoom__foot {
  display: grid; gap: 8px;
  padding: clamp(14px, 2.4vh, 20px) var(--gutter) calc(var(--gutter) + 4px);
  border-top: 0.5px solid var(--rule-deco);
}
.zoom__cap {
  margin: 0;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 11px; letter-spacing: .16em; text-transform: uppercase;
  line-height: 1.5; color: var(--tag);
}
/* Prose that travelled with a chart caption keeps the sans it was set in
   rather than being run through the label's uppercase mono. */
.zoom__note {
  margin: 0;
  font-family: "IBM Plex Sans", system-ui, sans-serif; font-weight: 300;
  font-size: .92rem; line-height: 1.5; color: var(--ghost);
}
/* Only stated when the figure actually overflows its pane, so it is a fact
   about this figure rather than a standing instruction. */
.zoom__hint {
  margin: 0;
  font-family: "IBM Plex Mono", ui-monospace, monospace;
  font-size: 10px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--tag);
}
.zoom:not(.is-pannable) .zoom__hint { display: none; }

.zoom__close {
  position: absolute; z-index: 6;
  top: var(--gutter); right: var(--gutter);
  display: grid; place-items: center;
  width: 44px; height: 44px;
  padding: 0; border: none; border-radius: 50%;
  background: var(--ink); color: var(--paper);
  cursor: pointer;
  transition: transform .2s cubic-bezier(.2,.7,.3,1);
}
[data-theme="dark"] .zoom__close {
  background: #0D0E11; color: #FFFFFF;
  border: 1px solid var(--rule-link, #5F6368);
}
.zoom__close:hover { transform: scale(1.05); }
.zoom__close:focus-visible { outline-color: var(--paper); }
[data-theme="dark"] .zoom__close:focus-visible { outline-color: #FFFFFF; }
.zoom__close-icon {
  width: 16px; height: 16px;
  stroke: currentColor; fill: none; stroke-width: 1.6;
  stroke-linecap: round; stroke-linejoin: round;
  pointer-events: none;
}

/* The zoom owns the whole canvas while it is open; the page behind it is
   frozen by .is-zoomed on <body>, and the toggle would otherwise float on
   top of the reader. */
body.is-zoomed { overflow: hidden; }
body.is-zoomed .theme-toggle,
body.is-zoomed .totop { display: none; }

/* ---- the reader above the breakpoint ---- */
/* The stacked layout gives every figure a zoom because a phone column is too
   narrow to render one legibly. The Gantt panel needs one for the opposite
   reason and at every width: it is a fixed 340-wide box that a portrait export
   lands inside at a fraction of its own resolution, and unlike a timeline shot
   it never grows to fill the canvas. So its trigger stays live above 960px,
   where the case-study shots' triggers are still dissolved and disabled.

   The panel is painted pointer-events:none so an inactive shot never blocks the
   chart; the active one has to take the click. */
.gt__shot.is-active { pointer-events: auto; }

@media (min-width: 961px) {
  .gt__shot .zoom__trigger {
    display: block; width: 100%; cursor: pointer;
  }
  .gt__shot .zoom__trigger:focus-visible {
    outline: 2px solid var(--ink); outline-offset: 3px;
  }
}

/* A drawn schematic is the one panel figure with no reader at any width: it is
   painted at the unit width it was drawn at, so there is no detail left to
   magnify. Its wrapper stays dissolved and its cursor stays default, the way a
   timeline shot's does on the wide canvas. */
.gt__shot .zoom__trigger:has(.gt__shot-diagram) {
  display: contents; cursor: default;
}

/* Touch devices */
@media (hover: none) {
  .theme-toggle { min-width: 44px; min-height: 44px; }
  .ia__idea { color: var(--ghost); }
  .ia__idea--link { color: var(--ghost-link); }
  .ia__tag, .ct__tag { opacity: 1; }
  .cs__step { cursor: default; }
  .cs__shot { opacity: 1; transform: translateY(0); }
  /* The chart's art column holds one panel at a time, so it cannot simply be
     pinned open the way the timeline's per-step shots are; a tap switches the
     active track instead, and the first phase is active from the start. */
  .gt__track--phase, .gt__track--aside { cursor: default; }
  /* No hover to confirm the die is live, so its label carries full
     metadata contrast at rest instead of waiting for a pointer. */
  .nf__die-label { color: var(--woke); }
}

/* Touch on a wide canvas keeps the tag optically centred under its idea. Below
   960px the tag is a static block in document flow, where the centring offset
   would drag it off the left edge — so it is scoped to the wide layout only. */
@media (hover: none) and (min-width: 961px) {
  .ia__tag, .ct__tag { transform: translate(-50%, 0); }
}

/* Reduced motion */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    transition-duration: .001s !important;
    animation-duration: .001s !important;
  }
  .ia__drift, .ct__drift { animation: none !important; }
  .ia__word { animation: none !important; opacity: 1 !important; transform: none !important; }
  /* The block above collapses durations but not delays, and this one gates a
     panel's tab stops -- left at .26s it would hold an off-screen control
     focusable after the fade it was waiting on had already finished. */
  .gt__shot { transition-delay: 0s !important; }
  .am__thread-base, .am__scroll-hint-arrow { animation: none !important; }
  /* A 0.001s spin is a single jarring jump frame; the roll still
     resolves, it simply lands without tumbling. */
  .nf__die[data-rolling="true"] .nf__die-svg { animation: none !important; }
}

/* The marquee is the only animation on the site that carries content rather
   than decorating it: collapsed to 0.001s by the block above, it would strand a
   long name at the far end of its travel with its opening words clipped off. So
   here the name wraps instead, which is what it did before it was given
   anywhere to travel. Placed after that block because it has to outrank it. */
@media (max-width: 960px) and (prefers-reduced-motion: reduce) {
  .ia__idea { white-space: normal; overflow: visible; }
  .ia__label { display: inline; }
  .ia__idea.is-marquee .ia__label {
    animation: none !important; transform: none;
  }
}
