:root {
  --color-bg: #0B0C0E;
  --color-text: #F2F1ED;
  --color-accent: #8A8F98;

  --font-display: 'Bebas Neue', sans-serif;
  --font-body: 'Archivo', sans-serif;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  scroll-behavior: smooth;
}

body {
  background-color: var(--color-bg);
  color: var(--color-text);
  font-family: var(--font-body);
  overflow-x: hidden;
}

a {
  color: inherit;
}

/* ---------- Nav ---------- */

.site-nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 100;
  background-color: transparent;
  border-bottom: 1px solid transparent;
  transition: background-color 0.3s ease, border-color 0.3s ease;
}

.site-nav.is-scrolled {
  background-color: rgba(11, 12, 14, 0.9);
  border-bottom-color: rgba(138, 143, 152, 0.2);
  /* Restored 08.06.2026, per Xavier, after seeing it live. This was removed
     for perf (a fixed backdrop-filter re-blurs its backdrop every scrolled
     frame) on the assumption that a 90%-opaque background over it made the
     blur nearly invisible anyway — verified against measurements, not real
     eyes on the real page. It reads as a visible loss live: the small sliver
     of blurred content still showing through is apparently more noticeable
     than the numbers suggested, especially against a moving video. Design
     wins this trade. If the top of the page starts lagging again, that's
     this line — the fix is elsewhere (the video/filter work), not here. */
  backdrop-filter: blur(6px);
}

.nav-inner {
  max-width: 1400px;
  margin: 0 auto;
  padding: 1.25rem 2rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-wrap: nowrap;
}

.nav-logo {
  font-family: var(--font-display);
  font-size: 1.25rem;
  letter-spacing: 0.05em;
  text-decoration: none;
  white-space: nowrap;
  flex-shrink: 0;
}

/* Just the "II" spins in place — "Chapter" stays plain static text. Sized to
   roughly the display font's cap-height (not a full 1em line) and nudged with
   a small negative vertical-align offset so the spinning glyph's top/bottom
   lines up with "Chapter"'s own letters instead of floating inside a taller,
   baseline-anchored box. margin-left is the real gap to "Chapter" now (the
   markup's own text no longer relies on a literal trailing space), sized with
   room for the 3D rotation's perspective foreshortening so the spin never
   reads as crowding the word next to it. */
.nav-logo-ii {
  display: inline-block;
  position: relative;
  width: 0.62em;
  height: 0.74em;
  margin-left: 0.3em;
  perspective: 200px;
  vertical-align: -0.03em;
}

.nav-logo-ii-inner {
  position: absolute;
  inset: 0;
  transform-style: preserve-3d;
  animation: navLogoSpin 7s linear infinite;
}

/* Two faces rotated 180deg apart, each hiding its own backface, so the
   readable side is always the one facing the viewer instead of showing a
   mirrored "II" for half of every rotation. Flex-centered so the glyph sits
   in the middle of its (shorter, cap-height-sized) box rather than hugging
   the box's own baseline-relative top/bottom. */
.nav-logo-ii-face {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  line-height: 1;
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}

/* SITE-CHANGES.md C1 (08.05): glyph moved into generated content instead of
   a real text node — ::before content isn't part of innerText/textContent,
   so it can't be read twice by copy-paste or a DOM-text scrape the way two
   literal "II" spans could, even with aria-hidden on one of them (aria-hidden
   only affects assistive tech, not raw text extraction). The <a> now carries
   aria-label="Chapter II" for the accessible name instead. */
.nav-logo-ii-face::before {
  content: "II";
}

.nav-logo-ii-face-back {
  transform: rotateY(180deg);
}

@keyframes navLogoSpin {
  from { transform: rotateY(0deg); }
  to { transform: rotateY(360deg); }
}

.nav-links {
  list-style: none;
  display: flex;
  gap: 2rem;
}

