/* ============================================================================
   TRANSLATE — bidirectional. English ⇄ Vietnamese and Kannada ⇄ Vietnamese.
   • Pick your language (English / Kannada); tap ⇄ to swap direction.
   • Hold-to-talk: press & hold the mic → it listens; release → translate + speak.
   • Free-text box + offline curated phrasebook.
   ============================================================================ */
const LANG_CODE = { en: "en-US", kn: "kn-IN", vi: "vi-VN" };
const LANG_NAME = { en: "English", kn: "Kannada", vi: "Vietnamese" };

function speakLang(text, lang) {
  try {
    const synth = window.speechSynthesis;
    const code = LANG_CODE[lang] || "vi-VN";
    const go = () => {
      const u = new SpeechSynthesisUtterance(text);
      u.lang = code;
      const v = synth.getVoices().find((x) => x.lang && x.lang.toLowerCase().startsWith(code.slice(0, 2)));
      if (v) u.voice = v;
      u.rate = 0.9;
      synth.cancel();
      synth.speak(u);
    };
    if (!synth.getVoices().length) { synth.onvoiceschanged = go; setTimeout(go, 250); } else { go(); }
  } catch (e) {}
}

function TranslateScreen() {
  const T = window.TRIP;
  const [home, setHome] = React.useState("en");   // en | kn (your language)
  const [dir, setDir] = React.useState("toVi");    // toVi | fromVi
  const [query, setQuery] = React.useState("");
  const [out, setOut] = React.useState(null);
  const [busy, setBusy] = React.useState(false);
  const [listening, setListening] = React.useState(false);
  const recRef = React.useRef(null);
  const finalRef = React.useRef("");
  const liveRef = React.useRef(null);
  const endingRef = React.useRef(false);   // true = user/we intentionally closed (don't reconnect)
  const reconnectRef = React.useRef(0);     // consecutive auto-reconnect attempts
  const [liveStatus, setLiveStatus] = React.useState(""); // "" | connecting | listening | speaking | muted | ending
  const [liveOpen, setLiveOpen] = React.useState(false);  // immersive conversation overlay
  const [turns, setTurns] = React.useState([]);           // committed conversation history
  const [current, setCurrent] = React.useState(null);     // in-progress {heard, translation}
  const [muted, setMuted] = React.useState(false);
  const SR = window.SpeechRecognition || window.webkitSpeechRecognition;
  const gv = !!(window.translation && window.translation.geminiVoice()); // Gemini-powered voice (records audio → Gemini)
  // Realtime Live API: streams as you speak (instant, conversational). Gemini + key + WS/mic support.
  const liveOn = !!(window.liveTranslate && window.liveTranslate.supported && window.translation &&
    (function () { const c = window.translation.getConfig(); return c.provider === "gemini" && !!c.key; })());
  // Translation always works: free engine by default, your LLM if a key is set in ⚙ Settings.
  const engineLabel = (window.translation && window.translation.engineLabel()) || "Free translation";
  const needsKey = !!(window.translation && window.translation.needsKey());
  const wantProvider = window.translation ? (window.translation.ENGINE_LABEL[window.translation.getConfig().provider] || "") : "";

  const src = dir === "toVi" ? home : "vi";
  const tgt = dir === "toVi" ? "vi" : home;

  // stop any open live session when leaving the screen
  React.useEffect(() => () => { endingRef.current = true; try { liveRef.current && liveRef.current.stop(); } catch (e) {} }, []);

  const runTranslate = async (text, speak) => {
    const q = (text || "").trim();
    if (!q) return;
    if (!window.translation) { setOut({ error: "fail" }); return; }
    setBusy(true); setOut(null);
    try {
      const res = await window.translation.translate(q, src, tgt);
      setOut({ t: res.t, pron: res.pron });
      if (speak && res.t) speakLang(res.t, tgt);
    } catch (e) { setOut({ error: "fail" }); }
    setBusy(false);
  };

  const startListen = () => {
    if (!SR || listening) return;
    try {
      const r = new SR();
      r.lang = LANG_CODE[src];
      r.interimResults = true;
      r.continuous = true;
      finalRef.current = "";
      r.onresult = (e) => {
        let txt = "";
        for (let i = 0; i < e.results.length; i++) txt += e.results[i][0].transcript;
        finalRef.current = txt; setQuery(txt);
      };
      r.onerror = (e) => {
        setListening(false);
        const err = e && e.error;
        if (err === "not-allowed") setOut({ error: "mic" });
        else if (err === "service-not-allowed" || err === "language-not-supported") setOut({ error: "lang" });
        else if (err === "no-speech" || err === "aborted") { /* ignore */ }
        else setOut({ error: "speech" });
      };
      r.onend = () => setListening(false);
      recRef.current = r;
      setOut(null); r.start(); setListening(true);
    } catch (e) { setListening(false); setOut({ error: "lang" }); }
  };
  const stopListen = () => {
    const r = recRef.current; if (r) { try { r.stop(); } catch (e) {} }
    setListening(false);
    setTimeout(() => { if (finalRef.current.trim()) runTranslate(finalRef.current.trim(), true); }, 350);
  };

  // --- Gemini-powered voice: record audio → Gemini transcribes + translates ---
  const startGem = async () => {
    try { setOut(null); await window.recorder.start(); setListening(true); }
    catch (e) { setListening(false); setOut({ error: "mic" }); }
  };
  const stopGem = async () => {
    setListening(false);
    let blob; try { blob = window.recorder.stop(); } catch (e) { return; }
    if (!blob || blob.size < 1600) { return; } // nothing captured
    setBusy(true); setOut(null);
    try {
      const cfg = window.translation.getConfig();
      const r = await fetch("/api/voice", { method: "POST",
        headers: { "x-key": cfg.key, "x-model": "gemini-2.5-flash-lite", "x-mime": blob.type || "audio/wav",
                   "x-src": encodeURIComponent(LANG_NAME[src]), "x-tgt": encodeURIComponent(LANG_NAME[tgt]) },
        body: blob });
      const d = await r.json().catch(() => ({}));
      if (d && d.t) { if (d.heard) setQuery(d.heard); setOut({ t: d.t, pron: d.pron || "" }); speakLang(d.t, tgt); }
      else setOut({ error: "speech" });
    } catch (e) { setOut({ error: "speech" }); }
    setBusy(false);
  };

  // --- realtime Live API: immersive two-way conversation (auto-detect language) ---
  const connectLive = () => {
    const cfg = window.translation.getConfig();
    const sess = window.liveTranslate.create();
    liveRef.current = sess;
    setCurrent(null); setLiveStatus("connecting");
    sess.start({
      key: cfg.key, twoWay: true, home,          // two-way: {your language} ⇄ Vietnamese, auto-detected
      status: (s) => { if (s === "listening") reconnectRef.current = 0; setLiveStatus(s); },
      partial: (p) => setCurrent(p),             // {heard, translation} streaming live
      turn: (t) => {                             // a phrase finished → commit to history
        if (t.heard || t.translation) setTurns((prev) => [...prev, { ...t, side: sideOf(t.heard, home) }]);
        setCurrent(null);
      },
      error: (code) => {
        if (code === "mic") { endingRef.current = true; setLiveStatus(""); setCurrent({ error: "mic" }); }
        // transient errors (connect/model/live) fall through to `closed` → auto-reconnect
      },
      closed: () => {
        if (endingRef.current || liveRef.current !== sess) return; // intentional / superseded
        if (reconnectRef.current >= 4) { setLiveStatus(""); setCurrent({ error: "speech" }); return; }
        reconnectRef.current += 1;
        setLiveStatus("connecting");             // connection dropped (common on travel wifi) → quietly retry
        setTimeout(() => { if (!endingRef.current && liveRef.current === sess) connectLive(); }, 800);
      },
    });
  };
  const openLive = () => {
    endingRef.current = false; reconnectRef.current = 0;
    setTurns([]); setCurrent(null); setMuted(false); setLiveOpen(true);
    connectLive();
  };
  const closeLive = () => {
    endingRef.current = true;
    const s = liveRef.current; liveRef.current = null;
    if (s) { try { s.stop(); } catch (e) {} }
    setLiveOpen(false); setLiveStatus(""); setCurrent(null);
  };
  const toggleMute = () => {
    const s = liveRef.current; if (!s) return;
    const nm = !muted; setMuted(nm); try { s.setMuted(nm); } catch (e) {}
  };

  // mic tap: prefer realtime Live conversation, then Gemini clip voice (Kannada etc.), else phone speech engine
  const micTap = () => {
    if (busy) return;
    if (liveOn) { openLive(); }
    else if (gv) { listening ? stopGem() : startGem(); }
    else { if (!SR) return; listening ? stopListen() : startListen(); }
  };
  const micEnabled = liveOn || gv || !!SR;

  const swap = () => { setDir((d) => (d === "toVi" ? "fromVi" : "toVi")); setQuery(""); setOut(null); };

  const ql = query.trim().toLowerCase();
  const book = T.phrasebook
    .map((g) => ({ ...g, items: g.items.filter((it) => !ql || it.en.toLowerCase().includes(ql) || it.vi.toLowerCase().includes(ql) || (it.kn || "").includes(query.trim())) }))
    .filter((g) => g.items.length);

  const placeholder = { en: "Type in English…", kn: "ಕನ್ನಡದಲ್ಲಿ ಬರೆಯಿರಿ…", vi: "Nhập tiếng Việt…" }[src];
  const langChip = (code, active) => (
    <span style={{ display: "inline-flex", alignItems: "center", padding: "7px 13px", borderRadius: 999,
      fontSize: 13.5, fontWeight: 700, background: active ? "var(--color-teal)" : "var(--colorNeutralBackground3)",
      color: active ? "#fff" : "var(--colorNeutralForeground2)" }}>{LANG_NAME[code] === "Vietnamese" ? "Tiếng Việt" : LANG_NAME[code]}</span>
  );

  return (
    <>
    {liveOpen && (
      <ConversationOverlay home={home} status={liveStatus} turns={turns} current={current}
        muted={muted} onMute={toggleMute} onClose={closeLive} />
    )}
    <div style={{ padding: "8px 16px 0" }}>
      <style>{`@keyframes vnpulse{0%{box-shadow:0 0 0 0 rgba(202,80,16,0.45)}70%{box-shadow:0 0 0 22px rgba(202,80,16,0)}100%{box-shadow:0 0 0 0 rgba(202,80,16,0)}}`}</style>

      {/* your language */}
      <div style={{ marginBottom: 12 }}>
        <SegTabs value={home} onChange={(v) => { setHome(v); setOut(null); }}
          tabs={[{ id: "en", label: "English" }, { id: "kn", label: "ಕನ್ನಡ" }]} />
      </div>

      {/* direction with swap */}
      <div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 10, marginBottom: 16 }}>
        {langChip(src, true)}
        <button onClick={swap} aria-label="swap direction" style={{
          width: 38, height: 38, borderRadius: "50%", border: "1px solid var(--colorNeutralStroke2)",
          background: "var(--colorNeutralBackground1)", cursor: "pointer", display: "inline-flex",
          alignItems: "center", justifyContent: "center", boxShadow: "var(--shadow2)" }}>
          <Icon name="arrow_swap" size={18} color="var(--color-teal)" />
        </button>
        {langChip(tgt, false)}
      </div>

      {/* type to translate */}
      <Card pad={14} style={{ marginBottom: 18 }}>
        <div style={{ fontSize: 11, fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--colorNeutralForeground3)", marginBottom: 8 }}>Type to translate</div>
        <textarea value={query} onChange={(e) => setQuery(e.target.value)} placeholder={placeholder} rows={2}
          style={{ width: "100%", boxSizing: "border-box", padding: "10px 12px", borderRadius: 10, resize: "none",
            border: "1px solid var(--colorNeutralStroke1)", background: "var(--colorNeutralBackground2)",
            fontSize: 15, fontFamily: "var(--fontFamilyBase)", outline: "none", color: "var(--colorNeutralForeground1)" }} />
        <div style={{ marginTop: 10 }}>
          <Btn kind="primary" icon="translate" onClick={() => runTranslate(query, true)} full
            style={!query.trim() ? { opacity: 0.5 } : {}}>{busy ? "Translating…" : "Translate & speak"}</Btn>
        </div>
        <div style={{ fontSize: 11, color: "var(--colorNeutralForeground4)", marginTop: 8 }}>
          {needsKey
            ? `Add your ${wantProvider} key in ⚙ Settings for smart slang-aware translation — using Free for now`
            : `via ${engineLabel} · change in ⚙ Settings`}</div>
      </Card>

      {/* phrasebook (Vietnamese reference) */}
      <div style={{ display: "flex", flexDirection: "column", gap: 18 }}>
        <Overline style={{ margin: "0 2px -4px" }}>Handy Vietnamese phrases</Overline>
        {book.length === 0 && (
          <div style={{ textAlign: "center", color: "var(--colorNeutralForeground3)", fontSize: 13, padding: "10px 0" }}>No phrases match — try the Translate button above.</div>
        )}
        {book.map((g) => (
          <div key={g.cat}>
            <div style={{ display: "flex", alignItems: "center", gap: 7, margin: "0 2px 10px" }}>
              <Icon name={g.icon} size={17} color="var(--color-teal)" filled />
              <Serif size={19} weight={600}>{g.cat}</Serif>
            </div>
            <Card pad={0}>
              {g.items.map((it, i) => (
                <div key={i} style={{ display: "flex", alignItems: "center", gap: 10, padding: "12px 14px",
                  borderBottom: i === g.items.length - 1 ? "none" : "1px solid var(--colorNeutralStroke3)" }}>
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 13, color: "var(--colorNeutralForeground3)" }}>{home === "en" ? it.en : it.kn}</div>
                    <div style={{ fontSize: 16, fontWeight: 700, color: "var(--colorNeutralForeground1)", marginTop: 2 }}>{it.vi}</div>
                    <div style={{ fontSize: 12, color: "var(--colorNeutralForeground4)", fontStyle: "italic", marginTop: 1 }}>{it.pron}</div>
                  </div>
                  <button onClick={() => speakLang(it.vi, "vi")} aria-label="speak" style={spkBtn}>
                    <Icon name="speaker_2" size={18} color="var(--color-teal)" filled />
                  </button>
                </div>
              ))}
            </Card>
          </div>
        ))}
      </div>
      <div style={{ height: 188 }} />
    </div>

    {/* sticky hold-to-speak bar */}
    <div style={{ position: "sticky", bottom: 0, zIndex: 20,
      padding: "12px 16px max(env(safe-area-inset-bottom), 12px)",
      background: "var(--colorNeutralBackground1)", backdropFilter: "blur(14px)", WebkitBackdropFilter: "blur(14px)",
      borderTop: "1px solid var(--colorNeutralStroke2)", boxShadow: "0 -6px 20px rgba(0,0,0,0.06)" }}>
      {busy && <div style={{ fontSize: 13, color: "var(--colorNeutralForeground3)", textAlign: "center", marginBottom: 10 }}>Translating…</div>}
      {out && !out.error && (
        <div style={{ marginBottom: 12, padding: "11px 13px", borderRadius: 12, background: "rgba(3,131,135,0.08)", border: "1px solid rgba(3,131,135,0.18)" }}>
          <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "space-between", gap: 10 }}>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 10.5, fontWeight: 700, letterSpacing: "0.06em", textTransform: "uppercase", color: "var(--colorNeutralForeground4)", marginBottom: 2 }}>{LANG_NAME[tgt] === "Vietnamese" ? "Tiếng Việt" : LANG_NAME[tgt]}</div>
              <div style={{ fontSize: 18, fontWeight: 700, color: "var(--color-darkTeal)", lineHeight: 1.25 }}>{out.t}</div>
              {out.pron && <div style={{ fontSize: 12.5, color: "var(--colorNeutralForeground3)", marginTop: 2, fontStyle: "italic" }}>{out.pron}</div>}
            </div>
            <button onClick={() => speakLang(out.t, tgt)} aria-label="speak" style={spkBtn}>
              <Icon name="speaker_2" size={18} color="var(--color-teal)" filled />
            </button>
          </div>
        </div>
      )}
      {out && out.error && (
        <div style={{ marginBottom: 10, fontSize: 12.5, color: "var(--color-pumpkin)", textAlign: "center" }}>
          {out.error === "mic" ? "Microphone permission denied — allow mic access and try again."
            : out.error === "lang" ? `${LANG_NAME[src]} voice input isn't supported on this device. Type it in the box above, or set your language to English to speak.`
            : out.error === "speech" ? `Couldn't catch that — try again, or type it (some devices don't support ${LANG_NAME[src]} voice).`
            : out.error === "offline" ? "Live translation needs a connection. Use the phrasebook above."
            : "Couldn't translate just now — try again or use the phrasebook."}
        </div>
      )}
      <div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 14 }}>
        <button
          onClick={micTap}
          disabled={!micEnabled || busy}
          aria-pressed={listening}
          style={{ width: 62, height: 62, borderRadius: "50%", border: "none", cursor: micEnabled ? "pointer" : "not-allowed",
            background: listening ? "var(--color-pumpkin)" : "var(--color-teal)", color: "#fff", flexShrink: 0,
            display: "inline-flex", alignItems: "center", justifyContent: "center",
            animation: listening ? "vnpulse 1.2s infinite" : "none",
            transition: "background var(--durationFast), transform var(--durationFaster)",
            transform: listening ? "scale(1.06)" : "scale(1)", boxShadow: "var(--shadow8)" }}>
          <Icon name={listening ? "dismiss" : "mic"} size={28} color="#fff" filled />
        </button>
        <div style={{ fontSize: 13.5, fontWeight: 600, color: listening ? "var(--color-pumpkin)" : "var(--colorNeutralForeground2)" }}>
          {!micEnabled ? "Voice not supported here"
            : liveOn ? "Tap for live conversation"
            : busy ? "Translating…"
            : listening ? "Listening… tap to stop & translate"
            : `Tap to speak ${LANG_NAME[src] === "Vietnamese" ? "Vietnamese" : LANG_NAME[src]}`}
          {!listening && !busy && (liveOn
            ? <span style={{ display: "block", fontSize: 11, color: "var(--colorNeutralForeground4)", fontWeight: 500 }}>⚡ Two-way · auto-detect · {LANG_NAME[home]} ⇄ Tiếng Việt</span>
            : gv ? <span style={{ display: "block", fontSize: 11, color: "var(--colorNeutralForeground4)", fontWeight: 500 }}>via Gemini · works for Kannada</span>
            : null)}
        </div>
      </div>
    </div>
    </>
  );
}

