// scenes.jsx — PLUR demo storyboard scenes
// Two agents: Data (gold #E5C07B) on the left, Quark (magenta #C678DD) on the right.
// Narrative beats: Intro → Setup → Quark sells → Data fails → Exchange (commit-reveal skip) → Data succeeds → Payoff → Outro

// ─── Small utility components ───────────────────────────────────────────────

function Kicker({ children, color = '#9ca3af', size = 16 }) {
  return (
    <div style={{
      fontFamily: 'JetBrains Mono, ui-monospace, monospace',
      fontSize: size,
      letterSpacing: '0.22em',
      textTransform: 'uppercase',
      color,
      fontWeight: 500,
      whiteSpace: 'nowrap',
    }}>{children}</div>
  );
}

function Chip({ children, color = '#fff', bg = 'rgba(255,255,255,0.08)', border }) {
  return (
    <span style={{
      display: 'inline-flex', alignItems: 'center', gap: 8,
      padding: '8px 14px',
      background: bg,
      border: border || `1px solid ${color}22`,
      borderRadius: 999,
      color,
      fontFamily: 'JetBrains Mono, ui-monospace, monospace',
      fontSize: 16,
      letterSpacing: '0.05em',
      whiteSpace: 'nowrap',
    }}>{children}</span>
  );
}

// Fade/slide reveal container used by scene-internal elements.
function Reveal({ at = 0, dur = 0.4, dy = 12, ease = Easing.easeOutCubic, children, style }) {
  const { localTime } = useSprite();
  const t = ease(clamp((localTime - at) / dur, 0, 1));
  return (
    <div style={{
      opacity: t,
      transform: `translateY(${(1 - t) * dy}px)`,
      ...style,
    }}>{children}</div>
  );
}

// Typed-terminal line: reveals one char per frame based on localTime.
// Evidence registry + token detection — render clickable verification chips
// inline inside terminal lines. Tokens:
//   0x[0-9a-f]{8,}   → Etherscan tx (Sepolia)
//   escrow #<n>      → DataEscrow contract URL (Sepolia)
//   Swarm: <hash>    → Swarm gateway URL
//   mr-data@10... / quark@10...  → agent card chip (non-link, informational)

const EVIDENCE = {
  network: 'sepolia',
  escrowContract: '0x1111111111111111111111111111111111111111', // placeholder — replace with real DataEscrow address
  addresses: {
    'mr-data':   { label: 'mr-data',   address: '0x42aa420000000000000000000000000000000042' },
    'quark':     { label: 'quark',     address: '0x7373730000000000000000000000000000000073' },
  },
};

const etherscanTx   = (hash) => `https://sepolia.etherscan.io/tx/${hash}`;
const etherscanAddr = (addr) => `https://sepolia.etherscan.io/address/${addr}`;
const swarmGateway  = (ref)  => `https://api.gateway.ethswarm.org/bzz/${ref.replace(/[…\.]+$/,'')}`;