.nav-links a {
  font-family: var(--font-body);
  font-size: 0.8rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-accent);
  text-decoration: none;
  white-space: nowrap;
  transition: color 0.2s ease;
  position: relative;
}

/* Tap targets (08.05.2026): the nav links render ~12-20px tall, well under
   the 44px minimum. Expanding the anchors themselves would grow the nav bar
   and push the whole layout around, so the hit area is enlarged with an
   invisible overlay instead — nothing moves, nothing looks different, the
   tappable region is just bigger than the glyphs.

   Horizontal growth is capped at half the flex gap per side so adjacent
   links' hit areas meet exactly rather than overlapping (an overlap would
   make the boundary tap ambiguous — the wrong link would win). */
.nav-links a::after {
  content: '';
  position: absolute;
  left: -0.4rem;
  right: -0.4rem;
  top: 50%;
  transform: translateY(-50%);
  height: 44px;
}

.nav-links a:hover {
  color: var(--color-text);
}

@media (max-width: 600px) {
  .nav-inner {
    padding: 1rem 1.25rem;
  }
  .nav-logo {
    font-size: 1.05rem;
  }
  .nav-links {
    /* 0.6rem -> 0.8rem (08.05.2026): sized so half the gap per side is
       0.4rem, which is exactly what the ::after hit area claims — enough
       for the shortest label ("Shop") to clear 44px wide without adjacent
       links' tap areas overlapping each other. */
    gap: 0.8rem;
  }
  .nav-links a {
    font-size: 0.68rem;
    letter-spacing: 0.04em;
  }
}

/* ---------- Hero ---------- */

.hero {
  position: relative;
  height: 100vh;
  width: 100%;
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: var(--color-bg);
}

.video-embed {
  /* script.js sets this element's background-image to the video's own
     YouTube thumbnail on mount, so the mask window below (opacity:0 iframe)
     reads as "this is loading" rather than an empty gap — measured against
     the real production deploy at up to ~4.5s. */
  background-size: cover;
  background-position: center;
  background-repeat: no-repeat;
}

.video-embed iframe {
  border: 0;
  display: block;
  /* Hidden until script.js confirms real playback (YT.PlayerState.PLAYING),
     not just on mount — YouTube's own "about to play" chrome (title, channel,
     play/pause/next cluster) is a real rendered state that controls=0 doesn't
     suppress, so revealing immediately on mount would show it. Whatever
     renders during that startup window is masked behind this instead. */
  opacity: 0;
  transition: opacity 0.3s ease;
}

.video-embed.is-playing iframe {
  opacity: 1;
}

.hero-video-embed {
  position: absolute;
  inset: 0;
  overflow: hidden;
  /* Restored 08.06.2026, per Xavier, after seeing it live. Briefly cut to
     grayscale(0.55) alone for perf, with .hero-overlay's alphas raised to
     algebraically compensate for the dropped brightness(0.55) — verified by
     pixel-diffing a synthetic test frame (max 8/255) rather than the real,
     moving, compressed video. The color read differently on the actual
     hero footage than the math predicted; design wins this trade over the
     filter-pass cost. .hero-overlay's alphas are reverted alongside this in
     the same commit — the two only make sense read together. */
  filter: grayscale(0.55) brightness(0.55) contrast(1.05);
}

.hero-video-embed iframe {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 100vw;
  height: 56.25vw; /* 16:9 */
  min-height: 100vh;
  min-width: 177.78vh; /* 16:9 */
  transform: translate(-50%, -50%);
  pointer-events: none;
}

.hero-overlay {
  position: absolute;
  inset: 0;
  /* Reverted to original alphas 08.06.2026 alongside .hero-video-embed's
     filter (see the note there) — they were only ever raised to compensate
     for that filter's dropped brightness(0.55), and now that it's back,
     stacking both would double-darken the hero. */
  background: linear-gradient(180deg,
    rgba(11, 12, 14, 0.35) 0%,
    rgba(11, 12, 14, 0.55) 55%,
    rgba(11, 12, 14, 0.85) 100%);
}

