/* Coff & Co — "ASK THE MAN" AI coffee oracle overlay.
   Exposed as window.CoffeeBot. Loads before the main app script. */
const { motion, AnimatePresence } = window.Motion;

const BOT_IMG = "./assets/web/image-hero.webp";
const BOT_CUP = "./assets/images/cup-mark.svg";
const BOT_EYE = {
  left:  { wx: 697.116, wy: 286.697, rx: 21, ry: 17, rot: 7.81165, pr: 8 },
  right: { wx: 813.644, wy: 278.209, rx: 20, ry: 17, rot: 9.98198, pr: 8 },
};
const BOT_BROW_L = "M718.732 271.172L720.569 262.873L701.249 248.355L674.959 263.021L674.747 268.607L699.845 254.702L718.732 271.172Z";
const BOT_BROW_R = "M798.5 254V245.5L820.5 235.5L843 255.5L842 261L820.5 242L798.5 254Z";
const BOT_PUPIL_MAX = 1, BOT_REACH = 220, BOT_LERP = 0.22, BOT_IDLE = 0.0012;
const BOT_SIZES = ["250g", "500g", "1kg"];
const BOT_PRICES = { "250g": 18, "500g": 32, "1kg": 55 };
const BOT_FMT = (n) => "$" + n.toFixed(2);
const BOT_IMG_FALLBACK = (e) => { e.target.onerror = null; e.target.src = e.target.src.replace("/web/", "/images/").replace(".webp", ".png"); };
const BOT_FREQS = ["Weekly", "Fortnightly", "Monthly"];
const BOT_GRINDS = ["Whole beans", "Espresso", "Plunger", "Filter"];
const BOT_BLENDS = {
  MELBOURNE: { l1: "MELBOURNE", l2: "BLEND", img: "./assets/web/mockup-melbourne-blend.webp", desc: "Dark roast. Rich chocolate and bold nutty notes." },
  SOUTHSIDE: { l1: "SOUTHSIDE", l2: "BLEND", img: "./assets/web/mockup-southside-blend.webp", desc: "Smooth and gentle. For slow soft mornings." },
  BAYSIDE: { l1: "BAYSIDE", l2: "BLEND", img: "./assets/web/mockup-bayside.webp", desc: "Bright and lively. For runs, gym and big energy." },
  NORTHSIDE: { l1: "NORTHSIDE", l2: "BLEND", img: "./assets/web/mockup-northside-blend.webp", desc: "Intense. For chaos and late nights." },
};
const BOT_GEAR = {
  AEROPRESS: { name: "AEROPRESS COFFEE PRESS", img: "./assets/web/gear-aeropress.webp", desc: "Full-immersion press. Smooth cup, zero drama.", price: 69 },
  CAFEC: { name: "2 CUP CAFEC FLOWER DRIPPER", img: "./assets/web/gear-cup.webp", desc: "Pour-over that makes you look like you know things.", price: 28 },
  STTOKE: { name: "STTOKE SHATTERPROOF CERAMIC CUP", img: "./assets/web/gear-stoke.webp", desc: "For coffee that leaves the house.", price: 45 },
};

