/* explainer-scenes.jsx
   Six-scene timeline for the Osynax explainer.
   Uses globals from animations.jsx: Stage, Sprite, useTime, useSprite,
   useTimeline, Easing, interpolate, animate, clamp. */

const { useEffect, useRef, useState } = React;

/* ===================== PALETTE ===================== */
const P = {
  // Light bracketing palette (intro / outro) — matches landing page
  light:  "#FAFAF9",
  ink:    "#0A0A0A",
  ink2:   "#1F2024",
  muted:  "#5A5C63",
  muted2: "#8A8C93",
  line:   "#E6E5E1",
  accent:    "oklch(0.52 0.055 200)",
  accentInk: "oklch(0.40 0.06 200)",
  accentWash:"oklch(0.96 0.012 200)",

  // Dark product palette — matches dashboard/app/globals.css
  dark:    "#0a0a0f",
  surface: "#0f0f1a",
  card:    "#0c0c14",
  border:  "#1a1a2e",
  borderHi:"#1e1e2e",
  text:    "#e2e8f0",
  dim:     "#475569",
  blue:    "#3b82f6",
  blue2:   "#60a5fa",
  safe:    "#22c55e",
  unsafe:  "#ef4444",
  warn:    "#eab308",
  remed:   "#4ade80",
  mono: `"JetBrains Mono", ui-monospace, monospace`,
  sans: `"Inter", system-ui, sans-serif`,
};

/* ===================== ICON ATOMS ===================== */
/* Reused inline SVG primitives so we don't depend on lucide. */
const Ico = {
  pr: (size=18, c="currentColor") => (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
      <circle cx="6" cy="6" r="3"/><circle cx="6" cy="18" r="3"/><path d="M6 9v6"/><circle cx="18" cy="18" r="3"/><path d="M13 6h3a2 2 0 0 1 2 2v7"/>
    </svg>
  ),
  branch: (size=14, c="currentColor") => (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
      <circle cx="6" cy="6" r="3"/><circle cx="18" cy="18" r="3"/><path d="M6 9v3a3 3 0 0 0 3 3h6"/>
    </svg>
  ),
  scan: (size=22, c="currentColor") => (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
      <path d="M3 7V5a2 2 0 0 1 2-2h2M17 3h2a2 2 0 0 1 2 2v2M21 17v2a2 2 0 0 1-2 2h-2M7 21H5a2 2 0 0 1-2-2v-2"/>
      <circle cx="12" cy="12" r="3"/><path d="m16 16-1.5-1.5"/>
    </svg>
  ),
  brain: (size=22, c="currentColor") => (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
      <path d="M9.5 2a3 3 0 0 0-3 3 3 3 0 0 0-2 5.5A3 3 0 0 0 7 16a3 3 0 0 0 2.5 4 2.5 2.5 0 0 0 2.5-2.5V5.5A3.5 3.5 0 0 0 9.5 2zM14.5 2a3 3 0 0 1 3 3 3 3 0 0 1 2 5.5A3 3 0 0 1 17 16a3 3 0 0 1-2.5 4 2.5 2.5 0 0 1-2.5-2.5V5.5A3.5 3.5 0 0 1 14.5 2z"/>
    </svg>
  ),
  scale: (size=22, c="currentColor") => (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
      <path d="M16 16h6l-3-6-3 6Z"/><path d="M2 16h6l-3-6-3 6Z"/><path d="M12 4v18"/><path d="M5 22h14"/><path d="M5 10s2-2 7-2 7 2 7 2"/>
    </svg>
  ),
  gavel: (size=22, c="currentColor") => (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
      <path d="m14 13-7.5 7.5a2.12 2.12 0 0 1-3-3L11 10"/><path d="m16 16 6-6"/><path d="m8 8 6-6"/><path d="m9 7 8 8"/><path d="m21 11-8-8"/>
    </svg>
  ),
  zap: (size=22, c="currentColor") => (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
      <path d="M4 14h7l-3 7 10-11h-7l3-7-10 11Z"/>
    </svg>
  ),
  wrench: (size=22, c="currentColor") => (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
      <path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/>
    </svg>
  ),
  shieldX: (size=24, c="currentColor") => (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
      <path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="m9.5 9 5 6m0-6-5 6"/>
    </svg>
  ),
  check: (size=18, c="currentColor") => (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
      <path d="m5 12 5 5L20 7"/>
    </svg>
  ),
  book: (size=14, c="currentColor") => (
    <svg width={size} height={size} viewBox="0 0 24 24" fill="none" stroke={c} strokeWidth="1.7" strokeLinecap="round" strokeLinejoin="round">
      <path d="M2 3h6a4 4 0 0 1 4 4v14a3 3 0 0 0-3-3H2z"/><path d="M22 3h-6a4 4 0 0 0-4 4v14a3 3 0 0 1 3-3h7z"/>
    </svg>
  ),
};

const STAGES = [
  { id: "init",        label: "INIT",        icon: "zap"    },
  { id: "planner",     label: "PLANNER",     icon: "brain"  },
  { id: "sast",        label: "SAST GATE",        icon: "scan"   },
  { id: "evaluator",   label: "EVALUATOR",   icon: "scale"  },
  { id: "auditor",     label: "AUDITOR",     icon: "gavel"  },
  { id: "remediation", label: "GENERATOR", icon: "wrench" },
];

