/* ───────────────── tokens ───────────────── */
:root {
  --ink:        #16191e;      /* cool near-black */
  --ink-dim:    #3f4651;
  --ink-faint:  #6c7585;
  --bg:         #eef0e7;      /* cool paper, off-white with green-grey lift */
  --bg-raise:   #e2e5db;      /* card */
  --bg-deep:    #d6dbcf;      /* deepest tier */
  --amber:      #8a5a14;      /* deeper gold that holds on cool paper */
  --amber-soft: #b07a1f;
  --red:        #8e2c25;
  --rule:       #b7bdac;      /* lifted contrast vs bg */
  --rule-soft:  #c8cdba;

  --turn-warm:  #c25a18;      /* peak — single warm */
  --turn-cool:  #2b4a6f;      /* trough — single cool */

  --serif-display: "Fraunces", "Source Serif 4", Georgia, serif;
  --serif-body:    "Source Serif 4", Georgia, serif;
  --sans:          "Inter", system-ui, -apple-system, sans-serif;
  --mono:          "JetBrains Mono", ui-monospace, monospace;

  --max: 1280px;
  --gutter: clamp(1.25rem, 4vw, 3rem);
}

* { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
  background: var(--bg);
  color: var(--ink);
  font-family: var(--serif-body);
  font-size: 18px;
  line-height: 1.55;
  font-feature-settings: "kern", "liga";
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}
em { font-style: italic; color: var(--amber-soft); font-weight: 500; }
/* Bold for inline UI instructions ("hover X", "click Y", "toggle Z").
   Source Serif 4 supports a full weight axis — 700 reads as a clear
   directive without shouting. Inherits color from surrounding copy. */
strong { font-weight: 700; color: inherit; }
.num, .mono { font-family: var(--mono); font-variant-numeric: tabular-nums; color: var(--amber); }

h1, h2, h3, h4 {
  font-family: var(--serif-display);
  font-weight: 400;
  font-variation-settings: "opsz" 120, "SOFT" 30;
  letter-spacing: -0.015em;
  line-height: 1.02;
  margin: 0 0 0.5em;
}
h2 { font-size: clamp(2.2rem, 5vw, 4rem); }
h3 { font-size: clamp(1.4rem, 2.4vw, 2rem); font-variation-settings: "opsz" 48, "SOFT" 20; }
h4 { font-size: 1.05rem; font-variation-settings: "opsz" 24, "SOFT" 30; margin-bottom: 0.4em; }
p  { margin: 0 0 1em; max-width: 38ch; color: var(--ink); }

.eyebrow {
  font-family: var(--sans);
  text-transform: uppercase;
  letter-spacing: 0.22em;
  font-size: 0.72rem;
  color: var(--ink-dim);
  margin-bottom: 2rem;
}
/* Single-line variant for the hero passion-project credit. */
.eyebrow-line { white-space: nowrap; }

/* Section title reveal: only the italicized / .scramble words animate (via
   ScrambleText). The rest of the title is plain text, no animation, so we
   don't fight italic letterforms with a letter-rise.
   Hide .scramble until JS adds is-revealed so the user never sees the final
   text before the scramble plays. */
.scramble { opacity: 0; }
.scramble.is-revealed { opacity: 1; }
/* Italic glyphs occasionally overshoot their advance width — pad the box on
   inline scrambles so a "loose" character can't escape into the margin. */
#intro-prelude-title { padding-right: 0.05em; }

/* Hero entrance: subtle fade-up on eyebrow / title / subtitle. Hidden via
   CSS-first so there's no flash before GSAP takes over. .no-gsap (set by JS
   when GSAP fails to load) restores them so the page is still readable. */
.hero .eyebrow,
.hero-title,
.hero-sub {
  opacity: 0;
  transform: translateY(16px);
  will-change: opacity, transform;
}
/* Lift the eyebrow above the title in .hero-inner's stacking context so the
   .bio popover (a positioned descendant of the eyebrow) can paint over the
   title. Without this, will-change on .hero .eyebrow creates a stacking
   context that traps .bio's z-index inside the eyebrow, and the title —
   another stacking context at z-index: auto, later in source order — paints
   on top of anything coming out of the eyebrow. */
.hero .eyebrow {
  position: relative;
  z-index: 2;
}
.no-gsap .hero .eyebrow,
.no-gsap .hero-title,
.no-gsap .hero-sub {
  opacity: 1;
  transform: none;
}

/* ───────────────── hero ───────────────── */
.hero {
  min-height: 100vh;
  position: relative;
  display: flex;
  align-items: center;
  padding: 0 var(--gutter) 8rem;          /* 8rem bottom mirrors era's pattern */
  overflow: hidden;
}
.hero-arc {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0.5;
  z-index: 0;
  pointer-events: none;
}
.hero-inner {
  position: relative;
  z-index: 1;
  max-width: var(--max);
  margin: 0 auto;
  width: 100%;
}
.hero-title {
  font-family: var(--serif-display);
  font-variation-settings: "opsz" 144, "SOFT" 20;
  font-weight: 300;
  font-size: clamp(4rem, 12vw, 10rem);
  line-height: 0.9;
  letter-spacing: -0.035em;
  margin: 0 0 1.5rem;
}
.hero-title em {
  font-style: italic;
  color: var(--amber);
  font-variation-settings: "opsz" 144, "SOFT" 80;
}
.hero-sub {
  font-family: var(--serif-body);
  font-size: clamp(1.1rem, 1.6vw, 1.45rem);
  color: var(--ink-dim);
  max-width: 46ch;
  line-height: 1.5;
}
/* Bottom-center thin chevron. Hidden on initial paint; JS fades it in
   AFTER the hero entrance animation finishes (so it doesn't compete with
   the title for attention). The CSS animation drives only the transform —
   opacity stays JS-controlled so fade-in / dismiss don't fight the keyframe. */
.scroll-hint {
  position: absolute;
  bottom: 2.4rem;
  left: 50%;
  width: 28px;
  height: 16px;
  color: var(--ink-faint);
  z-index: 2;
  pointer-events: none;
  user-select: none;
  opacity: 0;
  transform: translate(-50%, 0);
  animation: scroll-nudge 2.2s ease-in-out infinite;
  transition: opacity 420ms ease;
}
.scroll-hint svg {
  width: 100%;
  height: 100%;
  display: block;
}
.scroll-hint.is-revealed { opacity: 0.7; }
.scroll-hint.is-dismissed { opacity: 0; animation: none; }
@keyframes scroll-nudge {
  0%, 100% { transform: translate(-50%, 0); }
  50%      { transform: translate(-50%, 6px); }
}
@media (prefers-reduced-motion: reduce) {
  .scroll-hint { animation: none; }
}
.hero-film {
  position: absolute;
  bottom: 2rem;
  right: var(--gutter);
  z-index: 1;
  margin: 0;
  text-align: right;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 0.35rem;
  opacity: 0;        /* fades in as the hero arc nears completion */
}
.no-gsap .hero-film { opacity: 1; }

/* Reshuffle button — sits under the warm-up intermission's body copy. */
.warmup-shuffle {
  background: transparent;
  border: 0;
  margin: 1.6rem 0 0;
  padding: 0.4rem 0;
  font-family: var(--sans);
  font-size: 0.72rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-faint);
  cursor: pointer;
  align-self: flex-start;
  transition: color 200ms;
}
.warmup-shuffle:hover { color: var(--amber); }
.warmup-shuffle span { margin-right: 0.4em; }
.hero-film-key {
  font-family: var(--sans);
  font-size: 0.7rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-faint);
}
.hero-film-val {
  font-family: var(--serif-display);
  font-variation-settings: "opsz" 48, "SOFT" 30;
  font-size: 1.1rem;
  line-height: 1.2;
  color: var(--ink);
  white-space: nowrap;
}
.hero-film-val em { color: var(--amber); font-style: italic; }

/* ───────────────── vonnegut intro ───────────────── */
/* Vonnegut: two separate full-page sections. Subsection 1 has image+text
   (one viewport tall) so the user reads it without seeing subsection 2.
   Subsection 2 is text-only, full-width, fades in on scroll. */

/* Shared block typography — both subsections use the same vibe (matches the
   rest of the site: light weight, larger optical size, tighter tracking).
   This was previously different between the two; now they're unified. */
