/* ==========================================================================
   Yoosl — Motion layer

   NOTE: the Figma file carries no keyframe/prototype motion data
   (get_motion_context returned an empty node set for every frame checked),
   so these are authored to match the design's character rather than
   transcribed from it. Everything here is additive: with JS disabled or
   reduced motion requested, content renders in its final state.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Scroll reveal
   Elements opt in with [data-reveal]; js/animations.js adds .is-in when they
   cross the viewport. The pre-state is only applied once JS has marked the
   document with .js — so no-JS visitors see the finished layout.
   -------------------------------------------------------------------------- */

.js [data-reveal] {
  opacity: 0;
  transition: opacity 700ms var(--ease-out),
              transform 700ms var(--ease-out);
  transition-delay: var(--reveal-delay, 0ms);
  will-change: opacity, transform;
}

.js [data-reveal="up"]    { transform: translateY(28px); }
.js [data-reveal="down"]  { transform: translateY(-28px); }
.js [data-reveal="start"] { transform: translateX(36px); }  /* from inline-start (right in RTL) */
.js [data-reveal="end"]   { transform: translateX(-36px); }
.js [data-reveal="scale"] { transform: scale(.94); }
.js [data-reveal="fade"]  { transform: none; }

.js [data-reveal].is-in {
  opacity: 1;
  transform: none;
}

/* Stagger: set on a parent, applied to its [data-reveal] children */
[data-stagger] > * { --reveal-delay: 0ms; }
[data-stagger] > *:nth-child(1) { --reveal-delay: 0ms; }
[data-stagger] > *:nth-child(2) { --reveal-delay: 80ms; }
[data-stagger] > *:nth-child(3) { --reveal-delay: 160ms; }
[data-stagger] > *:nth-child(4) { --reveal-delay: 240ms; }
[data-stagger] > *:nth-child(5) { --reveal-delay: 320ms; }
[data-stagger] > *:nth-child(6) { --reveal-delay: 400ms; }
[data-stagger] > *:nth-child(7) { --reveal-delay: 480ms; }
[data-stagger] > *:nth-child(8) { --reveal-delay: 560ms; }

/* --------------------------------------------------------------------------
   Hero entrance — runs on load, not on scroll
   -------------------------------------------------------------------------- */

@keyframes rise {
  from { opacity: 0; transform: translateY(32px); }
  to   { opacity: 1; transform: none; }
}
@keyframes fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
@keyframes popIn {
  0%   { opacity: 0; transform: scale(.9); }
  60%  { opacity: 1; transform: scale(1.02); }
  100% { opacity: 1; transform: scale(1); }
}

.js [data-enter] {
  opacity: 0;
  animation: rise 800ms var(--ease-out) forwards;
  animation-delay: var(--enter-delay, 0ms);
}
.js [data-enter="fade"] { animation-name: fade; }
.js [data-enter="pop"]  { animation-name: popIn; }

[data-enter-stagger] > *:nth-child(1) { --enter-delay: 80ms; }
[data-enter-stagger] > *:nth-child(2) { --enter-delay: 200ms; }
[data-enter-stagger] > *:nth-child(3) { --enter-delay: 320ms; }
[data-enter-stagger] > *:nth-child(4) { --enter-delay: 440ms; }
[data-enter-stagger] > *:nth-child(5) { --enter-delay: 560ms; }

/* --------------------------------------------------------------------------
   Line-by-line headline reveal
   js/animations.js wraps each line in .line > span
   -------------------------------------------------------------------------- */

.line { display: block; overflow: hidden; }
.line > span {
  display: block;
  transform: translateY(110%);
  transition: transform 900ms var(--ease-out);
  transition-delay: var(--line-delay, 0ms);
}
.is-in .line > span,
.line.is-in > span { transform: none; }

