/* ────────────────────────────────────────────────────────────────
   Davi Serrano — Case Study Template (shared)
   Spec: eyebrow · H1 · sticky meta sidebar · 5 beats · 3 inserts
         · outcome line · full-bleed closing hero · next link
   ──────────────────────────────────────────────────────────────── */

:root {
  /* Ink + paper — DARK is the default. Light mode tokens override below. */
  --ink:      #1c1c1c;
  --ink-2:    #161616;
  --paper:    #f3f3f3;
  --paper-2:  #ffffff;
  --muted:    #aeaeae;
  --muted-2:  #7a7a7a;
  --rule:     color-mix(in srgb, var(--paper) 10%, transparent);
  --rule-2:   color-mix(in srgb, var(--paper) 20%, transparent);

  /* Primary accent — purple */
  --purple:   #9747FF;
  --purple-2: #c294ff;
  --purple-soft: rgba(151,71,255,0.14);

  /* Feedback — error. Reserved for validation states (gate password, forms).
     Never decorative: if it's red, something needs the visitor's attention. */
  --error:    #ff6b5e;

  /* v3.1 vivid accent palette — reserved for the synth presets + sparing accents.
     Borrowed from neon-on-dark editorial work. Use one cell per bento, max. */
  --lime:     #C8E600;           /* electric lime — synth · primary spark */
  --magenta:  #FF3D87;           /* hot pink — synth · contrast */
  --orange:   #FF8C2E;           /* warm orange — synth · energy */
  --electric: #835CFC;           /* electric violet — sister to --purple, slightly bluer */

  --lime-soft:     rgba(200,230,0,0.14);
  --magenta-soft:  rgba(255,61,135,0.14);
  --orange-soft:   rgba(255,140,46,0.14);
  --electric-soft: rgba(131,92,252,0.14);

  /* Type stack — Syne for display, Inter for body, Mono for spec.
     The italic accent is set on Syne italic, no separate serif face. */
  --display: "Syne", ui-sans-serif, system-ui, sans-serif;
  --body:    "Inter", ui-sans-serif, system-ui, sans-serif;
  --mono:    "JetBrains Mono", ui-monospace, monospace;

  /* ── MOTION SYSTEM ────────────────────────────────────────────────
     One easing, one duration scale, one travel distance. Restrained and
     precise: motion clarifies state, it never performs. Every value here
     is a token so the whole site can be retuned from one place.

     --ease      the house curve. Fast out, long settle. Used for
                 everything that moves in space.
     --ease-soft symmetric curve for pure fades and colour changes,
                 where directional weight would read as fussy.  */
  --ease:       cubic-bezier(.2,.7,.2,1);
  --ease-soft:  cubic-bezier(.4,0,.4,1);

  /* Duration scale. Nothing between steps, nothing outside it. */
  --t-instant:  120ms;   /* colour, opacity on small controls */
  --t-quick:    200ms;   /* hover states, taps, badges */
  --t-base:     320ms;   /* the default: most micro-interactions */
  --t-slow:     480ms;   /* image scale, larger surfaces */
  --t-reveal:   620ms;   /* scroll reveals + hero entrance */
  --t-layout:   680ms;   /* height / expand-collapse */

  /* Sequencing. Reveals cascade at one step per element so a section
     resolves as a sequence rather than a single block. */
  --t-stagger:  70ms;
  --rise:       16px;    /* travel distance for reveals */
  --nudge:      4px;     /* travel for arrows and small indicators */

  /* Theme dissolve — the one deliberately slow beat. The whole page
     crossfades between dark and light, so it reads as a scene change,
     not a control state. */
  --t-theme:    720ms;
}

/* Reduced motion: the whole system collapses to instant, everywhere.
   Tokens are zeroed at the source so no component needs its own guard. */
@media (prefers-reduced-motion: reduce) {
  :root {
    --t-instant: 1ms; --t-quick: 1ms; --t-base: 1ms; --t-slow: 1ms;
    --t-reveal: 1ms; --t-layout: 1ms; --t-theme: 1ms; --t-stagger: 0ms;
    --rise: 0px; --nudge: 0px;
  }
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── Light theme — opt-in via [data-theme="light"] on <html> ──
   The Davi.theme localStorage key persists the user's choice (handled
   by assets/theme.js, auto-injected on every page). Vivid accents
   (lime/magenta/orange/electric) keep their hue across themes; only the
   ink/paper relationship inverts. The purple accent shifts to a slightly
   deeper tone in light mode for legibility on cream. */
[data-theme="light"] {
  /* Cool-neutral palette — pure off-white, no warm cream tint.
     Contrast checks (WCAG 2.1 AA / AAA):
       paper #1c1c1c on ink #fafafa     → 17.4 : 1   AAA ✓
       muted #5d5d5d on ink #fafafa     →  6.6 : 1   AA  ✓
       purple #7a3ce0 on ink #fafafa    →  5.0 : 1   AA  ✓ (large-text AAA)
       purple-2 #5a25c8 on ink #fafafa  →  7.4 : 1   AAA ✓
     All token combinations pass WCAG AA for normal body text. */
  --ink:      #fafafa;
  --ink-2:    #f0f0f0;
  --paper:    #1c1c1c;
  --paper-2:  #0e0e0e;
  --muted:    #5d5d5d;
  --muted-2:  #8a8a8a;
  /* Rule colors auto-flip via color-mix on --paper — no override needed. */

  --purple:   #7a3ce0;
  --purple-2: #5a25c8;
  --purple-soft: rgba(122,60,224,0.10);
  --error:    #d64533;   /* deeper red for contrast on paper */
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
html {
  background: var(--ink);
  transition: background-color var(--t-theme) var(--ease-soft);
  /* Sideways overflow is contained HERE as well as on body. iOS Safari pans
     the viewport off any element that sticks out past the right edge even
     when body alone is set to hide it, which drags every heading and column
     left and clips them at x=0. `clip` rather than `hidden` on purpose:
     `hidden` turns this into a scroll container and kills `position: sticky`
     on the nav. This is containment, not a licence to overflow: anything
     genuinely too wide should still be fixed at the source. */
  overflow-x: clip;
}
body {
  background: var(--ink); color: var(--paper);
  font-family: var(--body); font-size: 16px; line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
  /* Theme dissolve — slow crossfade so the swap reads as a soft blend
     instead of a snap. Same easing as the unicorn stage opacity on the
     homepage, so the whole site feels coordinated. */
  transition: background-color var(--t-theme) var(--ease-soft),
              color var(--t-theme) var(--ease-soft);
}
/* In light mode, invert the animated logo gif so it reads on the cream nav. */
[data-theme="light"] .logo-mark {
  filter: invert(1) hue-rotate(180deg);
}
::selection { background: var(--purple); color: var(--paper); }
a { color: inherit; text-decoration: none; }
img { display: block; max-width: 100%; }

.wrap { max-width: 1320px; margin: 0 auto; padding: 0 56px; }
.wrap-narrow { max-width: 1180px; margin: 0 auto; padding: 0 56px; }
@media (max-width: 900px) {
  .wrap, .wrap-narrow { padding: 0 24px; }
}

/* ── Top nav ─────────────────────────────────────── */
.nav {
  position: sticky; top: 0; z-index: 50;
  backdrop-filter: blur(14px);
  background: rgba(28,28,28,0.78);
  border-bottom: 1px solid var(--rule);
}
.nav-inner {
  max-width: 1320px; margin: 0 auto;
  padding: 22px 56px;
  display: flex; align-items: center; justify-content: space-between;
  font-family: var(--mono); font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.14em;
}
@media (max-width: 900px) { .nav-inner { padding: 22px 24px; } }
.logo { display: inline-flex; align-items: center; gap: 10px; }
.logo-mark {
  width: 56px; height: 32px;
  object-fit: contain;
  mix-blend-mode: lighten;
}
.nav-back {
  display: inline-flex; align-items: center; gap: 10px;
  color: var(--muted);
  transition: color var(--t-quick) var(--ease-soft);
  /* Push to the right edge of the nav, sit just left of the lang toggle */
  margin-left: auto;
}
.nav-back:hover { color: var(--paper); }
.nav-back .arrow { transition: transform var(--t-base) var(--ease); }
.nav-back:hover .arrow { transform: translateX(calc(var(--nudge) * -1)); }
/* Tighten the gap between nav-back and lang-switch so they read as a pair */
.nav-inner > .nav-back + .lang-switch,
.nav-inner > .lang-switch { margin-left: 24px; }
/* On pages without a language toggle (e.g. the design system), theme.js drops
   the picker straight after nav-back. Keep it from crowding "All Work". */
.nav-inner > .nav-back + .theme-switch,
.nav-inner > .nav-back + .nav-controls { margin-left: 24px; }

/* Language pill switcher (injected by i18n.js) */
.lang-switch {
  display: inline-flex; border: 1px solid var(--rule-2);
  border-radius: 999px; overflow: hidden;
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.06em;
  margin-left: 16px;
}
.lang-switch button {
  background: transparent; border: 0; color: var(--paper);
  padding: 6px 12px; cursor: pointer; opacity: 0.55;
  text-transform: uppercase; font: inherit;
}
.lang-switch button.on { background: var(--paper); color: var(--ink); opacity: 1; }
.nav-inner > .lang-switch { margin-left: auto; }

/* ── Nav controls group — tight wrapper around lang-switch + theme-switch
   so the parent's flex gap (e.g. .nav-menu gap: 36px on the homepage)
   doesn't apply between them. The wrapper is inserted by assets/theme.js
   when it lifts the lang-switch into a shared parent with the picker. */
.nav-controls {
  display: inline-flex;
  align-items: center;
  gap: 14px;
}

/* ── Theme switcher — color-picker style.
   Two small swatches (dark + light), the active one gets a purple ring.
   Sits inside .nav-controls so the surrounding nav layout is unaffected. */
.theme-switch {
  display: inline-flex; align-items: center;
  gap: 8px;
  height: 22px;
}
.theme-switch button {
  background: transparent; border: 0; cursor: pointer;
  padding: 2px;
  width: 22px; height: 22px;
  border-radius: 999px;
  display: inline-flex; align-items: center; justify-content: center;
  transition: box-shadow var(--t-quick) var(--ease-soft), transform var(--t-quick) var(--ease-soft);
}
.theme-switch button .swatch {
  display: block;
  width: 14px; height: 14px;
  border-radius: 999px;
  transition: transform var(--t-quick) var(--ease-soft);
}
.theme-switch button[data-mode="dark"] .swatch {
  background: #1c1c1c;
  border: 1px solid color-mix(in srgb, var(--paper) 32%, transparent);
}
.theme-switch button[data-mode="light"] .swatch {
  background: #f4f1ea;
  border: 1px solid rgba(28,28,28,0.20);
}
.theme-switch button:hover .swatch { transform: scale(1.08); }
.theme-switch button.on {
  box-shadow: 0 0 0 1.5px var(--purple);
}
.theme-switch button:focus-visible {
  outline: 2px solid var(--purple);
  outline-offset: 2px;
}
/* In light mode, deepen the ring contrast against cream */
[data-theme="light"] .theme-switch button.on {
  box-shadow: 0 0 0 1.5px var(--purple);
}

/* ── Results strip — 2–3 outcome numbers under the hero, for the
   executive skim. One line of proof before the story starts. ── */
.cs-results {
  border-top: 1px solid var(--rule);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 32px;
  padding: 28px 0 36px;
}
@media (max-width: 700px) { .cs-results { grid-template-columns: 1fr; gap: 20px; padding: 22px 0 28px; } }
.cs-results .r-num {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(26px, 3vw, 38px);
  letter-spacing: -0.8px; line-height: 1;
  color: var(--paper);
}
.cs-results .r-num em { color: var(--purple-2); font-style: italic; font-weight: 600; }
.cs-results .r-lab {
  margin-top: 8px;
  font-size: 14px; line-height: 1.45;
  color: var(--muted);
}

/* ── 1. Eyebrow + 2. H1 + 2-up split (headline left, visual right) ── */
.cs-hero {
  padding: 80px 0 0;
  border-bottom: 1px solid var(--rule);
}
.cs-eyebrow {
  font-family: var(--mono); font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.18em;
  color: var(--muted);
  display: inline-flex; align-items: center; gap: 12px;
  margin-bottom: 48px;
}
.cs-eyebrow .dot {
  width: 6px; height: 6px; background: var(--purple); border-radius: 999px;
  box-shadow: 0 0 16px var(--purple);
}
.cs-eyebrow .num { color: var(--paper); }

.cs-hero-split {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 1fr);
  gap: 64px;
  align-items: start;
  padding-bottom: 80px;
}
@media (max-width: 1000px) {
  .cs-hero-split { grid-template-columns: 1fr; gap: 40px; align-items: stretch; }
}
.cs-hero-left {
  display: flex; flex-direction: column;
  min-height: 100%;
  min-width: 0;  /* prevent flex/grid blowout if a child wants more width */
}
.cs-h1 {
  font-family: var(--display); font-weight: 800;
  /* Single flat clamp — caps at 80px so the longest single word in any
     headline ("Rebuilding") fits inside the cs-hero-left column on the
     widest monitors. The previous 1400px+ override (96–116px) overflowed
     into the visual on big screens. */
  font-size: clamp(30px, 4.4vw, 80px);
  line-height: 1.02; letter-spacing: -2.4px;
  margin: 0 0 8px;
  text-wrap: balance;
  padding-bottom: 0.06em;
  /* Safety net — if a future headline carries an unusually long word, break
     instead of overflowing into the hero visual. */
  overflow-wrap: break-word;
  min-width: 0;
}
.cs-h1 .accent { color: var(--purple); font-style: italic; font-weight: 700; }

