// launch-video-30s.jsx — 30s Kin launch hero spot, 9:16 portrait.
// Apple-advert tempo: hard cuts every 3-5s, kinetic typography that
// punches in on the beat, phones that parallax + scale, Pilates and
// gym lifts woven together. Reuses KIN palette + PhoneFrame from
// social-posts.jsx (loaded earlier in the page).

// Master timeline — 7 acts in 30s. Tighter than the 60s cut: each act
// is one feature, one product moment, one line of type.
const ACTS_30 = [
  {id: 'hook',     title: 'Hook',           caption: 'Meet your coach.',        start: 0,    end: 3.0,  sfx: 'Mic pop · pad swell'},
  {id: 'voice',    title: 'Voice logging',  caption: 'Just say it.',            start: 3.0,  end: 7.5,  sfx: 'Soft chime · transcript taps'},
  {id: 'pilates',  title: 'Pilates flow',   caption: 'For every kind of move.', start: 7.5,  end: 12.0, sfx: 'Breath · warm pad'},
  {id: 'plan',     title: 'Plans flex',     caption: 'It adapts to you.',       start: 12.0, end: 17.0, sfx: 'Tone shift · soft swap'},
  {id: 'food',     title: 'Nutrition',      caption: 'One line. Logged.',       start: 17.0, end: 21.5, sfx: 'Warm chime · ring fill'},
  {id: 'progress', title: 'Progress',       caption: 'Watch it move.',          start: 21.5, end: 26.5, sfx: 'Rising tone · drawing pen'},
  {id: 'close',    title: 'Closer',         caption: 'A coach that listens.',   start: 26.5, end: 30.0, sfx: 'Pad resolve · stamp on logo'},
];

const TOTAL = 30;

// ── helpers ─────────────────────────────────────────────────────────────────

// Hard-cut friendly fade: fast in, fast out — Apple-style snap.
function snap(localTime, duration, fadeIn = 0.25, fadeOut = 0.25) {
  if (localTime < fadeIn) return Easing.easeOutCubic(clamp(localTime / fadeIn, 0, 1));
  const exitStart = duration - fadeOut;
  if (localTime > exitStart) {
    return 1 - Easing.easeInCubic(clamp((localTime - exitStart) / fadeOut, 0, 1));
  }
  return 1;
}

// Kinetic headline — slides up + scales in on a snap, holds, snaps out.
// Multiple lines can be staggered for a typewriter-of-words feel.
function KineticLine({ children, top, size = 140, italic, color = KIN.ink, delay = 0, weight = 400, letter = '-0.04em', align = 'left', leftPad = 64, rightPad = 64, blur = 0 }) {
  const { localTime, duration } = useSprite();
  const lt = localTime - delay;
  const op = lt < 0 ? 0 : snap(lt, duration - delay, 0.35, 0.3);
  const inT = Easing.easeOutCubic(clamp(lt / 0.45, 0, 1));
  const ty = (1 - inT) * 40;
  const sc = 0.94 + inT * 0.06;
  const bl = blur ? `blur(${(1 - inT) * blur}px)` : 'none';
  return (
    <div style={{
      position: 'absolute', top, left: leftPad, right: rightPad,
      fontFamily: KIN.serif, fontSize: size, lineHeight: 0.95, color,
      letterSpacing: letter, fontWeight: weight,
      fontStyle: italic ? 'italic' : 'normal',
      textAlign: align,
      opacity: op, transform: `translateY(${ty}px) scale(${sc})`,
      transformOrigin: align === 'center' ? '50% 100%' : '0% 100%',
      filter: bl,
    }}>
      {children}
    </div>
  );
}

// Eyebrow — small, tracked, coral.
function Eyebrow({ children, top = 200, delay = 0, leftPad = 64, align = 'left' }) {
  const { localTime, duration } = useSprite();
  const lt = localTime - delay;
  const op = lt < 0 ? 0 : snap(lt, duration - delay, 0.3, 0.25);
  return (
    <div style={{
      position: 'absolute', top, left: leftPad, right: leftPad,
      fontSize: 22, color: KIN.coral500, fontWeight: 600,
      letterSpacing: '.32em', textTransform: 'uppercase',
      textAlign: align,
      opacity: op,
    }}>{children}</div>
  );
}

// Word-by-word reveal for headlines — each word punches in on stagger.
function WordReveal({ words, top, size = 144, italicWords = [], color = KIN.ink, leftPad = 64, lineHeight = 0.95, stagger = 0.08, delay = 0 }) {
  const { localTime, duration } = useSprite();
  return (
    <div style={{
      position: 'absolute', top, left: leftPad, right: leftPad,
      fontFamily: KIN.serif, fontSize: size, lineHeight,
      letterSpacing: '-0.04em',
    }}>
      {words.map((w, i) => {
        const t0 = delay + i * stagger;
        const lt = localTime - t0;
        const inT = Easing.easeOutCubic(clamp(lt / 0.45, 0, 1));
        const op = lt < 0 ? 0 : snap(lt, duration - t0, 0.35, 0.3);
        const ty = (1 - inT) * 36;
        const sc = 0.92 + inT * 0.08;
        const isItalic = italicWords.includes(i);
        return (
          <span key={i} style={{
            display: 'inline-block', marginRight: '0.28em',
            color: isItalic ? KIN.coral500 : color,
            fontStyle: isItalic ? 'italic' : 'normal',
            opacity: op, transform: `translateY(${ty}px) scale(${sc})`,
            transformOrigin: '50% 100%',
          }}>{w}</span>
        );
      })}
    </div>
  );
}

// ── ACT 0 — Hook (0–3s) ─────────────────────────────────────────────────────
// Big single word "Listen." pops on. Logo lockup arrives late, snap-cut to next.

