/* ==========================================================================
   hero.css — Dynamic hero section: layout, entrance, ambient mesh & 3D tilt
   --------------------------------------------------------------------------
   Depends on css/variables.css tokens (--brand, --brand-accent, --ring, --bg,
   --card, --muted, --radius-*, --space-*, --stagger-delay, easings) and is
   driven by js/hero.js (task 9.1), which:
     - sets --px / --py (0–100%) on `.hero` from a pointer handler (mesh center)
     - adds `.hero-enter` to trigger the entrance sequence
     - adds `.hero-idle` on touch-only devices (automatic ambient loop)
     - writes an inline `perspective(...) rotateX() rotateY()` on `#hero-img`

   Requirements:
     2.1 — staggered entrance (text reveal, image scale-in, tag cascade) ≤1500ms
     2.2 — pointer-reactive radial-gradient mesh via --px/--py, transform/opacity
     2.3 — image container `perspective` + ≤200ms transform transition for tilt
     2.4 — typographic gradient animation on the name/role within the entrance
     2.5 — automatic idle mesh loop on touch-only devices (`.hero-idle`)
     + prefers-reduced-motion: render final state, no entrance/idle/tilt motion.

   NO-JS SAFETY
   --------------------------------------------------------------------------
   All hidden "from" states live inside @keyframes and only apply while an
   entrance animation is running — i.e. only once js/hero.js adds `.hero-enter`.
   Without JS (or before it runs) every element renders in its final, visible
   state, so content is never withheld (progressive enhancement).

   ENTRANCE TIMELINE (all end ≤1500ms after `.hero-enter` is added — Req 2.1)
   --------------------------------------------------------------------------
     image scale-in      delay   0ms  dur 700ms  -> 700ms
     name (h1)           delay 120ms  dur 600ms  -> 720ms  (+ gradient sweep)
     name gradient sweep delay 120ms  dur 1200ms -> 1320ms
     role lines (2)      delay 240ms  dur 600ms  -> 840ms  (both share the rule)
     tags container      delay 320ms  dur 500ms  -> 820ms
     tag cascade (5)     delay 400ms +70ms/tag   -> last tag ends ~1080ms
     bio paragraphs (5)  delay 480ms +60ms/para  -> last ends 1320ms
     links row           delay 720ms  dur 600ms  -> 1320ms
   ========================================================================== */

/* ==========================================================================
   1. Hero layout
   --------------------------------------------------------------------------
   Image + text side by side on wide viewports, stacked on narrow ones. The
   hero is a positioned, clipped stacking context so the ambient mesh
   (`::before`) can sit behind the content without escaping the section.
   ========================================================================== */

.hero {
  position: relative;
  isolation: isolate;               /* own stacking context for the mesh layer */
  overflow: hidden;
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: var(--space-4);
  padding: var(--space-4);
  border-radius: var(--radius-xl);
  /* Default mesh center if JS never sets --px/--py (Req 2.2 default 50%/50%). */
  --px: 50%;
  --py: 50%;
}

/* Profile photo — rounded, brand-bordered, fixed intrinsic size. The wrapper
   for the 3D tilt is the image itself; `perspective` here gives the inline
   rotateX/rotateY written by hero.js real depth (Req 2.3). */
.hero-img {
  flex: 0 0 auto;
  width: 200px;
  height: 200px;
  border-radius: var(--radius-lg);
  object-fit: cover;
  /* The source photo is taller than the square 200x200 box, so `cover` crops
     it. Anchoring the crop window to the top of the frame keeps the crown of
     the head inside the box instead of slicing it off (the default `50% 50%`
     centred the crop). Tunable: nudge to e.g. `50% 8%` if this ends up too
     tight to the crown and a little headroom is wanted. */
  object-position: center top;
  border: 3px solid var(--brand);
  box-shadow: 0 10px 30px -12px var(--ring);
  transform-origin: center center;
  /* ≤200ms eased transition so hero.js's rotateX/rotateY tilt glides and the
     pointer-leave reset eases back to neutral (Req 2.3). */
  transition: transform 180ms var(--ease-out);
  will-change: transform;
  backface-visibility: hidden;
}

/* Text column takes the remaining width and never collapses below a readable
   measure before wrapping under the image. */
