/* ============================================================
   Motion — everything below the hero banner.

   The hero is out of scope and untouched: no selector in this
   file matches inside .mast, .name-wordmark, .name-photo, or the
   header pill. hero.css keeps sole authority over that sequence.

   Thesis — the passing edge.
   The hero reveals by a clip edge that sweeps up from the bottom
   and changes the state of everything it crosses: the ground
   arrives, the type flips dark to white, the portrait lands.
   Below the fold the reader's scroll becomes that edge.

   The CV is the one authored sequence, because it is the one
   place where the vertical axis carries meaning. The rail draws
   itself downward as you descend twenty years of it, and every
   dot flashes accent the moment the edge reaches it, then cools
   to grey — except the two roles that are still running, which
   stay lit and keep breathing while the section is on screen.
   Every role was "now" once; two still are.

   Everything else is quiet supporting state in the same idiom:
   headings wipe up, copy rises, the video posters land from a
   clip and a 1.06 scale exactly the way the hero portrait does.
   ============================================================ */

:root {
  /* Shared with the hero's wipe so the page reads as one piece of
     choreography rather than two systems. hero.css owns the values. */
  --m-wipe-ease: var(--wipe-ease, cubic-bezier(.33, .7, .35, 1));
  --m-rise-ease: cubic-bezier(.16, 1, .3, 1);
  /* Snappier and flatter than --m-rise-ease: the lightning round
     arrives, it does not settle. */
  --m-burst-ease: cubic-bezier(.2, .9, .3, 1);
  --m-rise: .46s;
  --m-wipe: .72s;
}

/* The base sheet drifts the first section down on load. It plays a
   full viewport below the fold where no one is looking, and it
   double-reveals against the scroll pass below. */
.loading main .wrap > section:first-child,
.loaded main .wrap > section:first-child {
  opacity: 1;
  top: 0;
  transition: none;
}

/* ---------------------------------------------------------------
   Supporting reveals

   Hidden states are gated on body.loaded, which scripts.js sets in
   the same pass that starts the IntersectionObserver. If the script
   never runs, nothing here hides and the page reads in full.
   --------------------------------------------------------------- */

.loaded main .wrap > section > :is(p, .option-toggle),
.loaded main .wrap > section > :is(.lightning, .videos) {
  opacity: 0;
  transform: translateY(.55em);
  transition:
    opacity var(--m-rise) var(--m-rise-ease) var(--d, 0ms),
    transform var(--m-rise) var(--m-rise-ease) var(--d, 0ms);
}
.loaded main .wrap > section.shown > :is(p, .option-toggle),
.loaded main .wrap > section > :is(.lightning, .videos).shown {
  opacity: 1;
  transform: none;
}

/* Blocks arrive in reading order, capped so the tail of a long
   section is never waiting on the head. */
.loaded main .wrap > section > *:nth-child(2) { --d: 80ms; }
.loaded main .wrap > section > *:nth-child(3) { --d: 150ms; }
.loaded main .wrap > section > *:nth-child(4) { --d: 210ms; }
.loaded main .wrap > section > *:nth-child(n+5) { --d: 260ms; }
/* These two carry their own trigger, so they start the moment they
   are reached rather than waiting out the section's queue. */
.loaded main .wrap > section > :is(.lightning, .videos) { --d: 0ms; }

/* Headings take the hero's wipe: the clip edge rises and the line
   is simply there behind it. The negative insets keep descenders,
   italic overhang, and the serif's side bearings out of the clip at
   rest, so nothing is shaved once the animation is done. */
.loaded main .wrap > section > h2 {
  clip-path: inset(100% -.25em -.4em -.25em);
  transform: translateY(.16em);
  transition:
    clip-path var(--m-wipe) var(--m-wipe-ease),
    transform var(--m-wipe) var(--m-wipe-ease);
}
.loaded main .wrap > section.shown > h2 {
  clip-path: inset(-.4em -.25em -.4em -.25em);
  transform: none;
}

/* Lightning round. This one is a list and reads as a list, so a
   stagger is honest here — and the section is named for its cadence,
   so this is the one place on the page that is allowed a different
   one. The rows fire in a hard, fast burst behind the bolt strike in
   delight.css rather than drifting up like body copy. --ld is the
   single delay for the row and its emoji, so the two never separate.

   .lightning and .videos carry their own data-section so the
   observer watches them directly. Sitting ~700px and ~1000px below
   the top of [data-section="more"], they would otherwise reveal on
   the section's trigger — a full viewport before anyone can see
   them — and the reader would arrive to motion that already ran. */
.loaded main .lightning .list li {
  --ld: .22s;
  opacity: 0;
  transform: translateY(.5em);
  transition:
    opacity .24s var(--m-burst-ease) var(--ld),
    transform .24s var(--m-burst-ease) var(--ld);
}
.loaded main .lightning .list li .emoji {
  display: inline-block;
  transform: scale(.55);
  transition: transform .26s var(--linkBtnBounce) var(--ld);
}
.loaded main .lightning.shown .list li { opacity: 1; transform: none; }
.loaded main .lightning.shown .list li .emoji { transform: none; }