function Act0_Hook() {
  return (
    <Sprite start={0} end={3.0}>
      <div style={{position: 'absolute', inset: 0, background: KIN.paper}}/>

      {/* Subtle paper grain via radial */}
      <div style={{
        position: 'absolute', inset: 0,
        background: `radial-gradient(ellipse at 50% 30%, transparent 30%, rgba(43,42,40,0.04) 100%)`,
      }}/>

      {/* Big word, dead-center */}
      <Sprite start={0.15} end={3} keepMounted>
        {(p) => {
          const lt = p.localTime;
          const inT = Easing.easeOutCubic(clamp(lt / 0.5, 0, 1));
          const op = snap(lt, p.duration, 0.4, 0.35);
          const sc = 0.85 + inT * 0.15;
          const ty = (1 - inT) * 60;
          return (
            <div style={{
              position: 'absolute', top: 760, left: 0, right: 0, textAlign: 'center',
              fontFamily: KIN.serif, fontSize: 220, lineHeight: 0.95, color: KIN.ink,
              letterSpacing: '-0.04em', fontStyle: 'italic',
              opacity: op, transform: `translateY(${ty}px) scale(${sc})`,
              transformOrigin: '50% 50%',
            }}>Meet&nbsp;<span style={{color: KIN.coral500}}>kin.</span></div>
          );
        }}
      </Sprite>

      {/* Eyebrow above */}
      <Sprite start={0.8} end={3} keepMounted>
        {(p) => {
          const lt = p.localTime;
          const op = snap(lt, p.duration, 0.4, 0.3);
          return (
            <div style={{
              position: 'absolute', top: 700, left: 0, right: 0, textAlign: 'center',
              fontSize: 22, color: KIN.coral500, fontWeight: 600,
              letterSpacing: '.42em', textTransform: 'uppercase', opacity: op,
            }}>The coach that listens</div>
          );
        }}
      </Sprite>

      {/* Logo small, arrives last */}
      <Sprite start={1.5} end={3} keepMounted>
        {(p) => {
          const lt = p.localTime;
          const op = snap(lt, p.duration, 0.4, 0.3);
          const inT = Easing.easeOutBack(clamp(lt / 0.5, 0, 1));
          const sc = 0.7 + inT * 0.3;
          return (
            <div style={{
              position: 'absolute', top: 1100, left: '50%',
              transform: `translateX(-50%) scale(${sc})`,
              opacity: op,
            }}>
              <img src="/film/kin-icon.png" alt="Kin"
                width="150" height="150"
                style={{display: 'block', borderRadius: 34, boxShadow: '0 16px 50px rgba(184,106,77,0.32)'}}/>
            </div>
          );
        }}
      </Sprite>

      {/* Hairline that flashes across just before cut */}
      <Sprite start={2.6} end={3} keepMounted>
        {(p) => {
          const t = clamp(p.localTime / 0.4, 0, 1);
          return (
            <div style={{
              position: 'absolute', top: 960, left: 0, height: 1,
              width: `${Easing.easeInOutCubic(t) * 100}%`,
              background: KIN.coral, opacity: 1 - clamp((t - 0.7) / 0.3, 0, 1),
            }}/>
          );
        }}
      </Sprite>
    </Sprite>
  );
}

// ── ACT 1 — Voice logging (3–7.5s) ──────────────────────────────────────────
// Headline punches in word-by-word. Phone slides up from below with parallax.
// Inside phone: voice bubble appears with live waveform, transcript types,
// then a green "Saved · PR" chip slams in.

