/* ============================================================
   RootBound — Animated Video · scenes (the 150s film)
   Composes Sprites from animations.jsx + rbv-lib.jsx.
   ============================================================ */

const WORLD = 'assets/world-warm.jpg';
const WORLD2 = 'assets/world-clean.jpg';

const RBV_CHAPTERS = [
  { t: 0,   label: 'The gap' },
  { t: 18,  label: 'RootBound' },
  { t: 36,  label: 'The network' },
  { t: 62,  label: 'Programs' },
  { t: 88,  label: 'Patients' },
  { t: 114, label: 'The role' },
  { t: 134, label: 'The ask' },
];

const RBV_CAPS = [
  { t: 1,    x: "Every day, someone is discharged into a life that doesn't fit the plan.", d: 6 },
  { t: 7.5,  x: "A plan that doesn't match real life. A follow-up that never happens.", d: 6 },
  { t: 14,   x: "Harm that happens in the gap — where no one is responsible.", d: 3.6 },
  { t: 19,   x: "RootBound is built to close that gap.", d: 5 },
  { t: 24.5, x: "A nurse-led, direct-care clinic — rooted in one community.", d: 6 },
  { t: 31,   x: "Open to everyone — regardless of insurance, income, or ZIP code.", d: 4.6 },
  { t: 37,   x: "Its roots reach into every corner of care.", d: 5 },
  { t: 42.5, x: "The hospital. The ER. The pharmacy. Public health. Social services.", d: 7 },
  { t: 50,   x: "And into the home — 150 ZIP codes across King and Snohomish.", d: 5.5 },
  { t: 56,   x: "Six service lines, three entities — a clinician on the line around the clock.", d: 5.6 },
  { t: 63,   x: "Six programs grow on one backbone.", d: 5 },
  { t: 68.5, x: "Glow funds the mission. TLC brings patients home from the hospital.", d: 6.5 },
  { t: 75.5, x: "A public-health partnership. A membership for every budget. Rural reach.", d: 6.5 },
  { t: 82.5, x: "And underneath it all — a named owner for every open episode.", d: 5 },
  { t: 89,   x: "Take Amy — fifty-one, in Lynnwood. A telehealth visit; her labs, read line by line.", d: 7 },
  { t: 96.5, x: "Robert, sixty-eight — home from the hospital with COPD. TLC reaches him within a day.", d: 7 },
  { t: 104,  x: "Maria, in rural Eastern Washington — monitored at home, in her own language.", d: 5.2 },
  { t: 109.6,x: "Three lives. One ending: a follow-up booked before the call closes.", d: 3.8 },
  { t: 115,  x: "This is what the Clinic Director holds.", d: 5 },
  { t: 120.5,x: "Run the programs. Build the network.", d: 5 },
  { t: 125.5,x: "Guard the structure. Drive the funding.", d: 5 },
  { t: 130.5,x: "The person who keeps every root connected.", d: 3.4 },
  { t: 135,  x: "Every program is the same promise, scaled.", d: 4.6 },
  { t: 140,  x: "No one falls through.", d: 4 },
  { t: 144.5,x: "RootBound Health — Clinic Director.", d: 3 },
  { t: 147.5,x: "When you're ready, the next step is a conversation.", d: 2.4 },
];

