/* ================================================================
   COMPONENT: HERO
   Full-bleed image section, content bottom-left.
   ================================================================ */

.hero {
  /* Same horizontal inset as section/footer: gutter-inner so content aligns and feels less tight */
  --hero-inset: 16px;
  width: calc(100% - 2 * var(--gutter-inner));
  margin-top: calc(72px + var(--hero-inset)); /* 16px top + 56px navbar */
  margin-bottom: var(--gutter-inner);
  margin-left: var(--gutter-inner);
  margin-right: var(--gutter-inner);
  position: relative;
  /* Deep teal UNDER the shader canvas. The teal is normally painted by
     WebGL, but a cross-document view transition snapshots the incoming
     page at `pagereveal` — BEFORE first paint, so p5 hasn't booted and the
     canvas is still blank. Without this the hero captured as bare paper: a
     near-white block where the teal should be, most obvious at the end of
     the fold when the homepage is fully revealed. Also covers first load
     and any no-WebGL fallback. */
  background-color: var(--surface-hero);
  border-radius: var(--radius-panel);
  corner-shape: squircle;   /* Apple continuous corner */
  overflow: hidden;
  /* SMALL viewport, not large. Mobile browsers resolve `vh` against the
     viewport with the retractable toolbar HIDDEN — 40–110px taller than what
     is actually visible when the page loads. Sized in `vh`, the hero's bottom
     edge and its radius land below the fold and the panel reads as cut off.
     `svh` is the height with the toolbar shown, which is the state the page
     loads in; the `vh` line above it is the fallback for engines without
     small-viewport units, and resolves identically on desktop where the two
     are equal.

     `min-height` stays a flat floor on purpose. Clamping it to the viewport
     (`min(560px, calc(100svh - …))`) does keep the panel inside the fold on a
     very short viewport — but the panel is `justify-content: flex-end` +
     `overflow: hidden`, so the height it gives up comes off the TOP of the
     copy: at 800×360 it cut 137px off the heading. A panel running past the
     fold is a smaller failure than a beheaded headline. */
  height: calc(100vh - 72px - var(--hero-inset) - var(--gutter-inner));
  height: calc(100svh - 72px - var(--hero-inset) - var(--gutter-inner));
  min-height: 560px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  padding: var(--sp-10) var(--gutter-inner);
  padding-bottom: var(--sp-16);
}

/* -- Background: full hero (hero section already has gutter margin) ─ */
.hero-bg {
  position: absolute;
  inset: 0;
  overflow: hidden;
  z-index: 0;
  will-change: transform;
}

.hero-bg canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  border: 0;
  object-fit: cover;
  pointer-events: none;
  touch-action: none;
  /* Hidden until the shader's first painted frame (index.html adds
     `hero-shader-ready`). Returning from a case page skips the preloader, so
     the hero would otherwise show as a flat block of `--surface-hero` for the
     frames before the shader paints — an unfinished-looking state. Fading over
     that same teal makes the arrival continuous instead. */
  opacity: 0;
  transition: opacity 0.45s var(--ease-out);
}
html.hero-shader-ready .hero-bg canvas {
  opacity: 1;
}
/* No JS / no WebGL: never leave the canvas invisible-but-present. */
@media (prefers-reduced-motion: reduce) {
  .hero-bg canvas { transition: none; }
}


.hero-bg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(0,0,0,.25), rgba(0,0,0,.45));
  pointer-events: none;
}

@media (prefers-reduced-motion: reduce) {
  .hero-bg {
    filter: saturate(.9) brightness(.95);
  }
}

.hero__overlay {
  position: absolute;
  inset: 0;
  z-index: 1;
  background: linear-gradient(
    to bottom,
    rgba(15, 35, 38, 0.1)  0%,
    rgba(15, 35, 38, 0.4)  50%,
    rgba(15, 35, 38, 0.78) 100%
  );
}

.hero__grain {
  position: absolute;
  inset: -50%;
  z-index: 2;
  width: 200%;
  height: 200%;
  opacity: 0.035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='noise'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.75' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='300' height='300' filter='url(%23noise)' opacity='1'/%3E%3C/svg%3E");
  background-repeat: repeat;
  animation: grainShift 7s steps(1) infinite;
  pointer-events: none;
}

/* -- Content ---------------------------------------------------- */
.hero__content {
  position: relative;
  z-index: 3;
  max-width: 680px;
}