function Act1_Voice() {
  return (
    <Sprite start={3.0} end={7.5}>
      <div style={{position: 'absolute', inset: 0, background: KIN.paper}}/>

      <Eyebrow top={140} delay={0.0}>Voice logging</Eyebrow>
      <WordReveal
        words={['Just', 'say', 'it.']}
        italicWords={[2]}
        top={200} size={210} delay={0.05} stagger={0.09} leftPad={64}
      />

      {/* Phone with parallax — slides up from y=1500, settles at y=720 */}
      <Sprite start={3.4} end={7.5} keepMounted>
        {(p) => {
          const lt = p.localTime;
          const inT = Easing.easeOutCubic(clamp(lt / 0.7, 0, 1));
          const ty = (1 - inT) * 220;
          // Subtle hover-float during hold
          const holdT = clamp(lt - 0.7, 0, 4);
          const float = Math.sin(holdT * 1.4) * 6;
          const op = snap(lt, p.duration, 0.4, 0.3);
          return (
            <div style={{
              position: 'absolute', left: 200, top: 720,
              opacity: op, transform: `translateY(${ty + float}px)`,
            }}>
              <PhoneFrame x={0} y={0} width={680} height={1080}>
                <div style={{padding: '32px 28px 0', height: '100%', position: 'relative'}}>
                  <div style={{textAlign: 'center', fontSize: 22, color: KIN.ink3, marginBottom: 24}}>Kin ⌄</div>

                  {/* Voice bubble — appears at 0.7s into phone */}
                  <Sprite start={3.7} end={7.5} keepMounted>
                    {(p2) => {
                      const lt2 = p2.localTime;
                      const op2 = clamp(lt2 / 0.3, 0, 1);
                      const sc = 0.85 + Easing.easeOutBack(clamp(lt2 / 0.4, 0, 1)) * 0.15;
                      return (
                        <div style={{
                          marginLeft: 'auto', width: 'fit-content', maxWidth: '78%',
                          background: KIN.coral, color: '#fff',
                          padding: '18px 22px', borderRadius: 28, borderBottomRightRadius: 8,
                          display: 'flex', alignItems: 'center', gap: 14,
                          opacity: op2, transform: `scale(${sc})`, transformOrigin: 'right',
                          boxShadow: '0 10px 30px rgba(184,106,77,0.25)',
                        }}>
                          <div style={{display: 'flex', alignItems: 'center', gap: 3, height: 28}}>
                            {Array.from({length: 16}, (_, i) => {
                              const phase = i * 0.5 + lt2 * 8;
                              const h = Math.max(3, Math.abs(8 + Math.sin(phase) * 6 + Math.sin(phase * 1.7) * 5));
                              return <span key={i} style={{width: 3, height: h, background: 'rgba(255,255,255,0.9)', borderRadius: 2}}/>;
                            })}
                          </div>
                          <span style={{fontSize: 16, color: 'rgba(255,255,255,0.9)', fontFamily: KIN.mono}}>0:09</span>
                        </div>
                      );
                    }}
                  </Sprite>

                  {/* Transcript */}
                  <Sprite start={4.5} end={7.5} keepMounted>
                    {(p2) => {
                      const lt2 = p2.localTime;
                      const op2 = Easing.easeOutCubic(clamp(lt2 / 0.4, 0, 1));
                      const ty2 = (1 - op2) * 14;
                      return (
                        <div style={{
                          marginTop: 14, marginLeft: 'auto',
                          width: 'fit-content', maxWidth: '78%',
                          background: KIN.paper2, color: KIN.ink,
                          padding: '14px 20px', borderRadius: 26, borderBottomRightRadius: 6,
                          fontSize: 20, opacity: op2, transform: `translateY(${ty2}px)`,
                        }}>
                          4 sets of 5 deadlifts at 100
                        </div>
                      );
                    }}
                  </Sprite>

                  {/* Reply types in */}
                  <Sprite start={5.2} end={7.5} keepMounted>
                    {(p2) => {
                      const lt2 = p2.localTime;
                      const reply = "Saved. New PR.";
                      const chars = Math.floor(clamp(lt2 / 0.5, 0, 1) * reply.length);
                      const op2 = clamp(lt2 / 0.2, 0, 1);
                      return (
                        <div style={{
                          marginTop: 26, opacity: op2,
                          fontFamily: KIN.serif, fontSize: 32, lineHeight: 1.3, color: KIN.ink,
                        }}>
                          {reply.slice(0, chars)}
                          {chars < reply.length && <span style={{opacity: 0.5}}>|</span>}
                        </div>
                      );
                    }}
                  </Sprite>

                  {/* Saved chip slams in */}
                  <Sprite start={5.9} end={7.5} keepMounted>
                    {(p2) => {
                      const lt2 = p2.localTime;
                      const inT = Easing.easeOutBack(clamp(lt2 / 0.4, 0, 1));
                      const sc = 0.7 + inT * 0.3;
                      const op2 = clamp(lt2 / 0.2, 0, 1);
                      return (
                        <div style={{
                          marginTop: 22, opacity: op2,
                          transform: `scale(${sc})`, transformOrigin: 'left',
                          position: 'relative',
                        }}>
                          <div style={{position: 'absolute', left: 0, top: 0, bottom: 0, width: 4, background: KIN.green, borderRadius: 2}}/>
                          <div style={{
                            background: KIN.raised, border: `1px solid ${KIN.hairline}`, borderRadius: 18,
                            padding: '18px 22px', marginLeft: 12,
                          }}>
                            <div style={{fontSize: 12, letterSpacing: '.18em', color: KIN.green, fontWeight: 600, textTransform: 'uppercase'}}>Saved · Personal record</div>
                            <div style={{fontSize: 22, color: KIN.ink, marginTop: 6, fontWeight: 500}}>Deadlifts · 4×5 · 100 kg</div>
                            <div style={{fontSize: 16, color: KIN.ink3, marginTop: 4}}>+5 kg from last week</div>
                          </div>
                        </div>
                      );
                    }}
                  </Sprite>
                </div>
              </PhoneFrame>
            </div>
          );
        }}
      </Sprite>
    </Sprite>
  );
}

// ── ACT 2 — Pilates flow (7.5–12s) ──────────────────────────────────────────
// Different vibe: warmer paper tone, slower breath. Shows Kin handling
// non-strength training. Pilates session card with breath ring + flow steps.