const PROGRAMS = [
  { tag: 'A · Revenue', nm: 'RootBound Glow', ds: 'The aesthetics line — the cash engine.',
    detail: 'A cash-pay aesthetics line at ~77% gross margin on the entry HydraFacial. It cross-subsidizes sliding-scale primary care and the rural mission — and every visit routes a primary-care touchpoint.' },
  { tag: 'B · Transitions', nm: 'TLC', ds: '30-day hospital-to-home pathway.',
    detail: 'High-risk discharges flagged by the hospital case manager from Day −2, first contact paced by condition (COPD ≤24h, heart failure ≤48h), held to a clean handoff at Day 30.' },
  { tag: 'C · Public health', nm: 'STI Partnership', ds: 'Proposed §318 with PHSKC.',
    detail: 'A proposed designated §318 STI-clinic partnership with Public Health–Seattle & King County — adding telehealth and after-hours capacity into the south-King ZIP codes the system can\'t reach at scale.' },
  { tag: 'D · Membership', nm: 'The Continuum', ds: 'A tier for every budget.',
    detail: 'A six-tier membership pairing continuity DPC with patient financing and three catastrophic-coverage paths — from $0 sponsored care to a complete healthcare home.' },
  { tag: 'E · Access', nm: 'Taproot', ds: 'Statewide rural reach.',
    detail: 'Cellular-first RPM (no Wi-Fi needed), a monthly mobile-clinic circuit, and food-as-medicine — built to survive even if telehealth flexibilities sunset.' },
  { tag: 'F · Backbone', nm: 'Follow-Up Ops', ds: 'A named owner for every episode.',
    detail: 'The SOP spine under everything: four parallel tracks, three-attempts-then-escalate, red-flag routing, and a single named owner who holds each episode through closure.' },
];

const ROLE = [
  { n: '01', t: 'Run the programs', d: 'All six, on one shared clinical backbone.' },
  { n: '02', t: 'Build the network', d: 'Hospitals, public health, FQHCs, state agencies.' },
  { n: '03', t: 'Guard the structure', d: 'Keep the three entities and their root barriers clean.' },
  { n: '04', t: 'Drive the funding', d: 'HRSA, USDA, and CMS rural-transformation grants.' },
];

/* three patient stories, shown in sequence inside the "Patients" chapter */
const CASES = [
  { nm: 'Amy W.', meta: '· 51 · Lynnwood, WA', tag: 'SYNCHRONOUS TELEHEALTH · ESTABLISHED PATIENT',
    note: 'Results review\n& continuity', start: 89, end: 97.4,
    rows: [
      'Labs reviewed — thyroid stable on current dose',
      'Mixed lipids — diet plan, recheck in 3 months',
      'Hepatitis B non-immune — revaccination started',
      'Macrocytosis, no anemia — reviewed, reassured',
      'Follow-up visit booked before the call ends',
    ] },
  { nm: 'Robert T.', meta: '· 68 · home from hospital', tag: 'TLC · 30-DAY HOSPITAL-TO-HOME',
    note: 'COPD\ntransition', start: 97.4, end: 105.4,
    rows: [
      'Discharged after a COPD exacerbation',
      'First TLC contact inside 24 hours',
      'Medications reconciled — two duplicates caught',
      'Inhaler technique re-taught on camera',
      'Pulmonology follow-up + a clean Day-30 handoff',
    ] },
  { nm: 'Maria S.', meta: '· 47 · rural Eastern WA', tag: 'TAPROOT · CELLULAR RPM · EN ESPAÑOL',
    note: 'Rural\nreach', start: 105.4, end: 113.6,
    rows: [
      'Enrolled on cellular RPM — no Wi-Fi needed',
      'Blood pressure trending down over three weeks',
      'Whole visit conducted in Spanish',
      'Monthly Produce-Rx food box delivered',
      'Specialist tele-referral — no three-hour drive',
    ] },
];

/* ---------------- small helpers ---------------- */
function fadeAt(localTime, dur, inD = 0.5, outD = 0.5) {
  return clamp(Math.min(localTime / inD, (dur - localTime) / outD), 0, 1);
}