/* ===================== CAMERA WRAPPER =====================
   Wraps a scene in a transform driven by useSprite() progress. */
function Camera({ from = { scale: 1, x: 0, y: 0 }, to = { scale: 1.04, x: 0, y: 0 },
                  ease = Easing.easeOutQuad, children }) {
  const { progress } = useSprite();
  const t = ease(progress);
  const scale = from.scale + (to.scale - from.scale) * t;
  const x = from.x + (to.x - from.x) * t;
  const y = from.y + (to.y - from.y) * t;
  return (
    <div style={{
      position: "absolute", inset: 0,
      transform: `translate(${x}px, ${y}px) scale(${scale})`,
      transformOrigin: "center",
      willChange: "transform",
    }}>
      {children}
    </div>
  );
}

/* Crossfade-able layer — opacity ramps from 0 → 1 over entry, 1 → 0 over exit */
function FadeLayer({ children, entry = 0.4, exit = 0.4 }) {
  const { localTime, duration } = useSprite();
  const exitStart = Math.max(0, duration - exit);
  let opacity = 1;
  if (localTime < entry)        opacity = Easing.easeOutCubic(clamp(localTime / entry, 0, 1));
  else if (localTime > exitStart) opacity = 1 - Easing.easeInCubic(clamp((localTime - exitStart) / exit, 0, 1));
  return (
    <div style={{ position: "absolute", inset: 0, opacity, willChange: "opacity" }}>
      {children}
    </div>
  );
}

/* ===================== BACKGROUND ===================== */
/* Crossfades the canvas background colour based on absolute scene timeline. */
function Background() {
  const time = useTime();
  // breakpoints: scene transitions
  let bg = P.light;
  if      (time < 5.0)  bg = P.light;
  else if (time < 5.6)  bg = mix(P.light, P.dark, (time - 5.0) / 0.6);
  else if (time < 39.5) bg = P.dark;
  else if (time < 40.2) bg = mix(P.dark, P.light, (time - 39.5) / 0.7);
  else                  bg = P.light;
  return <div style={{ position: "absolute", inset: 0, background: bg, transition: "background 60ms linear" }} />;
}
function mix(a, b, t) {
  // Quick mix only for our two known palette ends — pick whichever t is closer to
  return t < 0.5 ? a : b;
}

/* ===================== TIMESTAMP HUD ===================== */
/* Top-right corner: thin mono timestamp + scene label. */
function TimestampHud() {
  const time = useTime();
  const scene = (() => {
    if (time < 5.0)  return "01 · A PR is opened";
    if (time < 13.0) return "02 · Osynax intercepts";
    if (time < 21.0) return "03 · SAST detects PII in logs";
    if (time < 31.0) return "04 · Citations ground the verdict";
    if (time < 39.0) return "05 · Verdict and remediation";
    return "06 · Outro";
  })();

  // Choose colour for HUD vs current bg so it stays readable across scenes
  const onDark = time >= 5.6 && time < 40.0;
  const fg   = onDark ? "rgba(255,255,255,0.55)" : P.muted2;
  const line = onDark ? "rgba(255,255,255,0.16)" : P.line;

  const m = Math.floor(time / 60);
  const s = Math.floor(time % 60);
  const cs = Math.floor((time * 100) % 100);
  const stamp = `${String(m).padStart(2,"0")}:${String(s).padStart(2,"0")}.${String(cs).padStart(2,"0")}`;

  return (
    <div style={{
      position: "absolute", top: 32, right: 40, zIndex: 5,
      display: "flex", flexDirection: "column", alignItems: "flex-end", gap: 6,
      fontFamily: P.mono, fontSize: 16, letterSpacing: "0.04em",
      color: fg, pointerEvents: "none",
    }}>
      <span style={{ fontVariantNumeric: "tabular-nums" }}>{stamp}</span>
      <span style={{ fontSize: 13, textTransform: "uppercase", letterSpacing: "0.08em", borderTop: `1px solid ${line}`, paddingTop: 6 }}>
        {scene}
      </span>
    </div>
  );
}

/* Osynax wordmark — small bottom-left tag throughout */
function CornerMark() {
  const time = useTime();
  const onDark = time >= 5.6 && time < 40.0;
  const fg = onDark ? "rgba(255,255,255,0.65)" : P.ink;
  const swatch = onDark ? P.blue : P.accent;
  return (
    <div style={{
      position: "absolute", bottom: 32, left: 40, zIndex: 5,
      display: "flex", alignItems: "center", gap: 10,
      fontFamily: P.sans, fontSize: 18, fontWeight: 600,
      letterSpacing: "-0.02em",
      color: fg, pointerEvents: "none",
    }}>
      <span style={{ width: 8, height: 8, background: swatch, borderRadius: 1 }} />
      Osynax
    </div>
  );
}