.hero-text {
  flex: 1 1 320px;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.hero-text .role {
  color: var(--muted);
  font-size: 1.05rem;
}

/* Two stacked role lines (PhD position + industry position) must read as ONE
   block rather than two unrelated paragraphs, so the second sits tighter than
   the .hero-text column `gap` (16px) by pulling half of it back — 8px between
   the lines, the full gap before/after the pair. */
.hero-text .role + .role {
  margin-top: calc(var(--space-1) - var(--space-2));
}

/* The country in each role line is an <em>, and base.css renders <em> in the
   self-hosted Merriweather italic. At the role's 1.05rem that serif italic
   reads as a font swap mid-sentence next to the Inter sans around it, so keep
   the italic but inherit the surrounding family. */
.hero-text .role em {
  font-family: inherit;
  font-style: italic;
}

/* Tags row (Req 2.1 cascade target). */
.hero-text .tags {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1);
}

.hero-text .links {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-top: var(--space-1);
}

/* ==========================================================================
   2. Ambient gradient mesh (Req 2.2, 2.5)
   --------------------------------------------------------------------------
   A radial-gradient layer whose two blobs are centered on the pointer-driven
   --px/--py (and its mirror), tinted with the brand + brand-accent colors.
   As hero.js updates --px/--py the mesh center shifts under the pointer. The
   layer is inflated past the hero bounds so the idle translate loop never
   exposes an edge. Purely decorative and non-interactive.
   ========================================================================== */

.hero::before {
  content: '';
  position: absolute;
  inset: -25%;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(
      42% 42% at var(--px, 50%) var(--py, 50%),
      color-mix(in srgb, var(--brand) 42%, transparent),
      transparent 68%
    ),
    radial-gradient(
      46% 46% at calc(100% - var(--px, 50%)) calc(100% - var(--py, 50%)),
      color-mix(in srgb, var(--brand-accent) 38%, transparent),
      transparent 70%
    );
  /* Faint base wash so the panel reads as a distinct surface even at rest. */
  background-color: var(--card);
  opacity: 0.9;
  will-change: transform;
}

/* Touch-only devices never move the pointer, so animate the mesh automatically
   (Req 2.5). Transform-only keeps it on the compositor; --px/--py stay at their
   50%/50% default and the drift supplies the motion. */
.hero.hero-idle::before {
  animation: hero-mesh-drift 14s var(--ease-in-out) infinite;
}

@keyframes hero-mesh-drift {
  0% {
    transform: translate3d(-4%, -3%, 0) scale(1.05);
  }
  25% {
    transform: translate3d(5%, -2%, 0) scale(1.08);
  }
  50% {
    transform: translate3d(3%, 5%, 0) scale(1.05);
  }
  75% {
    transform: translate3d(-5%, 3%, 0) scale(1.08);
  }
  100% {
    transform: translate3d(-4%, -3%, 0) scale(1.05);
  }
}

/* ==========================================================================
   3. Typographic name / role treatment (Req 2.4)
   --------------------------------------------------------------------------
   The name is rendered as brand→accent gradient text. It is a large display
   heading (>= 24px), so the 3:1 large-text contrast target applies and both
   gradient endpoints clear it in each theme. A one-time gradient sweep plays
   during the entrance (see §4); the static gradient remains afterward.
   ========================================================================== */

.hero-text h1 {
  background-image: linear-gradient(
    100deg,
    var(--brand) 0%,
    var(--brand-accent) 45%,
    var(--brand) 90%
  );
  background-size: 220% 100%;
  background-position: 0% 50%;
  -webkit-background-clip: text;
  background-clip: text;
  color: transparent;
  /* Fallback: if background-clip:text is unsupported the text stays legible. */
  -webkit-text-fill-color: transparent;
}

/* ==========================================================================
   4. Entrance sequence (Req 2.1, 2.4)
   --------------------------------------------------------------------------
   Gated entirely on `.hero-enter` (added by hero.js). Every rule uses CSS
   animations with `both` fill so the hidden "from" frame only applies while
   the animation runs; without `.hero-enter` all elements are fully visible.
   Transform + opacity only — never triggers layout (compositor-friendly).
   ========================================================================== */