/* ===================== SCENE 0 — THE GAP ===================== */
function S0() {
  return (
    <Sprite start={0} end={18.4}>
      <WorldBG src={WORLD} from={1.0} to={1.2} fxr={0.5} fyr={0.5} dim={0.72} tint={TOK.deep2} vignette={0.66} warm={0.32} />
      <Sprite start={1} end={17.6}><Eyebrow y={fy(0.12)} color="#C9A24A">00 · The problem</Eyebrow></Sprite>
      <Sprite start={2.4} end={9}><Title y={fy(0.40)} size={92} max={20}>People slip through the cracks.</Title></Sprite>
      <Sprite start={9.6} end={17.6}><Title y={fy(0.40)} size={74} max={24} color={TOK.onDeep}>A discharge. A plan that doesn't fit. <span style={{ color: '#E0C04A', fontStyle: 'italic', fontFamily: TOK.serif }}>Silence.</span></Title></Sprite>
    </Sprite>
  );
}

/* ===================== SCENE 1 — ROOTBOUND ===================== */
function S1() {
  return (
    <Sprite start={18} end={36.4}>
      <WorldBG src={WORLD} from={1.22} to={1.06} fxr={0.48} fyr={0.34} dim={0.34} vignette={0.34} />
      <Sprite start={19} end={36}><Pulse xr={0.475} yr={0.30} at={1.5} size={420} /></Sprite>
      <Sprite start={20.4} end={29}>{({ localTime }) => {
        const e = Easing.easeOutCubic(clamp(localTime / 1.3, 0, 1));
        const op = clamp(Math.min(localTime / 0.6, (8.6 - localTime) / 2.4), 0, 1);
        return <div style={{ position: 'absolute', left: fx(0.475), top: fy(0.30), width: 640, height: 640,
          marginLeft: -320, marginTop: -320, borderRadius: '50%', pointerEvents: 'none', mixBlendMode: 'screen',
          opacity: op * 0.85, transform: `scale(${0.5 + 0.6 * e})`,
          background: 'radial-gradient(circle, rgba(255,247,214,.55), rgba(255,240,188,0) 60%)' }} />;
      }}</Sprite>
      <Sprite start={18.6} end={36}><Eyebrow y={fy(0.62)} align="left" x={fx(0.07)}>01 · The answer</Eyebrow></Sprite>
      <Sprite start={19.4} end={36}><Title x={fx(0.07)} y={fy(0.66)} align="left" size={108} max={14}>RootBound&nbsp;Health</Title></Sprite>
      <Sprite start={25} end={36}><Lead x={fx(0.07)} y={fy(0.82)} align="left" size={29} max={52}>A nurse-led, direct-care clinic — telehealth-first, after-hours, open to everyone.</Lead></Sprite>
    </Sprite>
  );
}

/* ===================== SCENE 2 — THE NETWORK ===================== */
const NET = [
  { xr: 0.135, yr: 0.40, label: 'Hospital', sub: 'discharges → home', at: 2 },
  { xr: 0.175, yr: 0.57, label: 'Emergency', sub: 'after the visit', at: 3 },
  { xr: 0.30,  yr: 0.45, label: 'Pharmacy', sub: 'meds reconciled', at: 4 },
  { xr: 0.70,  yr: 0.45, label: 'Public Health', sub: 'PHSKC · §318', at: 5 },
  { xr: 0.81,  yr: 0.55, label: 'Social Services', sub: 'DSHS · coverage', at: 6 },
  { xr: 0.875, yr: 0.82, label: 'The Home', sub: 'telehealth + RPM', at: 7 },
];
function S2() {
  return (
    <Sprite start={36} end={62.4}>
      <WorldBG src={WORLD} from={1.02} to={1.12} fxr={0.5} fyr={0.52} dim={0.3} vignette={0.32} />
      <Sprite start={36.4} end={61.6}><Eyebrow y={fy(0.075)}>02 · The network is the job</Eyebrow></Sprite>
      {/* beams + tags reaching out */}
      <Sprite start={37} end={53}>
        <Pulse xr={0.475} yr={0.52} at={0} size={300} />
        {NET.map((n, i) => <Beam key={i} x1r={0.475} y1r={0.52} x2r={n.xr} y2r={n.yr} at={n.at} />)}
        {NET.map((n, i) => <Tag key={i} xr={n.xr} yr={n.yr} label={n.label} sub={n.sub} at={n.at + 0.7} />)}
      </Sprite>
      {/* the numbers */}
      <Sprite start={53} end={62}>
        <Stat xr={0.20} yr={0.40} to={150} suffix="+" label="ZIP codes served" at={0.1} />
        <Stat xr={0.40} yr={0.40} to={6} label="clinical service lines" at={0.3} />
        <Stat xr={0.60} yr={0.40} to={6} label="programs, one backbone" at={0.5} />
        <Stat xr={0.80} yr={0.40} to={3} label="legal entities" at={0.7} />
        <Sprite start={1.4} end={9}><Lead y={fy(0.62)} size={28} max={48}>One clinic, reaching the hours and ZIP codes the system can't cover at scale.</Lead></Sprite>
      </Sprite>
    </Sprite>
  );
}