.hero-content {
  position: relative;
  z-index: 2;
  text-align: center;
  padding: 0 1.5rem;
}

.hero-headline {
  font-family: var(--font-display);
  font-size: clamp(3.5rem, 12vw, 9rem);
  line-height: 0.95;
  letter-spacing: 0.03em;
  color: var(--color-text);
  opacity: 0;
  animation: heroSlideInLeft 1.1s cubic-bezier(0.16, 1, 0.3, 1) forwards;
  animation-delay: calc(var(--delay, 0) * 0.18s + 0.3s);
  will-change: transform, filter, opacity;
}

.hero-subhead {
  font-family: var(--font-body);
  font-weight: 400;
  font-size: clamp(1rem, 2vw, 1.35rem);
  color: var(--color-accent);
  margin-top: 1rem;
  letter-spacing: 0.01em;
  opacity: 0;
  animation: heroSlideInRight 1.1s cubic-bezier(0.16, 1, 0.3, 1) forwards;
  animation-delay: calc(var(--delay, 0) * 0.18s + 0.3s);
  will-change: transform, filter, opacity;
}

/* Slide-in-and-settle: fast, blurred entry that ramps down to a sharp stop.
   Opacity resolves early so the whole travel reads as a blurred pass, not a fade. */
@keyframes heroSlideInLeft {
  0% {
    opacity: 0;
    transform: translateX(-9vw);
    filter: blur(18px);
  }
  12% {
    opacity: 1;
  }
  100% {
    opacity: 1;
    transform: translateX(0);
    filter: blur(0);
  }
}

@keyframes heroSlideInRight {
  0% {
    opacity: 0;
    transform: translateX(9vw);
    filter: blur(18px);
  }
  12% {
    opacity: 1;
  }
  100% {
    opacity: 1;
    transform: translateX(0);
    filter: blur(0);
  }
}

/* Scroll-scrubbed shimmer: a light sweep across "CHAPTER II" whose position
   is driven directly by scroll progress in script.js (element.style.
   backgroundPosition), not a replayed CSS keyframe animation — each bit of
   scrolling advances the sweep, scrolling back reverses it. The base
   background-position below (280% 0) is the sweep's "not started yet" state
   and matches script.js's own starting value, so there's no flash before JS
   runs. Gated behind @supports so browsers without text-clip gradients keep
   the plain solid color instead of falling back to invisible transparent
   text. script.js independently checks prefers-reduced-motion before ever
   touching background-position, so under reduced motion this just sits at
   its static base position. */
@supports ((background-clip: text) or (-webkit-background-clip: text)) {
  .hero-headline {
    color: transparent;
    /* The site's text color is already near-white (#F2F1ED), so a plain white
       peak against it barely registers. Dipping to the darker --color-accent
       just before/after the peak (a genuine light-vs-shadow swing, not just
       off-white-vs-white) is what makes the sweep actually read as light
       passing over the text, the way a shine crosses glass or metal. */
    background-image: linear-gradient(100deg,
      var(--color-text) 0%,
      var(--color-text) 34%,
      var(--color-accent) 44%,
      #ffffff 50%,
      var(--color-accent) 56%,
      var(--color-text) 66%,
      var(--color-text) 100%);
    background-size: 280% 100%;
    background-position: 280% 0;
    -webkit-background-clip: text;
    background-clip: text;
  }
}

/* ---------- Scroll cue ---------- */

