/* ═══════════════════════════════════════════════════════════════════════════
   LLMChat — generic macOS-window LLM chat mock for the hero.
   Original UI (no real-product recreation). Bone/ink/rust palette.
   Two exports:
     LLMChatWindow            — chat alone in a mac window
     ChatPlusExtensionWindow  — single mac window: chat (left) + extension (right)
   ═══════════════════════════════════════════════════════════════════════════ */

const TrafficLights = () => (
  <div style={{ display: "flex", gap: 7, alignItems: "center" }}>
    {["#FF5F57", "#FEBC2E", "#28C840"].map(c => (
      <span key={c} style={{
        width: 11, height: 11, borderRadius: "50%",
        background: c, boxShadow: "inset 0 0 0 0.5px rgba(0,0,0,0.18)",
      }}/>
    ))}
  </div>
);

const INTERACTIVE_CONTENT = {
  ja: {
    suggestions: [
      "リピート率が低下してる原因を整理したい",
      "CACの改善施策を構造化して",
      "明日のキックオフの論点をまとめて",
    ],
    responses: [
      "まず数字を確認しましょう。18ヶ月で42%から31%に落ちているなら、獲得チャネル別に2回目購入率を分解するのが最初の一手です。広告経由と自然流入で差が出ているはずです。",
      "CACが1.4倍に膨らんでいるなら、まずファネルのどこで漏れているか特定しましょう。クリエイティブ疲弊、LPの離脱率、CVR低下のどれが主因かで打ち手が変わります。",
      "論点は3つに絞るのがおすすめです。現状の数字共有、成長ボトルネックの特定、次の90日で検証すべき仮説。この順番なら自然にCEOの合意を取れます。",
    ],
  },
  en: {
    suggestions: [
      "I want to organize the reasons behind declining repeat rate",
      "Structure the CAC improvement measures",
      "Summarize the discussion points for tomorrow's kickoff",
    ],
    responses: [
      "Let's start with the numbers. If repeat rate dropped from 42% to 31% over 18 months, the first move is breaking down second purchase rate by acquisition channel. There's likely a gap between paid and organic.",
      "If CAC is up 1.4x, first identify where the funnel is leaking. Creative fatigue, landing page drop off, or CVR decline each call for a different playbook.",
      "I'd narrow it to three points: current metrics review, identifying the growth bottleneck, and the hypotheses to test in the next 90 days. That sequence makes it easy to get CEO alignment.",
    ],
  },
  trees: [
    {
      hub: { ja: "リピート率低下", en: "Repeat Rate Decline" },
      branches: [
        { ja: "チャネル別分解", en: "By Channel", leaves: [{ ja: "広告経由", en: "Paid" }, { ja: "自然流入", en: "Organic" }] },
        { ja: "2回目購入率", en: "2nd Purchase", leaves: [{ ja: "42%→31%", en: "42%→31%" }] },
        { ja: "時系列推移", en: "Timeline", leaves: [{ ja: "18ヶ月", en: "18 months" }] },
      ],
    },
    {
      hub: { ja: "CAC改善", en: "CAC Improvement" },
      branches: [
        { ja: "ファネル分析", en: "Funnel Analysis", leaves: [{ ja: "LP離脱率", en: "LP Drop Off" }, { ja: "CVR低下", en: "CVR Decline" }] },
        { ja: "クリエイティブ", en: "Creative", leaves: [{ ja: "疲弊度", en: "Fatigue" }] },
        { ja: "打ち手", en: "Playbook", leaves: [{ ja: "チャネル別", en: "Per Channel" }] },
      ],
    },
    {
      hub: { ja: "キックオフ論点", en: "Kickoff Agenda" },
      branches: [
        { ja: "数字共有", en: "Metrics Review", leaves: [{ ja: "現状KPI", en: "Current KPIs" }] },
        { ja: "ボトルネック", en: "Bottleneck", leaves: [{ ja: "成長阻害", en: "Growth Blocker" }, { ja: "優先度", en: "Priority" }] },
        { ja: "仮説検証", en: "Hypotheses", leaves: [{ ja: "90日計画", en: "90 Day Plan" }] },
      ],
    },
  ],
};

const AutoTypeInput = ({ suggestions, onConfirm, onIndexChange }) => {
  const [idx, setIdx] = React.useState(0);
  const [charPos, setCharPos] = React.useState(0);
  const [erasing, setErasing] = React.useState(false);
  const [confirmed, setConfirmed] = React.useState(false);
  const [paused, setPaused] = React.useState(false);
  const timerRef = React.useRef(null);

  const text = suggestions[idx] || "";
  const displayed = confirmed ? text : text.slice(0, charPos);

  React.useEffect(() => {
    if (confirmed) return;
    if (paused) {
      timerRef.current = setTimeout(() => {
        setPaused(false);
        setErasing(true);
      }, 2000);
      return () => clearTimeout(timerRef.current);
    }
    if (erasing) {
      if (charPos <= 0) {
        timerRef.current = setTimeout(() => {
          const nextIdx = (idx + 1) % suggestions.length;
          setErasing(false);
          setIdx(nextIdx);
          setCharPos(0);
          if (onIndexChange) onIndexChange(nextIdx);
        }, 300);
        return () => clearTimeout(timerRef.current);
      }
      timerRef.current = setTimeout(() => setCharPos(charPos - 1), 20);
      return () => clearTimeout(timerRef.current);
    }
    if (charPos < text.length) {
      timerRef.current = setTimeout(() => setCharPos(charPos + 1), 40);
      return () => clearTimeout(timerRef.current);
    }
    setPaused(true);
  }, [charPos, erasing, paused, confirmed, idx, text.length]);

  const handleClick = () => {
    if (confirmed) return;
    setConfirmed(true);
    setCharPos(text.length);
    clearTimeout(timerRef.current);
    onConfirm(idx);
  };

  return (
    <div onClick={handleClick} style={{
      flex: 1, height: 30, borderRadius: 8, background: "var(--bg)",
      boxShadow: "inset 0 0 0 1px var(--ink-10)",
      display: "flex", alignItems: "center", padding: "0 10px",
      font: '400 12px/1 var(--font-sans)',
      color: confirmed ? "var(--ink)" : "var(--ink-40)",
      cursor: confirmed ? "default" : "pointer",
      userSelect: "none",
      overflow: "hidden", whiteSpace: "nowrap", textOverflow: "ellipsis",
    }}>
      {displayed}
      {!confirmed && (
        <span style={{
          display: "inline-block", width: 1, height: 13,
          background: "var(--ink-40)", marginLeft: 1,
          animation: "blink 1.1s steps(2) infinite",
        }}/>
      )}
    </div>
  );
};