.cs-hero-visual {
  border: 1px solid var(--rule-2);
  background:
    radial-gradient(60% 70% at 50% 50%, rgba(151,71,255,0.22), transparent 70%),
    repeating-linear-gradient(135deg,
      color-mix(in srgb, var(--paper) 5%, transparent) 0 14px,
      transparent 14px 28px),
    #221a23;
  aspect-ratio: 4 / 5;
  position: relative; overflow: hidden;
  align-self: start;
}
@media (max-width: 1000px) { .cs-hero-visual { aspect-ratio: 16 / 10; } }
.cs-hero-visual[data-label]::after {
  content: attr(data-label);
  position: absolute; left: 16px; bottom: 12px;
  font-family: var(--mono); font-size: 10px;
  text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--muted);
}
.cs-hero-visual .center-tag {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-family: var(--display); font-weight: 800;
  font-size: clamp(20px, 2.6vw, 36px);
  color: color-mix(in srgb, var(--paper) 50%, transparent); letter-spacing: -0.6px;
  text-align: center; padding: 0 24px;
}
/* Real image rendering — when an <img> sits inside a hero/frame slot,
   it fills the slot via object-fit: cover. The :has() rule below hides the
   placeholder text + the diagonal gradient so the image stands alone. */
.cs-hero-visual > img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; display: block;
}
.cs-hero-visual:has(> img) {
  background: var(--ink-2);
}
.cs-hero-visual:has(> img) .center-tag { display: none; }
.cs-hero-visual img {
  width: 100%; height: 100%; object-fit: cover; display: block;
}

/* Meta strip — sits inside the LEFT column, under the headline.
   Pushed to the bottom of the column so it bottom-aligns with the visual. */
.cs-meta-strip {
  margin-top: auto;
  padding-top: 40px;
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 24px 40px;
  border-top: 1px solid var(--rule-2);
  font-family: var(--mono); font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--muted);
}
@media (max-width: 1000px) {
  .cs-meta-strip { margin-top: 8px; padding-top: 28px; }
}
@media (max-width: 540px) { .cs-meta-strip { grid-template-columns: 1fr; } }
.cs-meta-strip .row .k { color: var(--muted-2); margin-bottom: 6px; }
.cs-meta-strip .row .v {
  color: var(--paper); font-weight: 500; line-height: 1.5;
  /* Values carry bare domains (brain.daviserrano.com) with no space to break
     on. In a narrow phone column an unbreakable token like that pushes past
     the cell and takes the whole page sideways with it. */
  overflow-wrap: anywhere;
}
.cs-meta-strip .row .v i { color: var(--purple-2); font-style: italic; font-weight: 400; }

/* Client logo cell — wordmark sits in .v slot, same flow as other rows */
.cs-meta-strip .row-logo .v {
  display: flex; align-items: center;
  height: 22px;
}
.cs-meta-strip .row-logo .v img {
  height: 22px; width: auto; max-width: 160px; display: block;
  filter: brightness(0) invert(1);
  opacity: 0.92;
}
.cs-meta-strip .row-logo .v .wordmark {
  font-family: var(--display); font-weight: 700;
  font-size: 18px; letter-spacing: -0.3px;
  text-transform: none;
  color: var(--paper);
  line-height: 1;
  display: inline-flex; align-items: center; gap: 6px;
}
.cs-meta-strip .row-logo .v .wordmark .mark {
  display: inline-block; width: 6px; height: 6px;
  background: var(--purple); border-radius: 999px;
  box-shadow: 0 0 10px var(--purple);
}

/* ── 3. Body — single column, centered ── */
.cs-body {
  padding: 96px 0 120px;
}

/* ── Main column ── */
.cs-main { max-width: 760px; margin: 0 auto; }

/* The five beats */
.cs-beat { margin: 0 0 56px; }
.cs-beat:last-child { margin-bottom: 0; }
.cs-beat-label {
  font-family: var(--mono); font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.16em;
  color: var(--muted-2);
  margin-bottom: 20px;
  display: inline-flex; align-items: center; gap: 12px;
}
.cs-beat-label .n { color: var(--purple-2); }
.cs-beat h2 {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(28px, 3.6vw, 44px);
  line-height: 1.05; letter-spacing: -1px;
  margin: 0 0 24px;
  text-wrap: pretty;
}
.cs-beat h2 .accent { color: var(--purple); font-style: italic; font-weight: 600; }
.cs-beat p {
  font-size: 19px; line-height: 1.55;
  margin: 0 0 18px;
  color: color-mix(in srgb, var(--paper) 92%, transparent);
  text-wrap: pretty;
}
.cs-beat p:last-child { margin-bottom: 0; }
.cs-beat p i, .cs-beat p em { color: var(--purple-2); font-style: italic; }
.cs-beat p b, .cs-beat p strong { color: var(--paper); font-weight: 600; }

/* ── Insert slots — break out of cs-main's narrow column to the wider wrap ── */
.cs-insert {
  margin: 80px 0;
  /* Break out of cs-main's 760px to fill .wrap (1320 - 112 padding = 1208) */
  width: min(1208px, calc(100vw - 112px));
  margin-left: 50%;
  transform: translateX(-50%);
  position: relative;
}
@media (max-width: 900px) {
  .cs-insert { width: calc(100vw - 48px); }
}

.cs-insert-label {
  font-family: var(--mono); font-size: 10px;
  text-transform: uppercase; letter-spacing: 0.22em;
  color: var(--muted-2);
  display: inline-flex; align-items: center; gap: 12px;
  margin-bottom: 24px;
}
.cs-insert-label::before {
  content: ""; display: inline-block;
  width: 24px; height: 1px; background: var(--rule-2);
}

/* ── Signature moment — ONE per case study.
   The page's climax insert wears the project's own vivid accent: a faint
   tinted band, accent rules, accent label, spec corner brackets. Everything
   else on the page stays quiet — this is where the boldness is spent.
   Set the accent via a .sig-* class: lime · magenta · orange · electric · purple. */
.cs-insert.cs-signature {
  --sig: var(--purple);
  padding: 48px clamp(20px, 4vw, 48px) 44px;
  background:
    radial-gradient(60% 90% at 50% 0%, color-mix(in srgb, var(--sig) 8%, transparent), transparent 70%),
    color-mix(in srgb, var(--sig) 3%, transparent);
  border-top: 1px solid color-mix(in srgb, var(--sig) 40%, transparent);
  border-bottom: 1px solid color-mix(in srgb, var(--sig) 40%, transparent);
}
.cs-insert.cs-signature::before,
.cs-insert.cs-signature::after {
  content: ""; position: absolute;
  width: 18px; height: 18px;
  border: 1.5px solid var(--sig);
  pointer-events: none;
}
.cs-insert.cs-signature::before { top: -1px; left: 0; border-right: 0; border-bottom: 0; }
.cs-insert.cs-signature::after  { bottom: -1px; right: 0; border-left: 0; border-top: 0; }
.cs-insert.cs-signature .cs-insert-label { color: var(--sig); }
.cs-insert.cs-signature .cs-insert-label::before { background: var(--sig); }
.cs-insert.cs-signature .cs-insert-caption em { color: var(--sig); }
.cs-signature.sig-lime     { --sig: var(--lime); }
.cs-signature.sig-magenta  { --sig: var(--magenta); }
.cs-signature.sig-orange   { --sig: var(--orange); }
.cs-signature.sig-electric { --sig: var(--electric); }
.cs-signature.sig-purple   { --sig: var(--purple); }
/* Lime + orange are light hues — on the light theme they need a darker mix
   for the label text to stay legible on cream. */