.hero__heading {
  font-family: var(--type-h1-font);
  font-size: var(--type-h1-size);
  line-height: var(--type-h1-line);
  font-weight: var(--weight-regular);
  letter-spacing: var(--tracking-tight);
  color: var(--text-on-dark);
  margin-bottom: var(--sp-5);
}

/* Flattened from CSS nesting — word clip containers */
.hero__heading .word {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  line-height: 1.15;
}

.hero__heading .word-inner {
  display: inline-block;
}

/* THE FALLBACK. `hero-entrance.js` tweens these spans out of their clip when
   GSAP is driving, so the keyframe must be gated off — two systems writing
   `transform` on the same element is a double-animation, not a double-ease,
   and the loser wins at unpredictable moments. Gated at the selector, per
   DESIGN.md → Motion → Gating a fallback. */
html:not(.gsap-motion) .hero__heading .word-inner {
  animation: wordReveal 0.8s var(--ease-editorial) both;
}

/* Per-word stagger */
html:not(.gsap-motion) .hero__heading .word:nth-child(1)  .word-inner { animation-delay: 0.12s; }
html:not(.gsap-motion) .hero__heading .word:nth-child(2)  .word-inner { animation-delay: 0.20s; }
html:not(.gsap-motion) .hero__heading .word:nth-child(3)  .word-inner { animation-delay: 0.28s; }
html:not(.gsap-motion) .hero__heading .word:nth-child(4)  .word-inner { animation-delay: 0.36s; }
html:not(.gsap-motion) .hero__heading .word:nth-child(5)  .word-inner { animation-delay: 0.44s; }
html:not(.gsap-motion) .hero__heading .word:nth-child(6)  .word-inner { animation-delay: 0.52s; }
html:not(.gsap-motion) .hero__heading .word:nth-child(7)  .word-inner { animation-delay: 0.60s; }

.hero__subheading {
  font-family: var(--type-body1-font);
  font-size: var(--type-body1-size);
  line-height: var(--type-body1-line);
  color: var(--text-on-dark);
  opacity: 0.82;
  margin-bottom: var(--sp-5);
  max-width: 420px;
}

.hero__meta {
  font-family: var(--type-caption-font);
  font-size: var(--type-caption-size);
  line-height: var(--type-caption-line);
  font-weight: var(--weight-book);
  letter-spacing: var(--tracking-wide);
  color: var(--text-on-dark-dim);
  margin-bottom: var(--sp-6);
}

/* Typing cursor — blinking bar after meta text */
.hero__cursor {
  display: inline-block;
  width: 1px;
  height: 1em;
  background-color: var(--text-on-dark-dim);
  margin-left: 4px;
  vertical-align: middle;
  animation: cursorBlink 1s steps(1) infinite;
}

@keyframes cursorBlink {
  0%, 100% { opacity: 1; }
  50% { opacity: 0; }
}

.hero__tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--sp-2);
}

/* -- Responsive ------------------------------------------------- */
@media (max-width: 768px) {
  .hero {
    --hero-inset: 6px;
    width: calc(100% - 2 * var(--gutter));
    margin-left: var(--gutter);
    margin-right: var(--gutter);
    margin-bottom: var(--gutter);
    margin-top: calc(56px + var(--hero-inset));
    min-height: 420px;
    /* No max-height. `height: calc(100svh - …)` is meant to fill the small
       viewport minus the navbar zone and gutters (matching the desktop hero,
       which fills to a single gutter gap). A `max-height: 75vh` used to cap it
       here — but 75vh is ~167px shorter than the calc, so it left a big dead
       band of paper below the hero (~183px at 760×980). The svh height already
       bounds it correctly; the cap only broke it.

       The `vh` line is the no-small-viewport-units fallback (see the base
       rule). It cannot be the same calc: `vh` is the toolbar-hidden height on
       mobile, so it budgets an extra 96px for the toolbar rather than letting
       the panel run past the fold. Engines that understand `svh` never see
       it. */
    height: calc(100vh - 56px - var(--hero-inset) - var(--gutter) - 96px);
    height: calc(100svh - 56px - var(--hero-inset) - var(--gutter));
    padding: 24px 20px;
    padding-bottom: 32px;
    /* radius comes from --radius-panel (32px on mobile) */
  }

  .hero__content {
    max-width: 100%;
  }

  .hero__heading {
    font-size: var(--type-h1-size);
    line-height: var(--type-h1-line);
    margin-bottom: 16px;
  }

  .hero__subheading {
    font-size: var(--type-body1-size);
    line-height: var(--type-body1-line);
    max-width: 100%;
    margin-bottom: 20px;
  }

  .hero__tags {
    gap: 4px;
  }
}