const DEMO_COLORS = ["#C9583C", "#B8864A", "#7A9A85", "#536A8A", "#8F6FA3"];

/* DemoMindmap — progressive reveal matching the real product (mindmap.js).
   The actual product uses D3 data-join + transitions: it starts with just
   the root, then reveals children one-by-one via setTimeout → _update().
   Each _update() triggers D3 enter transitions: links interpolate from
   the parent position to the target (300ms cubicOut), dots grow from r=0
   to final r (180ms easeBackOut overshoot 1.2), labels appear after dots.

   This component replicates that with React state: a `revealStep` counter
   advances via setTimeout, and elements render only when their step has
   been reached. CSS transitions on `r` and `opacity` handle the smooth
   interpolation. Curved quadratic paths match the product's radial links. */
const DemoMindmap = ({ tree, lang, animate }) => {
  if (!tree) return null;
  const hubLabel = tree.hub[lang] || tree.hub.en;
  const W = 320, H = 280;
  const cx = W / 2, cy = H / 2;
  const R1 = 70, R2 = 120;
  const branches = tree.branches;
  const angleStep = (2 * Math.PI) / branches.length;
  const startAngle = -Math.PI / 2;

  // Product timing: 250ms between each branch reveal, 300ms between phases
  const PER_NODE = 250;

  // Build the reveal schedule — same as the product's progressive reveal:
  // step 0: hub only
  // step 1..N: each depth-1 branch (one by one)
  // step N+1..M: each depth-2 leaf group (one by one)
  const totalBranches = branches.length;
  const totalSteps = 1 + totalBranches + branches.filter(b => b.leaves && b.leaves.length).length;

  const [step, setStep] = React.useState(animate ? 0 : totalSteps);

  React.useEffect(() => {
    if (!animate) return;
    setStep(0);
    const timers = [];
    // Step 0: hub appears immediately
    // Steps 1..N: branches revealed one by one (400ms initial delay, 250ms apart)
    for (let i = 0; i < totalBranches; i++) {
      timers.push(setTimeout(() => setStep(s => Math.max(s, 1 + i)), 400 + i * PER_NODE));
    }
    // Steps N+1..M: leaf groups revealed one by one (300ms after last branch, 250ms apart)
    const leafStart = 400 + totalBranches * PER_NODE + 300;
    let leafIdx = 0;
    for (let i = 0; i < totalBranches; i++) {
      if (branches[i].leaves && branches[i].leaves.length) {
        timers.push(setTimeout(() => setStep(s => Math.max(s, 1 + totalBranches + leafIdx)),
          leafStart + leafIdx * PER_NODE));
        leafIdx++;
      }
    }
    return () => timers.forEach(clearTimeout);
  }, [animate, tree]);

  // Helper: quadratic curve from (x1,y1) to (x2,y2) with perpendicular bulge
  const curvedPath = (x1, y1, x2, y2, curlFactor) => {
    const mx = (x1 + x2) / 2, my = (y1 + y2) / 2;
    const dx = x2 - x1, dy = y2 - y1;
    const len = Math.sqrt(dx * dx + dy * dy) || 1;
    // Perpendicular offset for the control point
    const px = -dy / len * curlFactor;
    const py = dx / len * curlFactor;
    return "M " + x1 + " " + y1 + " Q " + (mx + px) + " " + (my + py) + " " + x2 + " " + y2;
  };

  // Track which leaf groups have been revealed (for leaf step mapping)
  let leafGroupIdx = 0;
  const branchLeafStep = branches.map(b => {
    if (b.leaves && b.leaves.length) {
      return 1 + totalBranches + leafGroupIdx++;
    }
    return Infinity;
  });

  return (
    <svg viewBox={"0 0 " + W + " " + H} width="100%" height="100%"
      style={{ display: "block", maxHeight: "100%" }}>

      {/* Sonar rings — 3 concentric pulses from hub, active during reveal */}
      {animate && step < totalSteps && [0, 0.8, 1.6].map((delay, i) => (
        <circle key={"sonar" + i} cx={cx} cy={cy} r="7" fill="none"
          stroke="var(--accent)" strokeWidth="1.5"
          style={{
            opacity: 0,
            animation: "demoSonar 2.4s cubic-bezier(0.2, 0.7, 0.4, 1) " + delay + "s infinite",
          }}/>
      ))}

      {/* Hub dot + label — always visible once step >= 0 */}
      <circle cx={cx} cy={cy}
        r={step >= 0 ? 7 : 0}
        fill="var(--ink)"
        style={{ transition: "r 180ms cubic-bezier(0.34, 1.56, 0.64, 1)" }}/>
      {step >= 0 && (
        <text x={cx} y={cy - 14} textAnchor="middle"
          fontSize="10" fill="var(--ink)" fontWeight="600"
          fontFamily="var(--font-sans)"
          style={{ opacity: step >= 1 ? 1 : 0.6, transition: "opacity 200ms ease-out" }}>
          {hubLabel}
        </text>
      )}

      {branches.map((b, i) => {
        const angle = startAngle + i * angleStep;
        const mx = cx + R1 * Math.cos(angle);
        const my = cy + R1 * Math.sin(angle);
        const branchLabel = b[lang] || b.en;
        const branchStep = 1 + i;
        const branchVisible = step >= branchStep;
        const curlSign = i % 2 === 0 ? 1 : -1;
        const curlAmount = 15 * curlSign;

        return (
          <g key={i}>
            {/* Hub→Branch curved link — grows by interpolating from hub to target */}
            <path
              d={branchVisible
                ? curvedPath(cx, cy, mx, my, curlAmount)
                : curvedPath(cx, cy, cx, cy, 0)}
              stroke="var(--ink-20)" strokeWidth="1.2" fill="none" strokeLinecap="round"
              style={{ transition: "d 300ms cubic-bezier(0.33, 1, 0.68, 1)" }}/>

            {/* Branch dot — grows from r=0 after link arrives */}
            <circle cx={mx} cy={my}
              r={branchVisible ? 4.5 : 0}
              fill={DEMO_COLORS[i % DEMO_COLORS.length]}
              style={{ transition: "r 180ms cubic-bezier(0.34, 1.56, 0.64, 1) 280ms" }}/>

            {/* Branch label — fades in after dot pops */}
            <text x={mx} y={my - 9} textAnchor="middle"
              fontSize="9" fill="var(--ink)"
              fontFamily="var(--font-sans)" fontWeight="500"
              style={{
                opacity: branchVisible ? 1 : 0,
                transition: "opacity 200ms ease-out 400ms",
              }}>
              {branchLabel}
            </text>

            {/* Leaves — revealed as a group in the second phase */}
            {(b.leaves || []).map((leaf, j) => {
              const leafAngle = angle + (j - (b.leaves.length - 1) / 2) * 0.45;
              const lx = cx + R2 * Math.cos(leafAngle);
              const ly = cy + R2 * Math.sin(leafAngle);
              const leafLabel = leaf[lang] || leaf.en;
              const leavesVisible = step >= branchLeafStep[i];
              const leafCurl = (8 + j * 4) * curlSign;

              return (
                <g key={j}>
                  {/* Branch→Leaf curved link */}
                  <path
                    d={leavesVisible
                      ? curvedPath(mx, my, lx, ly, leafCurl)
                      : curvedPath(mx, my, mx, my, 0)}
                    stroke="var(--ink-10)" strokeWidth="0.9" fill="none" strokeLinecap="round"
                    style={{ transition: "d 300ms cubic-bezier(0.33, 1, 0.68, 1)" }}/>

                  {/* Leaf dot */}
                  <circle cx={lx} cy={ly}
                    r={leavesVisible ? 3 : 0}
                    fill={DEMO_COLORS[i % DEMO_COLORS.length]} opacity="0.7"
                    style={{ transition: "r 180ms cubic-bezier(0.34, 1.56, 0.64, 1) 280ms" }}/>

                  {/* Leaf label */}
                  <text x={lx} y={ly - 6} textAnchor="middle"
                    fontSize="7.5" fill="var(--ink-60)"
                    fontFamily="var(--font-sans)"
                    style={{
                      opacity: leavesVisible ? 1 : 0,
                      transition: "opacity 200ms ease-out 400ms",
                    }}>
                    {leafLabel}
                  </text>
                </g>
              );
            })}
          </g>
        );
      })}
    </svg>
  );
};