[data-theme="light"] .cs-signature.sig-lime .cs-insert-label,
[data-theme="light"] .cs-signature.sig-lime .cs-insert-caption em { color: color-mix(in srgb, var(--lime) 55%, #1c1c1c); }
[data-theme="light"] .cs-signature.sig-orange .cs-insert-label,
[data-theme="light"] .cs-signature.sig-orange .cs-insert-caption em { color: color-mix(in srgb, var(--orange) 70%, #1c1c1c); }
@media (max-width: 700px) {
  .cs-insert.cs-signature { padding: 32px 16px 30px; }
}
.cs-insert-caption {
  margin-top: 20px;
  font-size: 15px; line-height: 1.55;
  color: color-mix(in srgb, var(--paper) 88%, transparent);
  max-width: 720px;
  text-wrap: pretty;
}
.cs-insert-caption em, .cs-insert-caption i {
  color: var(--purple-2); font-style: italic;
}

/* ── Insert block: Frame (single image) ── */
.frame-img {
  border: 1px solid var(--rule-2);
  background:
    repeating-linear-gradient(135deg,
      color-mix(in srgb, var(--paper) 5%, transparent) 0 14px,
      transparent 14px 28px),
    #1a1a1a;
  position: relative; overflow: hidden;
}
.frame-img.tinted {
  background:
    radial-gradient(60% 70% at 50% 50%, rgba(151,71,255,0.18), transparent 70%),
    repeating-linear-gradient(135deg,
      color-mix(in srgb, var(--paper) 5%, transparent) 0 14px,
      transparent 14px 28px),
    #221a23;
}
.frame-img.ratio-21x9 { aspect-ratio: 21/9; }
.frame-img.ratio-2x1  { aspect-ratio: 2/1;  }
.frame-img.ratio-16x9 { aspect-ratio: 16/9; }
.frame-img.ratio-4x3  { aspect-ratio: 4/3;  }
.frame-img.ratio-3x4  { aspect-ratio: 3/4;  }
.frame-img.ratio-9x16 { aspect-ratio: 9/16; }
.frame-img.ratio-1x1  { aspect-ratio: 1/1;  }
.frame-img[data-label]::after {
  content: attr(data-label);
  position: absolute; left: 16px; bottom: 12px;
  font-family: var(--mono); font-size: 10px;
  text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--muted);
}
.frame-img .center-tag {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-family: var(--display); font-weight: 800;
  font-size: clamp(18px, 2.5vw, 32px);
  color: color-mix(in srgb, var(--paper) 45%, transparent); letter-spacing: -0.5px;
  text-align: center; padding: 0 24px;
}
/* Real image rendering — same pattern as the hero slot. */
.frame-img > img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; display: block;
}
.frame-img:has(> img) {
  background: var(--ink-2);
}
.frame-img:has(> img) .center-tag { display: none; }
/* Device variant — a phone/browser mockup sits inside the frame instead of a
   flat screenshot. Drops the hatched placeholder fill (nothing is missing
   here) and centres the mockup at the frame's height, so a 9:19.5 phone
   lines up cleanly beside sibling 3:4 image frames in the same grid. */
.frame-img.device {
  background: color-mix(in srgb, var(--paper) 3%, transparent);
  display: grid; place-items: center; padding: 20px;
}
.frame-img.device > .center-tag { display: none; }
.frame-img.device .mock-phone {
  height: 100%; width: auto; max-width: 100%;
  aspect-ratio: 9 / 19.5;
  margin: 0 auto;              /* centred in the frame's inline axis */
}
/* The screen fills the shell exactly; aspect-ratio is released so the height
   comes from the mockup rather than the element's own ratio. */
.frame-img.device .mock-phone .mp-screen {
  height: 100%; width: 100%; aspect-ratio: auto;
}
/* When you want the image to show in full (e.g. a UI mockup or diagram),
   add .contain to the frame and the image switches to object-fit: contain. */
.frame-img.contain > img { object-fit: contain; padding: 8px; }
/* Full-page screenshots: cover-crop anchored to the top so the page header/hero
   shows rather than the vertical middle. */
.frame-img.crop-top > img,
.cs-hero-visual.crop-top > img { object-position: top; }

/* ── Insert block: Grid (multi-image) ── */
.frame-grid { display: grid; gap: 16px; }
.frame-grid.cols-2 { grid-template-columns: 1fr 1fr; }
.frame-grid.cols-3 { grid-template-columns: repeat(3, 1fr); }
.frame-grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
.frame-grid.cols-5 { grid-template-columns: repeat(5, 1fr); gap: 10px; }
@media (max-width: 800px) {
  .frame-grid.cols-2, .frame-grid.cols-3, .frame-grid.cols-4, .frame-grid.cols-5 { grid-template-columns: 1fr 1fr; }
}
@media (max-width: 540px) {
  .frame-grid.cols-2, .frame-grid.cols-3, .frame-grid.cols-4, .frame-grid.cols-5 { grid-template-columns: 1fr; }
}

/* ── Looping video tile (muted, autoplay, no controls) ──
   For Spotify-Canvas-style loops. Drop into a .frame-grid; the <video> covers
   the tile, plays muted on loop, and shows no player chrome. A subtle scrim
   carries the track label. Playback is driven by IntersectionObserver so only
   on-screen tiles run. */
.frame-video-loop {
  position: relative;
  aspect-ratio: 9 / 16;
  overflow: hidden;
  border: 1px solid var(--rule-2);
  background: #0e0e0e;
  cursor: pointer;
}
/* Click-to-play hint. Shown while paused; fades once playing (returns on hover). */
.frame-video-loop .vl-hint {
  position: absolute; inset: 0; z-index: 2;
  display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 12px;
  color: #f3f3f3; background: rgba(0,0,0,0.30);
  transition: opacity var(--t-base) var(--ease-soft), background var(--t-base) var(--ease-soft);
}
.frame-video-loop .vl-play {
  width: 46px; height: 46px; border-radius: 50%;
  border: 1.5px solid rgba(243,243,243,0.9);
  display: grid; place-items: center; font-size: 14px; line-height: 1;
}
.frame-video-loop .vl-txt {
  font-family: var(--mono); font-size: 9px; text-transform: uppercase; letter-spacing: 0.18em;
}
.frame-video-loop.is-playing .vl-hint { opacity: 0; }
.frame-video-loop.is-playing:hover .vl-hint { opacity: 1; background: rgba(0,0,0,0.18); }
.frame-video-loop video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; display: block;
  pointer-events: none;            /* no interaction, no controls surface */
}
.frame-video-loop::before {        /* legibility scrim under the label */
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 38%;
  background: linear-gradient(180deg, transparent, rgba(0,0,0,0.5));
  z-index: 1; pointer-events: none;
}
.frame-video-loop[data-label]::after {
  content: attr(data-label);
  position: absolute; left: 12px; right: 12px; bottom: 11px; z-index: 2;
  font-family: var(--mono); font-size: 9px;
  text-transform: uppercase; letter-spacing: 0.12em;
  color: rgba(243,243,243,0.9);
  text-shadow: 0 1px 4px rgba(0,0,0,0.55);
  pointer-events: none;
}

/* ── Device mockups: browser window + phone ──
   Wrap a UI screenshot in clean device chrome. Screens are cover-cropped to a
   viewport (top-anchored) so the mockups stay uniform. The chrome is a fixed
   dark shell in both themes, matching the dark UI it usually frames. */
.mock-browser {
  border: 1px solid var(--rule-2);
  border-radius: 11px 11px 9px 9px;
  overflow: hidden;
  background: #0e0e0e;
  box-shadow: 0 26px 70px -34px rgba(0,0,0,0.7);
}
.mock-browser .mb-bar {
  display: flex; align-items: center; gap: 14px;
  height: 38px; padding: 0 15px;
  background: #161616;
  border-bottom: 1px solid var(--rule-2);
}
.mock-browser .mb-dots { display: flex; gap: 7px; flex-shrink: 0; }
.mock-browser .mb-dots i { width: 11px; height: 11px; border-radius: 50%; background: rgba(243,243,243,0.18); display: block; }
.mock-browser .mb-url {
  flex: 1; max-width: 520px; height: 21px; border-radius: 11px;
  background: rgba(243,243,243,0.06);
  display: flex; align-items: center; padding: 0 13px;
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.08em;
  color: rgba(243,243,243,0.5); white-space: nowrap; overflow: hidden;
}
.mock-browser .mb-screen {
  display: block; width: 100%;
  aspect-ratio: 16 / 10; object-fit: cover; object-position: top;
}
.mock-browser.tall .mb-screen { aspect-ratio: 16 / 13; }
/* .shot — show a screenshot at its natural ratio (uncropped) inside the chrome,
   for images that are already viewport-shaped (e.g. app/editor captures). */
.mock-browser.shot .mb-screen { aspect-ratio: auto; height: auto; object-fit: fill; }
/* placeholder variant (no image yet) */
.mock-browser .mb-ph {
  aspect-ratio: 16 / 10;
  display: grid; place-items: center;
  background:
    repeating-linear-gradient(135deg, color-mix(in srgb, var(--paper) 5%, transparent) 0 14px, transparent 14px 28px),
    #141414;
  font-family: var(--mono); font-size: 12px; letter-spacing: 0.06em;
  color: rgba(243,243,243,0.45); text-align: center; padding: 0 22px;
}
.mock-browser.tall .mb-ph { aspect-ratio: 16 / 13; }

.mock-phone {
  position: relative;
  border: 7px solid #101010;
  border-radius: 34px;
  overflow: hidden;
  background: #000;
  box-shadow: 0 24px 56px -30px rgba(0,0,0,0.7);
}
.mock-phone .mp-notch {
  position: absolute; top: 0; left: 50%; transform: translateX(-50%);
  width: 44%; height: 15px; background: #101010; border-radius: 0 0 13px 13px; z-index: 2;
}
.mock-phone .mp-screen {
  display: block; width: 100%;
  aspect-ratio: 9 / 19.5; object-fit: cover; object-position: top;
}
/* A video screen is a recording of the whole interface, not a tall screenshot,
   so it centres in the frame instead of anchoring to the top — any difference
   between the clip's ratio and the device's is split evenly top and bottom
   rather than taken entirely off the foot of the screen. */
.mock-phone video.mp-screen { object-position: center; }
.mock-phone .mp-ph {
  aspect-ratio: 9 / 19.5;
  display: grid; place-items: center;
  background:
    repeating-linear-gradient(135deg, color-mix(in srgb, var(--paper) 5%, transparent) 0 14px, transparent 14px 28px),
    #141414;
  font-family: var(--mono); font-size: 10px; letter-spacing: 0.06em;
  color: rgba(243,243,243,0.45); text-align: center; padding: 0 14px;
}
/* Pair layout: a browser with a phone overlapping its lower-right */
.mock-pair { position: relative; }
.mock-pair .mock-phone {
  position: absolute; right: -8px; bottom: -28px;
  width: 22%; min-width: 150px; z-index: 3;
}
@media (max-width: 700px) {
  .mock-pair .mock-phone { position: static; width: 56%; margin: 18px auto 0; right: auto; bottom: auto; }
}

/* Live content inside the browser mockup (instead of a static .mb-screen image):
   .mb-live  — a scaled preview of a live site, rendered at 1440px desktop and
               scaled to fit by JS. Scrolling is left enabled on the iframe
               itself (no `scrolling="no"`), but a transparent guard sits on
               top so wheel/touch over the preview passes straight through to
               the page instead of getting trapped inside the frame — same
               principle as the homepage canvas. The guard leaves an 18px
               strip uncovered on the right so the framed site's own native
               scrollbar (rendered inside its cross-origin document — we
               can't restyle it from here) stays reachable by hand.
   .mb-live-scrollbar — an optional, more prominent scrollbar this page draws
               itself. Cross-origin content can't be styled from the parent,
               so this only appears once the embedded page checks in over
               postMessage (see the bridge script this pairs with, kept in
               that site's own codebase). Until it checks in, `.has-bridge`
               never gets added, the custom bar stays hidden, and nothing
               about the fallback above changes — the native scrollbar strip
               is what visitors use. Once it IS active, the guard's ::after
               masks that native scrollbar's own strip (we can't hide a
               cross-origin document's scrollbar any other way), so the two
               don't render side by side as a visual "double scrollbar."
   .mb-embed — a full, interactive live embed (e.g. a chat agent). Lazy-mounts an
               iframe over a fallback CTA. This is what used to be the standalone
               "Live iframe" component, now a mode of the browser mockup. */
.mock-browser .mb-live {
  position: relative; width: 100%;
  aspect-ratio: 16 / 10;
  overflow: hidden; background: #0e0e0e;
}
.mock-browser .mb-live .mb-live-inner {
  position: absolute; top: 0; left: 0;
  width: 1440px; height: 900px; border: 0;
  transform-origin: top left;
}
.mock-browser .mb-live .mb-live-guard {
  position: absolute; inset: 0; right: 18px;
  z-index: 2; background: transparent;
}
.mock-browser .mb-live.has-bridge .mb-live-guard { right: 0; }
.mock-browser .mb-live.has-bridge .mb-live-guard::after {
  /* Covers the strip where the framed page's own native scrollbar
     renders, now that our custom one has taken over — a soft fade
     rather than a hard-edged block so it doesn't read as a cut line. */
  content: ""; position: absolute; top: 0; right: 0; bottom: 0;
  width: 26px; pointer-events: none;
  background: linear-gradient(to right, transparent, #0e0e0e 55%);
}
.mock-browser .mb-live .mb-live-scrollbar {
  position: absolute; top: 0; right: 0; bottom: 0;
  width: 16px; z-index: 3; display: none;
}
.mock-browser .mb-live.has-bridge .mb-live-scrollbar { display: block; }
.mock-browser .mb-live .mb-live-scrollbar-track {
  position: absolute; inset: 0;
  background: rgba(243,243,243,0.06);
}
.mock-browser .mb-live .mb-live-scrollbar-thumb {
  position: absolute; right: 3px; top: 0;
  width: 6px; border-radius: 4px;
  background: rgba(243,243,243,0.8);
  /* Dark outline so the bar reads against light AND dark stretches of
     whatever the framed page happens to be showing behind it — the same
     double-paint-for-legibility idea as the canvas drag cursor. */
  box-shadow: 0 0 0 1px rgba(0,0,0,0.6), 0 1px 3px rgba(0,0,0,0.35);
  cursor: grab; touch-action: none;
  transition: width .15s ease, right .15s ease, background .15s ease;
}
/* Widening on hover/drag is a real size change, not just a color swap —
   the hit target grows along with what's visibly there to grab. */
.mock-browser .mb-live .mb-live-scrollbar:hover .mb-live-scrollbar-thumb,
.mock-browser .mb-live .mb-live-scrollbar-thumb.is-dragging {
  width: 10px; right: 3px;
  background: var(--purple-2); cursor: grabbing;
}
/* Extra invisible margin around the (already-widened-on-hover) bar, so
   the first touch/click lands even before the hover state has kicked in
   — 44px is the standard comfortable tap target (Apple HIG / Material). */
.mock-browser .mb-live .mb-live-scrollbar-thumb::before {
  content: ""; position: absolute;
  top: -4px; bottom: -4px; left: 50%;
  width: 44px; transform: translateX(-50%);
}
@media (max-width: 720px) {
  /* No hover on touch — the bigger size has to be there by default. */
  .mock-browser .mb-live .mb-live-scrollbar { width: 22px; }
  .mock-browser .mb-live .mb-live-scrollbar-thumb { width: 12px; right: 5px; }
  .mock-browser .mb-live.has-bridge .mb-live-guard::after { width: 30px; }
}
.mock-browser .mb-embed {
  position: relative;
  aspect-ratio: 16 / 10;
  overflow: hidden;
  background:
    radial-gradient(60% 70% at 50% 50%, rgba(151,71,255,0.14), transparent 70%),
    var(--ink-2);
}
.mock-browser .mb-embed iframe {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  border: 0; display: block; z-index: 1;
  opacity: 0; transition: opacity var(--dur-3, .4s) var(--ease-out, ease);
}
.mock-browser .mb-embed .iframe-fallback {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  text-align: center; padding: 32px; z-index: 0;
}
/* The fallback CTA is hidden by .is-live, which the mount script sets only
   after the iframe fires `load`. Keyed on a successful load rather than on
   the iframe merely existing (`:has(iframe)`), because a frame blocked by CSP
   or a dead origin would otherwise hide the CTA and leave an empty well: a
   silent failure that looks like an empty design. If the embed never loads,
   the visitor keeps a working link instead. */
.mock-browser .mb-embed.is-live iframe { opacity: 1; }
.mock-browser .mb-embed.is-live .iframe-fallback { display: none; }
.mock-browser .mb-embed .iframe-fallback h4 {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(20px, 2.4vw, 28px);
  margin: 0 0 8px; letter-spacing: -0.4px;
}
.mock-browser .mb-embed .iframe-fallback p {
  margin: 0 0 20px; max-width: 480px;
  font-size: 14px; color: var(--muted); line-height: 1.55;
}
.mock-browser .mb-embed .iframe-cta {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 12px 18px;
  background: var(--purple); color: var(--paper);
  font-family: var(--mono); font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.16em;
  border: 0;
}
.mock-browser .mb-embed .iframe-cta:hover { background: var(--purple-2); color: var(--ink); }

/* Visually hidden but reachable by screen readers. Shared utility. */
.sr-only {
  position: absolute; width: 1px; height: 1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}

/* ── Embed loader: shown only while the mount script is actually waiting on
   the iframe (.is-loading, set by JS right before it appends the frame).
   Everywhere else, .iframe-fallback above is the default: a no-JS visitor,
   or one where the script never runs, still lands on a working link rather
   than a spinner that goes nowhere. On timeout the script drops
   .is-loading and the fallback reappears automatically, no extra state
   needed. An accordion of monospace block glyphs sliding over a dotted
   track, restyled from the loading-ui "Accordion Loader" (MIT license,
   loading-ui.com/docs/components/accordion-loader) to the site's own
   ink/purple palette instead of building a spinner from scratch. */
.mock-browser .mb-embed .mb-embed-loader {
  display: none; position: absolute; inset: 0;
  place-items: center; z-index: 0;
}
.mock-browser .mb-embed.is-loading .mb-embed-loader { display: grid; }
.mock-browser .mb-embed.is-loading .iframe-fallback { display: none; }

.accordion-loader {
  position: relative;
  display: inline-flex; align-items: center;
  height: 1em; width: var(--al-width, 15ch);
  overflow: hidden;
  font-family: var(--mono); font-size: 15px;
}
.accordion-loader .al-track {
  position: absolute; inset: 0;
  white-space: nowrap; color: var(--rule-2);
}
.accordion-loader .al-glyph {
  position: absolute; top: 0; left: 0;
  display: flex; height: 100%; width: 1ch;
  align-items: center; justify-content: center;
  color: var(--purple-2);
  background: var(--ink-2);
  animation: accordion-loader-slide var(--al-duration, 2.6s) ease-in-out infinite;
}
.accordion-loader .al-glyph:nth-child(2) { z-index: 3; }
.accordion-loader .al-glyph:nth-child(3) { z-index: 2; animation-delay: .05s; }
.accordion-loader .al-glyph:nth-child(4) { z-index: 1; animation-delay: .1s; }
@keyframes accordion-loader-slide {
  0%, 100% { transform: translateX(0); }
  50%      { transform: translateX(var(--al-x, 15ch)); }
}

/* ── Insert block: Numbers (oversized stat cells, no boxed frame) ── */
.frame-numbers {
  display: grid; grid-template-columns: 1fr 1fr; gap: 48px;
}
/* Solo variant: a single figure has no sibling to sit against, so left-aligning
   it leaves the row visually unbalanced. Centre the cell and its label instead. */
.frame-numbers.solo { grid-template-columns: 1fr; justify-items: center; text-align: center; }
.frame-numbers.solo .num-cell .lab { max-width: 420px; margin-left: auto; margin-right: auto; }
.frame-numbers.three { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 700px) {
  .frame-numbers, .frame-numbers.three { grid-template-columns: 1fr; gap: 36px; }
}
.num-cell {
  padding: 0;
}
.num-cell .big {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(36px, 9vw, 132px);
  line-height: 0.92; letter-spacing: -3px;
  color: var(--paper);
}
.num-cell .big em { color: var(--purple-2); font-style: italic; font-weight: 600; }
.num-cell .lab {
  margin-top: 18px;
  font-family: var(--body); font-size: 15px;
  color: color-mix(in srgb, var(--paper) 78%, transparent);
  max-width: 320px; line-height: 1.5;
}
.num-cell .lab b { color: var(--paper); font-weight: 600; }

/* ── Insert block: Timeline (horizontal) ── */
.frame-timeline {
  border-top: 1px solid var(--rule-2);
  border-bottom: 1px solid var(--rule-2);
  padding: 36px 0 8px;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 24px;
  position: relative;
}
.frame-timeline::before {
  content: ""; position: absolute;
  left: 24px; right: 24px; top: 36px;
  border-top: 1px dashed var(--rule-2);
}
@media (max-width: 800px) { .frame-timeline { grid-template-columns: 1fr 1fr; } }
@media (max-width: 540px) { .frame-timeline { grid-template-columns: 1fr; } }
/* 3-column variant — for 3 or 6 step timelines (balanced rows). */
.frame-timeline.cols-3 { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 800px) { .frame-timeline.cols-3 { grid-template-columns: 1fr 1fr; } }
@media (max-width: 540px) { .frame-timeline.cols-3 { grid-template-columns: 1fr; } }
.tl-step { padding-top: 8px; position: relative; }
.tl-step::before {
  content: ""; position: absolute; left: 0; top: -6px;
  width: 10px; height: 10px; border-radius: 999px;
  background: var(--purple); box-shadow: 0 0 14px var(--purple);
}
.tl-step .yr {
  font-family: var(--mono); font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--purple-2);
  margin-bottom: 8px;
}
.tl-step h4 {
  font-family: var(--display); font-weight: 700;
  font-size: 18px; line-height: 1.15; letter-spacing: -0.4px;
  margin: 0 0 8px;
}
.tl-step p { margin: 0; font-size: 13px; line-height: 1.5; color: var(--muted); }

/* Single-row variant — for longer sequences (5+ steps) that still need to read
   left-to-right. All steps sit in one row connected by a continuous dashed
   line; text is smaller but readable. On narrow screens it scrolls
   horizontally rather than stacking, so the timeline never loses its direction. */
.frame-timeline.row {
  display: flex;
  grid-template-columns: none;
  gap: 22px;
  overflow-x: auto;
  padding: 30px 2px 16px;
  /* Scrollbar styling comes from the shared .frame-timeline.row /
     .cs-carousel-track / .frame-code pre rule further down. */
}
.frame-timeline.row::before { display: none; }   /* per-step connectors instead of a frame line */
.frame-timeline.row .tl-step {
  flex: 1 0 0;
  min-width: 148px;
  padding-top: 22px;
  position: relative;
}
.frame-timeline.row .tl-step::before {            /* dot */
  content: ""; position: absolute; left: 0; top: 0;
  width: 12px; height: 12px; border-radius: 999px;
  background: var(--purple); box-shadow: 0 0 12px rgba(151,71,255,0.5);
  z-index: 1;
}
.frame-timeline.row .tl-step::after {             /* dashed connector to the next dot */
  content: ""; position: absolute; left: 12px; top: 5px; right: -22px;
  border-top: 2px dashed var(--rule-2); z-index: 0;
}
.frame-timeline.row .tl-step:last-child::after { display: none; }
.frame-timeline.row .tl-step .yr { font-size: 10px; margin-bottom: 6px; }
.frame-timeline.row .tl-step h4 { font-size: 14.5px; line-height: 1.18; margin: 0 0 6px; }
.frame-timeline.row .tl-step p  { font-size: 12px; line-height: 1.45; }

/* ── Two-act timeline removed v3 — Olivia now uses .frame-timeline directly ── */

/* ── Insert block: Diagram (architecture / system) ── */
.frame-diagram {
  border: 1px solid var(--rule-2);
  background:
    radial-gradient(60% 80% at 50% 40%, rgba(151,71,255,0.10), transparent 70%),
    var(--ink-2);
  padding: 56px 48px;
  position: relative;
}
.frame-diagram .dg-title {
  font-family: var(--mono); font-size: 10px;
  text-transform: uppercase; letter-spacing: 0.22em;
  color: var(--muted);
  margin-bottom: 32px;
}
.dg-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}
/* Four-step variant, for pipelines read left to right (e.g. a runtime path).
   Collapses 2x2 on tablet so a step never wraps alone under a row of three. */