/* ===================== SCENE 01 — GitHub PR opens ===================== */
function Scene01() {
  return (
    <Sprite start={0} end={5.6} keepMounted>
      <FadeLayer entry={0.3} exit={0.6}>
        <Camera from={{ scale: 0.96, x: 0, y: 20 }} to={{ scale: 1.02, x: 0, y: 0 }}>
          <div style={{ position: "absolute", inset: 0, padding: "120px 220px 0", color: P.ink }}>

            {/* Eyebrow */}
            <Sprite start={0.3} end={5.6}>
              {() => (
                <div style={{
                  fontFamily: P.mono, fontSize: 14, letterSpacing: "0.1em",
                  textTransform: "uppercase", color: P.muted2,
                  display: "inline-flex", alignItems: "center", gap: 12,
                }}>
                  <span style={{ width: 24, height: 1, background: P.line }}/>
                  09:14 · Pull request opened
                </div>
              )}
            </Sprite>

            {/* Headline — absolute Sprite times */}
            <Sprite start={0.5} end={5.6}>
              <TitleText />
            </Sprite>

            {/* The GitHub PR card itself */}
            <Sprite start={1.0} end={5.6}>
              <PRCardLight />
            </Sprite>

          </div>
        </Camera>
      </FadeLayer>
    </Sprite>
  );
}
function TitleText() {
  const { localTime } = useSprite();
  const t = Easing.easeOutCubic(clamp(localTime / 0.6, 0, 1));
  return (
    <h1 style={{
      margin: "20px 0 0", fontFamily: P.sans,
      fontSize: 96, fontWeight: 500, letterSpacing: "-0.025em",
      lineHeight: 1.05, color: P.ink, maxWidth: "16ch",
      opacity: t, transform: `translateY(${(1-t)*20}px)`,
    }}>
      A pull request opens.
      <br /><span style={{ color: P.accentInk }}>Senior eng review queues up.</span>
    </h1>
  );
}
function PRCardLight() {
  const { localTime } = useSprite();
  const t = Easing.easeOutBack(clamp(localTime / 0.6, 0, 1));
  return (
    <div style={{
      marginTop: 48, maxWidth: 980,
      background: "#fff", border: `1px solid ${P.line}`,
      borderRadius: 10, padding: "20px 24px",
      boxShadow: "0 18px 48px rgba(0,0,0,0.06)",
      transform: `translateY(${(1-t)*40}px) scale(${0.97 + 0.03*t})`,
      opacity: clamp(localTime/0.4, 0, 1),
    }}>
      <div style={{ display: "flex", alignItems: "center", gap: 14 }}>
        <span style={{ color: P.accent }}>{Ico.pr(22, P.accent)}</span>
        <span style={{ fontFamily: P.mono, fontSize: 16, color: P.accentInk, fontWeight: 600 }}>#142</span>
        <span style={{
          padding: "4px 10px", borderRadius: 4,
          background: P.accentWash, color: P.accentInk,
          fontFamily: P.mono, fontSize: 12, fontWeight: 500, letterSpacing: "0.04em",
        }}>OPEN</span>
        <span style={{ marginLeft: "auto", color: P.muted, fontFamily: P.mono, fontSize: 13 }}>
          just now
        </span>
      </div>
      <h3 style={{
        margin: "14px 0 6px", fontFamily: P.sans,
        fontSize: 28, fontWeight: 500, color: P.ink, letterSpacing: "-0.015em",
      }}>feat(patients): add free-text search endpoint</h3>
      <div style={{
        display: "flex", flexWrap: "wrap", gap: "8px 22px",
        fontSize: 16, fontFamily: P.mono, color: P.muted,
      }}>
        <span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
          {Ico.branch(14, P.muted)} feat/patient-search → main
        </span>
        <span>@alex-hodgson</span>
        <span style={{ color: P.muted2 }}>8c4a1f2</span>
        <span style={{ color: P.accentInk }}>+142 −18</span>
      </div>
    </div>
  );
}

/* ===================== SCENE 02 — Pipeline ===================== */
function Scene02() {
  return (
    <Sprite start={5.4} end={13.4} keepMounted>
      <FadeLayer entry={0.5} exit={0.5}>
        <Camera from={{ scale: 1.0, x: 0, y: 0 }} to={{ scale: 1.06, x: 0, y: 0 }}>
          <div style={{
            position: "absolute", inset: 0, color: P.text, padding: "140px 200px 0",
            display: "flex", flexDirection: "column", alignItems: "center",
          }}>
            <Sprite start={5.8} end={13.4}>
              {() => (
                <div style={{
                  fontFamily: P.mono, fontSize: 14, letterSpacing: "0.1em",
                  textTransform: "uppercase", color: "rgba(255,255,255,0.55)",
                  display: "inline-flex", alignItems: "center", gap: 12,
                  marginBottom: 10,
                }}>
                  <span style={{ width: 24, height: 1, background: "rgba(255,255,255,0.2)" }}/>
                  09:14:02 · Webhook received
                </div>
              )}
            </Sprite>

            <Sprite start={5.9} end={13.4}>
              <Scene02Headline />
            </Sprite>

            {/* The pipeline */}
            <Sprite start={6.6} end={13.4}>
              <PipelineRow />
            </Sprite>

            {/* Sub-caption */}
            <Sprite start={11.4} end={13.4}>
              {() => (
                <div style={{
                  marginTop: 80, fontFamily: P.mono, fontSize: 18,
                  color: P.blue2, letterSpacing: "0.04em",
                }}>
                  Four agents · one deterministic SAST gate
                </div>
              )}
            </Sprite>
          </div>
        </Camera>
      </FadeLayer>
    </Sprite>
  );
}
function Scene02Headline() {
  const { localTime } = useSprite();
  const t = Easing.easeOutCubic(clamp(localTime / 0.6, 0, 1));
  return (
    <h2 style={{
      margin: 0, fontFamily: P.sans,
      fontSize: 72, fontWeight: 500, letterSpacing: "-0.022em", textAlign: "center",
      lineHeight: 1.08, color: P.text, maxWidth: "22ch",
      opacity: t, transform: `translateY(${(1-t)*16}px)`,
    }}>
      Osynax runs <span style={{ color: P.blue2 }}>four agents</span> against it
      <br />before the human queue even gets a chance.
    </h2>
  );
}