const SAMPLE = {
  ja: [
    { who: "user", t: "明日クライアント（D2Cのスキンケアブランド、年商18億）と販売戦略のキックオフ。CACが去年の1.4倍に膨らんでて、CEOは「広告を倍にしてでも成長を取り戻したい」って言ってる。私はそれは違うと思ってる。話の入り方どうしよう。" },
    { who: "ai", t: "CACが1.4倍になってる時点で、**広告倍プッシュは赤字を倍にするだけ**の確率が高いです。けど、CEOの「成長を取り戻したい」を否定から入ると刺さらないので、入り方を一段ずらすのが良さそう。\n\n私ならこの順:\n\n1. **同意できる事実から始める** — 「成長は取り戻すべき」ここは合意。\n2. **問題の置き換え** — 議題は『広告予算をどうするか』ではなく『1人の顧客から取り戻すべきLTV』。\n3. **数字で見せる** — CAC 1.4倍 + LTV横ばい = ペイバック期間が何ヶ月延びてる？\n\nLTV側のデータって直近で見れる状態ですか？" },
    { who: "user", t: "見れる。リピート率が18ヶ月で42% → 31%に落ちてる。新規は獲れてるけど2回目が来ない。" },
    { who: "ai", t: "それなら話はシンプルです — **獲得の問題じゃなくて定着の問題**。広告を倍にすると、漏れてるバケツに水を倍そそぐ構図になります。\n\n1枚目のスライドはこういう構成が綺麗:\n\n- **左**: CAC推移（1.4倍）\n- **右**: 2回目購入率（42% → 31%）\n- **下**: ペイバック期間 8ヶ月 → 14ヶ月（仮）\n\n結論を言葉にしないで、CEOに**自分で気づかせる**のがコツ。" },
  ],
  en: [
    { who: "user", t: "Got a kickoff tomorrow with a client — D2C skincare brand, ~$12M revenue. Their CAC is up 1.4× YoY and the CEO wants to \"double down on paid to get growth back.\" I think that's wrong. How do I open the conversation?" },
    { who: "ai", t: "If CAC is already 1.4× and you double paid, you're most likely **doubling the bleed, not the growth.** But opening with \"you're wrong\" doesn't land — you need to redirect, not contradict.\n\nI'd structure the open like this:\n\n1. **Agree on what's true** — \"Growth needs to come back\" — full agreement, lock it in.\n2. **Reframe the question** — the real question isn't \"how much to spend on paid\", it's \"how much LTV are we leaving on the table per customer?\"\n3. **Let the numbers speak** — CAC 1.4× + flat LTV = payback period stretched by how many months?\n\nDo you have LTV / repeat-rate data you can pull before tomorrow?" },
    { who: "user", t: "Yes — repeat rate over 18 months has fallen from 42% to 31%. They're acquiring fine, customers just aren't coming back for a second purchase." },
    { who: "ai", t: "Then the story tells itself — **this isn't an acquisition problem, it's a retention problem.** Doubling ad spend is pouring twice the water into a leaking bucket.\n\nFor your opening slide:\n\n- **Left**: CAC trend (1.4×)\n- **Right**: 2nd-purchase rate (42% → 31%)\n- **Bottom**: Payback period — likely 8mo → ~14mo\n\nDon't say the conclusion out loud — let the CEO see it." },
  ],
};

const renderRich = (text) => {
  const lines = text.split("\n");
  const out = [];
  let listBuf = [];
  const flushList = () => {
    if (!listBuf.length) return;
    out.push(<ol key={out.length} style={{
      margin: "8px 0 8px 18px", padding: 0, display: "flex", flexDirection: "column", gap: 4,
    }}>{listBuf.map((l, i) => <li key={i} style={{ paddingLeft: 4 }}>{renderInline(l)}</li>)}</ol>);
    listBuf = [];
  };
  lines.forEach((l, i) => {
    const m = l.match(/^\d+\.\s+(.*)$/);
    if (m) listBuf.push(m[1]);
    else {
      flushList();
      if (l.trim() === "") out.push(<div key={"sp" + i} style={{ height: 6 }}/>);
      else out.push(<div key={i}>{renderInline(l)}</div>);
    }
  });
  flushList();
  return out;
};
const renderInline = (s) => {
  const parts = s.split(/(\*\*[^*]+\*\*)/g);
  return parts.map((p, i) => p.startsWith("**")
    ? <b key={i} style={{ color: "var(--ink)", fontWeight: 600 }}>{p.slice(2, -2)}</b>
    : <span key={i}>{p}</span>);
};

