/* ==========================================================================
   Last to Leave — global styles
   ========================================================================== */

@font-face {
  font-family: 'Glacial Indifference';
  src: url('../fonts/GlacialIndifference-Regular.woff2') format('woff2');
  font-weight: 400;
  font-style: normal;
  font-display: swap;
}

@font-face {
  font-family: 'Glacial Indifference';
  src: url('../fonts/GlacialIndifference-Bold.woff2') format('woff2');
  font-weight: 700;
  font-style: normal;
  font-display: swap;
}

:root {
  --black: #000000;
  --white: #ffffff;
  --pink: #f11ac5;
  --grey-dark: #3c3c3c;
  --grey-mid: #666666;
  --grey-hairline: #cccccc;
  --nav-black: #242424;

  --font-display: 'Glacial Indifference', sans-serif;
  --font-body: 'Inclusive Sans', sans-serif;
  --font-mono: 'IBM Plex Mono', monospace;

  --section-pad-x: clamp(1.5rem, 6vw, 6rem);

  /* The nav bar's rendered height, and the whole of it: .site-nav carries no
     padding and no border, so this value IS what the bar measures. Lives here
     rather than on .site-nav because html's scroll-padding-top below and
     .hero's top padding both reserve space against it, and neither can read a
     custom property declared on the bar itself. */
  --nav-h: 64px;
}

/* Reset ------------------------------------------------------------------ */

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

html {
  -webkit-text-size-adjust: 100%;
  /* The nav is fixed, so an in-page jump would otherwise land the target's top
     edge underneath it. Derived from --nav-h and never a literal: a hardcoded
     number here would go quietly wrong the day the bar's height changes. The
     extra 1rem is breathing room. */
  scroll-padding-top: calc(var(--nav-h) + 1rem);
}

body {
  margin: 0;
  background: var(--black);
  color: var(--white);
  font-family: var(--font-body);
  line-height: 1.5;
  /* clip, not hidden: hidden forces overflow-y to compute as "auto" per
     the CSS overflow spec (pairing a non-visible x with a visible y),
     which turns body into a scroll container and breaks position:sticky
     for every descendant on the page. clip doesn't have that side effect. */
  overflow-x: clip;
}

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

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

a {
  color: inherit;
}

button {
  font-family: inherit;
}

h1, h2, h3, p, ul {
  margin: 0;
}

/* Motion: respect reduced-motion preference -------------------------------- */

@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* Shared: tape strips -------------------------------------------------------
   Reusable hand-stuck tape treatment. Default pink; .tape--black for the
   manifesto video frame. Jagged edges via clip-path. */

.tape {
  position: relative;
  display: inline-block;
  background: var(--pink);
  clip-path: polygon(
    0% 0%, 100% 0%,
    100% 32%, 93% 32%, 93% 44%, 100% 44%,
    100% 100%, 0% 100%,
    0% 68%, 7% 68%, 7% 56%, 0% 56%
  );
}

.tape--black {
  background: var(--black);
}

/* Shared: mono aside voice -------------------------------------------------- */

.mono-aside {
  font-family: var(--font-mono);
  font-size: clamp(1.1rem, 2.4vw, 1.5rem);
  line-height: 1.4;
  color: var(--white);
}

/* Shared: newsletter form ---------------------------------------------------- */

.signup-form {
  display: flex;
  gap: 0.75rem;
  max-width: 32rem;
  margin-top: 1.25rem;
}

.signup-form__email {
  flex: 1;
  min-width: 0;
  padding: 0.9rem 1.25rem;
  border-radius: 0;
  border: 1px solid var(--grey-hairline);
  background: var(--white);
  color: var(--black);
  font-family: var(--font-body);
  font-size: 1rem;
}

.signup-form__email::placeholder {
  color: #6b6b6b;
}

.signup-form__submit {
  flex-shrink: 0;
  padding: 0.9rem 1.75rem;
  border: none;
  border-radius: 0;
  background: var(--pink);
  color: var(--black);
  font-family: var(--font-body);
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  font-size: 1.5rem;
  cursor: pointer;
}

.signup-form__submit:hover {
  background: var(--white);
}

.signup-form__note {
  font-size: 0.8rem;
  color: var(--grey-hairline);
  margin-top: 0.5rem;
}

/* Shared: social icon row ---------------------------------------------------- */

.social-row {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.social-row a {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  color: var(--white);
  transition: color 0.2s ease;
}

.social-row a:hover {
  color: var(--pink);
}

.social-row svg {
  width: 100%;
  height: 100%;
  fill: currentColor;
}

.social-handle {
  color: var(--pink);
  font-weight: 700;
  text-decoration: none;
}

/* ==========================================================================
   Persistent nav

   Fixed and always visible — no reveal, no hidden state. A visitor landing at
   the top from social needs the link to the show right away, so there is
   nothing here to trigger.

   STACKING. z-index 100 because .manifesto is z-index 10 and position:sticky,
   so it paints over anything below 10 as it travels; 100 clears it with room
   to spare and matches the show page's chrome value.
   ========================================================================== */

.site-nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  background: var(--nav-black);
  /* Elevation is the whole edge treatment — no border, no rule. box-shadow
     paints outside the border box, so it adds nothing to the height below. */
  box-shadow: 0 4px 20px rgba(0, 0, 0, 0.4);
  /* Crops the bulb's cord at the top edge. The graphic overflows upward on
     purpose — see .site-nav__bulb. */
  overflow: hidden;
  border-bottom: 1px white;
}