/* ===================== SCENE 3 — PROGRAMS (interactive) ===================== */
function ProgramCard({ p, i, onOpen }) {
  const { localTime } = useSprite();
  const at = 1.2 + i * 0.35;
  const t = clamp((localTime - at) / 0.5, 0, 1);
  const e = Easing.easeOutBack(t);
  const col = i % 3, row = Math.floor(i / 3);
  const cw = 392, ch = 196, gap = 30;
  const gridW = cw * 3 + gap * 2;
  const x = (W - gridW) / 2 + col * (cw + gap);
  const y = fy(0.30) + row * (ch + gap);
  return (
    <div onClick={() => onOpen(i)}
      style={{ position: 'absolute', left: x, top: y, width: cw, height: ch, opacity: t,
        transform: `scale(${0.9 + 0.1 * e}) translateY(${(1 - e) * 16}px)`, transformOrigin: 'center',
        background: 'rgba(1,28,38,.72)', backdropFilter: 'blur(10px)', WebkitBackdropFilter: 'blur(10px)',
        border: '1px solid rgba(224,192,74,.28)', borderRadius: 16, padding: '26px 28px', cursor: 'pointer',
        boxShadow: '0 16px 44px rgba(0,0,0,.4)', display: 'flex', flexDirection: 'column' }}>
      <div style={{ fontFamily: TOK.mono, fontSize: 15, fontWeight: 600, letterSpacing: '.12em', textTransform: 'uppercase', color: ACC().on }}>{p.tag}</div>
      <div style={{ fontFamily: TOK.serif, fontWeight: 600, fontSize: 34, color: TOK.onDeep, margin: '10px 0 8px' }}>{p.nm}</div>
      <div style={{ fontFamily: TOK.sans, fontSize: 19, color: TOK.onDeepSoft, lineHeight: 1.4 }}>{p.ds}</div>
      <div style={{ marginTop: 'auto', fontFamily: TOK.mono, fontSize: 12.5, letterSpacing: '.08em', textTransform: 'uppercase', color: 'rgba(224,192,74,.7)' }}>Click to expand →</div>
    </div>
  );
}
function S3({ onOpen }) {
  return (
    <Sprite start={62} end={88.4}>
      <WorldBG src={WORLD} from={1.08} to={1.14} fxr={0.5} fyr={0.5} dim={0.62} tint={TOK.deep2} vignette={0.4} blur={2} />
      <Sprite start={62.4} end={87.6}><Eyebrow y={fy(0.1)}>03 · Six programs, one backbone</Eyebrow></Sprite>
      <Sprite start={63} end={88}>
        {PROGRAMS.map((p, i) => <ProgramCard key={i} p={p} i={i} onOpen={onOpen} />)}
      </Sprite>
      <Sprite start={64} end={88}><Lead y={fy(0.84)} size={24} max={70} color="rgba(168,191,198,.85)">Each one a revenue engine, a pathway, a partnership — held together by one follow-up system.</Lead></Sprite>
    </Sprite>
  );
}

