/* THE KINETIC LAYER. Added 28 Sept 2026 with the redesign
   (docs/superpowers/specs/2026-09-24-site-redesign-design.md). motion.js splits the headlines and
   puts one of two classes on <html>:

     .motion-scroll  the browser supports scroll-driven animation: letters assemble and scatter on
                     the scroll timeline itself, run by the compositor, no script per frame.
     .motion-reveal  it does not: each block plays a one-time arrival when it enters (an
                     IntersectionObserver adds .in), and never plays again.

   Neither class is ever set for a visitor who asks for reduced motion, and the media query at the
   bottom stops everything this file added in case the preference changes mid-visit. With script
   blocked none of these rules match and the page reads as plain type.

   EVERY START STATE IS BEHIND .motion-on. A letter is only ever scattered because script put it
   there and promised to bring it back; nothing in the markup ships invisible. */

.motion-on .kw { display: inline-block; white-space: nowrap; }
.motion-on .kl { display: inline-block; }

/* THE FOIL MOVES DOWN TO THE LETTER. styles.css stamps the hero as `.hero-h1 span`: a clipped
   stripe background and a drop shadow. Once the words are split, that selector also matches every
   word and letter span, so the foil was stacked three deep, and a moving letter left its line's
   clipped foil behind as a ghost (seen in the first desktop screenshot, 28 Sept 2026). So the line
   and word spans shed it, and each letter, which the same rule already matches, carries its own. */
.motion-on .hero-h1 > span, .motion-on .hero-h1 .kw { background-image: none; filter: none; }

/* The holo word. Each letter carries its own slice of the gradient under the foil stripes, because
   a transformed child falls out of its parent's background-clip: text. --j is its place in the word. */
.motion-on .hero-h1 .holo .kl {
  background-image:
    repeating-linear-gradient(102deg,
      rgba(255,255,255,.00) 0px, rgba(255,255,255,.00) 6px,
      rgba(255,255,255,.55) 8px, rgba(255,255,255,.00) 13px,
      rgba(255,255,255,.00) 22px),
    var(--holo);
  background-size: auto, 6em 100%;
  background-position: var(--foil) 0, calc(var(--j, 0) * -0.52em) 0;
}

@keyframes k-settle {
  from { transform: translate(var(--dx), var(--dy)) rotate(var(--r)); opacity: 0; }
  to { transform: none; opacity: 1; }
}
@keyframes k-through {
  0% { transform: translate(var(--dx), var(--dy)) rotate(var(--r)); opacity: 0; }
  30%, 70% { transform: none; opacity: 1; }
  100% { transform: translate(calc(var(--dx) * -1), calc(var(--dy) * -1)) rotate(calc(var(--r) * -1)); opacity: 0; }
}
@keyframes k-arrive {
  from { transform: translate(var(--dx), calc(var(--dy) + .6em)) rotate(var(--r)); opacity: 0; }
  to { transform: none; opacity: 1; }
}
@keyframes k-drift-l { to { transform: translateX(-28vw); opacity: 0; } }
@keyframes k-drift-r { to { transform: translateX(28vw); opacity: 0; } }
@keyframes k-tilt { to { transform: perspective(1100px) rotateX(9deg) rotateY(-10deg) scale(.95); } }
@keyframes k-stack { from { transform: translate(-50%, 0) rotate(0deg); } }

/* ------------------------------------------------------------ the opening screen */
/* ON LOAD the hero letters settle in from scattered offsets. This is time-based, so both tiers get
   it; it is short, and the button beside it is on screen and clickable the whole time
   (test/hero-page.test.js: nothing required waits on an animation). */
.motion-on .hero-h1 .kl { animation: k-settle .85s cubic-bezier(.2, .8, .2, 1) both; animation-delay: calc(var(--i) * 20ms); }