const PIPELINE_BEATS = [
  { id: "init",        start: 0.0, end: 0.5 },
  { id: "planner",     start: 0.4, end: 1.1 },
  { id: "sast",        start: 1.0, end: 2.0 },
  { id: "evaluator",   start: 1.9, end: 3.0 },
  { id: "auditor",     start: 2.9, end: 3.9 },
  { id: "remediation", start: 3.8, end: 4.8 },
];
function PipelineRow() {
  const { localTime } = useSprite();
  return (
    <div style={{
      marginTop: 80, display: "flex", alignItems: "center",
      justifyContent: "center", gap: 0,
    }}>
      {STAGES.map((s, i) => {
        const beat = PIPELINE_BEATS[i];
        let status = null;
        if (localTime >= beat.start && localTime < beat.end) status = "running";
        else if (localTime >= beat.end) status = "done";

        const isConn = i > 0;
        return (
          <React.Fragment key={s.id}>
            {isConn && <PipelineConn active={status !== null} />}
            <PipelineNode stage={s} status={status} />
          </React.Fragment>
        );
      })}
    </div>
  );
}
function PipelineNode({ stage, status }) {
  let border = P.borderHi;
  let bg = "#111119";
  let icoCol = P.dim;
  let glow = "none";
  let labelCol = P.dim;

  if (status === "running") {
    border = P.blue; bg = "#1e3a5f"; icoCol = P.blue2;
    glow = "0 0 28px rgba(59,130,246,0.55)";
    labelCol = P.blue2;
  } else if (status === "done") {
    border = P.safe; bg = "#0f2e1a"; icoCol = P.safe;
    glow = "0 0 14px rgba(34,197,94,0.3)";
    labelCol = P.safe;
  }

  const IcoFn = Ico[stage.icon];
  return (
    <div style={{ display: "flex", flexDirection: "column", alignItems: "center", gap: 14, minWidth: 120 }}>
      <div style={{
        width: 90, height: 90, borderRadius: "50%",
        border: `2px solid ${border}`, background: bg,
        display: "flex", alignItems: "center", justifyContent: "center",
        boxShadow: glow, transition: "all 320ms ease",
        position: "relative",
      }}>
        {status === "running" ? (
          <span style={{ animation: "spinrot 1.2s linear infinite", display: "inline-flex" }}>
            <svg width="36" height="36" viewBox="0 0 36 36" fill="none" stroke={icoCol} strokeWidth="3" strokeLinecap="round">
              <path d="M30 18a12 12 0 1 1-9-11.6"/>
            </svg>
          </span>
        ) : status === "done" ? (
          Ico.check(36, icoCol)
        ) : (
          IcoFn(36, icoCol)
        )}
      </div>
      <div style={{
        fontFamily: P.mono, fontSize: 14, fontWeight: 600,
        color: labelCol, letterSpacing: "0.08em",
        transition: "color 320ms",
      }}>
        {stage.label}
      </div>
    </div>
  );
}
function PipelineConn({ active }) {
  return (
    <div style={{
      width: 56, height: 2,
      background: active ? P.safe : P.borderHi,
      transition: "background 320ms",
      marginBottom: 28, /* visually centred against the 90px nodes */
    }} />
  );
}