/* The height lives here and nowhere else: with no padding and no border on
   .site-nav, --nav-h is the bar's rendered height. The gutters reuse
   --section-pad-x so the bar's contents line up with the page below it. */
.site-nav__inner {
  display: flex;
  align-items: center;
  height: var(--nav-h);
  gap: clamp(1.5rem, 4vw, 2.75rem);
  padding-inline: var(--section-pad-x);
}

/* A fixed-width, full-height slot in the flex row. The graphic inside is
   absolutely positioned so it can overflow the bar without adding height. */
.site-nav__bulb {
  --bulb-w: 34px;
  --bulb-drop: 6px;
  position: relative;
  flex: 0 0 var(--bulb-w);
  align-self: stretch;
}

/* images/bulb.png is 348x2208 — white line art on transparency, a long
   diagonal cord with the bulb in the bottom portion. Anchoring the graphic's
   BOTTOM inside the bar lands the bulb in the bar and sends the cord up
   through the top edge, where .site-nav's overflow:hidden cuts it. Used as a
   MASK and coloured, not tinted through a filter. The @supports guard means a
   browser without mask support gets nothing rather than a solid slab.
   url() is relative to THIS FILE's location (/css/). */
@supports ((mask-image: url('../images/bulb.png')) or (-webkit-mask-image: url('../images/bulb.png'))) {
  .site-nav__bulb::before {
    content: "";
    position: absolute;
    left: 0;
    bottom: var(--bulb-drop);
    width: var(--bulb-w);
    /* The PNG's own ratio. The mask must not distort. */
    aspect-ratio: 348 / 2208;
    background-color: rgba(255, 255, 255, 0.18);
    /* A switch, not a fade. Short and linear on purpose. */
    transition: background-color 120ms linear;
    -webkit-mask-image: url('../images/bulb.png');
            mask-image: url('../images/bulb.png');
    -webkit-mask-size: 100% 100%;
            mask-size: 100% 100%;
    -webkit-mask-repeat: no-repeat;
            mask-repeat: no-repeat;
  }

  /* The one class js/main.js sets, and it sets it on the nav, not the bulb. */
  .site-nav.is-lit .site-nav__bulb::before {
    background-color: var(--white);
  }
}

.site-nav__link {
  font-family: var(--font-mono);
  font-weight: 200;
  font-size: 1rem;
  letter-spacing: 0.12em;
  line-height: 1;
  text-transform: uppercase;
  text-decoration: none;
  color: #FFFFFF;
  transition: color 0.2s ease;
  padding-bottom: 0.5em;
}

.site-nav__link:hover,
.site-nav__link:focus-visible { color: var(--pink); }

/* Current page. It stays a real link, so its hover is neutralised too: a hover
   response would promise a destination it doesn't have. aria-current says out
   loud what the grey says visually. */
.site-nav__link--current,
.site-nav__link--current:hover,
.site-nav__link--current:focus-visible { color: rgba(255, 255, 255, 0.45); }

/* The bulb's trigger point, and nothing else. It sits directly after
   .hero__lockup in the markup, so its own top edge IS the lockup's bottom
   edge, and js/main.js observes it rather than the image.

   MUST NOT AFFECT LAYOUT. .hero is a flex column with no gap, so a
   zero-height child contributes exactly zero. Do not give it content,
   padding, margin, a border, or a height. */
.chrome-sentinel {
  display: block;
  height: 0;
  margin: 0;
  align-self: stretch;
}

/* ==========================================================================
   1 — Hero
   ========================================================================== */

.hero-wrap {
  position: relative;
}

.hero {
  --wordmark-w: min(60vw, 34rem);
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: flex-start;
  padding: var(--section-pad-x);
  /* Clears the fixed nav, which is out of flow and reserves no space of its
     own. Derived from --nav-h, never a literal. */
  padding-top: calc(var(--nav-h));
  /* No bottom padding: Manifesto's overlap math (js/main.js) assumes
     Hero's rendered bottom edge coincides exactly with #flagPhrases'
     own bottom edge. */
  padding-bottom: 0;
  position: relative;
  background: var(--black);
}

.hero__lockup {
  width: var(--wordmark-w, min(60vw, 34rem));
  height: auto;
  margin-top: 0;
  transform: rotate(-2deg);
}

.hero__tape {
  margin-top: 2rem;
  /* right edge aligned to the bulb graphic baked into the wordmark PNG,
     measured at 93.3% of the wordmark's own width. Rotation is 1deg (not
     the tape's overall ~4.3deg tilt) because the tape-hero.png artwork
     already has its own ~1deg inherent tilt baked in; this cancels it out
     so the shape's own bottom edge renders horizontal. */
  --tape-offset: calc(0.433 * var(--wordmark-w) - 50%);
  transform: translateX(var(--tape-offset)) rotate(1deg);
}