const Bubble = ({ who, t, last }) => {
  const isAi = who === "ai";
  return (
    <div style={{
      display: "flex", gap: 10, alignItems: "flex-start",
      flexDirection: isAi ? "row" : "row-reverse",
    }}>
      <div style={{
        width: 24, height: 24, borderRadius: "50%", flex: "0 0 auto",
        display: "flex", alignItems: "center", justifyContent: "center",
        background: isAi ? "var(--ink)" : "var(--bg-deep)",
        color: isAi ? "var(--bg)" : "var(--ink-60)",
        font: '600 9px/1 var(--font-mono)', letterSpacing: "0.06em",
        marginTop: 2,
      }}>
        {isAi ? "AI" : "YOU"}
      </div>
      <div style={{
        maxWidth: "82%",
        padding: isAi ? 0 : "9px 12px",
        borderRadius: 12,
        background: isAi ? "transparent" : "var(--bg-raised)",
        boxShadow: isAi ? "none" : "inset 0 0 0 1px var(--ink-10)",
        font: '400 12.5px/1.55 var(--font-sans)',
        color: "var(--ink-60)",
        textAlign: "left",
      }}>
        {renderRich(t)}
        {isAi && last && (
          <span style={{
            display: "inline-block", width: 5, height: 13,
            background: "var(--accent)", marginLeft: 2,
            verticalAlign: -2, animation: "blink 1.1s steps(2) infinite",
          }}/>
        )}
      </div>
    </div>
  );
};

const InteractiveChatPane = ({ lang, onReady, onChoose, autoPlay = false, fast = false }) => {
  const content = INTERACTIVE_CONTENT[lang] || INTERACTIVE_CONTENT.en;
  const [phase, setPhase] = React.useState("idle");
  const [chosenIdx, setChosenIdx] = React.useState(null);
  const [responseChars, setResponseChars] = React.useState(0);
  const [messages, setMessages] = React.useState([]);
  const [currentSuggestionIdx, setCurrentSuggestionIdx] = React.useState(0);
  const timerRef = React.useRef(null);
  const scrollRef = React.useRef(null);

  const staticMsgs = (SAMPLE[lang] || SAMPLE.en).slice(0, 2);

  React.useEffect(() => {
    if (scrollRef.current) {
      scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
    }
  }, [messages, responseChars]);

  React.useEffect(() => {
    if (phase === "ready" && onReady) onReady();
  }, [phase]);

  // Auto-play: auto-send the first suggestion after a short delay
  React.useEffect(() => {
    if (!autoPlay || phase !== "idle") return;
    const t = setTimeout(() => handleConfirm(0), fast ? 250 : 800);
    return () => clearTimeout(t);
  }, [autoPlay]);

  const handleConfirm = (idx) => {
    setChosenIdx(idx);
    setMessages([{ who: "user", t: content.suggestions[idx] }]);
    setTimeout(() => setPhase("sent"), 100);
    if (onChoose) onChoose(idx);
  };

  React.useEffect(() => {
    if (phase !== "sent") return;
    timerRef.current = setTimeout(() => {
      setPhase("responding");
      setResponseChars(0);
    }, fast ? 400 : 1200);
    return () => clearTimeout(timerRef.current);
  }, [phase]);

  React.useEffect(() => {
    if (phase !== "responding" || chosenIdx === null) return;
    const fullText = content.responses[chosenIdx];
    if (responseChars < fullText.length) {
      timerRef.current = setTimeout(() => {
        setResponseChars(responseChars + (fast ? 2 : 1));
      }, fast ? 12 : 30);
      return () => clearTimeout(timerRef.current);
    }
    setPhase("ready");
  }, [phase, responseChars, chosenIdx]);

  let visibleMsgs = staticMsgs.slice();
  if (messages.length > 0) {
    visibleMsgs = visibleMsgs.concat(messages);
  }
  if (phase === "responding" || phase === "ready" || phase === "generated") {
    const fullResponse = content.responses[chosenIdx] || "";
    const partialResponse = phase === "responding"
      ? fullResponse.slice(0, responseChars)
      : fullResponse;
    visibleMsgs = visibleMsgs.concat([{ who: "ai", t: partialResponse }]);
  }

  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%", background: "var(--bg-raised)" }}>
      <div style={{
        padding: "8px 16px", display: "flex", alignItems: "center", gap: 10,
        borderBottom: "1px solid var(--ink-10)", flex: "0 0 auto",
      }}>
        <span className="mono-hi" style={{ fontSize: 9.5, letterSpacing: "0.18em" }}>ChatGPT</span>
        <span style={{ width: 1, height: 10, background: "var(--ink-10)" }}/>
        <span className="mono" style={{ color: "var(--ink-40)", fontSize: 10 }}>
          {lang === "ja" ? "販売戦略レビュー" : "Sales-strategy review"}
        </span>
      </div>
      <div ref={scrollRef} style={{
        flex: 1, padding: "16px 18px", overflowY: "auto",
        display: "flex", flexDirection: "column", gap: 16,
      }}>
        {visibleMsgs.map((m, i) => (
          <Bubble key={i} who={m.who} t={m.t}
            last={i === visibleMsgs.length - 1 && m.who === "ai" && phase === "responding"}/>
        ))}
      </div>
      <div style={{
        padding: "8px 12px 10px", borderTop: "1px solid var(--ink-10)",
        display: "flex", alignItems: "center", gap: 8, flex: "0 0 auto",
      }}>
        {phase === "idle" ? (
          <AutoTypeInput suggestions={content.suggestions} onConfirm={handleConfirm} onIndexChange={setCurrentSuggestionIdx}/>
        ) : (
          <div style={{
            flex: 1, height: 30, borderRadius: 8, background: "var(--bg)",
            boxShadow: "inset 0 0 0 1px var(--ink-10)",
            display: "flex", alignItems: "center", padding: "0 10px",
            font: '400 12px/1 var(--font-sans)', color: "var(--ink-40)",
          }}>
            {lang === "ja" ? "メッセージを送信…" : "Send a message…"}
          </div>
        )}
        <button onClick={() => { if (phase === "idle") handleConfirm(currentSuggestionIdx); }} style={{
          width: 30, height: 30, borderRadius: 8, border: 0,
          cursor: phase === "idle" ? "pointer" : "default",
          background: "var(--accent)", color: "#fff",
          display: "flex", alignItems: "center", justifyContent: "center",
          opacity: phase === "idle" ? 1 : 0.5,
        }}>
          <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
            <path d="M12 19V5M5 12l7-7 7 7"/>
          </svg>
        </button>
      </div>
    </div>
  );
};