.vonnegut-1 .lede,
.vonnegut-2 .lede {
  font-family: var(--serif-display);
  font-variation-settings: "opsz" 96, "SOFT" 25;
  font-weight: 300;
  letter-spacing: -0.01em;
  line-height: 1.4;
  color: var(--ink);
  margin: 0 0 2rem;
}
.vonnegut-1 p,
.vonnegut-2 p {
  font-size: 1.18rem;
  line-height: 1.65;
  color: var(--ink-dim);
  margin: 0 0 1.4rem;
}
.vonnegut-1 a,
.vonnegut-2 a {
  color: var(--amber);
  text-decoration: none;
  border-bottom: 1px solid var(--amber-soft);
  transition: color 200ms, border-color 200ms;
}
.vonnegut-1 a:hover,
.vonnegut-2 a:hover { color: var(--amber-soft); border-bottom-color: var(--amber); }

/* Subsection 1 — image + text. Mirrors the intermission spacing pattern
   (100vh, content vertically centered, no vertical padding) so the gap from
   the hero matches the gap between era and the essay-prelude intermission. */
.vonnegut-1 {
  min-height: 100vh;
  max-width: 1640px;
  margin: 0 auto;
  padding: 0 clamp(0.75rem, 1vw, 1rem);  /* very tight side margins so the row has every pixel */
  display: flex;
  flex-direction: column;
  justify-content: center;
}
/* Content-sized columns centered as a single unit. align-items: center so
   the image's vertical center sits on the same horizontal line as the text's
   vertical center — image hits the middle of the text block, not the top. */
.von-row {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: clamp(3rem, 5vw, 5rem);
  width: 100%;
  flex-wrap: nowrap;
}
.von-portrait {
  margin: 0;
  flex: 0 1 440px;       /* content-sized; can shrink, doesn't grow */
  max-width: 440px;
}
.von-media {
  position: relative;
  width: 100%;
  cursor: pointer;
}
.von-portrait img {
  display: block;
  width: 100%;
  height: auto;          /* native aspect ratio, no crop */
  background: var(--bg-raise);
  filter: grayscale(0.15) contrast(1.02);
}
.von-media .von-video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  opacity: 0;
  pointer-events: none;
  transition: opacity 320ms ease-out;
}
.von-media.is-playing .von-video { opacity: 1; }
.von-portrait img.missing {
  aspect-ratio: 4 / 3;
  background: repeating-linear-gradient(45deg, var(--bg-raise), var(--bg-raise) 6px, var(--bg-deep) 6px, var(--bg-deep) 12px);
}
/* Caption inherits figure width so it tracks the image edge-to-edge. */
.von-portrait figcaption {
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  line-height: 1.55;
  margin-top: 0.8rem;
  max-width: none;
}
/* Wider text column lets the lede break to 3 lines (instead of 4) without
   shrinking the lede font. Lede max-width is in `ch` measured at the lede's
   own font-size, so it caps roughly to the column edge. */
.vonnegut-1 .von-copy { flex: 0 1 72ch; max-width: 72ch; }
.vonnegut-1 .lede { font-size: clamp(1.9rem, 3.2vw, 2.7rem); max-width: 42ch; }
.vonnegut-1 p { max-width: 62ch; }

/* Subsection 2 — full-page, text-only. Aligned to the same canvas as the
   intermissions (`--max` width, `--gutter` padding) so the lede sits at the
   same indentation as the editorial intermission copy elsewhere on the page. */
.vonnegut-2 {
  min-height: 100vh;
  max-width: var(--max);
  margin: 0 auto;
  padding: 0 var(--gutter);
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.vonnegut-2 .von-copy { width: 100%; }
.vonnegut-2 .lede {
  font-size: clamp(1.9rem, 3.2vw, 2.7rem);  /* match vonnegut-1 */
  max-width: 46ch;       /* wide enough to break the lede into two sentences */
  margin-bottom: 2.2rem;
}
.vonnegut-2 p {
  max-width: 80ch;
  margin-bottom: 1.4rem;
}

/* ───────────────── intro pin (full-bleed canvas with floating beats) ───────────────── */
/* The stage is pinned by ScrollTrigger (not CSS sticky) so it cooperates with
   ScrollSmoother. Section height is added by ScrollTrigger's pinSpacing.
   Beats are absolutely positioned — they float in DIFFERENT corners per beat
   so the chart doesn't read as left-half-graph / right-half-text. */
.intro-pin {
  position: relative;
  width: 100%;
  background: var(--bg);
}
.intro-stage {
  height: 100vh;
  width: 100%;
  display: block;
  overflow: hidden;
}
.intro-stage .intro-svg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}
.intro-beat {
  position: absolute;
  max-width: 42ch;
  pointer-events: none;
  opacity: 0;
}
.intro-beat h3 {
  font-size: clamp(1.6rem, 3vw, 2.4rem);
  margin: 0 0 0.6rem;
  font-variation-settings: "opsz" 96, "SOFT" 25;
  font-weight: 300;
}
.intro-beat p {
  font-size: 1.02rem;
  color: var(--ink-dim);
  line-height: 1.5;
  /* Avoid orphan widows on the body line ("lows" sitting alone). */
  text-wrap: pretty;
}
/* The instruction line ("Keep scrolling …") must always render as one line
   so the call-to-action reads cleanly — never wrapped, never orphaned. */
.intro-beat p strong { white-space: nowrap; }
.intro-beat .hover-hint {
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-top: 0.6rem;
}
/* Both beats anchored to the same column (left), with vertical placement
   driven by JS (placeIntroBeats) — JS swaps top↔bottom per beat based on
   where the line actually is in that beat's horizontal range. */
.intro-beat[data-beat="0"] { left: var(--gutter); top: 18vh; }
.intro-beat[data-beat="1"] { left: var(--gutter); top: 18vh; }

.intro-beat[data-beat="2"] { left: var(--gutter); bottom: 12vh; }

/* Editorial progress bar — sits below the chart. Hairline rail, amber fill
   traces left→right, small diamond marker rides the leading edge. No labels
   — the bar reads as a quiet story-runner, not a UI control. */
.intro-progress {
  position: absolute;
  left: var(--gutter);
  right: var(--gutter);
  bottom: 1.6rem;
  height: 18px;
  pointer-events: none;
  z-index: 3;
  display: flex;
  align-items: center;
}
.intro-progress-rail {
  position: relative;
  flex: 1 1 auto;
  height: 18px;
  display: flex;
  align-items: center;
}
.intro-progress-rail::before {
  content: "";
  position: absolute;
  left: 0; right: 0;
  height: 1px;
  background: var(--rule-soft);
}
.intro-progress-fill {
  position: absolute;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--amber);
  /* GPU-composited scaleX (no layout/paint per frame). The scrub itself
     smooths the underlying progress; an extra CSS transition on top would
     fight it and read as lag. */
  transform: scaleX(0);
  transform-origin: left center;
  will-change: transform;
}
.intro-progress-marker {
  position: absolute;
  left: 0;
  width: 8px;
  height: 8px;
  background: var(--amber);
  transform: translate(-50%, 0) rotate(45deg);
  will-change: left;
}

/* ───────────────── intermission ───────────────── */
.intermission {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: flex-start;
  padding: 0 var(--gutter);
  max-width: var(--max);
  margin: 0 auto;
}
.intermission-eyebrow {
  font-family: var(--mono);
  text-transform: uppercase;
  letter-spacing: 0.3em;
  font-size: 0.72rem;
  color: var(--ink-faint);
  margin-bottom: 1.5rem;
}
.intermission-title {
  font-size: clamp(2.8rem, 7vw, 6rem);
  line-height: 1;
  max-width: 18ch;
  margin: 0 0 2rem;
  font-variation-settings: "opsz" 144, "SOFT" 25;
  font-weight: 300;
}
.intermission-title em {
  font-style: italic;
  color: var(--amber);
}
.intermission-sub {
  font-size: 1.2rem;
  color: var(--ink-dim);
  max-width: 52ch;
  line-height: 1.5;
}
/* Longer intermission sub paragraphs (archetypes, archpin-prelude, and the
   "across time" weights prelude) widen to match vonnegut-2's body width so
   they don't read as narrow columns. */
.intermission[data-intermission="archetypes"] .intermission-sub,
.intermission[data-intermission="archpin-prelude"] .intermission-sub,
.intermission[data-intermission="weights-prelude"] .intermission-sub {
  max-width: 80ch;
}
/* Warm-up sub — short sentence that should fit on 2 lines, slightly wider
   than the 80ch group so the sentiment-model line doesn't break early. */
.intermission[data-intermission="intro-prelude"] .intermission-sub {
  max-width: 100ch;
}


/* Inline links inside intermission body copy — match the Vonnegut "on tape"
   treatment: amber text, underlined with the soft amber, brightens on hover. */
