/* ==========================================================================
   Hero — noomo-scale type under a twisting ribbon of work running across the hero
   Layer order inside .hero (isolated):
     0 backdrop · 1 spiral · 2 type · 4 foot
   ========================================================================== */

/* The hero is sticky: it holds where it is while the work section rides up
   over it (as on Atelier North), settling back underneath (hero.js: scale
   0.94, fading into the paper). The pin is one screen taller than the hero
   and .work pulls itself up by the same screen (work.css), so the section
   starts right under the hero and the hero lets go just as it covers the
   screen. A hero taller than the screen (short windows) scrolls to its foot
   first, then holds.
   The extra screen is a spacer, not padding: a sticky element only travels
   inside its container's content box, so padding would leave it no room. */
.hero-pin::after {
  content: "";
  display: block;
  height: 100svh;
}

.hero {
  position: sticky;
  top: min(0px, 100svh - 620px);
  height: 100svh;
  min-height: 620px;
  overflow: clip;
  isolation: isolate;
  background: var(--bone);
}

@supports not (height: 100svh) {
  .hero-pin::after {
    height: 100vh;
  }

  .hero {
    top: min(0px, 100vh - 620px);
    height: 100vh;
  }
}

/* Soft studio light on paper: neutral pools only (brand is ink + bone). */
.hero__backdrop {
  position: absolute;
  inset: 0;
  z-index: 0;
  /* Its own layer from the first paint, drawn once at full size: when the
     intro hands off and the page drops its card transform, the screen-sized
     gradients and grain would otherwise repaint in one frame (up to ~95ms). */
  will-change: transform;
  background:
    radial-gradient(58% 62% at 66% 46%, rgb(255 255 255 / 0.95) 0%, rgb(255 255 255 / 0) 72%),
    radial-gradient(46% 52% at 8% 92%, rgb(231 226 218 / 0.9) 0%, rgb(231 226 218 / 0) 70%),
    radial-gradient(42% 48% at 96% 4%, rgb(233 235 238 / 0.95) 0%, rgb(233 235 238 / 0) 70%),
    var(--bone);
}

.hero__backdrop::after {
  content: "";
  position: absolute;
  inset: 0;
  background: url("../assets/textures/grain.webp") repeat;
  background-size: 160px 160px;
  opacity: 0.55;
  pointer-events: none;
}

/* Spiral of work: a twisting ribbon of image cards across the band between
   the header and the headline, drawn by helix.js on one WebGL canvas.
   Decorative, so it never takes the pointer. */
.hero__spiral {
  position: absolute;
  inset: 0;
  z-index: 1;
  overflow: hidden;
  pointer-events: none;
}

.spiral__canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
}

/* ------------------------------------------------------------------ type */

.hero__stage {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: calc(var(--header-h) + 2svh) var(--title-inset) clamp(128px, 17svh, 184px);
  container-type: inline-size;
}

/* Desktop: a caption under the type (the ribbon owns the top-left).
   Phones: an eyebrow above it (the ribbon runs below the type there). */
.hero__eyebrow {
  order: 1;
  display: flex;
  align-items: center;
  gap: 14px;
  margin: clamp(18px, 3.2svh, 34px) 0 0 0.3em;
  color: var(--text-mute);
}

.hero__eyebrow-rule {
  width: 34px;
  height: 1px;
  background: currentColor;
}

/*
  The display face is Archivo at 125% width, the brand's own family.
  --fit-* is the natural width of the fitted line in em (measured in Chrome
  with the webfont): line 3 fills the stage on wide screens and the longest
  word fills it on phones. hero.js corrects it if a fallback face renders.
*/
.hero__title {
  --fit: 12.652; /* "AMBITIOUS BRANDS" */
  /* Sits the type and caption low in the stage, on the foot's clearance
     (the stage's bottom padding). If it doesn't fit, it centres as before. */
  margin-top: auto;
  font-size: calc(100cqw / var(--fit) * var(--fit-adjust, 1));
  font-stretch: var(--wdth-display);
  font-weight: 500;
  line-height: 0.94;
  letter-spacing: -0.012em;
  text-transform: uppercase;
  font-kerning: normal;
  font-synthesis: none;
}

.hero__line {
  display: flex;
  column-gap: 0.25em;
  white-space: nowrap;
}

.hero__line--2 {
  justify-content: space-between;
}

/* Each word is its own box: the reveal flies it in whole from its own
   direction (hero.js). The padding gives the glyphs room (the rotation and
   lean need it); the negative margin cancels it in layout. */
.word {
  display: inline-block;
  padding: 0.1em 0.04em 0.04em;
  margin: -0.1em -0.04em -0.04em;
}

.word > span {
  display: inline-block;
}

/* ------------------------------------------------------------------ foot */

.hero__foot {
  position: absolute;
  left: var(--gutter);
  right: var(--gutter);
  bottom: clamp(20px, 3.6svh, 40px);
  z-index: 4;
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: end;
  gap: 24px;
}

.hero__status {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  justify-self: start;
  padding: 10px 16px 10px 12px;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: rgb(247 245 242 / 0.72);
  backdrop-filter: blur(10px);
  -webkit-backdrop-filter: blur(10px);
}

.hero__dot {
  position: relative;
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ink);
}

.hero__dot::after {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: 50%;
  background: var(--ink);
  animation: dot-pulse 2.4s var(--ease-out) infinite;
}

@keyframes dot-pulse {
  0% {
    opacity: 0.45;
    transform: scale(1);
  }
  70%,
  100% {
    opacity: 0;
    transform: scale(3.2);
  }
}

.hero__scroll {
  display: grid;
  justify-items: center;
  gap: 10px;
  padding: 6px 10px 0;
  color: var(--text);
}