/* ── chat-only pane (no chrome) ──────────────────────────────────────── */
const ChatPane = ({ lang }) => {
  const msgs = SAMPLE[lang] || SAMPLE.en;
  return (
    <div style={{ display: "flex", flexDirection: "column", height: "100%", background: "var(--bg-raised)" }}>
      <div style={{
        padding: "8px 16px", display: "flex", alignItems: "center", gap: 10,
        borderBottom: "1px solid var(--ink-10)", flex: "0 0 auto",
      }}>
        <span className="mono-hi" style={{ fontSize: 9.5, letterSpacing: "0.18em" }}>ChatGPT</span>
        <span style={{ width: 1, height: 10, background: "var(--ink-10)" }}/>
        <span className="mono" style={{ color: "var(--ink-40)", fontSize: 10 }}>
          {lang === "ja" ? "販売戦略レビュー" : "Sales-strategy review"}
        </span>
      </div>
      <div style={{
        flex: 1, padding: "16px 18px", overflowY: "auto",
        display: "flex", flexDirection: "column", gap: 16,
      }}>
        {msgs.map((m, i) => <Bubble key={i} who={m.who} t={m.t} last={i === msgs.length - 1 && m.who === "ai"}/>)}
      </div>
      <div style={{
        padding: "8px 12px 10px", borderTop: "1px solid var(--ink-10)",
        display: "flex", alignItems: "center", gap: 8, flex: "0 0 auto",
      }}>
        <div style={{
          flex: 1, height: 30, borderRadius: 8, background: "var(--bg)",
          boxShadow: "inset 0 0 0 1px var(--ink-10)",
          display: "flex", alignItems: "center", padding: "0 10px",
          font: '400 12px/1 var(--font-sans)', color: "var(--ink-40)",
        }}>
          {lang === "ja" ? "メッセージを送信…" : "Send a message…"}
        </div>
        <button style={{
          width: 30, height: 30, borderRadius: 8, border: 0, cursor: "pointer",
          background: "var(--accent)", color: "#fff",
          display: "flex", alignItems: "center", justifyContent: "center",
        }}>
          <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.2" strokeLinecap="round" strokeLinejoin="round">
            <path d="M12 19V5M5 12l7-7 7 7"/>
          </svg>
        </button>
      </div>
    </div>
  );
};

