/* ===========================================================
   Evy Diepenbroek — Hero tornado + scroll "eject" interaction
   Built on the Osmo "3D Cards Tornado" component (GSAP +
   Observer). Paste this file's content into your Slater CSS.

   The tornado spins freely and continuously, the whole time —
   scrolling never stops it. Only one thing is scroll-linked:
   whichever card happens to be nearest the visual center at the
   moment you start scrolling gets pulled out of the orbit, flips
   over (revealing your portrait on its back face) and slides
   down, while the rest of the tornado keeps spinning behind it.

   Home page only — fonts, colours, spacing tokens, .eod-page,
   .eod-btn, [data-eod-reveal], Awards/CTA/Footer now live in
   shared.css (loaded before this file) so every page can reuse
   them from one place.

   REQUIRES, loaded BEFORE script.js (head custom code):
     <script src="https://cdn.jsdelivr.net/npm/gsap@3.15/dist/gsap.min.js"></script>
     <script src="https://cdn.jsdelivr.net/npm/gsap@3.15/dist/Observer.min.js"></script>
   =========================================================== */

/* One continuous frame that scrolls NORMALLY — the tornado itself is
   never pinned, it scrolls away like any other content. .eod-hero
   carries the background colour (set from JS), so the area the
   tornado sits in and the area the card lands in are always painted
   with the exact same colour by construction.

   The only thing that "stays with you" is the ejected card itself —
   it becomes position:sticky once picked, so it rides along, centred
   on screen, for as long as you're still scrolling through this
   frame, then releases into its resting spot once you scroll past it.

   Height is 220svh — 2.2x the viewport, fluid per device (svh, not
   dvh: stays put as mobile browser chrome collapses/expands, which
   is what stableViewportHeight in script.js's scroll math assumes). */
.eod-hero {
  position: relative;
  height: 220svh;
  background: var(--eod-black);
  perspective: 75em;
  /* font-family not redeclared here: inherited from .eod-page (shared.css) — .eod-hero is a descendant of it. */
}
/* script.js measures the landed intro text and resizes .eod-hero
   (via .style.height) to hug it — once scrolling has gone idle after
   landing, never while actively scrolling. This transition is what
   makes that idle-time resize read as a smooth settle instead of a
   snap. Scoped to .is-landed only, so it never fights the
   scroll-scrubbed fall/flip/grow phase before that (which has no
   reason to animate — it's already tracking the scroll position 1:1
   every frame).
   A flat height for mobile was tried here instead (script.js's own
   measurement kept tripping over timing edge cases) and reverted —
   a forced real landing measured the card settling 300px+ lower than
   any single hand-picked constant accounted for, because the card's
   landing position isn't a fixed offset from .eod-hero's own top; it
   scales with viewport height via the 220svh scroll-track. Only a
   live measurement (script.js) adapts to that correctly. */
.eod-hero.is-landed {
  transition: height 0.45s cubic-bezier(0.16, 1, 0.3, 1);
}
.eod-hero__stage {
  position: relative; /* NOT sticky — scrolls away normally with the page */
  height: 100svh;
  overflow: hidden;
  isolation: isolate;
}
.eod-backdrop {
  position: absolute;
  inset: 0;
  background-color: var(--eod-black);
  z-index: 0;
}

/* ---- Hero overlay text (heading) ----
   A sibling of .eod-hero__stage, not a child — sits above the stage
   (z-index) but below an ejected/landed card (z-index up to 9999,
   set in script.js) once one is out and flipping/growing — that's
   intentional, the card becomes the focal point at that point.
   Only the first screen (100svh) — it scrolls away with the stage
   as you continue down, same as before. */
.eod-hero__overlay {
  position: absolute;
  inset: 0;
  z-index: 10;
  height: 100svh;
  padding: 2.5em;
  display: flex;
  flex-direction: column;
  justify-content: center;
  color: var(--eod-white);
  pointer-events: none;
}
.eod-hero__heading {
  display: flex;
  justify-content: space-between;
  font-size: clamp(1.8em, 5vw, 3.5em);
  font-weight: 400;
  letter-spacing: -0.02em;
  line-height: 1;
  /* Flex item of a column-direction parent (.eod-hero__overlay) —
     width: 100% (not the implicit stretch a bare max-width/margin
     would rely on: auto cross-axis margins on a flex item actually
     OPT OUT of stretch per spec, collapsing this to its own content
     width and killing the space-between spread at every size, not
     just ultra-wide ones) fills the overlay exactly like the old
     unstretched default did; max-width only kicks in once that 100%
     would exceed the cap, and margin: auto centers the difference
     — "Have a look" / "at all my work" just don't end up absurdly far
     apart on an ultra-wide monitor. No-op below the cap. */
  width: 100%;
  max-width: var(--eod-content-max);
  margin: 0 auto;
}