/* --------------------------------------------------------------------------
   Stats band timeline — transcribed from Figma "Desktop - 1" (192:3954)
   via get_motion_context(recursive): 14 animated nodes on one 4000ms
   timeline. Times below are that timeline's own percentages.

   Figma reports loopMode "boomerang" — the canvas preview looping back and
   forth on its own. What happens here is closer to a scrub: js/animations.js
   pauses these animations and sets one shared currentTime straight from the
   scroll position, between 0 and 2981ms (the last frame anything moves on).
   Scrolling down grows the band, scrolling up shrinks it, and it stops when
   the scrolling stops.

   `.is-shot` goes on at startup and stays on — currentTime 0 is already the
   pre-state, so there is nothing to take off to get back there.

   The one exception is bandFlash, which is triggered rather than scrubbed;
   see the note on it below.

   Node map: 316:375 band mask · 275:512 band container · 267:329 / 267:330
   vertical guides · 267:331 horizontal guide · 329:1764 bottom line ·
   275:513 food photo · 275:514 photographer · 316:313 flash ·
   275:516 / :519 / :522 / :525 the four figures.
   -------------------------------------------------------------------------- */

/* Pre-state, i.e. the frame at currentTime 0. Only applied once JS has marked
   the document, so a no-JS visitor gets the finished band. */
.js .stats-photo__frame {
  clip-path: inset(0 var(--strip-side) var(--strip-hide) var(--strip-side));
  translate: 0 var(--band-lift);
}
/* The strip shows only the band's top 12.222vw, which on its own cuts the
   plates off along the bottom. The photo starts zoomed out and slid up so the
   plates sit in the middle of the strip instead, trimmed a little top and
   bottom. js/animations.js works out --photo-shift for the current layout. */
.js .stats-photo__bg { scale: var(--photo-start); translate: 0 var(--photo-shift, 0px); }
.js .stats-photo__shot { translate: -110% 0; scale: .94; }
.js .stats-photo__grid > li { opacity: 0; translate: 0 30px; }
.js .hero__rule--v1 { inset-inline-start: var(--band-side); }
.js .hero__rule--v2 { inset-inline-end: var(--band-side); }
.js .hero__rule--h { translate: 0 calc(var(--band-rise) * -1); }
.js .stats-photo__rule { translate: 0 var(--band-top-gap); opacity: 1; }

/* 316:375 — the mask grows from 1036x176 to full bleed, ease-in-out, done at
   23.075%. The lift is Component 7's own 150px travel folded in: the band's
   top edge finishes 70px lower than it starts. */
@keyframes bandGrow {
  0% {
    animation-timing-function: ease-in-out;
    clip-path: inset(0 var(--strip-side) var(--strip-hide) var(--strip-side));
    translate: 0 var(--band-lift);
  }
  23.075%, 100% {
    clip-path: inset(0 0 0 0);
    translate: 0 0;
  }
}

/* 275:513 — the photo eases back from its zoomed-out, slid-up start (plates
   centred in the strip) to its resting place, in step with the mask opening. */
@keyframes bandPhoto {
  0% {
    animation-timing-function: ease-in-out;
    scale: var(--photo-start);
    translate: 0 var(--photo-shift, 0px);
  }
  23.075%, 100% { scale: 1; translate: 0 0; }
}

/* 275:514 — slides in from the left and settles to full size. The x track's
   midpoint sits at 24.5% of the travel, which is what gives it the overshoot
   feel; the cubic below is Figma's own.

   Figma scales him 0.83 -> 1, but the cut-out is cropped flat across the top
   of his cap, and the box is only 107% of the band: below a scale of ~.935
   that flat edge drops inside the band and shows as a straight cut through
   his head while he slides in (Trello, 2026-10-01). Starting at .94 keeps the
   crop above the band's top edge for the whole entrance. */