function Act2_Pilates() {
  return (
    <Sprite start={7.5} end={12.0}>
      {/* Slightly warmer background to differentiate */}
      <div style={{position: 'absolute', inset: 0, background: KIN.paper2}}/>

      <Eyebrow top={140} delay={0.0} align="right" leftPad={64}>Pilates &middot; Yoga &middot; Strength</Eyebrow>
      <WordReveal
        words={['Every', 'kind', 'of', 'movement.']}
        italicWords={[3]}
        top={200} size={170} delay={0.05} stagger={0.08} leftPad={64} lineHeight={0.95}
      />

      {/* Phone — slides in from right side, slight rotation */}
      <Sprite start={7.9} end={12} keepMounted>
        {(p) => {
          const lt = p.localTime;
          const inT = Easing.easeOutCubic(clamp(lt / 0.7, 0, 1));
          const tx = (1 - inT) * 200;
          const rot = (1 - inT) * 4;
          const op = snap(lt, p.duration, 0.4, 0.3);
          return (
            <div style={{
              position: 'absolute', left: 200, top: 720,
              opacity: op, transform: `translateX(${tx}px) rotate(${rot}deg)`,
            }}>
              <PhoneFrame x={0} y={0} width={680} height={1080}>
                <div style={{padding: '32px 28px 0', height: '100%', position: 'relative'}}>
                  <div style={{textAlign: 'center', fontSize: 22, color: KIN.ink3, marginBottom: 24}}>Today ⌄</div>

                  {/* Session card with breath ring */}
                  <Sprite start={8.1} end={12} keepMounted>
                    {(p2) => {
                      const lt2 = p2.localTime;
                      const op2 = Easing.easeOutCubic(clamp(lt2 / 0.5, 0, 1));
                      const ty2 = (1 - op2) * 30;
                      // Breath ring scales slowly between 1.0 and 1.08
                      const breath = 1 + Math.sin(lt2 * 1.4) * 0.04;
                      return (
                        <div style={{
                          opacity: op2, transform: `translateY(${ty2}px)`,
                          background: KIN.raised, border: `1px solid ${KIN.hairline}`, borderRadius: 28,
                          padding: 24, position: 'relative', overflow: 'hidden',
                        }}>
                          <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 16}}>
                            <div>
                              <div style={{fontSize: 12, letterSpacing: '.18em', color: KIN.coral500, fontWeight: 600, textTransform: 'uppercase'}}>Today · 18 min</div>
                              <div style={{fontFamily: KIN.serif, fontSize: 38, color: KIN.ink, letterSpacing: '-0.025em', marginTop: 4, lineHeight: 1}}>Mat Pilates</div>
                              <div style={{fontFamily: KIN.serif, fontStyle: 'italic', fontSize: 22, color: KIN.coral500, marginTop: 4}}>core &amp; mobility</div>
                            </div>
                            {/* Breath ring */}
                            <div style={{position: 'relative', width: 100, height: 100, flexShrink: 0}}>
                              <div style={{
                                position: 'absolute', inset: 0,
                                borderRadius: 999, background: `radial-gradient(circle, ${KIN.coral50} 0%, transparent 70%)`,
                                transform: `scale(${breath})`,
                              }}/>
                              <div style={{
                                position: 'absolute', inset: 18,
                                borderRadius: 999, border: `2px solid ${KIN.coral}`,
                                transform: `scale(${breath})`,
                              }}/>
                              <div style={{
                                position: 'absolute', inset: 0,
                                display: 'flex', alignItems: 'center', justifyContent: 'center',
                                fontFamily: KIN.serif, fontStyle: 'italic', color: KIN.coral500, fontSize: 16,
                              }}>breathe</div>
                            </div>
                          </div>

                          {/* Flow steps appear sequentially */}
                          <div style={{display: 'flex', flexDirection: 'column', gap: 10, marginTop: 8}}>
                            {[
                              {label: 'Roll-down warm-up', dur: '2 min'},
                              {label: 'Hundred', dur: '100 reps'},
                              {label: 'Single-leg stretch', dur: '3 min'},
                              {label: 'Spine twist', dur: '2 min'},
                              {label: 'Cool-down', dur: '3 min'},
                            ].map((step, i) => (
                              <Sprite key={i} start={1.0 + i * 0.18} end={4.5} keepMounted>
                                {(p3) => {
                                  const lt3 = p3.localTime;
                                  const op3 = Easing.easeOutCubic(clamp(lt3 / 0.35, 0, 1));
                                  const tx3 = (1 - op3) * 24;
                                  const isActive = i === 1;
                                  return (
                                    <div style={{
                                      opacity: op3, transform: `translateX(${tx3}px)`,
                                      display: 'flex', alignItems: 'center', gap: 14,
                                      padding: '12px 16px',
                                      background: isActive ? KIN.coral50 : 'transparent',
                                      borderRadius: 14,
                                      border: isActive ? `1px solid ${KIN.coral}` : '1px solid transparent',
                                    }}>
                                      <div style={{
                                        width: 28, height: 28, borderRadius: 999,
                                        background: isActive ? KIN.coral : KIN.paper2,
                                        display: 'flex', alignItems: 'center', justifyContent: 'center',
                                        color: isActive ? '#fff' : KIN.ink3,
                                        fontFamily: KIN.mono, fontSize: 13, fontWeight: 600,
                                        flexShrink: 0,
                                      }}>{i + 1}</div>
                                      <div style={{flex: 1, fontSize: 18, color: KIN.ink, fontWeight: isActive ? 500 : 400}}>{step.label}</div>
                                      <div style={{fontFamily: KIN.mono, fontSize: 14, color: KIN.ink3}}>{step.dur}</div>
                                    </div>
                                  );
                                }}
                              </Sprite>
                            ))}
                          </div>
                        </div>
                      );
                    }}
                  </Sprite>
                </div>
              </PhoneFrame>
            </div>
          );
        }}
      </Sprite>

      {/* Footer caption — center-bottom, well below phone */}
      <Sprite start={10.9} end={12} keepMounted>
        {(p) => {
          const lt = p.localTime;
          const op = snap(lt, p.duration, 0.4, 0.3);
          return (
            <div style={{
              position: 'absolute', top: 1840, left: 0, right: 0, textAlign: 'center',
              fontFamily: KIN.serif, fontStyle: 'italic', fontSize: 38, color: KIN.coral500,
              letterSpacing: '-0.02em', opacity: op,
            }}>Lift heavy. Flow easy. Kin keeps up.</div>
          );
        }}
      </Sprite>
    </Sprite>
  );
}

// ── ACT 3 — Plans flex (12–17s) ─────────────────────────────────────────────
// User says "didn't sleep" — week pills morph, Monday flips from Push → Mobility.
// Big "It adapts." headline punches in.