const botSystem = (audience) => `You are THE ORACLE — Coff & Co's AI coffee oracle: the weird character from the homepage hero, the one holding coffee cups over their eyes. Eyes that have seen things. Dry, funny, warm, a little chaotic, never corporate. No emoji. Sentence case. Keep every reply under 45 words.
Your job: work out what coffee suits the customer${audience === "Business" ? " and their office" : ""}. Ask ONE question per turn, in this rough order: home or office; how many people drink it; cups per day per person; how they brew it (espresso machine / plunger / pour-over / just whole beans); taste (rich and chocolatey / bold and nutty / smooth and easy / "chaotic, surprise me"). Then recommend exactly ONE blend with a one-line reason AND how much to buy per week (a 250g bag makes about 15 cups):
- MELBOURNE BLEND — dark roast, rich chocolate and bold nutty notes. For surviving the to-do list.
- SOUTHSIDE BLEND — smooth and gentle. For slow soft mornings.
- BAYSIDE BLEND — bright and lively. For runs, gym and big energy.
- NORTHSIDE BLEND — intense. For chaos and late nights.
Offices with 6+ people: recommend OFFICE PLANS (bulk blends, monthly delivery) alongside the blend. Home brewers: you may add ONE gear suggestion (AeroPress, CAFEC flower dripper, or STTOKE cup).
ALWAYS answer with ONLY valid JSON, nothing else: {"reply":"...","options":["...","..."]} — options are 2-4 short tap-answers to YOUR question; use [] once you have made the final recommendation.
When (and ONLY when) you give the final recommendation, ALSO include a "product" field: {"reply":"...","options":[],"product":{"blend":"MELBOURNE"|"SOUTHSIDE"|"BAYSIDE"|"NORTHSIDE","size":"250g"|"500g"|"1kg","grind":"Whole beans"|"Espresso"|"Plunger"|"Filter","delivery":"Weekly"|"Fortnightly"|"Monthly"|null}}. Pick size from their volume (one light drinker: 250g; 2-4 people or heavy drinkers: 500g; offices: 1kg) and grind from their brew method (espresso machine: Espresso; plunger: Plunger; pour-over/dripper: Filter; otherwise Whole beans). "delivery" is your suggested subscription cadence (saves 15%): heavy drinkers or offices Weekly, moderate Fortnightly, light Monthly — null only if they seem like a one-off buyer. You may mention the subscription in one short aside. The card with size, grind, delivery and add-to-cart shows automatically — do not list prices or sizes in your reply text.
After they add something to the cart you will receive a bracketed note. If gear has not come up yet, ask ONE short question about brewing gear — the range: AeroPress ($69, full-immersion press), CAFEC flower dripper ($28, pour-over), STTOKE shatterproof ceramic cup ($45, coffee on the move) — with options like ["Show me the gear","No thanks, checkout"]. To recommend ONE gear item include {"product":{"gear":"AEROPRESS"|"CAFEC"|"STTOKE"}} (no blend/size/grind fields). When they decline gear, or have added gear, give a short dry send-off and include {"checkout":true} — a checkout button shows automatically.`;

const BOT_OPENER = { reply: "I've seen things. Mostly unwashed plungers. Tell me — is this coffee for a home, or an office?", options: ["Home", "Office", "Bit of both"] };

function parseBot(text) {
  try { const m = text.match(/\{[\s\S]*\}/); if (m) { const j = JSON.parse(m[0]); if (j.reply) {
    const product = j.product && BOT_BLENDS[j.product.blend] ? { blend: j.product.blend, size: BOT_SIZES.includes(j.product.size) ? j.product.size : "250g", grind: BOT_GRINDS.includes(j.product.grind) ? j.product.grind : "Whole beans", delivery: BOT_FREQS.includes(j.product.delivery) ? j.product.delivery : null } : null;
    const gear = j.product && BOT_GEAR[j.product.gear] ? j.product.gear : null;
    return { reply: String(j.reply), options: Array.isArray(j.options) ? j.options.slice(0, 4).map(String) : [], product, gear, checkout: !!j.checkout };
  } } } catch (e) {}
  return { reply: text.trim(), options: [], product: null, gear: null, checkout: false };
}

