/* ==========================================================================
   PULSE — the hero, the beat line, and THE CUT.

   The cut is the signature. On the first strong scroll the hero photograph
   divides into vertical slices, each shifted by a different distance, while the
   headline stays put for a beat and then leaves on one hard step. It is a
   musical edit, not an image reveal, and it happens exactly once.

   How it is wired: core/hero.js publishes --hero-p (0 as the hero sits, 1 as it
   has left) on the hero element. Everything below is arithmetic on that one
   number, in CSS. assets/js/site/cut.js only builds the slices; it does not
   animate them, and there is no scroll listener of its own anywhere in this
   chapter.
   ========================================================================== */
.hero {
  position: relative;
  min-height: 100svh;
  min-height: calc(var(--vh, 1vh) * 100);
  /* flex, not grid, and the reason is not taste. An absolutely positioned child
     of a GRID container with auto placement is laid out against the grid's
     content box, which `align-content: end` has already pushed to the bottom of
     the screen — so the descriptor pinned at top: 5.6rem landed 250px down,
     under the headline. A flex container positions abspos children against its
     padding box, which is what "pinned under the header" has to mean. */
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  overflow: hidden;
  isolation: isolate;
}

/* --- the photograph -------------------------------------------------------- */
.hero__stage { position: absolute; inset: 0; z-index: 0; }
.hero__pic, .hero__stage img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 42%;
}
@media (max-width: 899px) {
  .hero__stage img { object-position: 50% 34%; }
}

/* the ground the composition sits on: a gradient, not a flat scrim, so the
   sunset survives at the top of the frame and the type is readable at the
   bottom of it */
.hero::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(to bottom,
      color-mix(in srgb, var(--night) 46%, transparent) 0%,
      transparent 26%,
      transparent 44%,
      color-mix(in srgb, var(--night) 74%, transparent) 88%,
      var(--bg) 100%);
}

/* --- THE CUT ---------------------------------------------------------------
   Six slices, each a clone of the same <img> clipped to its own vertical band.
   The clones share the original's srcset, so they cost no extra request.

   clip-path is safe here and nowhere else on this page: these elements are
   never observed by core/reveal.js, and the photograph they contain is
   hand-written in index.html with its own preload rather than mounted by an
   IntersectionObserver that a clipped box would starve. */
.cut { position: absolute; inset: 0; }
.cut__slice {
  position: absolute;
  inset: 0;
  clip-path: inset(0 calc(100% - var(--x1)) 0 var(--x0));
  transform: translate3d(0, calc(var(--dy) * var(--hero-p, 0) * 13vh), 0)
             scale(calc(1 + var(--hero-p, 0) * 0.04));
  will-change: transform;
}
.cut__slice img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  object-position: 50% 42%;
}
@media (max-width: 899px) {
  .cut__slice img { object-position: 50% 34%; }
  /* a shorter travel on a phone: the brief asks for the first transition to be
     briefer there, and 13vh of slip on a tall screen reads as a glitch */
  .cut__slice {
    transform: translate3d(0, calc(var(--dy) * var(--hero-p, 0) * 7vh), 0)
               scale(calc(1 + var(--hero-p, 0) * 0.03));
  }
}
/* the original stays in the document — it is the LCP element and the preload's
   target — and is hidden only once the slices exist to replace it */
.hero__stage.is-cut .hero__pic { visibility: hidden; }

/* --- the composition ------------------------------------------------------- */
.hero__body {
  position: relative;
  z-index: 2;
  width: min(100% - var(--gut) * 2, var(--wrap));
  margin-inline: auto;
  padding-bottom: clamp(5rem, 11vh, 8rem);
  /* the headline holds its place for the first third of the cut and then goes
     on one step — no fade, no drift */
  opacity: clamp(0, calc((0.42 - var(--hero-p, 0)) * 7), 1);
}