/* ===================== SCENE 03 — SAST detects PII ===================== */
function Scene03() {
  return (
    <Sprite start={13.2} end={21.2} keepMounted>
      <FadeLayer entry={0.4} exit={0.5}>
        <Camera from={{ scale: 0.94, x: 0, y: 0 }} to={{ scale: 1.0, x: 0, y: 0 }}>
          <div style={{
            position: "absolute", inset: 0, padding: "120px 180px 0",
            display: "grid", gridTemplateColumns: "1fr 1fr", gap: 56, alignItems: "center",
          }}>
            <Sprite start={13.4} end={21.2}>
              <LeftCallout />
            </Sprite>
            <Sprite start={13.6} end={21.2}>
              <CodeWindow />
            </Sprite>
          </div>
        </Camera>
      </FadeLayer>
    </Sprite>
  );
}
function LeftCallout() {
  const { localTime } = useSprite();
  const t = Easing.easeOutCubic(clamp(localTime / 0.6, 0, 1));
  return (
    <div style={{ opacity: t, transform: `translateY(${(1-t)*16}px)` }}>
      <div style={{
        fontFamily: P.mono, fontSize: 14, letterSpacing: "0.1em",
        textTransform: "uppercase", color: "rgba(255,255,255,0.55)",
        display: "inline-flex", alignItems: "center", gap: 12, marginBottom: 22,
      }}>
        <span style={{ width: 24, height: 1, background: "rgba(255,255,255,0.2)" }}/>
        SAST · 09:14:08
      </div>
      <h2 style={{
        margin: 0, fontFamily: P.sans, color: P.text,
        fontSize: 64, fontWeight: 500, letterSpacing: "-0.022em", lineHeight: 1.08,
      }}>
        The patient's <span style={{ color: P.unsafe }}>query string</span> ends up in the request log.
      </h2>
      <p style={{
        marginTop: 24, fontFamily: P.sans, color: "#a8b3c9",
        fontSize: 22, lineHeight: 1.5, maxWidth: "30ch",
      }}>
        Free-text patient search · <code style={{ fontFamily: P.mono, color: P.blue2 }}>/v1/patients/search?q=&hellip;</code> · logged at INFO level by the standard middleware.
      </p>

      <Sprite start={15.2} end={21.2}>
        <SastFindings />
      </Sprite>
    </div>
  );
}
function SastFindings() {
  const { localTime } = useSprite();
  const lines = [
    { sev: "CRITICAL", col: P.unsafe, txt: "Sensitive query parameter in log line" },
    { sev: "HIGH",     col: P.warn,   txt: "Unbound LIKE clause in patient search" },
    { sev: "HIGH",     col: P.warn,   txt: "No rate limit on /v1/patients/search" },
  ];
  return (
    <div style={{ marginTop: 32, display: "flex", flexDirection: "column", gap: 10 }}>
      {lines.map((l, i) => {
        const at = i * 0.5;
        const t = Easing.easeOutCubic(clamp((localTime - at) / 0.4, 0, 1));
        return (
          <div key={i} style={{
            display: "flex", alignItems: "center", gap: 14,
            opacity: t, transform: `translateX(${(1-t)*-12}px)`,
            fontFamily: P.mono, fontSize: 16,
          }}>
            <span style={{
              padding: "3px 8px", borderRadius: 3,
              background: `${l.col}1a`, color: l.col,
              fontSize: 11, fontWeight: 600, letterSpacing: "0.06em",
              minWidth: 78, textAlign: "center",
              border: `1px solid ${l.col}40`,
            }}>{l.sev}</span>
            <span style={{ color: "#a8b3c9" }}>{l.txt}</span>
          </div>
        );
      })}
    </div>
  );
}
function CodeWindow() {
  const { localTime } = useSprite();
  const t = Easing.easeOutCubic(clamp(localTime / 0.6, 0, 1));
  return (
    <div style={{
      background: P.card, border: `1px solid ${P.border}`, borderRadius: 12,
      overflow: "hidden", opacity: t, transform: `translateY(${(1-t)*16}px)`,
      boxShadow: "0 24px 64px rgba(0,0,0,0.45)",
    }}>
      <div style={{
        display: "flex", alignItems: "center", gap: 8,
        padding: "14px 18px", background: "#0e0e18",
        borderBottom: `1px solid ${P.border}`,
      }}>
        <div style={{ width: 12, height: 12, borderRadius: "50%", background: "#ff5f57" }} />
        <div style={{ width: 12, height: 12, borderRadius: "50%", background: "#febc2e" }} />
        <div style={{ width: 12, height: 12, borderRadius: "50%", background: "#28c840" }} />
        <span style={{ marginLeft: 14, fontFamily: P.mono, fontSize: 14, color: P.dim }}>
          internal/logging/middleware.go
        </span>
      </div>
      <pre style={{
        margin: 0, padding: "22px 24px", fontFamily: P.mono, fontSize: 16,
        lineHeight: 1.65, color: "#a8b3c9",
        background: "#0a0a12",
      }}>
{`func RequestLogger(next http.Handler) http.Handler {
  return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    start := time.Now()
    next.ServeHTTP(w, r)
`}<HighlightLine localTime={localTime}>
{`    log.Info("http",
        "path", `}<u style={{ color: P.unsafe, textDecorationStyle: "wavy", textDecorationThickness: 2, textUnderlineOffset: 4 }}>r.URL.RequestURI()</u>{`,
        "dur_ms", time.Since(start).Milliseconds())
`}</HighlightLine>{`  })
}`}
      </pre>
    </div>
  );
}
function HighlightLine({ children, localTime }) {
  // Pulse a soft red bg from t=1.4s
  const pulseStart = 1.4;
  const t = clamp((localTime - pulseStart) / 0.6, 0, 1);
  const wave = Math.sin((localTime - pulseStart) * 4) * 0.5 + 0.5;
  const intensity = t * (0.35 + 0.25 * wave);
  return (
    <span style={{
      display: "block",
      background: `rgba(239,68,68,${intensity * 0.18})`,
      borderLeft: `3px solid rgba(239,68,68,${intensity})`,
      paddingLeft: 12,
      marginLeft: -15,
      transition: "background 80ms linear",
    }}>{children}</span>
  );
}