.intermission-sub a,
.cluster-sub a {
  color: var(--amber);
  text-decoration: none;
  border-bottom: 1px solid var(--amber-soft);
  transition: color 200ms, border-color 200ms;
}
.intermission-sub a:hover,
.cluster-sub a:hover {
  color: var(--amber-soft);
  border-bottom-color: var(--amber);
}

/* ───────────────── cluster overview (all 6 at once) ───────────────── */
/* Single column, tight stack: eyebrow → title → sub (with inline CTA) → board.
   No more orphan headlines floating away from the chart. */
.cluster-overview {
  position: relative;
  padding: 4rem var(--gutter) 2rem;
  max-width: var(--max);
  margin: 0 auto;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
}
.cluster-overview .eyebrow { margin-bottom: 0.8rem; }
.cluster-title {
  font-size: clamp(2.4rem, 4.6vw, 4rem);
  font-weight: 300;
  font-variation-settings: "opsz" 144, "SOFT" 25;
  line-height: 0.95;
  margin: 0 0 1rem;
  max-width: 16ch;
}
.cluster-title em { color: var(--amber); font-style: italic; }
.cluster-sub {
  max-width: 120ch;
  font-size: 1.05rem;
  color: var(--ink-dim);
  line-height: 1.5;
  margin-bottom: 1.4rem;
  /* Wide enough to fit in ≤3 lines, balanced so the last line never
     orphans (e.g. "their name." sitting on its own line). */
  text-wrap: balance;
}
.cluster-cta-inline {
  display: inline;
  font-family: var(--mono);
  font-size: 0.74rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-left: 0.6rem;
}
.cluster-board {
  width: 100%;
  flex: 1 1 auto;
  min-height: 360px;
  background: var(--bg);
  position: relative;
}
.cluster-board svg { width: 100%; height: 100%; display: block; }

.cluster-grid {
  position: absolute;
  inset: 0;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  grid-template-rows: repeat(2, 1fr);
  gap: 1px;
  background: var(--rule-soft);
  border: 1px solid var(--rule-soft);
}
.flip-card {
  position: relative;
  cursor: pointer;
  perspective: 1400px;
  background: var(--bg);
  overflow: hidden;
}
.flip-card-inner {
  position: relative;
  width: 100%;
  height: 100%;
  transform-style: preserve-3d;
  transition: transform 0.75s cubic-bezier(0.5, 0.05, 0.2, 1);
  will-change: transform;
}
.flip-card.is-flipped .flip-card-inner { transform: rotateY(180deg); }
.flip-front, .flip-back {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
  overflow: hidden;
}
.flip-front svg { width: 100%; height: 100%; display: block; }
.flip-back {
  transform: rotateY(180deg);
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 2.4rem 2.2rem;
  color: rgba(255, 255, 255, 0.96);
}
.flip-back .arch-icon {
  position: absolute;
  top: 1.6rem;
  right: 1.6rem;
  width: clamp(52px, 6vw, 80px);
  height: auto;
  /* Hand-drawn pen lines on a colored card — invert so the ink renders
     as paper-color over the archetype's tile. */
  filter: brightness(0) invert(1);
  opacity: 0.92;
  pointer-events: none;
}
.flip-back .arch-name {
  font-family: "Fraunces", serif;
  font-size: clamp(1.6rem, 2.4vw, 2.4rem);
  font-weight: 400;
  font-variation-settings: "opsz" 96, "SOFT" 30;
  line-height: 1;
  margin: 0 0 1rem;
  color: #fff;
}
.flip-back .arch-blurb {
  font-size: 0.95rem;
  line-height: 1.55;
  max-width: 38ch;
  color: rgba(255, 255, 255, 0.88);
  margin: 0;
}

/* ───────────────── archetypes pin (one big morphing chart) ───────────────── */
/* Pinned by ScrollTrigger. Section length added by ScrollTrigger pinSpacing —
   six segments × 100vh of scroll. */
.archetypes-pin {
  position: relative;
  width: 100%;
  background: var(--bg);
}
.pin-stage {
  height: 100vh;
  width: 100%;
  display: grid;
  grid-template-columns: 1fr;
  align-items: stretch;
  overflow: hidden;
}
.pin-stage .pin-svg {
  grid-column: 1;
  grid-row: 1;
  width: 100%;
  height: 100%;
  display: block;
}
.pin-stage .pin-meta {
  grid-column: 1;
  grid-row: 1;
  align-self: end;
  justify-self: start;
  padding: 4rem var(--gutter) 4vh;
  max-width: 56ch;
  pointer-events: none;
  position: relative;
  width: 100%;
  /* Soft fade-to-bg so the caption stays readable over the chart that now
     fills the full viewport. Sits behind the text, not over the chart edges. */
  background: linear-gradient(to top, rgba(238,240,231,0.88) 0%, rgba(238,240,231,0.7) 55%, rgba(238,240,231,0) 100%);
}
/* Shape index (X of 6) lifted to the top-left corner — sits where POSITIVE
   used to be on the left axis, freeing the lower-left text block from
   overlap with the arc. Style mirrors the original .arch-progress treatment
   inside .pin-meta. */
.pin-stage .arch-progress.pin-progress-corner {
  position: absolute;
  top: 2.2rem;
  left: var(--gutter);
  margin: 0;
  z-index: 2;
  pointer-events: none;
}
/* Hover tooltip mirrors the corner count: same top inset, same gutter on the
   opposite side. Mono uppercase tracking matches the count's typographic
   treatment so the two corners read as a pair. */
.pin-stage .pin-tip-corner {
  position: absolute;
  top: 2.2rem;
  right: var(--gutter);
  margin: 0;
  z-index: 2;
  pointer-events: none;
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-dim);
  text-align: right;
  /* Cap to viewport width minus the two gutters so long titles can't run
     off-screen to the left. Stays on one line when it fits; wraps right-
     aligned only for the rare extra-long combination. */
  max-width: calc(100vw - 2 * var(--gutter));
}
.pin-stage .pin-tip-corner[hidden] { display: none; }
.pin-stage .pin-tip-corner .pin-tip-title,
.pin-stage .pin-tip-corner .pin-tip-match { display: block; }
/* Title line — Fraunces in italic for the film name only; the year stays
   upright. Override the mono uppercase + tracking that the corner element
   sets. */
.pin-stage .pin-tip-corner .pin-tip-title {
  font-family: var(--serif-display);
  font-weight: 400;
  font-size: 1.05rem;
  font-variation-settings: "opsz" 24, "SOFT" 30;
  letter-spacing: 0;
  text-transform: none;
}
.pin-stage .pin-tip-corner .pin-tip-name { font-style: italic; }
.pin-stage .pin-tip-corner .pin-tip-year { font-style: normal; }
.pin-stage .pin-tip-corner .pin-tip-match {
  margin-top: 0.35rem;
}
.pin-stage .pin-tip-corner .pin-tip-pct { font-weight: 700; }
.pin-stage .pin-tip-corner .pin-tip-tail { font-weight: 400; }
.pin-stage h3 {
  font-size: clamp(2.4rem, 6vw, 5rem);
  margin: 0 0 0.5rem;
  font-variation-settings: "opsz" 144, "SOFT" 25;
  font-weight: 300;
  letter-spacing: -0.02em;
}
.pin-stage .arch-shape {
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--amber);          /* default; JS overrides per-archetype */
  margin: 0 0 1.4rem;
  transition: color 380ms ease;
}
.pin-stage #pin-blurb {
  max-width: 60ch;
  font-size: 1.02rem;
  color: var(--ink);
  line-height: 1.5;
  margin-bottom: 1rem;
}
.pin-stage #pin-blurb em {
  color: var(--archetype-color, var(--ink));
  font-style: italic;
  cursor: pointer;
  /* .pin-meta sets pointer-events:none so hover passes through to the chart;
     re-enable specifically on the canonical-film names so they can light
     up the matching closest-15 line. */
  pointer-events: auto;
  transition: color 160ms ease, text-shadow 160ms ease;
}
.pin-stage #pin-blurb em.is-film-hover {
  text-shadow: 0 0 0.01em currentColor;
}
.pin-stage .arch-count {
  font-family: var(--sans);
  font-size: 0.86rem;
  color: var(--ink-faint);
  margin-bottom: 0.4rem;
}
.pin-stage .arch-progress {
  font-family: var(--mono);
  font-size: 0.78rem;
  color: var(--ink-faint);
  letter-spacing: 0.18em;
  text-transform: uppercase;
}
.pin-stage .arch-progress #pin-idx {
  font-size: 1.6rem;
  color: var(--ink);
  letter-spacing: 0;
}
.pin-stage .arch-progress .of { color: var(--ink-faint); }