/* ── Interactive extension panel for the hero demo ──────────────────── */
const InteractiveExtensionPanel = ({ lang, isReady, chosenIdx, autoPlay = false, fast = false, onDone, onGenerating }) => {
  const [phase, setPhase] = React.useState("idle");
  // idle → canFetch → fetching → fetched → canGenerate → generating

  React.useEffect(() => {
    if (isReady && phase === "idle") setPhase("canFetch");
  }, [isReady]);

  React.useEffect(() => {
    if (phase === "fetched") {
      const t = setTimeout(() => setPhase("canGenerate"), fast ? 200 : 600);
      return () => clearTimeout(t);
    }
  }, [phase]);

  // Auto-play: auto-fetch and auto-generate without user clicks
  React.useEffect(() => {
    if (!autoPlay) return;
    if (phase === "canFetch") {
      const t = setTimeout(() => { setPhase("fetching"); setTimeout(() => setPhase("fetched"), fast ? 250 : 800); }, fast ? 150 : 400);
      return () => clearTimeout(t);
    }
    if (phase === "canGenerate") {
      const t = setTimeout(() => setPhase("generating"), fast ? 150 : 400);
      return () => clearTimeout(t);
    }
  }, [autoPlay, phase]);

  const handleFetch = () => {
    if (phase !== "canFetch") return;
    setPhase("fetching");
    setTimeout(() => setPhase("fetched"), 800);
  };

  const handleGenerate = () => {
    if (phase !== "canGenerate") return;
    setPhase("generating");
  };

  // Fire onGenerating when mindmap starts, onDone after it completes (~5s)
  React.useEffect(() => {
    if (phase !== "generating") return;
    if (onGenerating) onGenerating();
    if (!onDone) return;
    const t = setTimeout(onDone, fast ? 2500 : 5000);
    return () => clearTimeout(t);
  }, [phase, onDone, onGenerating]);

  const afterFetch = ["fetched","canGenerate","generating"].includes(phase);

  const railIcons = [
    { k:"map", kind:"mark" },
    { k:"refresh", d:"M4.5 10 A4.5 4.5 0 0 1 13 7.5 M13 4.5 L13 7.5 L10 7.5 M13.5 8 A4.5 4.5 0 0 1 5 10.5 M5 13.5 L5 10.5 L8 10.5" },
    { k:"divider" },
    { k:"tag", d:"M14.5 3H9l-6 6 6 6h5.5a2 2 0 0 0 2-2V5a2 2 0 0 0-2-2z M7 9h.01" },
    { k:"search", d:"M4 8 A4 4 0 1 1 12 8 A4 4 0 1 1 4 8 M11 11 L15 15" },
    { k:"star", d:"M9 2l2.2 4.5 5 .7-3.6 3.5.9 5L9 13.3 4.6 15.7l.9-5L1.8 7.2l5-.7L9 2z" },
    { k:"q", d:"M9 14 A5 5 0 1 1 9 4 A5 5 0 1 1 9 14 M7.5 7.5a1.5 1.5 0 0 1 3 0c0 1-1.5 1.3-1.5 2.5 M9 12.5L9 12.51" },
    { k:"glasses", d:"M5 9a3 3 0 1 0 6 0 3 3 0 1 0-6 0 M7 9h4 M11 9a3 3 0 1 0 6 0 3 3 0 1 0-6 0 M2 9L3.5 7 M16 9L14.5 7" },
    { k:"history", d:"M9 14 A5 5 0 1 1 9 4 A5 5 0 1 1 9 14 M9 6v3l2 2" },
    { k:"divider" },
    { k:"download", d:"M15 11v3a1.5 1.5 0 0 1-1.5 1.5h-9A1.5 1.5 0 0 1 3 14v-3 M5.5 8L9 11.5 12.5 8 M9 11.5V3" },
  ];

  const stats = [
    { jp:"課題", en:"issue", c:"#E74C3C", n:4 },
    { jp:"決定", en:"decision", c:"#2E86DE", n:6 },
    { jp:"情報", en:"info", c:"#27AE60", n:3 },
  ];

  const tabs = lang === "ja"
    ? ["販売戦略レビュー","JBN UI改善","チェックイン"]
    : ["Sales strategy","JP UI review","Check-in"];

  const NM_BG = "#F5F0E8";
  const NM_S  = "#C8BFAE";
  const NM_H  = "#FFFDF8";
  const INK   = "#3B352D";
  const INK_M = "#8C8070";
  const RUST  = "#C9583C";

  return (
    <div style={{
      background:NM_BG, fontFamily:"'IBM Plex Sans', var(--font-sans)", color:INK,
      display:"grid", gridTemplateColumns:"52px 1fr", height:"100%", overflow:"hidden",
    }}>
      {/* Rail */}
      <div style={{
        padding:"10px 6px", display:"flex", flexDirection:"column",
        alignItems:"center", gap:5,
        borderRight:`1px solid rgba(59,53,45,0.08)`,
      }}>
        {railIcons.map((ic, i) => {
          if (ic.k === "divider") return (
            <div key={i} style={{ width:26, height:1, background:"rgba(59,53,45,0.1)", margin:"1px 0" }}/>
          );
          const isMap = ic.kind === "mark", isRefresh = ic.k === "refresh";
          const clickable = (isRefresh && phase === "canFetch") || (isMap && phase === "canGenerate");
          const active = isMap && !clickable;
          const iconColor = clickable ? RUST : INK_M;
          return (
            <div key={i} onClick={() => { if (isRefresh) handleFetch(); if (isMap) handleGenerate(); }}
              style={{
                width:38, height:38, borderRadius:10, background:NM_BG,
                display:"grid", placeItems:"center",
                cursor: clickable ? "pointer" : "default",
                boxShadow: active
                  ? `inset 2px 2px 5px ${NM_S}, inset -2px -2px 5px ${NM_H}`
                  : `3px 3px 7px ${NM_S}, -3px -3px 7px ${NM_H}` + (clickable ? `, 0 0 0 2px rgba(201,88,60,0.3)` : ""),
                animation: clickable ? "railPulse 2s ease-in-out infinite" : "none",
                transition: "box-shadow 300ms ease",
              }}>
              {ic.kind === "mark" ? (
                <svg width="17" height="17" viewBox="0 0 24 24" fill="none">
                  <path d="M12 12L5.5 5.5 M12 12L18.5 5.5 M12 12L5.5 18.5 M12 12L18.5 18.5"
                    stroke="#6BBF8A" strokeWidth="1.4" strokeLinecap="round"/>
                  <circle cx="12" cy="12" r="2.6" fill="#6BBF8A"/>
                  <circle cx="5" cy="5" r="2" stroke="#6BBF8A" strokeWidth="1.2" fill="none"/>
                  <circle cx="19" cy="5" r="2" stroke="#6BBF8A" strokeWidth="1.2" fill="none"/>
                  <circle cx="5" cy="19" r="2" stroke="#6BBF8A" strokeWidth="1.2" fill="none"/>
                  <circle cx="19" cy="19" r="2" stroke="#6BBF8A" strokeWidth="1.2" fill="none"/>
                </svg>
              ) : (
                <svg width="14" height="14" viewBox="0 0 18 18" fill="none"
                  stroke={isRefresh && phase === "fetching" ? RUST : iconColor}
                  strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round"
                  style={isRefresh && phase === "fetching" ? { animation:"spin 1s linear infinite" } : undefined}>
                  <path d={ic.d}/>
                </svg>
              )}
            </div>
          );
        })}
        <div style={{ flex:1 }}/>
        <div style={{
          width:34, height:34, borderRadius:8, background:NM_BG,
          display:"grid", placeItems:"center",
          boxShadow:`3px 3px 7px ${NM_S}, -3px -3px 7px ${NM_H}`,
        }}>
          <svg width="14" height="14" viewBox="0 0 18 18" fill="none" stroke={INK_M} strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round">
            <circle cx="9" cy="9" r="2.2"/><path d="M9 2.5L9 4 M9 14L9 15.5 M3.5 9L5 9 M13 9L14.5 9 M5 5L6 6 M12 12L13 13 M13 5L12 6 M6 12L5 13"/>
          </svg>
        </div>
        <div style={{
          width:34, height:34, borderRadius:8, background:NM_BG,
          display:"grid", placeItems:"center",
          boxShadow:`3px 3px 7px ${NM_S}, -3px -3px 7px ${NM_H}`,
          fontSize:13, color:"#D4B85C",
        }}>☽</div>
        <div style={{
          width:34, height:34, borderRadius:8, background:NM_BG,
          display:"grid", placeItems:"center",
          boxShadow:`3px 3px 7px ${NM_S}, -3px -3px 7px ${NM_H}`,
          fontFamily:"var(--font-mono)", fontSize:9, fontWeight:600,
          color:INK_M, letterSpacing:"0.08em",
        }}>JA</div>
        <div style={{
          width:34, height:34, borderRadius:8, background:NM_BG,
          display:"grid", placeItems:"center",
          boxShadow:`3px 3px 7px ${NM_S}, -3px -3px 7px ${NM_H}`,
          fontFamily:"var(--font-mono)", fontSize:9, fontWeight:600,
          color:INK_M,
        }}>20</div>
        <div style={{
          width:34, height:34, borderRadius:8, background:NM_BG,
          display:"grid", placeItems:"center",
          boxShadow:`inset 2px 2px 5px ${NM_S}, inset -2px -2px 5px ${NM_H}`,
          fontFamily:"'SF Mono', ui-monospace, monospace", fontSize:8, fontWeight:700,
          color:"#E05A5A", letterSpacing:"0.02em",
          position:"relative",
        }}>LIVE
          <span style={{
            position:"absolute", top:5, right:5, width:5, height:5,
            borderRadius:"50%", background:"#DC2626",
            boxShadow:"0 0 3px rgba(220,38,38,0.6)",
          }}/>
        </div>
      </div>

      {/* Main */}
      <div style={{ display:"flex", flexDirection:"column", minWidth:0, overflow:"hidden" }}>
        {/* Header */}
        <div style={{ display:"flex", alignItems:"center", padding:"10px 14px 8px", gap:8 }}>
          <svg width="22" height="22" viewBox="0 0 144 144" fill="none">
            <g stroke={INK} strokeWidth="3.2" strokeLinecap="round">
              <path d="M72 72 Q100 78 118 72"/><path d="M72 72 Q44 66 26 72"/>
              <path d="M72 72 Q92 50 95 32"/><path d="M72 72 Q52 94 49 112"/>
              <path d="M72 72 Q52 50 49 32"/><path d="M72 72 Q92 94 95 112"/>
            </g>
            <circle cx="118" cy="72" r="5" fill="#F0A89E"/><circle cx="26" cy="72" r="5" fill="#F0A89E"/>
            <circle cx="95" cy="32" r="5" fill="#B5C9BB"/><circle cx="49" cy="112" r="5" fill="#B5C9BB"/>
            <circle cx="49" cy="32" r="5" fill="#9CB6CE"/><circle cx="95" cy="112" r="5" fill="#9CB6CE"/>
            <circle cx="72" cy="72" r="7" fill={INK}/>
          </svg>
          <div>
            <div style={{ fontFamily:"'IBM Plex Sans', var(--font-sans)", fontWeight:700, fontSize:14, letterSpacing:"-0.01em", lineHeight:1.1 }}>
              <span style={{ color:INK }}>LL</span>
              <span style={{ color:RUST }}>Mind</span>
              <span style={{ color:INK }}>Map</span>
            </div>
            <div style={{
              fontFamily:"var(--font-mono)", fontSize:8.5,
              color:INK_M, letterSpacing:"0.01em", marginTop:2,
            }}>Trace, edit, and surpass any LLM chat</div>
          </div>
        </div>

        {/* History */}
        <div style={{
          display:"flex", alignItems:"center", justifyContent:"space-between",
          padding:"5px 14px 6px", borderBottom:`1px solid rgba(59,53,45,0.08)`,
        }}>
          <span style={{ fontSize:11, color:INK_M }}>
            {lang === "ja" ? "履歴 (1/1)" : "History (1/1)"}
          </span>
          <svg width="8" height="8" viewBox="0 0 10 10" fill="none">
            <path d="M3 1.5 L6.5 5 L3 8.5" stroke={INK_M} strokeWidth="1.2" strokeLinecap="round" strokeLinejoin="round"/>
          </svg>
        </div>

        {/* Banner — appears after fetch */}
        {afterFetch && (
          <div style={{ padding:"8px 14px 4px", animation:"fadeIn 400ms ease-out" }}>
            <div style={{
              padding:"7px 10px", background:NM_BG, borderRadius:8,
              boxShadow:`inset 2px 2px 4px ${NM_S}, inset -2px -2px 4px ${NM_H}`,
              display:"flex", alignItems:"center", gap:6,
              fontSize:10.5, color:"#4B8263",
            }}>
              <span style={{
                width:5, height:5, borderRadius:"50%",
                background:"#4B8263", boxShadow:"0 0 0 2px rgba(75,130,99,0.2)",
              }}/>
              {lang === "ja" ? "会話データが更新されました" : "Conversation synced"}
            </div>
          </div>
        )}

        {/* Stats chips — appear after fetch */}
        {afterFetch && (
          <div style={{
            display:"flex", flexWrap:"wrap", gap:5, padding:"4px 14px 6px",
            animation:"fadeIn 400ms ease-out 200ms both",
          }}>
            {stats.map(s => (
              <span key={s.en} style={{
                display:"inline-flex", alignItems:"center", gap:5,
                height:22, padding:"0 8px", borderRadius:999,
                background:NM_BG, fontSize:10, color:INK, fontWeight:500,
                boxShadow:`-1.5px -1.5px 4px ${NM_H}, 2px 2px 5px ${NM_S}`,
              }}>
                <span style={{ width:6, height:6, borderRadius:"50%", background:s.c, display:"inline-block" }}/>
                {lang === "ja" ? s.jp : s.en}
                <span style={{ fontFamily:"var(--font-mono)", fontSize:9, color:INK_M, marginLeft:1 }}>{s.n}</span>
              </span>
            ))}
          </div>
        )}

        {/* Tabs */}
        <div style={{ display:"flex", gap:6, padding:"6px 14px 8px", overflow:"hidden" }}>
          {tabs.map((t, i) => {
            const isActive = i === 0;
            return (
              <div key={i} style={{
                flex:1, minWidth:0,
                display:"flex", alignItems:"center", justifyContent:"space-between", gap:4,
                padding:"5px 8px", borderRadius:6,
                background: isActive ? NM_BG : "transparent",
                boxShadow: isActive ? `inset 1.5px 1.5px 3px ${NM_S}, inset -1.5px -1.5px 3px ${NM_H}` : "none",
                fontSize:10, color: isActive ? INK : INK_M,
                fontWeight: isActive ? 600 : 400,
                whiteSpace:"nowrap", overflow:"hidden", textOverflow:"ellipsis",
              }}>
                <span style={{ overflow:"hidden", textOverflow:"ellipsis" }}>{t}</span>
                {isActive && <span style={{ color:INK_M, fontSize:11, flexShrink:0 }}>×</span>}
              </div>
            );
          })}
        </div>

        {/* Canvas */}
        <div style={{
          flex:1, position:"relative", overflow:"hidden",
          display:"flex", alignItems:"center", justifyContent:"center",
        }}>
          {phase === "generating" ? (
            <DemoMindmap tree={INTERACTIVE_CONTENT.trees[chosenIdx != null ? chosenIdx : 0]} lang={lang} animate={true}/>
          ) : phase === "fetching" ? (
            <div style={{ display:"flex", flexDirection:"column", alignItems:"center", gap:8 }}>
              <svg width="24" height="24" viewBox="0 0 32 32" fill="none" style={{ animation:"spin 2s linear infinite" }}>
                <circle cx="16" cy="16" r="14" stroke={RUST} strokeWidth="1.4" strokeDasharray="20 60"/>
              </svg>
              <span style={{ fontFamily:"var(--font-mono)", fontSize:9, color:INK_M, letterSpacing:"0.08em" }}>
                {lang === "ja" ? "同期中…" : "SYNCING…"}
              </span>
            </div>
          ) : (
            <div style={{ opacity:0.3 }}>
              <svg width="48" height="48" viewBox="0 0 48 48" fill="none">
                <circle cx="24" cy="24" r="20" stroke={INK_M} strokeWidth="0.8" strokeDasharray="3 3"/>
                <circle cx="24" cy="24" r="3" fill={INK_M}/>
              </svg>
            </div>
          )}
        </div>
      </div>
    </div>
  );
};