/* ===================== SCENE 04 — Citations ===================== */
function Scene04() {
  return (
    <Sprite start={21.0} end={31.0} keepMounted>
      <FadeLayer entry={0.4} exit={0.5}>
        <Camera from={{ scale: 1.0, x: 0, y: 0 }} to={{ scale: 1.03, x: 0, y: 0 }}>
          <div style={{
            position: "absolute", inset: 0, padding: "100px 180px 0",
            color: P.text,
          }}>
            <Sprite start={21.2} end={31.0}>
              {() => (
                <div style={{
                  fontFamily: P.mono, fontSize: 14, letterSpacing: "0.1em",
                  textTransform: "uppercase", color: "rgba(255,255,255,0.55)",
                  display: "inline-flex", alignItems: "center", gap: 12, marginBottom: 14,
                }}>
                  <span style={{ width: 24, height: 1, background: "rgba(255,255,255,0.2)" }}/>
                  Evaluator · grounded retrieval
                </div>
              )}
            </Sprite>
            <Sprite start={21.3} end={31.0}>
              <Scene04Title />
            </Sprite>

            <div style={{
              marginTop: 56, display: "grid",
              gridTemplateColumns: "1fr 1fr 1fr", gap: 24,
            }}>
              <Sprite start={22.6} end={31.0}>
                <CitationCard
                  framework="UK GDPR" clauseRef="Article 32(1)"
                  title="Security of processing"
                  text="Taking into account the state of the art, the costs of implementation and the nature, scope, context and purposes of processing… the controller and the processor shall implement appropriate technical and organisational measures to ensure a level of security appropriate to the risk."
                  url="legislation.gov.uk/eur/2016/679/article/32"
                />
              </Sprite>
              <Sprite start={23.4} end={31.0}>
                <CitationCard
                  framework="OWASP Top 10:2025" clauseRef="A09"
                  title="Security Logging and Monitoring Failures"
                  text="Insufficient logging, detection, monitoring, and active response occurs any time logging of auditable events… Logs of applications and APIs are not monitored for suspicious activity."
                  url="owasp.org/Top10/A09_2025"
                />
              </Sprite>
              <Sprite start={24.2} end={31.0}>
                <CitationCard
                  framework="CWE Top 25:2025" clauseRef="CWE-532"
                  title="Insertion of Sensitive Information into Log File"
                  text="Information written to log files can be of a sensitive nature and give valuable guidance to an attacker or expose sensitive user information."
                  url="cwe.mitre.org/data/definitions/532"
                />
              </Sprite>
            </div>

            <Sprite start={27.0} end={31.0}>
              {() => (
                <p style={{
                  marginTop: 48, textAlign: "center",
                  fontFamily: P.mono, fontSize: 18,
                  color: P.blue2, letterSpacing: "0.04em",
                }}>
                  Hybrid retrieval · pgvector + tsvector · RRF k=60 · 566 clauses indexed locally
                </p>
              )}
            </Sprite>
          </div>
        </Camera>
      </FadeLayer>
    </Sprite>
  );
}
function Scene04Title() {
  const { localTime } = useSprite();
  const t = Easing.easeOutCubic(clamp(localTime / 0.6, 0, 1));
  return (
    <h2 style={{
      margin: 0, fontFamily: P.sans, color: P.text,
      fontSize: 64, fontWeight: 500, letterSpacing: "-0.022em", lineHeight: 1.1,
      maxWidth: "26ch",
      opacity: t, transform: `translateY(${(1-t)*16}px)`,
    }}>
      Every finding cites <span style={{ color: P.blue2 }}>verbatim</span> regulatory text.
    </h2>
  );
}
function CitationCard({ framework, clauseRef, title, text, url }) {
  const { localTime } = useSprite();
  const t = Easing.easeOutBack(clamp(localTime / 0.55, 0, 1));
  return (
    <div style={{
      background: P.card, border: `1px solid ${P.border}`,
      borderRadius: 10, padding: "22px 22px 18px",
      opacity: clamp(localTime / 0.4, 0, 1),
      transform: `translateY(${(1-t)*24}px) scale(${0.96 + 0.04*t})`,
      boxShadow: "0 18px 48px rgba(0,0,0,0.4)",
      display: "flex", flexDirection: "column", gap: 12,
      minHeight: 280,
    }}>
      <div style={{ display: "flex", alignItems: "center", gap: 10 }}>
        {Ico.book(16, P.blue2)}
        <span style={{ fontFamily: P.mono, fontSize: 13, color: P.blue2, fontWeight: 600 }}>
          {framework} · {clauseRef}
        </span>
      </div>
      <div style={{ fontFamily: P.sans, fontSize: 18, color: P.text, fontWeight: 500 }}>
        {title}
      </div>
      <blockquote style={{
        margin: 0, padding: "12px 14px",
        background: "#11111a", borderRadius: 6,
        fontFamily: P.sans, fontSize: 15, lineHeight: 1.55,
        color: "#a8b3c9", fontStyle: "italic",
        borderLeft: `2px solid ${P.blue}`, flex: 1,
      }}>
        "{text}"
      </blockquote>
      <div style={{ fontFamily: P.mono, fontSize: 12, color: P.dim, letterSpacing: "0.005em" }}>
        {url}
      </div>
    </div>
  );
}