/* ───────────────── weights over time (six shapes, five decades) ───────────────── */
/* Establishes the "shapes haven't moved" finding before the essay-prelude
   intermission. Six near-parallel lines, one per archetype, holding their
   band across forty years. */
.weights-time {
  position: relative;
  max-width: var(--max);
  margin: 0 auto;
  padding: 4rem var(--gutter) 2rem;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
}
.weights-time-header { flex: 0 0 auto; margin-bottom: 1.25rem; }
.weights-time .eyebrow { margin-bottom: 0.8rem; }
.weights-time-title {
  font-size: clamp(2.4rem, 4.6vw, 4rem);
  font-weight: 300;
  font-variation-settings: "opsz" 144, "SOFT" 25;
  line-height: 0.95;
  margin: 0 0 1rem;
}
.weights-time-title em { color: var(--amber); font-style: italic; }
.weights-time-sub {
  max-width: 105ch;
  font-size: 1.05rem;
  color: var(--ink-dim);
  line-height: 1.5;
  margin: 0 0 1.4rem;
}
.weights-time-chart {
  position: relative;
  width: 100%;
  flex: 1 1 auto;
  min-height: 360px;
}
.weights-time-svg { width: 100%; height: 100%; display: block; overflow: visible; }

/* Hover tooltip — mirrors the dialogue-density tooltip styling so the two
   read as the same family. Used both for archetype-name hover (renders the
   mini arc preview) and for cluster hover (rotates film titles). */
.weights-time-tooltip {
  position: absolute;
  top: 0;
  left: 0;
  background: var(--bg);
  border: 1px solid var(--rule);
  padding: 0.7rem 0.9rem;
  font-family: var(--sans);
  font-size: 0.84rem;
  color: var(--ink);
  pointer-events: none;
  z-index: 6;
  max-width: 290px;
  line-height: 1.45;
  opacity: 0;
  transition: opacity 120ms ease-out;
}
.weights-time-tooltip.is-visible { opacity: 1; }
.weights-time-tooltip strong {
  display: block;
  font-family: var(--sans);
  font-weight: 500;
  font-size: 0.92rem;
  color: var(--ink);
  margin-bottom: 0.2rem;
}
.weights-time-tooltip .stat {
  display: block;
  margin-top: 0.35rem;
  color: var(--ink-dim);
  font-size: 0.8rem;
}
.weights-time-tooltip .stat .num {
  color: var(--amber);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}
.weights-time-tooltip .titles {
  display: block;
  margin-top: 0.45rem;
  color: var(--ink-dim);
  font-size: 0.8rem;
  line-height: 1.5;
}
.weights-time-tooltip .titles em {
  color: var(--ink-dim);
  font-family: var(--serif-body);
  font-style: italic;
  font-size: 0.92rem;
  display: block;
}
.weights-time-tooltip svg.arc-preview {
  display: block;
  width: 150px;
  height: 80px;
}
.weights-time-tooltip .arc-shape {
  display: block;
  margin-top: 0.35rem;
  font-family: var(--mono);
  font-size: 0.72rem;
  letter-spacing: 0.14em;
  color: var(--ink-faint);
  text-transform: uppercase;
}

/* ───────────────── dialogue density (per-genre IQR + climb) ───────────────── */
/* First "why do films feel different?" reason. Header band on top, full-width
   chart below. The section locks to exactly one viewport and lets the chart
   shrink against the remaining budget rather than enforcing a tall min-height
   that would push the axis out of view. */
.dialogue-density {
  position: relative;
  max-width: var(--max);
  margin: 0 auto;
  padding: 2rem var(--gutter);
  min-height: 100vh;
  height: 100vh;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
}
.dialogue-density-header { flex: 0 0 auto; margin-bottom: 1.25rem; }
.dialogue-density .eyebrow { margin-bottom: 0.8rem; }
.dialogue-density-title {
  font-size: clamp(2.4rem, 4.6vw, 4rem);
  font-weight: 300;
  font-variation-settings: "opsz" 144, "SOFT" 25;
  line-height: 0.95;
  margin: 0 0 1rem;
}
.dialogue-density-title em { color: var(--amber); font-style: italic; }
.dialogue-density-sub {
  max-width: 105ch;
  font-size: 1.05rem;
  color: var(--ink-dim);
  line-height: 1.5;
  margin: 0 0 1.4rem;
}
/* Movie names in the body copy stay italic but inherit the surrounding
   ink-dim color — no amber accent, since they aren't an editorial em. */
.dialogue-density-sub em {
  color: inherit;
  font-weight: inherit;
}
.dialogue-density-chart {
  position: relative;
  width: 100%;
  flex: 1 1 0;          /* shrink/grow against the locked section height */
  min-height: 0;
  /* Use the available space — the section is locked to one viewport, so
     the chart should fill whatever's left after the header copy. */
  max-height: 56vh;
}
.dialogue-density-svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}
/* Hover tooltip — editorial styling, sans-serif body copy on a faint border. */
.dialogue-density-tooltip {
  position: absolute;
  top: 0;
  left: 0;
  background: var(--bg);
  border: 1px solid var(--rule);
  padding: 0.7rem 0.9rem;
  font-family: var(--sans);
  font-size: 0.84rem;
  color: var(--ink);
  pointer-events: none;
  z-index: 6;
  max-width: 260px;
  line-height: 1.45;
  opacity: 0;
  transition: opacity 120ms ease-out;
}
.dialogue-density-tooltip.is-visible { opacity: 1; }
.dialogue-density-tooltip strong {
  display: block;
  font-family: var(--sans);
  font-weight: 500;
  font-size: 0.92rem;
  color: var(--ink);
  margin-bottom: 0.2rem;
}
.dialogue-density-tooltip .stat {
  display: block;
  margin-top: 0.35rem;
  color: var(--ink-dim);
  font-size: 0.8rem;
}
.dialogue-density-tooltip .stat .num {
  color: var(--amber);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}
.dialogue-density-tooltip .delta {
  display: block;
  margin-top: 0.4rem;
  color: var(--ink-dim);
  font-size: 0.78rem;
  letter-spacing: 0.02em;
}

/* ───────────────── shape shift (second reason: barcode of films per archetype) ───────────────── */
/* Two horizontal tick strips share a year axis. Top strip: every R2R film
   plotted at its year as a gold vertical tick. Bottom strip: same for
   Icarus films in red. The visual story is the density shift across the
   year axis — gold is dense early and sparse late, red is the opposite. */
.shape-shift {
  position: relative;
  max-width: var(--max);
  margin: 0 auto;
  padding: 3rem var(--gutter) 2rem;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
}
.shape-shift-header { flex: 0 0 auto; margin-bottom: 0.4rem; }
.shape-shift .eyebrow { margin-bottom: 0.8rem; }
.shape-shift-title {
  font-size: clamp(2.4rem, 4.6vw, 4rem);
  font-weight: 300;
  font-variation-settings: "opsz" 144, "SOFT" 25;
  line-height: 0.95;
  margin: 0 0 1rem;
}
.shape-shift-title em { color: var(--amber); font-style: italic; }
.shape-shift-sub {
  max-width: 120ch;
  font-size: 1.05rem;
  color: var(--ink-dim);
  line-height: 1.5;
  margin: 0 0 1.4rem;
  /* Wide enough to fit in ≤3 lines, balanced so the last line never
     orphans (e.g. "don't recover." sitting on its own line). */
  text-wrap: balance;
}
.shape-shift-sub em {
  color: inherit;
  font-weight: inherit;
}
.shape-shift-chart {
  position: relative;
  width: 100%;
  flex: 1 1 auto;
  min-height: 360px;
  max-height: 70vh;
}
.shape-shift-svg {
  width: 100%;
  height: 100%;
  display: block;
  overflow: visible;
}
.shape-shift-tooltip {
  position: absolute;
  top: 0;
  left: 0;
  background: var(--bg);
  border: 1px solid var(--rule);
  padding: 0.7rem 0.9rem;
  font-family: var(--sans);
  font-size: 0.84rem;
  color: var(--ink);
  pointer-events: none;
  z-index: 6;
  max-width: 280px;
  line-height: 1.45;
  opacity: 0;
  transition: opacity 120ms ease-out;
}
.shape-shift-tooltip.is-visible { opacity: 1; }
.shape-shift-tooltip strong {
  display: block;
  font-family: var(--sans);
  font-weight: 500;
  font-size: 0.92rem;
  color: var(--ink);
  margin-bottom: 0.2rem;
}
.shape-shift-tooltip .shape {
  display: block;
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.18em;
  color: var(--ink-faint);
  text-transform: uppercase;
  margin-bottom: 0.45rem;
}
.shape-shift-tooltip .stat {
  display: block;
  margin-top: 0.35rem;
  color: var(--ink-dim);
  font-size: 0.8rem;
}
.shape-shift-tooltip .stat .num {
  color: var(--amber);
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
}
.shape-shift-tooltip .titles {
  display: block;
  margin-top: 0.55rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--rule);
}
.shape-shift-tooltip .titles em {
  display: block;
  color: var(--ink-dim);
  font-family: var(--serif-body);
  font-style: italic;
  font-size: 0.88rem;
  line-height: 1.45;
}
.shape-shift-tooltip .hint {
  display: block;
  margin-top: 0.55rem;
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--amber);
}