.dg-grid.cols-4 { grid-template-columns: repeat(4, 1fr); }
@media (max-width: 1000px) { .dg-grid.cols-4 { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 700px) { .dg-grid, .dg-grid.cols-4 { grid-template-columns: 1fr; } }
.dg-node {
  /* All nodes share the same simple style — purple border, near-transparent fill. */
  border: 1px solid rgba(151,71,255,0.55);
  padding: 22px 20px;
  background: color-mix(in srgb, var(--paper) 2%, transparent);
  display: flex;
  flex-direction: column;
}
/* Legacy: .purple modifier kept as a no-op so old markup doesn't break. */
.dg-node.purple { border-color: rgba(151,71,255,0.55); background: color-mix(in srgb, var(--paper) 2%, transparent); }
/* Icon sits at the top of each node, leading the label. Inherits color via
   currentColor, so .dg-icon picks up purple-2 by default. */
.dg-node .dg-icon {
  width: 22px; height: 22px;
  color: var(--purple-2);
  margin-bottom: 14px;
  flex-shrink: 0;
  display: block;
}

/* ── Iconography — thin-stroke, monoline, 24px viewBox ──
   General-purpose .icon utility used wherever inline SVG is preferable to a
   text glyph. Stroke 1.5, rounded caps, currentColor — so the icon inherits
   color from the surrounding text. Sizes: .icon (default 16), .icon-sm (12),
   .icon-md (22), .icon-lg (32). */
.icon, .icon-sm, .icon-md, .icon-lg {
  display: inline-block; vertical-align: middle;
  fill: none; stroke: currentColor;
  stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round;
  flex-shrink: 0;
}
.icon { width: 16px; height: 16px; }
.icon-sm { width: 12px; height: 12px; stroke-width: 1.6; }
.icon-md { width: 22px; height: 22px; }
.icon-lg { width: 32px; height: 32px; stroke-width: 1.4; }
.dg-node .nlab { font-family: var(--mono); font-size: 10px; color: var(--purple-2); letter-spacing: 0.16em; text-transform: uppercase; margin-bottom: 8px; }
.dg-node h5 { font-family: var(--display); font-weight: 700; font-size: 17px; letter-spacing: -0.3px; margin: 0 0 6px; }
.dg-node p { margin: 0; font-size: 12px; line-height: 1.5; color: var(--muted); }
.dg-foot {
  margin-top: 32px; padding-top: 20px; border-top: 1px solid var(--rule-2);
  font-family: var(--mono); font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.14em; color: var(--muted);
}
.dg-foot b { color: var(--paper); font-weight: 500; }

/* ── Before / after split ── */
.frame-split {
  display: grid; grid-template-columns: 1fr 1fr; gap: 16px;
}
@media (max-width: 800px) { .frame-split { grid-template-columns: 1fr; } }
.split-side .side-tag {
  font-family: var(--mono); font-size: 10px;
  text-transform: uppercase; letter-spacing: 0.18em;
  color: var(--muted);
  margin-bottom: 10px;
}
.split-side .side-tag b { color: var(--paper); font-weight: 500; }

/* ── Audio module (Bradesco) ── */
.frame-audio {
  border: 1px solid var(--rule-2);
  background:
    radial-gradient(50% 80% at 30% 50%, rgba(151,71,255,0.12), transparent 70%),
    var(--ink-2);
  padding: 36px 32px;
  display: flex; gap: 24px; align-items: center;
}
.audio-play {
  width: 64px; height: 64px; flex-shrink: 0;
  border-radius: 999px;
  background: var(--purple); color: var(--paper);
  display: grid; place-items: center;
  font-size: 18px; cursor: pointer;
  transition: transform var(--t-quick) var(--ease-soft), box-shadow var(--t-quick) var(--ease-soft);
  border: 0;
}
.audio-play:hover { transform: scale(1.05); box-shadow: 0 0 32px rgba(151,71,255,0.5); }
.audio-meta .audio-title { font-family: var(--display); font-weight: 700; font-size: 22px; letter-spacing: -0.4px; margin-bottom: 4px; }
.audio-meta .audio-sub  { font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.18em; color: var(--muted); }

/* ── 5. Outcome line (italic / muted, below thin rule) ── */
.cs-outcome {
  margin-top: 56px;
  padding-top: 32px;
  border-top: 1px solid var(--rule);
  font-family: var(--display);
  font-size: clamp(20px, 2vw, 26px);
  line-height: 1.4; letter-spacing: -0.3px;
  font-style: italic;
  color: var(--muted);
  max-width: 720px;
  text-wrap: pretty;
}
.cs-outcome b { color: var(--paper); font-style: normal; font-weight: 600; }

/* ── cs-close removed v2 — case studies now end on outcome line + cs-next ── */

/* ── 6. Chapter divider — narrative pivots inside the body
   ─────────────────────────────────────────────────────────────
   Used to separate The Idea / The Strategy / The Process / The Solution.
   Sits inside .wrap-narrow, full content width, slim editorial rule. */
.cs-chapter {
  margin: 96px 0 28px;
  padding-top: 28px;
  border-top: 1px solid var(--rule);
  /* Break out to the SAME width as .cs-insert and .cs-pair (1208px), so the
     chapter title shares its left edge with any insert that follows. Without
     this, the chapter sits in the narrower wrap-narrow column (1068px) and
     reads as inset from the editorial blocks below it. */
  width: min(1208px, calc(100vw - 112px));
  margin-left: 50%;
  transform: translateX(-50%);
  position: relative;
}
@media (max-width: 900px) {
  .cs-chapter { width: calc(100vw - 48px); }
}
.cs-chapter h3 {
  font-family: var(--display);
  font-weight: 800;
  font-size: clamp(30px, 4.4vw, 52px);
  letter-spacing: -1.4px;
  line-height: 1.02;
  margin: 0;
  color: var(--paper);
  text-wrap: balance;
}
.cs-chapter h3 em {
  font-style: italic;
  font-weight: 700;
  color: var(--purple-2);
}
/* First chapter sits closer to hero — no top rule */
.cs-chapter.is-first {
  margin-top: 8px;
  padding-top: 0;
  border-top: 0;
}
@media (max-width: 720px) {
  /* Longhand on purpose. The `margin` shorthand here used to reset
     margin-left to 0, which silently killed the `margin-left: 50%` half of
     the breakout above while `transform: translateX(-50%)` kept applying.
     Every chapter title was dragged half its own width off the left edge on
     any screen under 720px. Only ever set the vertical margins here. */
  .cs-chapter { margin-top: 72px; margin-bottom: 22px; padding-top: 22px; }
}

/* ── 7. Next case study — slimmer, editorial proportions ── */
/* ── cs-next v3 — the home feature card, dropped at the foot of every case study.
   Same component the work grid uses: 4:3 thumb well, hover bar, view-tag, title/client.
   Preceded by a "Next case study" eyebrow and followed by a "Back to all work" link. ── */
.cs-next { border-top: 1px solid var(--rule); padding: 64px 0 56px; }
.cs-next .wrap { display: flex; flex-direction: column; align-items: flex-start; }
.cs-next .lab {
  font-family: var(--mono); font-size: 10px;
  text-transform: uppercase; letter-spacing: 0.18em;
  color: var(--purple-2); margin-bottom: 24px;
}

/* The card — mirrors .work-card from the home grid */
.cs-next .next-card {
  display: block; position: relative;
  width: min(440px, 100%);
  padding-bottom: 18px; isolation: isolate; text-decoration: none;
}
.cs-next .next-thumb {
  position: relative; aspect-ratio: 65 / 42; overflow: hidden;
  margin-bottom: 24px; background: #1a1a1a;
  transition: transform var(--t-slow) var(--ease);
}
/* placeholder texture (thumb stays a dark well in both themes, like the home cards) */
.cs-next .next-thumb::after {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background:
    radial-gradient(120% 120% at 100% 0%, rgba(151,71,255,0.16), transparent 55%),
    radial-gradient(120% 120% at 0% 100%, rgba(255,255,255,0.05), transparent 55%),
    linear-gradient(135deg, #1f1722 0%, #161217 100%);
  transform: scale(1.02); transition: opacity var(--t-slow) var(--ease-soft);
}
.cs-next .next-thumb::before {
  content: attr(data-thumb); position: absolute; inset: 0; z-index: 1;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--mono); font-size: 11px;
  color: rgba(243,243,243,0.42);
  letter-spacing: 0.22em; text-transform: uppercase; text-align: center; padding: 0 24px;
}
/* Real image variant. The image URL is set as an inline background-image on
   the element (not via a custom property): relative url() inside a var() is
   resolved against this stylesheet's location in some browsers, which broke
   ../assets/work/ paths on case pages. Inline background-image always
   resolves against the page. */
.cs-next .next-thumb.has-img { background-size: cover; background-position: center; }
.cs-next .next-thumb.has-img::before, .cs-next .next-thumb.has-img::after { display: none; }
.cs-next .next-overlay {
  position: absolute; inset: 0; z-index: 1;
  background: linear-gradient(180deg, transparent 55%, rgba(0,0,0,0.45) 100%);
  opacity: 0; transition: opacity var(--t-base) var(--ease-soft);
}
.cs-next .next-card:hover .next-thumb.has-img { transform: scale(1.015); }
.cs-next .next-card:hover .next-overlay { opacity: 1; }
/* locked variant — purple wash */
.cs-next .next-card.locked .next-thumb::after {
  background:
    radial-gradient(120% 120% at 100% 0%, rgba(151,71,255,0.32), transparent 55%),
    radial-gradient(120% 120% at 0% 100%, rgba(151,71,255,0.18), transparent 55%),
    linear-gradient(135deg, #2a1a3a 0%, #18101e 100%);
}
.cs-next .next-card.locked .next-thumb::before { color: rgba(207,178,255,0.55); }
/* bottom hover bar */
.cs-next .next-card::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 2px; z-index: 3;
  background: var(--purple); box-shadow: 0 0 12px rgba(151,71,255,0.35);
  transform: scaleX(0); transform-origin: left center;
  transition: transform var(--t-slow) var(--ease), box-shadow var(--t-slow) var(--ease-soft);
}
.cs-next .next-card:hover::after { transform: scaleX(1); box-shadow: 0 0 18px rgba(151,71,255,0.6); }
.cs-next .next-card.locked::after { background: var(--purple-2); box-shadow: 0 0 14px rgba(194,148,255,0.45); }
/* view tag */
.cs-next .view-tag {
  position: absolute; left: 16px; bottom: 14px; z-index: 2;
  font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.18em;
  color: #f3f3f3; display: inline-flex; align-items: center; gap: 8px;
  opacity: 0; transform: translateY(6px);
  transition: opacity var(--t-base) var(--ease-soft), transform var(--t-slow) var(--ease);
}
.cs-next .next-card:hover .view-tag { opacity: 1; transform: translateY(0); }
.cs-next .view-tag .arr { transition: transform var(--t-base) var(--ease); }
.cs-next .next-card:hover .view-tag .arr { transform: translateX(4px); }
/* meta */
.cs-next .next-meta { display: flex; justify-content: space-between; align-items: baseline; gap: 16px; margin-bottom: 6px; }
.cs-next .next-name {
  font-family: var(--display); font-weight: 800;
  font-size: 22px; letter-spacing: -0.6px; line-height: 1.1;
  color: var(--paper); margin: 0;
  transition: color var(--t-base) var(--ease-soft);
}
.cs-next .next-suffix { color: var(--muted); font-weight: 500; }
.cs-next .next-card.locked:hover .next-name { color: var(--purple-2); }
.cs-next .next-client {
  font-family: var(--mono); font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.14em; color: var(--muted);
}
.cs-next .next-cat {
  margin-top: 3px;
  font-family: var(--mono); font-size: 10px;
  text-transform: uppercase; letter-spacing: 0.14em; color: var(--muted-2);
}
/* lock glyph — CSS padlock, matches the home grid */
.cs-next .lock-glyph {
  display: inline-block; width: 11px; height: 13px; vertical-align: -1px;
  border: 1.4px solid currentColor; border-radius: 1.5px; position: relative;
  margin-left: 4px; color: var(--purple-2);
}
.cs-next .lock-glyph::before {
  content: ""; position: absolute; left: 50%; top: -6px; transform: translateX(-50%);
  width: 7px; height: 6px; border: 1.4px solid currentColor;
  border-bottom: 0; border-radius: 4px 4px 0 0;
}
/* ── Carousel variant — a scroll-snap row of the other projects, so the
   visitor picks what to see next instead of being handed one card. ── */
.cs-next .cs-carousel { position: relative; width: 100%; }
.cs-next .cs-carousel-track {
  display: flex; gap: 24px;
  overflow-x: auto; overflow-y: hidden;
  scroll-snap-type: x mandatory; scroll-behavior: smooth;
  padding: 4px 2px 14px; margin: 0 -2px;
  -webkit-overflow-scrolling: touch;
  /* Scrollbar styling comes from the shared scrollable-surface rule below. */
}
/* Cards sit in a row at a fixed width, snapping to the left edge. */
.cs-next .cs-carousel-track .next-card {
  width: 300px; flex: 0 0 300px; padding-bottom: 16px;
  scroll-snap-align: start;
}
.cs-next .cs-carousel-track .next-thumb { margin-bottom: 18px; }
.cs-next .cs-carousel-track .next-name { font-size: 19px; }
/* Prev / next arrows — overlay the track edges, wired by theme.js. */
.cs-next .cs-carousel-arrow {
  position: absolute; top: calc(50% - 40px); transform: translateY(-50%);
  width: 42px; height: 42px; border-radius: 50%;
  display: grid; place-items: center; z-index: 4; cursor: pointer;
  background: color-mix(in srgb, var(--ink) 78%, transparent);
  border: 1px solid var(--rule-2); color: var(--paper);
  font-size: 16px; line-height: 1; backdrop-filter: blur(6px);
  transition: opacity var(--t-base) var(--ease-soft), background var(--t-quick) var(--ease-soft), border-color var(--t-quick) var(--ease-soft);
}
.cs-next .cs-carousel-arrow:hover { background: var(--purple); border-color: var(--purple); color: #fff; }
.cs-next .cs-carousel-arrow.prev { left: -8px; }
.cs-next .cs-carousel-arrow.next { right: -8px; }
.cs-next .cs-carousel-arrow.is-disabled { opacity: 0; pointer-events: none; }
.cs-next .cs-carousel-arrow:focus-visible { outline: 2px solid var(--purple-2); outline-offset: 2px; }
@media (hover: none), (max-width: 720px) {
  /* Touch devices scroll natively; hide the arrows. */
  .cs-next .cs-carousel-arrow { display: none; }
  .cs-next .cs-carousel-track .next-card { width: 78vw; flex-basis: 78vw; }
}

/* Back to all work */
.cs-next .next-all {
  margin-top: 30px; align-self: flex-start;
  font-family: var(--mono); font-size: 11px; text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--muted); text-decoration: none;
  display: inline-flex; align-items: center; gap: 10px;
  transition: color var(--t-quick) var(--ease-soft);
}
.cs-next .next-all:hover { color: var(--purple-2); }
.cs-next .next-all .arr { transition: transform var(--t-base) var(--ease); }
.cs-next .next-all:hover .arr { transform: translateX(var(--nudge)); }

/* ── Foot ── */
.foot {
  padding: 28px 0 32px;
  display: flex; justify-content: space-between; align-items: center;
  font-family: var(--mono); font-size: 11px; color: var(--muted);
  text-transform: uppercase; letter-spacing: 0.1em;
}
.foot a { color: var(--muted); }
.foot a:hover { color: var(--paper); }

/* ── Olivia gate (specific to Olivia teaser) ── */
.gate {
  margin-top: 64px;
  padding: 40px 0;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  display: grid; grid-template-columns: minmax(0,1fr) auto; gap: 32px; align-items: center;
}
@media (max-width: 800px) { .gate { grid-template-columns: 1fr; } }
.gate h3 { font-family: var(--display); font-weight: 700; font-size: 24px; letter-spacing: -0.5px; margin: 0 0 8px; }
.gate p  { margin: 0; color: var(--muted); font-size: 14px; max-width: 540px; }
.gate-form {
  display: flex; gap: 8px; align-items: center;
}
.gate-form input {
  background: transparent; color: var(--paper);
  border: 1px solid var(--rule-2); padding: 12px 16px;
  font: inherit; font-size: 14px; width: 220px;
  transition: border-color var(--t-quick) var(--ease-soft);
}
.gate-form input:focus { outline: 0; border-color: var(--purple); }
/* Token-driven so it stays legible in both themes rather than inheriting
   the field's text colour at a browser-chosen opacity. */
.gate-form input::placeholder { color: var(--muted); opacity: 1; }
.gate-form button {
  background: var(--purple); color: var(--paper);
  border: 0; padding: 12px 20px;
  font-family: var(--mono); font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.14em;
  cursor: pointer; transition: background var(--t-quick) var(--ease-soft);
}
.gate-form button:hover { background: var(--purple-2); color: var(--ink); }
/* Gate error state — text + colour + one restrained shake.
   The message fades and rises in on the reveal grammar (opacity + small
   translate); the input border flips to --error; the form shakes once,
   ±--nudge, and settles. All values resolve through the motion tokens,
   so prefers-reduced-motion collapses the shake and travel to nothing
   and the feedback remains as instant text + colour. */
.gate-err {
  color: var(--error); font-size: 12px; margin-top: 10px; min-height: 16px;
  opacity: 0; transform: translateY(calc(var(--nudge) * -1));
  transition: opacity var(--t-base) var(--ease-soft), transform var(--t-base) var(--ease);
}
.gate-err.is-on { opacity: 1; transform: none; }
.gate-form.is-error input { border-color: var(--error); }
.gate-form.is-error { animation: gateShake var(--t-base) var(--ease); }
@keyframes gateShake {
  0%, 100% { transform: translateX(0); }
  20%, 60% { transform: translateX(calc(var(--nudge) * -1)); }
  40%, 80% { transform: translateX(var(--nudge)); }
}
.gate-alt { font-size: 12px; color: var(--muted); margin-top: 8px; }
.gate-alt a { color: var(--paper); border-bottom: 1px solid var(--rule-2); }
.full-only { display: none; }
body.unlocked .full-only { display: block; }
body.unlocked .teaser-only { display: none; }
/* Back-to-teaser control. Sits outside a .wrap, so it carries the page
   gutter itself to stay aligned with the content edge. */
.relock {
  display: inline-flex; align-items: center; gap: 8px;
  padding: 10px 14px; margin: 32px 0 32px 56px;
  background: transparent; color: var(--muted);
  border: 1px solid var(--rule-2);
  font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: 0.14em;
  cursor: pointer;
}
.relock:hover { color: var(--paper); border-color: var(--paper); }
@media (max-width: 900px) {
  .relock { margin-left: 24px; }
}

/* ────────────────────────────────────────────────────────────────
   v2 — extra patterns added for the case-study buildout
   ──────────────────────────────────────────────────────────────── */

/* ── Hero image — full-bleed below the title block ─── */
.cs-hero-image {
  width: 100vw;
  margin-left: calc(50% - 50vw);
  margin-top: 56px;
  aspect-ratio: 21 / 9;
  position: relative; overflow: hidden;
  border-top: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  background:
    radial-gradient(50% 70% at 50% 50%, rgba(151,71,255,0.18), transparent 70%),
    repeating-linear-gradient(135deg, color-mix(in srgb, var(--paper) 5%, transparent) 0 14px, transparent 14px 28px),
    #221a23;
}
.cs-hero-image.tall { aspect-ratio: 4 / 3; }
.cs-hero-image.portrait { aspect-ratio: 3 / 4; max-width: 720px; margin-left: auto; margin-right: auto; }
.cs-hero-image[data-label]::after {
  content: attr(data-label);
  position: absolute; left: 56px; bottom: 24px;
  font-family: var(--mono); font-size: 10px;
  text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--muted);
}
.cs-hero-image .center-tag {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-family: var(--display); font-weight: 800;
  font-size: clamp(20px, 2.6vw, 36px);
  color: color-mix(in srgb, var(--paper) 50%, transparent); letter-spacing: -0.6px;
  text-align: center; padding: 0 24px;
}
.cs-hero-image img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ── Video embed (16:9) — supports YouTube + Vimeo iframes ─── */
.frame-video {
  position: relative;
  aspect-ratio: 16 / 9;
  border: 1px solid var(--rule-2);
  background:
    radial-gradient(50% 70% at 50% 50%, rgba(151,71,255,0.18), transparent 70%),
    repeating-linear-gradient(135deg, color-mix(in srgb, var(--paper) 5%, transparent) 0 12px, transparent 12px 24px),
    #1a1820;
  overflow: hidden;
}
.frame-video iframe { width: 100%; height: 100%; border: 0; display: block; }
.frame-video .video-poster {
  position: absolute; inset: 0;
  display: flex; align-items: center; justify-content: center;
  flex-direction: column; gap: 14px;
  text-align: center; padding: 24px;
}
.frame-video .play-button {
  width: 64px; height: 64px; border-radius: 999px;
  background: var(--purple); color: var(--paper);
  display: grid; place-items: center; font-size: 22px;
  box-shadow: 0 0 28px rgba(151,71,255,0.4);
}
.frame-video .video-label {
  font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--muted);
}