@keyframes bandShooter {
  0% {
    animation-timing-function: cubic-bezier(.5, 0, .5, 1);
    translate: -110% 0;
    scale: .94;
  }
  16.35% {
    animation-timing-function: cubic-bezier(0, 0, 0, 1.033);
    translate: -27% 0;
    scale: .96;
  }
  38.25% {
    animation-timing-function: cubic-bezier(.5, 0, .5, 1);
    translate: 0 0;
    scale: .98;
  }
  39.29%, 100% { translate: 0 0; scale: 1; }
}

/* 316:313 — the only shine in the file: two pulses to 0.7 and back, then
   hidden for the rest of the timeline.

   This one is NOT scrubbed. Tied to the scroll it stops being a flash and
   becomes a dimmer, sitting half-lit whenever you rest mid-scroll. Instead
   js/animations.js triggers it when the scrub crosses 1530ms going down — the
   frame the photographer lands on — and lets it run 1530->2981ms on its own
   clock, so both pulses fire at their real speed. Scrolling back up hides it
   and re-arms it for the next pass. */
@keyframes bandFlash {
  0%      { opacity: 0; }
  38.25%  { animation-timing-function: cubic-bezier(.5, 0, .5, 1); opacity: 0; }
  42.284% { animation-timing-function: cubic-bezier(.5, 0, .5, 1); opacity: .7; }
  45.628% { opacity: 0; }
  67.145% { animation-timing-function: cubic-bezier(.5, 0, .5, 1); opacity: 0; }
  71.178% { animation-timing-function: cubic-bezier(.5, 0, .5, 1); opacity: .7; }
  74.522% { opacity: 0; }
  100%    { opacity: 0; }
}

/* 267:329 / 267:330 / 267:331 — the guides travel with the band's edges. */
@keyframes ruleOutStart {
  0% { animation-timing-function: ease-in-out; inset-inline-start: var(--band-side); }
  23.075%, 100% { inset-inline-start: 0%; }
}
@keyframes ruleOutEnd {
  0% { animation-timing-function: ease-in-out; inset-inline-end: var(--band-side); }
  22.95%, 100% { inset-inline-end: 0%; }
}
@keyframes ruleDown {
  0% { animation-timing-function: ease-in-out; translate: 0 calc(var(--band-rise) * -1); }
  22.95%, 100% { translate: 0 0; }
}

/* 329:1764 — starts on the strip's bottom edge, then drops away under the
   band. Figma's cubic overshoots slightly on the way down. */
@keyframes bandRuleDrop {
  0% {
    animation-timing-function: cubic-bezier(.633, -.002, .24, 1.012);
    translate: 0 var(--band-top-gap);
    opacity: 1;
  }
  22.95%, 100% {
    /* 364px at the 1440 reference: 214 of its own plus Component 7's 150 */
    translate: 0 calc(var(--band-top-gap) + 25.278vw);
    opacity: 0;
  }
}

/* 275:516 / :519 / :522 / :525 — each figure holds, then rises 30px and fades
   in. Linear, as the file has them. The per-item delay/duration below is the
   timeline's own staggering, right to left. */
@keyframes statRise {
  from { opacity: 0; translate: 0 30px; }
  to   { opacity: 1; translate: 0 0; }
}

.js .stats-photo__frame.is-shot { animation: bandGrow 4000ms linear both; }
.js .stats-photo__frame.is-shot .stats-photo__bg { animation: bandPhoto 4000ms linear both; }
.js .stats-photo__frame.is-shot .stats-photo__shot { animation: bandShooter 4000ms linear both; }
.js .stats-photo__frame.is-shot .stats-photo__flash { animation: bandFlash 4000ms linear both; }
.js .stats-photo.is-shot .stats-photo__rule { animation: bandRuleDrop 4000ms linear both; }
.js .page--home.is-shot .hero__rule--v1 { animation: ruleOutStart 4000ms linear both; }
.js .page--home.is-shot .hero__rule--v2 { animation: ruleOutEnd 4000ms linear both; }
.js .page--home.is-shot .hero__rule--h  { animation: ruleDown 4000ms linear both; }