.hero__tape .tape {
  width: clamp(16rem, 24vw, 24rem);
  aspect-ratio: 500 / 178;
  background-image: url('../images/tape-hero.png');
  background-size: 100% 100%;
  background-color: transparent;
  clip-path: none;
  display: flex;
  flex-direction: column;
  justify-content: center;
  align-items: center;
  padding: 0.9rem 3rem 0 2rem;
}

.hero__tape-text {
  font-family: var(--font-body);
  font-weight: 500;
  color: var(--black);
  font-size: clamp(1.375rem, 3.74vw, 1.98rem);
  line-height: 1.15;
  text-align: center;
}

.hero__tape-text .word--potent {
  letter-spacing: 0.09em;
}

.hero__tape-text .word--theater {
  letter-spacing: 0;
}

.hero__la {
  margin-top: 1.1rem;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.68rem, 4.8vw, 2.52rem);
}

.hero__la .word--los {
  display: inline-block;
  transform: rotate(-1deg);
}

.hero__la .word--angeles {
  display: inline-block;
  transform: translateY(20px) rotate(8deg);
}

/* ==========================================================================
   2 — Flag phrases (now part of Hero, not a separate scroll section --
   see .manifesto for the sticky/overlap mechanic that covers these at
   rest and reveals them as Hero scrolls up underneath it)
   ========================================================================== */

.hero__flag-text {
  margin-top: 70px;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 4rem;
  text-align: center;
}

.flag__flag-rotate {
  display: inline-block;
  transform: rotate(-4deg);
}

/* ==========================================================================
   3 — Manifesto
   ========================================================================== */

.manifesto {
  /* No min-height: sized to its own content (~650px), not the viewport.
     This matters: it means once stuck, Manifesto does NOT necessarily
     cover the whole screen, which is what makes a real reveal possible
     (see the sticky `top` offset below). */
  background: var(--pink);
  color: var(--black);
  padding: 10px 20px 50px 20px;
  display: flex;
  flex-direction: column;
  justify-content: center;
  gap: clamp(1.25rem, 3vh, 2rem);
  /* Negative margin-top set in js/main.js, matched exactly to
     #flagPhrases' rendered height, so it initially sits covering just
     the two phrases (screenshot B). Sticky `top` is a positive offset,
     NOT 0 -- 0 would mean Manifesto's covering box starts at the very
     top of the viewport with nothing ever above it to reveal. A
     positive offset leaves that band permanently open, so Hero's
     content (moving normally, up and off) passes visibly through it
     before going off-screen -- that band is the actual reveal. */
  position: sticky;
  top: 30vh;
  z-index: 10;
}

/* Genuine extra scroll-runway (not just extra height ON Manifesto, which
   doesn't work -- see comment above) so Manifesto has 250px of stuck
   time for the reveal fade to play out over, before it releases and
   scrolls away normally. Pink so releasing into it reads as a seamless
   continuation rather than a gap. REVEAL_BUFFER_PX in js/main.js must
   match this height. */
.manifesto-reveal-spacer {
  height: 250px;
  background: var(--pink);
}

.manifesto__title {
  font-family: var(--font-display);
  font-weight: 700;
  line-height: 0.95;
  letter-spacing: -0.06em;
  text-transform: uppercase;
  /* .manifesto is display:flex, so this h2 is a flex item -- and flex
     items are always block-level ("blockified") no matter what display
     you give them; display:inline-block here would compute back to
     block regardless. The actual reason it was stretching to full
     width is align-items:stretch (the flex default on .manifesto),
     which sizes every flex item to the container's cross-axis size
     unless it opts out. align-self:flex-start opts this one out, so it
     shrinks to its actual text width instead -- without this,
     getBoundingClientRect() always reports the CONTAINER's width here,
     not the rendered text width, making syncManifestoTitle()'s fit
     measurement in js/main.js a silent no-op (it reads back the target
     width whether or not the text actually reaches it, so font-size
     never gets corrected off the fallback clamp below). */
  align-self: flex-start;
  /* Exact font-size is set by syncManifestoTitle() in js/main.js, which
     fits this to span the full content width of #manifesto (not just an
     approximation via vw units) -- this clamp is only the pre-JS/no-JS
     fallback. white-space:nowrap keeps "Potent Theater" from wrapping
     mid-fit; the mobile breakpoint below switches each word to its own
     line and lets JS fit them independently instead. */
  font-size: clamp(2rem, 12vw, 10rem);
  white-space: nowrap;
}

.manifesto__title-word {
  /* Always inline-block, never block -- a block element stretches to
     its container's width regardless of font-size, which would make
     syncManifestoTitle()'s width measurement always read the container
     width back (a no-op fit). inline-block sizes to content instead, on
     both desktop and the stacked mobile layout below. The line break
     between words on mobile comes from .manifesto__title-break, not
     from changing this to block. letter-spacing is redeclared here (not
     just inherited from .manifesto__title) because an em-based value
     resolves against the font-size of the element it's declared ON --
     inherited, it would stay frozen at the H2's font-size even as JS
     sets each word's OWN font-size independently on mobile, breaking
     the proportional width-vs-font-size relationship fitFontToWidth()
     relies on for its one-shot scaling in js/main.js. Declared directly
     here, it scales with each word's own size instead. */
  letter-spacing: -0.06em;
  display: inline-block;
}

