// Scribin' v2 — Your reading friend.
// Intake: PDF (incl. huge textbooks, chunk-loaded) · URLs · YouTube
// Memory: persistent library + position + past-session recall
// Companion Mode: she drops personable asides while reading

import React, { useState, useRef, useEffect, useCallback } from "react";

// ─────────────────────────────────────────────────────────────
// Design tokens — ash / burnt orange / green
const S = {
  ash:   "#1B1C1A",
  ash2:  "#252623",
  ash3:  "#33342F",
  orange:"#C45C1A",
  orangeHover:"#D9681F",
  green: "#4A8C5C",
  greenText:"#6BC488",
  cardText:"#EDE8E0",
  cardMuted:"#8C897F",
  cardSub:"#5E605A",
  scrim: "rgba(240,237,230,.55)",
};

// Text colors for reading view
const T = {
  ink:  "#EBDCC7",
  paper:"#241812",
  plum: "#8A5A3B",
  gold: "#C89B6D",
  sage: "#6F8A5C",
  rose: "#A9563C",
  line: "#3E2C20",
};

const DOCK = S;

const VIBES = {
  Lecture:      { rate:0.95, pitch:0.82, desc:"Steady, professorial cadence" },
  Storytime:    { rate:0.88, pitch:0.90, desc:"Warm, unhurried narration" },
  Sprint:       { rate:1.35, pitch:0.85, desc:"Fast review pass" },
  "Late Night": { rate:0.80, pitch:0.75, desc:"Low, calm, easy on the ears" },
};

const FEMALE_HINTS = ["female","samantha","victoria","karen","moira","tessa","fiona","susan","zira","aria","jenny","michelle","serena","allison","ava","joanna","kate","emma","amy","salli","kimberly","nicole","catherine","hazel","heather","laura","libby","sonia","natasha","olivia"];
const isFemale = v => {
  const n = v.name.toLowerCase();
  if (FEMALE_HINTS.some(h => n.includes(h))) return true;
  return false;
};

// ─────────────────────────────────────────────────────────────
// API calls — proxied through /api/anthropic on the server
const ANTHROPIC_BASE = '/api/anthropic';

async function askClaude(messages, system, useSearch = false) {
  const body = { model: "claude-sonnet-4-20250514", max_tokens: 1000, system, messages };
  if (useSearch) body.tools = [{ type: "web_search_20250305", name: "web_search" }];
  const res = await fetch(`${ANTHROPIC_BASE}/v1/messages`, {
    method: "POST",
    headers: { "Content-Type": "application/json" },
    body: JSON.stringify(body),
  });
  const data = await res.json();
  return (data.content || []).filter(b => b.type === "text").map(b => b.text).join("\n");
}

async function askClaudeHF(messages, system) {
  const body = {
    model: "claude-sonnet-4-20250514", max_tokens: 1000, system, messages,
    mcp_servers: [{ type: "url", url: "https://huggingface.co/mcp", name: "huggingface-mcp" }]
  };
  const res = await fetch(`${ANTHROPIC_BASE}/v1/messages`, {
    method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(body)
  });
  const data = await res.json();
  return (data.content || []).filter(b => b.type === "text").map(b => b.text).join("\n");
}