.scroll-cue {
  /* Centered via left/right + margin auto, deliberately NOT transform:
     translateX(-50%) — this element also carries .reveal-line, whose entrance
     animation drives `transform` (translateY) on load. Both rules setting
     `transform` would collide (animation always wins), silently dropping the
     horizontal centering the moment the page loads and leaving the cue
     sitting ~28px right of true center. Keeping centering off the transform
     property entirely sidesteps the collision rather than reordering it. */
  position: absolute;
  bottom: 2.5rem;
  left: 0;
  right: 0;
  width: fit-content;
  margin: 0 auto;
  z-index: 2;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  color: var(--color-accent);
  text-decoration: none;
  opacity: 0.85;
}

.scroll-cue-label {
  font-family: var(--font-body);
  font-size: 0.7rem;
  font-weight: 500;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  /* A tiny static glow, not a pulse — asked for pulsing first, then walked it
     back to just a slight glow. */
  text-shadow: 0 0 6px rgba(242, 241, 237, 0.45);
}

.scroll-cue-arrow {
  width: 1px;
  height: 28px;
  background: rgba(138, 143, 152, 0.4);
  position: relative;
  overflow: hidden;
}

.scroll-cue-arrow::after {
  content: '';
  position: absolute;
  top: -100%;
  left: 0;
  width: 100%;
  height: 100%;
  background: var(--color-text);
  animation: scrollLine 1.8s ease-in-out infinite;
}

@keyframes scrollLine {
  0% { top: -100%; }
  60% { top: 100%; }
  100% { top: 100%; }
}

/* ---------- Entrance reveal (load, not scroll) ---------- */

.reveal-line {
  opacity: 0;
  transform: translateY(-18px);
  animation: revealLine 0.8s cubic-bezier(0.22, 1, 0.36, 1) forwards;
  animation-delay: calc(var(--delay, 0) * 0.18s + 0.3s);
}

@keyframes revealLine {
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ---------- Work / Reel ---------- */

.work {
  min-height: 100vh;
  display: flex;
  align-items: center;
  padding: 8rem 2rem;
}

.work-inner {
  max-width: 1100px;
  width: 100%;
  margin: 0 auto;
}

.section-label {
  display: block;
  font-family: var(--font-body);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--color-accent);
  margin-bottom: 1.5rem;
}

.reel-frame {
  position: relative;
  width: 100%;
  aspect-ratio: 16 / 9;
  background-color: #111318;
  border: 1px solid rgba(138, 143, 152, 0.25);
  overflow: hidden;
  display: flex;
  align-items: center;
  justify-content: center;
}

.reel-video-embed {
  position: absolute;
  inset: 0;
}

.reel-video-embed iframe {
  width: 100%;
  height: 100%;
}

.reel-placeholder-label {
  position: relative;
  z-index: 1;
  font-family: var(--font-body);
  font-size: 0.85rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--color-accent);
}

.reel-frame.has-video .reel-placeholder-label {
  display: none;
}

.reel-specs {
  font-family: var(--font-body);
  font-size: clamp(0.95rem, 1.4vw, 1.15rem);
  color: var(--color-text);
  line-height: 1.5;
  max-width: 640px;
  margin-top: 1.75rem;
}

.clip-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
  gap: 1rem;
  margin-top: 2.5rem;
}

.clip-slot {
  aspect-ratio: 9 / 16;
  border: 1px dashed rgba(138, 143, 152, 0.35);
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 1rem;
  font-family: var(--font-body);
  font-size: 0.8rem;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--color-accent);
  position: relative;
  overflow: hidden;
}

.clip-slot.has-media {
  border-style: solid;
  border-color: rgba(138, 143, 152, 0.25);
  padding: 0;
}

.clip-video-embed {
  position: absolute;
  inset: 0;
}

.clip-video-embed iframe {
  width: 100%;
  height: 100%;
  pointer-events: none;
}

/* ---------- Pricing / Call Sheet ---------- */

.pricing {
  min-height: 100vh;
  display: flex;
  align-items: center;
  padding: 8rem 2rem;
}

.pricing-inner {
  max-width: 1100px;
  width: 100%;
  margin: 0 auto;
}