/* Paired video row */
.frame-video-pair {
  display: grid; grid-template-columns: 1fr 1fr; gap: 16px;
}
@media (max-width: 800px) {
  .frame-video-pair { grid-template-columns: 1fr; }
}

/* ── Code block — designed typography, not pasted-in IDE ─── */
/* File window — code snippets and text-based file previews, dressed in the same
   window chrome as the browser mockup (traffic dots + a filename in the title
   bar), adapted for a file/editor context. The body scrolls when the file is
   long; the title bar stays put. */
.frame-code {
  border: 1px solid var(--rule-2);
  border-radius: 11px 11px 9px 9px;
  overflow: hidden;
  background: var(--ink-2);
  font-family: var(--mono);
  font-size: 13px;
  line-height: 1.7;
  color: color-mix(in srgb, var(--paper) 85%, transparent);
}
.frame-code .fc-bar {
  position: relative;
  display: flex; align-items: center; gap: 14px;
  height: 38px; padding: 0 15px;
  background: #161616;
  border-bottom: 1px solid var(--rule-2);
}
.frame-code .fc-dots { display: flex; gap: 7px; flex-shrink: 0; z-index: 1; }
.frame-code .fc-dots i { width: 11px; height: 11px; border-radius: 50%; background: rgba(243,243,243,0.18); display: block; }
.frame-code .fc-name {
  position: absolute; left: 0; right: 0; text-align: center;
  font-family: var(--mono); font-size: 11px; letter-spacing: 0.1em;
  color: rgba(243,243,243,0.5); pointer-events: none;
}
/* Body scrolls when the file is long; short files show no scrollbar. */
.frame-code pre {
  margin: 0; padding: 26px 40px 30px;
  white-space: pre-wrap; word-break: break-word;
  max-height: 340px; overflow-y: auto;
}
/* On a phone the insert is only about 354px wide, and 40px of side padding
   plus 13px monospace left roughly 274px of text: every line of persona.md
   broke two or three times and the block read as fragments. Trim the gutters
   and the type so a line of prompt survives as a line. `break-word` above
   already prevents a long token from pushing the page sideways. */
@media (max-width: 700px) {
  .frame-code pre { padding: 20px 18px 22px; font-size: 12px; line-height: 1.62; }
  .frame-code .h1 { font-size: 14px; }
}

/* ── Scrollable surfaces — one scrollbar recipe for every component ───
   The file-window body, the next-work carousel, the horizontal
   timeline, and the design-system side nav all scroll; they share this
   slim rounded thumb on a transparent track (the frame-code treatment,
   promoted to the shared style). New scrollable components should join
   this selector list rather than styling their own. The color-mix
   values track --paper, so the bar flips with the theme automatically. */
.frame-code pre,
.cs-next .cs-carousel-track,
.frame-timeline.row,
.ds-side {
  scrollbar-width: thin;
  scrollbar-color: color-mix(in srgb, var(--paper) 20%, transparent) transparent;
}
.frame-code pre::-webkit-scrollbar,
.cs-next .cs-carousel-track::-webkit-scrollbar,
.frame-timeline.row::-webkit-scrollbar,
.ds-side::-webkit-scrollbar {
  width: 11px; height: 11px;
}
.frame-code pre::-webkit-scrollbar-track,
.cs-next .cs-carousel-track::-webkit-scrollbar-track,
.frame-timeline.row::-webkit-scrollbar-track,
.ds-side::-webkit-scrollbar-track { background: transparent; }
.frame-code pre::-webkit-scrollbar-thumb,
.cs-next .cs-carousel-track::-webkit-scrollbar-thumb,
.frame-timeline.row::-webkit-scrollbar-thumb,
.ds-side::-webkit-scrollbar-thumb {
  background: color-mix(in srgb, var(--paper) 16%, transparent);
  border-radius: 8px; border: 3px solid transparent; background-clip: padding-box;
}
.frame-code pre::-webkit-scrollbar-thumb:hover,
.cs-next .cs-carousel-track::-webkit-scrollbar-thumb:hover,
.frame-timeline.row::-webkit-scrollbar-thumb:hover,
.ds-side::-webkit-scrollbar-thumb:hover {
  background: color-mix(in srgb, var(--paper) 30%, transparent);
  background-clip: padding-box;
}
.frame-code .h1 { color: var(--purple-2); font-weight: 500; font-size: 16px; display: block; }
.frame-code .h2 { color: var(--paper); font-weight: 500; display: block; margin-top: 1.4em; }
.frame-code .li { color: color-mix(in srgb, var(--paper) 78%, transparent); display: block; }
.frame-code .sym { color: var(--muted-2); }
.frame-code .blank { display: block; height: 0.6em; }