/* ===================== SCENE 05 — Verdict + remediation ===================== */
function Scene05() {
  return (
    <Sprite start={30.8} end={39.6} keepMounted>
      <FadeLayer entry={0.4} exit={0.5}>
        <Camera from={{ scale: 1.0, x: 0, y: 0 }} to={{ scale: 1.04, x: 0, y: 0 }}>
          <div style={{
            position: "absolute", inset: 0, padding: "110px 180px 0", color: P.text,
            display: "grid", gridTemplateColumns: "1fr 1fr", gap: 48, alignItems: "start",
          }}>
            <div>
              <Sprite start={31.0} end={39.6}>
                {() => (
                  <div style={{
                    fontFamily: P.mono, fontSize: 14, letterSpacing: "0.1em",
                    textTransform: "uppercase", color: "rgba(255,255,255,0.55)",
                    display: "inline-flex", alignItems: "center", gap: 12, marginBottom: 14,
                  }}>
                    <span style={{ width: 24, height: 1, background: "rgba(255,255,255,0.2)" }}/>
                    Verdict · 09:14:13
                  </div>
                )}
              </Sprite>
              <Sprite start={31.1} end={39.6}>
                <Scene05Title />
              </Sprite>
              <Sprite start={31.7} end={39.6}>
                <VerdictBanner />
              </Sprite>
              <Sprite start={32.8} end={39.6}>
                <AuditorBlock />
              </Sprite>
            </div>

            <div>
              <Sprite start={33.3} end={39.6}>
                <FixPRCard />
              </Sprite>
              <Sprite start={34.2} end={39.6}>
                <DiffWindow />
              </Sprite>
            </div>
          </div>
        </Camera>
      </FadeLayer>
    </Sprite>
  );
}
function Scene05Title() {
  const { localTime } = useSprite();
  const t = Easing.easeOutCubic(clamp(localTime / 0.6, 0, 1));
  return (
    <h2 style={{
      margin: 0, fontFamily: P.sans, color: P.text,
      fontSize: 56, fontWeight: 500, letterSpacing: "-0.022em", lineHeight: 1.08,
      opacity: t, transform: `translateY(${(1-t)*14}px)`,
    }}>
      Unsafe.<br/><span style={{ color: P.blue2 }}>Here is the fix.</span>
    </h2>
  );
}
function VerdictBanner() {
  const { localTime } = useSprite();
  const t = Easing.easeOutBack(clamp(localTime / 0.5, 0, 1));
  return (
    <div style={{
      marginTop: 26, padding: "18px 24px", borderRadius: 10,
      background: "rgba(127,29,29,0.18)", border: `1px solid #7f1d1d`,
      color: P.unsafe, display: "inline-flex", alignItems: "center", gap: 14,
      fontFamily: P.mono, fontSize: 22, fontWeight: 700, letterSpacing: "0.04em",
      opacity: clamp(localTime/0.3, 0, 1),
      transform: `scale(${0.94 + 0.06*t})`,
    }}>
      {Ico.shieldX(28, P.unsafe)}
      VERDICT · UNSAFE
    </div>
  );
}
function AuditorBlock() {
  const { localTime } = useSprite();
  const t = Easing.easeOutCubic(clamp(localTime / 0.6, 0, 1));
  return (
    <div style={{
      marginTop: 28, padding: "16px 18px",
      borderLeft: `2px solid ${P.borderHi}`,
      fontFamily: P.mono, fontSize: 15, color: "#a8b3c9",
      lineHeight: 1.6, maxWidth: "44ch",
      opacity: t, transform: `translateX(${(1-t)*-8}px)`,
    }}>
      <span style={{ color: P.warn, fontWeight: 600 }}>[Auditor]</span>{" "}
      Adversarial review: argued the logs sit inside the UK VPC. Argument fails — Art. 32(1) is about <em>appropriate measures</em> regardless of egress. Verdict holds.
    </div>
  );
}
function FixPRCard() {
  const { localTime } = useSprite();
  const t = Easing.easeOutBack(clamp(localTime / 0.5, 0, 1));
  return (
    <div style={{
      background: P.card, border: `1px solid ${P.safe}66`, borderRadius: 10,
      padding: "18px 22px",
      transform: `translateY(${(1-t)*20}px) scale(${0.96 + 0.04*t})`,
      opacity: clamp(localTime/0.3, 0, 1),
    }}>
      <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
        <span style={{ color: P.safe }}>{Ico.pr(22, P.safe)}</span>
        <span style={{ fontFamily: P.mono, fontSize: 16, color: P.safe, fontWeight: 600 }}>#142-fix</span>
        <span style={{
          padding: "3px 9px", borderRadius: 4,
          background: `${P.safe}22`, color: P.safe,
          fontFamily: P.mono, fontSize: 11, fontWeight: 600, letterSpacing: "0.06em",
        }}>OSYNAX DRAFT</span>
      </div>
      <div style={{
        marginTop: 10, fontFamily: P.sans,
        fontSize: 20, color: P.text, fontWeight: 500,
      }}>
        fix(logging): redact patient paths, parameterise search, add rate limit
      </div>
    </div>
  );
}
function DiffWindow() {
  const { localTime } = useSprite();
  const t = Easing.easeOutCubic(clamp(localTime / 0.6, 0, 1));
  const lines = [
    { type: "head",   t: "--- a/internal/logging/middleware.go" },
    { type: "head",   t: "+++ b/internal/logging/middleware.go" },
    { type: "hunk",   t: "@@ -32,7 +32,11 @@" },
    { type: "ctx",    t: "    next.ServeHTTP(w, r)" },
    { type: "remove", t: "-   log.Info(\"http\", \"path\", r.URL.RequestURI(), …)" },
    { type: "add",    t: "+   path := r.URL.Path" },
    { type: "add",    t: "+   if matchPatient(path) {" },
    { type: "add",    t: "+       path = redactPatientPath(path)" },
    { type: "add",    t: "+   }" },
    { type: "add",    t: "+   log.Info(\"http\", \"path\", path, …)" },
  ];
  return (
    <div style={{
      marginTop: 22, background: "#0a0a12", border: `1px solid ${P.border}`,
      borderRadius: 10, padding: "16px 18px",
      fontFamily: P.mono, fontSize: 14, lineHeight: 1.7,
      opacity: t, transform: `translateY(${(1-t)*16}px)`,
      boxShadow: "0 16px 40px rgba(0,0,0,0.4)",
      whiteSpace: "pre",
    }}>
      {lines.map((l, i) => {
        const at = 0.2 + i * 0.12;
        const lt = Easing.easeOutCubic(clamp((localTime - at) / 0.3, 0, 1));
        const col = l.type === "add"    ? P.safe
                  : l.type === "remove" ? P.unsafe
                  : l.type === "hunk"   ? P.blue2
                  : l.type === "head"   ? P.text
                  : "#94a3b8";
        return (
          <div key={i} style={{
            color: col, opacity: lt,
            transform: `translateX(${(1-lt)*-6}px)`,
          }}>{l.t}</div>
        );
      })}
    </div>
  );
}