.manifesto__title-break {
  display: none;
}

.manifesto__body {
  display: flex;
  gap: clamp(2rem, 6vw, 5rem);
  align-items: flex-start;
  flex-wrap: wrap;
  /* Keeps the list and video from drifting far apart on very wide
     screens, even though .manifesto__title above spans full width.
     73rem specifically: past this row width, .manifesto__lines' font-
     size (clamp ceiling 2rem) and .video-frame's height-matched ideal
     width are both already maxed out and constant, so this is the exact
     threshold where the list's natural (no-wrap) width + gap + the
     video's height-matched width all fit with nothing to spare --
     below it the video would have to give up some of the height match
     to avoid wrapping the list (see syncManifestoVideoSize in
     js/main.js); above it there's no more benefit, just extra gap. */
  max-width: 73rem;
  margin-inline: auto;
}

.manifesto__lines {
  /* flex-basis:0, not auto -- with auto, the flex-wrap decision is based
     on this list's own unwrapped max-content width (i.e. its longest
     line rendered on one line), which is wide enough to trigger a
     premature wrap of the whole row well before space is actually
     tight. With basis 0, the wrap decision is driven by .video-frame's
     content width instead, and this just grows to fill whatever's left. */
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: clamp(1.25rem, 4vh, 2.5rem);
  font-family: var(--font-body);
  font-size: clamp(1.1rem, 2.6vw, 2rem);
  font-weight: 300;
  letter-spacing: 0.02em;
  text-transform: uppercase;
  list-style: none;
  padding: 0;
}

.manifesto__video-wrap {
  /* Width is NOT a flex share -- js/main.js's syncManifestoVideoSize()
     sets .video-frame's width to whatever makes its height (via its own
     aspect-ratio) match .manifesto__lines' rendered height, so the two
     columns balance vertically instead of the video sitting at some
     unrelated size. This wrap just shrinks to fit that. */
  flex: 0 0 auto;
  min-width: 0;
  align-self: center;
}

.video-frame {
  position: relative;
  transform: rotate(3deg);
}

.video-frame__media {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  background: var(--black);
}

.video-frame__media video,
.video-frame__media img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.video-frame__play {
  position: absolute;
  inset: 0;
  margin: auto;
  width: 4.5rem;
  height: 4.5rem;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.6);
  border: none;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  padding: 0;
}

.video-frame__play svg {
  width: 1.6rem;
  height: 1.6rem;
  fill: var(--white);
  margin-left: 4px;
}

.video-frame__play.is-hidden {
  display: none;
}

.video-frame .tape {
  position: absolute;
  height: 2rem;
  /* Solid piece, not the shared .tape class's jagged clip-path -- that
     cut is sized for a wide banner (see .hero__tape .tape); on a small,
     steeply-rotated rectangle like these corners it reads as a pointed
     blade rather than a torn strip of tape. */
  clip-path: none;
  /* .video-frame__media comes after these spans in the DOM and is also
     position:relative with no z-index of its own -- without this, plain
     DOM-order stacking puts the video on top of the tape instead of the
     tape appearing stuck down over it. */
  z-index: 2;
}

/* Different widths (lengths) per corner so these read as separate,
   individually-torn pieces of tape rather than 4 copies of one shape. */
.video-frame .tape--tl { top: -1rem; left: -1.25rem; width: 4rem; transform: rotate(-40deg); }
.video-frame .tape--tr { top: -1rem; right: -1.5rem; width: 5.5rem; transform: rotate(35deg); }
.video-frame .tape--bl { bottom: -1rem; left: -2.5rem; width: 6rem; transform: rotate(50deg); }
.video-frame .tape--br { bottom: -1.2rem; right: -1.25rem; width: 4.5rem; transform: rotate(-30deg); }

/* ==========================================================================
   4A / 4B — Assassins feature + Conversion
   ========================================================================== */

.conversion {
  position: relative;
  background: var(--black);
  padding: clamp(2.5rem, 6vh, 4rem) var(--section-pad-x);
}

.assassins-feature {
  /* z-index on the SECTION is load-bearing here, and it deliberately
     reverses what the old debut teaser did in this spot. 4B's FOLLOW ghost
     lane is bottom-anchored and rises past .conversion's own top edge into
     this section's box. Under the old teaser that was wanted: the lane
     stayed visible against the shared black and tucked behind the tape
     graphic alone, so z-index lived on the tape and the section itself was
     left at auto -- putting it on the section would have let the section's
     opaque black paint over the lane everywhere in its box, invisibly,
     black-on-black.
     That is now the goal, not the bug. This section is a different surface
     -- an opaque #232921 field belonging to the show -- and a grey FOLLOW
     lane bleeding up into it reads as a rendering fault, not texture. So
     the whole section, background included, sits above the lane's z-index:0
     and masks it; the lane still paints normally inside 4B below, where it
     belongs. Keep this on the section: moving it to an inner element is
     precisely what would let the lane show through again. */
  position: relative;
  z-index: 1;
  background: #232921;
  padding: clamp(2.5rem, 6vh, 4rem) var(--section-pad-x);
}