/* ── Live iframe / fallback embed (Twin Brain Talk-To-It) ─── */
/* Persistent "open in new tab" action, sits BELOW a live embed and stays
   regardless of whether the iframe mounted. Ghost / outline treatment. */
.iframe-actions { display: flex; justify-content: center; margin-top: 20px; }
.iframe-open {
  display: inline-flex; align-items: center; gap: 10px;
  padding: 13px 24px;
  border: 1px solid var(--rule-2); border-radius: 999px;
  font-family: var(--mono); font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--paper); text-decoration: none;
  transition: border-color var(--t-quick) var(--ease-soft), color var(--t-quick) var(--ease-soft);
}
.iframe-open:hover { border-color: var(--purple); color: var(--purple-2); }
.iframe-open .ext { transition: transform var(--t-base) var(--ease); }
.iframe-open:hover .ext { transform: translate(2px, -2px); }

/* ── Manifesto numbered list (TBL pain points) ─── */
.frame-manifesto {
  border-top: 1px solid var(--rule-2);
}
.frame-manifesto .mf-item {
  padding: 28px 0;
  border-bottom: 1px solid var(--rule);
  display: grid;
  grid-template-columns: 80px 1fr;
  gap: 32px;
  align-items: baseline;
}
@media (max-width: 600px) {
  .frame-manifesto .mf-item { grid-template-columns: 56px 1fr; gap: 16px; padding: 22px 0; }
}
.frame-manifesto .mf-num {
  font-family: var(--mono);
  font-size: 13px;
  color: var(--purple-2);
  letter-spacing: 0.16em;
  font-variant-numeric: tabular-nums;
}
.frame-manifesto .mf-body h4 {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(20px, 2.4vw, 28px);
  letter-spacing: -0.5px;
  margin: 0 0 6px;
  line-height: 1.1;
  color: var(--paper);
}
.frame-manifesto .mf-body p {
  margin: 0;
  font-size: 15px; line-height: 1.55;
  color: var(--muted);
}

/* ── Campaign mosaic — one composed block for a campaign's spread across
   formats. A six-column grid: a full-width opener, then a wide motion tile
   paired with a portrait one, then a run of square-ish units (banners, poses).
   Every tile carries its own aspect-ratio so the rows resolve without fixed
   heights, and .mo-tall spans two rows to sit flush with the pair beside it.
   Collapses to two columns on narrow screens. ── */
.frame-mosaic {
  display: grid;
  grid-template-columns: repeat(6, minmax(0, 1fr));
  gap: 14px;
}
.frame-mosaic > * {
  position: relative; overflow: hidden; min-width: 0;
  border: 1px solid var(--rule-2); background: #0e0e0e;
}
.frame-mosaic img,
.frame-mosaic video {
  display: block; width: 100%; height: 100%;
  object-fit: cover;
}
.frame-mosaic .mo-wide  { grid-column: span 6; aspect-ratio: 16 / 9; }
.frame-mosaic .mo-video { grid-column: span 4; aspect-ratio: 16 / 9; }
.frame-mosaic .mo-tall  { grid-column: span 2; grid-row: span 2; }
.frame-mosaic .mo-sq    { grid-column: span 2; aspect-ratio: 6 / 5; }
/* The portrait tile is cropped to the height of the two rows it spans, so it
   anchors to the top where the page header and headline live. */
.frame-mosaic .mo-tall img { object-position: top; }
@media (max-width: 760px) {
  .frame-mosaic { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px; }
  .frame-mosaic .mo-wide,
  .frame-mosaic .mo-video { grid-column: span 2; }
  .frame-mosaic .mo-tall  { grid-column: span 2; grid-row: auto; aspect-ratio: 4 / 5; }
  .frame-mosaic .mo-sq    { grid-column: span 1; }
}

/* ── Spec list — a compact numbered list for enumerating parts of a system
   inside a tight column (typically .cs-pair-text, beside a visual). Reads
   like the manifesto block but at roughly a third the vertical cost: hairline
   rows, mono index, one bold term per row. Use when the list supports a
   visual rather than carrying a section on its own. ── */
.spec-list { display: block; border-top: 1px solid var(--rule-2); }
.spec-list .sl-row {
  display: grid; grid-template-columns: 24px minmax(0, 1fr); gap: 14px;
  padding: 13px 0; border-bottom: 1px solid var(--rule); align-items: baseline;
}
.spec-list .sl-num {
  font-family: var(--mono); font-size: 11px; color: var(--purple-2);
  font-variant-numeric: tabular-nums; letter-spacing: 0.08em;
}
.spec-list .sl-body b {
  display: block; margin-bottom: 3px;
  font-size: 15px; font-weight: 600; color: var(--paper); letter-spacing: -0.1px;
}
.spec-list .sl-body span {
  display: block; font-size: 13.5px; line-height: 1.5; color: var(--muted);
}
@media (max-width: 600px) {
  .spec-list .sl-row { grid-template-columns: 20px minmax(0, 1fr); gap: 10px; padding: 11px 0; }
}

/* ── Highlight block (TBL key changes) ─── */
.frame-highlight {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 56px;
  align-items: center;
  margin: 0 0 80px;
}
@media (max-width: 800px) {
  .frame-highlight { grid-template-columns: 1fr; gap: 24px; margin-bottom: 48px; }
}
.frame-highlight.reverse > .hl-text { order: 2; }
.frame-highlight.reverse > .hl-visual { order: 1; }
@media (max-width: 800px) {
  .frame-highlight.reverse > .hl-text { order: 1; }
  .frame-highlight.reverse > .hl-visual { order: 2; }
}
.frame-highlight .hl-text { padding-top: 8px; }
.frame-highlight .hl-num {
  font-family: var(--mono);
  font-size: 11px; letter-spacing: 0.18em;
  text-transform: uppercase; color: var(--purple-2);
  margin-bottom: 12px;
}
.frame-highlight .hl-text h4 {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(22px, 2.6vw, 32px);
  letter-spacing: -0.6px; line-height: 1.1;
  margin: 0 0 16px;
  color: var(--paper);
}
.frame-highlight .hl-text p {
  font-size: 16px; line-height: 1.55;
  color: color-mix(in srgb, var(--paper) 84%, transparent);
  margin: 0;
}
.frame-highlight .hl-text .hl-cap {
  margin-top: 16px;
  font-family: var(--mono); font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--muted);
}

/* ── Portrait gallery 4×2 (Olivia avatars) ─── */
.frame-portraits {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 4px;
}
@media (max-width: 1000px) {
  .frame-portraits { grid-template-columns: repeat(3, 1fr); }
}
@media (max-width: 700px) {
  .frame-portraits { grid-template-columns: repeat(2, 1fr); }
}
.frame-portraits .portrait {
  aspect-ratio: 3 / 4;
  border: 1px solid var(--rule-2);
  background:
    radial-gradient(60% 70% at 50% 40%, rgba(151,71,255,0.18), transparent 70%),
    repeating-linear-gradient(135deg, color-mix(in srgb, var(--paper) 5%, transparent) 0 14px, transparent 14px 28px),
    #221a23;
  position: relative; overflow: hidden;
}
.frame-portraits .portrait[data-label]::after {
  content: attr(data-label);
  position: absolute; bottom: 10px; left: 12px;
  font-family: var(--mono); font-size: 9px;
  text-transform: uppercase; letter-spacing: 0.18em;
  color: var(--muted);
}
.frame-portraits .portrait .center-tag {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-family: var(--display); font-weight: 700;
  font-size: clamp(14px, 1.6vw, 20px);
  color: color-mix(in srgb, var(--paper) 42%, transparent);
  text-align: center; padding: 0 12px;
}
.frame-portraits .portrait img { width: 100%; height: 100%; object-fit: cover; display: block; }

/* ── Avatar evolution — static 4×2 grid (was: horizontal scroll) ─── */
.frame-panorama {
  width: 100%;
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 6px;
  border-top: 1px solid var(--rule-2);
  border-bottom: 1px solid var(--rule-2);
  padding: 28px 0;
}
@media (max-width: 700px) { .frame-panorama { grid-template-columns: repeat(2, 1fr); } }
/* 3-column variant — for evolution sequences with exactly 3 frames, so the
   grid doesn't leave a phantom 4th column. */
.frame-panorama.cols-3 { grid-template-columns: repeat(3, 1fr); }
@media (max-width: 700px) { .frame-panorama.cols-3 { grid-template-columns: 1fr; } }
.frame-panorama .pano-frame {
  aspect-ratio: 3 / 4;
  background:
    radial-gradient(60% 70% at 50% 50%, rgba(151,71,255,0.10), transparent 70%),
    repeating-linear-gradient(135deg, color-mix(in srgb, var(--paper) 4%, transparent) 0 12px, transparent 12px 24px),
    #1a1820;
  border: 1px solid var(--rule-2);
  position: relative;
  overflow: hidden;
}
/* Real media — img/video fill the frame via object-fit: cover, same contract
   as .frame-img elsewhere. A bottom scrim keeps pano-yr legible over a photo
   or a playing video instead of only over the placeholder hatch. */
.frame-panorama .pano-frame img,
.frame-panorama .pano-frame video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; display: block;
}
.frame-panorama .pano-frame:has(> img),
.frame-panorama .pano-frame:has(> video) { background: var(--ink-2); }
.frame-panorama .pano-frame:has(> img) .center-tag,
.frame-panorama .pano-frame:has(> video) .center-tag { display: none; }
.frame-panorama .pano-frame:has(> img)::before,
.frame-panorama .pano-frame:has(> video)::before {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 42%;
  background: linear-gradient(180deg, transparent, rgba(0,0,0,0.55));
  pointer-events: none;
}
.frame-panorama .pano-frame .pano-yr {
  position: absolute; bottom: 8px; left: 10px; z-index: 1;
  font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.18em; color: var(--purple-2);
  text-transform: uppercase;
  text-shadow: 0 1px 4px rgba(0,0,0,0.5);
}
.frame-panorama .pano-frame .center-tag {
  position: absolute; inset: 0; padding-bottom: 24px;
  display: grid; place-items: center;
  font-family: var(--display); font-weight: 700;
  font-size: 14px; color: color-mix(in srgb, var(--paper) 42%, transparent);
  text-align: center;
}
/* Optional per-frame caption: wrap a .pano-frame + .pano-cap in a .pano-col
   so each column carries its own short explanatory line below the image,
   instead of one shared sentence under the whole grid. .pano-col becomes
   the grid item; grid-template-columns above still applies unchanged. */
.frame-panorama .pano-col { display: flex; flex-direction: column; gap: 10px; }
.frame-panorama .pano-cap {
  margin: 0; font-family: var(--mono); font-size: 11px; line-height: 1.55;
  color: var(--muted); letter-spacing: 0.01em;
}
.frame-panorama .pano-cap b { color: var(--paper); font-weight: 600; }

/* ── Banner gallery — composed editorial (Bradesco) ─── */
.frame-banners {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 12px;
}
/* A real banner fills its slot and hides the placeholder hatch + label, the
   same contract .frame-img uses for a populated frame. Banner creative is
   built to an exact IAB ratio, so cover crops at most a hairline. */
.frame-banners .bn > img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover; display: block; z-index: 1;
}
.frame-banners .bn:has(> img) { background: #0e0e0e; }
.frame-banners .bn:has(> img) .center-tag { display: none; }
.frame-banners .bn {
  background:
    repeating-linear-gradient(135deg, color-mix(in srgb, var(--paper) 5%, transparent) 0 12px, transparent 12px 24px),
    #1f1f1f;
  border: 1px solid var(--rule-2);
  position: relative;
  overflow: hidden;
}
.frame-banners .bn[data-label]::after {
  content: attr(data-label);
  position: absolute; left: 10px; bottom: 8px;
  font-family: var(--mono); font-size: 9px;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--muted);
}
.frame-banners .bn .center-tag {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: color-mix(in srgb, var(--paper) 40%, transparent);
}
.frame-banners .bn-leader { grid-column: 1 / -1; aspect-ratio: 728 / 90; }
.frame-banners .bn-rect-a { grid-column: 1 / span 4; aspect-ratio: 4 / 3; }
.frame-banners .bn-rect-b { grid-column: 5 / span 4; aspect-ratio: 4 / 3; }
.frame-banners .bn-tall   { grid-column: 9 / span 4; aspect-ratio: 9 / 16; grid-row: span 2; }
.frame-banners .bn-skyscraper { grid-column: 1 / span 8; aspect-ratio: 21 / 9; }
@media (max-width: 800px) {
  .frame-banners { grid-template-columns: 1fr; }
  .frame-banners .bn,
  .frame-banners .bn-leader,
  .frame-banners .bn-rect-a,
  .frame-banners .bn-rect-b,
  .frame-banners .bn-tall,
  .frame-banners .bn-skyscraper {
    grid-column: 1 / -1; grid-row: auto; aspect-ratio: 4 / 3;
  }
}

/* ── Pair — text + visual side-by-side, breaks rhythm of stacked centered inserts ─── */
.cs-pair {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.45fr);
  gap: 56px;
  align-items: start;
  margin: 80px 0;
  width: min(1208px, calc(100vw - 112px));
  margin-left: 50%;
  transform: translateX(-50%);
  position: relative;
}
@media (max-width: 900px) { .cs-pair { width: calc(100vw - 48px); gap: 32px; } }
/* margin-top/bottom longhand, never the shorthand: `margin` here would reset
   margin-left to 0 and break the breakout centering above, dragging the whole
   pair half its width off the left edge. Same trap as .cs-chapter had. */