@keyframes hero-rise {
  from {
    opacity: 0;
    transform: translateY(22px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes hero-image-in {
  from {
    opacity: 0;
    transform: scale(0.86);
  }
  to {
    opacity: 1;
    transform: scale(1);
  }
}

@keyframes hero-tag-in {
  from {
    opacity: 0;
    transform: translateY(10px) scale(0.94);
  }
  to {
    opacity: 1;
    transform: translateY(0) scale(1);
  }
}

/* Gradient sweep for the name: slides the oversized gradient across once,
   giving a "shine" that lands on the resting position (Req 2.4). */
@keyframes hero-name-sweep {
  from {
    background-position: 120% 50%;
  }
  to {
    background-position: 0% 50%;
  }
}

/* --- Image scale-in ------------------------------------------------------- */
.hero-enter .hero-img {
  animation: hero-image-in 700ms var(--ease-out) both;
}

/* --- Name: rise + gradient sweep (two coordinated animations) ------------- */
.hero-enter .hero-text h1 {
  animation:
    hero-rise 600ms var(--ease-out) 120ms both,
    hero-name-sweep 1200ms var(--ease-out) 120ms both;
}

/* --- Role ----------------------------------------------------------------- */
.hero-enter .hero-text .role {
  animation: hero-rise 600ms var(--ease-out) 240ms both;
}

/* --- Tags container + cascading tag children (Req 2.1) -------------------- */
.hero-enter .hero-text .tags {
  animation: hero-rise 500ms var(--ease-out) 320ms both;
}

.hero-enter .hero-text .tags .tag {
  animation: hero-tag-in 400ms var(--ease-out) both;
}

/* Cascade: base 400ms + 70ms per tag (5 tags -> last ends ~1080ms). */
.hero-enter .hero-text .tags .tag:nth-child(1) { animation-delay: 400ms; }
.hero-enter .hero-text .tags .tag:nth-child(2) { animation-delay: 470ms; }
.hero-enter .hero-text .tags .tag:nth-child(3) { animation-delay: 540ms; }
.hero-enter .hero-text .tags .tag:nth-child(4) { animation-delay: 610ms; }
.hero-enter .hero-text .tags .tag:nth-child(5) { animation-delay: 680ms; }
.hero-enter .hero-text .tags .tag:nth-child(n + 6) { animation-delay: 750ms; }

/* --- Bio paragraphs ------------------------------------------------------- */
/* The bio is five separate <p class="muted stagger"> elements (one per
   paragraph). They rise as a short cascade: base 480ms + 60ms per paragraph,
   so the last one ends at 720 + 600 = 1320ms, inside the 1500ms budget.
   The nth-of-type indices start at 3 because the first two <p> children of
   .hero-text are the two `.role` lines. */
.hero-enter .hero-text > p.muted {
  animation: hero-rise 600ms var(--ease-out) 480ms both;
}

.hero-enter .hero-text > p.muted:nth-of-type(4) { animation-delay: 540ms; }
.hero-enter .hero-text > p.muted:nth-of-type(5) { animation-delay: 600ms; }
.hero-enter .hero-text > p.muted:nth-of-type(6) { animation-delay: 660ms; }
.hero-enter .hero-text > p.muted:nth-of-type(n + 7) { animation-delay: 720ms; }

/* --- Links row ------------------------------------------------------------ */
.hero-enter .hero-text .links {
  animation: hero-rise 600ms var(--ease-out) 720ms both;
}

/* ==========================================================================
   5. Responsive
   --------------------------------------------------------------------------
   Below 768px the hero stacks: the photo centers above the text and grows a
   little for presence on small screens (layout.css handles the outer grid).
   ========================================================================== */

@media (max-width: 767px) {
  .hero {
    flex-direction: column;
    align-items: center;
    text-align: center;
    padding: var(--space-3);
    gap: var(--space-3);
  }

  .hero-text {
    flex-basis: auto;
    width: 100%;
    align-items: center;
  }

  .hero-text .tags,
  .hero-text .links {
    justify-content: center;
  }
}

/* ==========================================================================
   6. Reduced motion (Req 2.1/2.4/2.5 motion-safety)
   --------------------------------------------------------------------------
   Render the final state instantly: no entrance animations, no idle mesh loop,
   and no tilt transition motion. hero.js still adds `.hero-enter` under reduced
   motion, so we must neutralize the animations here. The static gradient on the
   name is kept (it is not motion); only the sweep is disabled.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  .hero-enter .hero-img,
  .hero-enter .hero-text h1,
  .hero-enter .hero-text .role,
  .hero-enter .hero-text .tags,
  .hero-enter .hero-text .tags .tag,
  .hero-enter .hero-text > p.muted,
  .hero-enter .hero-text .links,
  .hero.hero-idle::before {
    animation: none !important;
  }

  /* Land the name gradient on its resting position without the sweep. */
  .hero-text h1 {
    background-position: 0% 50%;
  }

  /* Disable the tilt easing so no residual transform motion animates. */
  .hero-img {
    transition: none;
  }
}
