// Coframe Interview Playback · embedded interactive
// Adapted from uploads/coframe_interview_playback_experience_v2.jsx for AGI Intelligence Report v8.
// Changes from source:
//   - ES module imports → React globals + inline shims (no framer-motion / lucide-react CDNs)
//   - <motion.X animate={...}> → plain <X> via a tiny Proxy shim that strips motion props
//   - <AnimatePresence> → fragment (no exit animations)
//   - lucide-react icons → inline SVG icon components matching the lucide API (size, className)
//   - StyleGuide component removed (it injected body{} and clobbered the report's :root vars)
//   - AUDIO_SRC blanked so the audio element fails to load — the existing timer fallback then
//     drives playback simulation course-by-course (no external m4a dependency)
//   - Tailwind utility classes preserved; loaded via the play CDN with preflight disabled.
//     Editorial overrides live in the parent stylesheet under .coframe-mount.

(function () {

const { useEffect, useMemo, useRef, useState } = React;

// ---- framer-motion shim ---------------------------------------------------
// motion.div / motion.svg / motion.circle ... → plain element. animate / initial /
// exit / transition / whileHover / whileTap are filtered out; everything else passes
// through unchanged.
const MOTION_PROPS = new Set(['animate','initial','exit','transition','whileHover','whileTap','whileInView','layout','layoutId','variants','custom','drag','dragConstraints']);
function stripMotionProps(props) {
  const out = {};
  for (const k in props) {
    if (!MOTION_PROPS.has(k)) out[k] = props[k];
  }
  return out;
}
const motion = new Proxy({}, {
  get(_, tag) {
    return React.forwardRef(function MotionShim(props, ref) {
      return React.createElement(tag, Object.assign({ ref }, stripMotionProps(props)), props.children);
    });
  }
});
const AnimatePresence = ({ children }) => React.createElement(React.Fragment, null, children);

// ---- lucide-react icon stubs ---------------------------------------------
// Match the lucide API: <Icon size={N} className="..." />. SVGs are drawn fresh,
// not lifted from lucide; the stroke geometry just needs to read at small sizes.
function makeIcon(viewBox, paths) {
  return function Icon({ size = 18, className = '', ...rest }) {
    return React.createElement('svg', Object.assign({
      width: size, height: size, viewBox: viewBox,
      fill: 'none', stroke: 'currentColor', strokeWidth: 1.8,
      strokeLinecap: 'round', strokeLinejoin: 'round', className
    }, rest), paths);
  };
}
const Bot = makeIcon('0 0 24 24', [
  React.createElement('rect', { key: 'b1', x: 4, y: 8,  width: 16, height: 12, rx: 2 }),
  React.createElement('circle',{ key: 'b2', cx: 9,  cy: 14, r: 1 }),
  React.createElement('circle',{ key: 'b3', cx: 15, cy: 14, r: 1 }),
  React.createElement('path',  { key: 'b4', d: 'M12 4v4' }),
  React.createElement('circle',{ key: 'b5', cx: 12, cy: 3,  r: 1 }),
  React.createElement('path',  { key: 'b6', d: 'M4 14h-2 M20 14h2' })
]);
const Pause = makeIcon('0 0 24 24', [
  React.createElement('rect', { key:'p1', x: 6,  y: 5, width: 4, height: 14, rx: 0.5 }),
  React.createElement('rect', { key:'p2', x: 14, y: 5, width: 4, height: 14, rx: 0.5 })
]);
const Play = makeIcon('0 0 24 24', [
  React.createElement('path', { key:'pl', d: 'M7 5l12 7-12 7z', fill: 'currentColor' })
]);
const Quote = makeIcon('0 0 24 24', [
  React.createElement('path', { key:'q', d: 'M7 7h4v6c0 2-2 4-4 4 M14 7h4v6c0 2-2 4-4 4' })
]);
const Radio = makeIcon('0 0 24 24', [
  React.createElement('circle',{ key:'r1', cx:12, cy:12, r:2 }),
  React.createElement('path', { key:'r2', d: 'M4.93 19.07a10 10 0 010-14.14 M19.07 4.93a10 10 0 010 14.14 M7.76 16.24a6 6 0 010-8.48 M16.24 7.76a6 6 0 010 8.48' })
]);
const Search = makeIcon('0 0 24 24', [
  React.createElement('circle',{ key:'s1', cx:11, cy:11, r:7 }),
  React.createElement('path', { key:'s2', d: 'M21 21l-4-4' })
]);
const Send = makeIcon('0 0 24 24', [
  React.createElement('path', { key:'snd', d: 'M22 2L11 13 M22 2l-7 20-4-9-9-4z' })
]);
const SkipBack = makeIcon('0 0 24 24', [
  React.createElement('path', { key:'sb1', d: 'M19 20L9 12l10-8z', fill: 'currentColor' }),
  React.createElement('rect',{ key:'sb2', x:5, y:5, width: 2, height: 14, fill:'currentColor', stroke:'none' })
]);
const SkipForward = makeIcon('0 0 24 24', [
  React.createElement('path', { key:'sf1', d: 'M5 4l10 8-10 8z', fill: 'currentColor' }),
  React.createElement('rect',{ key:'sf2', x:17, y:5, width: 2, height: 14, fill:'currentColor', stroke:'none' })
]);
const ScrollText = makeIcon('0 0 24 24', [
  React.createElement('path', { key:'st1', d: 'M8 21h12a2 2 0 002-2v-1a2 2 0 00-2-2H6 M4 3h12 M4 3v14a4 4 0 004 4 M9 7h7 M9 11h7' })
]);

const AUDIO_SRC = "";

const transcriptLog = [
  { id: 0, speaker: "Kat", time: "00:00", topicId: "origin", text: "Hey Josh, we're honored to have you here today. You're a serial entrepreneur with amazing stories. Can you tell us a little bit about your inspiration for Coframe and how it got started?" },
  { id: 1, speaker: "Josh", time: "00:21", topicId: "origin", text: "Coframe started out as this more abstract idea that the internet in the future, instead of being static and unadaptive, is going to have its own sense of life and intelligence. We call this idea living interfaces." },
  { id: 2, speaker: "Josh", time: "01:20", topicId: "origin", text: "The origin story actually starts here at the AGI House. We were hosting a dinner, and at these dinners you have incredible people who are luminaries in the space. The discussions always create and spark new ideas." },
  { id: 3, speaker: "Josh", time: "02:15", topicId: "origin", text: "The thing that struck me most was that the internet would look nothing like it does today. It looks a lot more like interfacing with a living being — almost like Jarvis in Iron Man." },
  { id: 4, speaker: "Kat", time: "04:10", topicId: "acceleration", text: "What were you seeing in 2023 that made you feel like the Internet of Agents was not only a possibility, but a likely potentiality?" },
  { id: 5, speaker: "Josh", time: "04:35", topicId: "acceleration", text: "Something I've had to learn is adapting to the pace of acceleration we're experiencing right now. Not just acceleration, but the pace at which that accelerates — the second derivative. All of the predictions people make tend to undershoot." },
  { id: 6, speaker: "Kat", time: "08:35", topicId: "hci", text: "Before Coframe, you were at the GSB. You founded the Stanford COVID Response Lab and then Access built an autograph. How did those experiences come together for your insights?" },
  { id: 7, speaker: "Josh", time: "09:12", topicId: "hci", text: "The common thread for me is how people interact with the digital world. Before I started doing business building, my research was primarily at the intersection of AI and human-computer interaction." },
  { id: 8, speaker: "Kat", time: "14:20", topicId: "agents", text: "One framework around agent experience is Matt Billman's four pillars of access, context, tools, and orchestration. Where does Coframe shine and where has the technology been most challenging?" },
  { id: 9, speaker: "Josh", time: "15:06", topicId: "agents", text: "The two pillars we focus a lot on are context and tools. On the context side, we're pulling in the business's context. Tools are also very important because agents have goals, and that's why they exist." },
  { id: 10, speaker: "Kat", time: "20:45", topicId: "evals", text: "How does your team think about designing agent evaluation frameworks so that the human stays in the loop at the right time?" },
  { id: 11, speaker: "Josh", time: "21:22", topicId: "evals", text: "This part of the process is actually a lot more like trading — quant trading. Alpha is a signal that lets you outperform beta. In this case, beta could be thought of as the original user experience." },
  { id: 12, speaker: "Josh", time: "26:50", topicId: "approval", text: "After it's gone through the idea and the variation, then it gets to the human. That part is still pretty manual. But we're able to take that feedback and feed it back into our system." },
  { id: 13, speaker: "Kat", time: "33:40", topicId: "multimodal", text: "There's been a lot of buzz around multimodal optimizations. How has Coframe thought about these challenges and kept ahead of the curve?" },
  { id: 14, speaker: "Josh", time: "34:15", topicId: "multimodal", text: "Coframe is a multimodal native company. The lesson was that you don't want to be in the warpath of these big model companies. You want to build stuff that benefits from that advancement." },
  { id: 15, speaker: "Kat", time: "41:15", topicId: "superhuman", text: "What surprised you about how the model approached these problems? Can you tell us one of those war stories?" },
  { id: 16, speaker: "Josh", time: "42:10", topicId: "superhuman", text: "The reason we're able to get such large wins is because there are brand new types of strategies that weren't feasible before. One strategy is being able to throw massive swings at the problem — dozens of completely brand new pages instead of smaller changes." },
  { id: 17, speaker: "Josh", time: "47:40", topicId: "superhuman", text: "There's augmentation, and then there's completely different category type of strategies. What we're seeing, the 10x returns are on the second bucket, which are superhuman capabilities." },
  { id: 18, speaker: "Kat", time: "49:00", topicId: "haystacks", text: "You had the recent Haystacks AI acquisition. What capabilities are you most looking forward to with this new combined team?" },
  { id: 19, speaker: "Josh", time: "49:35", topicId: "haystacks", text: "Every function in the company has the opportunity to be automated and level up at the same time. The Haystacks acquisition is primarily focused on the go-to-market function." },
  { id: 20, speaker: "Kat", time: "50:40", topicId: "haystacks", text: "So the acquisition is not just about adding a sales tool, but expanding the pattern of self-improvement into another company function?" },
  { id: 21, speaker: "Josh", time: "51:05", topicId: "haystacks", text: "Exactly. The same underlying idea applies: take a function that has signals, workflows, and feedback loops, and build a system that can continuously improve how that function performs." },
  { id: 22, speaker: "Kat", time: "52:15", topicId: "superhuman", text: "That seems connected to your earlier point about AI not only speeding up human work, but making new categories of strategy possible." },
  { id: 23, speaker: "Josh", time: "52:44", topicId: "superhuman", text: "That's the part I think is most important. The bigger opportunity is when the system can explore a search space that a human team would not have the time or bandwidth to explore manually." },
  { id: 24, speaker: "Kat", time: "54:10", topicId: "approval", text: "Where do you think the human role becomes most important as these systems become more autonomous?" },
  { id: 25, speaker: "Josh", time: "54:38", topicId: "approval", text: "Humans are still critical for judgment, brand, taste, and deciding which risks are acceptable. The machine can generate and test, but the organization still needs to know what it stands for." },
  { id: 26, speaker: "Kat", time: "56:00", topicId: "multimodal", text: "As models get better at visual reasoning and interface generation, does the product become more about orchestration than generation itself?" },
  { id: 27, speaker: "Josh", time: "56:31", topicId: "multimodal", text: "That is how we think about it. The model layer keeps improving, so the durable work is building the system around it: context, tooling, evaluation, workflow, and trust." },
  { id: 28, speaker: "Kat", time: "58:05", topicId: "agents", text: "When you talk about context, are you thinking mostly about customer data, business goals, brand constraints, or something broader than that?" },
  { id: 29, speaker: "Josh", time: "58:36", topicId: "agents", text: "It is broader. Context is the business, the customer, the product surface, the conversion goal, the brand boundaries, and the historical feedback from what has worked before." },
  { id: 30, speaker: "Kat", time: "59:50", topicId: "evals", text: "So the evaluation layer becomes the memory of the organization, not just a report on whether one experiment won." },
  { id: 31, speaker: "Josh", time: "60:14", topicId: "evals", text: "Exactly. The point is not only to know what won. The point is to compound learning so the next generation of ideas starts from a better place." },
  { id: 32, speaker: "Kat", time: "61:30", topicId: "hci", text: "That makes the interface feel less like a screen and more like a relationship between the company, the customer, and the system interpreting both." },
  { id: 33, speaker: "Josh", time: "61:58", topicId: "hci", text: "That is a good way to put it. The interface becomes the meeting point where business intent and user behavior can actually adapt to each other." },
  { id: 34, speaker: "Kat", time: "63:05", topicId: "approval", text: "What do you think teams misunderstand about keeping humans in the loop?" },
  { id: 35, speaker: "Josh", time: "63:30", topicId: "approval", text: "They often think human review means slowing the system down. But the better framing is that human review gives the system a stronger taste function and safer boundaries." },
  { id: 36, speaker: "Kat", time: "64:42", topicId: "superhuman", text: "It sounds like the real strategic advantage is knowing when to trust the system to explore and when to ask people to judge." },
  { id: 37, speaker: "Josh", time: "65:10", topicId: "superhuman", text: "Yes. Exploration is where AI can be superhuman. Judgment is where the organization has to decide what it actually wants to become." },
  { id: 38, speaker: "Kat", time: "66:20", topicId: "haystacks", text: "What should people watch for next from Coframe after the Haystacks acquisition?" },
  { id: 39, speaker: "Josh", time: "66:48", topicId: "haystacks", text: "I would watch for more company functions becoming adaptive. Growth is the starting point, but the broader pattern is that every workflow with feedback can become more intelligent over time." },
];

const moodStyles = {
  visionary: { accent: "#4267D9", glow: "rgba(66,103,217,.30)", glow2: "rgba(117,83,201,.16)", wash: "rgba(66,103,217,.08)", node2: "rgba(117,83,201,.85)" },
  urgent: { accent: "#3D77A8", glow: "rgba(61,119,168,.28)", glow2: "rgba(176,110,36,.13)", wash: "rgba(61,119,168,.08)", node2: "rgba(176,110,36,.72)" },
  reflective: { accent: "#7255C9", glow: "rgba(114,85,201,.24)", glow2: "rgba(236,239,243,.10)", wash: "rgba(114,85,201,.07)", node2: "rgba(236,239,243,.78)" },
  systemic: { accent: "#15958B", glow: "rgba(21,149,139,.28)", glow2: "rgba(66,103,217,.12)", wash: "rgba(21,149,139,.08)", node2: "rgba(66,103,217,.75)" },
  analytical: { accent: "#B06E24", glow: "rgba(176,110,36,.28)", glow2: "rgba(66,103,217,.10)", wash: "rgba(176,110,36,.08)", node2: "rgba(66,103,217,.64)" },
  cautious: { accent: "#B94C61", glow: "rgba(185,76,97,.25)", glow2: "rgba(236,239,243,.09)", wash: "rgba(185,76,97,.07)", node2: "rgba(236,239,243,.74)" },
  curious: { accent: "#3D77A8", glow: "rgba(61,119,168,.24)", glow2: "rgba(66,103,217,.13)", wash: "rgba(61,119,168,.07)", node2: "rgba(66,103,217,.78)" },
  electric: { accent: "#7553C9", glow: "rgba(117,83,201,.34)", glow2: "rgba(66,103,217,.18)", wash: "rgba(117,83,201,.10)", node2: "rgba(66,103,217,.86)" },
  forward: { accent: "#475160", glow: "rgba(71,81,96,.25)", glow2: "rgba(21,149,139,.14)", wash: "rgba(71,81,96,.08)", node2: "rgba(21,149,139,.75)" },
};

const topics = [
  { id: "origin", title: "Living Interfaces", mood: "visionary", summary: "Josh explains Coframe as a bet on adaptive, self-improving interfaces rather than static websites.", quoteLineId: 1, visual: "network" },
  { id: "acceleration", title: "The Second Derivative", mood: "urgent", summary: "The conversation turns to AI acceleration and why forecasts consistently underestimate the pace of change.", quoteLineId: 5, visual: "curve" },
  { id: "hci", title: "AI × Human-Computer Interaction", mood: "reflective", summary: "Josh connects his founder journey to a core interest in how humans interact with the digital world.", quoteLineId: 7, visual: "network" },
  { id: "agents", title: "Agents Need Context + Tools", mood: "systemic", summary: "Coframe’s product logic is framed around giving agents the context and tools needed to act effectively.", quoteLineId: 9, visual: "flow" },
  { id: "evals", title: "Growth as Quant Trading", mood: "analytical", summary: "Evaluation is described like quant trading: experiments are signals that compete against the baseline experience.", quoteLineId: 11, visual: "curve" },
  { id: "approval", title: "Human-in-the-Loop Approval", mood: "cautious", summary: "Automation generates and tests variations, but human review remains essential before decisions are finalized.", quoteLineId: 12, visual: "approval" },
  { id: "multimodal", title: "Multimodal Native", mood: "curious", summary: "Josh positions Coframe as building on top of model progress rather than competing directly with foundation model labs.", quoteLineId: 14, visual: "flow" },
  { id: "superhuman", title: "Superhuman Strategy", mood: "electric", summary: "The strongest claim is that AI unlocks strategies humans would never attempt manually, creating the biggest upside.", quoteLineId: 17, visual: "burst" },
  { id: "haystacks", title: "Go-to-Market Agents", mood: "forward", summary: "The Haystacks acquisition broadens the automation story into go-to-market and internal workflow acceleration.", quoteLineId: 19, visual: "pipeline" },
];

function getMood(topic) { return moodStyles[topic?.mood] || moodStyles.visionary; }
function findTopic(topicId) { return topics.find((t) => t.id === topicId) || topics[0]; }
function timeToSeconds(time) {
  const parts = time.split(":").map((value) => Number(value));
  if (parts.length !== 2 || parts.some((value) => Number.isNaN(value))) return 0;
  return parts[0] * 60 + parts[1];
}
function formatSeconds(value) {
  if (!Number.isFinite(value)) return "00:00";
  const minutes = Math.floor(value / 60);
  const seconds = Math.floor(value % 60);
  return `${String(minutes).padStart(2, "0")}:${String(seconds).padStart(2, "0")}`;
}
function clampIndex(index) {
  if (!Number.isFinite(index)) return 0;
  return Math.max(0, Math.min(transcriptLog.length - 1, index));
}

function activeIndexForTime(seconds) {
  let index = 0;
  for (let i = 0; i < transcriptLog.length; i += 1) {
    if (timeToSeconds(transcriptLog[i].time) <= seconds) index = i;
  }
  return clampIndex(index);
}

function sourceOnlyAnswer(question, turn = 0) {
  const q = question.trim().toLowerCase();
  if (!q) return "Raise your hand to ask a question";

  const stopWords = new Set(["what", "when", "where", "which", "why", "how", "does", "about", "that", "this", "with", "from", "into", "your", "their", "there", "would", "could", "should", "think", "tell", "more", "question"]);
  const clean = q.replaceAll("?", " ").replaceAll(",", " ").replaceAll(".", " ").replaceAll("—", " ").replaceAll(":", " ").replaceAll(";", " ").replaceAll("!", " ");
  const words = clean.split(" ").map((w) => w.trim()).filter((w) => w.length > 2 && !stopWords.has(w));

  const topicScores = topics.map((topic) => {
    const relatedLines = transcriptLog.filter((line) => line.topicId === topic.id);
    const haystack = [topic.title, topic.summary, ...relatedLines.map((line) => line.text)].join(" ").toLowerCase();
    const keywordScore = words.reduce((acc, word) => acc + (haystack.includes(word) ? 1 : 0), 0);
    const directBoost = q.includes(topic.id) || topic.title.toLowerCase().split(" ").some((part) => part.length > 4 && q.includes(part)) ? 2 : 0;
    return { topic, score: keywordScore + directBoost };
  }).sort((a, b) => b.score - a.score);

  const selectedTopic = topicScores[0]?.score > 0 ? topicScores[0].topic : topics[turn % topics.length];
  const related = transcriptLog.filter((line) => line.topicId === selectedTopic.id);
  const quoteCandidates = related.length ? related : transcriptLog;
  const quoteLine = quoteCandidates[turn % quoteCandidates.length];
  const secondLine = quoteCandidates[(turn + 2) % quoteCandidates.length];

  if (q.includes("technical") || q.includes("eval") || q.includes("layer") || q.includes("design")) {
    return `I’d explain it simply this way: the eval layer is the part of the system that helps the company learn what actually works. It should remember the goal, compare new ideas against the old experience, collect human feedback, and feed that learning back into the next round. Josh gets at this when he says, “${quoteLine.text}” I’d keep the technical version practical: inputs, candidate ideas, tests, human review, and a learning memory.`;
  }

  if (q.includes("thesis") || q.includes("main idea") || q.includes("big idea")) {
    return `The big idea is that websites are starting to become more alive. Instead of being fixed pages, they can learn from signals, try new versions, and improve over time. Josh calls this “living interfaces,” and the clearest line is: “${quoteLine.text}”`;
  }

  if (q.includes("matter") || q.includes("important")) {
    return `It matters because this changes the role of a website or interface. It’s no longer just a place where people click around. It becomes a system that can understand context, test ideas, and get better with feedback. That’s why the human judgment part still matters too — someone has to decide what the company actually wants to stand for.`;
  }

  if (q.includes("agent")) {
    return `Josh is saying agents need more than a chat box. They need context so they understand the situation, and tools so they can actually do something useful. The friendly version is: an agent is only as helpful as what it knows and what it’s allowed to act on. One supporting moment is: “${quoteLine.text}”`;
  }

  if (q.includes("human") || q.includes("approval") || q.includes("trust") || q.includes("review")) {
    return `The human role is still really important. AI can explore, generate, and test, but people still bring taste, judgment, brand sense, and boundaries. I hear Josh’s point as: let the machine search widely, but let humans decide what is actually right for the company.`;
  }

  if (q.includes("superhuman") || q.includes("10x") || q.includes("strategy")) {
    return `The superhuman part is not just doing human work faster. It’s using AI to explore options a human team would never have time to try manually. Josh describes this through larger experimental swings — not just tiny tweaks, but whole new directions.`;
  }

  return `I’d connect your question to ${selectedTopic.title}. The simple read is: ${selectedTopic.summary} Josh says, “${quoteLine.text}” Another useful moment is, “${secondLine.text}”`;
}

function StyleGuide() { return null; }

function LogoMark() {
  return <div className="grid h-7 w-7 place-items-center border border-[var(--ink)] text-[var(--ink)]"><svg viewBox="0 0 32 32" className="h-5 w-5" fill="none"><rect x="6" y="6" width="20" height="20" stroke="currentColor" strokeWidth="1.5" /><rect x="11" y="11" width="10" height="10" stroke="currentColor" strokeWidth="1.5" /><circle cx="16" cy="16" r="1.5" fill="currentColor" /></svg></div>;
}

function Waveform({ playing, activeIndex, topic, currentTime = 0 }) {
  const mood = getMood(topic);
  const segmentEnergy = {
    origin: 0.62,
    acceleration: 0.78,
    hci: 0.5,
    agents: 0.72,
    evals: 0.86,
    approval: 0.58,
    multimodal: 0.68,
    superhuman: 0.94,
    haystacks: 0.74,
  }[topic.id] || 0.65;
  const speakerPulse = transcriptLog[activeIndex]?.speaker === "Josh" ? 1.08 : 0.92;
  const bars = Array.from({ length: 168 }, (_, i) => {
    const broadWave = Math.abs(Math.sin((i + activeIndex * 5) * 0.12));
    const midWave = Math.abs(Math.sin((i + currentTime * 2.1) * 0.43));
    const fineWave = Math.abs(Math.sin((i * 1.73 + currentTime * 5.2)));
    const phraseSpike = i % 17 === (activeIndex * 3) % 17 ? 1.28 : 1;
    return Math.max(6, (8 + broadWave * 18 + midWave * 20 + fineWave * 10) * segmentEnergy * speakerPulse * phraseSpike);
  });
  return <div className="relative flex h-14 w-full items-center gap-[2px] overflow-hidden bg-[#070A0E] p-2"><div className="absolute inset-0 opacity-45" style={{ background: `linear-gradient(90deg, transparent 0%, ${mood.wash} 35%, ${mood.glow} 58%, transparent 100%)` }} /><div className="absolute inset-x-2 top-1/2 h-px bg-white/10" />{bars.map((h, i) => <motion.div key={i} className="relative z-10 min-w-[1px] flex-1 rounded-full" style={{ background: i % 8 === 0 ? mood.accent : i % 13 === 0 ? mood.node2 : "rgba(236,239,243,.74)" }} animate={{ height: playing ? [h * 0.58, h, h * 0.72] : h * 0.52, opacity: playing ? [0.36, 0.95, 0.5] : 0.3 }} transition={{ duration: 0.42 + (i % 6) * 0.045, repeat: playing ? Infinity : 0, ease: "easeInOut" }} />)}</div>;
}

// 2.5D illustrated avatar rig — layered hair / face / jacket / flower with
// parallax rotation, blinking eyes, and a lip-sync mouth. Inspired by the
// uploaded "illustrated 2.5D avatar rig" motion brief.
function Avatar({ person, activeSpeaker, playing }) {
  const isActive = person === activeSpeaker;
  const isJosh = person === "Josh";
  const accent = isJosh ? "#3D77A8" : "#7553C9";
  const bg = isJosh
    ? "radial-gradient(circle at 50% 22%, rgba(255,255,255,.30), transparent 38%), linear-gradient(145deg, #E8EDF3, #B8C3D2 52%, #8FA0B7)"
    : "radial-gradient(circle at 50% 22%, rgba(255,255,255,.34), transparent 36%), linear-gradient(145deg, #F2E9DE, #D7C6B8 55%, #BFA895)";
  const skin = "#E5B69B";
  const hair = "#151214";
  const jacketBg = isJosh ? "#2A5076" : "#DDE0D7";
  const lapelTone = isJosh ? "#16314C" : "#2C2A27";

  return (
    <motion.div
      className={`relative flex flex-col items-center ${isActive ? "z-30" : "z-20 opacity-80"}`}
      animate={{ y: isActive && playing ? [0, -6, 0] : 0, scale: isActive ? 1.045 : 0.92 }}
      transition={{ duration: 1.35, repeat: isActive && playing ? Infinity : 0, ease: "easeInOut" }}
      style={{ perspective: 900 }}
    >
      <div className={`mb-1 rounded-md px-2 py-1 font-mono text-[10px] uppercase tracking-[0.18em] shadow-sm ${isActive ? "bg-[var(--paper)] text-[var(--ink)]" : "bg-white/[.10] text-white/[.65]"}`}>
        {person}
      </div>

      <motion.div
        className="relative h-44 w-36 overflow-hidden rounded-[1.4rem] border border-white/30 shadow-[0_24px_55px_rgba(0,0,0,.42)] md:h-52 md:w-40"
        style={{ background: bg, transformStyle: "preserve-3d" }}
        animate={{ rotateY: isActive && playing ? [-5, 6, -4] : -3, rotateX: isActive && playing ? [1, -2, 1] : 0 }}
        transition={{ duration: 2.4, repeat: isActive && playing ? Infinity : 0, ease: "easeInOut" }}
      >
        {/* gloss + soft signal field */}
        <div className="absolute inset-0 bg-gradient-to-br from-white/[.22] via-transparent to-black/[.24]" />
        <div className="pointer-events-none absolute inset-0">
          {Array.from({ length: 8 }).map((_, i) => (
            <motion.div
              key={i}
              className="absolute rounded-full"
              style={{
                width: 3 + (i % 3),
                height: 3 + (i % 3),
                left: `${10 + ((i * 23) % 80)}%`,
                top: `${6 + ((i * 31) % 78)}%`,
                background: i % 3 === 0 ? accent : "rgba(255,255,255,.6)",
              }}
              animate={{ opacity: isActive && playing ? [0.12, 0.55, 0.12] : 0.16, scale: isActive && playing ? [0.7, 1.3, 0.7] : 0.85 }}
              transition={{ duration: 2.4 + (i % 4) * 0.3, delay: i * 0.08, repeat: isActive && playing ? Infinity : 0 }}
            />
          ))}
        </div>

        {/* hair back */}
        <motion.div
          className="absolute left-1/2 -translate-x-1/2"
          style={{
            top: "10%",
            height: "56%",
            width: isJosh ? "56%" : "64%",
            borderRadius: "44% 44% 45% 45%",
            background: hair,
            transform: "translateZ(20px)",
            boxShadow: "0 10px 22px rgba(20,10,8,.32)",
          }}
          animate={{ x: isActive && playing ? [-2, 2, -2] : 0, rotate: isActive && playing ? [-0.6, 0.6, -0.6] : 0 }}
          transition={{ duration: 2.1, repeat: isActive && playing ? Infinity : 0, ease: "easeInOut" }}
        />

        {/* face */}
        <motion.div
          className="absolute left-1/2 -translate-x-1/2"
          style={{
            top: "13%",
            height: "37%",
            width: "44%",
            borderRadius: "48%",
            background: skin,
            boxShadow: "inset -10px -12px 14px rgba(99,48,39,.16), inset 8px 8px 12px rgba(255,255,255,.42), 0 12px 18px rgba(93,54,41,.22)",
            transform: "translateZ(60px)",
          }}
          animate={{ rotate: isActive && playing ? [-1.4, 1.6, -1.2] : 0, y: isActive && playing ? [0, -1.5, 0] : 0 }}
          transition={{ duration: 1.25, repeat: isActive && playing ? Infinity : 0, ease: "easeInOut" }}
        >
          {/* bangs */}
          <div
            className="absolute"
            style={{
              top: "-14%",
              left: isJosh ? "-4%" : "-16%",
              height: isJosh ? "44%" : "50%",
              width: isJosh ? "112%" : "132%",
              borderRadius: isJosh ? "52% 62% 22% 22%" : "48% 55% 35% 40%",
              background: hair,
              boxShadow: "0 4px 8px rgba(0,0,0,.20)",
            }}
          />
          {/* brows */}
          <div className="absolute" style={{ left: "16%", top: "38%", height: "4%", width: "22%", borderRadius: 999, background: "#3A2222", opacity: 0.75 }} />
          <div className="absolute" style={{ right: "16%", top: "38%", height: "4%", width: "22%", borderRadius: 999, background: "#3A2222", opacity: 0.75 }} />
          {/* eyes */}
          <motion.div className="absolute rounded-full" style={{ left: "21%", top: "46%", height: "9%", width: "11%", background: "#221719" }} animate={{ scaleY: isActive && playing ? [1, 0.22, 1] : 1 }} transition={{ duration: 2.35, repeat: isActive && playing ? Infinity : 0 }} />
          <motion.div className="absolute rounded-full" style={{ right: "21%", top: "46%", height: "9%", width: "11%", background: "#221719" }} animate={{ scaleY: isActive && playing ? [1, 0.22, 1] : 1 }} transition={{ duration: 2.35, delay: 0.12, repeat: isActive && playing ? Infinity : 0 }} />
          {/* nose */}
          <div className="absolute left-1/2 -translate-x-1/2 rounded-full" style={{ top: "57%", height: "9%", width: "3%", background: "rgba(0,0,0,.18)" }} />
          {/* mouth (lip-sync) */}
          <motion.div
            className="absolute left-1/2 -translate-x-1/2 rounded-full"
            style={{ top: "72%", background: "#9A4E49", boxShadow: "inset 0 -1.5px 2px rgba(54,18,17,.22)" }}
            animate={{
              width: isActive && playing ? [18, 30, 22, 26, 18] : 20,
              height: isActive && playing ? [4, 9, 5, 8, 4] : 4,
            }}
            transition={{ duration: 0.65, repeat: isActive && playing ? Infinity : 0, ease: "easeInOut" }}
          />
        </motion.div>

        {/* jacket / shoulders */}
        <motion.div
          className="absolute left-1/2 bottom-0 -translate-x-1/2"
          style={{
            height: "42%",
            width: "86%",
            borderTopLeftRadius: "3rem",
            borderTopRightRadius: "3rem",
            background: jacketBg,
            boxShadow: isJosh
              ? "inset -22px -16px 22px rgba(11,30,60,.30), inset 14px 8px 14px rgba(255,255,255,.16)"
              : "inset -22px -14px 20px rgba(106,116,104,.18), inset 14px 8px 14px rgba(255,255,255,.32)",
            transform: "translateZ(20px)",
          }}
          animate={{ y: isActive && playing ? [0, 1.5, 0] : 0 }}
          transition={{ duration: 1.55, repeat: isActive && playing ? Infinity : 0, ease: "easeInOut" }}
        />

        {/* tie / inner lapel cutout */}
        <div
          className="absolute left-1/2 bottom-0 -translate-x-1/2"
          style={{
            height: "28%",
            width: "28%",
            background: lapelTone,
            clipPath: "polygon(24% 0, 76% 0, 58% 100%, 42% 100%)",
            transform: "translateZ(48px)",
          }}
        />

        {/* flower (Kat only) */}
        {!isJosh && (
          <motion.div
            className="absolute"
            style={{
              right: "18%",
              bottom: "34%",
              height: "20%",
              width: "20%",
              borderRadius: "50%",
              background: "#D9DCD2",
              boxShadow: "inset -6px -7px 9px rgba(87,89,80,.22), 0 8px 14px rgba(70,65,58,.18)",
              transform: "translateZ(70px)",
            }}
            animate={{ rotate: isActive && playing ? [-4, 5, -4] : 0, scale: isActive && playing ? [1, 1.06, 1] : 1 }}
            transition={{ duration: 1.22, repeat: isActive && playing ? Infinity : 0, ease: "easeInOut" }}
          >
            {Array.from({ length: 8 }).map((_, i) => (
              <div
                key={i}
                className="absolute left-1/2 top-1/2 rounded-full"
                style={{
                  height: "55%",
                  width: "30%",
                  background: "#C7CBC1",
                  transformOrigin: "left center",
                  transform: `translate(0, -50%) rotate(${i * 45}deg)`,
                }}
              />
            ))}
            <div
              className="absolute left-1/2 top-1/2 rounded-full"
              style={{
                height: "36%",
                width: "36%",
                marginLeft: "-18%",
                marginTop: "-18%",
                background: "#EEF0EA",
                boxShadow: "inset -2px -2px 4px rgba(90,90,82,.18)",
              }}
            />
          </motion.div>
        )}

        {/* active aura */}
        {isActive && playing && (
          <motion.div
            className="pointer-events-none absolute inset-1 rounded-[1.2rem] border"
            style={{ borderColor: "rgba(255,255,255,.55)", boxShadow: `0 0 26px ${accent}aa, inset 0 0 18px rgba(255,255,255,.18)` }}
            animate={{ opacity: [0.18, 0.6, 0.18], scale: [0.985, 1.02, 0.985] }}
            transition={{ duration: 1.5, repeat: Infinity, ease: "easeInOut" }}
          />
        )}

        {/* live dot */}
        {isActive && (
          <motion.div
            className="absolute bottom-2 right-2 z-10 h-3 w-3 rounded-full bg-[#9EE7DD] shadow-[0_0_18px_rgba(158,231,221,.95)]"
            animate={{ scale: [0.85, 1.25, 0.85], opacity: [0.55, 1, 0.55] }}
            transition={{ duration: 0.9, repeat: Infinity }}
          />
        )}
      </motion.div>

      {/* ground shadow */}
      <div className="pointer-events-none absolute -bottom-2 left-1/2 h-5 w-[68%] -translate-x-1/2 rounded-full bg-black/45 blur-md" />
    </motion.div>
  );
}

function BroadcastVisual({ topic, playing }) {
  const mood = getMood(topic);
  const dots = useMemo(() => Array.from({ length: 26 }, (_, i) => ({ x: 7 + ((i * 37) % 86), y: 10 + ((i * 29) % 76), s: 2.5 + ((i * 7) % 7) })), [topic.id]);
  if (topic.visual === "curve") return <svg viewBox="0 0 600 230" className="h-full w-full"><defs><pattern id={`grid-${topic.id}`} width="28" height="28" patternUnits="userSpaceOnUse"><path d="M 28 0 L 0 0 0 28" fill="none" stroke="rgba(236,239,243,.075)" strokeWidth="1" /></pattern></defs><rect width="600" height="230" fill={`url(#grid-${topic.id})`} /><motion.path d="M24 188 C 105 185, 120 152, 174 145 S 254 112, 314 95 S 390 58, 462 45 S 536 34, 576 22" fill="none" stroke="rgba(236,239,243,.82)" strokeWidth="4" strokeLinecap="square" animate={{ pathLength: playing ? [0.35, 1, 0.75] : 0.85 }} transition={{ duration: 4, repeat: playing ? Infinity : 0 }} />{[65, 148, 238, 330, 438, 540].map((x, i) => <motion.rect key={x} x={x - 5} y={188 - i * 29 - 5} width="10" height="10" fill={i % 2 ? mood.node2 : mood.accent} animate={{ scale: playing ? [1, 1.6, 1] : 1, opacity: playing ? [0.45, 1, 0.65] : 0.75 }} transition={{ duration: 1.7, delay: i * 0.2, repeat: playing ? Infinity : 0 }} />)}</svg>;
  if (topic.visual === "approval") return <div className="grid h-full grid-cols-3 items-center gap-3">{[0, 1, 2].map((i) => <motion.div key={i} className="h-24 border bg-white/[.055]" style={{ borderColor: i === 1 ? mood.accent : "rgba(236,239,243,.18)" }} animate={{ y: playing ? [0, -8, 0] : 0, boxShadow: playing && i === 1 ? [`0 0 0 rgba(0,0,0,0)`, `0 0 28px ${mood.glow}`, `0 0 0 rgba(0,0,0,0)`] : "none" }} transition={{ duration: 2.2, delay: i * 0.35, repeat: playing ? Infinity : 0 }}><div className="m-3 h-2" style={{ background: i === 1 ? mood.accent : "rgba(236,239,243,.28)" }} /><div className="mx-3 h-12 border border-white/15" /></motion.div>)}</div>;
  if (topic.visual === "flow" || topic.visual === "pipeline") return <svg viewBox="0 0 600 230" className="h-full w-full"><defs><pattern id={`flow-grid-${topic.id}`} width="28" height="28" patternUnits="userSpaceOnUse"><path d="M 28 0 L 0 0 0 28" fill="none" stroke="rgba(236,239,243,.075)" strokeWidth="1" /></pattern></defs><rect width="600" height="230" fill={`url(#flow-grid-${topic.id})`} />{[70, 195, 320, 445].map((x, i) => <React.Fragment key={x}><motion.rect x={x} y={80} width="82" height="54" fill="rgba(255,255,255,.06)" stroke={i % 2 === 0 ? mood.accent : "rgba(236,239,243,.22)"} strokeWidth="1.2" animate={{ y: playing ? [80, 76, 80] : 80 }} transition={{ duration: 2.2, delay: i * 0.2, repeat: playing ? Infinity : 0 }} />{i < 3 && <motion.line x1={x + 82} y1={107} x2={x + 125} y2={107} stroke={mood.accent} strokeWidth="2" strokeDasharray="5 5" animate={{ pathLength: playing ? [0.2, 1, 0.4] : 0.8 }} transition={{ duration: 2.6, delay: i * 0.15, repeat: playing ? Infinity : 0 }} />}</React.Fragment>)}</svg>;
  if (topic.visual === "burst") return <svg viewBox="0 0 600 230" className="h-full w-full">{Array.from({ length: 18 }).map((_, i) => { const angle = (i / 18) * Math.PI * 2; const x2 = 300 + Math.cos(angle) * 95; const y2 = 115 + Math.sin(angle) * 70; return <motion.line key={i} x1="300" y1="115" x2={x2} y2={y2} stroke={i % 3 === 0 ? mood.accent : "rgba(236,239,243,.24)"} strokeWidth="2" animate={{ opacity: playing ? [0.25, 1, 0.35] : 0.45 }} transition={{ duration: 1.6 + i * 0.05, repeat: playing ? Infinity : 0 }} />; })}<motion.circle cx="300" cy="115" r="16" fill={mood.accent} animate={{ scale: playing ? [1, 1.25, 1] : 1 }} transition={{ duration: 1.5, repeat: playing ? Infinity : 0 }} /></svg>;
  return <svg viewBox="0 0 600 230" className="h-full w-full"><defs><pattern id={`network-grid-${topic.id}`} width="28" height="28" patternUnits="userSpaceOnUse"><path d="M 28 0 L 0 0 0 28" fill="none" stroke="rgba(236,239,243,.075)" strokeWidth="1" /></pattern><radialGradient id={`node-glow-${topic.id}`} cx="50%" cy="50%" r="50%"><stop offset="0%" stopColor={mood.accent} stopOpacity="1" /><stop offset="100%" stopColor={mood.accent} stopOpacity="0" /></radialGradient></defs><rect width="600" height="230" fill={`url(#network-grid-${topic.id})`} />{dots.slice(0, 13).map((d, i) => dots.slice(13).map((e, j) => <motion.line key={`${i}-${j}`} x1={d.x * 6} y1={d.y * 2.3} x2={e.x * 6} y2={e.y * 2.3} stroke={i % 4 === 0 ? mood.glow : "rgba(236,239,243,.135)"} strokeWidth="1" initial={{ pathLength: 0 }} animate={{ pathLength: playing ? [0.18, 1, 0.42] : 0.55 }} transition={{ duration: 4 + j, repeat: playing ? Infinity : 0 }} />))}{dots.map((d, i) => <motion.rect key={i} x={d.x * 6 - d.s / 2} y={d.y * 2.3 - d.s / 2} width={d.s} height={d.s} fill={i % 5 === 0 ? mood.accent : i % 3 === 0 ? mood.node2 : "rgba(236,239,243,.68)"} animate={{ opacity: playing ? [0.35, 1, 0.45] : 0.65, scale: playing && i % 5 === 0 ? [1, 1.55, 1] : 1 }} transition={{ duration: 2 + (i % 5), repeat: playing ? Infinity : 0 }} />)}<motion.circle cx="300" cy="118" r="34" fill={`url(#node-glow-${topic.id})`} animate={{ opacity: playing ? [0.08, 0.22, 0.08] : 0.12 }} transition={{ duration: 3.2, repeat: playing ? Infinity : 0 }} /></svg>;
}

function VisualStage({ topic, line, playing, audienceAnswer, onAudienceDismiss }) {
  const mood = getMood(topic);
  const stageSpeaker = audienceAnswer ? "Kat" : line.speaker;

  return (
    <div className="relative min-h-[460px] overflow-hidden rounded-[1.35rem] border border-[var(--ink)] bg-[var(--ink)] p-3 shadow-[0_28px_70px_rgba(11,15,20,.24)]">
      <div
        className="absolute inset-0 opacity-25"
        style={{
          backgroundImage:
            "linear-gradient(rgba(236,239,243,.075) 1px, transparent 1px), linear-gradient(90deg, rgba(236,239,243,.075) 1px, transparent 1px)",
          backgroundSize: "32px 32px",
        }}
      />

      <motion.div
        className="absolute right-[-10%] top-[-35%] h-80 w-80 rounded-full blur-3xl"
        style={{ background: mood.glow }}
        animate={{ opacity: playing ? [0.16, 0.32, 0.16] : 0.2 }}
        transition={{ duration: 5, repeat: playing ? Infinity : 0 }}
      />
      <motion.div
        className="absolute bottom-[-34%] left-[-12%] h-72 w-72 rounded-full blur-3xl"
        style={{ background: mood.glow2 }}
        animate={{ opacity: playing ? [0.08, 0.2, 0.08] : 0.12 }}
        transition={{ duration: 6, repeat: playing ? Infinity : 0 }}
      />
      <div
        className="absolute inset-0 opacity-70"
        style={{
          background: `radial-gradient(circle at 52% 35%, ${mood.wash} 0%, transparent 48%), linear-gradient(135deg, transparent 0%, ${mood.glow2} 100%)`,
        }}
      />

      <div className="relative z-10 grid min-h-[300px] gap-3 rounded-[1.05rem] border border-white/[.10] bg-black/[.24] p-3 backdrop-blur md:grid-cols-[.9fr_1.1fr]">
        <div className="flex flex-col justify-between rounded-xl border border-white/[.08] bg-white/[.055] p-4 text-white shadow-inner">
          <div>
            <div className="mb-4 inline-flex items-center gap-2 rounded-md bg-white/[.10] px-2.5 py-1.5 font-mono text-[10px] uppercase tracking-[0.16em] text-white/[.60]">
              <Radio size={11} />
              <span>{audienceAnswer ? "Audience question" : line.time}</span>
            </div>

            <AnimatePresence mode="wait">
              <motion.div
                key={audienceAnswer ? "audience" : topic.id}
                initial={{ opacity: 0, y: 10 }}
                animate={{ opacity: 1, y: 0 }}
                exit={{ opacity: 0, y: -8 }}
              >
                <h2 className="max-w-md text-3xl font-semibold leading-[.98] tracking-[-0.04em] text-white drop-shadow-[0_2px_8px_rgba(0,0,0,0.45)] md:text-4xl">
                  {audienceAnswer ? "Kat answers live" : topic.title}
                </h2>
                <p className="mt-3 max-w-md text-sm leading-6 text-white/[.78]">
                  {audienceAnswer
                    ? "The interview pauses while Kat responds from the exchange."
                    : topic.summary}
                </p>
              </motion.div>
            </AnimatePresence>
          </div>

          </div>

        <div className="relative min-h-[210px] overflow-hidden rounded-xl border border-white/[.08] bg-black/[.28] p-3">
          <BroadcastVisual topic={topic} playing={playing && !audienceAnswer} />
          <div className="pointer-events-none absolute inset-0 rounded-xl ring-1 ring-white/[.08]" />
        </div>
      </div>

      <div className="relative z-20 -mt-12 flex min-h-[130px] items-end justify-between px-2 md:px-10">
        <Avatar person="Kat" activeSpeaker={stageSpeaker} playing={playing || Boolean(audienceAnswer)} />

        <AnimatePresence mode="wait">
          {audienceAnswer ? (
            <motion.div
              key="audience-answer"
              initial={{ opacity: 0, y: 14, scale: 0.97 }}
              animate={{ opacity: 1, y: 0, scale: 1 }}
              exit={{ opacity: 0, y: -10, scale: 0.98 }}
              transition={{ duration: 0.3 }}
              className="relative mb-3 max-w-md rounded-lg bg-[#E9FBF8] px-4 py-3 text-left text-[var(--ink)] shadow-[0_14px_34px_rgba(0,0,0,.24)]"
            >
              <div className="font-serif text-sm leading-6">{audienceAnswer}</div>
              <button
                onClick={onAudienceDismiss}
                className="mt-3 rounded-full bg-[#15958B] px-4 py-1.5 text-[11px] font-semibold text-white shadow-sm transition hover:bg-[#117E75]"
              >
                OK
              </button>
              <div className="absolute -left-2 bottom-8 h-4 w-4 rotate-45 bg-[#E9FBF8]" />
            </motion.div>
          ) : (
            <motion.div
              key={line.id}
              initial={{ opacity: 0, y: 8 }}
              animate={{ opacity: 1, y: 0 }}
              exit={{ opacity: 0, y: -8 }}
              className="mb-3 hidden max-w-sm rounded-lg bg-white/[.10] px-3 py-2.5 text-center text-white shadow-lg backdrop-blur md:block"
            >
              <div className="mb-1 font-mono text-[9px] uppercase tracking-[0.14em] text-white/[.45]">
                {line.speaker} · {line.time}
              </div>
              <div className="font-serif text-xs leading-5 text-white/[.90]">“{line.text}”</div>
            </motion.div>
          )}
        </AnimatePresence>

        <Avatar person="Josh" activeSpeaker={audienceAnswer ? "" : stageSpeaker} playing={playing && !audienceAnswer} />
      </div>
    </div>
  );
}

function Player({ topic, active, playing, audioReady, currentTime, duration, onSeek, prev, next, setPlaying }) {
  const mood = getMood(topic);
  const safeDuration = duration || timeToSeconds(transcriptLog[Math.min(transcriptLog.length - 1, 19)].time);
  const progress = safeDuration ? Math.min(100, (currentTime / safeDuration) * 100) : Math.round(((active + 1) / transcriptLog.length) * 100);
  const waveRef = useRef(null);
  const [isDragging, setIsDragging] = useState(false);

  const seekFromEvent = (e) => {
    const el = waveRef.current; if (!el || !safeDuration) return;
    const r = el.getBoundingClientRect();
    const cx = (e.touches && e.touches[0] ? e.touches[0].clientX : e.clientX);
    const pct = Math.max(0, Math.min(1, (cx - r.left) / r.width));
    onSeek(pct * safeDuration);
  };
  const onDown = (e) => { setIsDragging(true); seekFromEvent(e); };
  const onMove = (e) => { if (isDragging) seekFromEvent(e); };
  const onUp   = () => setIsDragging(false);

  useEffect(() => {
    if (!isDragging) return;
    const m = (e) => seekFromEvent(e);
    const u = () => setIsDragging(false);
    window.addEventListener("mousemove", m);
    window.addEventListener("mouseup", u);
    window.addEventListener("touchmove", m, { passive: false });
    window.addEventListener("touchend", u);
    return () => {
      window.removeEventListener("mousemove", m);
      window.removeEventListener("mouseup", u);
      window.removeEventListener("touchmove", m);
      window.removeEventListener("touchend", u);
    };
  }, [isDragging, safeDuration]);

  // Compute transcript moment markers along the waveform
  const moments = transcriptLog.map((line, i) => ({
    i,
    time: timeToSeconds(line.time),
    pct: safeDuration ? (timeToSeconds(line.time) / safeDuration) * 100 : 0,
    speaker: line.speaker,
  }));

  return (
    <div className="rounded-[1.75rem] bg-[var(--ink)] p-4 text-[var(--paper)] shadow-xl">
      <div className="mb-3 flex items-center justify-between gap-3">
        <div className="flex items-center gap-2">
          <button onClick={prev} className="rounded-full bg-white/[.08] p-3 hover:bg-white/[.15]"><SkipBack size={16} /></button>
          <button onClick={() => setPlaying(!playing)} className="rounded-full bg-[var(--paper)] px-5 py-4 text-[var(--ink)] shadow-lg hover:scale-105">{playing ? <Pause size={20} /> : <Play size={20} />}</button>
          <button onClick={next} className="rounded-full bg-white/[.08] p-3 hover:bg-white/[.15]"><SkipForward size={16} /></button>
        </div>
        <div className="font-mono text-[10px] uppercase tracking-[0.16em] text-white/[.45]">
          <span>{audioReady ? formatSeconds(currentTime) : transcriptLog[active].time}</span>
          <span className="mx-2">/</span>
          <span>{audioReady ? formatSeconds(duration) : transcriptLog[transcriptLog.length - 1].time}</span>
        </div>
      </div>

      <div
        ref={waveRef}
        className="cf-wave-scrub relative cursor-pointer select-none"
        onMouseDown={onDown}
        onMouseMove={onMove}
        onMouseUp={onUp}
        onTouchStart={onDown}
        role="slider"
        aria-label="Interview waveform · click or drag to seek"
        aria-valuemin={0}
        aria-valuemax={Math.round(safeDuration)}
        aria-valuenow={Math.round(currentTime)}
      >
        <Waveform playing={playing} activeIndex={active} topic={topic} currentTime={currentTime} />

        {/* Played-portion overlay */}
        <div
          className="cf-wave-played pointer-events-none absolute inset-y-0 left-0"
          style={{ width: progress + "%", background: "linear-gradient(90deg, rgba(255,255,255,0.0), rgba(255,255,255,0.08))" }}
        />

        {/* Moment markers, synced to transcript */}
        <div className="cf-wave-markers pointer-events-none absolute inset-0">
          {moments.map((m) => (
            <div
              key={m.i}
              className={`cf-wave-marker absolute top-0 bottom-0 ${m.i === active ? "is-active" : ""}`}
              style={{
                left: m.pct + "%",
                width: m.i === active ? 2 : 1,
                background: m.i === active ? mood.accent : "rgba(236,239,243,0.18)",
              }}
            />
          ))}
        </div>

        {/* Playhead */}
        <div
          className="cf-wave-playhead pointer-events-none absolute top-0 bottom-0"
          style={{ left: progress + "%", width: 2, background: "var(--paper)", boxShadow: "0 0 12px rgba(255,255,255,0.6)" }}
        />
      </div>

      <div className="mt-2 flex items-center justify-between font-mono text-[9px] uppercase tracking-[0.14em] text-white/[.42]">
        <span>{transcriptLog[active].speaker} · {transcriptLog[active].time}</span>
        <span>Click or drag the waveform to seek</span>
      </div>
    </div>
  );
}

function JoinDialogue({ onAudienceAnswer }) {
  const [question, setQuestion] = useState("");
  const [turn, setTurn] = useState(0);
  const hasQuestion = question.trim().length > 0;

  const ask = () => {
    const nextTurn = turn + 1;
    const prompt = question.trim() || "What is the most important idea in this exchange?";
    const generated = sourceOnlyAnswer(prompt, nextTurn);
    setTurn(nextTurn);
    setQuestion("");
    if (onAudienceAnswer) onAudienceAnswer(generated);
  };

  return <div className="rounded-[2rem] bg-[#E9F2FF] p-5 shadow-[0_22px_55px_rgba(66,103,217,.16)]"><div className="mb-4 flex items-center gap-3"><div className="grid h-11 w-11 shrink-0 place-items-center rounded-full bg-[var(--signal)] text-white shadow-[0_8px_24px_rgba(66,103,217,.35)]"><Bot size={18} /></div><h3 className="text-2xl font-semibold tracking-[-0.03em]">Join the dialogue</h3></div><div className="rounded-[1.5rem] bg-white p-2 shadow-inner"><div className="flex gap-2"><input value={question} onChange={(e) => setQuestion(e.target.value)} onKeyDown={(e) => e.key === "Enter" && ask()} placeholder="Raise your hand to ask a question" className="min-h-[58px] w-full rounded-[1.2rem] bg-[#F8FBFF] px-4 py-3 text-base shadow-sm outline-none placeholder:text-[var(--muted)] focus:bg-white" /><button onClick={ask} className={`rounded-[1.2rem] px-5 py-3 text-white shadow-lg transition hover:scale-105 hover:opacity-90 active:scale-95 ${hasQuestion ? "bg-[#15958B]" : "bg-[var(--ink)]"}`}><Send size={18} /></button></div><div className="mt-2 flex flex-wrap gap-2 px-1 pb-1">{["What’s the big idea here?", "Why does this matter?", "How would you design the eval layer technically?"].map((sample) => <button key={sample} onClick={() => setQuestion(sample)} className="rounded-full bg-[#F3F7FF] px-3 py-1.5 text-xs text-[var(--slate)] transition hover:bg-[#DDEBFF] hover:text-[var(--signal)]">{sample}</button>)}</div></div></div>;
}

function TranscriptLog({ lines, activeIndex, setActive }) {
  const scrollRef = useRef(null);
  const scrollPosition = useRef(0);
  const [showFull, setShowFull] = useState(false);
  const jumpToLine = (i) => {
    if (scrollRef.current) scrollPosition.current = scrollRef.current.scrollTop;
    setActive(i);
    requestAnimationFrame(() => {
      if (scrollRef.current) scrollRef.current.scrollTop = scrollPosition.current;
    });
  };

  return (
    <div className="border border-[var(--rule)] bg-[var(--paper-warm)] p-4 shadow-sm">
      <div className="mb-3 flex items-center gap-2">
        <ScrollText size={16} />
        <h3 className="text-base font-semibold">Transcript log</h3>
        <button onClick={() => setShowFull(true)} className="ml-auto text-sm font-semibold text-[var(--signal)] underline-offset-4 hover:underline">
          Full Transcript
        </button>
      </div>

      <div ref={scrollRef} onScroll={(e) => { scrollPosition.current = e.currentTarget.scrollTop; }} className="max-h-[420px] overflow-y-auto pr-1 overscroll-contain">
        <div className="space-y-2">
          {lines.map((line, i) => {
            const isActive = i === activeIndex;
            return (
              <button key={line.id} onClick={() => jumpToLine(i)} className={`block w-full border px-3 py-2 text-left transition ${isActive ? "border-[var(--ink)] bg-white" : "border-transparent hover:border-[var(--rule)] hover:bg-white/70"}`}>
                <div className="text-sm leading-6 text-[var(--muted)]">
                  <span className="mr-2 font-mono text-[10px] uppercase tracking-[0.12em] text-[var(--muted)]">{line.time}</span>
                  <span className={`mr-2 ${isActive ? "font-bold text-[var(--ink)]" : "font-semibold text-[var(--graphite)]"}`}>{line.speaker}:</span>
                  <span className={isActive ? "font-bold text-[var(--ink)]" : ""}>{line.text}</span>
                </div>
              </button>
            );
          })}
        </div>
      </div>

      <AnimatePresence>
        {showFull && (
          <motion.div className="fixed inset-0 z-50 grid place-items-center bg-black/55 p-4 backdrop-blur-sm" initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}>
            <motion.div initial={{ y: 28, opacity: 0, scale: 0.98 }} animate={{ y: 0, opacity: 1, scale: 1 }} exit={{ y: 18, opacity: 0, scale: 0.985 }} className="max-h-[86vh] w-full max-w-4xl overflow-hidden rounded-[2rem] bg-[#F4F6F9] shadow-[0_38px_100px_rgba(0,0,0,.34)]">
              <div className="relative overflow-hidden bg-[var(--ink)] px-7 py-7 text-center text-white">
                <div className="absolute inset-0 opacity-20" style={{ backgroundImage: "linear-gradient(rgba(236,239,243,.08) 1px, transparent 1px), linear-gradient(90deg, rgba(236,239,243,.08) 1px, transparent 1px)", backgroundSize: "26px 26px" }} />
                <div className="relative z-10 mx-auto max-w-2xl">
                  <div className="mb-2 font-mono text-[10px] uppercase tracking-[0.18em] text-white/[.45]">Coframe interview</div>
                  <h3 className="font-serif text-4xl leading-none tracking-[-0.04em] text-white md:text-5xl">Full Q&A Transcript</h3>
                  <p className="mx-auto mt-3 max-w-xl text-sm leading-6 text-white/[.62]">A clean reading view of Kat and Josh’s exchange, styled like an intelligence report dossier.</p>
                </div>
                <button onClick={() => setShowFull(false)} className="absolute right-5 top-5 rounded-full bg-white px-4 py-2 text-sm font-semibold text-[var(--ink)] shadow-sm transition hover:bg-[#E9FBF8]">
                  Close
                </button>
              </div>

              <div className="grid border-b border-[var(--hairline)] bg-white px-7 py-4 md:grid-cols-3">
                <div className="font-mono text-[10px] uppercase tracking-[0.14em] text-[var(--muted)]">40 moments</div>
                <div className="font-mono text-[10px] uppercase tracking-[0.14em] text-[var(--muted)]">Kat / Josh</div>
                <div className="font-mono text-[10px] uppercase tracking-[0.14em] text-[var(--muted)]">Living interfaces</div>
              </div>

              <div className="max-h-[58vh] overflow-y-auto px-5 py-5 md:px-8 md:py-7">
                <div className="mx-auto max-w-3xl space-y-4">
                  {lines.map((line) => {
                    const isKat = line.speaker === "Kat";
                    return (
                      <div key={`full-${line.id}`} className={`grid gap-3 rounded-2xl bg-white p-4 shadow-sm md:grid-cols-[92px_1fr] ${isKat ? "border-l-4 border-[#7553C9]" : "border-l-4 border-[#3D77A8]"}`}>
                        <div>
                          <div className="font-sans text-sm font-bold text-[var(--ink)]">{line.speaker}</div>
                          <div className="mt-1 font-mono text-[10px] uppercase tracking-[0.12em] text-[var(--muted)]">{line.time}</div>
                        </div>
                        <p className="font-serif text-lg leading-8 text-[var(--graphite)]">{line.text}</p>
                      </div>
                    );
                  })}
                </div>
              </div>
            </motion.div>
          </motion.div>
        )}
      </AnimatePresence>
    </div>
  );
}

function ConversationalContextMap({ activeTopicId, setActiveFromTopic }) {
  const graphNodes = [
    { id: "origin", label: "Living interfaces", x: 120, y: 115, r: 34 },
    { id: "hci", label: "Human-computer interaction", x: 245, y: 78, r: 28 },
    { id: "acceleration", label: "AI acceleration", x: 360, y: 108, r: 30 },
    { id: "agents", label: "Context + tools", x: 480, y: 84, r: 34 },
    { id: "evals", label: "Evaluation memory", x: 570, y: 170, r: 36 },
    { id: "approval", label: "Human judgment", x: 438, y: 238, r: 32 },
    { id: "multimodal", label: "Multimodal orchestration", x: 300, y: 236, r: 30 },
    { id: "superhuman", label: "Superhuman search", x: 165, y: 245, r: 38 },
    { id: "haystacks", label: "GTM workflows", x: 70, y: 185, r: 28 },
  ];

  const edges = [
    ["origin", "hci"],
    ["origin", "acceleration"],
    ["acceleration", "agents"],
    ["agents", "evals"],
    ["evals", "approval"],
    ["evals", "multimodal"],
    ["approval", "superhuman"],
    ["superhuman", "haystacks"],
    ["multimodal", "agents"],
    ["hci", "approval"],
    ["superhuman", "origin"],
  ];

  const nodeById = Object.fromEntries(graphNodes.map((node) => [node.id, node]));

  return (
    <div className="border border-[var(--rule)] bg-[var(--paper-warm)] p-4 shadow-sm">
      <div className="mb-4 border-b border-[var(--hairline)] pb-3">
        <h3 className="text-base font-semibold">Abstract knowledge graph</h3>
        <p className="mt-1 text-xs leading-5 text-[var(--muted)]">
          A semantic map of the interview’s ideas: concepts cluster by relationship, not chronology. Select a node to jump to its anchor moment.
        </p>
      </div>

      <div className="relative overflow-hidden bg-[var(--ink)] p-3 text-white">
        <div className="absolute inset-0 opacity-25" style={{ backgroundImage: "linear-gradient(rgba(236,239,243,.075) 1px, transparent 1px), linear-gradient(90deg, rgba(236,239,243,.075) 1px, transparent 1px)", backgroundSize: "28px 28px" }} />
        <svg viewBox="0 0 650 340" className="relative z-10 h-[360px] w-full">
          <defs>
            <filter id="softGlow" x="-50%" y="-50%" width="200%" height="200%">
              <feGaussianBlur stdDeviation="6" result="blur" />
              <feMerge>
                <feMergeNode in="blur" />
                <feMergeNode in="SourceGraphic" />
              </feMerge>
            </filter>
          </defs>

          {edges.map(([a, b], i) => {
            const from = nodeById[a];
            const to = nodeById[b];
            const active = activeTopicId === a || activeTopicId === b;
            return (
              <motion.line
                key={`${a}-${b}`}
                x1={from.x}
                y1={from.y}
                x2={to.x}
                y2={to.y}
                stroke={active ? "rgba(158,231,221,.72)" : "rgba(236,239,243,.16)"}
                strokeWidth={active ? 2.2 : 1}
                strokeDasharray={i % 3 === 0 ? "4 6" : ""}
                animate={{ opacity: active ? [0.55, 1, 0.55] : 0.45 }}
                transition={{ duration: 2.4, repeat: Infinity }}
              />
            );
          })}

          {graphNodes.map((node) => {
            const topic = findTopic(node.id);
            const mood = getMood(topic);
            const selected = activeTopicId === node.id;
            return (
              <g key={node.id} onClick={() => setActiveFromTopic(topic.quoteLineId)} className="cursor-pointer">
                <motion.circle
                  cx={node.x}
                  cy={node.y}
                  r={node.r + 13}
                  fill={mood.glow}
                  opacity={selected ? 0.55 : 0.22}
                  filter="url(#softGlow)"
                  animate={{ scale: selected ? [1, 1.08, 1] : 1 }}
                  transition={{ duration: 2, repeat: selected ? Infinity : 0 }}
                />
                <motion.circle
                  cx={node.x}
                  cy={node.y}
                  r={node.r}
                  fill={selected ? mood.accent : "rgba(236,239,243,.11)"}
                  stroke={selected ? "#9EE7DD" : mood.accent}
                  strokeWidth={selected ? 2.6 : 1.4}
                  animate={{ opacity: selected ? [0.88, 1, 0.88] : 0.8 }}
                  transition={{ duration: 1.8, repeat: Infinity }}
                />
                <circle cx={node.x - node.r * 0.18} cy={node.y - node.r * 0.22} r={Math.max(5, node.r * 0.18)} fill="rgba(255,255,255,.62)" />
                <text x={node.x} y={node.y + node.r + 18} textAnchor="middle" fontSize="11" fontWeight="700" fill="rgba(236,239,243,.92)">{node.label}</text>
                <text x={node.x} y={node.y + node.r + 33} textAnchor="middle" fontSize="9" fill="rgba(236,239,243,.48)">{transcriptLog.find((line) => line.id === topic.quoteLineId)?.time}</text>
              </g>
            );
          })}
        </svg>
      </div>
    </div>
  );
}

function CoframeInterviewPlayback() {
  const [active, setActive] = useState(0);
  const [playing, setPlaying] = useState(false);
  const [search, setSearch] = useState("");
  const [audienceAnswer, setAudienceAnswer] = useState("");
  const [audioSrc] = useState(AUDIO_SRC);
  const [audioReady, setAudioReady] = useState(false);
  const [currentTime, setCurrentTime] = useState(0);
  const [duration, setDuration] = useState(0);
  const audioRef = useRef(null);
  const resumeRef = useRef(false);
  const audienceTimer = useRef(null);
  const timer = useRef(null);
  const safeActive = clampIndex(active);
  const currentLine = transcriptLog[safeActive] || transcriptLog[0];
  const currentTopic = findTopic(currentLine.topicId);

  useEffect(() => {
    if (!playing || audienceAnswer || audioReady) return undefined;
    timer.current = window.setInterval(() => {
      setActive((i) => (i + 1) % transcriptLog.length);
    }, 6500);
    return () => {
      if (timer.current) window.clearInterval(timer.current);
    };
  }, [playing, audienceAnswer, audioReady]);

  useEffect(() => {
    const audio = audioRef.current;
    if (!audio || !audioReady) return undefined;
    if (playing && !audienceAnswer) {
      audio.play().catch(() => setPlaying(false));
    } else {
      audio.pause();
    }
    return undefined;
  }, [playing, audienceAnswer, audioReady]);

  useEffect(() => {
    return () => {
      if (audienceTimer.current) window.clearTimeout(audienceTimer.current);
      if (timer.current) window.clearInterval(timer.current);
      if (typeof window !== "undefined" && window.speechSynthesis) window.speechSynthesis.cancel();
    };
  }, []);

  const handleAudienceAnswer = (answer) => {
    resumeRef.current = playing;
    setPlaying(false);
    setAudienceAnswer(answer);
    if (typeof window !== "undefined" && window.speechSynthesis) window.speechSynthesis.cancel();
    if (audienceTimer.current) window.clearTimeout(audienceTimer.current);
  };

  const handleAudienceDismiss = () => {
    setAudienceAnswer("");
    if (resumeRef.current) setPlaying(true);
  };

  const syncToTime = (time) => {
    setCurrentTime(time);
    setActive(activeIndexForTime(time));
  };

  const seekToLine = (index) => {
    const safeIndex = clampIndex(index);
    const seconds = timeToSeconds(transcriptLog[safeIndex].time);
    setActive(safeIndex);
    setCurrentTime(seconds);
    if (audioRef.current && audioReady) audioRef.current.currentTime = seconds;
  };

  const handleSeek = (seconds) => {
    setCurrentTime(seconds);
    setActive(activeIndexForTime(seconds));
    if (audioRef.current && audioReady) audioRef.current.currentTime = seconds;
  };

  const next = () => seekToLine((active + 1) % transcriptLog.length);
  const prev = () => seekToLine((active - 1 + transcriptLog.length) % transcriptLog.length);

  return <div className="min-h-screen bg-[var(--paper)] p-4 text-[var(--ink)] md:p-6"><StyleGuide /><audio ref={audioRef} src={audioSrc || undefined} preload={audioSrc ? "auto" : "none"} onLoadedMetadata={(e) => { setAudioReady(true); setDuration(e.currentTarget.duration || 0); }} onTimeUpdate={(e) => syncToTime(e.currentTarget.currentTime)} onEnded={() => setPlaying(false)} onError={() => setAudioReady(false)} /><div className="mx-auto max-w-7xl"><header className="mb-4"><div className="mb-4 flex items-center gap-3"><LogoMark /><div className="font-mono text-[10px] uppercase tracking-[0.18em] text-[var(--muted)]">AI-native interview broadcast</div></div><div className="grid gap-5 border-y border-[var(--hairline)] py-5 lg:grid-cols-[1.1fr_.9fr]"><h1 className="max-w-5xl text-4xl font-bold leading-[.9] tracking-[-0.055em] md:text-7xl">Coframe Interview: <span className="font-serif italic font-normal">Living Interfaces</span></h1><p className="self-end text-base leading-7 text-[var(--slate)] md:text-lg">Kat and Josh trace how static websites become adaptive systems: interfaces that listen, test, learn, and invite humans back in at the moments where judgment matters.</p></div></header><main className="space-y-4"><VisualStage topic={currentTopic} line={currentLine} playing={playing} audienceAnswer={audienceAnswer} onAudienceDismiss={handleAudienceDismiss} /><Player topic={currentTopic} active={safeActive} playing={playing} audioReady={audioReady} currentTime={currentTime} duration={duration} onSeek={handleSeek} prev={prev} next={next} setPlaying={setPlaying} /><JoinDialogue onAudienceAnswer={handleAudienceAnswer} /><section className="grid gap-4 md:grid-cols-2"><TranscriptLog lines={transcriptLog} activeIndex={safeActive} setActive={seekToLine} /><ConversationalContextMap activeTopicId={currentTopic.id} setActiveFromTopic={seekToLine} /></section></main></div></div>;
}

// Mount into the report's Field Notes section.
(function mountCoframe() {
  function mount() {
    const el = document.getElementById('coframe-mount');
    if (!el) return false;
    if (el.dataset.mounted === '1') return true;
    el.dataset.mounted = '1';
    ReactDOM.createRoot(el).render(React.createElement(CoframeInterviewPlayback));
    return true;
  }
  if (!mount()) {
    document.addEventListener('DOMContentLoaded', mount);
  }
})();

})();