/* The descriptor is lifted OUT of the stack and pinned under the header. It is
   a brand line rather than part of the headline's composition, and taking its
   39px out of the column is what lets the headline stay at nearly a fifth of
   the viewport height on a laptop instead of colliding with the navigation. */
.hero__descriptor {
  position: absolute;
  z-index: 2;
  /* clear of BOTH headers: the desktop bar is about 66px, the phone bar about
     75px because the ticket button sets its height */
  top: clamp(5.8rem, 11vh, 7.4rem);
  left: 50%;
  translate: -50% 0;
  width: min(100% - var(--gut) * 2, var(--wrap));
  color: var(--glow);
}

.hero__line {
  /* Sized from the SHORTER of the two axes. A headline set on three lines at
     17vw is architecture on a 1440x900 laptop and an overflow on a 1440x700
     one — the composition has to fit the screen it is on, so the width term is
     capped by a height term and the type stops growing when the screen runs
     out of vertical room rather than when it runs out of horizontal. */
  font-size: clamp(3.2rem, min(14vw, 19vh), 13rem);
  line-height: 0.82;
  letter-spacing: -0.035em;
  margin-bottom: clamp(1rem, 2vw, 1.5rem);
}

.hero__sub {
  font-size: var(--t-lead);
  color: var(--ink-dim);
  margin-bottom: clamp(1.1rem, 2vw, 1.6rem);
}

.hero__cta { display: flex; flex-wrap: wrap; gap: 0.8rem; }

/* --- the entrance ----------------------------------------------------------
   core/hero.js releases [data-enter] children in document order once the
   photograph has decoded. They arrive by CUT — a cover leaving — for the same
   reason nothing else on this page fades up. */
.js .hero [data-enter] {
  position: relative;
  overflow: hidden;
}
.js .hero [data-enter]::after {
  content: '';
  position: absolute;
  inset: -0.1em -0.2em;
  background: var(--bg);
  transform: translateX(0);
  transition: transform 620ms cubic-bezier(0.76, 0, 0.14, 1);
  transition-delay: var(--delay, 0ms);
}
.js .hero [data-enter].is-in::after { transform: translateX(103%); }

/* The rule above sets position: relative on EVERY [data-enter] child so its
   cover has something to sit in — and with three class selectors it outranks
   .hero__descriptor's own `position: absolute`, which silently dropped the
   descriptor back into the column and landed it across the headline. It is
   already a containing block for its own cover, so it only needs its position
   back. */
.js .hero .hero__descriptor { position: absolute; }

@media (prefers-reduced-motion: reduce) {
  .js .hero [data-enter]::after { display: none; }
}

/* --- THE BEAT LINE ---------------------------------------------------------
   Station one. Nearly flat, with one travelling point to say the system is
   alive, until the first scroll fires exactly one beat.

   preserveAspectRatio="none" stretches the viewBox to whatever width the line
   has, which would stretch the stroke with it — vector-effect keeps the stroke
   a true 2px at every width. That is why the path can be authored once in a
   1000-unit space and used at any size on the page. */
.hero__beat {
  position: absolute;
  left: 0; right: 0;
  bottom: clamp(2rem, 5vh, 3.4rem);
  z-index: 2;
  display: flex;
  align-items: center;
  gap: 1rem;
  padding-inline: var(--gut);
  opacity: clamp(0, calc((0.5 - var(--hero-p, 0)) * 5), 1);
}
.hero__cue {
  color: var(--ink-faint);
  white-space: nowrap;
}

.beat {
  flex: 1 1 auto;
  width: 100%;
  height: clamp(38px, 7vh, 68px);
  overflow: visible;
}
.beat__path {
  fill: none;
  stroke: var(--line);
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
  vector-effect: non-scaling-stroke;
  transition: stroke var(--settle);
}
.beat__dot {
  fill: var(--glow);
  transition: fill var(--settle);
}
.beat--wide { height: clamp(54px, 11vh, 104px); }
.beat--final { height: clamp(44px, 8vh, 78px); }

@media (max-width: 899px) {
  .hero__cue { display: none; }
}