/* ---- Hero intro (title + bio) — flanks the landed card ----
   NOT inside .eod-hero__overlay: this needs to sit wherever the
   card actually lands, further down the 220%-tall hero, not just
   within the first screen. Hidden/inert until script.js's settle()
   adds .is-landed to .eod-hero (and sets --eod-card-h + this row's
   `top` to the same value it just gave the card, so the two share
   one vertical anchor) — unsettle() (scrolling back up) removes it
   again. The card itself — already flipped to its portrait face by
   then — is the "photo" in the middle; no separate image needed.

   Title/body carry their own opacity+transform (not the row) so
   each can enter from its own direction: sideways from off-screen
   on desktop (flanking the card), or vertically from behind the
   card itself on mobile (see the media query below). */
.eod-hero__intro {
  position: absolute;
  /* left:50% + translateX(-50%) (folded into the existing
     translateY(-50%) below) instead of left/right:2.5em — width is
     the viewport minus that same 2.5em gutter on each side UNTIL
     max-width caps it on an ultra-wide monitor, at which point this
     centers itself instead of the flanking title/body just ending up
     very far apart. Behaves identically to the old left/right:2.5em
     on any screen narrower than the cap. */
  left: 50%;
  width: calc(100% - 5em);
  max-width: var(--eod-content-max);
  top: 50vh;
  transform: translate(-50%, -50%);
  z-index: 5; /* above the stage, below the landed card (9999) */
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: 2.5em;
  flex-wrap: wrap;
  /* Dark, not white: by the time this becomes visible (the card has
     landed), .eod-hero's own background has already crossfaded to
     white (see updateScroll()'s colorProgress in script.js) — white
     text here would sit on a white background. */
  color: var(--eod-ink);
  pointer-events: none;
}
.eod-hero.is-landed .eod-hero__intro {
  pointer-events: auto;
}
.eod-hero__intro-title,
.eod-hero__intro-body {
  opacity: 0;
  transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1), transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);
}
.eod-hero.is-landed .eod-hero__intro-title,
.eod-hero.is-landed .eod-hero__intro-body {
  opacity: 1;
  transform: translate(0);
}
.eod-hero__intro-title {
  flex: 0 1 auto;
  margin: 0;
  font-size: clamp(1.8em, 4vw, 2.75em);
  font-weight: 400;
  letter-spacing: -0.02em;
  line-height: 1.15;
  transform: translateX(-3em); /* enters from the left, off past the card */
}
.eod-hero__intro-body {
  flex: 1 1 20em;
  max-width: 24em;
  margin-left: auto;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 1.2em;
  transform: translateX(3em); /* enters from the right, off past the card */
}
.eod-hero__intro-body p {
  margin: 0;
  font-size: 0.95em;
  font-weight: 300;
  line-height: 1.5;
  color: var(--eod-muted);
}
@media (max-width: 767px) {
  /* Desktop lays "Have a look" / "at all my work" side by side in one
     row (space-between). On a narrow screen that crams both phrases
     into one cramped line fighting the spinning cards for space —
     stacked top/bottom instead (own reference screenshot), centered
     horizontally (mobile-only — desktop's own space-between stays
     left/right, unaffected). height:52% (not 100%) keeps the pair
     hugging close above/below the card stack itself — the cards sit
     roughly centered within .eod-hero__stage's own 100svh, so
     stretching this text to the full height pushed it out to the
     literal screen edges, far from the cards it's meant to bookend. */
  .eod-hero__heading {
    flex-direction: column;
    justify-content: space-between;
    align-items: center;
    height: 52%;
    font-size: 2.2em;
  }
  /* Stacked instead of flanking: title above the card, body below.
     Flexbox column+gap can't do this reliably (centering the whole
     stack only lands the gap on the card's true center when title
     and body happen to be the same height) — instead .eod-hero__intro
     collapses to a zero-height anchor line at the card's vertical
     center (top + translateY(-50%), same shared anchor as desktop),
     and title/body each position themselves independently off of
     THAT line via bottom/top, so their own heights can't throw the
     other one off. --eod-card-h is set by script.js's settle(). */
  .eod-hero__intro {
    display: block;
    height: 0;
    /* Base rule (unscoped) is left: 50%; width: calc(100% - 5em);
       transform: translate(-50%, -50%) — the desktop gutter, with an
       ultra-wide max-width cap folded in (see --eod-content-max).
       Every OTHER section uses 1.25em on mobile (.eod-page section's
       own mobile padding), so the bio text sat noticeably narrower/
       more inset than the rest of the page. Overridden here to match
       that — explicit left + width (not right) since the base rule's
       left:50% + width still cascade otherwise, and combining
       left/right/width on an absolutely-positioned box just gets one
       of them silently ignored rather than erroring. max-width: none
       since the cap is meaningless at any width this media query
       covers. title/body's own left:0; right:0 (below) are relative
       to THIS box, so this is the one place that actually needs to
       change. */
    left: 1.25em;
    width: calc(100% - 2.5em);
    max-width: none;
    /* Base rule's transform is translate(-50%, -50%) — the X half is
       specifically what makes left: 50% + width center the box on
       wide screens; with an explicit left + width here instead, that
       X shift would incorrectly nudge the whole box further left by
       half its own width. Only the Y half (vertical centering on the
       landed card, unrelated to any of this) is still needed. */
    transform: translateY(-50%);
  }
  .eod-hero__intro-title,
  .eod-hero__intro-body {
    position: absolute;
    left: 0;
    right: 0;
    max-width: none;
    margin: 0;
    text-align: center;
  }
  .eod-hero__intro-title {
    font-size: 2.2em;
  }
  .eod-hero__intro-body p {
    font-size: 1.05em;
    /* Clamped to 3 lines each (of 2 paragraphs) on mobile — the full
       bio is long enough that it was a major contributor to how much
       height .eod-hero needs to hug on a narrow screen, and reads as
       a lot of text to land on right as the card settles in. Ellipsis
       marks the cut; the full bio is still one tap away via "More
       about me" below it. */
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 3;
    overflow: hidden;
  }
  /* "From behind the card": each rests just clear of it (bottom/top)
     but starts pulled in further toward that center line — roughly
     at the card itself, hidden behind it (z-index 5 vs. the card's
     9999) — and travels out to its resting spot as it fades in. */
  .eod-hero__intro-title {
    bottom: calc(var(--eod-card-h, 20em) / 2 + 1.5em);
    transform: translateY(calc(var(--eod-card-h, 20em) / 2 + 1em));
  }
  .eod-hero__intro-body {
    top: calc(var(--eod-card-h, 20em) / 2 + 1.5em);
    align-items: center;
    /* The shared rule above resets max-width: none (needed so left:0;
       right:0 can center title/body against the full screen width) —
       but that let the bio paragraph run edge-to-edge, hurting
       readability on a wide phone. Capped again here and centered
       within that full-width box via auto margins. */
    max-width: 26em;
    margin-left: auto;
    margin-right: auto;
    transform: translateY(calc(var(--eod-card-h, 20em) / -2 - 1em));
  }
}