.loaded main .lightning .list li:nth-child(2)  { --ld: 0.266s; }
.loaded main .lightning .list li:nth-child(3)  { --ld: 0.312s; }
.loaded main .lightning .list li:nth-child(4)  { --ld: 0.358s; }
.loaded main .lightning .list li:nth-child(5)  { --ld: 0.404s; }
.loaded main .lightning .list li:nth-child(6)  { --ld: 0.450s; }
.loaded main .lightning .list li:nth-child(7)  { --ld: 0.496s; }
.loaded main .lightning .list li:nth-child(8)  { --ld: 0.542s; }
.loaded main .lightning .list li:nth-child(9)  { --ld: 0.588s; }
.loaded main .lightning .list li:nth-child(10) { --ld: 0.634s; }



/* On Screen. The card arrives, then the poster lands inside it from
   a bottom-up clip and a 1.06 scale — the hero portrait's move,
   scaled down to a thumbnail. */
.loaded main .videos .video-card {
  --vd: .28s;
  opacity: 0;
  transform: translateY(.7em);
  transition:
    opacity .44s var(--m-rise-ease) var(--vd),
    transform .44s var(--m-rise-ease) var(--vd);
}
.loaded main .videos .video-card:nth-child(2) { --vd: .38s; }
.loaded main .videos .video-card:nth-child(3) { --vd: .48s; }
.loaded main .videos.shown .video-card { opacity: 1; transform: none; }

.loaded main .videos .video-facade img {
  clip-path: inset(100% 0 0 0);
  transform: scale(1.06);
  transition:
    clip-path .8s var(--m-wipe-ease) calc(var(--vd) + .08s),
    transform .95s var(--m-rise-ease) calc(var(--vd) + .08s);
}
.loaded main .videos.shown .video-facade img {
  clip-path: inset(0 0 0 0);
  transform: none;
}

/* ---------------------------------------------------------------
   The CV — the authored sequence

   Resting colours for the dot, so one keyframe can flash every dot
   accent and then land each one on the colour it is meant to hold.
   polish.css already paints current roles accent; this preserves it.
   --------------------------------------------------------------- */

.cv > li {
  --dot-rest: var(--color-fill);
  --dot-bg: var(--color-bg);
}
.cv > li.current {
  --dot-rest: var(--accent);
  --dot-bg: var(--accent);
}

/* One-shot fallback for engines without scroll-driven animation.
   Each role is phased: the block arrives, the rail draws through it,
   the dot lands. --cd walks the roles down the list. */
.loaded main .cv > li { --cd: 0s; }
.loaded main .cv > li:nth-child(2) { --cd: .14s; }
.loaded main .cv > li:nth-child(3) { --cd: .28s; }
.loaded main .cv > li:nth-child(4) { --cd: .42s; }

.loaded main .cv .cv-wrap {
  opacity: 0;
  transform: translateY(.5em);
  transition:
    opacity var(--m-rise) var(--m-rise-ease) var(--cd),
    transform var(--m-rise) var(--m-rise-ease) var(--cd);
}
.loaded main .shown .cv .cv-wrap { opacity: 1; transform: none; }

/* The rail. scaleY needs no knowledge of the segment heights, which
   the base sheet sets differently per role and again at every
   breakpoint — so the draw rides on top of that geometry rather than
   duplicating it.

   The base sheet paints the rail rgba(0,0,0,.84) and lets
   mix-blend-mode:overlay lighten it to #eee against the page ground.
   Blending resolves against the nearest isolated stacking context,
   and animating the rail — or its ancestor's opacity — makes one:
   measured, that turned the rail from rgb(238,238,238) to
   rgb(40,40,40). Same resting colour, stated outright, so it no
   longer depends on what else on the page is moving. */