/* ── single Mac window: chat + extension panel side-by-side ──────────── */
const ChatPlusExtensionWindow = ({ lang = "en", interactive = false, autoPlay = false, fast = false, onStep }) => {
  const [isReady, setIsReady] = React.useState(false);
  const [chosenIdx, setChosenIdx] = React.useState(null);
  const [loopKey, setLoopKey] = React.useState(0);

  // Report demo step: 0=chat, 1=generate, 2=explore
  const stepRef = React.useRef(0);
  const reportStep = (s) => { if (onStep && stepRef.current !== s) { stepRef.current = s; onStep(s); } };

  const handleDone = React.useCallback(() => {
    if (!autoPlay) return;
    const t = setTimeout(() => {
      setIsReady(false);
      setChosenIdx(null);
      reportStep(0);
      setLoopKey(k => k + 1);
    }, fast ? 1200 : 3000);
    return () => clearTimeout(t);
  }, [autoPlay]);

  return (
    <div style={{
      width: "100%", maxWidth: 1400,
      height: 580,
      borderRadius: 14, overflow: "hidden",
      background: "var(--bg-raised)",
      boxShadow:
        "0 1px 0 rgba(255,255,255,0.6) inset, " +
        "0 0 0 1px var(--ink-10), " +
        "0 28px 56px -16px rgba(26,24,21,0.28), " +
        "0 10px 22px -12px rgba(26,24,21,0.20)",
      fontFamily: 'var(--font-sans)',
      display: "flex", flexDirection: "column",
    }}>
      {/* titlebar */}
      <div style={{
        height: 36, display: "flex", alignItems: "center",
        padding: "0 14px", gap: 14, flex: "0 0 auto",
        background: "linear-gradient(180deg, rgba(26,24,21,0.05), rgba(26,24,21,0.01))",
        borderBottom: "1px solid var(--ink-10)",
      }}>
        <TrafficLights/>
        <div style={{ flex: 1, display: "flex", justifyContent: "center", alignItems: "center", gap: 8 }}>
          <span style={{
            font: '500 12px/1 var(--font-sans)', color: "var(--ink-60)",
            letterSpacing: "-0.005em",
          }}>
            {lang === "ja" ? "Chrome — chatgpt.com / LLMindMap" : "Chrome — chatgpt.com / LLMindMap"}
          </span>
        </div>
        <div style={{ width: 42 }}/>
      </div>

      {/* body — chat + extension side by side */}
      <div style={{ flex: 1, display: "flex", minHeight: 0 }}>
        <div style={{
          flex: "0 0 50%", minWidth: 0,
          borderRight: "1px solid var(--ink-10)",
        }}>
          {interactive ? (
            <InteractiveChatPane key={"chat"+loopKey} lang={lang} onReady={() => { setIsReady(true); reportStep(1); }} onChoose={(idx) => setChosenIdx(idx)} autoPlay={autoPlay} fast={fast}/>
          ) : (
            <ChatPane lang={lang}/>
          )}
        </div>
        <div style={{ flex: "0 0 50%", minWidth: 0, overflow: "hidden" }}>
          {interactive ? (
            <InteractiveExtensionPanel key={"ext"+loopKey} lang={lang} isReady={isReady} chosenIdx={chosenIdx} autoPlay={autoPlay} fast={fast} onDone={handleDone} onGenerating={() => reportStep(2)}/>
          ) : (
            <div style={{ width: "100%", height: "100%", overflow: "hidden" }}>
              <ExtensionSnapshot lang={lang} variant="checkin" zoom={null} compact={true}/>
            </div>
          )}
        </div>
      </div>

      <style>{`
        @keyframes blink { 50% { opacity: 0; } }
        @keyframes pulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:0.7;transform:scale(0.94)} }
        @keyframes demoSonar { 0% { r: 7; opacity: 0; } 15% { opacity: 0.45; } 100% { r: 40; opacity: 0; } }
        @keyframes railPulse { 0%,100%{box-shadow:3px 3px 7px #C8BFAE,-3px -3px 7px #FFFDF8,0 0 0 2px rgba(201,88,60,0.3)} 50%{box-shadow:3px 3px 7px #C8BFAE,-3px -3px 7px #FFFDF8,0 0 0 3px rgba(201,88,60,0.5)} }
        @keyframes spin { to { transform: rotate(360deg); } }
        @keyframes fadeIn { from { opacity: 0; transform: translateY(-4px); } to { opacity: 1; transform: translateY(0); } }
      `}</style>
    </div>
  );
};