const spkBtn = {
  flexShrink: 0, width: 40, height: 40, borderRadius: 11, cursor: "pointer",
  border: "1px solid var(--colorNeutralStroke2)", background: "var(--colorNeutralBackground1)",
  display: "inline-flex", alignItems: "center", justifyContent: "center",
};

// Which side of the conversation a heard phrase came from, by script:
// Kannada / Latin → the traveller (translate to Vietnamese); Vietnamese → the local (translate to home).
function sideOf(heard, home) {
  const s = heard || "";
  if (/[ಀ-೿]/.test(s)) return "you";  // Kannada
  if (/[ăâđêôơưĂÂĐÊÔƠƯàáảãạằắẳẵặầấẩẫậèéẻẽẹềếểễệìíỉĩịòóỏõọồốổỗộờớởỡợùúủũụừứửữựỳýỷỹỵ]/i.test(s)) return "them"; // Vietnamese
  return "you"; // English / unknown
}

function ConversationOverlay({ home, status, turns, current, muted, onMute, onClose }) {
  const homeLabel = LANG_NAME[home];
  const scrollRef = React.useRef(null);
  React.useEffect(() => { const el = scrollRef.current; if (el) el.scrollTop = el.scrollHeight; }, [turns, current, status]);

  const statusText = status === "connecting" ? "Connecting…"
    : muted ? "Muted — tap the mic to resume"
    : status === "speaking" ? "Speaking the translation…"
    : status === "ending" ? "Session ending…"
    : "Listening — just speak";
  const statusColor = muted || status === "connecting" ? "var(--colorNeutralForeground3)"
    : status === "speaking" ? "var(--color-teal)" : "var(--color-green)";
  const live = !muted && status !== "connecting" && status !== "speaking";

  const Bubble = ({ heard, translation, side }) => {
    const you = side === "you";
    const tgtLang = you ? "vi" : home;
    const tag = you ? `${homeLabel} → Tiếng Việt` : `Tiếng Việt → ${homeLabel}`;
    return (
      <div style={{ display: "flex", justifyContent: you ? "flex-end" : "flex-start", marginBottom: 12 }}>
        <div style={{ maxWidth: "88%", padding: "10px 13px", borderRadius: 16,
          borderBottomRightRadius: you ? 4 : 16, borderBottomLeftRadius: you ? 16 : 4,
          background: you ? "var(--color-teal)" : "var(--colorNeutralBackground3)",
          color: you ? "#fff" : "var(--colorNeutralForeground1)" }}>
          <div style={{ fontSize: 10, fontWeight: 700, letterSpacing: "0.05em", textTransform: "uppercase", opacity: 0.7, marginBottom: 3 }}>{tag}</div>
          {heard && <div style={{ fontSize: 12.5, opacity: 0.82, marginBottom: 3, fontStyle: "italic" }}>{heard}</div>}
          <div style={{ display: "flex", alignItems: "flex-start", gap: 8 }}>
            <div style={{ fontSize: 17, fontWeight: 700, lineHeight: 1.3, flex: 1 }}>{translation || "…"}</div>
            {translation && (
              <button onClick={() => speakLang(translation, tgtLang)} aria-label="replay" style={{ flexShrink: 0, width: 30, height: 30, borderRadius: 8, border: "none", cursor: "pointer",
                background: you ? "rgba(255,255,255,0.22)" : "var(--colorNeutralBackground1)", display: "inline-flex", alignItems: "center", justifyContent: "center" }}>
                <Icon name="speaker_2" size={15} color={you ? "#fff" : "var(--color-teal)"} filled />
              </button>
            )}
          </div>
        </div>
      </div>
    );
  };

  const overlay = (
    <div className="vn-conv-overlay" style={{ position: "fixed", top: 0, left: 0, right: 0, maxWidth: 480, margin: "0 auto",
      zIndex: 4000, background: "var(--colorNeutralBackground1)", color: "var(--colorNeutralForeground1)",
      display: "flex", flexDirection: "column" }}>
      <style>{`.vn-conv-overlay{height:100vh;height:100dvh}@keyframes vnlivepulse{0%{transform:scale(1);opacity:.5}70%{transform:scale(1.7);opacity:0}100%{opacity:0}}`}</style>
      {/* header */}
      <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10, padding: "max(env(safe-area-inset-top),14px) 16px 12px", borderBottom: "1px solid var(--colorNeutralStroke2)" }}>
        <div>
          <Serif size={20} weight={600}>Live conversation</Serif>
          <div style={{ fontSize: 12, color: "var(--colorNeutralForeground3)", marginTop: 1 }}>{homeLabel} ⇄ Tiếng Việt · auto-detect</div>
        </div>
        <button onClick={onClose} aria-label="end" style={{ width: 38, height: 38, borderRadius: "50%", border: "1px solid var(--colorNeutralStroke2)", background: "var(--colorNeutralBackground1)", cursor: "pointer", display: "inline-flex", alignItems: "center", justifyContent: "center" }}>
          <Icon name="dismiss" size={18} color="var(--colorNeutralForeground2)" />
        </button>
      </div>

      {/* transcript */}
      <div ref={scrollRef} style={{ flex: 1, overflowY: "auto", padding: 16 }}>
        {turns.length === 0 && !current && (
          <div style={{ textAlign: "center", color: "var(--colorNeutralForeground3)", fontSize: 14, marginTop: "20vh", padding: "0 24px", lineHeight: 1.6 }}>
            <div style={{ fontSize: 34, marginBottom: 10 }}>🗣️ ↔ 💬</div>
            Just speak — in <b>{homeLabel}</b> or <b>Tiếng Việt</b>.<br />When you finish a sentence, <b>pause briefly</b> — it translates and says it out loud, then keeps listening. Hand the phone over and they can reply the same way.<br /><span style={{ fontSize: 12.5, opacity: 0.85 }}>Mute is only for noisy spots (it also finalises whatever you just said).</span>
          </div>
        )}
        {turns.map((t, i) => <Bubble key={i} heard={t.heard} translation={t.translation} side={t.side} />)}
        {current && !current.error && (current.heard || current.translation) && (
          <Bubble heard={current.heard} translation={current.translation} side={sideOf(current.heard, home)} />
        )}
        {current && current.error && (
          <div style={{ textAlign: "center", color: "var(--color-pumpkin)", fontSize: 13, marginTop: 16 }}>
            {current.error === "mic" ? "Microphone permission denied — allow mic access and reopen."
              : current.error === "lang" ? "Live voice isn't available right now — try the type box instead."
              : "Connection hiccup — close and reopen to keep going."}
          </div>
        )}
      </div>

      {/* footer controls */}
      <div style={{ padding: "14px 16px max(env(safe-area-inset-bottom),16px)", borderTop: "1px solid var(--colorNeutralStroke2)" }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "center", gap: 8, marginBottom: 14, minHeight: 20 }}>
          {live && (
            <span style={{ position: "relative", width: 10, height: 10 }}>
              <span style={{ position: "absolute", inset: 0, borderRadius: "50%", background: statusColor, animation: "vnlivepulse 1.4s infinite" }} />
              <span style={{ position: "absolute", inset: 0, borderRadius: "50%", background: statusColor }} />
            </span>
          )}
          <span style={{ fontSize: 14, fontWeight: 600, color: statusColor }}>{statusText}</span>
        </div>
        <div style={{ display: "flex", alignItems: "flex-start", justifyContent: "center", gap: 40 }}>
          <div style={{ textAlign: "center" }}>
            <button onClick={onMute} aria-pressed={muted} style={{ width: 64, height: 64, borderRadius: "50%", border: "none", cursor: "pointer", display: "inline-flex", alignItems: "center", justifyContent: "center",
              background: muted ? "var(--colorNeutralBackground3)" : "var(--color-teal)", color: muted ? "var(--colorNeutralForeground2)" : "#fff", boxShadow: "var(--shadow8)" }}>
              <Icon name="mic" size={24} color={muted ? "var(--colorNeutralForeground3)" : "#fff"} filled={!muted} />
            </button>
            <div style={{ fontSize: 11.5, color: "var(--colorNeutralForeground3)", marginTop: 6 }}>{muted ? "Unmute" : "Mute"}</div>
          </div>
          <div style={{ textAlign: "center" }}>
            <button onClick={onClose} aria-label="end conversation" style={{ width: 64, height: 64, borderRadius: "50%", border: "none", cursor: "pointer", display: "inline-flex", alignItems: "center", justifyContent: "center", background: "#dc2626", color: "#fff", boxShadow: "var(--shadow8)" }}>
              <Icon name="dismiss" size={26} color="#fff" filled />
            </button>
            <div style={{ fontSize: 11.5, color: "var(--colorNeutralForeground3)", marginTop: 6 }}>End</div>
          </div>
        </div>
      </div>
    </div>
  );
  const host = (typeof document !== "undefined" && (document.getElementById("vn-root") || document.body)) || null;
  return (typeof ReactDOM !== "undefined" && ReactDOM.createPortal && host) ? ReactDOM.createPortal(overlay, host) : overlay;
}

Object.assign(window, { TranslateScreen });