.hero__scroll-line {
  position: relative;
  width: 1px;
  height: 36px;
  overflow: hidden;
  background: var(--line);
}

.hero__scroll-line::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--ink);
  transform-origin: 50% 0;
  animation: scroll-cue 2.2s var(--ease-in-out) infinite;
}

@keyframes scroll-cue {
  0% {
    transform: scaleY(0);
    transform-origin: 50% 0;
  }
  45% {
    transform: scaleY(1);
    transform-origin: 50% 0;
  }
  55% {
    transform: scaleY(1);
    transform-origin: 50% 100%;
  }
  100% {
    transform: scaleY(0);
    transform-origin: 50% 100%;
  }
}

.hero__blurb {
  justify-self: end;
  width: min(100%, 21.5em);
}

.hero__blurb-label {
  margin-bottom: 12px;
  color: var(--text-mute);
}

.hero__blurb-text {
  font-size: var(--fs-body);
  line-height: 1.38;
  letter-spacing: -0.005em;
  text-wrap: pretty;
}

/* Blur-in words (built by hero.js) keep their natural spacing */
.bw {
  display: inline-block;
}

/* ------------------------------------------------------------------ intro pre-states
   Everything the reveal animates starts hidden in CSS, so nothing shows,
   vanishes and shows again when the scripts land. intro.js removes
   .is-loading after the reveal; the head safety timer does it if the
   scripts never arrive. */

/* 0.002, not 0: out of sight (under one colour level, behind the loader
   card), but Chrome only rasterises what has some opacity, so their tiles
   are drawn during the loader instead of on the reveal's first frames
   (a 25-40ms hitch as the words appeared). hero.js fades from the same. */
html.is-loading .site-header__logo,
html.is-loading .site-header .btn-cta,
html.is-loading .site-header .burger,
html.is-loading .hero__eyebrow,
html.is-loading .hero__status,
html.is-loading .hero__scroll,
html.is-loading .hero__blurb-label {
  opacity: 0.002;
}

/* The words fly in whole, from their own directions (hero.js). */
html.is-loading .word {
  opacity: 0.002;
}

html.is-loading .hero__blurb-text {
  opacity: 0;
}

html.intro-failed .site-header__logo,
html.intro-failed .site-header .btn-cta,
html.intro-failed .site-header .burger,
html.intro-failed .hero__eyebrow,
html.intro-failed .hero__status,
html.intro-failed .hero__scroll,
html.intro-failed .hero__blurb-label,
html.intro-failed .hero__blurb-text {
  opacity: 1;
  transition: opacity 600ms ease;
}

html.intro-failed .word {
  opacity: 1;
  transition: opacity 900ms var(--ease-out);
}

/* ------------------------------------------------------------------ responsive */

.hero__status {
  white-space: nowrap;
}

@media (max-width: 1100px) {
  /* explicit tracks: an auto track would size to the blurb's one-line width */
  .hero__foot {
    grid-template-columns: minmax(0, 1fr) minmax(0, 22em);
  }

  .hero__scroll {
    display: none;
  }
}

/* Portrait (phones, tablets, narrow windows) keeps the desktop's
   composition: the ribbon across the band under the header, the type low
   over the foot, the caption under the type. The title stacks into five
   lines on alternating edges so "MOMENTUM" can fill the width. The stage
   and the foot share one column here, so the type always lands right on
   the foot, however many lines the blurb wraps to.
   Keep this query in step with S.env.stacked and helix.js. */
@media (max-aspect-ratio: 1/1) {
  .hero {
    display: flex;
    flex-direction: column;
  }

  .hero__stage {
    position: relative;
    inset: auto;
    flex: 1 1 auto;
    min-height: 0;
    padding-top: calc(var(--header-h) + 2svh);
    padding-bottom: clamp(26px, 4.4svh, 48px);
  }

  .hero__title {
    --fit: 6.871; /* "MOMENTUM" at 112.5% width */
    font-stretch: 112.5%; /* "semi-expanded": a keyword the canvas mask can match */
    line-height: 0.96;
  }

  .hero__line--2,
  .hero__line--3 {
    flex-direction: column;
    align-items: flex-start;
  }

  .hero__line--2 > .word:last-child,
  .hero__line--3 > .word:last-child {
    align-self: flex-end;
  }

  .hero__foot {
    position: relative;
    left: auto;
    right: auto;
    bottom: auto;
    flex: none;
    margin: 0 var(--gutter) clamp(20px, 3.6svh, 40px);
  }
}

/* Short portrait screens: the label makes way so the ribbon keeps its band. */
@media (max-aspect-ratio: 1/1) and (max-height: 700px) {
  .hero__blurb-label {
    display: none;
  }
}

/* Phones: the foot stacks, the blurb (with its label, as on desktop) over
   the status pill, which keeps the bottom-left corner. */
@media (max-width: 719px) {
  .hero__foot {
    grid-template-columns: minmax(0, 1fr);
    gap: 20px;
  }

  .hero__blurb {
    justify-self: start;
    order: -1;
  }

  .hero__blurb-label {
    margin-bottom: 10px;
  }
}

/* Short landscape screens (phones on their side, small laptops zoomed) */
@media (max-height: 520px) and (min-width: 720px) {
  .hero {
    top: min(0px, 100svh - 520px);
    min-height: 520px;
  }

  .hero__stage {
    padding-bottom: 110px;
  }

  .hero__blurb {
    display: none;
  }

  .hero__foot {
    grid-template-columns: 1fr auto;
  }
}

@media (prefers-reduced-motion: reduce) {
  .hero__dot::after,
  .hero__scroll-line::after {
    animation: none;
  }

  .hero__scroll-line::after {
    transform: scaleY(0.5);
  }
}