/* keep the standalone version exported in case it's used elsewhere */
const LLMChatWindow = ({ lang = "en" }) => (
  <div style={{
    width: "100%", maxWidth: 560, borderRadius: 14, overflow: "hidden",
    background: "var(--bg-raised)",
    boxShadow:
      "0 1px 0 rgba(255,255,255,0.6) inset, 0 0 0 1px var(--ink-10), " +
      "0 24px 48px -12px rgba(26,24,21,0.22), 0 8px 18px -10px rgba(26,24,21,0.18)",
    fontFamily: 'var(--font-sans)',
    height: 520, display: "flex", flexDirection: "column",
  }}>
    <div style={{
      height: 36, display: "flex", alignItems: "center", padding: "0 14px", gap: 14,
      background: "linear-gradient(180deg, rgba(26,24,21,0.04), rgba(26,24,21,0.01))",
      borderBottom: "1px solid var(--ink-10)", flex: "0 0 auto",
    }}>
      <TrafficLights/>
      <div style={{ flex: 1, textAlign: "center", font: '500 12px/1 var(--font-sans)', color: "var(--ink-60)" }}>
        ChatGPT
      </div>
      <div style={{ width: 42 }}/>
    </div>
    <div style={{ flex: 1, minHeight: 0 }}><ChatPane lang={lang}/></div>
  </div>
);

Object.assign(window, { LLMChatWindow, ChatPlusExtensionWindow, DemoMindmap, INTERACTIVE_CONTENT });