/* ───────────────── explorer ───────────────── */
.explorer {
  max-width: var(--max);
  margin: 0 auto;
  /* Generous top breathing room so the "your turn" handoff feels like a
     new beat after the second-reason chart, not a continuation. The
     bottom padding ensures the closing section's title doesn't kiss the
     explorer's chart on tall viewports. */
  padding: 7rem var(--gutter) 6rem;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
}
/* Two-column header band: copy on the left, search + shuffle on the right.
   Tightened gap and a wider right column so the search bar sits closer to
   the intro copy instead of being marooned at the page edge. Items align
   to the top of the row so the search bar lands at the same level as the
   h2 (offset by the eyebrow above it). */
.explorer-top {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(320px, 1fr);
  gap: 2rem;
  align-items: start;
  margin-bottom: 1.2rem;
}
.explorer-head { max-width: 64ch; }
.explorer-head .eyebrow { margin-bottom: 0.8rem; }
.explorer-head h2 {
  font-size: clamp(2.2rem, 4.6vw, 3.6rem);
  font-weight: 300;
  font-variation-settings: "opsz" 144, "SOFT" 25;
  line-height: 1;
  margin: 0 0 1rem;
  white-space: nowrap;       /* keep "Pick one. Watch its pulse." on one line */
}
.explorer-head h2 em { color: var(--amber); font-style: italic; }
.explorer-intro {
  max-width: 120ch;
  font-size: 0.95rem;
  color: var(--ink-dim);
  line-height: 1.5;
  margin-bottom: 0;
  /* Wide enough to fit in ≤3 lines, balanced so the last line doesn't
     orphan a tail of words. */
  text-wrap: balance;
}
/* Search input + shuffle live on a single row. Search takes all available
   width; shuffle sits flush right against it. The turn-toggle has been
   moved to the chart area so it lives next to the thing it controls. The
   margin-top centers the search bar vertically with the h2 — accounts
   for the eyebrow above the h2 plus roughly half the difference between
   h2 line-height and the input's height, so the visual centers align
   instead of the top edges (which read as "search slightly above title"
   because Fraunces sets letters lower in their line-box). */
.explorer-controls {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 0.8rem;
  margin: 2.5rem 0 0;
}
.search { position: relative; flex: 1 1 auto; min-width: 0; }
.search input {
  width: 100%;
  padding: 0.7rem 1rem;
  font-family: var(--serif-body);
  font-size: 1rem;
  background: var(--bg-raise);
  color: var(--ink);
  border: 1px solid var(--rule);
  border-radius: 2px;
  outline: none;
  transition: border-color 150ms;
}
.search input:focus { border-color: var(--amber); }
.search-results {
  position: absolute;
  top: 100%; left: 0; right: 0;
  margin: 0; padding: 0.3rem 0;
  list-style: none;
  background: var(--bg);
  border: 1px solid var(--rule);
  max-height: 280px;
  overflow-y: auto;
  z-index: 5;
}
.search-results li {
  padding: 0.5rem 1rem;
  font-family: var(--serif-display);
  font-size: 0.95rem;
  cursor: pointer;
  font-variation-settings: "opsz" 24, "SOFT" 30;
}
.search-results li:hover, .search-results li.is-focus { background: var(--bg-raise); color: var(--amber); }
.search-results li .year { font-family: var(--mono); font-size: 0.7rem; color: var(--ink-faint); margin-left: 0.6rem; }

.explorer-shuffle {
  flex: 0 0 auto;
  background: transparent;
  border: 1px solid var(--rule);
  color: var(--ink-dim);
  font-family: var(--sans);
  font-size: 0.74rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  padding: 0.7rem 1rem;
  cursor: pointer;
  transition: color 200ms, border-color 200ms;
  white-space: nowrap;
}
.explorer-shuffle:hover { color: var(--amber); border-color: var(--amber); }
.explorer-shuffle span { margin-right: 0.4em; }

.explorer-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 0;
  border-top: 1px solid var(--rule);
  border-left: 1px solid var(--rule);
  margin-bottom: 1.8rem;
}
.film-cell {
  padding: 0.7rem 0.85rem;
  border-right: 1px solid var(--rule);
  border-bottom: 1px solid var(--rule);
  cursor: pointer;
  transition: background 200ms;
  font-family: var(--serif-display);
  font-variation-settings: "opsz" 24, "SOFT" 30;
  font-size: 0.88rem;
  line-height: 1.2;
}
.film-cell:hover { background: var(--bg-raise); color: var(--amber-soft); }
.film-cell.is-active { background: var(--ink); color: var(--bg); }
.film-cell .year {
  display: block;
  font-family: var(--mono);
  font-size: 0.68rem;
  color: var(--ink-faint);
  margin-top: 0.35rem;
  letter-spacing: 0.05em;
}
.film-cell.is-active .year { color: rgba(238, 240, 231, 0.7); }
.film-cell.is-hidden { display: none; }

.explorer-stage {
  display: grid;
  grid-template-columns: 1fr 300px;
  gap: 2.5rem;
  /* `start` instead of `stretch` so the chart cell never grows to match
     the meta column when its stage-blob expands on hover (the script
     excerpt block adds ~20em on hover; with `stretch` that pushed the
     SVG taller mid-interaction, which read as the graph "stretching"). */
  align-items: start;
  flex: 1 1 auto;
  min-height: 0;
}
/* The chart cell is its own positioning context so the turn-toggle can
   float over the top-right of the arc without escaping into the meta
   column. min-width/min-height: 0 keeps the SVG from blowing past its
   grid track on narrow viewports. */
.explorer-stage-chart {
  position: relative;
  min-width: 0;
  min-height: 0;
}
/* Dominant-archetype mark: the la-linea icon for the loaded film's
   shape, tinted to the archetype's color. Implemented as a CSS mask
   so the same hand-drawn PNG can render in any of the six colors —
   the PNG's alpha channel is the mask, the element's background-color
   is the ink color. mask-image and background-color are set inline by
   JS in loadFilm based on the film's dominant_archetype. */
.arc-archetype-mark {
  /* Lives in the meta column above the match-stats panel — appears as
     the "verdict" once the line has drawn and turned color, hides while
     a turning-point dot is hovered (the stage-blob fills with the
     script excerpt at that moment, so the icon would crowd it). Sized
     to roughly half the 300px meta column so it reads as a real
     graphic, not a decorative chip.
     On dot-hover we add `.is-collapsed` to ALSO zero out the icon's
     vertical footprint so the excerpt panel doesn't sit below a 180px
     ghost rectangle of empty space. */
  margin-top: 1.4rem;
  width: 180px;
  height: 180px;
  max-height: 200px;
  pointer-events: none;
  opacity: 0.9;
  -webkit-mask-size: contain;
          mask-size: contain;
  -webkit-mask-repeat: no-repeat;
          mask-repeat: no-repeat;
  -webkit-mask-position: top left;
          mask-position: top left;
  /* Prefer alpha mask so a pure-black-ink PNG with transparent bg renders
     the ink (not the absence of ink) in the archetype color. */
  -webkit-mask-mode: alpha;
          mask-mode: alpha;
  /* Hidden by default — JS reveals once a film with a known dominant
     archetype loads (avoids a flash of an unstyled mark). */
  display: none;
  overflow: hidden;
}
/* Collapsed state for dot-hover: zero out the vertical footprint AND
   the margin so the panel below it shifts up cleanly while the script
   excerpt expands. !important needed because JS sets opacity inline
   for the initial reveal animation. */
