// living-lab-scroll-app.jsx — an elevated, scroll-scrubbed presentation of the
// Evidence Engine with chalk subtitle captions. Scroll drives the playhead;
// a play button auto-advances (and syncs the scrollbar) for a video pass.
const { Scene, SEQ_END_SCENE, LAB, clamp, lerp,
        useTweaks, TweaksPanel, TweakSection, TweakSlider, TweakToggle } = window;

// chalk explainer captions, keyed to the lecture schedule (seconds)
const CAPTIONS = [
  { t: [0.0, 3.2],   text: 'How the lab turns research into governed solutions.' },
  { t: [3.2, 9.4],   text: 'Research runs in three stages — basic, oriented, applied.' },
  { t: [9.4, 14.4],  text: 'A field of agents does the basic research, generating evidence.' },
  { t: [14.4, 17.0], text: 'Their findings orient around a thesis — the lab’s current position.' },
  { t: [17.0, 20.0], text: 'The thesis rests on an open protocol — the foundation it’s all built on.' },
  { t: [20.0, 22.6], text: 'Two highways carry the information between the stages.' },
  { t: [22.6, 24.2], text: 'Evidence flows in from research…' },
  { t: [24.2, 26.0], text: '…and the thesis clears it for spending on solutions.' },
  { t: [26.0, 30.4], text: 'Applied work returns innovation, which funds the next research.' },
  { t: [30.4, 32.6], text: 'Once proven, the thesis packages into its technical reports.' },
  { t: [32.6, 36.2], text: 'Solutions are built as those reports publish.' },
  { t: [36.2, 39.4], text: 'Meanwhile a new thesis forms — a thought held above the board.' },
  { t: [39.4, 43.4], text: 'It descends to take the anchor; the proven thesis settles beneath, still feeding up.' },
  { t: [43.4, 46.8], text: 'New solutions follow the new thesis.' },
  { t: [46.8, 50.0], text: 'And the next thought is already forming — the lab keeps changing its mind.' },
];
function captionAt(mt) {
  const c = CAPTIONS.find((c) => mt >= c.t[0] && mt < c.t[1]);
  if (!c) return { text: '', o: 0 };
  const o = Math.min(clamp((mt - c.t[0]) / 0.5, 0, 1), clamp((c.t[1] - mt) / 0.5, 0, 1));
  return { text: c.text, o };
}

function PlayIcon({ playing, done }) {
  if (done) return <svg width="20" height="20" viewBox="0 0 20 20" fill="none"><path d="M10 3a7 7 0 1 1-6.3 4" stroke={LAB.chalk} strokeWidth="1.6" strokeLinecap="round" /><path d="M3 3v4h4" stroke={LAB.chalk} strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" /></svg>;
  return playing
    ? <svg width="18" height="18" viewBox="0 0 18 18"><rect x="3.5" y="2.5" width="3.6" height="13" fill={LAB.chalk} /><rect x="10.9" y="2.5" width="3.6" height="13" fill={LAB.chalk} /></svg>
    : <svg width="18" height="18" viewBox="0 0 18 18"><path d="M4 2.5 15 9 4 15.5V2.5Z" fill={LAB.chalk} /></svg>;
}