@media (max-width: 800px) { .cs-pair { grid-template-columns: 1fr; gap: 28px; margin-top: 56px; margin-bottom: 56px; } }
/* Tight-visual variant: flips the weighting so the text column leads and the
   visual sits smaller beside it. For diagrams that read fine at reduced size
   (a square sunburst, a small chart) where the default 1.45fr makes the image
   dominate a section that is really carried by the copy. */
.cs-pair.tight-visual { grid-template-columns: minmax(0, 1.3fr) minmax(0, 1fr); align-items: center; }
@media (max-width: 800px) { .cs-pair.tight-visual { grid-template-columns: 1fr; } }
.cs-pair.reverse > .cs-pair-text { order: 2; }
.cs-pair.reverse > .cs-pair-visual { order: 1; }
@media (max-width: 800px) {
  .cs-pair.reverse > .cs-pair-text { order: 1; }
  .cs-pair.reverse > .cs-pair-visual { order: 2; }
}
.cs-pair-text { padding-top: 8px; }
.cs-pair-text .cs-pair-label {
  font-family: var(--mono); font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.16em;
  color: var(--purple-2);
  margin-bottom: 18px;
  display: inline-flex; align-items: center; gap: 12px;
}
.cs-pair-text h3 {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(22px, 2.8vw, 32px);
  letter-spacing: -0.6px; line-height: 1.1;
  margin: 0 0 18px;
  text-wrap: balance;
  color: var(--paper);
}
.cs-pair-text h3 .accent { color: var(--purple); font-style: italic; font-weight: 600; }
.cs-pair-text p {
  margin: 0 0 14px;
  font-size: 16px; line-height: 1.55;
  color: color-mix(in srgb, var(--paper) 84%, transparent);
  text-wrap: pretty;
}
.cs-pair-text p:last-child { margin-bottom: 0; }
.cs-pair-text p i, .cs-pair-text p em { color: var(--purple-2); font-style: italic; }
.cs-pair-text p b, .cs-pair-text p strong { color: var(--paper); font-weight: 600; }
.cs-pair-text .cs-pair-cap {
  margin-top: 18px;
  font-family: var(--mono); font-size: 11px;
  text-transform: uppercase; letter-spacing: 0.14em;
  color: var(--muted);
}
.cs-pair.text-heavy { grid-template-columns: minmax(0, 1.45fr) minmax(0, 1fr); }
@media (max-width: 800px) { .cs-pair.text-heavy { grid-template-columns: 1fr; } }

/* ── Slide deck — static 2-col editorial grid, hero spans both (Yuma) ─── */
.frame-carousel {
  width: 100%;
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 14px;
  padding: 0;
}
@media (max-width: 700px) { .frame-carousel { grid-template-columns: 1fr; } }
.frame-carousel .slide {
  aspect-ratio: 16 / 10;
  background:
    radial-gradient(70% 90% at 50% 50%, rgba(151,71,255,0.10), transparent 70%),
    repeating-linear-gradient(135deg, color-mix(in srgb, var(--paper) 4%, transparent) 0 14px, transparent 14px 28px),
    #1a1820;
  border: 1px solid var(--rule-2);
  position: relative;
  display: grid; place-items: center;
  padding: 28px;
  text-align: center;
}
/* First slide — hero, spans both columns and uses a deeper purple wash */
.frame-carousel .slide:first-child {
  grid-column: 1 / -1;
  aspect-ratio: 21 / 9;
  background:
    radial-gradient(60% 90% at 30% 50%, rgba(151,71,255,0.22), transparent 70%),
    repeating-linear-gradient(135deg, color-mix(in srgb, var(--paper) 5%, transparent) 0 14px, transparent 14px 28px),
    #221a30;
}
.frame-carousel .slide:first-child .slide-title { font-size: clamp(22px, 3vw, 36px); }
@media (max-width: 700px) { .frame-carousel .slide:first-child { aspect-ratio: 4 / 3; } }
.frame-carousel .slide-num {
  position: absolute; top: 14px; left: 18px;
  font-family: var(--mono); font-size: 9px;
  letter-spacing: 0.22em; text-transform: uppercase;
  color: var(--purple-2);
}
.frame-carousel .slide-title {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(17px, 2.1vw, 24px);
  letter-spacing: -0.4px; line-height: 1.2;
  max-width: 90%;
  color: color-mix(in srgb, var(--paper) 92%, transparent);
}
.frame-carousel .slide-sub {
  margin-top: 10px;
  font-family: var(--mono); font-size: 10px;
  letter-spacing: 0.16em; text-transform: uppercase;
  color: var(--muted);
}

/* ────────────────────────────────────────────────────────────────
   v3 — bento grid + spec marks for editorial compositions
   inspired by Avio (mixed cells), Drilling (asymmetric stats),
   Outer (editorial spec marks).
   ──────────────────────────────────────────────────────────────── */

/* ── Bento grid — variable-size cells of mixed content ─── */
.frame-bento {
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  grid-auto-rows: 104px;
  gap: 14px;
}
@media (max-width: 900px) { .frame-bento { grid-template-columns: repeat(6, 1fr); grid-auto-rows: 116px; } }
@media (max-width: 600px) { .frame-bento { grid-template-columns: repeat(2, 1fr); grid-auto-rows: 136px; } }

.bento-cell {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--rule-2);
  background: var(--ink-2);
  padding: 28px 32px;
  display: flex;
  flex-direction: column;
}
.bento-cell.is-purple {
  background:
    radial-gradient(60% 90% at 30% 50%, rgba(151,71,255,0.22), transparent 70%),
    var(--ink-2);
  border-color: rgba(151,71,255,0.32);
}
.bento-cell.is-paper {
  background: var(--paper);
  color: var(--ink);
  border-color: var(--paper);
}
.bento-cell.is-paper .cell-label { color: var(--muted-2); }
.bento-cell.is-paper .stat-num { color: var(--ink); }
.bento-cell.is-paper .stat-num em { color: var(--purple); }
.bento-cell.is-paper h4 { color: var(--ink); }
.bento-cell.is-paper p { color: rgba(28,28,28,0.72); }

/* Photo cell — placeholder fill with optional center label */
.bento-cell.is-photo {
  padding: 0;
  background:
    radial-gradient(60% 80% at 50% 50%, rgba(151,71,255,0.10), transparent 70%),
    repeating-linear-gradient(135deg, color-mix(in srgb, var(--paper) 5%, transparent) 0 14px, transparent 14px 28px),
    #1a1820;
}
.bento-cell.is-photo .photo-label {
  position: absolute; left: 14px; bottom: 12px;
  font-family: var(--mono); font-size: 9px;
  text-transform: uppercase; letter-spacing: 0.2em;
  color: var(--muted);
}
.bento-cell.is-photo .photo-tag {
  position: absolute; right: 12px; top: 12px;
  font-family: var(--mono); font-size: 9px;
  text-transform: uppercase; letter-spacing: 0.18em;
  color: var(--paper);
  background: rgba(0,0,0,0.6);
  padding: 5px 9px;
}
.bento-cell.is-photo .center-tag {
  position: absolute; inset: 0;
  display: grid; place-items: center;
  font-family: var(--display); font-weight: 700;
  font-size: clamp(14px, 1.6vw, 20px);
  color: color-mix(in srgb, var(--paper) 42%, transparent);
  text-align: center; padding: 0 16px;
}

/* Cell content — labels, numbers, copy */
.bento-cell .cell-label {
  font-family: var(--mono); font-size: 9px;
  text-transform: uppercase; letter-spacing: 0.2em;
  color: var(--purple-2);
  margin-bottom: 12px;
}
.bento-cell .stat-num {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(28px, 4.6vw, 64px); line-height: 0.95;
  letter-spacing: -1.6px;
  color: var(--paper);
  margin-top: auto;
}
.bento-cell .stat-num em { color: var(--purple-2); font-style: italic; font-weight: 600; }
.bento-cell .stat-lab {
  margin-top: 14px;
  font-family: var(--body); font-size: 14px;
  color: color-mix(in srgb, var(--paper) 78%, transparent);
  line-height: 1.5;
}
.bento-cell .stat-lab b { color: var(--paper); font-weight: 600; }
.bento-cell.is-paper .stat-lab { color: rgba(28,28,28,0.72); }
.bento-cell.is-paper .stat-lab b { color: var(--ink); }
.bento-cell h4 {
  font-family: var(--display); font-weight: 700;
  font-size: clamp(18px, 2vw, 24px);
  letter-spacing: -0.5px; line-height: 1.1;
  margin: 0 0 8px;
  color: var(--paper);
}
.bento-cell p {
  margin: 0;
  font-size: 14px; line-height: 1.5;
  color: color-mix(in srgb, var(--paper) 78%, transparent);
}
.bento-cell .quote {
  font-family: var(--display); font-style: italic; font-weight: 500;
  font-size: clamp(18px, 2.2vw, 28px); line-height: 1.2;
  letter-spacing: -0.4px;
  color: var(--paper);
  margin: auto 0;
}
.bento-cell .quote .who {
  display: block; margin-top: 14px;
  font-family: var(--mono); font-style: normal; font-size: 10px;
  letter-spacing: 0.18em; text-transform: uppercase;
  color: var(--muted);
}

/* Span helpers — based on a 12-col grid */
.bento-3x2 { grid-column: span 3; grid-row: span 2; }
.bento-3x3 { grid-column: span 3; grid-row: span 3; }
.bento-4x2 { grid-column: span 4; grid-row: span 2; }
.bento-4x3 { grid-column: span 4; grid-row: span 3; }
.bento-4x4 { grid-column: span 4; grid-row: span 4; }
.bento-5x3 { grid-column: span 5; grid-row: span 3; }
.bento-6x2 { grid-column: span 6; grid-row: span 2; }
.bento-6x3 { grid-column: span 6; grid-row: span 3; }
.bento-6x4 { grid-column: span 6; grid-row: span 4; }
.bento-8x3 { grid-column: span 8; grid-row: span 3; }
.bento-8x4 { grid-column: span 8; grid-row: span 4; }
.bento-12x2 { grid-column: span 12; grid-row: span 2; }

@media (max-width: 900px) {
  .bento-3x2, .bento-4x2, .bento-6x2, .bento-12x2 { grid-column: span 6; grid-row: span 2; }
  .bento-3x3, .bento-4x3, .bento-5x3, .bento-6x3, .bento-8x3 { grid-column: span 6; grid-row: span 3; }
  .bento-4x4, .bento-6x4, .bento-8x4 { grid-column: span 6; grid-row: span 4; }
}
@media (max-width: 600px) {
  .bento-3x2, .bento-4x2, .bento-6x2, .bento-12x2,
  .bento-3x3, .bento-4x3, .bento-5x3, .bento-6x3, .bento-8x3,
  .bento-4x4, .bento-6x4, .bento-8x4 { grid-column: span 2; grid-row: span 2; }
}

/* ── Spec marks — editorial ornaments (corner brackets, registration crosses) ─── */
.spec-corners { position: relative; }
.spec-corners::before,
.spec-corners::after {
  content: ""; position: absolute; width: 14px; height: 14px;
  border: 1px solid var(--purple-2); pointer-events: none;
}
.spec-corners::before {
  top: 8px; left: 8px;
  border-right: 0; border-bottom: 0;
}
.spec-corners::after {
  bottom: 8px; right: 8px;
  border-left: 0; border-top: 0;
}

/* Section eyebrow with tabs (Outer / Drilling references) */
.spec-row {
  display: flex; align-items: center; gap: 32px;
  padding: 16px 0; border-bottom: 1px solid var(--rule-2);
  font-family: var(--mono); font-size: 10px;
  text-transform: uppercase; letter-spacing: 0.2em;
  color: var(--muted);
  margin-bottom: 32px;
}
.spec-row .num { color: var(--purple-2); }
.spec-row .tab { color: var(--muted); }
.spec-row .tab.is-on { color: var(--paper); }
.spec-row .grow { flex: 1; }
.spec-row .target {
  width: 14px; height: 14px;
  border: 1px solid var(--rule-2); position: relative;
}
.spec-row .target::before, .spec-row .target::after {
  content: ""; position: absolute; background: var(--rule-2);
}
.spec-row .target::before { left: 50%; top: 0; bottom: 0; width: 1px; transform: translateX(-50%); }
.spec-row .target::after { top: 50%; left: 0; right: 0; height: 1px; transform: translateY(-50%); }

/* ────────────────────────────────────────────────────────────────
   v3.1 — Vivid accent palette (synth-first) + bento color variants
   .accent stays Syne italic purple (reverted from serif italic).
   ──────────────────────────────────────────────────────────────── */