.arc-archetype-mark.is-collapsed {
  opacity: 0 !important;
  max-height: 0 !important;
  margin-top: 0 !important;
  pointer-events: none;
}
.explorer-svg {
  width: 100%;
  height: 100%;
  min-height: 440px;
  display: block;
  /* No background or border — match the rest of the page's chart canvases
     (intro pin, shape-shift, dialogue-density) which sit transparent on
     the page bg. The explorer was the only chart with a darker raised
     panel, which read as inconsistent. */
}
.explorer-meta { padding: 1rem 0; }
.stage-title {
  font-family: var(--serif-display);
  font-variation-settings: "opsz" 72, "SOFT" 30;
  font-size: 1.7rem;
  color: var(--ink);
  margin-bottom: 0.25rem;
}
/* Stage title italic = the film title itself; the year stays upright in
   parens. Override the global em (amber-soft) so the title reads in the
   panel's normal ink color. */
.stage-title em { color: inherit; font-style: italic; font-weight: inherit; }
.stage-sub {
  font-family: var(--mono);
  font-size: 0.72rem;
  color: var(--ink-dim);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  margin-bottom: 1.5rem;
}
.stage-blob {
  font-family: var(--serif-body);
  font-size: 0.98rem;
  color: var(--ink);
  border-left: 2px solid var(--amber);
  padding: 0.5rem 0 0.5rem 1rem;
  min-height: 4em;
}
.stage-blob em { color: var(--ink-dim); font-style: italic; }
/* Match-stat: mono uppercase, bold percentage with a normal-weight tail.
   Mirrors the .pin-tip-match / .pin-tip-pct / .pin-tip-tail treatment in
   the one-at-a-time pin so the explore panel reads as the same family
   of side annotation. Color is set inline per archetype in JS. */
.stage-blob .arch-match {
  display: block;
  font-family: var(--mono);
  font-size: 0.78rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
}
.stage-blob .arch-match-pct  { font-weight: 700; }
.stage-blob .arch-match-tail { font-weight: 400; }
/* Secondary match lines are supporting context — keep the percentage
   at the same regular weight as its tail so the only visually bold
   stat is the primary (dominant-archetype) line. */
.stage-blob .arch-match-secondary .arch-match-pct { font-weight: 400; }
/* Loading state: while the curve is drawing + colorizing, the match
   panel shows a quiet pulsing "..." in the same mono family. Replaced
   with the real percentages by JS once the line-color tween ends. */
/* Loading state: a stack of three skeleton bars with a moving gradient
   highlight that sweeps across each. The shimmer reads as editorial
   scaffolding (think NYT / FT skeleton placeholders) rather than a
   generic spinner. Bar heights mirror the typography they replace —
   primary is taller (mono uppercase 0.78rem) and secondaries are
   shorter (mono uppercase 0.7rem). The shimmer animation runs on each
   bar with a small stagger so the panel feels alive but not noisy. */
.stage-blob .arch-match-loading {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  padding-top: 0.15rem;
}
.stage-blob .arch-match-loading .skeleton {
  display: block;
  height: 0.62em;
  border-radius: 1px;
  background: linear-gradient(
    90deg,
    var(--rule-soft) 0%,
    var(--rule-soft) 35%,
    var(--ink-faint) 50%,
    var(--rule-soft) 65%,
    var(--rule-soft) 100%
  );
  background-size: 220% 100%;
  background-position: 100% 0;
  opacity: 0.55;
  animation: arch-skeleton-shimmer 1.6s ease-in-out infinite;
}
.stage-blob .arch-match-loading .skeleton-primary   { width: 11rem; height: 0.7em; }
.stage-blob .arch-match-loading .skeleton-secondary { width: 8.5rem; height: 0.55em; }
.stage-blob .arch-match-loading .skeleton:nth-child(2) { animation-delay: 0.18s; }
.stage-blob .arch-match-loading .skeleton:nth-child(3) { animation-delay: 0.36s; }
@keyframes arch-skeleton-shimmer {
  0%   { background-position: 120% 0; }
  100% { background-position: -120% 0; }
}
/* Secondary matches: same typographic family as the primary line but
   smaller, neutral-color, and tighter top-margin so they read clearly
   as supporting context rather than parallel claims. */
.stage-blob .arch-match-secondary {
  font-size: 0.7rem;
  margin-top: 0.25rem;
  color: var(--ink-faint);
}
/* Raw screenplay excerpt — shown when a turning point has no curated
   NOTES entry. Monospace + tight leading reads as source rather than
   prose; max-height + overflow-y let the reader scroll within the box
   without resizing the panel. */
.stage-blob .script-excerpt {
  font-family: var(--mono);
  font-size: 0.72rem;
  /* Clean-number line-height makes max-height land on integer lines. */
  line-height: 1.5;
  color: var(--ink-dim);
  white-space: pre-wrap;
  margin: 0.4rem 0 0;
  /* Top + bottom padding equal one line-height so the visible content
     window snaps to whole lines when overflow kicks in (no mid-glyph
     bottom slice). */
  padding: 0.75em 0.7rem;
  background: var(--bg);
  border: 1px solid var(--rule);
  border-radius: 2px;
  /* 14 lines × 1.5em + 1.5em padding = 22.5em, so the content area
     always closes on a clean line boundary. */
  max-height: 22.5em;
  overflow-y: auto;
  outline: none;
  /* Soft fade at the bottom edge as an affordance for "scroll to see
     more" — even with the line snapping above, this signals that more
     content is below and prevents any residual half-line cutoff from
     reading as a visual error. */
  mask-image: linear-gradient(to bottom, #000 calc(100% - 1.6em), transparent 100%);
  -webkit-mask-image: linear-gradient(to bottom, #000 calc(100% - 1.6em), transparent 100%);
}
.stage-blob .script-excerpt:focus { border-color: var(--amber-soft); }
.stage-blob .note {
  display: block;
  margin-top: 0.4rem;
  font-style: italic;
  color: var(--ink-dim);
}

/* ───────────────── methodology drawer (optional, click to open) ───────────────── */
.meth-drawer {
  position: fixed;
  top: 0; right: 0;
  height: 100vh;
  width: min(720px, 100vw);
  background: var(--bg);
  border-left: 1px solid var(--rule);
  z-index: 200;
  transform: translateX(100%);
  transition: transform 380ms cubic-bezier(.4,.1,.2,1);
  overflow-y: auto;
  padding: 5rem 4rem;
  box-shadow: -20px 0 40px rgba(22,25,30,0.04);
}
.meth-drawer.is-open { transform: translateX(0); }
.meth-drawer .meth-close {
  position: absolute;
  top: 1.4rem; right: 1.6rem;
  background: transparent;
  border: 0;
  font-size: 2rem;
  line-height: 1;
  color: var(--ink-dim);
  cursor: pointer;
  padding: 0.3rem 0.6rem;
}
.meth-drawer .meth-close:hover { color: var(--ink); }
.meth-inner h2 {
  font-size: clamp(2rem, 4vw, 3rem);
  max-width: 18ch;
  margin-bottom: 2.5rem;
}
/* Mosaic / magazine-column layout — blocks flow into two columns and
   pack by height, so a long block (Sentiment, Clustering) sits next to
   two shorter blocks rather than leaving a row-gap of empty space. The
   `break-inside: avoid` rule keeps each .meth-block whole; varying
   heights produce the staggered, packed-mosaic feel. Reading order is
   newspaper-style: first column top-to-bottom, then second column. */
.meth-grid {
  column-count: 2;
  column-gap: 3rem;
  column-rule: none;
}
.meth-block {
  break-inside: avoid;
  -webkit-column-break-inside: avoid;
  page-break-inside: avoid;
  margin: 0 0 2rem;
}
.meth-block h4 {
  font-family: var(--mono);
  font-size: 0.74rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--amber);
  margin-bottom: 0.6rem;
}
.meth-block p { color: var(--ink-dim); font-size: 0.95rem; }
/* Inline links inside methodology body copy — same amber treatment as
   the intermission-sub / cluster-sub treatment so external citations
   read as part of the page's link family. */
.meth-block a {
  color: var(--amber);
  text-decoration: none;
  border-bottom: 1px solid var(--amber-soft);
  transition: color 200ms, border-color 200ms;
}
.meth-block a:hover {
  color: var(--amber-soft);
  border-bottom-color: var(--amber);
}
.meth-block a em { color: inherit; font-style: italic; font-weight: inherit; }
.meth-scrim {
  position: fixed;
  inset: 0;
  background: rgba(22,25,30,0.32);
  z-index: 199;
  opacity: 0;
  pointer-events: none;
  transition: opacity 320ms ease;
}
.meth-scrim.is-open { opacity: 1; pointer-events: auto; }

.closing .meth-link { margin: 2rem 0 4rem; }
.closing .meth-link a {
  font-family: var(--sans);
  font-size: 0.84rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ink-dim);
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  padding-bottom: 0.2rem;
  transition: color 200ms;
}
.closing .meth-link a:hover { color: var(--amber); }