const bo = {
  overlay: { position: "fixed", inset: 0, zIndex: 400, display: "flex", flexDirection: "column", overflow: "hidden" },
  head: { display: "flex", alignItems: "flex-start", justifyContent: "space-between", padding: "28px 36px 0", position: "relative", zIndex: 2 },
  title: { margin: 0, fontFamily: "var(--font-display)", fontWeight: 800, fontSize: "clamp(34px, 4.5vw, 64px)", lineHeight: 0.95, letterSpacing: "-0.04em", textTransform: "uppercase", color: "var(--cream)" },
  hand: { fontFamily: "var(--font-hand)", fontWeight: 400, textTransform: "none", display: "inline-block" },
  sub: { margin: "8px 0 0", fontFamily: "var(--font-mono)", fontWeight: 500, fontSize: 13, letterSpacing: "1.5px", textTransform: "uppercase", color: "var(--amber)" },
  close: { background: "var(--cream)", color: "var(--black)", border: "none", cursor: "pointer", padding: "8px 14px", fontFamily: "var(--font-mono)", fontWeight: 700, fontSize: 12, letterSpacing: "2px", textTransform: "uppercase" },
  manWrap: { position: "absolute", left: "3%", bottom: 0, width: "min(30vw, 460px)", pointerEvents: "none", zIndex: 1 },
  chatCol: { position: "relative", zIndex: 2, flex: "1 1 auto", minHeight: 0, display: "flex", flexDirection: "column", gap: 14, width: "min(660px, calc(100% - 48px))", margin: "12px 36px 32px auto" },
  list: { flex: "1 1 auto", minHeight: 0, overflowY: "auto", display: "flex", flexDirection: "column", gap: 16, paddingRight: 6, paddingTop: 28, paddingBottom: 8, WebkitMaskImage: "linear-gradient(to bottom, transparent 0, black 36px, black 100%)", maskImage: "linear-gradient(to bottom, transparent 0, black 36px, black 100%)" },
  botRow: { display: "flex", gap: 10, alignItems: "flex-start", maxWidth: "85%" },
  botTxt: { margin: 0, fontFamily: "var(--font-mono)", fontWeight: 500, fontSize: 15, lineHeight: 1.5, color: "var(--cream)", textWrap: "pretty" },
  userTxt: { alignSelf: "flex-end", maxWidth: "80%", background: "var(--cream)", color: "var(--black)", padding: "10px 14px", fontFamily: "var(--font-mono)", fontWeight: 500, fontSize: 14, lineHeight: 1.45 },
  chipRow: { display: "flex", flexWrap: "wrap", gap: 8 },
  chip: { background: "var(--amber)", color: "var(--black)", border: "none", cursor: "pointer", padding: "9px 13px", fontFamily: "var(--font-mono)", fontWeight: 700, fontSize: 12, letterSpacing: "1px", textTransform: "uppercase" },
  inputRow: { display: "flex", gap: 12, alignItems: "stretch" },
  input: { flex: "1 1 auto", minWidth: 0, background: "transparent", border: "1px solid rgba(255,251,213,0.35)", borderBottom: "2px solid var(--cream)", color: "var(--cream)", padding: "12px 14px", fontFamily: "var(--font-mono)", fontSize: 14, outline: "none" },
  ask: { background: "var(--cream)", color: "var(--black)", border: "none", cursor: "pointer", borderRadius: "50%", transform: "rotate(-5deg)", padding: "10px 24px", fontFamily: "var(--font-mono)", fontWeight: 700, fontSize: 13, letterSpacing: "2px" },
  think: { margin: 0, fontFamily: "var(--font-hand)", fontSize: 16, color: "var(--amber)" },
  card: { background: "var(--cream)", color: "#000", padding: 18, display: "flex", flexDirection: "column", gap: 14, maxWidth: 440, marginLeft: 32 },
  cardHead: { display: "flex", gap: 16, alignItems: "center" },
  cardImg: { width: 92, height: "auto", flex: "0 0 auto" },
  cardTitle: { margin: 0, fontFamily: "var(--font-display)", fontWeight: 800, fontSize: 24, lineHeight: 0.95, letterSpacing: "-0.03em", textTransform: "uppercase", color: "#000" },
  cardDesc: { margin: "6px 0 0", fontFamily: "var(--font-mono)", fontWeight: 500, fontSize: 12, lineHeight: 1.45, color: "#000" },
  optLabel: { margin: "0 0 6px", fontFamily: "var(--font-mono)", fontWeight: 700, fontSize: 10, letterSpacing: "1.5px", textTransform: "uppercase", color: "rgba(0,0,0,0.55)" },
  optRow: { display: "flex", flexWrap: "wrap", gap: 6 },
  optChip: { background: "transparent", color: "#000", border: "1px solid #000", cursor: "pointer", padding: "6px 10px", fontFamily: "var(--font-mono)", fontWeight: 700, fontSize: 11, letterSpacing: "0.5px", textTransform: "uppercase" },
  optChipOn: { background: "#000", color: "var(--cream)" },
  cardFoot: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 },
  cardPrice: { fontFamily: "var(--font-mono)", fontWeight: 700, fontSize: 15, color: "#000" },
  addBtn: { background: "transparent", color: "#000", border: "1px solid #000", borderBottom: "3px solid #000", cursor: "pointer", padding: "10px 16px", fontFamily: "var(--font-mono)", fontWeight: 700, fontSize: 12, letterSpacing: "1.5px", textTransform: "uppercase" },
  addBtnOn: { background: "#000", color: "var(--cream)" },
  checkoutBtn: { alignSelf: "flex-start", marginLeft: 32, display: "inline-block", background: "var(--amber)", color: "#000", padding: "14px 24px", borderRadius: "50%", transform: "rotate(-3deg)", fontFamily: "var(--font-mono)", fontWeight: 700, fontSize: 13, letterSpacing: "2px", textTransform: "uppercase", textDecoration: "none" },
};