.assassins-feature__aside-img {
  /* Same handwriting device as 4B's .conversion__aside-img and deliberately
     the same 5.36rem height, so the two Madelyn asides read as one system.
     Height-sized with width:auto, same as that rule -- each PNG keeps its
     own aspect. Centered rather than left-aligned: this section is
     symmetrical, 4B is not. See the mobile block for why the height, not
     the width, is what gets overridden below 640px. */
  display: block;
  height: 5.36rem;
  width: auto;
  margin: 0 auto clamp(2rem, 5vw, 3rem);
}

.assassins-feature__inner {
  display: grid;
  grid-template-columns: minmax(0, 18rem) minmax(0, 1fr);
  gap: clamp(2rem, 5vw, 3.5rem);
  align-items: center;
  max-width: 58rem;
  margin-inline: auto;
}

.assassins-feature__art img {
  display: block;
  width: 100%;
  height: auto;
}

.assassins-feature__title {
  margin: 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(4.5rem, 9.75vw, 8.25rem);
  line-height: 0.95;
  color: #FFDE00;
}

.assassins-feature__tagline {
  margin: 0.6rem 0 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.98rem, 3.63vw, 2.7225rem);
  line-height: 1.2;
}

.assassins-feature__dates {
  margin: clamp(1.25rem, 3vw, 2rem) 0 0;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(2.32rem, 4.64vw, 3.44rem);
  line-height: 1.1;
  color: #FFDE00;
}

.assassins-feature__venue {
  margin: 0.35rem 0 0;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: clamp(1.5rem, 2.7vw, 1.95rem);
}

.assassins-feature__cta {
  /* Hard edges, uppercase, letter-spacing: the same house button treatment
     as .signup-form__submit. Only the palette differs, because this button
     belongs to the show. */
  display: inline-block;
  margin-top: clamp(1.5rem, 3.5vw, 2.25rem);
  padding: 0.9rem 1.75rem;
  background: #FFDE00;
  color: #232921;
  font-family: var(--font-body);
  font-weight: 700;
  font-size: 1.25rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
}

.assassins-feature__cta:hover,
.assassins-feature__cta:focus-visible {
  background: var(--white);
}

/* Still live: 4B's "...or you find out late" is commented out in the HTML,
   not deleted, and this is the rule it comes back to. */
.debut-teaser__aside {
  /* font-size overrides .mono-aside's shared clamp(1.1rem, 2.4vw, 1.5rem)
     -- scoped here, not changed on .mono-aside itself, since that class is
     reused by the flag-beat and lights-out asides too. Both this and
     margin-top below are the original .mono-aside/margin-top values with
     every clamp() argument scaled by the same factor (1.15x, 1.25x), so
     the responsive curve keeps its shape at every viewport size instead of
     just being right at one width. */
  font-size: clamp(1.265rem, 2.76vw, 1.725rem);
  margin-top: clamp(7.875rem, 22.4vh, 14rem);
  text-align: center;
  opacity: 0.85;
}

.conversion {
  /* Deliberately no overflow:hidden here (unlike .debut-teaser above,
     which still has it) -- the FOLLOW ghost column is bottom-anchored and
     allowed to rise past this section's own top edge so it can pass
     behind .debut-teaser's tape graphic. Each .ghost-column still clips
     its own excess repeated words independently via its own
     overflow:hidden, so this doesn't reintroduce spillover there. */
  padding-top: 0;
}

.ghost-column {
  /* top/height/right are set per-instance by syncConversionGhostColumns()
     in main.js -- each lane must hug a different heading's real
     rendered position, which isn't a fixed value across breakpoints. */
  position: absolute;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1rem, 2.4vw, 1.4rem);
  line-height: 0.6;
  overflow: hidden;
  pointer-events: none;
  user-select: none;
  z-index: 0;
}

.ghost-column--follow {
  color: var(--grey-dark);
}

.ghost-column--signup {
  color: var(--grey-mid);
}

.ghost-column-ampersand {
  /* left/top are set by syncConversionGhostColumns() in main.js, as the
     midpoint of the gap between the two ghost columns on the line where
     they meet -- transform centers the glyph itself on that point. */
  position: absolute;
  transform: translate(-50%, -50%);
  color: var(--white);
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1rem, 2.4vw, 1.4rem);
  line-height: 1;
  pointer-events: none;
  user-select: none;
}

.conversion__inner {
  /* Base position lines up with .debut-teaser__tape above: the tape is
     centered via margin-inline:auto with a fixed 44rem max-width, and its
     own section's horizontal padding is symmetric, so those padding terms
     cancel out of the tape's left-edge position algebraically, leaving a
     plain function of viewport width (tapeLeft = 50vw - 352px). On top of
     that, +300px is a deliberate further nudge right (past the tape's own
     left edge, roughly under "our" in its text) requested after seeing it
     landed flush with the tape -- so the constant below is 352 - 300, not
     the plain 352 the algebra alone would give. Matching this here (minus
     this section's own padding, which is added back by the browser since
     margin-left is relative to the padding edge) rigidly carries the whole
     4B block -- and the ghost columns, which track these headings' real
     position via main.js -- at any desktop width. Floored at 0 so it
     doesn't go negative just above the mobile breakpoint. */
  position: relative;
  z-index: 1;
  max-width: 40rem;
  margin-left: max(0px, calc(44vw - 152px));
  padding-top: 100px;
}