/* SCROLLING AWAY drifts the words apart, alternate lines to alternate sides. */
@supports (animation-timeline: view()) {
  .motion-scroll .hero-h1 > span:nth-child(odd) .kw { animation: k-drift-l linear both; animation-timeline: view(); animation-range: exit 0% exit 100%; }
  .motion-scroll .hero-h1 > span:nth-child(even) .kw { animation: k-drift-r linear both; animation-timeline: view(); animation-range: exit 0% exit 100%; }
  /* The hero card leans back as the page moves on. */
  .motion-scroll .pack-stage { animation: k-tilt linear both; animation-timeline: scroll(root); animation-range: 0 100vh; }

  /* ------------------------------------------------------------ the story */
  /* One line per screen: letters assemble as the line reaches the middle and scatter as it goes.
     The timeline is the SCREEN's, named, so a letter's own movement never feeds back into it. */
  .motion-scroll .story-screen { view-timeline-name: --line; }
  .motion-scroll .story-line .kl { animation: k-through linear both; animation-timeline: --line; animation-range: cover 0% cover 100%; }
  .motion-scroll .story-cap { animation: k-arrive linear both; animation-timeline: --line; animation-range: cover 22% cover 45%; }

  /* Section headings slide up and assemble on arrival, and then stay. */
  .motion-scroll [data-kinetic="arrive"] { view-timeline-name: --arrive; }
  .motion-scroll [data-kinetic="arrive"] .kl { animation: k-arrive linear both; animation-timeline: --arrive; animation-range: entry 0% cover 40%; }

  /* The card fan: a stack that spreads like a hand being opened. The spread positions are the
     base styles in styles.css, so this only supplies the stacked start. */
  .motion-scroll .card-fan { view-timeline-name: --fan; }
  .motion-scroll .card-fan .fan-card { animation: k-stack linear both; animation-timeline: --fan; animation-range: entry 25% cover 55%; }
}

/* ------------------------------------------------------------ the about photos */
/* Two frames of him a second apart, a smile and a laugh, fading into each other (28 Sept 2026, his
   request). Time-based and CSS-only, so it needs neither motion tier; opacity only, so it runs on
   the compositor. Nine seconds a cycle: long enough to read each face, slow enough not to flicker. */
@keyframes who-fade {
  0%, 38% { opacity: 0; }
  50%, 88% { opacity: 1; }
  100% { opacity: 0; }
}
.who-fade .who-b { animation: who-fade 9s ease-in-out infinite; }

/* ------------------------------------------------------------ the fallback tier */
.motion-reveal [data-kinetic]:not([data-kinetic="hero"]) .kl {
  transform: translate(var(--dx), calc(var(--dy) + .6em)) rotate(var(--r));
  opacity: 0;
  transition: transform .7s cubic-bezier(.2, .8, .2, 1), opacity .7s ease;
  transition-delay: calc(var(--i) * 18ms);
}
.motion-reveal [data-kinetic].in .kl { transform: none; opacity: 1; }
/* Captions are observed themselves (data-reveal), not found through :has() — a browser old enough
   to be in this tier may not have :has(), and the caption would then never come back. */
.motion-reveal .story-cap { transition: opacity .6s ease .35s, transform .6s ease .35s; }
.motion-reveal .story-cap:not(.in) { opacity: 0; transform: translateY(12px); }
.motion-reveal .card-fan:not(.in) .fan-card { transform: translate(-50%, 0) rotate(0deg); }
.motion-reveal .card-fan .fan-card { transition: transform .9s cubic-bezier(.2, .8, .2, 1); }

/* ------------------------------------------------------------ reduced motion */
@media (prefers-reduced-motion: reduce) {
  .kl, .kw, .story-cap, .pack-stage, [data-kinetic], [data-reveal] {
    animation: none !important;
    transition: none !important;
    transform: none !important;
    opacity: 1 !important;
  }
  /* The fan keeps its spread: that is its final state, not a movement. */
  .card-fan .fan-card { animation: none !important; transition: none !important; }
  /* The about photo holds on the smile; the laugh never fades in. */
  .who-fade .who-b { animation: none !important; opacity: 0 !important; }
}