/* ===================== SCENE 4 — ONE PATIENT (Amy) ===================== */
function EncounterRow({ text, i, localTime, at }) {
  const t = clamp((localTime - (at + i * 1.05)) / 0.45, 0, 1);
  const e = Easing.easeOutCubic(t);
  return (
    <div style={{ display: 'flex', alignItems: 'flex-start', gap: 14, opacity: t,
      transform: `translateX(${(1 - e) * 14}px)`, padding: '10px 0', borderBottom: '1px solid rgba(255,255,255,.08)' }}>
      <div style={{ width: 26, height: 26, borderRadius: '50%', flex: 'none', marginTop: 1,
        background: `rgba(${ACC().glow},${0.18 + 0.82 * t})`, border: `1.5px solid rgba(${ACC().glow},${t})`,
        display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
        <svg width="15" height="15" viewBox="0 0 14 14" style={{ opacity: t }}><path d="M3 7.4 5.7 10 11 4.3" fill="none" stroke={TOK.gold2} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" /></svg>
      </div>
      <div style={{ fontFamily: TOK.sans, fontSize: 22, color: TOK.onDeep, lineHeight: 1.35 }}>{text}</div>
    </div>
  );
}
function CaseCard({ c }) {
  const dur = c.end - c.start;
  return (
    <Sprite start={c.start} end={c.end}>
      {({ localTime }) => {
        const fade = clamp(Math.min(localTime / 0.5, (dur - localTime) / 0.5), 0, 1);
        return (
          <div style={{ opacity: fade }}>
            <Panel x={fx(0.5)} y={fy(0.2)} w={1000} at={0.3} align="center">
              <div style={{ display: 'flex', alignItems: 'baseline', justifyContent: 'space-between', borderBottom: '1px solid rgba(224,192,74,.25)', paddingBottom: 16, marginBottom: 8 }}>
                <div>
                  <div style={{ fontFamily: TOK.serif, fontWeight: 600, fontSize: 36, color: TOK.onDeep }}>{c.nm} <span style={{ fontSize: 22, color: TOK.onDeepSoft, fontFamily: TOK.sans, fontWeight: 400 }}>{c.meta}</span></div>
                  <div style={{ fontFamily: TOK.mono, fontSize: 15, letterSpacing: '.06em', color: ACC().on, marginTop: 6 }}>{c.tag}</div>
                </div>
                <div style={{ fontFamily: TOK.mono, fontSize: 14, color: TOK.onDeepSoft, textAlign: 'right', whiteSpace: 'pre-line' }}>{c.note}</div>
              </div>
              {c.rows.map((e, i) => <EncounterRow key={i} text={e} i={i} localTime={localTime} at={0.9} />)}
            </Panel>
          </div>
        );
      }}
    </Sprite>
  );
}
function S4() {
  return (
    <Sprite start={88} end={114.4}>
      <WorldBG src={WORLD} from={1.5} to={1.78} fxr={0.86} fyr={0.82} dim={0.46} vignette={0.5} />
      <Sprite start={88.4} end={113.6}><Eyebrow y={fy(0.08)} align="left" x={fx(0.06)}>04 · Real patients, end to end</Eyebrow></Sprite>
      {CASES.map((c, i) => <CaseCard key={i} c={c} />)}
      <Sprite start={110} end={113.6}>
        {({ localTime }) => {
          const t = clamp((localTime) / 0.6, 0, 1);
          return (
            <div style={{ position: 'absolute', left: fx(0.5), top: fy(0.9), transform: `translate(-50%,0) scale(${0.85 + 0.15 * Easing.easeOutBack(t)})`, opacity: t,
              fontFamily: TOK.serif, fontStyle: 'italic', fontWeight: 600, fontSize: 40, color: TOK.gold2, textShadow: '0 3px 20px rgba(1,28,38,.7)' }}>
              No one fell through.
            </div>
          );
        }}
      </Sprite>
    </Sprite>
  );
}

/* ===================== SCENE 5 — THE ROLE ===================== */
function RoleCard({ r, i, localTime }) {
  const at = 1 + i * 0.4;
  const t = clamp((localTime - at) / 0.5, 0, 1);
  const e = Easing.easeOutBack(t);
  const col = i % 2, row = Math.floor(i / 2);
  const cw = 560, ch = 176, gap = 28;
  const gw = cw * 2 + gap;
  const x = (W - gw) / 2 + col * (cw + gap);
  const y = fy(0.34) + row * (ch + gap);
  return (
    <div style={{ position: 'absolute', left: x, top: y, width: cw, height: ch, opacity: t,
      transform: `translateY(${(1 - e) * 18}px)`, background: 'rgba(1,28,38,.72)', backdropFilter: 'blur(10px)', WebkitBackdropFilter: 'blur(10px)',
      border: '1px solid rgba(224,192,74,.24)', borderRadius: 16, padding: '28px 30px', display: 'flex', gap: 22, alignItems: 'flex-start' }}>
      <div style={{ fontFamily: TOK.serif, fontSize: 52, lineHeight: 1, color: ACC().on, flex: 'none' }}>{r.n}</div>
      <div>
        <div style={{ fontFamily: TOK.serif, fontWeight: 600, fontSize: 30, color: TOK.onDeep, marginBottom: 8 }}>{r.t}</div>
        <div style={{ fontFamily: TOK.sans, fontSize: 20, color: TOK.onDeepSoft, lineHeight: 1.4 }}>{r.d}</div>
      </div>
    </div>
  );
}
function S5() {
  return (
    <Sprite start={114} end={134.4}>
      <WorldBG src={WORLD} from={1.12} to={1.18} fxr={0.5} fyr={0.45} dim={0.64} tint={TOK.deep2} vignette={0.42} blur={2.5} />
      <Sprite start={114.4} end={133.6}><Eyebrow y={fy(0.12)}>05 · What you'd own</Eyebrow></Sprite>
      <Sprite start={114.4} end={133.6}><Title y={fy(0.17)} size={62} max={26}>The person who keeps every root connected</Title></Sprite>
      <Sprite start={115} end={133.6}>{({ localTime }) => ROLE.map((r, i) => <RoleCard key={i} r={r} i={i} localTime={localTime} />)}</Sprite>
    </Sprite>
  );
}

/* ===================== SCENE 6 — THE ASK ===================== */
function S6({ headline }) {
  return (
    <Sprite start={134} end={150}>
      <WorldBG src={WORLD} from={1.06} to={1.16} fxr={0.5} fyr={0.54} dim={0.3} vignette={0.36} />
      <Sprite start={134.5} end={150}><Pulse xr={0.475} yr={0.54} at={0.4} size={520} /></Sprite>
      <Sprite start={135} end={150}><Eyebrow y={fy(0.16)}>The through-line</Eyebrow></Sprite>
      <Sprite start={135.4} end={150}><Title y={fy(0.24)} size={104} max={20} font={TOK.serif}>{headline || 'No one falls through.'}</Title></Sprite>
      <Sprite start={140.5} end={150}><Lead y={fy(0.52)} size={28} max={56}>The Clinic Director holds the programs together, keeps the entities clean, and turns relationships into reach.</Lead></Sprite>
      <Sprite start={143} end={150}>
        {({ localTime }) => {
          const t = clamp((localTime - 0) / 0.6, 0, 1);
          const links = [
            { href: 'walkthrough.html', label: 'Start the walkthrough', primary: true },
            { href: 'overview.html', label: 'The overview' },
            { href: 'programs.html', label: 'The programs' },
          ];
          return (
            <div style={{ position: 'absolute', left: fx(0.5), top: fy(0.66), transform: `translate(-50%, ${(1 - t) * 16}px)`, opacity: t,
              display: 'flex', gap: 16 }}>
              {links.map((l, i) => (
                <a key={i} href={l.href} style={{ pointerEvents: 'auto', textDecoration: 'none',
                  fontFamily: TOK.mono, fontSize: 16, fontWeight: 600, letterSpacing: '.08em', textTransform: 'uppercase',
                  padding: '17px 30px', borderRadius: 100,
                  background: l.primary ? TOK.gold : 'transparent', color: l.primary ? TOK.deep : TOK.onDeep,
                  border: l.primary ? '0' : '1px solid rgba(224,192,74,.4)',
                  boxShadow: l.primary ? '0 12px 30px rgba(176,135,26,.34)' : 'none' }}>{l.label}{l.primary ? ' →' : ''}</a>
              ))}
            </div>
          );
        }}
      </Sprite>
    </Sprite>
  );
}

/* ===================== DETAIL OVERLAY (click-to-reveal) ===================== */
function DetailOverlay({ idx, onClose }) {
  if (idx == null) return null;
  const p = PROGRAMS[idx];
  return (
    <div onClick={onClose} style={{ position: 'absolute', inset: 0, zIndex: 80, background: 'rgba(1,18,25,.74)',
      backdropFilter: 'blur(8px)', WebkitBackdropFilter: 'blur(8px)', display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer' }}>
      <div onClick={(e) => e.stopPropagation()} style={{ width: 880, background: 'linear-gradient(180deg,#06303D,#01202B)',
        border: '1px solid rgba(224,192,74,.3)', borderRadius: 22, padding: '44px 48px', cursor: 'default', boxShadow: '0 40px 100px rgba(0,0,0,.6)' }}>
        <div style={{ fontFamily: TOK.mono, fontSize: 16, fontWeight: 600, letterSpacing: '.14em', textTransform: 'uppercase', color: ACC().on }}>{p.tag}</div>
        <div style={{ fontFamily: TOK.serif, fontWeight: 600, fontSize: 52, color: TOK.onDeep, margin: '12px 0 18px' }}>{p.nm}</div>
        <div style={{ fontFamily: TOK.sans, fontSize: 24, lineHeight: 1.55, color: TOK.onDeepSoft }}>{p.detail}</div>
        <button onClick={onClose} style={{ marginTop: 30, cursor: 'pointer', fontFamily: TOK.mono, fontSize: 14, fontWeight: 600,
          letterSpacing: '.08em', textTransform: 'uppercase', color: TOK.deep, background: TOK.gold, border: 0, borderRadius: 100, padding: '14px 26px' }}>Resume ▶</button>
      </div>
    </div>
  );
}

/* ===================== FILM ROOT ===================== */
function Film({ capsOn = true, voiceOn = true, accent = 'gold', headline, speed = 1 }) {
  window.__RBV_ACCENT = accent;
  const { setPlaying } = useTimeline();
  const [detail, setDetail] = React.useState(null);
  const time = useTime();
  // live timestamp label for review comments
  React.useEffect(() => {
    const root = document.getElementById('rbv-root');
    if (root) root.setAttribute('data-screen-label', `t=${Math.floor(time)}s`);
  }, [Math.floor(time)]);

  const openDetail = (i) => { setPlaying(false); setDetail(i); };
  const closeDetail = () => { setDetail(null); setPlaying(true); };

  return (
    <>
      <S0 />
      <S1 />
      <S2 />
      <S3 onOpen={openDetail} />
      <S4 />
      <S5 />
      <S6 headline={headline} />
      <Particles count={38} />
      <Letterbox h={26} />
      <CaptionBar captions={RBV_CAPS} enabled={capsOn} />
      <VoiceController captions={RBV_CAPS} enabled={voiceOn} rate={0.96 * speed} />
      <ChapterBar chapters={RBV_CHAPTERS} />
      <DetailOverlay idx={detail} onClose={closeDetail} />
    </>
  );
}

Object.assign(window, { Film, RBV_CHAPTERS, RBV_CAPS });