/* ───────────────── closing ───────────────── */
.closing {
  max-width: var(--max);
  margin: 0 auto;
  /* Bumped top from 12rem → 18rem so the closing feels like a clean
     final beat after the explorer rather than tucked right behind it. */
  padding: 18rem var(--gutter) 8rem;
  text-align: left;
}
.closing-title {
  /* Matches .intermission-title — same display size, weight, optical-size,
     and ch-width cap so the closing reads as a peer to the other section
     beats (e.g., "Has Hollywood's grip on these six shapes changed?"),
     not a louder finale. */
  font-size: clamp(2.8rem, 7vw, 6rem);
  font-weight: 300;
  font-variation-settings: "opsz" 144, "SOFT" 25;
  line-height: 1;
  margin: 0 0 2rem;
  max-width: 18ch;
}
.closing-title em { color: var(--amber); font-style: italic; }
.closing-line {
  /* Body serif outro paragraph. Matches the wider .shape-shift-sub width
     (120ch) — the closing copy is four sentences of expanding thought, so
     a wider column lets it breathe in 2–3 lines instead of stacking into a
     narrow column that drags the eye. */
  font-family: var(--serif-body);
  font-size: 1.2rem;
  color: var(--ink-dim);
  line-height: 1.5;
  max-width: 120ch;
  margin-bottom: 5rem;
}
/* Italic-only override for plain <em> inside the closing line — global em
   adds amber + heavier weight, but here we want italic alone so the
   word doesn't visually compete with the rest of the line. */
.closing-line em { color: inherit; font-weight: inherit; font-style: italic; }
/* Opt-in accent variant: the closing question reaches for the page's
   amber treatment so it lands as the punctuating beat. */
.closing-line em.accent { color: var(--amber); }

/* ───────────────── site footer / colophon ─────────────────
   One thin tracked-mono line: copyright, source links, typeface credit.
   Hairline rule above separates it from the closing section. The bio that
   used to live here moved to the hero hover-reveal. */
.site-footer {
  max-width: var(--max);
  /* `auto` keeps horizontal centering inside --max; the explicit top/bottom
     give breathing room above the hairline rule and trailing scroll space
     below the colophon line. */
  margin: 6rem auto;
  padding: 0 var(--gutter);
}
.site-footer p {
  border-top: 1px solid var(--rule);
  padding: 1.5rem 0 0;
  font-family: var(--mono);
  font-size: 0.7rem;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  color: var(--ink-faint);
  line-height: 1.6;
  margin: 0;
  max-width: none;
}
.site-footer a {
  color: var(--ink-dim);
  text-decoration: none;
  border-bottom: 1px solid var(--rule);
  transition: color 200ms, border-color 200ms;
}
.site-footer a:hover {
  color: var(--amber);
  border-bottom-color: var(--amber-soft);
}

/* Footer quote info badge — same hover-reveal pattern as the hero .bio,
   shrunk for the colophon line and flipped to open ABOVE the icon (the
   footer is at the bottom of the page, so a downward popover would clip
   off-screen). */
.qinfo {
  position: relative;
  display: inline-flex;
  align-items: center;
  vertical-align: 0.18em;
  margin-left: 0.35em;
  cursor: pointer;
  outline: none;
  z-index: 5;
}
.qinfo-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.35em;
  height: 1.35em;
  border: 1px solid var(--ink-faint);
  border-radius: 50%;
  font-family: var(--serif-body);
  font-style: italic;
  font-size: 0.78em;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-dim);
  line-height: 1;
  transition: color 200ms, border-color 200ms, background-color 200ms;
}
.qinfo:hover .qinfo-mark,
.qinfo:focus-visible .qinfo-mark,
.qinfo:focus-within .qinfo-mark {
  color: var(--amber);
  border-color: var(--amber-soft);
  background: rgba(176, 122, 31, 0.08);
}
/* Hover bridge so the cursor doesn't lose .qinfo:hover crossing the gap
   between the icon and the popover. */
.qinfo::after {
  content: "";
  position: absolute;
  left: 50%;
  bottom: 100%;
  transform: translateX(-50%);
  width: 2.4em;
  height: 0.6rem;
}
.qinfo-pop {
  position: absolute;
  /* Open ABOVE the icon, anchored to its right edge so the box grows up
     and to the left — never below (would clip off-page) and never further
     right than the viewport. */
  bottom: calc(100% + 0.6rem);
  right: 0;
  white-space: normal;
  width: max-content;
  max-width: min(40ch, calc(100vw - 2 * var(--gutter)));
  padding: 0.6rem 0.85rem;
  background: var(--bg-raise);
  border: 1px solid var(--rule);
  border-radius: 6px;
  box-shadow: 0 -12px 28px -20px rgba(22, 25, 30, 0.35);
  /* Match the .bio-pop treatment — mono, uppercase, tracked, ink-faint —
     so both hover-revealed tooltips on the page read as the same family. */
  font-family: var(--mono);
  font-style: normal;
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  line-height: 1.55;
  opacity: 0;
  transform: translateY(4px);
  pointer-events: none;
  transition: opacity 220ms ease, transform 220ms ease;
}
.qinfo:hover .qinfo-pop,
.qinfo:focus-visible .qinfo-pop,
.qinfo:focus-within .qinfo-pop {
  opacity: 1;
  transform: translateY(0);
  pointer-events: auto;
}
.qinfo-pop a {
  color: var(--amber);
  text-decoration: none;
  border-bottom: 1px solid var(--amber-soft);
  transition: color 200ms, border-color 200ms;
}
.qinfo-pop a:hover {
  color: var(--amber-soft);
  border-bottom-color: var(--amber);
}
/* Hover/focus on the info badge tints the quote text amber, matching the
   bio→author-name pairing in the hero so both info-reveal patterns share
   the same interaction language. */
.qinfo-text { transition: color 200ms; }
#footer-quote:has(.qinfo:hover) .qinfo-text,
#footer-quote:has(.qinfo:focus-visible) .qinfo-text,
#footer-quote:has(.qinfo:focus-within) .qinfo-text {
  color: var(--amber);
}

/* ───────────────── hero bio (hover-revealed) ─────────────────
   The footer's long-form bio moved here. A small mono "i" sits next to
   "Derin Savasan" in the hero eyebrow; hover/focus lifts a serif card
   below it with the bio. Tap-to-toggle on touch via :focus (the wrapper
   is tabindex=0 / role=button so a tap focuses it and reveals the pop). */
.bio {
  position: relative;
  display: inline-flex;
  align-items: center;
  /* Optical alignment with the eyebrow's UPPERCASE cap line. `vertical-align:
     middle` aligns the box to the parent's x-height midpoint, which sits
     LOWER than cap-mid for uppercase tracked text — so the "i" looked like
     it was hanging below the line. A small positive length nudges it up
     toward the optical center of the cap line. */
  vertical-align: 0.18em;
  /* Lift above .hero-title — the title creates a stacking context via
     will-change, and as a later sibling it would otherwise paint on top
     of the popover even with the popover's own z-index. */
  z-index: 10;
  margin-left: 0.55em;
  cursor: pointer;
  outline: none;
}
.bio-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.25em;
  height: 1.25em;
  border: 1px solid var(--ink-faint);
  border-radius: 50%;
  font-family: var(--serif-body);
  font-style: italic;
  font-size: 0.78em;
  letter-spacing: 0;
  text-transform: none;
  color: var(--ink-dim);
  line-height: 1;
  transition: color 200ms, border-color 200ms, background-color 200ms;
}
/* Invisible bridge between the icon and the popover so the cursor doesn't
   lose .bio:hover when transiting the gap. Without it, :hover ends mid-gap,
   the popover hides, and the user can never reach the moviegoer link inside. */