/* ---- Cards tornado (Osmo component) ---- */
.cards-tornado {
  position: absolute;
  inset: 0;
  z-index: 1;
  cursor: grab;
  /* pan-y (not none): a touch swipe here must still scroll the page
     normally on mobile/tablet — it only additionally feeds the drag
     gesture into the tornado's spin velocity, same as a wheel event
     does on desktop. touch-action:none would swallow the swipe as a
     drag-only gesture and trap visitors on the first screen. */
  touch-action: pan-y;
  overflow: visible;
}
.cards-tornado__collection { width: 100%; height: 100%; position: relative; }
.cards-tornado__list {
  font-size: clamp(0.6em, 0.85vw, 1.5em);
  width: 100%;
  height: 100%;
  perspective: 75em;
  transform-style: preserve-3d;
  position: relative;
}
.cards-tornado__item {
  transform-style: preserve-3d;
  backface-visibility: visible;
  will-change: transform, filter;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

/* The card itself gets a second, independent 3D rotation (the
   flip) nested inside the item's orbital transform. */
.demo-card {
  aspect-ratio: 4 / 5;
  pointer-events: none;
  -webkit-user-select: none;
  user-select: none;
  border-radius: var(--eod-radius);
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.35);
  width: 15em;
  position: relative;
  transform-style: preserve-3d;
}
.demo-card__face {
  position: absolute;
  inset: 0;
  backface-visibility: hidden;
  border-radius: inherit;
  overflow: hidden;
  background-color: #222;
}
.demo-card__face--back { transform: rotateY(180deg); }

/* Once a card is pulled out of the tornado for the hand-off, it's
   re-parented directly under .eod-hero (escaping the clipped/3D
   orbit context) and given an explicit pixel size inline — make
   its inner demo-card fill that box. */
.eod-hero > .cards-tornado__item .demo-card { width: 100%; height: 100%; }

.cover-image {
  object-fit: cover;
  border-radius: inherit;
  width: 100%;
  height: 100%;
  position: absolute;
  top: 0;
  left: 0;
}