.pricing-title {
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 6vw, 4.5rem);
  line-height: 1;
  letter-spacing: 0.02em;
  color: var(--color-text);
  margin-bottom: 3rem;
  /* SITE-CHANGES.md A2: reserved in em (self-adapting to the clamp() above)
     so the longest heading ("The Partnership"), which can wrap to 2 lines on
     narrow screens, never shifts the tab row/cards below it on crossfade. */
  min-height: 2em;
}

.pricing-title-text {
  display: block;
  transition: opacity 0.2s cubic-bezier(0.16, 1, 0.3, 1);
}

.pricing-title-text.is-swapping {
  opacity: 0;
}

.call-sheet {
  border-top: 1px solid rgba(138, 143, 152, 0.3);
}

.scene {
  display: grid;
  grid-template-columns: 1fr 2fr 1fr;
  gap: 2rem;
  padding: 2.5rem 0;
  border-bottom: 1px solid rgba(138, 143, 152, 0.3);
}

.field-label {
  display: block;
  font-family: var(--font-body);
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.15em;
  text-transform: uppercase;
  color: var(--color-accent);
  margin-bottom: 0.6rem;
}

.scene-name h3 {
  font-family: var(--font-display);
  font-size: 1.8rem;
  letter-spacing: 0.02em;
  color: var(--color-text);
  font-weight: normal;
}

.scene-shotlist ul {
  list-style: none;
}

.scene-shotlist li {
  font-family: var(--font-body);
  font-size: 0.95rem;
  color: var(--color-text);
  line-height: 1.6;
  padding-left: 1.1rem;
  position: relative;
}

.scene-shotlist li::before {
  content: '—';
  position: absolute;
  left: 0;
  color: var(--color-accent);
}

.scene-budget .price {
  font-family: var(--font-display);
  font-size: 1.8rem;
  letter-spacing: 0.02em;
  color: var(--color-text);
}

/* ---------- Pricing tabs + calculator (PRICES.md tracks) ---------- */

.pricing-tabs {
  display: flex;
  justify-content: center;
  flex-wrap: wrap;
  gap: 0.5rem 2rem;
  border-bottom: 1px solid rgba(138, 143, 152, 0.3);
  padding-bottom: 1.25rem;
  margin-bottom: 2rem;
}

.pricing-tab {
  appearance: none;
  background: none;
  border: none;
  padding: 0.4rem 0.1rem;
  font-family: var(--font-body);
  font-size: 0.8rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-accent);
  cursor: pointer;
  position: relative;
}

/* Tap target (08.05.2026): tabs render ~24px tall, under the 44px minimum.
   Same invisible-overlay approach as the nav links, but ::before here since
   ::after is already the active-tab underline below.

   Vertical only — deliberately no left/right growth. The tab row is an
   overflow-x: auto scroller on mobile, and script.js toggles .is-scrollable
   off a scrollWidth vs clientWidth comparison; widening the first/last tab's
   box would inflate scrollWidth and could flip that check on when the tabs
   actually fit fine. Not needed anyway — every tab already measures 48-60px
   wide, so only the height was ever short. */
.pricing-tab::before {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  height: 44px;
}

.pricing-tab::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: -1.26rem;
  height: 2px;
  background: var(--color-text);
  transform: scaleX(0);
  transition: transform 0.25s ease;
}

.pricing-tab:hover,
.pricing-tab:focus-visible {
  color: var(--color-text);
}

.pricing-tab.is-active {
  color: var(--color-text);
}

.pricing-tab.is-active::after {
  transform: scaleX(1);
}

.pricing-estimate {
  text-align: center;
  font-family: var(--font-body);
  font-size: 0.9rem;
  color: var(--color-accent);
  margin-bottom: 2.5rem;
  transition: color 0.2s ease;
}

.pricing-estimate.has-selection {
  color: var(--color-text);
}

.pricing-panel[hidden] {
  display: none;
}