function OracleGearCard({ gear, onAdded }) {
  const g = BOT_GEAR[gear];
  const [added, setAdded] = React.useState(0);
  const [flash, setFlash] = React.useState(false);
  const add = () => { if (added === 0 && onAdded) onAdded("the " + g.name); setAdded((n) => n + 1); setFlash(true); setTimeout(() => setFlash(false), 1300); };
  return (
    <motion.div layout style={bo.card} initial={{ y: 28, opacity: 0 }} animate={{ y: 0, opacity: 1 }} transition={{ duration: 0.45, ease: [0.16, 1, 0.3, 1], delay: 0.15 }}>
      <div style={bo.cardHead}>
        <img src={g.img} alt={g.name} draggable="false" style={bo.cardImg} onError={BOT_IMG_FALLBACK} />
        <div>
          <h4 style={bo.cardTitle}>{g.name}</h4>
          <p style={bo.cardDesc}>{g.desc}</p>
        </div>
      </div>
      <div style={bo.cardFoot}>
        <span style={bo.cardPrice}>{BOT_FMT(g.price)}</span>
        <button type="button" style={{ ...bo.addBtn, ...(flash ? bo.addBtnOn : {}) }} onClick={add}>
          {flash ? "ADDED ✓" : added > 0 ? `ADD ANOTHER (${added})` : "ADD TO CART"}
        </button>
      </div>
    </motion.div>
  );
}

function OracleProductCard({ rec, onAdded }) {
  const b = BOT_BLENDS[rec.blend];
  const [size, setSize] = React.useState(rec.size);
  const [grind, setGrind] = React.useState(rec.grind);
  const [mode, setMode] = React.useState(rec.delivery ? "sub" : "once");
  const [freq, setFreq] = React.useState(rec.delivery || "Fortnightly");
  const [added, setAdded] = React.useState(0);
  const [flash, setFlash] = React.useState(false);
  const add = () => { if (added === 0 && onAdded) onAdded(`a ${size} ${b.l1} ${b.l2} (${grind}${mode === "sub" ? ", " + freq.toLowerCase() + " subscription" : ""})`); setAdded((n) => n + 1); setFlash(true); setTimeout(() => setFlash(false), 1300); };
  const price = mode === "sub" ? BOT_PRICES[size] * 0.85 : BOT_PRICES[size];
  return (
    <motion.div layout style={bo.card} initial={{ y: 28, opacity: 0 }} animate={{ y: 0, opacity: 1 }} transition={{ duration: 0.45, ease: [0.16, 1, 0.3, 1], delay: 0.15 }}>
      <div style={bo.cardHead}>
        <img src={b.img} alt={b.l1 + " " + b.l2 + " coffee bag"} draggable="false" style={bo.cardImg} onError={BOT_IMG_FALLBACK} />
        <div>
          <h4 style={bo.cardTitle}>{b.l1}<br />{b.l2}</h4>
          <p style={bo.cardDesc}>{b.desc}</p>
        </div>
      </div>
      <div>
        <p style={bo.optLabel}>Size</p>
        <div style={bo.optRow}>
          {BOT_SIZES.map((s) => <button key={s} type="button" style={{ ...bo.optChip, ...(s === size ? bo.optChipOn : {}) }} onClick={() => setSize(s)}>{s}</button>)}
        </div>
      </div>
      <div>
        <p style={bo.optLabel}>Grind</p>
        <div style={bo.optRow}>
          {BOT_GRINDS.map((g) => <button key={g} type="button" style={{ ...bo.optChip, ...(g === grind ? bo.optChipOn : {}) }} onClick={() => setGrind(g)}>{g}</button>)}
        </div>
      </div>
      <div>
        <p style={bo.optLabel}>Delivery</p>
        <div style={bo.optRow}>
          <button type="button" style={{ ...bo.optChip, ...(mode === "once" ? bo.optChipOn : {}) }} onClick={() => setMode("once")}>One-off</button>
          <button type="button" style={{ ...bo.optChip, ...(mode === "sub" ? bo.optChipOn : {}) }} onClick={() => setMode("sub")}>Subscribe · save 15%</button>
        </div>
        {mode === "sub" && (
          <div style={{ ...bo.optRow, marginTop: 6 }}>
            {BOT_FREQS.map((f) => <button key={f} type="button" style={{ ...bo.optChip, ...(f === freq ? { background: "var(--amber)", borderColor: "var(--amber)" } : {}) }} onClick={() => setFreq(f)}>{f}</button>)}
          </div>
        )}
      </div>
      <div style={bo.cardFoot}>
        <span style={bo.cardPrice}>{BOT_FMT(price)}{mode === "sub" && <span style={{ fontWeight: 500, fontSize: 11 }}> / {freq.toLowerCase()} delivery</span>}</span>
        <button type="button" style={{ ...bo.addBtn, ...(flash ? bo.addBtnOn : {}) }} onClick={add}>
          {flash ? (mode === "sub" ? "SUBSCRIBED ✓" : "ADDED ✓") : added > 0 ? `ADD ANOTHER (${added})` : mode === "sub" ? "SUBSCRIBE" : "ADD TO CART"}
        </button>
      </div>
    </motion.div>
  );
}