.js .stats-photo__frame.is-shot .stats-photo__grid > li {
  animation: statRise var(--stat-dur) linear var(--stat-delay) both;
}
/* 1.4M+ 378-1530ms · 820+ 654-1989ms · 640+ 1083-2132ms · 95% 1530-2922ms */
.stats-photo__grid > li:nth-child(1) { --stat-delay: 378ms;  --stat-dur: 1152ms; }
.stats-photo__grid > li:nth-child(2) { --stat-delay: 654ms;  --stat-dur: 1335ms; }
.stats-photo__grid > li:nth-child(3) { --stat-delay: 1083ms; --stat-dur: 1049ms; }
.stats-photo__grid > li:nth-child(4) { --stat-delay: 1530ms; --stat-dur: 1392ms; }

/* Reduced motion, or no way to scrub: show where the timeline ends. */
.js .stats-photo__frame.is-done,
.js .page--home.is-done .hero__rule--v1,
.js .page--home.is-done .hero__rule--v2,
.js .page--home.is-done .hero__rule--h,
.js .stats-photo.is-done .stats-photo__rule,
.js .stats-photo__frame.is-done .stats-photo__bg,
.js .stats-photo__frame.is-done .stats-photo__shot,
.js .stats-photo__frame.is-done .stats-photo__grid > li {
  animation: none;
}
.js .stats-photo__frame.is-done { clip-path: inset(0 0 0 0); translate: 0 0; scale: 1; }
.js .stats-photo__frame.is-done .stats-photo__bg { scale: 1; translate: 0 0; }
.js .stats-photo__frame.is-done .stats-photo__shot { translate: 0 0; scale: 1; }
.js .stats-photo__frame.is-done .stats-photo__flash { opacity: 0; }
.js .stats-photo__frame.is-done .stats-photo__grid > li { opacity: 1; translate: 0 0; }
.js .page--home.is-done .hero__rule--v1 { inset-inline-start: 0; }
.js .page--home.is-done .hero__rule--v2 { inset-inline-end: 0; }
.js .page--home.is-done .hero__rule--h  { translate: 0 0; }
.js .stats-photo.is-done .stats-photo__rule { opacity: 0; }

/* --------------------------------------------------------------------------
   Counters — the value is written by JS; this just softens the swap
   -------------------------------------------------------------------------- */

/* Same bidi isolation as .num: the value is written as "640+", and without an
   LTR isolate the RTL paragraph reorders the sign to read "+640". */
[data-count] {
  font-variant-numeric: tabular-nums;
  direction: ltr;
  unicode-bidi: isolate;
  display: inline-block;
}

/* --------------------------------------------------------------------------
   Marquee (logo / keyword ribbon)
   -------------------------------------------------------------------------- */

/* No gap between the two copies: each track's own padding-inline-end is the
   space at the seam, so it matches the space between logos and the loop
   lands exactly where it started. */