.scene-selectable {
  cursor: pointer;
  transition: background-color 0.2s ease, box-shadow 0.2s ease;
}

.scene-selectable:hover {
  background-color: rgba(242, 241, 237, 0.03);
}

.scene-selectable:focus-visible {
  outline: none;
  box-shadow: inset 0 0 0 1px rgba(242, 241, 237, 0.5);
}

.scene-selectable.is-selected {
  background-color: rgba(242, 241, 237, 0.05);
  box-shadow: inset 3px 0 0 var(--color-text);
}

.pricing-note-intro {
  margin-top: 0;
  margin-bottom: 1.5rem;
}

.partner-terms {
  list-style: none;
  max-width: 480px;
  margin: 2rem auto 0;
}

.partner-terms li {
  font-family: var(--font-body);
  font-size: 0.85rem;
  color: var(--color-accent);
  line-height: 1.6;
  padding-left: 1.1rem;
  position: relative;
  text-align: left;
}

.partner-terms li::before {
  content: '—';
  position: absolute;
  left: 0;
}

.placeholder-flag {
  display: block;
  font-family: var(--font-body);
  font-size: 0.65rem;
  font-weight: 500;
  font-style: italic;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--color-accent);
  margin-top: 0.4rem;
}

.pricing-note {
  font-family: var(--font-body);
  font-size: 1rem;
  color: var(--color-accent);
  max-width: 640px;
  margin-top: 2.5rem;
  margin-left: auto;
  margin-right: auto;
  text-align: center;
}

.pricing-note + .pricing-note {
  margin-top: 0.75rem;
}

@media (max-width: 800px) {
  .scene {
    grid-template-columns: 1fr;
    gap: 1rem;
  }

  .pricing-tabs {
    /* Wrapping to a second row broke the active-tab underline: it's offset a
       fixed amount below the tab's OWN row, so on a wrapped layout that
       offset could land it overlapping the next row entirely (verified: at
       375px the "Ad Ready" underline rendered on top of the wrapped
       "Partner" row). Horizontal scroll keeps everything on one row, so the
       underline is always directly under its own tab regardless of viewport
       width — the standard mobile tab-bar pattern for exactly this reason.

       Tightened gap/type below (0.72rem/1.5rem gap -> 0.66rem/0.6rem gap)
       measured to comfortably fit all 4 tabs on one line with margin to
       spare from ~360px width up — verified: content needs ~260px, 360px
       viewports give ~296px to work with. Centered by default so it reads
       as one deliberate row, matching the rest of the page, instead of
       stranded against the left edge. Only screens narrower than that
       (rare — sub-iPhone-SE) still overflow, at which point script.js
       flips on .is-scrollable below: left-aligned so scrolling starts from
       the first tab, with a right-edge fade signaling there's more instead
       of an abrupt, unexplained cut-off mid-label. */
    flex-wrap: nowrap;
    justify-content: center;
    gap: 0.45rem;
    overflow-x: auto;
    -webkit-overflow-scrolling: touch;
  }

  .pricing-tabs.is-scrollable {
    justify-content: flex-start;
    -webkit-mask-image: linear-gradient(to right, black calc(100% - 28px), transparent 100%);
    mask-image: linear-gradient(to right, black calc(100% - 28px), transparent 100%);
  }

  .pricing-tab {
    font-size: 0.64rem;
    letter-spacing: 0.06em;
    flex-shrink: 0;
  }
}

/* ---------- Merch / Shop ---------- */

.merch {
  min-height: 100vh;
  display: flex;
  align-items: center;
  padding: 8rem 2rem;
  text-align: center;
}

.merch-inner {
  max-width: 1100px;
  width: 100%;
  margin: 0 auto;
}

.merch-title {
  font-family: var(--font-display);
  font-size: clamp(2.5rem, 6vw, 4.5rem);
  line-height: 1;
  letter-spacing: 0.02em;
  color: var(--color-text);
  margin-top: 1.5rem;
}