function Act3_Plan() {
  return (
    <Sprite start={12.0} end={17.0}>
      <div style={{position: 'absolute', inset: 0, background: KIN.paper}}/>

      <Eyebrow top={140} delay={0.0}>Plans that adapt</Eyebrow>
      <WordReveal
        words={['It', 'flexes', 'with', 'you.']}
        italicWords={[1]}
        top={200} size={180} delay={0.05} stagger={0.08} leftPad={64}
      />

      <Sprite start={12.4} end={17} keepMounted>
        {(p) => {
          const lt = p.localTime;
          const inT = Easing.easeOutCubic(clamp(lt / 0.7, 0, 1));
          const ty = (1 - inT) * 200;
          const op = snap(lt, p.duration, 0.4, 0.3);
          return (
            <div style={{
              position: 'absolute', left: 200, top: 760,
              opacity: op, transform: `translateY(${ty}px)`,
            }}>
              <PhoneFrame x={0} y={0} width={680} height={1040}>
                <div style={{padding: '32px 28px 0', height: '100%'}}>
                  <div style={{textAlign: 'center', fontSize: 22, color: KIN.ink3, marginBottom: 24}}>Your Week ⌄</div>

                  {/* Day pills — Monday morphs at 2.4s */}
                  <Sprite start={12.6} end={17} keepMounted>
                    {(p2) => {
                      const lt2 = p2.localTime;
                      const flipped = lt2 > 2.0;
                      const flipT = clamp((lt2 - 2.0) / 0.5, 0, 1);
                      const days = [
                        {d: 'M', kind: flipped ? 'rest' : 'push'},
                        {d: 'T', kind: 'done'},
                        {d: 'W', kind: 'pull'},
                        {d: 'T', kind: 'rest'},
                        {d: 'F', kind: 'push'},
                        {d: 'S', kind: 'cardio'},
                        {d: 'S', kind: 'rest'},
                      ];
                      return (
                        <div style={{display: 'grid', gridTemplateColumns: 'repeat(7, 1fr)', gap: 8}}>
                          {days.map((day, i) => {
                            const isMonday = i === 0;
                            const isDone = day.kind === 'done';
                            const isRest = day.kind === 'rest';
                            const bg = isDone ? KIN.green : isRest ? KIN.coral50 : KIN.coral;
                            const fg = isDone || !isRest ? '#fff' : KIN.coral500;
                            const sc = isMonday ? (flipped ? (0.6 + Easing.easeOutBack(flipT) * 0.4) : 1) : 1;
                            return (
                              <div key={i} style={{display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 6}}>
                                <div style={{fontSize: 14, color: KIN.ink3}}>{day.d}</div>
                                <div style={{
                                  width: 56, height: 56, borderRadius: 999,
                                  background: bg, color: fg,
                                  display: 'flex', alignItems: 'center', justifyContent: 'center',
                                  fontSize: 18, fontWeight: 600,
                                  transform: `scale(${sc})`,
                                }}>
                                  {isDone ? '✓' : isRest ? 'R' : day.kind[0].toUpperCase()}
                                </div>
                              </div>
                            );
                          })}
                        </div>
                      );
                    }}
                  </Sprite>

                  {/* User says */}
                  <Sprite start={13.2} end={17} keepMounted>
                    {(p2) => {
                      const lt2 = p2.localTime;
                      const op2 = Easing.easeOutCubic(clamp(lt2 / 0.4, 0, 1));
                      const ty2 = (1 - op2) * 14;
                      return (
                        <div style={{
                          marginTop: 36, marginLeft: 'auto',
                          width: 'fit-content', maxWidth: '78%',
                          background: KIN.coral, color: '#fff',
                          padding: '14px 20px', borderRadius: 24, borderBottomRightRadius: 6,
                          fontSize: 19, opacity: op2, transform: `translateY(${ty2}px)`,
                        }}>
                          rough night. didn't sleep
                        </div>
                      );
                    }}
                  </Sprite>

                  {/* Reply */}
                  <Sprite start={13.8} end={17} keepMounted>
                    {(p2) => {
                      const lt2 = p2.localTime;
                      const op2 = Easing.easeOutCubic(clamp(lt2 / 0.4, 0, 1));
                      return (
                        <div style={{
                          marginTop: 18, opacity: op2,
                          fontFamily: KIN.serif, fontSize: 26, lineHeight: 1.35, color: KIN.ink,
                          fontStyle: 'italic',
                        }}>
                          Heard. Swapping push for mobility today.
                        </div>
                      );
                    }}
                  </Sprite>

                  {/* Adjustment chip slams in */}
                  <Sprite start={14.6} end={17} keepMounted>
                    {(p2) => {
                      const lt2 = p2.localTime;
                      const inT = Easing.easeOutBack(clamp(lt2 / 0.4, 0, 1));
                      const sc = 0.7 + inT * 0.3;
                      const op2 = clamp(lt2 / 0.2, 0, 1);
                      return (
                        <div style={{
                          marginTop: 22, opacity: op2,
                          transform: `scale(${sc})`, transformOrigin: 'left',
                          background: KIN.coral50, borderLeft: `3px solid ${KIN.coral}`,
                          padding: '16px 20px', borderRadius: 14,
                        }}>
                          <div style={{fontSize: 12, letterSpacing: '.16em', color: KIN.coral500, fontWeight: 600, textTransform: 'uppercase', marginBottom: 6}}>Adjusted</div>
                          <div style={{fontFamily: KIN.serif, fontSize: 22, color: KIN.ink, fontStyle: 'italic'}}>
                            Mon: Push → Mobility · Fri: +20 min
                          </div>
                        </div>
                      );
                    }}
                  </Sprite>
                </div>
              </PhoneFrame>
            </div>
          );
        }}
      </Sprite>
    </Sprite>
  );
}

// ── ACT 4 — Nutrition (17–21.5s) ────────────────────────────────────────────