.bio::after {
  content: "";
  position: absolute;
  left: 100%;
  top: 50%;
  transform: translateY(-50%);
  width: 1.2rem;
  height: 2.4em;
}
.bio:hover .bio-mark,
.bio:focus-visible .bio-mark,
.bio:focus-within .bio-mark {
  color: var(--amber);
  border-color: var(--amber-soft);
  background: rgba(176, 122, 31, 0.08);
}
.bio-pop {
  position: absolute;
  /* To the RIGHT of the icon, top-aligned with it so the box can never
     bulge upward past the eyebrow line into the page header. Earlier
     `top: 50%` centered it on the icon, which pushed half the popover
     above the eyebrow when the bio wrapped to many lines. */
  left: calc(100% + 1.2rem);
  top: 0;
  /* Width: ~64ch makes the bio fit in 3–4 lines instead of 7+ at the old
     36ch. Never push past the right edge of the viewport. The bio's parent
     .eyebrow-line sets white-space: nowrap, which would otherwise inherit
     and force the popover onto a single overflowing line. */
  white-space: normal;
  width: min(64ch, calc(100vw - 2 * var(--gutter) - 100%));
  padding: 1.1rem 1.25rem;
  background: var(--bg-raise);
  border: 1px solid var(--rule);
  border-radius: 6px;
  box-shadow: 0 18px 40px -24px rgba(22, 25, 30, 0.35);
  /* Mirrors the .von-portrait figcaption treatment — mono, uppercase, tracked,
     small. Reads as a sibling of the page's other "tech" labels (axis ticks,
     decade markers, image captions) rather than as body prose. */
  font-family: var(--mono);
  font-style: normal;
  font-size: 0.7rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--ink-faint);
  line-height: 1.55;
  opacity: 0;
  /* Slight left-offset for the slide-in. Top-anchored, so no vertical
     translate is needed — final hover state lands at translateX(0). */
  transform: translateX(-4px);
  pointer-events: none;
  transition: opacity 220ms ease, transform 220ms ease;
  z-index: 5;
}
.bio:hover .bio-pop,
.bio:focus-visible .bio-pop,
.bio:focus-within .bio-pop {
  opacity: 1;
  transform: translateX(0);
  pointer-events: auto;
}
.bio-pop a {
  color: var(--amber);
  text-decoration: none;
  border-bottom: 1px solid var(--amber-soft);
  transition: color 200ms, border-color 200ms;
}
.bio-pop a:hover {
  color: var(--amber-soft);
  border-bottom-color: var(--amber);
}
/* Sync the author name's color to the bio control. Hovering / focusing the
   "i" badge tints "Derin Savasan" in the same amber as the badge so the link
   between the name and the hover-revealed bio is explicit. Uses :has() to
   reach back to the earlier sibling .author-name from the .bio state. */
.author-name { transition: color 200ms; }
.eyebrow:has(.bio:hover) .author-name,
.eyebrow:has(.bio:focus-visible) .author-name,
.eyebrow:has(.bio:focus-within) .author-name {
  color: var(--amber);
}

/* ───────────────── shared SVG type ───────────────── */
svg text.axis-end {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 11px;
  letter-spacing: 0.06em;
  fill: var(--ink-dim);
}
svg .axis-y-side {
  font-family: "JetBrains Mono", ui-monospace, monospace;
  font-size: 10px;
  letter-spacing: 0.18em;
  fill: var(--ink-faint);
  text-transform: uppercase;
}

/* ───────────────── essay section ───────────────── */
.essay {
  max-width: var(--max);
  margin: 0 auto;
  padding: 6rem var(--gutter) 8rem;
}
.essay-inner {
  max-width: 64ch;
  margin: 0 auto;
}
.essay-lede {
  font-family: var(--serif-display);
  font-variation-settings: "opsz" 36, "SOFT" 30;
  font-size: clamp(1.4rem, 2.3vw, 2rem);
  line-height: 1.35;
  color: var(--ink);
  margin: 0 0 2.5rem;
  max-width: none;
}
.essay p {
  font-size: 1.15rem;
  line-height: 1.7;
  color: var(--ink);
  margin: 0 0 1.4em;
  max-width: none;
}
.essay .essay-h3 {
  font-family: var(--serif-display);
  font-variation-settings: "opsz" 48, "SOFT" 60;
  font-style: italic;
  font-size: 1.4rem;
  font-weight: 400;
  color: var(--amber);
  margin: 2.5rem 0 0.8rem;
}
.essay .essay-close {
  margin-top: 2.5rem;
  font-family: var(--serif-display);
  font-variation-settings: "opsz" 36, "SOFT" 30;
  font-size: 1.15rem;
}
.essay .essay-close em { color: var(--amber); font-style: italic; }

/* ───────────────── site nav (top-right pip rail) ─────────────────
   Editorial nav matched to the page's design tokens: bg-raise pip with
   rule border, amber fill on the active section, mono uppercase label
   that matches the section eyebrows.

   Reveal model: the nav is FULLY HIDDEN until the user hovers the
   top-right corner of the viewport. A transparent .site-nav-zone div
   sits as a sibling above the nav in DOM order — when it's hovered, a
   sibling combinator below shows the nav. While the user is reading
   the storytelling, the nav stays out of the way. */
.site-nav-zone {
  position: fixed;
  top: 0;
  right: 0;
  width: 220px;
  height: 220px;
  z-index: 49;
  pointer-events: auto;
  /* Invisible — purely a hover trap. Uncomment background for debug. */
  /* background: rgba(255, 0, 0, 0.08); */
}
.site-nav {
  position: fixed;
  right: 1.4rem;
  top: 1.4rem;
  z-index: 50;
  opacity: 0;
  pointer-events: none;
  transition: opacity 280ms ease;
}
/* Reveal when the corner hot-zone is hovered, OR when the nav itself is
   hovered (so moving from zone onto a pip doesn't break the chain), OR
   when keyboard focus lands inside it. */
.site-nav-zone:hover ~ .site-nav,
.site-nav:hover,
.site-nav:focus-within {
  opacity: 1;
  pointer-events: auto;
}
.site-nav.is-active,
.site-nav:hover,
.site-nav:focus-within {
  opacity: 1;
}
.site-nav ul {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.95rem;
  align-items: flex-end;
}
.site-nav li { margin: 0; padding: 0; }
.site-nav a {
  display: flex;
  /* row-reverse so the pip sits on the right (flush with the screen edge)
     and the label extends leftward into the page when revealed. */
  flex-direction: row-reverse;
  align-items: center;
  justify-content: flex-start;
  gap: 0.7rem;
  text-decoration: none;
  outline: none;
}
.site-nav .pip {
  flex: 0 0 auto;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--bg-raise);
  border: 1px solid var(--ink-faint);
  transition: background 220ms, border-color 220ms, transform 220ms;
}
.site-nav .label {
  font-family: var(--mono);
  font-size: 0.66rem;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--ink-dim);
  white-space: nowrap;
  opacity: 0;
  transform: translateX(6px);
  transition: opacity 220ms ease, transform 220ms ease, color 220ms;
  pointer-events: none;
}
.site-nav:hover .label,
.site-nav:focus-within .label,
.site-nav.is-active .label {
  opacity: 1;
  transform: translateX(0);
}
.site-nav a:hover .pip,
.site-nav a:focus-visible .pip {
  border-color: var(--amber);
  background: var(--amber-soft);
}
.site-nav a:hover .label,
.site-nav a:focus-visible .label {
  color: var(--amber);
}
.site-nav a.is-current .pip {
  background: var(--amber);
  border-color: var(--amber);
  transform: scale(1.25);
}
.site-nav a.is-current .label { color: var(--ink); }

/* ───────────────── responsive ───────────────── */
@media (max-width: 900px) {
  .explorer-stage, .explorer-top {
    grid-template-columns: 1fr;
  }
  .explorer-top { gap: 2rem; }
  .von-row { flex-direction: column; align-items: stretch; gap: 2rem; }
  .von-portrait { max-width: 320px; flex: 0 1 auto; }
  .intro-pin { height: 360vh; }
  .intro-beat[data-beat="1"] { left: var(--gutter); right: auto; text-align: left; top: 30vh; }
  .cluster-board { min-height: 320px; }
  .archetypes-pin { height: 600vh; }
  .intermission-title,
  .closing-title { font-size: clamp(2.2rem, 9vw, 4rem); }
  .meth-drawer { padding: 4rem 1.5rem; }
  /* Single-column methodology on narrow drawer widths so the columns
     aren't too narrow to read comfortably. */
  .meth-grid { column-count: 1; }
  /* Hide the desktop side-rail on narrow viewports — the page's intermissions
     and prelude eyebrows already orient the reader vertically on mobile. */
  .site-nav { display: none; }
}

/* SplitText reveal helper — hide split lines/words/chars before GSAP animates them in */
.split-mask { overflow: hidden; }
.split-line, .split-word { display: inline-block; }