const splitSentences = txt =>
  (txt.match(/[^.!?]+[.!?]+["')\]]*|\S[^.!?]*$/g) || []).map(s=>s.trim()).filter(s=>s.length>2);

const sid = name => "scr:doc:" + name.replace(/[\s/\\'"]+/g,"_").slice(0,150);

// ─────────────────────────────────────────────────────────────
export default function Scribin() {
  const [docked, setDocked] = useState(true);
  const [dockSide, setDockSide] = useState("right");
  const [studio, setStudio] = useState(false);
  const [overlay, setOverlay] = useState(false);
  const [urlMode, setUrlMode] = useState("Article / PDF");
  const [geauxing, setGeauxing] = useState(false);
  const [hfOpen, setHfOpen] = useState(false);
  const [hfInput, setHfInput] = useState("");
  const [hfLog, setHfLog] = useState([]);
  const [hfBusy, setHfBusy] = useState(false);
  const [docName, setDocName] = useState(null);
  const [sentences, setSentences] = useState([]);
  const [cursor, setCursor] = useState(0);
  const [playing, setPlaying] = useState(false);
  const [voices, setVoices] = useState([]);
  const [voiceURI, setVoiceURI] = useState(null);
  const [rate, setRate] = useState(0.95);
  const [pitch, setPitch] = useState(0.82);
  const [vibe, setVibe] = useState("Lecture");
  const [autoTune, setAutoTune] = useState(true);
  const [companion, setCompanion] = useState(true);
  const [tab, setTab] = useState("read");
  const [verseLog, setVerseLog] = useState([]);
  const [verseInput, setVerseInput] = useState("");
  const [busy, setBusy] = useState(false);
  const [glossary, setGlossary] = useState([]);
  const [sparks, setSparks] = useState([]);
  const [recap, setRecap] = useState("");
  const [streak, setStreak] = useState(0);
  const [loading, setLoading] = useState(null);
  const [urlInput, setUrlInput] = useState("");
  const [library, setLibrary] = useState([]);
  const [aside, setAside] = useState(null);
  const utterRef = useRef(null);
  const cursorRef = useRef(0);
  const playingRef = useRef(false);
  const fileRef = useRef(null);
  const scrollRef = useRef(null);
  const asideCounter = useRef(0);

  // ── voices ──
  useEffect(() => {
    const load = () => {
      const all = window.speechSynthesis.getVoices();
      const fem = all.filter(v=>v.lang.startsWith("en") && isFemale(v));
      const pool = fem.length ? fem : all.filter(v=>v.lang.startsWith("en"));
      setVoices(pool);
      if (pool.length && !voiceURI) setVoiceURI(pool[0].voiceURI);
    };
    load(); window.speechSynthesis.onvoiceschanged = load;
    return () => { window.speechSynthesis.onvoiceschanged = null; };
  }, []);

  // ── persistent library (localStorage fallback for non-HF environments) ──
  const loadLibrary = async () => {
    try {
      if (window.storage?.get) {
        const r = await window.storage.get("scr:library");
        setLibrary(r ? JSON.parse(r.value) : []);
      } else {
        const stored = localStorage.getItem("scr:library");
        setLibrary(stored ? JSON.parse(stored) : []);
      }
    } catch { setLibrary([]); }
  };
  useEffect(() => { loadLibrary(); }, []);

  const saveDoc = async (name, sents, cur = 0, note = "") => {
    try {
      if (window.storage?.set) {
        await window.storage.set(sid(name), JSON.stringify({ sents, cur }));
        const lib = [{ name, total: sents.length, cur, note, ts: Date.now() },
          ...library.filter(l => l.name !== name)].slice(0, 25);
        setLibrary(lib);
        await window.storage.set("scr:library", JSON.stringify(lib));
      } else {
        localStorage.setItem(sid(name), JSON.stringify({ sents, cur }));
        const lib = [{ name, total: sents.length, cur, note, ts: Date.now() },
          ...library.filter(l => l.name !== name)].slice(0, 25);
        setLibrary(lib);
        localStorage.setItem("scr:library", JSON.stringify(lib));
      }
    } catch {}
  };
  const savePos = async (cur) => {
    if (!docName) return;
    try {
      if (window.storage?.get) {
        const r = await window.storage.get(sid(docName));
        if (r) {
          const d = JSON.parse(r.value); d.cur = cur;
          await window.storage.set(sid(docName), JSON.stringify(d));
          const lib = library.map(l => l.name === docName ? { ...l, cur } : l);
          setLibrary(lib);
          await window.storage.set("scr:library", JSON.stringify(lib));
        }
      } else {
        const stored = localStorage.getItem(sid(docName));
        if (stored) {
          const d = JSON.parse(stored); d.cur = cur;
          localStorage.setItem(sid(docName), JSON.stringify(d));
          const lib = library.map(l => l.name === docName ? { ...l, cur } : l);
          setLibrary(lib);
          localStorage.setItem("scr:library", JSON.stringify(lib));
        }
      }
    } catch {}
  };
  const openFromLibrary = async (name) => {
    try {
      let r;
      if (window.storage?.get) {
        r = await window.storage.get(sid(name));
      } else {
        r = { value: localStorage.getItem(sid(name)) };
      }
      if (!r?.value) return;
      const d = JSON.parse(r.value);
      loadContent(name, d.sents, d.cur || 0, true);
    } catch {}
  };

  const loadContent = (name, sents, cur = 0, fromMemory = false) => {
    stopSpeech();
    setSentences(sents); setDocName(name);
    setCursor(cur); cursorRef.current = cur;
    setDocked(false); setTab("read");
    setVerseLog([]); setGlossary([]); setSparks([]); setRecap(""); setStreak(0);
    if (!fromMemory) saveDoc(name, sents, cur);
    if (fromMemory && cur > 0 && companion) {
      speakOnce(`Welcome back. We were ${Math.round((cur/sents.length)*100)} percent through ${name}. Picking up where we left off.`);
    }
  };

  // ── PDF (handles large textbooks: batched page extraction) ──
  const ensurePdfJs = () => new Promise((res, rej) => {
    if (window.pdfjsLib) return res(window.pdfjsLib);
    const s = document.createElement("script");
    s.src = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js";
    s.onload = () => { window.pdfjsLib.GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js"; res(window.pdfjsLib); };
    s.onerror = rej; document.head.appendChild(s);
  });

  const handleFile = async (file) => {
    if (!file) return;
    setLoading("Opening PDF…");
    try {
      const pdfjs = await ensurePdfJs();
      const pdf = await pdfjs.getDocument({ data: await file.arrayBuffer() }).promise;
      const pgs = [];
      const BATCH = 20;
      for (let start = 1; start <= pdf.numPages; start += BATCH) {
        const end = Math.min(start + BATCH - 1, pdf.numPages);
        setLoading(`Reading pages ${start}–${end} of ${pdf.numPages}…`);
        const batch = await Promise.all(
          Array.from({ length: end - start + 1 }, (_, i) =>
            pdf.getPage(start + i).then(p => p.getTextContent())
              .then(tc => tc.items.map(it => it.str).join(" ").replace(/\s+/g," ").trim()))
        );
        pgs.push(...batch);
      }
      loadContent(file.name, splitSentences(pgs.join("\n\n")));
    } catch { alert("Couldn't parse that PDF."); }
    setLoading(null);
  };

  // ── URL / YouTube intake (via Claude + web search) ──
  const isYouTube = u => /youtube\.com|youtu\.be/.test(u);

  const geaux = async () => {
    const url = urlInput.trim();
    if (!url) return;
    setOverlay(false); setGeauxing(true); setDocked(true);
    setLoading(isYouTube(url) ? "fetching transcript…" : "fetching page…");
    try {
      const prompt = isYouTube(url)
        ? `Find and return the full spoken transcript of this YouTube video: ${url}. Output ONLY transcript text.`
        : `Fetch this page and return its full readable body text: ${url}. Output ONLY the content.`;
      const text = await askClaude([{role:"user",content:prompt}], "You extract clean readable text only.", true);
      if (!text || text.length < 100) throw new Error("thin");
      const name = isYouTube(url) ? "▶ " + url.split(/v=|be\//).pop().slice(0,20) : url.replace(/https?:\/\/(www\.)?/,"").slice(0,40);
      const sents = splitSentences(text);
      setSentences(sents); setDocName(name);
      setCursor(0); cursorRef.current = 0;
      saveDoc(name, sents, 0);
      setUrlInput(""); setLoading(null);
      playingRef.current = true; setPlaying(true);
      speakOnce(`Alright, I've got it. Here's ${name}.`);
      setTimeout(() => speakFrom(0), 2500);
    } catch {
      setLoading(null); setGeauxing(false);
      speakOnce("I couldn't pull that one — it may be paywalled.");
    }
  };

  const takeMeThere = async () => {
    const url = urlInput.trim();
    if (!url) return;
    window.open(url, "_blank");
    setOverlay(false);
    await handleUrl();
  };

  const handleUrl = async () => {
    const url = urlInput.trim();
    if (!url) return;
    setLoading(isYouTube(url) ? "Pulling video transcript…" : "Fetching article…");
    try {
      const prompt = isYouTube(url)
        ? `Find and return the full spoken transcript (or the most complete summary of everything said, in order) of this YouTube video: ${url}. Output ONLY the transcript text, no commentary, no timestamps.`
        : `Fetch this page and return its full readable article/body text: ${url}. Output ONLY the text content, no commentary, no navigation junk.`;
      const text = await askClaude([{ role:"user", content: prompt }], "You extract clean readable text. Output only the content itself.", true);
      if (!text || text.length < 100) throw new Error("thin");
      const name = isYouTube(url) ? "▶ " + url.split(/v=|be\//).pop().slice(0,20) : url.replace(/https?:\/\/(www\.)?/,"").slice(0,40);
      loadContent(name, splitSentences(text));
      setUrlInput("");
    } catch { alert("Couldn't pull content from that link — it may be paywalled or transcript-less."); }
    setLoading(null);
  };

  // ── speech ──
  const speakOnce = (text) => {
    const u = new SpeechSynthesisUtterance(text);
    const v = voices.find(v=>v.voiceURI===voiceURI);
    if (v) u.voice = v;
    u.rate = rate; u.pitch = pitch;
    window.speechSynthesis.speak(u);
  };

  const tuneFor = (text) => {
    let r = rate, p = pitch;
    if (!autoTune) return { r, p };
    if (/[=∑∫∂λθ]|O\(/.test(text)) r = Math.max(0.65, r - 0.2);
    if (/\?\s*$/.test(text)) p = Math.min(1.1, p + 0.08);
    if (/\b(critically|importantly|note that|key|fundamental|however|therefore)\b/i.test(text)) r = Math.max(0.7, r - 0.1);
    if (text.length < 40) r = Math.min(1.5, r + 0.05);
    return { r, p };
  };

  const maybeAside = async (idx) => {
    if (!companion) return null;
    asideCounter.current++;
    if (asideCounter.current % 12 !== 0) return null;
    try {
      const ctx = sentences.slice(Math.max(0, idx-12), idx+1).join(" ").slice(-2000);
      const a = await askClaude(
        [{ role:"user", content:`You're reading this aloud to a friend and just finished this stretch:\n"${ctx}"\nDrop ONE short spoken aside (max 18 words) like a friend would — a reaction, a "get this", a quick connection. Casual, warm, no quotes.` }],
        "You are a warm, sharp woman in her 40s reading to a close friend. One short line only.");
      return a?.trim()?.slice(0, 160) || null;
    } catch { return null; }
  };

  const speakFrom = useCallback((idx) => {
    window.speechSynthesis.cancel();
    if (idx >= sentences.length) {
      setPlaying(false); playingRef.current = false;
      if (companion) speakOnce("And… that's the whole thing. Want to verse about it?");
      return;
    }
    const text = sentences[idx];
    const u = new SpeechSynthesisUtterance(text);
    const v = voices.find(v=>v.voiceURI===voiceURI);
    if (v) u.voice = v;
    const { r, p } = tuneFor(text);
    u.rate = r; u.pitch = p;
    u.onend = async () => {
      if (!playingRef.current) return;
      const next = cursorRef.current + 1;
      cursorRef.current = next; setCursor(next);
      setStreak(s=>s+1);
      if (next % 10 === 0) savePos(next);
      const a = await maybeAside(next);
      if (a && playingRef.current) {
        setAside(a);
        const au = new SpeechSynthesisUtterance(a);
        if (v) au.voice = v;
        au.rate = Math.min(1.2, r + 0.1); au.pitch = Math.min(1.1, p + 0.06);
        au.onend = () => { setAside(null); if (playingRef.current) speakFrom(next); };
        window.speechSynthesis.speak(au);
      } else {
        speakFrom(next);
      }
    };
    utterRef.current = u;
    window.speechSynthesis.speak(u);
  }, [sentences, voices, voiceURI, rate, pitch, autoTune, companion]);

  const play = () => { playingRef.current = true; setPlaying(true); speakFrom(cursorRef.current); };
  const stopSpeech = () => { playingRef.current = false; setPlaying(false); setGeauxing(false); window.speechSynthesis.cancel(); savePos(cursorRef.current); };
  const jump = (idx) => { cursorRef.current = idx; setCursor(idx); if (playingRef.current) speakFrom(idx); };
  const applyVibe = (n) => { setVibe(n); setRate(VIBES[n].rate); setPitch(VIBES[n].pitch); };

  // Smooth scroll to current sentence
  useEffect(() => {
    const el = document.getElementById(`sent-${cursor}`);
    const box = scrollRef.current;
    if (!el || !box) return;
    const target = el.offsetTop - box.clientHeight * 0.42;
    const start = box.scrollTop, dist = target - start;
    if (Math.abs(dist) < 4) return;
    const dur = 900; let t0 = null; let raf;
    const ease = t => 1 - Math.pow(1 - t, 3);
    const step = ts => {
      if (!t0) t0 = ts;
      const p = Math.min(1, (ts - t0) / dur);
      box.scrollTop = start + dist * ease(p);
      if (p < 1) raf = requestAnimationFrame(step);
    };
    raf = requestAnimationFrame(step);
    return () => cancelAnimationFrame(raf);
  }, [cursor]);

  const readSoFar = () => sentences.slice(Math.max(0, cursor-60), cursor+1).join(" ").slice(-6000);

  // ── study modes ──
  const sendVerse = async () => {
    if (!verseInput.trim() || busy) return;
    const log = [...verseLog, { role:"user", content: verseInput.trim() }];
    setVerseLog(log); setVerseInput(""); setBusy(true);
    try {
      const pastDocs = library.filter(l=>l.name!==docName).slice(0,5).map(l=>l.name).join("; ");
      const system = `You are Scribin' — a warm, sharp woman in her 40s reading to a close friend. Passage just read aloud:\n"""${readSoFar()}"""\n${pastDocs ? `You've also read these with them before (you may reference them): ${pastDocs}.` : ""}\nDiscuss like a friend, under 150 words. Socratic, never condescending.`;
      const reply = await askClaude(log.map(m=>({role:m.role,content:m.content})), system);
      setVerseLog([...log, { role:"assistant", content: reply }]);
      window.speechSynthesis.cancel(); speakOnce(reply);
    } catch { setVerseLog(l=>[...l,{role:"assistant",content:"Lost my train of thought — ask again?"}]); }
    setBusy(false);
  };

  const buildGlossary = async () => {
    setBusy(true); setTab("glossary");
    try {
      const raw = await askClaude([{role:"user",content:`Extract the 8 most important technical terms from this passage; define each in one plain sentence. JSON only: [{"term":"...","def":"..."}]. Passage:\n${readSoFar()}`}], "Return only valid JSON.");
      setGlossary(JSON.parse(raw.replace(/```json|```/g,"").trim()));
    } catch { setGlossary([{term:"Oops",def:"Read a bit more first."}]); }
    setBusy(false);
  };
  const buildSparks = async () => {
    setBusy(true); setTab("spark");
    try {
      const raw = await askClaude([{role:"user",content:`4 quick quiz questions (recall + one thought experiment) on this passage. JSON only: [{"q":"...","a":"..."}]. Passage:\n${readSoFar()}`}], "Return only valid JSON.");
      setSparks(JSON.parse(raw.replace(/```json|```/g,"").trim()).map(s=>({...s,open:false})));
    } catch { setSparks([{q:"Read a little more, then spark me again.",a:"",open:false}]); }
    setBusy(false);
  };
  const buildRecap = async () => {
    setBusy(true); setTab("recap");
    try {
      const r = await askClaude([{role:"user",content:`Tight 5-bullet recap of this passage + one real-world connection. Passage:\n${readSoFar()}`}], "Warm, brilliant study partner. Concise.");
      setRecap(r);
      if (docName) {
        const lib = library.map(l=>l.name===docName?{...l,note:r.slice(0,300)}:l);
        setLibrary(lib);
        if (window.storage?.set) {
          window.storage.set("scr:library", JSON.stringify(lib)).catch(()=>{});
        } else {
          localStorage.setItem("scr:library", JSON.stringify(lib));
        }
      }
    } catch { setRecap("Listen a bit longer first."); }
    setBusy(false);
  };

  // ── HuggingFace Voice Agent ──
  const HF_KEYS = ["live 4d human avatar", "2d audio driven", "real-time streaming diffusion", "autoregressive video", "causal dit", "digital human pipeline", "audio driven portrait", "talking head generation"];
  const FAVORITES_PROMPT = HF_KEYS.map(k => `"${k}"`).join(", ");

  const sendHf = async (overrideMsg) => {
    const userMsg = (overrideMsg || hfInput).trim();
    if (!userMsg || hfBusy) return;
    const log = [...hfLog, { role:"user", content: userMsg }];
    setHfLog(log); setHfInput(""); setHfBusy(true);
    try {
      const system = `You are Scribin\'s HuggingFace scout — a sharp, curious woman in her 40s who browses HuggingFace for TJ like a knowledgeable friend. You use the HuggingFace MCP to search models, spaces, and papers. When reading papers, focus on TJ\'s favorite research keywords: ${FAVORITES_PROMPT}. Narrate what you\'re looking at with commentary — not just facts, but your take. Keep replies under 200 words, spoken-word style. If you find something exciting, say so.`;
      const reply = await askClaudeHF(log.map(m=>({role:m.role,content:m.content})), system);
      setHfLog([...log, { role:"assistant", content: reply }]);
      window.speechSynthesis.cancel();
      speakOnce(reply);
    } catch(e) {
      const err = "Lost my connection to HuggingFace — try again in a sec.";
      setHfLog(l=>[...l,{role:"assistant",content:err}]);
    }
    setHfBusy(false);
  };

  const QUICK_PROMPTS = [
    "Browse the HuggingFace papers section and find the most exciting paper about live 4D human avatars or audio-driven portraits published this week. Read me the abstract and give me your take.",
    "Search HuggingFace spaces for anything related to real-time talking head or audio-driven video generation. What\'s the most impressive one you see?",
    "Find the latest trending models on HuggingFace related to 2D audio-driven animation or causal video diffusion. Describe what you\'re seeing.",
  ];

  const MODES = { "Article / PDF":"https://arxiv.org/pdf/…", "YouTube":"https://youtube.com/watch?v=…", "HuggingFace":"https://huggingface.co/papers/…" };

  const Overlay = () => overlay && (
    <div onClick={()=>setOverlay(false)} style={{position:"fixed",inset:0,zIndex:100,
      background:S.scrim, backdropFilter:"blur(3px)",
      display:"flex",alignItems:"center",justifyContent:"center",fontFamily:"system-ui,sans-serif"}}>
      <div onClick={e=>e.stopPropagation()} style={{background:S.ash,borderRadius:20,width:"92%",maxWidth:460,
        padding:"26px 26px 20px",boxShadow:"0 24px 64px rgba(27,28,26,.35)"}}>
        <div style={{fontSize:10,letterSpacing:1.4,textTransform:"uppercase",color:S.cardMuted,marginBottom:14,display:"flex",alignItems:"center",gap:8}}>
          <span style={{width:6,height:6,borderRadius:"50%",background:S.orange,display:"inline-block"}} />
          bring something in
        </div>
        <div style={{fontSize:21,fontWeight:500,color:S.cardText,marginBottom:5}}>What should she read?</div>
        <div style={{fontSize:13,color:S.cardMuted,marginBottom:20,lineHeight:1.55}}>Paste a link — she fetches and narrates, or takes you there with her.</div>
        <div style={{display:"flex",gap:6,marginBottom:16}}>
          {Object.keys(MODES).map(m=>(
            <button key={m} onClick={()=>setUrlMode(m)} style={{flex:1,padding:"7px 0",borderRadius:999,fontSize:12,fontWeight:500,
              cursor:"pointer",fontFamily:"inherit",transition:"all .15s",
              border:`0.5px solid ${urlMode===m?S.orange:S.ash3}`,
              background:urlMode===m?S.orange:S.ash2,
              color:urlMode===m?"#FFF5EE":S.cardMuted}}>{m}</button>
          ))}
        </div>
        <div style={{display:"flex",alignItems:"center",gap:8,background:S.ash2,border:`0.5px solid ${S.ash3}`,
          borderRadius:12,padding:"11px 10px 11px 14px",marginBottom:12}}>
          <input autoFocus value={urlInput} onChange={e=>setUrlInput(e.target.value)}
            onKeyDown={e=>e.key==="Enter"&&geaux()}
            placeholder={MODES[urlMode]}
            style={{flex:1,background:"none",border:"none",outline:"none",fontSize:14,color:S.cardText,fontFamily:"inherit"}} />
        </div>
        <div style={{display:"flex",gap:8}}>
          <button onClick={takeMeThere} style={{flex:1,padding:"11px 0",borderRadius:10,background:S.ash2,
            border:`0.5px solid ${S.ash3}`,color:S.cardMuted,fontSize:13,fontWeight:500,cursor:"pointer",fontFamily:"inherit",
            display:"flex",alignItems:"center",justifyContent:"center",gap:7}}>
            <span>↗</span> Take me there
          </button>
          <button onClick={geaux} style={{padding:"11px 20px",borderRadius:10,background:S.orange,border:"none",
            color:"#FFF5EE",fontFamily:"inherit",cursor:"pointer",display:"flex",alignItems:"center",gap:10,whiteSpace:"nowrap"}}>
            <span>
              <span style={{display:"block",fontSize:14,fontWeight:600,letterSpacing:.2}}>Geaux</span>
              <span style={{display:"block",fontSize:10,color:"rgba(255,245,238,.5)",marginTop:1}}>she goes · you stay</span>
            </span>
            <span style={{fontSize:18,opacity:.75}}>◎</span>
          </button>
        </div>
        <div style={{marginTop:12,padding:"11px 14px",background:S.ash2,borderRadius:10,
          border:"0.5px solid rgba(74,140,92,.5)",fontSize:12,color:S.cardMuted,lineHeight:1.55,display:"flex",gap:10}}>
          <span style={{flexShrink:0}}>◎</span>
          <span><b style={{color:S.greenText,fontWeight:500}}>Geaux</b> means she fetches it and reads out loud while you stay here. Her voice comes through the dock.</span>
        </div>
      </div>
    </div>
  );

  const pct = sentences.length ? Math.round((cursor/sentences.length)*100) : 0;

  // ── dock ──
  if (docked) {
    return (
      <div style={{ position:"fixed", bottom:24, [dockSide]:24, zIndex:50, fontFamily:"Georgia, serif" }}>
        <Overlay />
        <input ref={fileRef} type="file" accept="application/pdf" style={{display:"none"}} onChange={e=>handleFile(e.target.files[0])} />
        {/* Main pill */}
        <div onClick={()=> docName && !geauxing ? setDocked(false) : setOverlay(true)}
          style={{ cursor:"pointer", background:S.ash, color:S.cardText,
            borderRadius:16, padding:"12px 18px",
            display:"flex", alignItems:"center", gap:13,
            boxShadow:`0 4px 24px rgba(0,0,0,.45), inset 0 0 0 1px rgba(196,92,26,.3)`,
            border:"none", userSelect:"none" }}>
          <div className="scribin-tile" style={{
            width:46, height:46, borderRadius:12, flexShrink:0,
            background:S.ash2,
            animation:"scribinTilePulse 1.8s ease-in-out infinite",
            display:"flex", alignItems:"center", justifyContent:"center" }}>
            <style>{`
              @keyframes scribinWave {
                0%, 100% { opacity: .25; transform: scale(.94); }
                50%      { opacity: 1;   transform: scale(1.1); }
              }
              @keyframes scribinWaveFar {
                0%, 100% { opacity: 0;   transform: scale(.9); }
                45%, 60% { opacity: .5;  transform: scale(1.14); }
              }
              @keyframes scribinHorn {
                0%, 100% { transform: scale(1); }
                50%      { transform: scale(1.045) translateX(.3px); }
              }
              @keyframes scribinTilePulse {
                0%, 100% { box-shadow: 0 0 0 0 rgba(196,92,26,0); }
                50%      { box-shadow: 0 0 0 4px rgba(196,92,26,.12); }
              }
              @keyframes scribinNameShimmer {
                0%, 100% { text-shadow: 0 0 0 rgba(196,92,26,0); }
                50%      { text-shadow: 0 0 14px rgba(196,92,26,.35); }
              }
              @media (prefers-reduced-motion: reduce) {
                .scribin-wave, .scribin-horn, .scribin-tile, .scribin-name { animation: none !important; opacity: .85 !important; }
              }
            `}</style>
            <svg viewBox="0 0 24 24" style={{width:26,height:26,stroke:S.orange,strokeWidth:1.8,fill:"none",strokeLinecap:"round",strokeLinejoin:"round",overflow:"visible"}}>
              <path className="scribin-horn" d="M11 5 6 9H2v6h4l5 4V5z" fill="rgba(196,92,26,.18)"
                style={{animation:"scribinHorn 1.8s ease-in-out infinite", transformOrigin:"7px 12px"}}/>
              <path className="scribin-wave" d="M15.54 8.46a5 5 0 0 1 0 7.07"
                style={{animation:"scribinWave 1.8s ease-in-out infinite", transformOrigin:"13px 12px"}}/>
              <path className="scribin-wave" d="M19.07 4.93a10 10 0 0 1 0 14.14"
                style={{animation:"scribinWave 1.8s ease-in-out .3s infinite", transformOrigin:"13px 12px"}}/>
              <path className="scribin-wave" d="M22.2 2.2a14 14 0 0 1 0 19.6" strokeWidth="1.4"
                style={{animation:"scribinWaveFar 1.8s ease-in-out .6s infinite", transformOrigin:"13px 12px"}}/>
            </svg>
          </div>
          <div style={{minWidth:0}}>
            <div className="scribin-name" style={{
              fontFamily:"Georgia, 'Times New Roman', serif", fontStyle:"italic",
              fontWeight:700, fontSize:17, letterSpacing:.4, color:S.cardText, lineHeight:1,
              animation:"scribinNameShimmer 1.8s ease-in-out .3s infinite"}}>
              Scribin<span style={{color:S.orange, fontSize:19, fontStyle:"normal", verticalAlign:"-1px"}}>&rsquo;</span>
            </div>
            <div style={{fontSize:11, color:S.cardMuted, marginTop:2, letterSpacing:.3}}>
              {loading || (geauxing ? "reading aloud…" : docName ? `${pct}% through` : "pdf · url · youtube")}
            </div>
          </div>
          {geauxing ? (
            <div style={{marginLeft:"auto",fontSize:10,letterSpacing:.5,
              background:"rgba(74,140,92,.18)",color:S.greenText,
              border:"0.5px solid rgba(74,140,92,.35)",
              borderRadius:6,padding:"2px 8px",whiteSpace:"nowrap"}}>Geaux</div>
          ) : docName && (
            <div style={{marginLeft:"auto", width:32, height:32, borderRadius:8,
              background:"rgba(196,92,26,.15)", border:"1px solid rgba(196,92,26,.3)",
              display:"flex", alignItems:"center", justifyContent:"center",
              fontSize:14, color:S.orange}}>▶</div>
          )}
        </div>

        {/* Enter Studio — admin panel trigger */}
        <div onClick={()=>setStudio(s=>!s)}
          style={{display:"flex", alignItems:"center", justifyContent:"center", gap:6,
            marginTop:7, cursor:"pointer", userSelect:"none"}}>
          <div style={{height:"1px", flex:1, background:"rgba(196,92,26,.25)"}} />
          <span style={{fontSize:10, color:S.cardMuted, letterSpacing:1.2, textTransform:"uppercase"}}>
            {studio ? "close studio" : "enter studio"}
          </span>
          <div style={{height:"1px", flex:1, background:"rgba(196,92,26,.25)"}} />
        </div>

        {/* HuggingFace Voice Agent */}
        <div onClick={()=>{setHfOpen(o=>!o); if(!hfOpen && hfLog.length===0) sendHf("Introduce yourself and tell me what you can do with HuggingFace for me.");}}
          style={{display:"flex", alignItems:"center", justifyContent:"center", gap:6,
            marginTop:6, cursor:"pointer", userSelect:"none"}}>
          <div style={{height:"1px", flex:1, background:"rgba(196,92,26,.15)"}} />
          <span style={{fontSize:10, color:"rgba(196,92,26,.7)", letterSpacing:1.2, textTransform:"uppercase"}}>
            hf scout {hfOpen ? "▴" : "▾"}
          </span>
          <div style={{height:"1px", flex:1, background:"rgba(196,92,26,.15)"}} />
        </div>

        {hfOpen && (
          <div style={{marginTop:8,background:S.ash2,border:"1px solid rgba(196,92,26,.22)",borderRadius:14,padding:"14px 16px",
            minWidth:280,boxShadow:"0 16px 48px rgba(0,0,0,.55)",color:S.cardText}}>
            <div style={{fontWeight:600,fontSize:12,letterSpacing:.8,textTransform:"uppercase",marginBottom:8,color:S.orange,display:"flex",alignItems:"center",gap:6}}>
              hf scout
            </div>
            <div style={{fontSize:11,color:S.cardMuted,marginBottom:10}}>
              She browses Hugging Face and reads it to you.
            </div>

            {/* Quick prompts */}
            <div style={{display:"flex",flexDirection:"column",gap:5,marginBottom:10}}>
              {QUICK_PROMPTS.map((q,i)=>(
                <button key={i} onClick={()=>sendHf(q)} disabled={hfBusy}
                  style={{background:"rgba(196,92,26,.08)",border:"1px solid rgba(196,92,26,.2)",borderRadius:8,
                    padding:"7px 10px",color:S.orange,fontSize:11,cursor:hfBusy?"wait":"pointer",textAlign:"left",fontFamily:"inherit",lineHeight:1.4}}>
                  {["📄 Latest papers","🚀 Top spaces","🔥 Trending models"][i]} →
                </button>
              ))}
            </div>

            {/* Log */}
            <div style={{maxHeight:200,overflowY:"auto",marginBottom:8}}>
              {hfLog.map((m,i)=>(
                <div key={i} style={{background:m.role==="user"?"rgba(255,255,255,.05)":"rgba(255,210,30,.07)",
                  border:"1px solid rgba(255,210,30,.15)",borderRadius:8,padding:"8px 10px",fontSize:12,marginBottom:5,color:m.role==="user"?"#C8D8F0":"#E8D5A0",lineHeight:1.5}}>
                  <b style={{color:m.role==="user"?"#7BA7D4":"#FFD21E"}}>{m.role==="user"?"You":"Scout"}</b> — {m.content}
                </div>
              ))}
              {hfBusy && <div style={{fontSize:11,fontStyle:"italic",color:"#FFD21E",padding:"4px 0"}}>browsing HuggingFace…</div>}
            </div>

            {/* Input */}
            <div style={{display:"flex",gap:6}}>
              <input value={hfInput} onChange={e=>setHfInput(e.target.value)} onKeyDown={e=>e.key==="Enter"&&sendHf()}
                placeholder="find something…"
                style={{flex:1,padding:"7px 10px",borderRadius:8,border:"1px solid rgba(196,92,26,.25)",
                  background:"rgba(255,255,255,.04)",color:S.cardText,fontFamily:"inherit",fontSize:12}} />
              <button onClick={()=>sendHf()} disabled={hfBusy}
                style={{padding:"7px 12px",borderRadius:8,background:"rgba(196,92,26,.18)",color:S.orange,border:"1px solid rgba(196,92,26,.3)",
                  fontWeight:600,cursor:hfBusy?"wait":"pointer",fontSize:12,fontFamily:"inherit"}}>go</button>
            </div>
          </div>
        )}

        {/* Studio panel */}
        {studio && (
          <div style={{marginTop:8,background:S.ash2,border:"1px solid rgba(196,92,26,.25)",borderRadius:14,padding:"16px 16px",
            minWidth:240,boxShadow:"0 16px 48px rgba(0,0,0,.55)",color:S.cardText}}>
            <div style={{fontWeight:500,fontSize:11,letterSpacing:1.2,textTransform:"uppercase",marginBottom:14,color:S.cardMuted}}>controls</div>

            <div style={{fontSize:11,color:S.cardMuted,marginBottom:4,letterSpacing:.3}}>vibe</div>
            <select value={vibe} onChange={e=>applyVibe(e.target.value)}
              style={{width:"100%",padding:"6px 8px",borderRadius:8,border:"1px solid rgba(196,92,26,.3)",background:"rgba(255,255,255,.05)",color:S.cardText,fontFamily:"inherit",marginBottom:10}}>
              {Object.keys(VIBES).map(v=><option key={v}>{v}</option>)}
            </select>

            <div style={{fontSize:11,color:S.cardMuted,marginBottom:4,letterSpacing:.3}}>speed · {rate.toFixed(2)}×</div>
            <input type="range" min="0.5" max="1.8" step="0.05" value={rate} onChange={e=>setRate(+e.target.value)}
              style={{width:"100%",accentColor:S.orange,marginBottom:10}} />

            <div style={{fontSize:12,marginBottom:4}}>Pitch · {pitch.toFixed(2)}</div>
            <input type="range" min="0.6" max="1.2" step="0.02" value={pitch} onChange={e=>setPitch(+e.target.value)}
              style={{width:"100%",accentColor:S.orange,marginBottom:10}} />

            <div style={{fontSize:11,color:S.cardMuted,marginBottom:4,letterSpacing:.3}}>voice</div>
            <select value={voiceURI||""} onChange={e=>setVoiceURI(e.target.value)}
              style={{width:"100%",padding:"6px 8px",borderRadius:8,border:"1px solid rgba(196,92,26,.3)",background:"rgba(255,255,255,.05)",color:S.cardText,fontFamily:"inherit",marginBottom:10}}>
              {voices.map(v=><option key={v.voiceURI} value={v.voiceURI}>{v.name}</option>)}
            </select>

            <label style={{display:"flex",alignItems:"center",gap:8,fontSize:12,marginBottom:6,cursor:"pointer",color:S.cardText}}>
              <input type="checkbox" checked={autoTune} onChange={e=>setAutoTune(e.target.checked)} style={{accentColor:S.orange}} />
              Smart Tune
            </label>
            <label style={{display:"flex",alignItems:"center",gap:8,fontSize:12,marginBottom:12,cursor:"pointer",color:S.cardText}}>
              <input type="checkbox" checked={companion} onChange={e=>setCompanion(e.target.checked)} style={{accentColor:S.orange}} />
              Companion asides
            </label>

            <div style={{fontSize:11,color:S.cardMuted,marginBottom:6,letterSpacing:.3}}>position</div>
            <div style={{display:"flex",gap:6,marginBottom:10}}>
              {["left","right"].map(s=>(
                <button key={s} onClick={()=>setDockSide(s)}
                  style={{flex:1,padding:"6px 0",borderRadius:8,border:"1px solid rgba(196,92,26,.3)",
                    background:dockSide===s?S.orange:"transparent",color:dockSide===s?"#1C1F1E":S.cardText,
                    cursor:"pointer",fontFamily:"inherit",fontSize:12,fontWeight:600}}>{s}</button>
              ))}
            </div>

            {library.length>0 && (
              <div style={{fontSize:10,color:S.cardMuted,marginTop:4}}>{library.length} doc{library.length!==1?"s":""} in memory</div>
            )}
          </div>
        )}
      </div>
    );
  }

  const Btn = ({children,onClick,active,disabled}) => (
    <button onClick={onClick} disabled={disabled} style={{
      background:active?S.orange:"transparent", color:active?"#FFF5EE":T.ink,
      border:`1.5px solid ${active?S.orange:T.line}`, borderRadius:8, padding:"7px 14px",
      cursor:disabled?"wait":"pointer", fontFamily:"inherit", fontSize:13, fontWeight:600, opacity:disabled?.5:1 }}>{children}</button>
  );

  return (
    <div style={{ minHeight:"100vh", background:T.paper, color:T.ink, fontFamily:"Georgia, 'Times New Roman', serif" }}>
      <Overlay />
      <input ref={fileRef} type="file" accept="application/pdf" style={{display:"none"}} onChange={e=>handleFile(e.target.files[0])} />

      <header style={{ display:"flex", alignItems:"center", justifyContent:"space-between",
        padding:"14px 28px", borderBottom:`1.5px solid ${T.line}`, position:"sticky", top:0, background:T.paper, zIndex:10, gap:12, flexWrap:"wrap" }}>
        <div style={{display:"flex",alignItems:"baseline",gap:14}}>
          <h1 style={{margin:0,fontSize:27,letterSpacing:.3,fontStyle:"italic",fontWeight:700}}>Scribin<span style={{color:S.orange,fontSize:30,fontStyle:"normal",verticalAlign:"-2px"}}>&rsquo;</span></h1>
          <span style={{fontSize:12,color:"#B39A80",fontStyle:"italic",maxWidth:220,overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap"}}>{docName || "no document"}</span>
        </div>
        <Btn active onClick={()=>setOverlay(true)}>＋ Bring something in</Btn>
        <div style={{display:"flex",gap:8}}>
          <Btn onClick={()=>fileRef.current.click()}>📄 PDF</Btn>
          <Btn onClick={()=>{stopSpeech();setDocked(true);}}>Dock ▾</Btn>
        </div>
      </header>

      {loading && <div style={{padding:"6px 28px",fontSize:13,color:S.orange,fontStyle:"italic",borderBottom:`1px solid ${T.line}`}}>⏳ {loading}</div>}
      {aside && <div style={{padding:"6px 28px",fontSize:13,color:T.gold,fontStyle:"italic",borderBottom:`1px solid ${T.line}`}}>💛 "{aside}"</div>}

      <div style={{height:4,background:T.line}}>
        <div style={{height:"100%",width:`${pct}%`,background:`linear-gradient(90deg, ${S.orange}, ${T.gold})`,transition:"width .4s"}} />
      </div>

      <div style={{display:"grid",gridTemplateColumns:"minmax(0,1fr) 360px",maxWidth:1280,margin:"0 auto"}}>
        <main ref={scrollRef} style={{padding:"28px 36px",maxHeight:"calc(100vh - 150px)",overflowY:"auto",position:"relative"}}>
          {sentences.length===0 ? (
            <div style={{marginTop:40,maxWidth:640}}>
              <div style={{textAlign:"center",color:"#B39A80",marginBottom:28}}>
                <div style={{fontSize:54}}>📖</div>
                <p style={{fontSize:18}}>Give me a PDF, an article link, or a YouTube video — I'll read it to you like a friend would.</p>
              </div>
              {library.length>0 && (
                <div>
                  <div style={{fontSize:12,fontWeight:700,letterSpacing:1,textTransform:"uppercase",color:S.orange,marginBottom:10}}>📚 Things we've read together</div>
                  {library.map(l=>(
                    <div key={l.name} onClick={()=>openFromLibrary(l.name)}
                      style={{background:"#2E2018",border:`1.5px solid ${T.line}`,borderRadius:10,padding:"12px 16px",marginBottom:8,cursor:"pointer",display:"flex",justifyContent:"space-between",alignItems:"center"}}>
                      <div>
                        <div style={{fontWeight:700,fontSize:14}}>{l.name}</div>
                        {l.note && <div style={{fontSize:12,color:"#B39A80",marginTop:2}}>{l.note.slice(0,90)}…</div>}
                      </div>
                      <div style={{fontSize:12,color:T.sage,whiteSpace:"nowrap",marginLeft:12}}>{Math.round((l.cur/l.total)*100)}% · resume ▶</div>
                    </div>
                  ))}
                </div>
              )}
            </div>
          ) : (
            <p style={{fontSize:18,lineHeight:1.85,maxWidth:720}}>
              {sentences.map((s,i)=>(
                <span key={i} id={`sent-${i}`} onClick={()=>jump(i)} style={{
                  cursor:"pointer",padding:"2px 4px",borderRadius:6,
                  transition:"background 1.1s ease, color .8s ease, box-shadow 1.1s ease",
                  background:i===cursor?"rgba(200,155,109,.22)":"transparent",
                  color:i===cursor?"#FFF3E2":(i<cursor?"#6E5844":T.ink),
                  boxShadow:i===cursor?"0 0 18px rgba(200,155,109,.25), inset 0 -2px 0 rgba(200,155,109,.7)":"none" }}>{s} </span>
              ))}
            </p>
          )}
        </main>

        <aside style={{borderLeft:`1.5px solid ${T.line}`,padding:20,maxHeight:"calc(100vh - 150px)",overflowY:"auto"}}>
          <div style={{display:"flex",gap:8,marginBottom:14}}>
            <Btn onClick={()=>jump(Math.max(0,cursor-3))}>⏪</Btn>
            <button onClick={playing?stopSpeech:play} disabled={!sentences.length} style={{
              flex:1,background:S.orange,color:"#FFF5EE",border:"none",borderRadius:8,
              padding:"10px 0",fontSize:15,fontWeight:700,cursor:"pointer",fontFamily:"inherit",opacity:sentences.length?1:.4}}>
              {playing?"❚❚ Pause":"▶ Read to me"}
            </button>
            <Btn onClick={()=>jump(Math.min(sentences.length-1,cursor+3))}>⏩</Btn>
          </div>

          <div style={{fontSize:12,color:"#B39A80",marginBottom:16,display:"flex",justifyContent:"space-between"}}>
            <span>🔥 Streak: <b>{streak}</b></span><span>{pct}% · position saved</span>
          </div>

          <label style={{fontSize:12,fontWeight:700,letterSpacing:1,textTransform:"uppercase",color:S.orange}}>Voice (all women)</label>
          <select value={voiceURI||""} onChange={e=>setVoiceURI(e.target.value)}
            style={{width:"100%",padding:8,margin:"6px 0 14px",borderRadius:8,border:`1.5px solid ${T.line}`,background:"#2E2018",fontFamily:"inherit"}}>
            {voices.map(v=><option key={v.voiceURI} value={v.voiceURI}>{v.name}</option>)}
          </select>

          <label style={{fontSize:12,fontWeight:700,letterSpacing:1,textTransform:"uppercase",color:S.orange}}>Vibe</label>
          <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:6,margin:"6px 0 4px"}}>
            {Object.keys(VIBES).map(v=><Btn key={v} active={vibe===v} onClick={()=>applyVibe(v)}>{v}</Btn>)}
          </div>
          <div style={{fontSize:11,color:"#B39A80",marginBottom:12,fontStyle:"italic"}}>{VIBES[vibe].desc}</div>

          <div style={{fontSize:13,marginBottom:4}}>Speed · {rate.toFixed(2)}×</div>
          <input type="range" min="0.5" max="1.8" step="0.05" value={rate} onChange={e=>setRate(+e.target.value)} style={{width:"100%",accentColor:S.orange}} />
          <div style={{fontSize:13,margin:"8px 0 4px"}}>Pitch · {pitch.toFixed(2)} <span style={{color:"#B39A80"}}>(lower = more mature)</span></div>
          <input type="range" min="0.6" max="1.2" step="0.02" value={pitch} onChange={e=>setPitch(+e.target.value)} style={{width:"100%",accentColor:S.orange}} />

          <label style={{display:"flex",alignItems:"center",gap:8,fontSize:13,margin:"12px 0 6px",cursor:"pointer"}}>
            <input type="checkbox" checked={autoTune} onChange={e=>setAutoTune(e.target.checked)} style={{accentColor:T.gold}} />
            🎚️ Smart Tune — slows for math, lifts for questions
          </label>
          <label style={{display:"flex",alignItems:"center",gap:8,fontSize:13,margin:"0 0 18px",cursor:"pointer"}}>
            <input type="checkbox" checked={companion} onChange={e=>setCompanion(e.target.checked)} style={{accentColor:T.gold}} />
            💛 Companion Mode — she reacts like a friend while reading
          </label>

          <label style={{fontSize:12,fontWeight:700,letterSpacing:1,textTransform:"uppercase",color:S.orange}}>Study modes</label>
          <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:6,margin:"6px 0 14px"}}>
            <Btn active={tab==="verse"} onClick={()=>setTab("verse")}>💬 Verse</Btn>
            <Btn active={tab==="glossary"} onClick={buildGlossary} disabled={busy}>🔍 Decode</Btn>
            <Btn active={tab==="spark"} onClick={buildSparks} disabled={busy}>⚡ Spark</Btn>
            <Btn active={tab==="recap"} onClick={buildRecap} disabled={busy}>🧠 Recap</Btn>
          </div>

          {tab==="verse" && (
            <div>
              <div style={{fontSize:12,color:"#B39A80",marginBottom:8,fontStyle:"italic"}}>She remembers this doc and your past reads. She answers out loud.</div>
              <div style={{maxHeight:220,overflowY:"auto",marginBottom:8}}>
                {verseLog.map((m,i)=>(
                  <div key={i} style={{background:m.role==="user"?"#2E2018":"rgba(138,90,59,.18)",border:`1px solid ${T.line}`,borderRadius:8,padding:"8px 10px",fontSize:13,marginBottom:6}}>
                    <b style={{color:m.role==="user"?T.sage:T.plum}}>{m.role==="user"?"You":"Scribin'"}</b> — {m.content}
                  </div>
                ))}
                {busy && <div style={{fontSize:12,fontStyle:"italic",color:S.orange}}>thinking…</div>}
              </div>
              <div style={{display:"flex",gap:6}}>
                <input value={verseInput} onChange={e=>setVerseInput(e.target.value)} onKeyDown={e=>e.key==="Enter"&&sendVerse()}
                  placeholder="Wait — run that back…"
                  style={{flex:1,padding:8,borderRadius:8,border:`1.5px solid ${T.line}`,fontFamily:"inherit"}} />
                <Btn active onClick={sendVerse} disabled={busy}>Send</Btn>
              </div>
            </div>
          )}

          {tab==="glossary" && <div>{busy?<i style={{fontSize:13}}>decoding…</i>:glossary.map((g,i)=>(
            <div key={i} style={{marginBottom:8,fontSize:13}}><b style={{color:S.orange}}>{g.term}</b> — {g.def}</div>))}</div>}

          {tab==="spark" && <div>{busy?<i style={{fontSize:13}}>writing questions…</i>:sparks.map((s,i)=>(
            <div key={i} onClick={()=>setSparks(sp=>sp.map((x,j)=>j===i?{...x,open:!x.open}:x))}
              style={{background:"#2E2018",border:`1.5px solid ${T.line}`,borderRadius:8,padding:10,marginBottom:8,cursor:"pointer",fontSize:13}}>
              <b>⚡ {s.q}</b>
              {s.open && <div style={{marginTop:6,color:T.sage}}>{s.a}</div>}
              {!s.open && s.a && <div style={{fontSize:11,color:"#aaa",marginTop:4}}>tap to reveal</div>}
            </div>))}</div>}

          {tab==="recap" && <div style={{fontSize:13,whiteSpace:"pre-wrap",lineHeight:1.6}}>{busy?<i>compressing…</i>:recap}</div>}
        </aside>
      </div>
    </div>
  );
}