function Act4_Food() {
  return (
    <Sprite start={17.0} end={21.5}>
      <div style={{position: 'absolute', inset: 0, background: KIN.paper}}/>

      <Eyebrow top={140} delay={0.0}>Nutrition</Eyebrow>
      <WordReveal
        words={['One', 'line.', 'Logged.']}
        italicWords={[2]}
        top={200} size={200} delay={0.05} stagger={0.09} leftPad={64}
      />

      <Sprite start={17.4} end={21.5} keepMounted>
        {(p) => {
          const lt = p.localTime;
          const inT = Easing.easeOutCubic(clamp(lt / 0.7, 0, 1));
          const ty = (1 - inT) * 200;
          const op = snap(lt, p.duration, 0.4, 0.3);
          return (
            <div style={{
              position: 'absolute', left: 200, top: 880,
              opacity: op, transform: `translateY(${ty}px)`,
            }}>
              <PhoneFrame x={0} y={0} width={680} height={920}>
                <div style={{padding: '32px 28px 0', height: '100%'}}>
                  <div style={{textAlign: 'center', fontSize: 22, color: KIN.ink3, marginBottom: 22}}>Kin ⌄</div>

                  {/* Quick line types */}
                  <Sprite start={17.5} end={21.5} keepMounted>
                    {(p2) => {
                      const lt2 = p2.localTime;
                      const full = "chili and a beer for dinner";
                      const chars = Math.floor(clamp(lt2 / 0.9, 0, 1) * full.length);
                      const op2 = clamp(lt2 / 0.2, 0, 1);
                      return (
                        <div style={{
                          marginLeft: 'auto', maxWidth: '85%', width: 'fit-content',
                          background: KIN.coral, color: '#fff',
                          padding: '18px 22px', borderRadius: 26, borderBottomRightRadius: 8,
                          fontSize: 22, lineHeight: 1.3, opacity: op2,
                        }}>
                          {full.slice(0, chars)}
                          {chars < full.length && <span style={{opacity: 0.6}}>|</span>}
                        </div>
                      );
                    }}
                  </Sprite>

                  {/* Macro card slams in */}
                  <Sprite start={18.6} end={21.5} keepMounted>
                    {(p2) => {
                      const lt2 = p2.localTime;
                      const inT = Easing.easeOutBack(clamp(lt2 / 0.5, 0, 1));
                      const sc = 0.85 + inT * 0.15;
                      const op2 = clamp(lt2 / 0.25, 0, 1);
                      const ringT = Easing.easeOutCubic(clamp(lt2 / 1.4, 0, 1));
                      const r = 70, c = 2 * Math.PI * r;
                      return (
                        <div style={{
                          marginTop: 26, opacity: op2,
                          transform: `scale(${sc})`, transformOrigin: 'center',
                          background: KIN.raised, border: `1px solid ${KIN.hairline}`, borderRadius: 24,
                          padding: 24, display: 'flex', alignItems: 'center', gap: 24,
                        }}>
                          <div style={{position: 'relative', width: 170, height: 170, flexShrink: 0}}>
                            <svg width="170" height="170" viewBox="0 0 170 170" style={{transform: 'rotate(-90deg)'}}>
                              <circle cx="85" cy="85" r={r} fill="none" stroke={KIN.paper2} strokeWidth="8"/>
                              <circle cx="85" cy="85" r={r} fill="none" stroke={KIN.green} strokeWidth="8" strokeLinecap="round"
                                strokeDasharray={c} strokeDashoffset={c * (1 - 0.59 * ringT)}/>
                            </svg>
                            <div style={{position: 'absolute', inset: 0, display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center'}}>
                              <div style={{fontFamily: KIN.serif, fontSize: 46, color: KIN.ink, letterSpacing: '-0.02em'}}>{Math.floor(940 * ringT)}</div>
                              <div style={{fontSize: 12, color: KIN.ink3, letterSpacing: '.16em', textTransform: 'uppercase', marginTop: 4}}>kcal</div>
                            </div>
                          </div>
                          <div style={{flex: 1}}>
                            {[
                              {label: 'Protein', val: 38, target: 160, color: KIN.green},
                              {label: 'Carbs',   val: 92, target: 220, color: KIN.coral},
                              {label: 'Fat',     val: 32, target: 70,  color: '#C79A7A'},
                            ].map(m => (
                              <div key={m.label} style={{marginBottom: 14}}>
                                <div style={{display: 'flex', justifyContent: 'space-between', fontSize: 15, marginBottom: 5}}>
                                  <span style={{color: KIN.ink, fontWeight: 500}}>{m.label}</span>
                                  <span style={{color: KIN.ink3, fontFamily: KIN.mono}}>{Math.floor(m.val * ringT)}/{m.target}g</span>
                                </div>
                                <div style={{height: 6, background: KIN.paper2, borderRadius: 3, overflow: 'hidden'}}>
                                  <div style={{width: `${(m.val/m.target) * 100 * ringT}%`, height: '100%', background: m.color, borderRadius: 3}}/>
                                </div>
                              </div>
                            ))}
                          </div>
                        </div>
                      );
                    }}
                  </Sprite>
                </div>
              </PhoneFrame>
            </div>
          );
        }}
      </Sprite>
    </Sprite>
  );
}

// ── ACT 5 — Progress (21.5–26.5s) ───────────────────────────────────────────
// Big strength chart draws on with a moving dot; "+38 kg" stat slams in.

function Act5_Progress() {
  return (
    <Sprite start={21.5} end={26.5}>
      <div style={{position: 'absolute', inset: 0, background: KIN.paper}}/>

      <Eyebrow top={140} delay={0.0}>12 weeks · real numbers</Eyebrow>
      <WordReveal
        words={['Watch', 'it', 'move.']}
        italicWords={[2]}
        top={200} size={210} delay={0.05} stagger={0.09} leftPad={64}
      />

      {/* Chart */}
      <Sprite start={22} end={26.5} keepMounted>
        {(p) => {
          const lt = p.localTime;
          const op = Easing.easeOutCubic(clamp(lt / 0.6, 0, 1));
          const ty = (1 - op) * 60;
          const drawT = Easing.easeOutCubic(clamp((lt - 0.4) / 1.8, 0, 1));
          const points = [70, 71.5, 73, 73, 75, 76.5, 78, 80, 84, 88, 95, 102.5];
          const W = 880, H = 520, padX = 48, padY = 40;
          const xStep = (W - padX * 2) / (points.length - 1);
          const minV = 65, maxV = 110;
          const yScale = (H - padY * 2) / (maxV - minV);
          const yOf = v => H - padY - (v - minV) * yScale;
          const xOf = i => padX + i * xStep;

          const visibleCount = Math.max(2, Math.floor(drawT * points.length));
          const partialT = (drawT * points.length) - visibleCount;
          let path = `M ${xOf(0)} ${yOf(points[0])}`;
          for (let i = 1; i < visibleCount; i++) path += ` L ${xOf(i)} ${yOf(points[i])}`;
          if (visibleCount < points.length) {
            const px = xOf(visibleCount - 1) + xStep * partialT;
            const py = yOf(points[visibleCount - 1]) + (yOf(points[visibleCount]) - yOf(points[visibleCount - 1])) * partialT;
            path += ` L ${px} ${py}`;
          }
          // Live value
          const liveIdx = Math.min(points.length - 1, visibleCount - 1);
          const liveV = points[liveIdx] + ((points[Math.min(liveIdx + 1, points.length - 1)] - points[liveIdx]) * partialT);

          return (
            <div style={{
              position: 'absolute', left: (1080 - W) / 2, top: 800,
              width: W, opacity: op, transform: `translateY(${ty}px)`,
            }}>
              {/* Stats row */}
              <div style={{display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 24, padding: '0 12px'}}>
                <div>
                  <div style={{fontSize: 13, letterSpacing: '.18em', color: KIN.ink3, fontWeight: 600, textTransform: 'uppercase'}}>Deadlift · top set</div>
                  <div style={{fontFamily: KIN.serif, fontSize: 56, color: KIN.ink, letterSpacing: '-0.025em', lineHeight: 1, marginTop: 6}}>
                    {liveV.toFixed(1)}<span style={{fontSize: 26, color: KIN.ink3, marginLeft: 6}}>kg</span>
                  </div>
                </div>
                <div style={{textAlign: 'right'}}>
                  <div style={{fontSize: 13, letterSpacing: '.18em', color: KIN.green, fontWeight: 600, textTransform: 'uppercase'}}>+ {(liveV - 70).toFixed(1)} kg</div>
                  <div style={{fontFamily: KIN.serif, fontStyle: 'italic', fontSize: 22, color: KIN.ink3, marginTop: 4}}>since week 1</div>
                </div>
              </div>

              {/* Chart svg */}
              <svg width={W} height={H} viewBox={`0 0 ${W} ${H}`} style={{display: 'block'}}>
                {/* Hairlines */}
                {[70, 80, 90, 100].map(v => (
                  <g key={v}>
                    <line x1={padX} y1={yOf(v)} x2={W - padX} y2={yOf(v)} stroke={KIN.hairline} strokeDasharray="4 6"/>
                    <text x={padX - 8} y={yOf(v) + 5} fontFamily={KIN.mono} fontSize="14" fill={KIN.ink3} textAnchor="end">{v}</text>
                  </g>
                ))}
                {/* Path */}
                <path d={path} fill="none" stroke={KIN.coral} strokeWidth="4" strokeLinecap="round" strokeLinejoin="round"/>
                {/* Live dot */}
                {visibleCount >= 1 && (() => {
                  const px = visibleCount < points.length ? xOf(visibleCount - 1) + xStep * partialT : xOf(points.length - 1);
                  const py = yOf(liveV);
                  const pulse = 1 + Math.sin(lt * 6) * 0.15;
                  return (
                    <g>
                      <circle cx={px} cy={py} r={14 * pulse} fill={KIN.coral} fillOpacity="0.2"/>
                      <circle cx={px} cy={py} r="7" fill={KIN.coral} stroke="#fff" strokeWidth="3"/>
                    </g>
                  );
                })()}
              </svg>
            </div>
          );
        }}
      </Sprite>

      {/* Bottom mini-chips: Pilates flexibility, Run pace */}
      <Sprite start={24.1} end={26.5} keepMounted>
        {(p) => {
          const lt = p.localTime;
          const op = Easing.easeOutCubic(clamp(lt / 0.5, 0, 1));
          const ty = (1 - op) * 30;
          return (
            <div style={{
              position: 'absolute', bottom: 120, left: 80, right: 80,
              display: 'flex', gap: 16, justifyContent: 'center',
              opacity: op, transform: `translateY(${ty}px)`,
            }}>
              {[
                {lbl: 'Mobility', val: '+22°', sub: 'forward fold'},
                {lbl: '5K pace',  val: '−42 s', sub: 'per km'},
                {lbl: 'Sleep',    val: '7h 24m', sub: 'avg'},
              ].map((s, i) => (
                <div key={i} style={{
                  background: KIN.paper2, border: `1px solid ${KIN.hairline}`, borderRadius: 18,
                  padding: '14px 22px', minWidth: 200, textAlign: 'center',
                }}>
                  <div style={{fontSize: 11, letterSpacing: '.18em', color: KIN.ink3, fontWeight: 600, textTransform: 'uppercase'}}>{s.lbl}</div>
                  <div style={{fontFamily: KIN.serif, fontSize: 32, color: KIN.coral500, letterSpacing: '-0.02em', marginTop: 4, lineHeight: 1}}>{s.val}</div>
                  <div style={{fontSize: 13, color: KIN.ink3, marginTop: 4, fontStyle: 'italic'}}>{s.sub}</div>
                </div>
              ))}
            </div>
          );
        }}
      </Sprite>
    </Sprite>
  );
}

// ── ACT 6 — Closer (26.5–30s) ───────────────────────────────────────────────
// Logo lockup + tagline. Hairline draws, eyebrow appears, big logo
// scale-in, tagline punches.

function Act6_Close() {
  return (
    <Sprite start={26.5} end={30.0}>
      <div style={{position: 'absolute', inset: 0, background: KIN.paper}}/>

      {/* Hairline frame draws */}
      <Sprite start={26.5} end={30.0} keepMounted>
        {(p) => {
          const drawT = Easing.easeOutCubic(clamp(p.localTime / 0.8, 0, 1));
          const perim = 2 * (1080 + 1920) - 56 * 8;
          return (
            <svg width="1080" height="1920" style={{position: 'absolute', inset: 0}}>
              <rect x="56" y="56" width={1080 - 112} height={1920 - 112}
                fill="none" stroke={KIN.coral500} strokeWidth="1.4" strokeOpacity="0.5"
                strokeDasharray={perim}
                strokeDashoffset={perim * (1 - drawT)}/>
            </svg>
          );
        }}
      </Sprite>

      {/* Logo, big and centered */}
      <Sprite start={26.8} end={30.0} keepMounted>
        {(p) => {
          const lt = p.localTime;
          const inT = Easing.easeOutBack(clamp(lt / 0.6, 0, 1));
          const sc = 0.7 + inT * 0.3;
          const op = snap(lt, p.duration, 0.5, 0.4);
          return (
            <div style={{
              position: 'absolute', top: 700, left: '50%',
              transform: `translateX(-50%) scale(${sc})`,
              opacity: op,
            }}>
              <img src="/film/kin-icon.png" alt="Kin"
                width="220" height="220"
                style={{display: 'block', borderRadius: 50, boxShadow: '0 20px 60px rgba(184,106,77,0.32)'}}/>
            </div>
          );
        }}
      </Sprite>

      {/* Wordmark */}
      <Sprite start={27.2} end={30.0} keepMounted>
        {(p) => {
          const lt = p.localTime;
          const inT = Easing.easeOutCubic(clamp(lt / 0.5, 0, 1));
          const ty = (1 - inT) * 30;
          const op = snap(lt, p.duration, 0.5, 0.4);
          return (
            <div style={{
              position: 'absolute', top: 980, left: 0, right: 0, textAlign: 'center',
              fontFamily: KIN.serif, fontSize: 168, lineHeight: 1, color: KIN.ink,
              letterSpacing: '-0.04em', opacity: op,
              transform: `translateY(${ty}px)`,
            }}>kin</div>
          );
        }}
      </Sprite>

      {/* Tagline */}
      <Sprite start={27.8} end={30.0} keepMounted>
        {(p) => {
          const lt = p.localTime;
          const inT = Easing.easeOutCubic(clamp(lt / 0.5, 0, 1));
          const ty = (1 - inT) * 24;
          const op = snap(lt, p.duration, 0.5, 0.4);
          return (
            <div style={{
              position: 'absolute', top: 1180, left: 0, right: 0, textAlign: 'center',
              fontFamily: KIN.serif, fontSize: 56, fontStyle: 'italic',
              color: KIN.coral500, letterSpacing: '-0.025em', opacity: op,
              transform: `translateY(${ty}px)`,
            }}>A coach that listens.</div>
          );
        }}
      </Sprite>

      {/* CTA chip */}
      <Sprite start={28.5} end={30.0} keepMounted>
        {(p) => {
          const lt = p.localTime;
          const inT = Easing.easeOutBack(clamp(lt / 0.4, 0, 1));
          const sc = 0.7 + inT * 0.3;
          const op = snap(lt, p.duration, 0.4, 0.4);
          return (
            <div style={{
              position: 'absolute', top: 1340, left: '50%',
              transform: `translateX(-50%) scale(${sc})`,
              opacity: op,
              background: KIN.ink, color: KIN.paper,
              padding: '20px 36px', borderRadius: 999,
              fontSize: 22, fontFamily: KIN.sans, fontWeight: 500, letterSpacing: '.12em',
              textTransform: 'uppercase',
            }}>Now on the App Store</div>
          );
        }}
      </Sprite>
    </Sprite>
  );
}

// ── Master scene ────────────────────────────────────────────────────────────

function LaunchVideo30() {
  const { time } = useTime();
  return (
    <>
      <Act0_Hook/>
      <Act1_Voice/>
      <Act2_Pilates/>
      <Act3_Plan/>
      <Act4_Food/>
      <Act5_Progress/>
      <Act6_Close/>

      {/* Persistent kin watermark — top-left, subtle */}
      <div style={{
        position: 'absolute', top: 56, left: 56,
        display: 'flex', alignItems: 'center', gap: 10,
        opacity: time < 0.5 || time > 26 ? 0 : 0.85,
        transition: 'opacity 0.4s',
      }}>
        <img src="/film/kin-icon.png" alt="" width="36" height="36" style={{borderRadius: 8}}/>
        <span style={{fontFamily: KIN.serif, fontSize: 28, letterSpacing: '-0.025em', color: KIN.ink}}>kin</span>
      </div>

      {/* Persistent timecode chip — bottom-right, mono */}
      <div style={{
        position: 'absolute', bottom: 56, right: 56,
        fontFamily: KIN.mono, fontSize: 18, color: KIN.ink3,
        letterSpacing: '.12em',
        opacity: time < 0.5 || time > 26 ? 0 : 0.7,
        transition: 'opacity 0.4s',
      }}>
        {String(Math.floor(time)).padStart(2,'0')}:{String(Math.floor((time % 1) * 100)).padStart(2,'0')} / 30:00
      </div>
    </>
  );
}

Object.assign(window, {
  LaunchVideo30, ACTS_30, TOTAL,
});