.loaded main .cv .details::before,
.loaded main .cv > li:not(:last-child) .info p::before {
  --rail-rest: color-mix(in srgb, var(--color-bg) 96.75%, #000);
  mix-blend-mode: normal;
  background: var(--accent);
  transform: scaleY(0);
  transform-origin: 50% 0;
  transition:
    transform .62s var(--m-wipe-ease) calc(var(--cd) + .12s),
    background .42s ease calc(var(--cd) + .46s);
}
/* Drawn hot, then cooled — the rail keeps the faint resting weight it
   has always had, and the accent lives only in the passing edge. */
.loaded main .shown .cv .details::before,
.loaded main .shown .cv > li:not(:last-child) .info p::before {
  background: var(--rail-rest);
  transform: scaleY(1);
}

.loaded main .cv .title::before {
  transform: scale(0);
  transition: transform .5s var(--linkBtnBounce) calc(var(--cd) + .34s);
}
.loaded main .shown .cv .title::before { transform: scale(1); }

/* The "still running" ring. Sits under the dot at the same
   coordinates. Scoped to li.current so it cannot collide with the
   .promotions .title:after dot the base sheet defines. */
.cv > li.current .title::after {
  --dotCenter: calc((var(--size-m) * 1.4 - var(--dotSize)) / 2);
  content: "";
  position: absolute;
  top: var(--dotCenter);
  left: calc((var(--innerPad) + var(--dotSize) - 3px) / -2);
  width: var(--dotSize);
  height: var(--dotSize);
  border-radius: 100%;
  opacity: 0;
  pointer-events: none;
  z-index: 19;
  transition: opacity .4s ease calc(var(--cd) + .46s);
}
.loaded main .shown .cv > li.current .title::after { opacity: 1; }

/* The loop runs only while the section is on screen: .visible is
   toggled off by the observer, .shown is not. */
main [data-section="cv"].visible .cv > li.current .title::after {
  animation: nowPulse 3.2s ease-out infinite;
}
main [data-section="cv"].visible .cv > li.current + li.current .title::after {
  animation-delay: .55s;
}

/* ---------------------------------------------------------------
   Scroll-driven upgrade

   Where the engine supports it the reader's scroll drives the
   sequence directly, which is the whole point: the edge is their
   position in the page, not a timer that fired once.
   --------------------------------------------------------------- */

@supports (animation-timeline: view()) {
  .loaded main .cv .cv-wrap,
  .loaded main .cv .details::before,
  .loaded main .cv > li:not(:last-child) .info p::before,
  .loaded main .cv .title::before,
  .cv > li.current .title::after {
    transition: none;
  }

  .loaded main .cv .cv-wrap {
    animation: cvArrive linear both;
    animation-timeline: view(block);
    animation-range: cover 8vh cover 34vh;
  }

  .loaded main .cv .details::before,
  .loaded main .cv > li:not(:last-child) .info p::before {
    animation: railDraw linear both;
    animation-timeline: view(block);
    animation-range: cover 20vh cover 58vh;
  }

  .loaded main .cv .title::before {
    animation: dotPass linear both;
    animation-timeline: view(block);
    animation-range: cover 40vh cover 56vh;
  }

  .cv > li.current .title::after {
    animation: ringOn linear both;
    animation-timeline: view(block);
    animation-range: cover 56vh cover 66vh;
  }
  /* Two timelines on one element: the ring's arrival is scrolled,
     its breathing is not. */
  main [data-section="cv"].visible .cv > li.current .title::after {
    animation: ringOn linear both, nowPulse 3.2s ease-out infinite;
    animation-timeline: view(block), auto;
    animation-range: cover 56vh cover 66vh, normal;
  }
  main [data-section="cv"].visible .cv > li.current + li.current .title::after {
    animation-delay: 0s, .55s;
  }
}

@keyframes cvArrive {
  from { opacity: 0; transform: translateY(.5em); }
  to   { opacity: 1; transform: none; }
}

@keyframes railDraw {
  0%   { transform: scaleY(0); background: var(--accent); }
  62%  { background: var(--accent); }
  100% { transform: scaleY(1); background: var(--rail-rest); }
}

/* The edge arrives hot and cools. Past roles hollow out to grey,
   current roles keep the accent they already had. */
@keyframes dotPass {
  0% {
    transform: scale(.2);
    background: var(--accent);
    border-color: var(--accent);
  }
  58% {
    transform: scale(1.34);
    background: var(--accent);
    border-color: var(--accent);
  }
  100% {
    transform: scale(1);
    background: var(--dot-bg);
    border-color: var(--dot-rest);
  }
}

@keyframes ringOn {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes nowPulse {
  0%   { box-shadow: 0 0 0 0 rgba(253, 83, 33, .38); }
  62%  { box-shadow: 0 0 0 10px rgba(253, 83, 33, 0); }
  100% { box-shadow: 0 0 0 0 rgba(253, 83, 33, 0); }
}

/* ---------------------------------------------------------------
   Reduced motion

   polish.css zeroes transition and animation durations globally,
   but a scroll-driven animation takes its progress from the
   timeline and ignores duration, so those have to be switched off
   by name. Everything lands in its finished state.
   --------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .loaded main .wrap > section > :is(p, .lightning, .videos, .option-toggle),
  .loaded main .lightning .list li,
  .loaded main .lightning .list li .emoji,
  .loaded main .videos .video-card,
  .loaded main .cv .cv-wrap {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
  }
  .loaded main .wrap > section > h2,
  .loaded main .videos .video-facade img {
    clip-path: none !important;
    transform: none !important;
    animation: none !important;
  }
  .loaded main .cv .details::before,
  .loaded main .cv > li:not(:last-child) .info p::before,
  .loaded main .cv .title::before {
    transform: none !important;
    animation: none !important;
  }
  .cv > li.current .title::after,
  main [data-section="cv"].visible .cv > li.current .title::after {
    opacity: 1 !important;
    animation: none !important;
    box-shadow: 0 0 0 5px var(--accent-soft);
  }
}