function ScrollApp() {
  const [tw, setTweak] = useTweaks({ agents: 5, pulses: 2, labels: true, subtitles: true });
  const [prog, setProg] = React.useState(0);
  const [playing, setPlaying] = React.useState(true);   // movie mode (invest): auto-play on load
  const [hint, setHint] = React.useState(false);         // no scroll hint — it's a movie now
  const playingRef = React.useRef(false); playingRef.current = playing;
  const targetRef = React.useRef(0);
  const progRef = React.useRef(0);

  const maxScroll = () => Math.max(1, document.documentElement.scrollHeight - window.innerHeight);

  React.useEffect(() => {
    const onScroll = () => {
      if (playingRef.current) return;
      targetRef.current = clamp(window.scrollY / maxScroll(), 0, 1);
      if (window.scrollY > 6) setHint(false);
    };
    window.addEventListener('scroll', onScroll, { passive: true });
    onScroll();
    let last = performance.now(), raf;
    const tick = (now) => {
      const dt = Math.min(0.05, (now - last) / 1000); last = now;
      if (playingRef.current) {
        targetRef.current = clamp(targetRef.current + dt / SEQ_END_SCENE, 0, 1);
        window.scrollTo(0, targetRef.current * maxScroll());
        if (targetRef.current >= 1) setPlaying(false);
      }
      const k = playingRef.current ? 0.6 : 0.14;
      progRef.current += (targetRef.current - progRef.current) * k;
      if (Math.abs(progRef.current - targetRef.current) < 1e-4) progRef.current = targetRef.current;
      setProg(progRef.current);
      raf = requestAnimationFrame(tick);
    };
    raf = requestAnimationFrame(tick);
    return () => {
      window.removeEventListener('scroll', onScroll);
      cancelAnimationFrame(raf);
    };
  }, []);

  const mt = prog * SEQ_END_SCENE;
  const done = prog >= 0.999;
  const cap = captionAt(mt);
  const togglePlay = () => {
    if (done) { targetRef.current = 0; window.scrollTo(0, 0); }
    setHint(false);
    setPlaying((p) => !p);
  };

  return (
    <div style={{ position: 'fixed', inset: 0, background: '#0b0f0d', overflow: 'hidden' }}>
      <div style={{ position: 'absolute', top: 0, left: 0, right: 0, bottom: 96 }}>
        <Scene mt={mt} tweaks={tw} />
      </div>

      {/* bottom scrim so the chalk caption reads over the board */}
      <div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, height: 200,
        background: 'linear-gradient(to top, rgba(8,11,10,0.92), rgba(8,11,10,0))', pointerEvents: 'none' }} />

      {/* chalk subtitle */}
      {tw.subtitles ? (
        <div style={{ position: 'absolute', left: 0, right: 0, top: 'calc(100% - 86px)', textAlign: 'center',
          padding: '0 9%', pointerEvents: 'none' }}>
          <div style={{ fontFamily: "'Permanent Marker', cursive", color: LAB.chalk,
            fontSize: 'clamp(15px, 2.05vw, 27px)', lineHeight: 1.28, letterSpacing: '0.4px',
            opacity: cap.o, textWrap: 'balance', textShadow: '0 2px 14px rgba(0,0,0,0.65)',
            transition: 'opacity 90ms linear' }}>
            {cap.text}
          </div>
        </div>
      ) : null}

      {/* progress line */}
      <div style={{ position: 'absolute', left: 0, right: 0, bottom: 0, height: 3, background: 'rgba(233,227,211,0.12)' }}>
        <div style={{ height: '100%', width: `${prog * 100}%`, background: LAB.gold, opacity: 0.85 }} />
      </div>

      {/* play / replay */}
      <button onClick={togglePlay} aria-label={playing ? 'Pause' : 'Play'} style={{
        position: 'absolute', right: 24, bottom: 26, width: 46, height: 46, borderRadius: '50%',
        display: 'flex', alignItems: 'center', justifyContent: 'center', cursor: 'pointer',
        background: 'rgba(20,26,23,0.72)', border: '1px solid rgba(233,227,211,0.32)',
        backdropFilter: 'blur(8px)', WebkitBackdropFilter: 'blur(8px)' }}>
        <PlayIcon playing={playing} done={done} />
      </button>

      {/* scroll hint */}
      <div style={{ position: 'absolute', left: 0, right: 0, bottom: 26, textAlign: 'center',
        pointerEvents: 'none', opacity: hint && !playing ? 0.7 : 0, transition: 'opacity 500ms',
        fontFamily: "'Permanent Marker', cursive", color: LAB.chalk, fontSize: 15, letterSpacing: '1px' }}>
        scroll to explore  ·  or press play
      </div>

      <TweaksPanel title="Tweaks">
        <TweakSection label="Field" />
        <TweakSlider label="Research agents" value={tw.agents} min={3} max={9} step={1} onChange={(v) => setTweak('agents', v)} />
        <TweakSlider label="Flow density" value={tw.pulses} min={1} max={3} step={1} onChange={(v) => setTweak('pulses', v)} />
        <TweakSection label="Annotation" />
        <TweakToggle label="Diagram labels" value={tw.labels} onChange={(v) => setTweak('labels', v)} />
        <TweakToggle label="Subtitles" value={tw.subtitles} onChange={(v) => setTweak('subtitles', v)} />
      </TweaksPanel>
    </div>
  );
}

ReactDOM.createRoot(document.getElementById('stage')).render(<ScrollApp />);