.merch-intro {
  font-family: var(--font-body);
  font-size: clamp(1rem, 1.6vw, 1.2rem);
  color: var(--color-accent);
  max-width: 480px;
  line-height: 1.5;
  margin: 1.25rem auto 0;
}

.merch-showcase {
  position: relative;
  width: 100%;
  max-width: 640px;
  aspect-ratio: 16 / 9;
  margin: 3.5rem auto 0;
}

.merch-glow {
  position: absolute;
  top: -15%;
  left: 50%;
  width: 130%;
  height: 90%;
  transform: translateX(-50%);
  /* Perf 08.06.2026: dropped filter: blur(12px). This is a large element
     sitting directly behind the WebGL canvas and under the fixed Follow
     button, so it was both a filter pass of its own and an expensive
     backdrop for that button to re-blur. Blurring a radial gradient is
     close to a no-op visually — a gradient is already a smooth ramp — so
     the stops are simply spread a little wider to recover the softness the
     blur was contributing, at zero rendering cost. */
  background: radial-gradient(ellipse at center,
    rgba(184, 148, 98, 0.16) 0%,
    rgba(184, 148, 98, 0.09) 35%,
    rgba(184, 148, 98, 0.04) 55%,
    rgba(11, 12, 14, 0) 80%);
  pointer-events: none;
}

.merch-video-embed {
  position: absolute;
  inset: 0;
  overflow: hidden;
  border: 1px solid rgba(138, 143, 152, 0.25);
}

.merch-video-embed iframe {
  width: 100%;
  height: 100%;
  pointer-events: none;
}

.shirt-viewer {
  position: absolute;
  inset: 0;
  z-index: 2;
  opacity: 0;
  transition: opacity 1s ease;
  touch-action: none;
}

.shirt-viewer.is-ready {
  opacity: 1;
}

.shirt-viewer canvas {
  display: block;
  width: 100%;
  height: 100%;
}

.merch-showcase.has-shirt .merch-video-embed {
  opacity: 0;
  transition: opacity 0.6s ease;
}

.merch-cta {
  display: inline-block;
  margin-top: 3rem;
  padding: 0.9rem 2.2rem;
  border: 1px solid rgba(138, 143, 152, 0.4);
  font-family: var(--font-body);
  font-size: 0.8rem;
  font-weight: 500;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--color-text);
  text-decoration: none;
  background: none;
  appearance: none;
  cursor: pointer;
  transition: border-color 0.2s ease, background-color 0.2s ease;
}

.merch-cta:hover {
  border-color: var(--color-text);
  background-color: rgba(242, 241, 237, 0.05);
}

.merch-inner .placeholder-flag {
  display: block;
  margin-top: 0.75rem;
}

.notify-form {
  max-width: 480px;
  margin: 2.5rem auto 0;
  text-align: left;
  display: flex;
  flex-direction: column;
  gap: 1.25rem;
}

.form-field .field-label {
  margin-bottom: 0.5rem;
}

.form-field input {
  width: 100%;
  background: transparent;
  border: 1px solid rgba(138, 143, 152, 0.35);
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: 0.95rem;
  padding: 0.65rem 0.75rem;
  border-radius: 2px;
  transition: border-color 0.2s ease;
}

.form-field input:focus {
  outline: none;
  border-color: var(--color-text);
}

.notify-form .merch-cta {
  align-self: center;
  margin-top: 0.25rem;
}

/* ---------- Footer ---------- */

.site-footer {
  border-top: 1px solid rgba(138, 143, 152, 0.3);
  padding: 2.5rem 2rem;
  /* Extra bottom clearance so the icon row never sits under the fixed Follow
     button once the page is scrolled to its end — found during a pre-launch
     walkthrough: the last icon (WhatsApp) was fully hidden behind it at
     mobile widths, where the horizontally-centered icon row's right edge
     approaches the same corner the button is pinned to. 24px (button's
     bottom offset) + 44px (button height) + 24px breathing room = 92px. */
  padding-bottom: 92px;
}