.conversion__aside-img {
  /* ~30% bigger, same as the headings/socials/form below it. */
  height: 5.36rem;
  width: auto;
  margin-bottom: 0.5rem;
}

.conversion__heading {
  font-family: var(--font-display);
  font-weight: 700;
  /* ~30% bigger than the original clamp(2.75rem, 10vw, 6rem). */
  font-size: clamp(3.575rem, 13vw, 7.8rem);
  line-height: 0.95;
}

.conversion__signup .conversion__heading,
.conversion__signup p,
.conversion__signup .signup-form {
  margin-left: clamp(1.5rem, 6vw, 4rem);
}

/* "for our newsletter" -- ~30% bigger, scoped to this section only since
   the bare `p` selector would otherwise touch any future paragraph added
   inside .conversion__signup. */
.conversion__signup p {
  font-size: 1.9rem;
  font-weight: 700;
  color: #cccccc;
}

.conversion__socials {
  margin: 1.5rem 0;
  display: flex;
  flex-direction: column;
  gap: 0.9rem;
}

/* Socials icons + handle: ~30% bigger, but scoped to this section's own
   .social-row/.social-handle instances -- both classes are shared with
   the footer, which must not change. */
.conversion__socials .social-row {
  gap: 1.3rem;
}

.conversion__socials .social-row a {
  width: 3.25rem;
  height: 3.25rem;
}

.conversion__socials .social-handle {
  font-size: 1.9rem;
}

/* SUBMIT button + its text: ~30% bigger, scoped so the Play Reading Club
   form (section 6, which shares .signup-form__submit) is unaffected. The
   email input is deliberately left untouched at its shared size. */
.conversion__signup .signup-form__submit {
  padding: .5rem 1.275rem;
  font-size: 1.6rem;
}

.conversion__signup {
  /* Shifted up by one ghost-column line's worth (line-height 13.44px +
     gap 6.4px = 19.84px at this font-size) from the original
     clamp(2.5rem, 6vh, 4rem), so the SIGN UP ghost lane's first line lands
     on the same row as the FOLLOW lane's last line above. Subtracting a
     flat px constant from every clamp() argument keeps the same shape at
     any viewport height, just raised by that fixed amount everywhere. */
  margin-top: clamp(calc(2.5rem - 19.84px), calc(6vh - 19.84px), calc(4rem - 19.84px));
  margin-left: clamp(15px, calc(7vw - 25px), 55px);
}

/* ==========================================================================
   5 — Who we are
   ========================================================================== */

.who-we-are {
  background: var(--black);
  padding: clamp(4rem, 10vh, 7rem) var(--section-pad-x);
}

.who-we-are__heading {
  max-width: 90vw;
  margin-inline: auto;
  text-align: left;
  font-family: var(--font-display);
  font-weight: 700;
  font-size: clamp(1.8rem, 4.2vw, 2.75rem);
  line-height: 1.25;
}

.who-we-are__heading .accent {
  color: var(--pink);
}

.who-we-are__people {
  max-width: 76.5vw;
  margin-inline: auto;
  margin-top: clamp(3rem, 8vh, 5rem);
  display: grid;
  grid-template-columns: repeat(2, 35%);
  justify-content: space-evenly;
}

.person__photo-wrap {
  position: relative;
  max-width: 20rem;
}

.person__photo-wrap img {
  width: 100%;
  aspect-ratio: 3 / 4;
  object-fit: cover;
}

.person:nth-child(1) .person__photo-wrap { transform: rotate(-3deg); }
.person:nth-child(2) .person__photo-wrap { transform: rotate(2deg); }

.person__photo-wrap .tape {
  position: absolute;
  height: 2.75rem;
  /* Solid strip, not the shared .tape class's jagged clip-path cut. */
  clip-path: none;
}

/* Each piece's center lands exactly on the corner/edge point it overlaps
   (translate ±50%,±50% before rotating), so it straddles evenly -- half
   on the photo, half off -- at any rotation angle. Widths vary per piece
   so the four read as separately torn strips, not copies of one shape. */
.person__photo-wrap .tape--tl { top: 0; left: 0; width: 7rem; transform: translate(-50%, -50%) rotate(-47deg); }
.person__photo-wrap .tape--br { bottom: 0; right: 0; width: 5rem; transform: translate(50%, 50%) rotate(-40deg); }
.person__photo-wrap .tape--tr { top: 0; right: 0; width: 5.5rem; transform: translate(50%, -50%) rotate(37deg); }
.person__photo-wrap .tape--bc { bottom: 0; left: 50%; width: 9rem; transform: translate(-50%, 50%); }

.person__bio {
  margin-top: 3.25rem;
  font-size: 1.09rem;
  color: var(--white);
}

.person__bio a {
  color: var(--white);
  text-decoration-color: var(--grey-hairline);
}

/* ==========================================================================
   6 — Play Reading Club  /  7 — Lights-out + Footer
   ========================================================================== */