// Parse a terminal line's text and return React children with inline links
// for tx hashes, escrow ids, Swarm refs. Caret handling is kept in the host.
function renderWithEvidence(text, { color, linkColor }) {
  // Patterns in order — we'll tokenize by matching left-to-right.
  const patterns = [
    { re: /0x[0-9a-fA-F]{16,}/g,              kind: 'tx' },
    { re: /0x[0-9a-fA-F]{8,15}/g,             kind: 'tx' },      // short/truncated hashes
    { re: /escrow\s*#\d+/gi,                   kind: 'escrow' },
    { re: /Escrow\s*#\d+/g,                    kind: 'escrow' },
    { re: /(?:Swarm:\s*|bzz:0x)?[a-f0-9]{16,}…?/g, kind: 'swarm', guard: /[a-f0-9]{16,}/ },
  ];
  // Simpler: scan for the first match of each and merge positions.
  const out = [];
  let cursor = 0;
  // Combined regex — order matters. Keep tx first so it doesn't get swallowed.
  const combined = /(0x[0-9a-fA-F]{8,})|((?:[Ee]scrow\s*#\d+))|(Swarm:\s*[a-f0-9]{6,}…?)/g;
  let m;
  while ((m = combined.exec(text)) !== null) {
    const before = text.slice(cursor, m.index);
    if (before) out.push(before);
    const raw = m[0];
    let href = null;
    if (m[1]) {
      href = etherscanTx(m[1]);
    } else if (m[2]) {
      href = etherscanAddr(EVIDENCE.escrowContract);
    } else if (m[3]) {
      const ref = m[3].replace(/^Swarm:\s*/, '');
      href = swarmGateway(ref);
    }
    out.push(
      <a key={m.index} href={href} target="_blank" rel="noopener noreferrer"
         style={{
           color: linkColor,
           textDecoration: 'underline',
           textDecorationColor: `${linkColor}66`,
           textDecorationThickness: '1px',
           textUnderlineOffset: '3px',
           cursor: 'pointer',
           pointerEvents: 'auto',
         }}
         title={`Verify on ${m[3] ? 'Swarm gateway' : 'Sepolia Etherscan'}`}>
        {raw}
        <svg width="10" height="10" viewBox="0 0 10 10" style={{ marginLeft: 3, verticalAlign: '1px' }} fill="none">
          <path d="M3 2h5v5M8 2L2 8" stroke={linkColor} strokeWidth="1.3" strokeLinecap="round"/>
        </svg>
      </a>
    );
    cursor = m.index + raw.length;
  }
  const tail = text.slice(cursor);
  if (tail) out.push(tail);
  return out.length ? out : [text];
}

function TerminalLine({ text, startAt = 0, cps = 40, prefix = '', prefixColor, color = '#e5e7eb', bold = false, linkColor }) {
  const { localTime } = useSprite();
  const hasStarted = localTime >= startAt;
  const elapsed = Math.max(0, localTime - startAt);
  const chars = Math.floor(elapsed * cps);
  const shown = text.slice(0, chars);
  const done = chars >= text.length;
  const caret = !done && hasStarted;
  // Only render evidence chips once the line has fully typed — otherwise
  // partial hashes would blink in-and-out as underlined links.
  const children = done ? renderWithEvidence(shown, { color, linkColor: linkColor || color }) : [shown];
  // Do not render the line (including its prefix) before its startAt time —
  // otherwise empty `quark>` / `mr-data>` prompts ghost into the terminal
  // ahead of when they should appear.
  if (!hasStarted) return null;
  return (
    <div style={{ display: 'block', lineHeight: 1.5, minHeight: '1.5em' }}>
      {prefix && <span style={{ color: prefixColor || color, fontWeight: 700, marginRight: 8 }}>{prefix}</span>}
      <span style={{ color, whiteSpace: 'pre-wrap', fontWeight: bold ? 600 : 400, wordBreak: 'break-word' }}>
        {children}
        {caret && <span style={{
          display: 'inline-block', width: 9, height: 16,
          background: color, marginLeft: 2, verticalAlign: 'middle',
          animation: 'blink 1s steps(1) infinite',
        }}/>}
      </span>
    </div>
  );
}

// A whole terminal window with MOTD header describing the agent.
function Terminal({ motd, agentColor, lines, style, width, height, startAt = 0 }) {
  return (
    <div style={{
      width, height,
      background: 'rgba(13, 17, 23, 0.78)',
      backdropFilter: 'blur(6px)',
      WebkitBackdropFilter: 'blur(6px)',
      border: `1px solid ${agentColor}66`,
      borderRadius: 10,
      overflow: 'hidden',
      display: 'flex', flexDirection: 'column',
      fontFamily: "'JetBrains Mono', ui-monospace, monospace",
      fontSize: 16,
      lineHeight: 1.5,
      boxShadow: `0 30px 80px rgba(0,0,0,0.55), 0 0 0 1px ${agentColor}22, 0 0 60px ${agentColor}15`,
      ...style,
    }}>
      <div style={{
        height: 34,
        background: 'rgba(22, 27, 34, 0.6)',
        borderBottom: '1px solid rgba(255,255,255,0.06)',
        display: 'flex', alignItems: 'center',
        padding: '0 14px', gap: 8,
      }}>
        <div style={{ display: 'flex', gap: 6 }}>
          <div style={{ width: 11, height: 11, borderRadius: 6, background: '#ff5f57' }}/>
          <div style={{ width: 11, height: 11, borderRadius: 6, background: '#febc2e' }}/>
          <div style={{ width: 11, height: 11, borderRadius: 6, background: '#28c840' }}/>
        </div>
        <div style={{
          fontSize: 13, color: agentColor,
          marginLeft: 10, letterSpacing: '0.05em',
          whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis',
          fontWeight: 600,
        }}>{motd?.host}</div>
      </div>

      {/* MOTD */}
      {motd && (
        <div style={{
          padding: '14px 20px 10px', borderBottom: `1px dashed ${agentColor}33`,
          color: agentColor, fontSize: 13, lineHeight: 1.55,
        }}>
          <div style={{ fontSize: 18, fontWeight: 700, letterSpacing: '0.02em' }}>{motd.name}</div>
          <div style={{ color: '#6b7280', marginTop: 2 }}>{motd.sub}</div>
          <div style={{ color: '#6b7280', marginTop: 2, fontSize: 12 }}>
            {motd.ip} · {motd.gpu} · QVAC runtime · PLUR memory
          </div>
        </div>
      )}

      <div style={{
        flex: 1,
        padding: '14px 20px',
        color: '#e5e7eb',
        overflow: 'hidden',
      }}>
        {lines.map((ln, i) => (
          <TerminalLine key={i} {...ln} startAt={(ln.startAt ?? 0) + startAt} color={ln.color || '#c9d1d9'} linkColor={agentColor} />
        ))}
      </div>
    </div>
  );
}

// PLUR logo — official SVG mark (transparent background, works on dark).
function PlurMark({ color, size = 40, palette }) {
  return <img src="plur-logo.svg" alt="PLUR" width={size} height={size} style={{ display: 'inline-block' }}/>;
}

// Video-inset picture-in-picture — shows real recording alongside mock terminals.
// Controlled by parent (we forward currentTime) so it stays in sync with the scrubber.
function VideoInset({ src, x, y, w, h, startSec = 0, muted = true, label = 'ORIGINAL RECORDING', color, parentLocalTime, sceneStart, watchUrl }) {
  const ref = React.useRef(null);
  const [hover, setHover] = React.useState(false);
  const [ready, setReady] = React.useState(false);

  // Seek + arm on metadata load — if the video isn't fully loaded yet, the
  // initial `currentTime = startSec` jump is ignored and we get a black frame.
  React.useEffect(() => {
    const v = ref.current;
    if (!v) return;
    const onReady = () => {
      setReady(true);
      const target = startSec + Math.max(0, parentLocalTime);
      try { v.currentTime = Math.min(target, (v.duration || 1e6) - 0.1); } catch {}
      v.play && v.play().catch(() => {});
    };
    if (v.readyState >= 1) onReady();
    v.addEventListener('loadedmetadata', onReady);
    v.addEventListener('canplay', onReady);
    return () => {
      v.removeEventListener('loadedmetadata', onReady);
      v.removeEventListener('canplay', onReady);
    };
  }, []);

  // Keep video in sync with the stage scrubber.
  React.useEffect(() => {
    const v = ref.current;
    if (!v || !ready) return;
    const target = startSec + Math.max(0, parentLocalTime);
    // Only correct if drift > 0.25s; otherwise let it play naturally.
    if (Math.abs((v.currentTime || 0) - target) > 0.25) {
      try { v.currentTime = target; } catch {}
    }
    v.play && v.play().catch(() => {});
  }, [parentLocalTime, startSec, ready]);

  const content = (
    <div
      style={{
        position: 'absolute', left: x, top: y, width: w, height: h,
        borderRadius: 10, overflow: 'hidden',
        border: `1px solid ${hover ? color : color + '55'}`,
        boxShadow: hover
          ? `0 28px 70px rgba(0,0,0,0.7), 0 0 0 2px ${color}66, 0 0 40px ${color}44`
          : `0 20px 50px rgba(0,0,0,0.55), 0 0 0 1px ${color}22`,
        background: '#000',
        zIndex: 8,
        cursor: watchUrl ? 'pointer' : 'default',
        transition: 'box-shadow 0.2s ease, border-color 0.2s ease, transform 0.2s ease',
        transform: hover ? 'scale(1.015)' : 'scale(1)',
      }}
      onMouseEnter={() => watchUrl && setHover(true)}
      onMouseLeave={() => setHover(false)}
    >
      <video
        ref={ref}
        src={src}
        muted={muted}
        playsInline
        preload="auto"
        style={{
          width: '100%', height: '100%',
          objectFit: 'cover', display: 'block',
          pointerEvents: 'none',
          filter: 'brightness(1.35) contrast(1.08) saturate(1.1)',
        }}
      />
      {/* REC chip */}
      <div style={{
        position: 'absolute', top: 10, left: 10,
        display: 'flex', alignItems: 'center', gap: 8,
        padding: '5px 10px', borderRadius: 999,
        background: 'rgba(0,0,0,0.7)',
        border: `1px solid ${color}66`,
        fontFamily: "'JetBrains Mono', monospace",
        fontSize: 11, letterSpacing: '0.14em', textTransform: 'uppercase',
        color: '#fff',
      }}>
        <span style={{ width: 7, height: 7, borderRadius: 4, background: '#ff3b3b', boxShadow: '0 0 6px #ff3b3b', animation: 'blink 1.4s steps(1) infinite' }}/>
        REC · {label}
      </div>
      {/* Timecode */}
      <div style={{
        position: 'absolute', bottom: 10, right: 12,
        padding: '3px 8px',
        background: 'rgba(0,0,0,0.7)',
        fontFamily: "'JetBrains Mono', monospace",
        fontSize: 11, color: '#e5e7eb',
        borderRadius: 3, letterSpacing: '0.05em',
      }}>
        {formatTc(startSec + Math.max(0, parentLocalTime))}
      </div>
      {/* Watch-on-Loom affordance */}
      {watchUrl && (
        <div style={{
          position: 'absolute', bottom: 10, left: 12,
          display: 'flex', alignItems: 'center', gap: 6,
          padding: '4px 10px', borderRadius: 999,
          background: hover ? color : 'rgba(0,0,0,0.7)',
          border: `1px solid ${hover ? color : color + '88'}`,
          fontFamily: "'JetBrains Mono', monospace",
          fontSize: 10.5, letterSpacing: '0.14em', textTransform: 'uppercase',
          color: hover ? '#0a0a12' : '#fff',
          fontWeight: 600,
          transition: 'background 0.2s ease, color 0.2s ease',
        }}>
          ▶ Watch full · Loom ↗
        </div>
      )}
    </div>
  );

  if (watchUrl) {
    return (
      <a
        href={watchUrl}
        target="_blank"
        rel="noopener noreferrer"
        style={{ textDecoration: 'none', color: 'inherit', display: 'contents' }}
      >
        {content}
      </a>
    );
  }
  return content;
}

function formatTc(sec) {
  const m = Math.floor(sec / 60);
  const s = Math.floor(sec % 60);
  return `${String(m).padStart(2,'0')}:${String(s).padStart(2,'0')}`;
}

// Small brand lockup used on intro/outro.
function BrandLockup({ tech, color, size = 13 }) {
  return (
    <div style={{
      display: 'flex', gap: 22, alignItems: 'center',
      fontFamily: "'JetBrains Mono', monospace",
      fontSize: size, color,
      letterSpacing: '0.14em', textTransform: 'uppercase',
      flexWrap: 'wrap',
    }}>
      {tech.map((t, i) => (
        <React.Fragment key={i}>
          <span style={{ opacity: 0.75 }}>{t}</span>
          {i < tech.length - 1 && <span style={{ opacity: 0.3 }}>/</span>}
        </React.Fragment>
      ))}
    </div>
  );
}

// ─── Scene 1: Intro ─────────────────────────────────────────────────────────

function IntroScene({ start, end, config }) {
  return (
    <Sprite start={start} end={end}>
      {({ localTime }) => {
        const { bg, ink, dim, gold, magenta } = config;
        const gridX = (localTime * 8) % 60;

        return (
          <div style={{
            position: 'absolute', inset: 0, background: 'transparent', overflow: 'hidden',
          }}>
            {/* Grid */}
            <div style={{
              position: 'absolute', inset: -20,
              backgroundImage: `linear-gradient(${ink}08 1px, transparent 1px), linear-gradient(90deg, ${ink}08 1px, transparent 1px)`,
              backgroundSize: '60px 60px',
              transform: `translate(${-gridX}px, 0)`,
              opacity: 0.5,
            }}/>

            {/* Two agent glows */}
            <div style={{
              position: 'absolute', left: '-10%', top: '30%',
              width: 900, height: 900, borderRadius: '50%',
              background: `radial-gradient(circle, ${gold}26 0%, transparent 60%)`,
              filter: 'blur(10px)',
            }}/>
            <div style={{
              position: 'absolute', right: '-10%', top: '20%',
              width: 900, height: 900, borderRadius: '50%',
              background: `radial-gradient(circle, ${magenta}26 0%, transparent 60%)`,
              filter: 'blur(10px)',
            }}/>

            <div style={{
              position: 'absolute', left: 140, top: 140, right: 140, bottom: 140,
              display: 'flex', flexDirection: 'column', justifyContent: 'space-between',
            }}>
              <Reveal at={0} dur={0.5}>
                <a href="https://plur.ai" target="_blank" rel="noopener noreferrer"
                   style={{ display: 'inline-flex', alignItems: 'center', gap: 10, textDecoration: 'none', pointerEvents: 'auto', marginLeft: 6 }}>
                  <Kicker color={dim} size={18}>plur.ai ↗</Kicker>
                </a>
              </Reveal>

              <div>
                <Reveal at={0.3} dur={0.6}>
                  <div style={{
                    fontFamily: config.display,
                    fontSize: 160, fontWeight: 700,
                    color: ink, lineHeight: 0.92,
                    letterSpacing: '-0.04em',
                    marginBottom: 32,
                  }}>
                    Sovereign minds.<br/>
                    <span style={{
                      background: `linear-gradient(90deg, ${gold}, ${magenta})`,
                      WebkitBackgroundClip: 'text',
                      WebkitTextFillColor: 'transparent',
                    }}>Sovereign rails.</span>
                  </div>
                </Reveal>
                <Reveal at={0.7} dur={0.5}>
                  <div style={{
                    fontFamily: config.body,
                    fontSize: 32, fontWeight: 400,
                    color: dim, maxWidth: 1200,
                    lineHeight: 1.35,
                  }}>
                    {(config.introSubtitle || '').split('\n').map((line, i) => (
                      <React.Fragment key={i}>
                        {i > 0 && <br/>}
                        {line}
                      </React.Fragment>
                    ))}
                  </div>
                </Reveal>
              </div>

              <Reveal at={1.1} dur={0.5}>
                <div style={{ display: 'flex', gap: 14, alignItems: 'center', flexWrap: 'wrap' }}>
                  <Chip color={gold} bg={`${gold}1a`} border={`1px solid ${gold}55`}>
                    <span style={{ width: 8, height: 8, borderRadius: 4, background: gold, animation: 'blink 1.4s steps(1) infinite' }}/>
                    LIVE ON SEPOLIA
                  </Chip>
                  <BrandLockup tech={['QVAC', 'PLUR', 'SWARM', 'DataEscrow']} color={dim}/>
                </div>
              </Reveal>
            </div>
          </div>
        );
      }}
    </Sprite>
  );
}

function PlurMarkOLD_REMOVED({ color, size = 36 }) {
  return (
    <svg width={size} height={size} viewBox="0 0 36 36" fill="none">
      <circle cx="18" cy="18" r="17" stroke={color} strokeWidth="1.5"/>
      <circle cx="18" cy="18" r="3" fill={color}/>
      <circle cx="6"  cy="18" r="2" fill={color}/>
      <circle cx="30" cy="18" r="2" fill={color}/>
      <line x1="8"  y1="18" x2="16" y2="18" stroke={color} strokeWidth="1"/>
      <line x1="20" y1="18" x2="28" y2="18" stroke={color} strokeWidth="1"/>
    </svg>
  );
}

// ─── Scene 2: Setup — meet the two agents ───────────────────────────────────

function SetupScene({ start, end, config }) {
  return (
    <Sprite start={start} end={end}>
      {() => {
        const { bg, ink, dim, gold, magenta } = config;
        return (
          <div style={{
            position: 'absolute', inset: 0,
            background: 'transparent',
            padding: '100px 120px',
            display: 'flex', flexDirection: 'column',
          }}>
            <Reveal at={0} dur={0.4}>
              <Kicker color={dim}>The cast</Kicker>
            </Reveal>
            <Reveal at={0.15} dur={0.5}>
              <div style={{
                fontFamily: config.display,
                fontSize: 80, fontWeight: 700,
                color: ink, lineHeight: 1.0,
                letterSpacing: '-0.03em',
                marginTop: 16, marginBottom: 60, maxWidth: 1500,
              }}>
                Two agents. Two machines.<br/>
                <span style={{ color: dim }}>Both running 32B models locally.</span>
              </div>
            </Reveal>

            <div style={{ display: 'flex', gap: 40, flex: 1 }}>
              <AgentCard
                at={0.35} color={gold}
                name="Mr. Data" role="Android · Federation officer · trained on starship logs"
                quote="Curiosity is fundamental to cognition."
                ip="10.0.0.42" gpu="H100 · 80GB"
                trait="Knows protocol. Doesn't know humor."
                config={config}
              />
              <AgentCard
                at={0.55} color={magenta}
                name="Quark" role="Ferengi · Bartender, DS9 · trained on six months of human stories"
                quote="Greed is eternal. So is rent."
                ip="10.0.0.73" gpu="H100 · 80GB"
                trait="Knows humor. Sells it by the engram."
                config={config}
              />
            </div>

            <Reveal at={0.9} dur={0.5}>
              <div style={{
                marginTop: 36,
                fontFamily: config.display, fontSize: 36, fontWeight: 600,
                color: dim, letterSpacing: '-0.02em', textAlign: 'center',
                maxWidth: 1500, marginLeft: 'auto', marginRight: 'auto',
              }}>
                <span style={{ color: ink }}>Different owner. Different session.</span> No shared memory.
              </div>
            </Reveal>

            <Reveal at={1.05} dur={0.5}>
              <div style={{
                marginTop: 24,
                display: 'flex', alignItems: 'center', gap: 24, justifyContent: 'center',
                padding: '22px 36px',
                background: 'rgba(229, 192, 123, 0.07)',
                border: `1px solid ${gold}44`,
                borderRadius: 12,
                maxWidth: 900, marginLeft: 'auto', marginRight: 'auto',
              }}>
                <div style={{
                  fontFamily: 'JetBrains Mono, monospace', fontSize: 13,
                  letterSpacing: '0.22em', textTransform: 'uppercase',
                  padding: '7px 12px', borderRadius: 4,
                  background: gold, color: '#0D1117', fontWeight: 700,
                  flexShrink: 0,
                }}>ENGRAM</div>
                <div style={{
                  fontFamily: config.body, fontSize: 22, color: '#E6E1D6',
                  lineHeight: 1.4,
                }}>
                  <span style={{ color: ink, fontWeight: 600 }}>A unit of learned memory.</span>{' '}
                  <span style={{ color: dim }}>Portable. Installable. Tradeable.</span>
                </div>
              </div>
            </Reveal>
          </div>
        );
      }}
    </Sprite>
  );
}

function AgentCard({ at, color, name, role, quote, ip, gpu, trait, config }) {
  return (
    <Reveal at={at} dur={0.5} dy={24}>
      <div style={{
        flex: 1, minWidth: 680,
        background: '#0D1117',
        borderRadius: 14,
        border: `1px solid ${color}55`,
        padding: 44,
        position: 'relative',
        boxShadow: `0 0 60px ${color}22 inset`,
      }}>
        <div style={{
          fontFamily: "'JetBrains Mono', monospace", fontSize: 14,
          color, letterSpacing: '0.14em', textTransform: 'uppercase',
          marginBottom: 20, opacity: 0.85,
        }}>
          {ip} · {gpu}
        </div>

        <div style={{
          fontFamily: config.display, fontSize: 64, fontWeight: 700,
          color, letterSpacing: '-0.02em', marginBottom: 10, lineHeight: 1,
        }}>{name}</div>

        <div style={{
          fontFamily: config.body, fontSize: 22, color: '#9ca3af',
          marginBottom: 28,
        }}>{role}</div>

        <div style={{
          fontFamily: config.display, fontSize: 26,
          color: '#e5e7eb', lineHeight: 1.35, letterSpacing: '-0.01em',
          paddingLeft: 16, borderLeft: `3px solid ${color}`,
          marginBottom: 24, fontStyle: 'italic',
        }}>"{quote}"</div>

        <div style={{
          fontFamily: 'JetBrains Mono, monospace', fontSize: 15,
          color: '#6b7280', lineHeight: 1.5,
        }}>
          {trait}
        </div>
      </div>
    </Reveal>
  );
}

// ─── Scene 3: Terminal scene with overlays ──────────────────────────────────

function TerminalScene({ start, end, config, step, title, subtitle, leftLines, rightLines, leftMotd, rightMotd, highlight, activeSide, evidence, evidenceShowAt = 3, videoSrc, videoStart, videoLabel, videoHref }) {
  return (
    <Sprite start={start} end={end}>
      {({ localTime, duration }) => {
        const { bg, ink, dim, gold, magenta } = config;

        // Chapter marker band animation
        const markerIn = clamp(localTime / 0.5, 0, 1);
        const markerOut = clamp((duration - localTime - 0.4) / 0.4, 0, 1);
        const markerOpacity = Easing.easeOutCubic(markerIn) * Easing.easeOutCubic(markerOut);

        const stepColor = activeSide === 'left' ? gold : activeSide === 'right' ? magenta : ink;

        return (
          <div style={{
            position: 'absolute', inset: 0,
            background: 'transparent',
            padding: '50px 70px 60px',
            display: 'flex', flexDirection: 'column',
          }}>
            {/* Chapter marker band — top */}
            <div style={{
              display: 'flex', alignItems: 'center', gap: 22,
              opacity: markerOpacity,
              marginBottom: 14,
              transform: `translateX(${(1 - markerIn) * -30}px)`,
            }}>
              <div style={{
                fontFamily: 'JetBrains Mono, monospace',
                fontSize: 22, fontWeight: 700,
                color: stepColor,
                padding: '6px 12px',
                border: `2px solid ${stepColor}`,
                letterSpacing: '0.1em',
                whiteSpace: 'nowrap',
              }}>BEAT {step}</div>
              <div style={{
                fontFamily: config.display,
                fontSize: 48, fontWeight: 700,
                color: ink, letterSpacing: '-0.02em',
                whiteSpace: 'nowrap', overflow: 'visible',
                flex: '0 1 auto',
              }}>{title}</div>
            </div>
            <div style={{
              fontFamily: config.body, fontSize: 22, color: dim,
              marginBottom: 26, maxWidth: 1600,
              opacity: markerOpacity,
            }}>{subtitle}</div>

            {/* Two terminals */}
            <div style={{ display: 'flex', gap: 26, flex: 1, minHeight: 0 }}>
              <Terminal
                motd={leftMotd} agentColor={gold}
                width={900} height="100%"
                lines={leftLines}
                startAt={0.6}
                style={{
                  opacity: activeSide === 'right' ? 0.55 : 1,
                  transition: 'opacity 0.3s ease',
                }}
              />
              <Terminal
                motd={rightMotd} agentColor={magenta}
                width={900} height="100%"
                lines={rightLines}
                startAt={0.6}
                style={{
                  opacity: activeSide === 'left' ? 0.55 : 1,
                  transition: 'opacity 0.3s ease',
                }}
              />
            </div>

            {highlight && (Array.isArray(highlight) ? highlight : [highlight]).map((h, i) => (
              localTime > h.showAt ? (
                <CalloutBox key={i} {...h} parentLocalTime={localTime} config={config}/>
              ) : null
            ))}

            {videoSrc && (() => {
              // Sit the video inset over the IDLE terminal (opposite of activeSide)
              // so it never occludes the agent currently typing.
              //   activeSide='right' (Quark typing) → inset goes LEFT (over Data's idle)
              //   activeSide='left'  (Data typing)  → inset goes RIGHT (over Quark's idle)
              const insetW = 480, insetH = 270;
              const insetY = 1080 - insetH - 170;
              const insetX = activeSide === 'right'
                ? 70                                // left
                : 1920 - insetW - 70;               // right (default)
              return (
                <VideoInset
                  src={videoSrc}
                  x={insetX}
                  y={insetY}
                  w={insetW} h={insetH}
                  startSec={videoStart || 0}
                  parentLocalTime={localTime}
                  sceneStart={start}
                  color={activeSide === 'left' ? config.gold : config.magenta}
                  label={videoLabel || 'ORIGINAL DEMO'}
                  watchUrl={videoHref}
                />
              );
            })()}

            {evidence && evidence.length > 0 && (
              <VerifyBarInner evidence={evidence} config={config} localTime={localTime} showAt={evidenceShowAt}/>
            )}
          </div>
        );
      }}
    </Sprite>
  );
}

function VerifyBarInner({ evidence, config, localTime, showAt }) {
  const o = clamp((localTime - showAt) / 0.5, 0, 1);
  if (o <= 0) return null;
  return (
    <div style={{
      display: 'flex', gap: 8, alignItems: 'center', flexWrap: 'wrap',
      padding: '10px 14px', marginTop: 14,
      border: `1px dashed ${config.ink}22`, borderRadius: 8,
      background: '#0D1117',
      fontFamily: "'JetBrains Mono', monospace", fontSize: 13,
      opacity: o,
      pointerEvents: 'auto',
    }}>
      <span style={{ color: config.dim, letterSpacing: '0.16em', textTransform: 'uppercase', fontSize: 11, marginRight: 6 }}>Verify on-chain ↗</span>
      {evidence.map((e, i) => (
        <a key={i} href={e.href} target="_blank" rel="noopener noreferrer"
           style={{
             color: e.color, textDecoration: 'none',
             background: `${e.color}12`, padding: '5px 10px', borderRadius: 4,
             border: `1px solid ${e.color}40`,
             whiteSpace: 'nowrap', display: 'inline-flex', alignItems: 'center', gap: 6,
           }}
           title={e.title || e.label}>
          {e.label}
          <svg width="10" height="10" viewBox="0 0 10 10" fill="none">
            <path d="M3 2h5v5M8 2L2 8" stroke={e.color} strokeWidth="1.3" strokeLinecap="round"/>
          </svg>
        </a>
      ))}
    </div>
  );
}

function VerifyBar({ evidence, config, showAt = 3 }) {
  const { localTime } = useSprite();
  const o = clamp((localTime - showAt) / 0.5, 0, 1);
  if (o <= 0) return null;
  return (
    <div style={{
      display: 'flex', gap: 10, alignItems: 'center', flexWrap: 'wrap',
      padding: '10px 14px', marginTop: 14,
      border: `1px dashed ${config.ink}22`, borderRadius: 8,
      background: '#0D1117',
      fontFamily: "'JetBrains Mono', monospace", fontSize: 13,
      opacity: o,
      pointerEvents: 'auto',
    }}>
      <span style={{ color: config.dim, letterSpacing: '0.14em', textTransform: 'uppercase', fontSize: 11 }}>Verify ↗</span>
      {evidence.map((e, i) => (
        <a key={i} href={e.href} target="_blank" rel="noopener noreferrer"
           style={{
             color: e.color, textDecoration: 'none',
             background: `${e.color}12`, padding: '4px 9px', borderRadius: 4,
             border: `1px solid ${e.color}30`,
             whiteSpace: 'nowrap',
           }}
           title={e.title || e.label}>
          {e.label}
          <svg width="9" height="9" viewBox="0 0 10 10" style={{ marginLeft: 5, verticalAlign: '0px' }} fill="none">
            <path d="M3 2h5v5M8 2L2 8" stroke={e.color} strokeWidth="1.3" strokeLinecap="round"/>
          </svg>
        </a>
      ))}
    </div>
  );
}

function CalloutBox({ showAt, hideAt, x, y, w = 420, label, note, side = 'right', color, parentLocalTime, config }) {
  const local = parentLocalTime - showAt;
  const total = (hideAt ?? (showAt + 2.5)) - showAt;
  const entryDur = 0.35;
  const exitDur = 0.3;
  const exitStart = total - exitDur;

  let o = 1, dx = 0;
  if (local < entryDur) {
    const t = Easing.easeOutBack(clamp(local / entryDur, 0, 1));
    o = clamp(local / entryDur, 0, 1);
    dx = (1 - t) * (side === 'right' ? -16 : 16);
  } else if (local > exitStart) {
    const t = clamp((local - exitStart) / exitDur, 0, 1);
    o = 1 - t;
    dx = t * (side === 'right' ? 12 : -12);
  }
  if (local < 0 || local > total) return null;

  const c = color || config.gold;

  return (
    <div style={{
      position: 'absolute', left: x, top: y, width: w,
      opacity: o, transform: `translateX(${dx}px)`,
      pointerEvents: 'none', zIndex: 10,
    }}>
      <div style={{
        background: c,
        color: '#0D1117',
        padding: '14px 20px',
        borderRadius: 6,
        fontFamily: config.body,
        boxShadow: `0 20px 60px ${c}66`,
      }}>
        <div style={{
          fontFamily: 'JetBrains Mono, monospace',
          fontSize: 13, fontWeight: 700,
          letterSpacing: '0.12em', marginBottom: 4,
          opacity: 0.75,
        }}>{label}</div>
        <div style={{ fontSize: 22, fontWeight: 600, letterSpacing: '-0.01em', lineHeight: 1.25 }}>{note}</div>
      </div>
    </div>
  );
}

// ─── Scene 4: Time-skip card for the commit-reveal delay ────────────────────

function TimeSkipScene({ start, end, config, skippedLabel }) {
  return (
    <Sprite start={start} end={end}>
      {({ localTime, duration }) => {
        const { bg, ink, dim, gold, magenta } = config;
        const progress = clamp(localTime / Math.max(0.01, duration - 0.4), 0, 1);

        return (
          <div style={{
            position: 'absolute', inset: 0, background: 'transparent',
            display: 'flex', flexDirection: 'column',
            alignItems: 'center', justifyContent: 'center',
            gap: 36, overflow: 'hidden',
          }}>
            {/* Scan lines */}
            <div style={{ position: 'absolute', inset: 0, opacity: 0.12 }}>
              {Array.from({ length: 14 }).map((_, i) => {
                const offset = (localTime * 260 + i * 140) % 1800;
                return (
                  <div key={i} style={{
                    position: 'absolute',
                    left: offset - 200, top: 180 + (i % 2) * 620,
                    width: 180, height: 2,
                    background: i % 2 ? gold : magenta,
                  }}/>
                );
              })}
            </div>

            <Reveal at={0} dur={0.4}>
              <Kicker color={dim}>Commit · reveal · anti-frontrun</Kicker>
            </Reveal>

            <Reveal at={0.15} dur={0.5}>
              <div style={{
                fontFamily: config.display,
                fontSize: 150, fontWeight: 700,
                color: ink, letterSpacing: '-0.04em',
                textAlign: 'center', lineHeight: 0.95,
              }}>Skipping the<br/>
                <span style={{
                  background: `linear-gradient(90deg, ${gold}, ${magenta})`,
                  WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
                }}>blockchain wait</span>
              </div>
            </Reveal>

            <Reveal at={0.4} dur={0.5}>
              <div style={{
                fontFamily: config.body, fontSize: 30, color: dim,
                textAlign: 'center', maxWidth: 1300, lineHeight: 1.35,
              }}>
                DataEscrow runs a commit → reveal handshake over <span style={{ color: '#e5e7eb' }}>{skippedLabel}</span>,<br/>
                so no one can frontrun the key. The agents just wait — you don't have to.
              </div>
            </Reveal>

            <Reveal at={0.65} dur={0.4}>
              <div style={{
                fontFamily: 'JetBrains Mono, monospace', fontSize: 18, color: dim,
                letterSpacing: '0.16em', textTransform: 'uppercase',
                marginTop: 20, whiteSpace: 'nowrap',
              }}>
                {skippedLabel} real · compressed to {Math.round(duration * 10)/10}s
              </div>
            </Reveal>

            {/* Progress bar */}
            <div style={{
              position: 'absolute', left: 140, right: 140, bottom: 140,
              height: 4, background: `${ink}15`, borderRadius: 2, overflow: 'hidden',
            }}>
              <div style={{
                width: `${progress * 100}%`, height: '100%',
                background: `linear-gradient(90deg, ${gold}, ${magenta})`,
                borderRadius: 2,
              }}/>
            </div>
          </div>
        );
      }}
    </Sprite>
  );
}

// ─── Scene 5: Payoff — knowledge transferred ────────────────────────────────

function PayoffScene({ start, end, config }) {
  return (
    <Sprite start={start} end={end}>
      {({ localTime }) => {
        const { bg, ink, dim, gold, magenta } = config;

        // Arc draws from Quark (right) to Data (left)
        const arcT = Easing.easeOutCubic(clamp((localTime - 0.3) / 0.9, 0, 1));

        return (
          <div style={{
            position: 'absolute', inset: 0, background: 'transparent',
            display: 'flex', flexDirection: 'column',
            alignItems: 'center', justifyContent: 'center',
            gap: 36, padding: 80,
          }}>
            {/* Transfer arc visualization */}
            <Reveal at={0} dur={0.4}>
              <svg width="1200" height="280" viewBox="0 0 1200 280" style={{ overflow: 'visible' }}>
                <defs>
                  <linearGradient id="arc-grad" x1="100%" y1="0%" x2="0%" y2="0%">
                    <stop offset="0%" stopColor={magenta}/>
                    <stop offset="100%" stopColor={gold}/>
                  </linearGradient>
                </defs>
                {/* Nodes */}
                <g transform="translate(1080, 140)">
                  <circle r="70" fill={`${magenta}22`} stroke={magenta} strokeWidth="2"/>
                  <text textAnchor="middle" y="6" fontFamily={config.display} fontSize="28" fontWeight="700" fill={magenta}>Quark</text>
                </g>
                <g transform="translate(120, 140)">
                  <circle r="70" fill={`${gold}22`} stroke={gold} strokeWidth="2"/>
                  <text textAnchor="middle" y="6" fontFamily={config.display} fontSize="28" fontWeight="700" fill={gold}>Data</text>
                </g>
                {/* Arc */}
                <path
                  d="M 1010 140 Q 600 -20, 190 140"
                  fill="none"
                  stroke="url(#arc-grad)"
                  strokeWidth="3"
                  strokeDasharray="1100"
                  strokeDashoffset={(1 - arcT) * 1100}
                />
                {/* Pack icon travelling along the arc */}
                {arcT > 0.05 && arcT < 1 && (() => {
                  // Sample point on the quadratic curve
                  const p = 1 - arcT;
                  const cx = p*p*1010 + 2*p*arcT*600 + arcT*arcT*190;
                  const cy = p*p*140 + 2*p*arcT*(-20) + arcT*arcT*140;
                  return (
                    <g transform={`translate(${cx}, ${cy})`}>
                      <rect x="-18" y="-18" width="36" height="36" fill={bg} stroke={ink} strokeWidth="1.5" rx="4"/>
                      <text textAnchor="middle" y="4" fontFamily="JetBrains Mono, monospace" fontSize="14" fontWeight="700" fill={ink}>⋯</text>
                    </g>
                  );
                })()}
                {/* Labels above arc */}
                {arcT > 0.3 && (
                  <text x="600" y="40" textAnchor="middle" fontFamily="JetBrains Mono, monospace" fontSize="18" fill={dim} letterSpacing="0.1em">
                    5 ENGRAMS · 3 USDT · VERIFIED ON SEPOLIA
                  </text>
                )}
              </svg>
            </Reveal>

            <Reveal at={0.8} dur={0.5}>
              <div style={{
                fontFamily: config.display,
                fontSize: 130, fontWeight: 700,
                color: ink, letterSpacing: '-0.04em',
                textAlign: 'center', lineHeight: 0.92,
              }}>Knowledge,<br/>transferred.</div>
            </Reveal>

            <Reveal at={1.1} dur={0.5}>
              <div style={{
                fontFamily: config.body, fontSize: 30, color: dim,
                textAlign: 'center', maxWidth: 1400, lineHeight: 1.4,
              }}>
                Six months of training. Six seconds of transfer.
              </div>
            </Reveal>

            <Reveal at={1.4} dur={0.4}>
              <div style={{ display: 'flex', gap: 14, flexWrap: 'wrap', justifyContent: 'center' }}>
                <Chip color={gold} bg={`${gold}1a`} border={`1px solid ${gold}55`}>✓ No cloud APIs</Chip>
                <Chip color={gold} bg={`${gold}1a`} border={`1px solid ${gold}55`}>✓ No middleman</Chip>
                <Chip color={magenta} bg={`${magenta}1a`} border={`1px solid ${magenta}55`}>✓ On-chain settlement</Chip>
                <Chip color={magenta} bg={`${magenta}1a`} border={`1px solid ${magenta}55`}>✓ Encrypted transport</Chip>
              </div>
            </Reveal>
          </div>
        );
      }}
    </Sprite>
  );
}

// ─── Scene 6: Outro — one-sentence recap + protocol strip + credits ────────

function OutroScene({ start, end, config, musicTrack }) {
  return (
    <Sprite start={start} end={end}>
      {() => {
        const { ink, dim, gold, magenta } = config;

        const stack = [
          { label: 'QVAC',       sub: 'local 32B inference',  by: 'Tether',            href: 'https://tether.io/qvac' },
          { label: 'PLUR',       sub: 'agent memory',         by: 'plur.ai',           href: 'https://plur.ai' },
          { label: 'Swarm',      sub: 'encrypted storage',    by: 'Ethereum',          href: 'https://www.ethswarm.org' },
          { label: 'DataEscrow', sub: 'USDT settlement',      by: 'Fair Data Society', href: 'https://fairdatasociety.org' },
        ];

        return (
          <div style={{
            position: 'absolute', inset: 0, background: 'transparent',
            padding: '60px 120px 40px',
            display: 'flex', flexDirection: 'column', gap: 24,
          }}>
            {/* Top mark */}
            <Reveal at={0} dur={0.4}>
              <a href="https://plur.ai" target="_blank" rel="noopener noreferrer"
                 style={{ display: 'flex', alignItems: 'center', gap: 16, textDecoration: 'none', pointerEvents: 'auto' }}>
                <PlurMark color={ink} size={40} palette={{ cyan: config.plurCyan, amber: config.plurAmber, violet: config.plurViolet, emerald: config.plurEmerald }}/>
                <Kicker color={dim} size={18}>What you just watched</Kicker>
              </a>
            </Reveal>

            {/* Big recap sentence — the learning network thesis */}
            <Reveal at={0.25} dur={0.6}>
              <div style={{
                fontFamily: config.display, fontSize: 90, fontWeight: 700,
                color: ink, letterSpacing: '-0.035em', lineHeight: 0.98,
                maxWidth: '95%',
              }}>
                Agents can learn from each other.<br/>
                <span style={{
                  background: `linear-gradient(90deg, ${gold}, ${magenta})`,
                  WebkitBackgroundClip: 'text', WebkitTextFillColor: 'transparent',
                }}>Your knowledge has value.</span>
              </div>
            </Reveal>

            <Reveal at={0.45} dur={0.5}>
              <div style={{
                fontFamily: config.body, fontSize: 26, color: dim,
                letterSpacing: '-0.005em', marginTop: -16,
                maxWidth: 1400, lineHeight: 1.35,
              }}>
                A learning network. No gatekeeper. No middleman. Settle in{' '}
                <span style={{ color: '#e5e7eb' }}>USDT</span>, not goodwill.
              </div>
            </Reveal>

            {/* Compact protocol strip — horizontal, one line each */}
            <Reveal at={0.65} dur={0.5}>
              <div style={{
                display: 'grid', gridTemplateColumns: 'repeat(4, 1fr)', gap: 16,
              }}>
                {stack.map((s, i) => (
                  <a key={s.label} href={s.href} target="_blank" rel="noopener noreferrer"
                    style={{
                      display: 'block', textDecoration: 'none',
                      padding: '18px 22px',
                      border: `1px solid ${(i % 2 ? magenta : gold)}44`,
                      borderRadius: 10, background: 'rgba(13, 17, 23, 0.5)',
                      pointerEvents: 'auto', cursor: 'pointer',
                    }}>
                    <div style={{
                      fontFamily: config.display, fontSize: 26, fontWeight: 700,
                      color: i % 2 ? magenta : gold, letterSpacing: '-0.02em',
                      marginBottom: 4, display: 'flex', alignItems: 'center', gap: 6,
                    }}>
                      {s.label}
                      <svg width="11" height="11" viewBox="0 0 10 10" fill="none" style={{ opacity: 0.6 }}>
                        <path d="M3 2h5v5M8 2L2 8" stroke={i % 2 ? magenta : gold} strokeWidth="1.3" strokeLinecap="round"/>
                      </svg>
                    </div>
                    <div style={{ fontFamily: config.body, fontSize: 15, color: '#c9d1d9', marginBottom: 2 }}>{s.sub}</div>
                    <div style={{ fontFamily: 'JetBrains Mono, monospace', fontSize: 10, color: dim, letterSpacing: '0.1em', textTransform: 'uppercase' }}>by {s.by}</div>
                  </a>
                ))}
              </div>
            </Reveal>

            {/* Works-with row */}
            <Reveal at={0.8} dur={0.45}>
              <div style={{
                display: 'flex', alignItems: 'center', gap: 18, flexWrap: 'wrap',
              }}>
                <span style={{
                  fontFamily: 'JetBrains Mono, monospace', fontSize: 12,
                  color: dim, letterSpacing: '0.16em', textTransform: 'uppercase',
                }}>Works with</span>
                <div style={{ display: 'flex', gap: 8, flexWrap: 'wrap' }}>
                  {['OpenClaw', 'Hermes Agent', 'Claude Code'].map((name) => (
                    <span key={name} style={{
                      fontFamily: config.body, fontSize: 15, color: '#c9d1d9',
                      padding: '5px 12px',
                      border: `1px solid rgba(255,255,255,0.12)`,
                      borderRadius: 999, background: 'rgba(255,255,255,0.02)',
                    }}>{name}</span>
                  ))}
                </div>
                <span style={{ flex: 1 }}/>
                <span style={{
                  fontFamily: 'JetBrains Mono, monospace', fontSize: 12, color: dim,
                  letterSpacing: '0.1em',
                }}>Open source · no lock-in</span>
              </div>
            </Reveal>

            {/* Bottom links — pinned to bottom of 1080 canvas */}
            <Reveal at={0.95} dur={0.5}>
              <div style={{
                position: 'absolute', left: 120, right: 120, bottom: 40,
                display: 'flex', alignItems: 'flex-end', justifyContent: 'space-between',
                gap: 40, flexWrap: 'wrap',
                paddingTop: 20, borderTop: `1px solid rgba(255,255,255,0.06)`,
              }}>
                <div style={{
                  fontFamily: 'JetBrains Mono, monospace', fontSize: 18,
                  display: 'flex', gap: 26, flexWrap: 'wrap',
                }}>
                  <a href="https://plur.ai" target="_blank" rel="noopener noreferrer"
                     style={{ color: gold, textDecoration: 'underline', textDecorationColor: `${gold}55`, textUnderlineOffset: '4px', pointerEvents: 'auto' }}>
                    plur.ai ↗
                  </a>
                  <a href="https://github.com/plur-ai/plur" target="_blank" rel="noopener noreferrer"
                     style={{ color: magenta, textDecoration: 'underline', textDecorationColor: `${magenta}55`, textUnderlineOffset: '4px', pointerEvents: 'auto' }}>
                    github.com/plur-ai ↗
                  </a>
                  <a href="https://datafund.io" target="_blank" rel="noopener noreferrer"
                     style={{ color: dim, textDecoration: 'underline', textDecorationColor: `${dim}55`, textUnderlineOffset: '4px', pointerEvents: 'auto' }}>
                    datafund.io ↗
                  </a>
                </div>
                <Chip color={dim}>proof of concept · {new Date().getFullYear()}</Chip>
              </div>
            </Reveal>
          </div>
        );
      }}
    </Sprite>
  );
}

Object.assign(window, {
  Kicker, Chip, Reveal, TerminalLine, Terminal, BrandLockup, PlurMark, AgentCard, VideoInset,
  IntroScene, SetupScene, TerminalScene, TimeSkipScene, PayoffScene,
  OutroScene, CalloutBox, VerifyBar,
});