.marquee {
  display: flex;
  overflow: hidden;
  gap: 0;
  mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
  -webkit-mask-image: linear-gradient(to right, transparent, #000 8%, #000 92%, transparent);
}
.marquee__track {
  display: flex;
  flex: none;
  align-items: center;
  gap: var(--sp-12);
  padding-inline-end: var(--sp-12);
  animation: marquee var(--marquee-dur, 32s) linear infinite;
}
/* Pause on hover only where there is a real hover. On a touch screen a tap
   leaves :hover stuck on the tapped logo, so the ribbon stopped and never
   started again (Trello, 2026-10-01). */
@media (hover: hover) and (pointer: fine) {
  .marquee:hover .marquee__track { animation-play-state: paused; }
}
@keyframes marquee {
  from { transform: translateX(0); }
  to   { transform: translateX(-100%); }
}
/* RTL: travel the other way so it reads naturally */
[dir="rtl"] .marquee__track { animation-name: marqueeRtl; }
@keyframes marqueeRtl {
  from { transform: translateX(0); }
  to   { transform: translateX(100%); }
}

/* --------------------------------------------------------------------------
   Ambient motion
   -------------------------------------------------------------------------- */

@keyframes float {
  0%, 100% { transform: translateY(0); }
  50%      { transform: translateY(-14px); }
}
.float { animation: float 6s var(--ease-in-out) infinite; }
.float--slow { animation-duration: 9s; }

@keyframes pulseRing {
  0%   { box-shadow: 0 0 0 0 rgba(229, 105, 50, .45); }
  70%  { box-shadow: 0 0 0 18px rgba(229, 105, 50, 0); }
  100% { box-shadow: 0 0 0 0 rgba(229, 105, 50, 0); }
}
.pulse { animation: pulseRing 2.4s var(--ease-out) infinite; }

@keyframes spinSlow {
  to { transform: rotate(360deg); }
}
.spin-slow { animation: spinSlow 24s linear infinite; }

/* Progress bar for the article reading indicator */
.read-progress {
  position: fixed;
  inset-block-start: 0;
  inset-inline: 0;
  block-size: 3px;
  z-index: 150;
  background: transparent;
}
.read-progress__bar {
  block-size: 100%;
  inline-size: 0;
  background: var(--primary-500);
  transform-origin: inline-start;
  transition: inline-size 120ms linear;
}

/* Parallax — JS writes --py */
[data-parallax] { transform: translate3d(0, var(--py, 0px), 0); will-change: transform; }

/* Scroll-driven underline for the podcast/episode player bar */
@keyframes bars {
  0%, 100% { transform: scaleY(.35); }
  50%      { transform: scaleY(1); }
}
.eq { display: flex; align-items: center; gap: 3px; block-size: 20px; }
.eq span {
  inline-size: 3px;
  block-size: 100%;
  border-radius: 2px;
  background: var(--primary-500);
  transform-origin: bottom;
  animation: bars 900ms var(--ease-in-out) infinite;
}
.eq span:nth-child(2) { animation-delay: 140ms; }
.eq span:nth-child(3) { animation-delay: 280ms; }
.eq span:nth-child(4) { animation-delay: 420ms; }
.eq span:nth-child(5) { animation-delay: 560ms; }

/* --------------------------------------------------------------------------
   Reduced motion — collapse everything to a plain, instant state
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  .js [data-reveal],
  .js [data-enter] {
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
    transition: none !important;
  }
  .line > span { transform: none !important; transition: none !important; }
  /* The band timeline collapses to its end state, flash hidden. */
  .js .stats-photo__frame,
  .js .stats-photo__bg,
  .js .stats-photo__shot,
  .js .stats-photo__flash,
  .js .stats-photo__rule,
  .js .stats-photo__grid > li,
  .js .hero__rule--v1,
  .js .hero__rule--v2,
  .js .hero__rule--h {
    animation: none !important;
  }
  .js .stats-photo__frame { clip-path: inset(0 0 0 0) !important; translate: 0 !important; scale: 1 !important; }
  .js .stats-photo__bg { scale: 1 !important; }
  .js .stats-photo__shot { translate: 0 !important; scale: 1 !important; }
  .js .stats-photo__flash { opacity: 0 !important; }
  .js .stats-photo__rule { opacity: 0 !important; }
  .js .stats-photo__grid > li { opacity: 1 !important; translate: 0 !important; }
  .js .hero__rule--v1 { inset-inline-start: 0 !important; }
  .js .hero__rule--v2 { inset-inline-end: 0 !important; }
  .js .hero__rule--h { translate: 0 !important; }
  .marquee__track,
  .float,
  .pulse,
  .spin-slow,
  .eq span { animation: none !important; }
  [data-parallax] { transform: none !important; }
  *, *::before, *::after {
    transition-duration: .01ms !important;
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
  }
}