.grade-wrap {
  background: var(--black);
}

.reading-club {
  background: var(--grey-dark);
  padding: clamp(4rem, 10vh, 7rem) var(--section-pad-x);
}

.reading-club__container {
  max-width: 76.5vw;
  margin-inline: auto;
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 1.3fr);
  gap: clamp(2rem, 6vw, 4rem);
}

.reading-club__eyebrow {
  font-style: italic;
  color: #e6e6e6;
  font-size: 1.3rem;
}

.reading-club__heading {
  margin-top: 0.75rem;
  font-family: var(--font-display);
  font-weight: 700;
  /* Column width shrinks faster than a plain vw term as the viewport
     narrows (padding/gap clamps hit their own floors), so a straight
     Nvw curve can't stay safe at 768px and still reach 7rem by full
     desktop width -- this calc() pins two measured-safe points instead:
     ~52px at 768px (narrowest two-column width) and 112px by 1500px. */
  font-size: clamp(3.25rem, calc(8.2vw - 11px), 7rem);
  line-height: 0.95;
  color: #bdbdbd;
  text-shadow:
    -0.06em 0.0em 0 var(--grey-mid),
    -0.12em 0 0 var(--black);
}

.reading-club__heading span {
  display: block;
}

.reading-club__body p {
  margin-bottom: 1.25rem;
  font-size: 1.5rem;
}

.reading-club__body em {
  font-style: italic;
}

/* Below this, the two-column split gets too narrow for the heading
   even at its clamped minimum -- stack to one column instead. */
@media (max-width: 768px) {
  .reading-club__container {
    grid-template-columns: 1fr;
  }

  /* Stacked, the heading has the full column to itself again, so it
     should scale off that width instead of staying at the narrow
     two-column clamp's small end. Same measured-anchor approach as the
     two-column fix: ~52px at 320px (smallest phone) up to 112px (7rem,
     matching the desktop max) by 640px; flat at 7rem above that, up to
     this breakpoint's own 768px ceiling. */
  .reading-club__heading {
    font-size: clamp(3.25rem, calc(18.75vw - 8px), 7rem);
  }

}

.lights-out {
  padding: clamp(5rem, 16vh, 9rem) var(--section-pad-x) 0;
  text-align: left;
}

.lights-out__text {
  margin-top: 1rem;
}

.footer {
  /* No top padding: the wordmark column sits flush against Play
     Reading Club's bottom edge (the pull-cord should read as hanging
     from it), so footer content starts right at the section's own top
     edge instead of being pushed down. max-width/margin-inline (not
     the section-gutter padding used elsewhere) is how #reading-club's
     own content column is centered too (.reading-club__container,
     also shared by .who-we-are__people) -- same value, so both line up
     exactly instead of approximately. */
  max-width: 76.5vw;
  margin-inline: auto;
  padding-bottom: clamp(2rem, 5vh, 3rem);
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  align-items: start;
  gap: 2rem 4rem;
}

.footer__lockup {
  width: min(60vw, 16rem);
  margin: auto;
}

.footer__brand {
  display: flex;
  flex-direction: column;
  /* Opts out of the flex default (stretch), which would otherwise
     stretch the pink tape span to the full column width instead of
     hugging its own text. */
  align-items: flex-start;
}

.footer__tape {
  /* Overrides .tape's shared CSS-drawn background/clip-path with the
     PNG treatment instead, scoped to this class only (same pattern as
     .hero__tape .tape) -- .tape itself, and every other element using
     it, is untouched. aspect-ratio matches the PNG's own 500x178 pixel
     size so background-size:100% 100% doesn't stretch/distort it. */
  background-image: url('../images/tape-for-potent-theater.png');
  background-size: 100% 100%;
  background-color: transparent;
  clip-path: none;
  aspect-ratio: 447 / 177;
  /* Symmetric (was 2rem/1.25rem left-right), doubled -- centers the
     text on the tape and sizes the box, which sizes the image with it
     via aspect-ratio, up together. */
  padding: 1.2rem 3.25rem;
  display: inline-block;
  transform: rotate(2deg);
  text-align: center;
  margin-top: 45px;
}

.footer__tape-text {
  font-family: var(--font-body);
  font-weight: 600;
  color: var(--black);
  font-size: 1.9rem;
  line-height: 1.15;
  letter-spacing: 0.1em;
}

.footer__la {
  margin-top: .5rem;
  margin-left: 1.2rem;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 2.2rem;
}

/* Same word-level treatment as .hero__la (see .word--los/.word--angeles
   there). The rotation angles are unchanged (degrees don't need
   scaling), but the offset is doubled along with the text size so
   "Angeles" keeps the same proportional drop relative to "Los". */
.footer__la .word--los {
  display: inline-block;
  transform: rotate(-1deg);
}

.footer__la .word--angeles {
  display: inline-block;
  transform: translateY(20px) rotate(8deg);
}

.footer__meta {
  display: flex;
  flex-direction: column;
  gap: 1.75rem;
  margin-top: 45px;
}

.footer__meta h3 {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 2.1rem;
  margin-bottom: 0.5rem;
}

.footer__email {
  /* 75% larger than the 16px browser-default this was inheriting. */
  font-size: 1.45rem;
}