/* ===================== SCENE 06 — Outro ===================== */
function Scene06() {
  return (
    <Sprite start={39.4} end={46.0} keepMounted>
      <FadeLayer entry={0.5} exit={0.5}>
        <Camera from={{ scale: 0.97, x: 0, y: 12 }} to={{ scale: 1.02, x: 0, y: 0 }}>
          <div style={{
            position: "absolute", inset: 0, padding: "0 220px",
            display: "flex", flexDirection: "column",
            alignItems: "flex-start", justifyContent: "center",
            color: P.ink,
          }}>
            <Sprite start={39.6} end={46.0}>
              {() => (
                <div style={{
                  fontFamily: P.mono, fontSize: 14, letterSpacing: "0.1em",
                  textTransform: "uppercase", color: P.muted2,
                  display: "inline-flex", alignItems: "center", gap: 12, marginBottom: 18,
                }}>
                  <span style={{ width: 24, height: 1, background: P.line }}/>
                  09:14:13 · total elapsed 11.2s
                </div>
              )}
            </Sprite>
            <Sprite start={39.8} end={46.0}>
              <OutroTitle />
            </Sprite>
            <Sprite start={41.4} end={46.0}>
              <OutroCTA />
            </Sprite>
          </div>
        </Camera>
      </FadeLayer>
    </Sprite>
  );
}
function OutroTitle() {
  const { localTime } = useSprite();
  const t1 = Easing.easeOutCubic(clamp(localTime / 0.5, 0, 1));
  const t2 = Easing.easeOutCubic(clamp((localTime - 0.5) / 0.5, 0, 1));
  const t3 = Easing.easeOutCubic(clamp((localTime - 1.0) / 0.5, 0, 1));
  return (
    <h1 style={{
      margin: 0, fontFamily: P.sans,
      fontSize: 92, fontWeight: 500, letterSpacing: "-0.025em",
      lineHeight: 1.05, color: P.ink, maxWidth: "20ch",
    }}>
      <span style={{ opacity: t1, transform: `translateY(${(1-t1)*16}px)`, display: "block" }}>
        Catch the vulnerability.
      </span>
      <span style={{ opacity: t2, transform: `translateY(${(1-t2)*16}px)`, display: "block" }}>
        Cite the regulation.
      </span>
      <span style={{ opacity: t3, transform: `translateY(${(1-t3)*16}px)`, display: "block", color: P.accentInk }}>
        Open the fix.
      </span>
    </h1>
  );
}
function OutroCTA() {
  const { localTime } = useSprite();
  const t = Easing.easeOutCubic(clamp(localTime / 0.5, 0, 1));
  return (
    <div style={{
      marginTop: 48, display: "flex", alignItems: "center", gap: 24,
      fontFamily: P.sans, opacity: t, transform: `translateY(${(1-t)*12}px)`,
    }}>
      <a href="mailto:zach@osynax.ai" style={{
        display: "inline-flex", alignItems: "center", gap: 10,
        height: 56, padding: "0 22px",
        background: P.ink, color: P.light,
        border: `1px solid ${P.ink}`, borderRadius: 8,
        fontSize: 18, fontWeight: 500,
        textDecoration: "none",
      }}>
        Request a pilot
        <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" strokeWidth="1.6"><path d="M3 8h10M9 4l4 4-4 4"/></svg>
      </a>
      <span style={{
        fontFamily: P.mono, fontSize: 16, color: P.muted,
        letterSpacing: "0.005em",
      }}>
        zach@osynax.ai · 3-month pilot · £30k + VAT
      </span>
    </div>
  );
}

/* ===================== ROOT APP ===================== */
function ExplainerApp() {
  return (
    <Stage width={1920} height={1080} duration={46} background={P.light} persistKey="osynax-explainer">
      <Background />
      <Scene01 />
      <Scene02 />
      <Scene03 />
      <Scene04 />
      <Scene05 />
      <Scene06 />
      <TimestampHud />
      <CornerMark />
      <StageLabelSync />
    </Stage>
  );
}

/* Surface a [data-screen-label] for comments at any time. */
function StageLabelSync() {
  const time = useTime();
  useEffect(() => {
    const sec = Math.floor(time);
    const stamp = `${String(Math.floor(sec/60)).padStart(2,"0")}:${String(sec%60).padStart(2,"0")}`;
    document.documentElement.setAttribute("data-screen-label", `Explainer · ${stamp}`);
  }, [Math.floor(time)]);
  return null;
}

window.ExplainerApp = ExplainerApp;