/* Cs-hero-split — when only ONE column (no right visual), text stretches full width. */
.cs-hero-split.solo,
.cs-hero-split:has(> .cs-hero-left:only-child) { grid-template-columns: 1fr; }
.cs-hero-split.solo .cs-hero-left,
.cs-hero-split:has(> .cs-hero-left:only-child) .cs-hero-left { max-width: 1100px; }
.cs-hero-split.solo .cs-meta-strip {
  grid-template-columns: repeat(3, minmax(0, 1fr));
  margin-top: 56px;
}
@media (max-width: 900px) { .cs-hero-split.solo .cs-meta-strip { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (max-width: 540px) { .cs-hero-split.solo .cs-meta-strip { grid-template-columns: 1fr; } }

/* Bento cell vivid-tone variants — for synth-system color sparks.
   Use sparingly: one accent cell per bento composition, max. */
.bento-cell.is-lime {
  background:
    radial-gradient(60% 90% at 30% 50%, rgba(200,230,0,0.20), transparent 70%),
    var(--ink-2);
  border-color: rgba(200,230,0,0.32);
}
.bento-cell.is-lime .cell-label,
.bento-cell.is-lime .stat-num em { color: var(--lime); }
.bento-cell.is-magenta {
  background:
    radial-gradient(60% 90% at 30% 50%, rgba(255,61,135,0.20), transparent 70%),
    var(--ink-2);
  border-color: rgba(255,61,135,0.32);
}
.bento-cell.is-magenta .cell-label,
.bento-cell.is-magenta .stat-num em { color: var(--magenta); }
.bento-cell.is-orange {
  background:
    radial-gradient(60% 90% at 30% 50%, rgba(255,140,46,0.20), transparent 70%),
    var(--ink-2);
  border-color: rgba(255,140,46,0.32);
}
.bento-cell.is-orange .cell-label,
.bento-cell.is-orange .stat-num em { color: var(--orange); }
.bento-cell.is-electric {
  background:
    radial-gradient(60% 90% at 30% 50%, rgba(131,92,252,0.22), transparent 70%),
    var(--ink-2);
  border-color: rgba(131,92,252,0.32);
}
.bento-cell.is-electric .cell-label,
.bento-cell.is-electric .stat-num em { color: var(--electric); }

/* Stickers — small editorial badges (use mostly purple; vivid only when sparking) */
.sticker {
  display: inline-flex; align-items: center; justify-content: center;
  padding: 10px 14px;
  font-family: var(--mono); font-size: 10px;
  text-transform: uppercase; letter-spacing: 0.18em;
  color: var(--paper);
  background: var(--purple);
  border-radius: 999px;
  white-space: nowrap;
  box-shadow: 0 0 18px rgba(151,71,255,0.35);
}
.sticker.is-lime    { background: var(--lime);    color: var(--ink); box-shadow: 0 0 18px rgba(200,230,0,0.45); }
.sticker.is-magenta { background: var(--magenta); box-shadow: 0 0 18px rgba(255,61,135,0.45); }
.sticker.is-orange  { background: var(--orange);  color: var(--ink); box-shadow: 0 0 18px rgba(255,140,46,0.45); }
.sticker.is-paper   { background: var(--paper);   color: var(--ink); box-shadow: 0 0 14px color-mix(in srgb, var(--paper) 18%, transparent); }
.sticker.is-disc {
  width: 80px; height: 80px;
  border-radius: 999px;
  flex-direction: column;
  text-align: center;
  font-size: 9px;
  line-height: 1.2;
  padding: 12px;
}
.sticker.is-rotate { transform: rotate(-6deg); }

/* ────────────────────────────────────────────────────────────────
   LIGHT THEME — surface overrides
   ────────────────────────────────────────────────────────────────
   The token block at the top of this file flips ink/paper/muted/rule
   automatically. The rules below override the components that bake in
   hardcoded dark hex values — placeholder stripes, gradient washes,
   diagram fills, etc. — so they read correctly on cream.
*/
[data-theme="light"] .cs-hero-visual {
  background:
    radial-gradient(60% 70% at 50% 50%, rgba(122,60,224,0.10), transparent 70%),
    repeating-linear-gradient(135deg,
      rgba(28,28,28,0.05) 0 14px,
      transparent 14px 28px),
    var(--ink-2);
  border-color: var(--rule-2);
}
[data-theme="light"] .frame-img {
  background:
    repeating-linear-gradient(135deg,
      rgba(28,28,28,0.05) 0 14px,
      transparent 14px 28px),
    var(--ink-2);
  border-color: var(--rule-2);
}
[data-theme="light"] .frame-img.tinted {
  background:
    radial-gradient(60% 70% at 50% 50%, rgba(122,60,224,0.10), transparent 70%),
    repeating-linear-gradient(135deg,
      rgba(28,28,28,0.05) 0 14px,
      transparent 14px 28px),
    var(--ink-2);
}
[data-theme="light"] .frame-img > img,
[data-theme="light"] .cs-hero-visual > img { /* unchanged — image fills slot */ }
[data-theme="light"] .mock-browser .mb-embed {
  background:
    radial-gradient(60% 70% at 50% 50%, rgba(122,60,224,0.08), transparent 70%),
    var(--ink-2);
}
[data-theme="light"] .frame-diagram {
  background:
    radial-gradient(60% 80% at 50% 40%, rgba(122,60,224,0.06), transparent 70%),
    var(--ink-2);
  border-color: var(--rule-2);
}
[data-theme="light"] .dg-node {
  background: rgba(122,60,224,0.04);
}
[data-theme="light"] .frame-bento .bento-cell {
  background: var(--ink-2);
  border-color: var(--rule-2);
}
[data-theme="light"] .frame-bento .bento-cell.is-purple {
  background:
    radial-gradient(60% 90% at 30% 50%, rgba(122,60,224,0.14), transparent 70%),
    var(--ink-2);
  border-color: rgba(122,60,224,0.30);
}
[data-theme="light"] .frame-bento .bento-cell.is-paper {
  background: var(--paper);
  color: var(--ink);
  border-color: var(--rule-2);
}
[data-theme="light"] .frame-bento .bento-cell.is-paper .cell-label { color: var(--muted); }
[data-theme="light"] .frame-bento .bento-cell.is-paper .stat-num { color: var(--paper-2); }
[data-theme="light"] .frame-bento .bento-cell.is-paper .stat-lab b,
[data-theme="light"] .frame-bento .bento-cell.is-paper h4 { color: var(--paper-2); }
[data-theme="light"] .frame-bento .bento-cell.is-photo {
  background:
    radial-gradient(60% 80% at 50% 50%, rgba(122,60,224,0.06), transparent 70%),
    repeating-linear-gradient(135deg, rgba(28,28,28,0.05) 0 14px, transparent 14px 28px),
    var(--ink-2);
}
[data-theme="light"] .frame-bento .bento-cell.is-photo .center-tag {
  color: rgba(28,28,28,0.42);
}
[data-theme="light"] .frame-numbers .num-cell .lab b { color: var(--paper-2); }
[data-theme="light"] .frame-audio {
  background:
    radial-gradient(50% 80% at 30% 50%, rgba(122,60,224,0.08), transparent 70%),
    var(--ink-2);
  border-color: var(--rule-2);
}
[data-theme="light"] .audio-play { background: var(--purple); color: var(--ink); }
[data-theme="light"] .audio-play:hover { box-shadow: 0 0 32px rgba(122,60,224,0.4); }
[data-theme="light"] .frame-code {
  background: var(--ink-2);
  border-color: var(--rule-2);
}
[data-theme="light"] .frame-code pre { color: var(--paper); }
/* cs-next v3: the card thumb is a fixed dark well in both themes (matches the home
   grid), so no light-mode background override is needed. Title/client flip via tokens. */
[data-theme="light"] .gate {
  background: var(--ink-2);
  border-color: var(--rule-2);
}
/* The gate field. --ink / --paper swap roles between themes (in light,
   --ink is the page and --paper is the text), so the field reads as a
   slightly brighter well than the .gate panel behind it, with dark text.
   Selector matches .gate-form input's specificity chain, not just .gate. */
[data-theme="light"] .gate input,
[data-theme="light"] .gate-form input {
  background: var(--ink);
  color: var(--paper);
  border-color: var(--rule-2);
}
/* Purple is dark in both themes, so the label on it stays light either way.
   The base rule uses --paper, which inverts to near-black in light mode. */
[data-theme="light"] .gate-form button { color: var(--ink); }
[data-theme="light"] .gate-form button:hover { color: var(--ink); }
[data-theme="light"] .frame-img .center-tag,
[data-theme="light"] .cs-hero-visual .center-tag {
  color: rgba(28,28,28,0.42);
}
[data-theme="light"] .nav { background: rgba(250,250,250,0.72); backdrop-filter: blur(12px); }
[data-theme="light"] .cs-eyebrow .dot,
[data-theme="light"] .cs-eyebrow .dot { background: var(--purple); box-shadow: 0 0 16px rgba(122,60,224,0.4); }


/* ── Scroll reveal — one subtle system, site-wide.
   theme.js sets .js-reveal on <html> (skipped when prefers-reduced-motion),
   then flags elements .in-view as they enter the viewport. Content stays
   fully visible when JS is absent. ── */
/* NOTE: the animation uses the standalone `translate` property, NOT
   `transform` — .cs-insert / .cs-chapter / .cs-pair rely on
   transform: translateX(-50%) for their breakout centering, and a
   transform-based reveal would clobber it (content shifts half-width
   right). `translate` composes with `transform` independently. */
html.js-reveal .cs-beat,
html.js-reveal .cs-insert,
html.js-reveal .cs-chapter,
html.js-reveal .cs-pair,
html.js-reveal .cs-results {
  opacity: 0;
  translate: 0 var(--rise);
  transition: opacity var(--t-reveal) var(--ease-soft),
              translate var(--t-reveal) var(--ease);
}
html.js-reveal .cs-beat.in-view,
html.js-reveal .cs-insert.in-view,
html.js-reveal .cs-chapter.in-view,
html.js-reveal .cs-pair.in-view,
html.js-reveal .cs-results.in-view {
  opacity: 1;
  translate: 0 0;
}

/* ── Sequential cascade inside a group ──────────────────────────────
   A results strip or an image grid shouldn't arrive as one slab. JS tags
   each child with --i, and the delay compounds one --t-stagger step per
   item, so the group resolves left-to-right / top-to-bottom. The parent
   stays a single observer target, so the sequence always plays in order
   regardless of how fast the visitor scrolls. */
html.js-reveal .cs-results > .r-cell,
html.js-reveal .cs-insert .frame-grid > *,
html.js-reveal .cs-insert .frame-portraits > *,
html.js-reveal .cs-insert .frame-bento > .bento-cell,
html.js-reveal .cs-insert .frame-timeline > .tl-step,
html.js-reveal .cs-insert .frame-diagram .dg-node {
  opacity: 0;
  translate: 0 calc(var(--rise) * 0.55);
  transition: opacity var(--t-reveal) var(--ease-soft),
              translate var(--t-reveal) var(--ease);
  transition-delay: calc(var(--i, 0) * var(--t-stagger));
}
html.js-reveal .cs-results.in-view > .r-cell,
html.js-reveal .cs-insert.in-view .frame-grid > *,
html.js-reveal .cs-insert.in-view .frame-portraits > *,
html.js-reveal .cs-insert.in-view .frame-bento > .bento-cell,
html.js-reveal .cs-insert.in-view .frame-timeline > .tl-step,
html.js-reveal .cs-insert.in-view .frame-diagram .dg-node {
  opacity: 1;
  translate: 0 0;
}

/* ── Page transitions ────────────────────────────────────────────────
   Two mechanisms, one look, no double-animation:

   1. Browsers with cross-document View Transitions get the native path.
      The old page cross-fades into the new one, handled by the compositor.
   2. Everywhere else, a CSS fade-in on arrival plus a JS-driven fade-out
      on internal links (see theme.js) approximates the same thing.

   The fade-in is CSS-only and unconditional, so a page never sits blank
   waiting on JS. html.is-leaving is what theme.js sets to fade out. */
@view-transition { navigation: auto; }

::view-transition-old(root) {
  animation: pageOut var(--t-base) var(--ease-soft) both;
}
::view-transition-new(root) {
  animation: pageIn var(--t-reveal) var(--ease-soft) both;
}
@keyframes pageOut { to   { opacity: 0; } }
@keyframes pageIn  { from { opacity: 0; } }

/* Fallback path. Only runs where View Transitions are unavailable —
   theme.js sets html.vt-fallback so the two never stack. */
@media (prefers-reduced-motion: no-preference) {
  html.vt-fallback body {
    animation: pageIn var(--t-base) var(--ease-soft) both;
  }
  html.vt-fallback.is-leaving body {
    opacity: 0;
    transition: opacity var(--t-quick) var(--ease-soft);
  }
}

/* ── Case-study hero entrance ────────────────────────────────────────
   Plays once on load, in reading order: eyebrow → headline → meta rows →
   visual. Uses animation (not a transition) because there's no state
   change to hang a transition on, and `both` fill so nothing flashes at
   full opacity before its turn.

   Gated on a pure-CSS media query, NOT on a JS-applied class. The class
   would be added by a script at the end of <body>, so the hero would paint
   at full opacity and then snap back to invisible to animate — a flash.
   In the stylesheet, this rule is live before first paint.

   .cs-results is deliberately excluded: it's already a scroll-reveal
   target and staggers its own cells. */
@keyframes heroIn {
  from { opacity: 0; translate: 0 var(--rise); }
  to   { opacity: 1; translate: 0 0; }
}
@media (prefers-reduced-motion: no-preference) {
  .cs-hero .cs-eyebrow,
  .cs-hero .cs-h1,
  .cs-hero .cs-meta-strip .row,
  .cs-hero .cs-hero-visual {
    animation: heroIn var(--t-reveal) var(--ease) both;
  }
  .cs-hero .cs-eyebrow            { animation-delay: 0ms; }
  .cs-hero .cs-h1                 { animation-delay: calc(1 * var(--t-stagger)); }
  .cs-hero .cs-meta-strip .row    { animation-delay: calc(2 * var(--t-stagger)); }
  /* Meta rows cascade among themselves, continuing the same rhythm. */
  .cs-hero .cs-meta-strip .row:nth-child(2) { animation-delay: calc(2.4 * var(--t-stagger)); }
  .cs-hero .cs-meta-strip .row:nth-child(3) { animation-delay: calc(2.8 * var(--t-stagger)); }
  .cs-hero .cs-meta-strip .row:nth-child(4) { animation-delay: calc(3.2 * var(--t-stagger)); }
  .cs-hero .cs-meta-strip .row:nth-child(5) { animation-delay: calc(3.6 * var(--t-stagger)); }
  .cs-hero .cs-meta-strip .row:nth-child(6) { animation-delay: calc(4 * var(--t-stagger)); }
  /* The visual lands with the headline, not after the whole meta list — it's
     the co-lead of the composition, not a footnote. */
  .cs-hero .cs-hero-visual        { animation-delay: calc(1.5 * var(--t-stagger)); }
}

/* ── Keyboard focus — one visible ring everywhere. ── */
:focus-visible {
  outline: 2px solid var(--purple);
  outline-offset: 3px;
  border-radius: 2px;
}

/* ── Meta-text reduction — when an insert carries a caption, the mono
   label above it is redundant. Signature moments keep theirs (the accent
   label is part of the treatment). ── */
.cs-insert:not(.cs-signature):has(> .cs-insert-caption) > .cs-insert-label {
  display: none;
}