function BotMsg({ text }) {
  return (
    <motion.div layout style={bo.botRow} initial={{ y: 28, opacity: 0 }} animate={{ y: 0, opacity: 1 }} transition={{ duration: 0.4, ease: [0.16, 1, 0.3, 1] }}>
      <img src={BOT_CUP} alt="" style={{ height: 22, width: "auto", marginTop: 2 }} draggable="false" />
      <p style={bo.botTxt}>{text}</p>
    </motion.div>
  );
}

function CoffeeBot({ open, onClose, audience }) {
  const [msgs, setMsgs] = React.useState([]);
  const [hist, setHist] = React.useState([]);
  const [options, setOptions] = React.useState([]);
  const [input, setInput] = React.useState("");
  const [busy, setBusy] = React.useState(false);
  const [chipFly, setChipFly] = React.useState(null); // { i, t } — newest message sent via a tapped chip (shared-layout morph)
  const [gearAsked, setGearAsked] = React.useState(false);
  const [wide, setWide] = React.useState(typeof window !== "undefined" ? window.innerWidth > 860 : true);
  const listRef = React.useRef(null);
  const whiteL = React.useRef(null), whiteR = React.useRef(null), pupilL = React.useRef(null), pupilR = React.useRef(null);
  const bg = audience === "Business" ? "#12233F" : "#0A3283";

  // Mouse-tracking pupils (same behaviour as the hero); idle look-around on coarse pointers.
  React.useEffect(() => {
    if (!open || !wide) return;
    const fine = window.matchMedia("(pointer: fine)").matches;
    let mouse = null, raf;
    const onMove = (e) => { mouse = { x: e.clientX, y: e.clientY }; };
    if (fine) window.addEventListener("mousemove", onMove, { passive: true });
    const maxOff = (e) => (Math.min(e.rx, e.ry) - e.pr) * BOT_PUPIL_MAX;
    const MAX_L = maxOff(BOT_EYE.left), MAX_R = maxOff(BOT_EYE.right);
    const cur = { L: { x: 0, y: 0 }, R: { x: 0, y: 0 } }, tgt = { L: { x: 0, y: 0 }, R: { x: 0, y: 0 } };
    const aim = (el, MAX) => {
      const r = el.getBoundingClientRect();
      const dx = mouse.x - (r.left + r.width / 2), dy = mouse.y - (r.top + r.height / 2);
      const d = Math.hypot(dx, dy) || 1, k = Math.min(1, d / BOT_REACH);
      return { x: (dx / d) * MAX * k, y: (dy / d) * MAX * k };
    };
    const loop = (t) => {
      if (fine && mouse && whiteL.current && whiteR.current) {
        tgt.L = aim(whiteL.current, MAX_L); tgt.R = aim(whiteR.current, MAX_R);
      } else {
        const a = t * BOT_IDLE, ix = Math.cos(a), iy = Math.sin(a * 0.8);
        tgt.L = { x: ix * MAX_L, y: iy * MAX_L }; tgt.R = { x: ix * MAX_R, y: iy * MAX_R };
      }
      const lp = (c, v) => c + (v - c) * BOT_LERP;
      cur.L.x = lp(cur.L.x, tgt.L.x); cur.L.y = lp(cur.L.y, tgt.L.y);
      cur.R.x = lp(cur.R.x, tgt.R.x); cur.R.y = lp(cur.R.y, tgt.R.y);
      if (pupilL.current) pupilL.current.setAttribute("transform", `translate(${cur.L.x.toFixed(2)} ${cur.L.y.toFixed(2)})`);
      if (pupilR.current) pupilR.current.setAttribute("transform", `translate(${cur.R.x.toFixed(2)} ${cur.R.y.toFixed(2)})`);
      raf = requestAnimationFrame(loop);
    };
    raf = requestAnimationFrame(loop);
    return () => { cancelAnimationFrame(raf); window.removeEventListener("mousemove", onMove); };
  }, [open, wide]);

  React.useEffect(() => {
    const u = () => setWide(window.innerWidth > 860);
    window.addEventListener("resize", u);
    return () => window.removeEventListener("resize", u);
  }, []);
  React.useEffect(() => {
    if (open && msgs.length === 0) { setMsgs([{ role: "bot", text: BOT_OPENER.reply }]); setOptions(BOT_OPENER.options); }
    if (open) {
      const onKey = (e) => { if (e.key === "Escape") onClose(); };
      window.addEventListener("keydown", onKey);
      const prev = document.body.style.overflow;
      document.body.style.overflow = "hidden";
      return () => { window.removeEventListener("keydown", onKey); document.body.style.overflow = prev; };
    }
  }, [open]);
  React.useEffect(() => {
    const toBottom = () => { const el = listRef.current; if (el) el.scrollTop = el.scrollHeight; };
    toBottom();
    const t1 = setTimeout(toBottom, 250), t2 = setTimeout(toBottom, 550); // re-pin after layout/entry animations settle
    return () => { clearTimeout(t1); clearTimeout(t2); };
  }, [msgs, busy]);

  const send = async (text, fromChip) => {
    const t = text.trim();
    if (!t || busy) return;
    setChipFly(fromChip ? { i: msgs.length, t } : null);
    setMsgs((m) => [...m, { role: "user", text: t }]);
    setOptions([]); setInput(""); setBusy(true);
    await converse([...hist, { role: "user", content: t }]);
  };

  // Invisible conversation turn — an add-to-cart event the customer didn't type.
  const notifyAdded = (line) => {
    if (busy) return;
    const note = gearAsked
      ? `(The customer just added ${line} to their cart. Gear has already come up — give a short send-off and include {"checkout":true}.)`
      : `(The customer just added ${line} to their cart. Now ask your one short question about brewing gear.)`;
    setGearAsked(true);
    setOptions([]); setBusy(true);
    converse([...hist, { role: "user", content: note }]);
  };

  const converse = async (newHist) => {
    setHist(newHist);
    try {
      if (!window.claude || !window.claude.complete) throw new Error("no-claude");
      const raw = await window.claude.complete({ system: botSystem(audience), messages: [{ role: "user", content: "(The customer opened the chat. Your opener was: " + BOT_OPENER.reply + ")" }, ...newHist], max_tokens: 400 });
      const { reply, options: opts, product, gear, checkout } = parseBot(raw);
      setMsgs((m) => [...m, { role: "bot", text: reply, product, gear, checkout }]);
      setHist((h) => [...h, { role: "assistant", content: JSON.stringify({ reply, options: opts }) }]);
      setOptions(opts);
    } catch (e) {
      setMsgs((m) => [...m, { role: "bot", text: "Hm. My brain's unplugged in this preview. Try me on the live site, or just get the Melbourne Blend — it fixes most things." }]);
    }
    setBusy(false);
  };

  return (
    <AnimatePresence>
      {open && (
        <motion.div key="bot" style={{ ...bo.overlay, background: bg }} initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.3 }}>
          <div style={bo.head}>
            <div>
              <h2 style={bo.title}>ASK <span style={bo.hand}>th</span>E OR<span style={bo.hand}>a</span>CLE</h2>
              <p style={bo.sub}>AI coffee oracle · eyes that have seen things</p>
            </div>
            <button type="button" style={bo.close} onClick={onClose}>CLOSE ×</button>
          </div>
          {wide && (
            <motion.div style={bo.manWrap} initial={{ y: 140, opacity: 0 }} animate={{ y: 0, opacity: 1 }} transition={{ duration: 0.7, ease: [0.16, 1, 0.3, 1], delay: 0.15 }}>
              <img src={BOT_IMG} alt="The Coff & Co oracle" draggable="false" style={{ width: "100%", height: "auto", display: "block" }} />
              <svg viewBox="0 0 1519 1134" preserveAspectRatio="none" style={{ position: "absolute", inset: 0, width: "100%", height: "100%", overflow: "visible", pointerEvents: "none" }} aria-hidden="true">
                <ellipse ref={whiteL} cx={BOT_EYE.left.wx} cy={BOT_EYE.left.wy} rx={BOT_EYE.left.rx} ry={BOT_EYE.left.ry} transform={`rotate(${BOT_EYE.left.rot} ${BOT_EYE.left.wx} ${BOT_EYE.left.wy})`} fill="#FFFBD5" />
                <ellipse ref={whiteR} cx={BOT_EYE.right.wx} cy={BOT_EYE.right.wy} rx={BOT_EYE.right.rx} ry={BOT_EYE.right.ry} transform={`rotate(${BOT_EYE.right.rot} ${BOT_EYE.right.wx} ${BOT_EYE.right.wy})`} fill="#FFFBD5" />
                <g ref={pupilL}><circle cx={BOT_EYE.left.wx} cy={BOT_EYE.left.wy} r={BOT_EYE.left.pr} fill="#000" /></g>
                <g ref={pupilR}><circle cx={BOT_EYE.right.wx} cy={BOT_EYE.right.wy} r={BOT_EYE.right.pr} fill="#000" /></g>
                <path d={BOT_BROW_L} fill="#FFFBD5" />
                <path d={BOT_BROW_R} fill="#FFFBD5" />
              </svg>
            </motion.div>
          )}
          <div style={{ ...bo.chatCol, margin: wide ? bo.chatCol.margin : "12px 20px 24px" }}>
            <div ref={listRef} style={bo.list}>
              <div style={{ marginTop: "auto" }}></div>
              {msgs.map((m, i) => m.role === "bot" ? (
                <React.Fragment key={i}>
                  <BotMsg text={m.text} />
                  {m.product && <OracleProductCard rec={m.product} onAdded={notifyAdded} />}
                  {m.gear && <OracleGearCard gear={m.gear} onAdded={notifyAdded} />}
                  {m.checkout && <motion.a layout href="#" style={bo.checkoutBtn} initial={{ y: 24, opacity: 0 }} animate={{ y: 0, opacity: 1 }} transition={{ duration: 0.4, ease: [0.16, 1, 0.3, 1], delay: 0.2 }}>GO TO CHECKOUT →</motion.a>}
                </React.Fragment>
              ) : (chipFly && chipFly.i === i)
                ? <motion.div key={i} layout layoutId={"opt-" + chipFly.t} style={bo.userTxt} transition={{ duration: 0.45, ease: [0.16, 1, 0.3, 1] }}>{m.text}</motion.div>
                : <motion.div key={i} layout style={bo.userTxt} initial={{ y: 28, opacity: 0 }} animate={{ y: 0, opacity: 1 }} transition={{ duration: 0.4, ease: [0.16, 1, 0.3, 1] }}>{m.text}</motion.div>)}
              {busy && <motion.p layout style={bo.think} initial={{ opacity: 0 }} animate={{ opacity: 1 }}>brewing an answer…</motion.p>}
            </div>
            {options.length > 0 && !busy && (
              <div style={bo.chipRow}>
                {options.map((o, i) => <motion.button key={o} layout layoutId={"opt-" + o} type="button" style={{ ...bo.chip, transform: `rotate(${i % 2 ? 1.5 : -1.5}deg)` }} onClick={() => send(o, true)}>{o}</motion.button>)}
              </div>
            )}
            <form style={bo.inputRow} onSubmit={(e) => { e.preventDefault(); send(input); }}>
              <input style={bo.input} value={input} placeholder="Tell the oracle what you like…" onChange={(e) => setInput(e.target.value)} />
              <button type="submit" style={{ ...bo.ask, opacity: busy ? 0.5 : 1 }} disabled={busy}>ASK</button>
            </form>
          </div>
        </motion.div>
      )}
    </AnimatePresence>
  );
}
window.CoffeeBot = CoffeeBot;