.footer-inner {
  max-width: 1100px;
  margin: 0 auto;
  display: flex;
  justify-content: center;
}

.footer-socials {
  display: flex;
  align-items: center;
  gap: 1.5rem;
}

.footer-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  color: var(--color-accent);
  transition: color 0.2s ease;
}

.footer-icon:hover,
.footer-icon:focus-visible {
  color: var(--color-text);
}

.footer-icon svg {
  width: 20px;
  height: 20px;
  fill: currentColor;
}

/* Lucide's mail glyph is stroke-drawn, not filled — an outline icon mixed in
   among the filled Simple Icons brand marks, per the spec (deliberate choice,
   not a mismatch to "fix" by re-filling it). */
.footer-icon svg.icon-stroke {
  fill: none;
}

/* ---------- Follow button (persistent, global) ---------- */

.follow-button {
  position: fixed;
  bottom: 24px;
  right: 24px;
  z-index: 40;

  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  padding: 10px 18px;
  min-height: 44px;

  background: rgba(242, 241, 237, 0.08);
  backdrop-filter: blur(8px);
  -webkit-backdrop-filter: blur(8px);
  border: 1px solid rgba(242, 241, 237, 0.35);
  border-radius: 999px;

  /* 08.05.2026: was a hardcoded #F2F1ED (carried over verbatim from the
     code block in Follow-Button-Instructions.md). Same value as
     --color-text — swapped to the variable so the palette lives in one
     place. Purely a maintainability change, renders identically.
     The pill shape, glass background, and px sizing are NOT drift and were
     deliberately left alone: that file specs this as a "glass/see-through
     pill," visually distinct from the square .merch-cta on purpose. */
  color: var(--color-text);
  font-family: var(--font-body);
  font-size: 13px;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  text-decoration: none;

  transition: background 0.2s ease, border-color 0.2s ease;
}

.follow-button:hover,
.follow-button:focus-visible {
  background: rgba(242, 241, 237, 0.16);
  border-color: rgba(242, 241, 237, 0.6);
}

.follow-button svg {
  width: 16px;
  height: 16px;
  fill: var(--color-text);
}

@media (max-width: 600px) {
  .footer-socials {
    gap: 1rem;
  }
}

/* ---------- Scroll-triggered reveal (all sections after hero) ---------- */

.reveal-item {
  opacity: 0;
  transform: translateY(-24px);
  transition: opacity 0.7s cubic-bezier(0.22, 1, 0.36, 1),
              transform 0.7s cubic-bezier(0.22, 1, 0.36, 1);
  transition-delay: calc(var(--delay, 0) * 0.15s);
}

.reveal-section.in-view .reveal-item {
  opacity: 1;
  transform: translateY(0);
}

@media (prefers-reduced-motion: reduce) {
  html {
    scroll-behavior: auto;
  }
  .reveal-line {
    animation: none;
    opacity: 1;
    transform: none;
  }
  .hero-headline,
  .hero-subhead {
    animation: none;
    opacity: 1;
    transform: none;
    filter: none;
  }
  .scroll-cue-arrow::after {
    animation: none;
    display: none;
  }
  .nav-logo-ii-inner {
    animation: none;
  }
  .pricing-title-text {
    transition: none;
  }
  .reveal-item {
    transition: none;
    opacity: 1;
    transform: none;
  }
  .shirt-viewer,
  .merch-showcase.has-shirt .merch-video-embed {
    transition: none;
  }
  .video-embed iframe {
    /* Keep the masking itself (opacity still gated on .is-playing — that's
       hiding transient YouTube UI, not a decorative animation) but drop the
       fade so it's an instant cut instead of an animated transition. */
    transition: none;
  }
}