.footer__copyright {
  /* Spans just the wordmark + middle columns (not the full 3-column
     row), so it centers under those two only. */
  grid-column: 1 / 3;
  text-align: center;
  color: #9a9a9a;
  font-size: 1.1rem;
  transform: translateY(-10px) rotate(2.2deg);
}

/* ==========================================================================
   Responsive — mobile
   ========================================================================== */

@media (max-width: 900px) {
  .footer {
    grid-template-columns: 1fr;
    text-align: center;
  }

  /* Desktop's grid-column: 1 / 3 (line ~1011) spans 2 of the 3 desktop
     columns -- with only 1 explicit column here, that stray line-3
     reference forces the browser to fabricate an implicit second
     column to satisfy it, and .footer__brand (not .footer__copyright)
     is the sibling that lands in it instead of stacking normally --
     pushing it outside the viewport since there's nothing else to
     shrink it back in. Resetting to auto lets this sit in the single
     real column like everything else. */
  .footer__copyright {
    grid-column: auto;
  }

  .footer__brand,
  .footer__meta {
    align-items: center;
  }

  /* 4A stacks at 900, not at the 768 it was first written for and not at
     the 640 the rest of the page uses. "ASSASSINS" is one unbreakable word,
     so its min-content width is simply its rendered width -- there is no
     wrap point to relieve pressure. Below ~804px viewport that width ran
     past the right edge of the screen entirely, so the two-column layout
     has to be gone before that width is reached, not merely narrowed. */
  .assassins-feature__inner {
    grid-template-columns: minmax(0, 1fr);
    justify-items: center;
    text-align: center;
    gap: 2rem;
  }

  .assassins-feature__art {
    width: 100%;
    max-width: 20rem;
  }

  /* The base rule's 4.5rem floor is what broke phones, and it broke more
     than the heading: justify-items:center shrink-wraps this grid item, so
     the item can be no narrower than the title's min-content. At 4.5rem
     that was 392.5px against a 327px content box at 375px wide, which
     dragged the whole text block past BOTH viewport edges and took the
     centered tagline and dates out with it -- they were never independently
     too wide. Sizing the title to fit is therefore the entire fix.
     14.5vw is measured, not guessed: this face renders "ASSASSINS" at
     ~5.45x its font-size, so 14.5vw keeps the word inside the content box
     (which is viewport minus 2x --section-pad-x) across the whole stacked
     range, with the tightest margin ~19px at 320px. The 8.25rem ceiling is
     the base rule's, kept so nothing shrinks on the wide side of stacked. */
  .assassins-feature__title {
    font-size: clamp(2.75rem, 14.5vw, 8.25rem);
  }
}

@media (max-width: 640px) {
  .hero {
    min-height: auto;
    padding-bottom: 10rem;
  }

  .hero__lockup {
    width: 85vw;
    margin-left: 0;
  }

  .hero__flag-text {
    gap: 2.5rem;
    margin-top:230px;
  }

  .flag__flag-rotate {
    transform: none;
  }

  /* The sticky-overlap reveal is a desktop scroll effect; on mobile,
     Manifesto just follows Hero normally, no overlap, sticking, or fade
     -- the !important overrides js/main.js's inline opacity too. */
  .manifesto {
    position: static;
    margin-top: 0 !important;
    opacity: 1 !important;
  }

  .manifesto-reveal-spacer {
    display: none;
  }

  .manifesto__body {
    flex-direction: column;
  }

  .manifesto__lines {
    font-size: clamp(1.6rem, 7.5vw, 2.25rem);
    line-height: 1.08;
  }

  /* "Potent" / "Theater" split to their own stacked lines here, each
     fit independently to full width by syncManifestoTitle(). The words
     stay inline-block (see base rule) -- only the break between them
     switches on, forcing the second word onto its own line. */
  .manifesto__title-break {
    display: inline;
  }

  .video-frame__media {
    margin-top:35px;
  }

  .video-frame {
    width: 100% !important;
    max-width: 20rem;
    transform: rotate(2deg);
  }

  /* The one place this aside can't mirror 4B's rule literally. Both are
     height-sized, but this PNG is 500x100 (aspect 5.0) against 4B's 3.333,
     so at a shared 5.36rem height it renders 428.8px wide -- wider than the
     content box below a ~487px viewport, and capping width alone would
     squash it, since height would stay pinned. Driving off width with
     height:auto keeps the aspect and lands ~64px tall, within a hair of the
     4.36rem (69.75px) 4B drops to here -- so the two still read as a pair. */
  .assassins-feature__aside-img {
    width: min(100%, 20rem);
    height: auto;
  }

  .conversion__aside-img {
    height: 4.36rem;
  }

  .who-we-are__people {
    grid-template-columns: 1fr;
    gap: 3.5rem;
  }

  .ghost-column,
  .ghost-column-ampersand {
    display: none;
  }

  .conversion__inner {
    margin-left: 0;
  }

  .conversion__signup {
    margin-left: 0;
  }

  .signup-form {
    flex-direction: column;
  }

  .signup-form__submit {
    align-self: flex-start;
  }
  .footer__tape {
    margin-top: 15px;
  }
}
