Give Me Doc

Convert AI chat to Word documents — powered by Pandoc WASM

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

作者のサイトでサポートを受ける。または、このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Give Me Doc
// @name:zh-CN   Give Me Doc — AI 对话导出 Word
// @namespace    https://github.com/Qalxry/GiveMeDoc
// @version      1.0.0
// @description  Convert AI chat to Word documents — powered by Pandoc WASM
// @description:zh-CN  将 AI 对话导出为 Word 文档 — 由 Pandoc WASM 驱动
// @author       GiveMeDoc Contributors
// @homepageURL  https://github.com/Qalxry/GiveMeDoc
// @supportURL   https://github.com/Qalxry/GiveMeDoc/issues
// @icon         https://raw.githubusercontent.com/Qalxry/GiveMeDoc/main/public/icons/logo.svg
// @match        https://chat.deepseek.com/*
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_addStyle
// @grant        GM_registerMenuCommand
// @grant        GM_xmlhttpRequest
// @connect      pandoc.org
// @connect      cdn.jsdelivr.net
// @noframes
// @run-at       document-idle
// @license      AGPL-3.0
// ==/UserScript==

(function() {
  "use strict";
  /**
   * @license
   * Copyright 2019 Google LLC
   * SPDX-License-Identifier: Apache-2.0
   */
  const proxyMarker = Symbol("Comlink.proxy");
  const createEndpoint = Symbol("Comlink.endpoint");
  const releaseProxy = Symbol("Comlink.releaseProxy");
  const finalizer = Symbol("Comlink.finalizer");
  const throwMarker = Symbol("Comlink.thrown");
  const isObject = (val) => typeof val === "object" && val !== null || typeof val === "function";
  const proxyTransferHandler = {
    canHandle: (val) => isObject(val) && val[proxyMarker],
    serialize(obj) {
      const { port1, port2 } = new MessageChannel();
      expose(obj, port1);
      return [port2, [port2]];
    },
    deserialize(port) {
      port.start();
      return wrap(port);
    }
  };
  const throwTransferHandler = {
    canHandle: (value) => isObject(value) && throwMarker in value,
    serialize({ value }) {
      let serialized;
      if (value instanceof Error) {
        serialized = {
          isError: true,
          value: {
            message: value.message,
            name: value.name,
            stack: value.stack
          }
        };
      } else {
        serialized = { isError: false, value };
      }
      return [serialized, []];
    },
    deserialize(serialized) {
      if (serialized.isError) {
        throw Object.assign(new Error(serialized.value.message), serialized.value);
      }
      throw serialized.value;
    }
  };
  const transferHandlers = /* @__PURE__ */ new Map([
    ["proxy", proxyTransferHandler],
    ["throw", throwTransferHandler]
  ]);
  function isAllowedOrigin(allowedOrigins, origin) {
    for (const allowedOrigin of allowedOrigins) {
      if (origin === allowedOrigin || allowedOrigin === "*") {
        return true;
      }
      if (allowedOrigin instanceof RegExp && allowedOrigin.test(origin)) {
        return true;
      }
    }
    return false;
  }
  function expose(obj, ep = globalThis, allowedOrigins = ["*"]) {
    ep.addEventListener("message", function callback(ev) {
      if (!ev || !ev.data) {
        return;
      }
      if (!isAllowedOrigin(allowedOrigins, ev.origin)) {
        console.warn(`Invalid origin '${ev.origin}' for comlink proxy`);
        return;
      }
      const { id, type, path } = Object.assign({ path: [] }, ev.data);
      const argumentList = (ev.data.argumentList || []).map(fromWireValue);
      let returnValue;
      try {
        const parent = path.slice(0, -1).reduce((obj2, prop) => obj2[prop], obj);
        const rawValue = path.reduce((obj2, prop) => obj2[prop], obj);
        switch (type) {
          case "GET":
            {
              returnValue = rawValue;
            }
            break;
          case "SET":
            {
              parent[path.slice(-1)[0]] = fromWireValue(ev.data.value);
              returnValue = true;
            }
            break;
          case "APPLY":
            {
              returnValue = rawValue.apply(parent, argumentList);
            }
            break;
          case "CONSTRUCT":
            {
              const value = new rawValue(...argumentList);
              returnValue = proxy(value);
            }
            break;
          case "ENDPOINT":
            {
              const { port1, port2 } = new MessageChannel();
              expose(obj, port2);
              returnValue = transfer(port1, [port1]);
            }
            break;
          case "RELEASE":
            {
              returnValue = void 0;
            }
            break;
          default:
            return;
        }
      } catch (value) {
        returnValue = { value, [throwMarker]: 0 };
      }
      Promise.resolve(returnValue).catch((value) => {
        return { value, [throwMarker]: 0 };
      }).then((returnValue2) => {
        const [wireValue, transferables] = toWireValue(returnValue2);
        ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables);
        if (type === "RELEASE") {
          ep.removeEventListener("message", callback);
          closeEndPoint(ep);
          if (finalizer in obj && typeof obj[finalizer] === "function") {
            obj[finalizer]();
          }
        }
      }).catch((error) => {
        const [wireValue, transferables] = toWireValue({
          value: new TypeError("Unserializable return value"),
          [throwMarker]: 0
        });
        ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables);
      });
    });
    if (ep.start) {
      ep.start();
    }
  }
  function isMessagePort(endpoint) {
    return endpoint.constructor.name === "MessagePort";
  }
  function closeEndPoint(endpoint) {
    if (isMessagePort(endpoint))
      endpoint.close();
  }
  function wrap(ep, target) {
    const pendingListeners = /* @__PURE__ */ new Map();
    ep.addEventListener("message", function handleMessage(ev) {
      const { data } = ev;
      if (!data || !data.id) {
        return;
      }
      const resolver = pendingListeners.get(data.id);
      if (!resolver) {
        return;
      }
      try {
        resolver(data);
      } finally {
        pendingListeners.delete(data.id);
      }
    });
    return createProxy(ep, pendingListeners, [], target);
  }
  function throwIfProxyReleased(isReleased) {
    if (isReleased) {
      throw new Error("Proxy has been released and is not useable");
    }
  }
  function releaseEndpoint(ep) {
    return requestResponseMessage(ep, /* @__PURE__ */ new Map(), {
      type: "RELEASE"
    }).then(() => {
      closeEndPoint(ep);
    });
  }
  const proxyCounter = /* @__PURE__ */ new WeakMap();
  const proxyFinalizers = "FinalizationRegistry" in globalThis && new FinalizationRegistry((ep) => {
    const newCount = (proxyCounter.get(ep) || 0) - 1;
    proxyCounter.set(ep, newCount);
    if (newCount === 0) {
      releaseEndpoint(ep);
    }
  });
  function registerProxy(proxy2, ep) {
    const newCount = (proxyCounter.get(ep) || 0) + 1;
    proxyCounter.set(ep, newCount);
    if (proxyFinalizers) {
      proxyFinalizers.register(proxy2, ep, proxy2);
    }
  }
  function unregisterProxy(proxy2) {
    if (proxyFinalizers) {
      proxyFinalizers.unregister(proxy2);
    }
  }
  function createProxy(ep, pendingListeners, path = [], target = function() {
  }) {
    let isProxyReleased = false;
    const proxy2 = new Proxy(target, {
      get(_target, prop) {
        throwIfProxyReleased(isProxyReleased);
        if (prop === releaseProxy) {
          return () => {
            unregisterProxy(proxy2);
            releaseEndpoint(ep);
            pendingListeners.clear();
            isProxyReleased = true;
          };
        }
        if (prop === "then") {
          if (path.length === 0) {
            return { then: () => proxy2 };
          }
          const r = requestResponseMessage(ep, pendingListeners, {
            type: "GET",
            path: path.map((p) => p.toString())
          }).then(fromWireValue);
          return r.then.bind(r);
        }
        return createProxy(ep, pendingListeners, [...path, prop]);
      },
      set(_target, prop, rawValue) {
        throwIfProxyReleased(isProxyReleased);
        const [value, transferables] = toWireValue(rawValue);
        return requestResponseMessage(ep, pendingListeners, {
          type: "SET",
          path: [...path, prop].map((p) => p.toString()),
          value
        }, transferables).then(fromWireValue);
      },
      apply(_target, _thisArg, rawArgumentList) {
        throwIfProxyReleased(isProxyReleased);
        const last = path[path.length - 1];
        if (last === createEndpoint) {
          return requestResponseMessage(ep, pendingListeners, {
            type: "ENDPOINT"
          }).then(fromWireValue);
        }
        if (last === "bind") {
          return createProxy(ep, pendingListeners, path.slice(0, -1));
        }
        const [argumentList, transferables] = processArguments(rawArgumentList);
        return requestResponseMessage(ep, pendingListeners, {
          type: "APPLY",
          path: path.map((p) => p.toString()),
          argumentList
        }, transferables).then(fromWireValue);
      },
      construct(_target, rawArgumentList) {
        throwIfProxyReleased(isProxyReleased);
        const [argumentList, transferables] = processArguments(rawArgumentList);
        return requestResponseMessage(ep, pendingListeners, {
          type: "CONSTRUCT",
          path: path.map((p) => p.toString()),
          argumentList
        }, transferables).then(fromWireValue);
      }
    });
    registerProxy(proxy2, ep);
    return proxy2;
  }
  function myFlat(arr) {
    return Array.prototype.concat.apply([], arr);
  }
  function processArguments(argumentList) {
    const processed = argumentList.map(toWireValue);
    return [processed.map((v) => v[0]), myFlat(processed.map((v) => v[1]))];
  }
  const transferCache = /* @__PURE__ */ new WeakMap();
  function transfer(obj, transfers) {
    transferCache.set(obj, transfers);
    return obj;
  }
  function proxy(obj) {
    return Object.assign(obj, { [proxyMarker]: true });
  }
  function toWireValue(value) {
    for (const [name, handler] of transferHandlers) {
      if (handler.canHandle(value)) {
        const [serializedValue, transferables] = handler.serialize(value);
        return [
          {
            type: "HANDLER",
            name,
            value: serializedValue
          },
          transferables
        ];
      }
    }
    return [
      {
        type: "RAW",
        value
      },
      transferCache.get(value) || []
    ];
  }
  function fromWireValue(value) {
    switch (value.type) {
      case "HANDLER":
        return transferHandlers.get(value.name).deserialize(value.value);
      case "RAW":
        return value.value;
    }
  }
  function requestResponseMessage(ep, pendingListeners, msg, transfers) {
    return new Promise((resolve) => {
      const id = generateUUID();
      pendingListeners.set(id, resolve);
      if (ep.start) {
        ep.start();
      }
      ep.postMessage(Object.assign({ id }, msg), transfers);
    });
  }
  function generateUUID() {
    return new Array(4).fill(0).map(() => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16)).join("-");
  }
  let worker = null;
  let pandoc = null;
  let _ready = false;
  let _version = "";
  async function initPandoc(wasmSource, workerUrlOrInstance) {
    if (_ready) return;
    worker = workerUrlOrInstance instanceof Worker ? workerUrlOrInstance : new Worker(workerUrlOrInstance, { type: "module" });
    pandoc = wrap(worker);
    await pandoc.init(wasmSource);
    _version = await pandoc.getVersion();
    if (_version.startsWith('"') && _version.endsWith('"')) {
      _version = _version.slice(1, -1);
    }
    _ready = true;
  }
  function isPandocReady() {
    return _ready;
  }
  async function getPandocVersion() {
    if (!_ready || !pandoc) return "";
    return _version;
  }
  function formatMarkdown(raw) {
    let s = raw;
    const codeSlots = [];
    function stash(m) {
      codeSlots.push(m);
      return `\0CODE${codeSlots.length - 1}\0`;
    }
    s = s.replace(/^(`{3,}|~{3,}).*\n[\s\S]*?\n\1\s*$/gm, stash);
    s = s.replace(/`[^`]+`/g, stash);
    s = s.replace(/\\\((.+?)\\\)/gs, (_, inner) => `$${inner}$`);
    s = s.replace(/\\\[(.+?)\\\]/gs, (_, inner) => `$$${inner}$$`);
    s = s.replace(
      /(?<!\$)\$(?!\$)([^\S\n]*)([^\$\n]+?)([^\S\n]*)\$(?!\$)/g,
      (match, pre, inner, post) => {
        const trimmed = inner.trim();
        if (trimmed === inner && !pre && !post) return match;
        return `$${trimmed}$`;
      }
    );
    s = s.replace(/\[citation:\d+\]/g, "");
    s = s.replace(/\n{3,}/g, "\n\n");
    s = s.replace(/^([^\S\n]*)[•*+]\s/gm, "$1- ");
    s = s.replace(/\x00CODE(\d+)\x00/g, (_, idx) => codeSlots[Number(idx)]);
    return s;
  }
  function assembleDocument(messages, config2, sessionTitle) {
    const now = /* @__PURE__ */ new Date();
    const dateStr = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
    let doc = config2.documentPrefix.replace(/\{title\}/g, sessionTitle).replace(/\{output_date\}/g, dateStr);
    for (const msg of messages) {
      const template = msg.role === "user" ? config2.userMessageTemplate : config2.assistantMessageTemplate;
      doc += renderTemplate(template, msg, config2);
    }
    return doc;
  }
  function renderTemplate(template, msg, config2) {
    let result = template;
    result = result.replace(/\{content\}/g, () => msg.content);
    if (!config2.includeThinking || !msg.thinkingContent) {
      result = result.replace(/^>?\s*\{thinking_content\}\s*\n?/gm, "");
      result = result.replace(/^>\s*$/gm, "");
      result = result.replace(/\n{3,}/g, "\n\n");
    } else {
      const thinkLines = msg.thinkingContent.split("\n");
      result = result.replace(
        /^(>\s*)\{thinking_content\}/gm,
        (_, prefix) => thinkLines.map((l) => `${prefix}${l}`).join("\n")
      );
      result = result.replace(/\{thinking_content\}/g, () => msg.thinkingContent);
    }
    return result;
  }
  const DOCX_MIME = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
  async function exportToDocx(messages, config2, sessionTitle, referenceDocx) {
    if (!pandoc) throw new Error("Pandoc Worker not initialized");
    const raw = assembleDocument(messages, config2, sessionTitle);
    console.debug("[GiveMeDoc] Assembled Markdown before formatting:", "\n" + raw);
    const formatted = formatMarkdown(raw);
    console.debug("[GiveMeDoc] Formatted Markdown for export:", "\n" + formatted);
    const buffer = await pandoc.convert(formatted, referenceDocx, config2.lineBreaks);
    const safeName = sessionTitle.replace(/[<>:"/\\|?*]/g, "_").slice(0, 100) || "export";
    return {
      blob: new Blob([buffer], { type: DOCX_MIME }),
      filename: `${safeName}.docx`
    };
  }
  async function exportRawToDocx(markdown, filename, referenceDocx, lineBreaks) {
    if (!pandoc) throw new Error("Pandoc Worker not initialized");
    console.debug("[GiveMeDoc] Raw Markdown before formatting:", "\n" + markdown);
    const formatted = formatMarkdown(markdown);
    console.debug("[GiveMeDoc] Formatted raw Markdown for export:", "\n" + formatted);
    const buffer = await pandoc.convert(formatted, referenceDocx, lineBreaks);
    const safeName = filename.replace(/[<>:"/\\|?*]/g, "_").slice(0, 100) || "export";
    return {
      blob: new Blob([buffer], { type: DOCX_MIME }),
      filename: `${safeName}.docx`
    };
  }
  function downloadBlob(blob2, filename) {
    const url = URL.createObjectURL(blob2);
    const a = document.createElement("a");
    a.href = url;
    a.download = filename;
    document.body.appendChild(a);
    a.click();
    setTimeout(() => {
      document.body.removeChild(a);
      URL.revokeObjectURL(url);
    }, 100);
  }
  const S = 'xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"';
  const ICON_LOGO = `<svg ${S}><rect x="1" y="1" width="22" height="22" rx="6.5" ry="6.5" fill="#6750a4"/> <path d="M7.5 4.5 H14.5 L17.5 7.5 V18.5 Q17.5 19.5 16.5 19.5 H7.5 Q6.5 19.5 6.5 18.5 V5.5 Q6.5 4.5 7.5 4.5 Z" fill="white" fill-opacity="0.9"/> <path d="M14.5 4.5 V6.5 Q14.5 7.5 15.5 7.5 H17.5 L14.5 4.5 Z" fill="#a193c6" fill-opacity="1.0"/> <line x1="9.3" y1="8.4" x2="12.7" y2="8.4" stroke="#6750a4" stroke-opacity="0.6" stroke-width="1.375" stroke-linecap="round"/> <line x1="9.3" y1="10.8" x2="14.8" y2="10.8" stroke="#6750a4" stroke-opacity="0.6" stroke-width="1.375" stroke-linecap="round"/> <g stroke="#6750a4" stroke-width="1.375" stroke-linecap="round" stroke-linejoin="round"> <line x1="12" y1="13" x2="12" y2="17.25" opacity="0.6"/> <line x1="12" y1="17.25" x2="10.25" y2="15.5" opacity="0.6"/> <line x1="12" y1="17.25" x2="13.75" y2="15.5" opacity="0.6"/> </g></svg>`;
  const ICON_LOGO_FG = `<svg ${S}><path d="M7 2 H14 L19 7 V20 Q19 22 17 22 H7 Q5 22 5 20 V4 Q5 2 7 2 Z" fill="white" fill-opacity="0.9"/> <path d="M14 2 V5 Q14 7 16 7 H19 L14 2 Z" fill="#a193c6" fill-opacity="1.0"/> <line x1="8.5" y1="8" x2="13.5" y2="8" stroke="#6750a4" stroke-opacity="0.6" stroke-width="1.8" stroke-linecap="round"/> <line x1="8.5" y1="11" x2="15.5" y2="11" stroke="#6750a4" stroke-opacity="0.6" stroke-width="1.8" stroke-linecap="round"/> <g stroke="#6750a4" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"> <line x1="12" y1="14" x2="12" y2="19" opacity="0.6"/> <line x1="12" y1="19" x2="9.5" y2="16.5" opacity="0.6"/> <line x1="12" y1="19" x2="14.5" y2="16.5" opacity="0.6"/> </g></svg>`;
  const ICON_FILE_TEXT = `<svg ${S}><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/> <path d="M14 2v5a1 1 0 0 0 1 1h5"/> <path d="M10 9H8"/> <path d="M16 13H8"/> <path d="M16 17H8"/></svg>`;
  const ICON_FILE_TYPE = `<svg ${S}><path d="M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z"/> <path d="M14 2v5a1 1 0 0 0 1 1h5"/> <path d="M11 18h2"/> <path d="M12 12v6"/> <path d="M9 13v-.5a.5.5 0 0 1 .5-.5h5a.5.5 0 0 1 .5.5v.5"/></svg>`;
  const ICON_DOWNLOAD = `<svg ${S}><path d="M12 15V3"/> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/> <path d="m7 10 5 5 5-5"/></svg>`;
  const ICON_UPLOAD = `<svg ${S}><path d="M12 3v12"/> <path d="m17 8-5-5-5 5"/> <path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/></svg>`;
  const ICON_TRASH = `<svg ${S}><path d="M10 11v6"/> <path d="M14 11v6"/> <path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/> <path d="M3 6h18"/> <path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>`;
  const ICON_CHEVRON_LEFT = `<svg ${S}><path d="m15 18-6-6 6-6"/></svg>`;
  const ICON_CHEVRON_RIGHT = `<svg ${S}><path d="m9 18 6-6-6-6"/></svg>`;
  const ICON_CHECK = `<svg ${S}><path d="M20 6 9 17l-5-5"/></svg>`;
  const ICON_X = `<svg ${S}><path d="M18 6 6 18"/> <path d="m6 6 12 12"/></svg>`;
  const ICON_SETTINGS = `<svg ${S}><path d="M9.671 4.136a2.34 2.34 0 0 1 4.659 0 2.34 2.34 0 0 0 3.319 1.915 2.34 2.34 0 0 1 2.33 4.033 2.34 2.34 0 0 0 0 3.831 2.34 2.34 0 0 1-2.33 4.033 2.34 2.34 0 0 0-3.319 1.915 2.34 2.34 0 0 1-4.659 0 2.34 2.34 0 0 0-3.32-1.915 2.34 2.34 0 0 1-2.33-4.033 2.34 2.34 0 0 0 0-3.831A2.34 2.34 0 0 1 6.35 6.051a2.34 2.34 0 0 0 3.319-1.915"/> <circle cx="12" cy="12" r="3"/></svg>`;
  const ICON_INFO = `<svg ${S}><circle cx="12" cy="12" r="10"/> <path d="M12 16v-4"/> <path d="M12 8h.01"/></svg>`;
  const ICON_EXTERNAL_LINK = `<svg ${S}><path d="M15 3h6v6"/> <path d="M10 14 21 3"/> <path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/></svg>`;
  const ICON_REFRESH = `<svg ${S}><path d="M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8"/> <path d="M21 3v5h-5"/> <path d="M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16"/> <path d="M8 16H3v5"/></svg>`;
  const ICON_GITHUB = `<svg ${S}><path d="M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.403 5.403 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65-.17.6-.22 1.23-.15 1.85v4"/> <path d="M9 18c-4.51 2-5-2-7-2"/></svg>`;
  const ICON_BUG = `<svg ${S}><path d="M12 20v-9"/> <path d="M14 7a4 4 0 0 1 4 4v3a6 6 0 0 1-12 0v-3a4 4 0 0 1 4-4z"/> <path d="M14.12 3.88 16 2"/> <path d="M21 21a4 4 0 0 0-3.81-4"/> <path d="M21 5a4 4 0 0 1-3.55 3.97"/> <path d="M22 13h-4"/> <path d="M3 21a4 4 0 0 1 3.81-4"/> <path d="M3 5a4 4 0 0 0 3.55 3.97"/> <path d="M6 13H2"/> <path d="m8 2 1.88 1.88"/> <path d="M9 7.13V6a3 3 0 1 1 6 0v1.13"/></svg>`;
  const ICON_USER = `<svg ${S}><path d="M19 21v-2a4 4 0 0 0-4-4H9a4 4 0 0 0-4 4v2"/> <circle cx="12" cy="7" r="4"/></svg>`;
  const ICON_BOT = `<svg ${S}><path d="M12 8V4H8"/> <rect width="16" height="12" x="4" y="8" rx="2"/> <path d="M2 14h2"/> <path d="M20 14h2"/> <path d="M15 13v2"/> <path d="M9 13v2"/></svg>`;
  const ICON_CIRCLE_CHECK = `<svg ${S}><circle cx="12" cy="12" r="10"/> <path d="m9 12 2 2 4-4"/></svg>`;
  const ICON_TRIANGLE_ALERT = `<svg ${S}><path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3"/> <path d="M12 9v4"/> <path d="M12 17h.01"/></svg>`;
  const ICON_CIRCLE_X = `<svg ${S}><circle cx="12" cy="12" r="10"/> <path d="m15 9-6 6"/> <path d="m9 9 6 6"/></svg>`;
  const ICON_TEXT_CURSOR_INPUT = `<svg ${S}><path d="M12 20h-1a2 2 0 0 1-2-2 2 2 0 0 1-2 2H6"/> <path d="M13 8h7a2 2 0 0 1 2 2v4a2 2 0 0 1-2 2h-7"/> <path d="M5 16H4a2 2 0 0 1-2-2v-4a2 2 0 0 1 2-2h1"/> <path d="M6 4h1a2 2 0 0 1 2 2 2 2 0 0 1 2-2h1"/> <path d="M9 6v12"/></svg>`;
  const ICON_MESSAGES_SQUARE = `<svg ${S}><path d="M16 10a2 2 0 0 1-2 2H6.828a2 2 0 0 0-1.414.586l-2.202 2.202A.71.71 0 0 1 2 14.286V4a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2z"/> <path d="M20 9a2 2 0 0 1 2 2v10.286a.71.71 0 0 1-1.212.502l-2.202-2.202A2 2 0 0 0 17.172 19H10a2 2 0 0 1-2-2v-1"/></svg>`;
  function iconSize(svg, size) {
    return svg.replace('width="20"', `width="${size}"`).replace('height="20"', `height="${size}"`);
  }
  function iconStroke(svg, strokeWidth) {
    return svg.replace('stroke-width="2"', `stroke-width="${strokeWidth}"`);
  }
  function getToken() {
    try {
      const raw = localStorage.getItem("userToken");
      if (!raw) return null;
      const parsed = JSON.parse(raw);
      return parsed?.value ?? null;
    } catch {
      return null;
    }
  }
  const API_BASE = "https://chat.deepseek.com/api/v0";
  const COMMON_HEADERS = {
    "Accept": "*/*",
    "x-client-platform": "web",
    "x-client-version": "1.7.0",
    "x-client-locale": "zh_CN",
    "x-app-version": "20241129.1"
  };
  async function fetchSessionFromAPI(sessionId, token) {
    const url = `${API_BASE}/chat/history_messages?chat_session_id=${encodeURIComponent(sessionId)}`;
    const res = await fetch(url, {
      headers: { ...COMMON_HEADERS, authorization: `Bearer ${token}` },
      credentials: "include"
    });
    if (!res.ok) throw new Error(`DeepSeek API error: ${res.status} ${res.statusText}`);
    const json = await res.json();
    if (json.code !== 0) throw new Error(`DeepSeek biz error: code=${json.code}`);
    const { chat_session, chat_messages } = json.data.biz_data;
    return buildSession(chat_session, chat_messages);
  }
  function openDeepSeekDB() {
    return new Promise((resolve, reject) => {
      const req = indexedDB.open("deepseek-chat");
      req.onerror = () => reject(new Error("Cannot open deepseek-chat IndexedDB"));
      req.onsuccess = () => resolve(req.result);
    });
  }
  function idbGet(db, storeName, key) {
    return new Promise((resolve, reject) => {
      const tx = db.transaction(storeName, "readonly");
      const store = tx.objectStore(storeName);
      const req = store.get(key);
      req.onerror = () => reject(req.error);
      req.onsuccess = () => resolve(req.result);
    });
  }
  async function fetchSessionFromIDB(sessionId) {
    try {
      const db = await openDeepSeekDB();
      const record = await idbGet(db, "history-message", sessionId);
      db.close();
      if (!record?.data?.chat_session || !record.data.chat_messages?.length) return null;
      return buildSession(record.data.chat_session, record.data.chat_messages);
    } catch {
      return null;
    }
  }
  async function getSession(sessionId) {
    const idbSession = await fetchSessionFromIDB(sessionId);
    if (idbSession && idbSession.messages.size > 0) {
      return idbSession;
    }
    const token = getToken();
    if (!token) throw new Error("No DeepSeek auth token found");
    return fetchSessionFromAPI(sessionId, token);
  }
  function buildSession(rawSession, rawMessages) {
    const messages = /* @__PURE__ */ new Map();
    for (const rm of rawMessages) {
      const id = String(rm.message_id);
      const parentId = rm.parent_id != null ? String(rm.parent_id) : null;
      let content = "";
      let thinkingContent = "";
      for (const frag of rm.fragments) {
        if (frag.type === "REQUEST" || frag.type === "RESPONSE") {
          content += frag.content;
        } else if (frag.type === "THINK") {
          thinkingContent += frag.content;
        }
      }
      messages.set(id, {
        id,
        parentId,
        role: rm.role === "USER" ? "user" : "assistant",
        content,
        thinkingContent,
        timestamp: rm.inserted_at,
        status: rm.status === "FINISHED" ? "finished" : rm.status === "INCOMPLETE" ? "incomplete" : "error",
        childrenIds: []
      });
    }
    for (const msg of messages.values()) {
      if (msg.parentId && messages.has(msg.parentId)) {
        const parent = messages.get(msg.parentId);
        if (!parent.childrenIds.includes(msg.id)) {
          parent.childrenIds.push(msg.id);
        }
      }
    }
    return {
      id: rawSession.id,
      title: rawSession.title,
      updatedAt: rawSession.updated_at,
      messages,
      currentMessageId: String(rawSession.current_message_id)
    };
  }
  function getActiveChain(session2) {
    const chain2 = [];
    let id = session2.currentMessageId;
    while (id) {
      const msg = session2.messages.get(id);
      if (!msg) break;
      chain2.unshift(msg);
      id = msg.parentId;
    }
    return chain2;
  }
  function switchBranch(session2, nodeId, targetChildId) {
    const messages = session2.messages;
    const prefix = [];
    if (nodeId !== null) {
      let id = nodeId;
      while (id) {
        const msg = messages.get(id);
        if (!msg) break;
        prefix.unshift(msg);
        id = msg.parentId;
      }
    }
    const suffix = [];
    let current = targetChildId;
    while (current) {
      const msg = messages.get(current);
      if (!msg) break;
      suffix.push(msg);
      current = msg.childrenIds.length > 0 ? msg.childrenIds[0] : null;
    }
    return [...prefix, ...suffix];
  }
  function hasBranch(session2, nodeId) {
    if (nodeId === null) return getRootIds(session2).length > 1;
    const msg = session2.messages.get(nodeId);
    return !!msg && msg.childrenIds.length > 1;
  }
  function getChildIndex(session2, parentId, childId) {
    if (parentId === null) return getRootIds(session2).indexOf(childId);
    const parent = session2.messages.get(parentId);
    if (!parent) return 0;
    return parent.childrenIds.indexOf(childId);
  }
  function getRootIds(session2) {
    const ids = [];
    for (const msg of session2.messages.values()) {
      if (msg.parentId === null) ids.push(msg.id);
    }
    return ids;
  }
  function getCurrentSessionId() {
    const m = window.location.pathname.match(/\/a\/chat\/s\/([a-f0-9-]+)/i);
    return m ? m[1] : null;
  }
  const FILE_TYPE_SVG = iconSize(ICON_FILE_TYPE, 16);
  function injectSingleExportButtons(onClick) {
    const MARKER_ATTR = "data-gmd-injected";
    function processToolbar(toolbar) {
      if (toolbar.hasAttribute(MARKER_ATTR)) return;
      toolbar.setAttribute(MARKER_ATTR, "1");
      const copyBtn = toolbar.querySelector("div.ds-icon-button");
      if (!copyBtn) return;
      const exportBtn2 = document.createElement("div");
      exportBtn2.className = "ds-icon-button ds-icon-button--m ds-icon-button--sizing-container";
      exportBtn2.setAttribute("tabindex", "0");
      exportBtn2.setAttribute("role", "button");
      exportBtn2.setAttribute("aria-disabled", "false");
      exportBtn2.title = "导出为 Word";
      exportBtn2.innerHTML = `
      <div class="ds-icon-button__hover-bg"></div>
      <div class="ds-icon">${FILE_TYPE_SVG}</div>
      <div class="ds-focus-ring"></div>
    `;
      exportBtn2.addEventListener("click", async (e) => {
        e.stopPropagation();
        copyBtn.click();
        await new Promise((r) => setTimeout(r, 300));
        try {
          const md = await navigator.clipboard.readText();
          if (md) {
            const title = document.title.replace(/ - DeepSeek$/, "").trim() || "单条消息导出";
            onClick(md, title);
          }
        } catch (err) {
          console.error("[GiveMeDoc] Clipboard read failed:", err);
        }
      });
      toolbar.appendChild(exportBtn2);
    }
    function scanAll() {
      const toolbars = document.querySelectorAll(
        "div.ds-scroll-area div.ds-flex div.ds-flex"
      );
      toolbars.forEach(processToolbar);
    }
    scanAll();
    const observer = new MutationObserver(() => scanAll());
    observer.observe(document.body, { childList: true, subtree: true });
  }
  function injectSharePanelButton(onClick) {
    const MARKER_ATTR = "data-gmd-share-injected";
    function tryInject() {
      const allPrimary = document.querySelectorAll(
        ".ds-atom-button.ds-basic-button.ds-basic-button--primary"
      );
      let shareBtn = null;
      for (const btn of allPrimary) {
        if (btn.innerText.includes("创建分享链接")) {
          shareBtn = btn;
          break;
        }
      }
      if (!shareBtn) return;
      const bottomBar = shareBtn.parentElement;
      if (!bottomBar || bottomBar.hasAttribute(MARKER_ATTR)) return;
      bottomBar.setAttribute(MARKER_ATTR, "1");
      const exportBtn2 = document.createElement("button");
      exportBtn2.className = "ds-atom-button ds-basic-button ds-basic-button--outlined";
      exportBtn2.style = "padding: 6px 14px; font-size: 14px; line-height: 22px; min-width: 72px;";
      exportBtn2.innerHTML = `
      <div class="ds-icon ds-atom-button__icon" style="font-size: 14px; width: 14px; height: 14px; margin-right: 6px;"><div class="ds-icon" style="font-size: 14px; width: 14px; height: 14px;">${FILE_TYPE_SVG}</div></div>
      <span>导出为 Word</span>
    `;
      exportBtn2.addEventListener("click", (e) => {
        e.stopPropagation();
        const checkboxes = document.querySelectorAll(".ds-checkbox");
        const indices = [];
        for (let i = 0; i < checkboxes.length - 1; i++) {
          if (checkboxes[i].classList.contains("ds-checkbox--active")) {
            indices.push(i);
          }
        }
        onClick(indices);
      });
      bottomBar.insertBefore(exportBtn2, shareBtn);
    }
    const observer = new MutationObserver(() => tryInject());
    observer.observe(document.body, { childList: true, subtree: true });
    tryInject();
  }
  function el(tag, className, attrs) {
    const e = document.createElement(tag);
    if (className) e.className = className;
    if (attrs) {
      for (const [k, v] of Object.entries(attrs)) {
        e.setAttribute(k, v);
      }
    }
    return e;
  }
  function text(content) {
    return document.createTextNode(content);
  }
  function html(tag, className, innerHTML) {
    const e = el(tag, className);
    e.innerHTML = innerHTML;
    return e;
  }
  function append(parent, ...children) {
    for (const c of children) {
      if (typeof c === "string") {
        parent.appendChild(text(c));
      } else {
        parent.appendChild(c);
      }
    }
    return parent;
  }
  function createButton(opts) {
    const variant = opts.variant || "filled";
    const btn = el("button", `gmd-btn gmd-btn--${variant}${opts.className ? " " + opts.className : ""}`);
    btn.type = "button";
    if (opts.disabled) btn.disabled = true;
    if (opts.icon) {
      const iconSpan = html("span", "gmd-btn__icon", opts.icon);
      btn.appendChild(iconSpan);
    }
    const labelSpan = el("span", "gmd-btn__label");
    labelSpan.textContent = opts.label;
    btn.appendChild(labelSpan);
    if (opts.onClick) {
      btn.addEventListener("click", opts.onClick);
    }
    return btn;
  }
  function createIconButton(opts) {
    const variant = opts.variant || "standard";
    const btn = el("button", `gmd-icon-btn gmd-icon-btn--${variant}`);
    btn.type = "button";
    btn.innerHTML = opts.icon;
    if (opts.title) btn.title = opts.title;
    if (opts.onClick) btn.addEventListener("click", opts.onClick);
    return btn;
  }
  function createCheckbox(opts) {
    const wrapper = el("label", "gmd-checkbox");
    const input = el("input", "gmd-checkbox__input", { type: "checkbox" });
    if (opts.checked) input.checked = true;
    const box = el("span", "gmd-checkbox__box");
    box.innerHTML = iconStroke(ICON_CHECK, 3).replace("<svg ", '<svg class="gmd-checkbox__check" ');
    wrapper.appendChild(input);
    wrapper.appendChild(box);
    if (opts.label) {
      const lbl = el("span", "gmd-checkbox__label");
      lbl.textContent = opts.label;
      wrapper.appendChild(lbl);
    }
    if (opts.onChange) {
      input.addEventListener("change", () => opts.onChange(input.checked));
    }
    return wrapper;
  }
  function setCheckboxState(wrapper, checked) {
    const input = wrapper.querySelector("input");
    if (input) input.checked = checked;
  }
  function createSelect(opts) {
    const select = el("select", "gmd-select");
    if (opts.placeholder) {
      const ph = el("option", void 0, { value: "", disabled: "true", selected: "true" });
      ph.textContent = opts.placeholder;
      select.appendChild(ph);
    }
    for (const o of opts.options) {
      const option = el("option", void 0, { value: o.value });
      option.textContent = o.label;
      if (opts.value === o.value) option.selected = true;
      select.appendChild(option);
    }
    if (opts.onChange) {
      select.addEventListener("change", () => opts.onChange(select.value));
    }
    return select;
  }
  function createSegmentedControl(opts) {
    const root2 = el("div", "gmd-segmented");
    const buttons = [];
    for (const seg of opts.segments) {
      const btn = el("button", "gmd-segmented__btn");
      btn.type = "button";
      btn.dataset.segmentId = seg.id;
      if (seg.icon) {
        const iconSpan = html("span", "gmd-segmented__icon", seg.icon);
        btn.appendChild(iconSpan);
      }
      const labelSpan = el("span", "gmd-segmented__label");
      labelSpan.textContent = seg.label;
      btn.appendChild(labelSpan);
      if (seg.id === opts.activeId) btn.classList.add("gmd-segmented__btn--active");
      btn.addEventListener("click", () => {
        for (const b of buttons) b.classList.remove("gmd-segmented__btn--active");
        btn.classList.add("gmd-segmented__btn--active");
        opts.onChange?.(seg.id);
      });
      buttons.push(btn);
      root2.appendChild(btn);
    }
    return root2;
  }
  function createInput(opts) {
    const wrapper = el("div", "gmd-input");
    if (opts.label) {
      const lbl = el("label", "gmd-input__label");
      lbl.textContent = opts.label;
      wrapper.appendChild(lbl);
    }
    const input = el("input", "gmd-input__field", { type: "text" });
    if (opts.value) input.value = opts.value;
    if (opts.placeholder) input.placeholder = opts.placeholder;
    if (opts.onChange) {
      input.addEventListener("input", () => opts.onChange(input.value));
    }
    wrapper.appendChild(input);
    return wrapper;
  }
  function createTextarea(opts) {
    const wrapper = el("div", "gmd-textarea");
    if (opts.label) {
      const lbl = el("label", "gmd-textarea__label");
      lbl.textContent = opts.label;
      wrapper.appendChild(lbl);
    }
    const textarea = el("textarea", "gmd-textarea__input");
    if (opts.value) textarea.value = opts.value;
    if (opts.placeholder) textarea.placeholder = opts.placeholder;
    textarea.rows = opts.rows ?? 4;
    if (opts.wrap) {
      textarea.wrap = opts.wrap;
      if (opts.wrap === "off") textarea.classList.add("gmd-textarea__input--nowrap");
    }
    if (opts.onChange) {
      textarea.addEventListener("input", () => opts.onChange(textarea.value));
    }
    wrapper.appendChild(textarea);
    return wrapper;
  }
  function createTabs(opts) {
    const root2 = el("div", "gmd-tabs");
    const bar = el("div", "gmd-tabs__bar");
    bar.setAttribute("role", "tablist");
    const panelContainer = el("div", "gmd-tabs__panels");
    const indicator = el("div", "gmd-tabs__indicator");
    const tabEls = /* @__PURE__ */ new Map();
    const panelEls = /* @__PURE__ */ new Map();
    for (const tab of opts.tabs) {
      const tabEl = el("button", "gmd-tabs__tab");
      tabEl.setAttribute("role", "tab");
      tabEl.setAttribute("data-tab-id", tab.id);
      tabEl.type = "button";
      if (tab.icon) {
        const iconSpan = el("span", "gmd-tabs__tab-icon");
        iconSpan.innerHTML = tab.icon;
        tabEl.appendChild(iconSpan);
      }
      const labelSpan = el("span", "gmd-tabs__tab-label");
      labelSpan.textContent = tab.label;
      tabEl.appendChild(labelSpan);
      bar.appendChild(tabEl);
      tabEls.set(tab.id, tabEl);
      const panel = el("div", "gmd-tabs__panel");
      panel.setAttribute("role", "tabpanel");
      panel.setAttribute("data-panel-id", tab.id);
      panelEls.set(tab.id, panel);
      panelContainer.appendChild(panel);
    }
    bar.appendChild(indicator);
    root2.appendChild(bar);
    root2.appendChild(panelContainer);
    let activeId = opts.activeId ?? opts.tabs[0]?.id ?? "";
    function setActive(tabId) {
      activeId = tabId;
      for (const [id, tabEl] of tabEls) {
        const isActive = id === tabId;
        tabEl.classList.toggle("gmd-tabs__tab--active", isActive);
        tabEl.setAttribute("aria-selected", String(isActive));
      }
      const activeTabEl = tabEls.get(tabId);
      if (activeTabEl) {
        const barRect = bar.getBoundingClientRect();
        const tabRect = activeTabEl.getBoundingClientRect();
        indicator.style.left = `${tabRect.left - barRect.left}px`;
        indicator.style.width = `${tabRect.width}px`;
      }
      for (const [id, panel] of panelEls) {
        const isActive = id === tabId;
        panel.classList.toggle("gmd-tabs__panel--active", isActive);
        panel.hidden = !isActive;
        if (isActive && panel.children.length === 0) {
          const tab = opts.tabs.find((t) => t.id === id);
          if (tab) {
            panel.appendChild(tab.render());
          }
        }
      }
      opts.onTabChange?.(tabId);
    }
    for (const [id, tabEl] of tabEls) {
      tabEl.addEventListener("click", () => setActive(id));
    }
    requestAnimationFrame(() => setActive(activeId));
    return { root: root2, setActive };
  }
  let container = null;
  function ensureContainer() {
    if (container && document.body.contains(container)) return container;
    container = el("div", "gmd-toast-container");
    document.body.appendChild(container);
    return container;
  }
  function showToast(opts) {
    const c = ensureContainer();
    const level = opts.level ?? "info";
    const duration = opts.duration ?? 4e3;
    const toast = el("div", `gmd-toast gmd-toast--${level}`);
    const iconMap = {
      info: ICON_INFO,
      success: ICON_CIRCLE_CHECK,
      warning: ICON_TRIANGLE_ALERT,
      error: ICON_CIRCLE_X
    };
    const iconEl = el("span", "gmd-toast__icon");
    iconEl.innerHTML = iconMap[level];
    toast.appendChild(iconEl);
    const msgEl = el("span", "gmd-toast__message");
    msgEl.textContent = opts.message;
    toast.appendChild(msgEl);
    if (opts.action) {
      const actionBtn = el("button", "gmd-toast__action");
      actionBtn.textContent = opts.action.label;
      actionBtn.addEventListener("click", () => {
        opts.action.onClick();
        dismiss();
      });
      toast.appendChild(actionBtn);
    }
    const closeBtn = el("button", "gmd-toast__close");
    closeBtn.innerHTML = iconSize(ICON_X, 16);
    closeBtn.addEventListener("click", dismiss);
    toast.appendChild(closeBtn);
    c.appendChild(toast);
    requestAnimationFrame(() => toast.classList.add("gmd-toast--visible"));
    let timer = null;
    function dismiss() {
      if (timer) clearTimeout(timer);
      toast.classList.remove("gmd-toast--visible");
      toast.addEventListener("transitionend", () => toast.remove(), { once: true });
      setTimeout(() => toast.remove(), 400);
    }
    if (duration > 0) {
      timer = setTimeout(dismiss, duration);
    }
    return dismiss;
  }
  function debounce$1(fn, ms) {
    let timer = null;
    return (...args) => {
      if (timer !== null) clearTimeout(timer);
      timer = setTimeout(() => {
        fn(...args);
        timer = null;
      }, ms);
    };
  }
  let session = null;
  let chain = [];
  let selectedIds = /* @__PURE__ */ new Set();
  let templates$1 = [];
  let selectedTemplateId = "";
  let spinStartTime = 0;
  const MIN_SPIN_MS = 600;
  let mode = "session";
  let freetextMd = "";
  let freetextFilename = "export";
  let listEl;
  let selectAllCb;
  let templateSelect;
  let exportBtn;
  let countLabel;
  let refreshBtn;
  let root;
  let sessionContainer;
  let freetextContainer;
  let freetextFilenameInput;
  let freetextTextarea;
  let segmentedEl;
  const persistFreetextMd = debounce$1((cb, v) => {
    cb.onConfigChange({ freetextMd: v });
  }, 400);
  const persistFreetextFilename = debounce$1((cb, v) => {
    cb.onConfigChange({ freetextFilename: v });
  }, 400);
  function renderExportTab(cb) {
    root = el("div", "gmd-export");
    const modeSwitch = el("div", "gmd-export__mode-switch");
    const segmented = createSegmentedControl({
      segments: [
        { id: "session", label: "当前会话", icon: ICON_MESSAGES_SQUARE },
        { id: "freetext", label: "自由输入", icon: ICON_TEXT_CURSOR_INPUT }
      ],
      activeId: mode,
      onChange: (id) => {
        mode = id;
        cb.onConfigChange({ exportMode: mode });
        applyModeSwitch();
      }
    });
    segmentedEl = segmented;
    modeSwitch.appendChild(segmented);
    root.appendChild(modeSwitch);
    sessionContainer = el("div", "gmd-export__session");
    const toolbar = el("div", "gmd-export__toolbar");
    selectAllCb = createCheckbox({
      label: "全选",
      onChange: (checked) => {
        for (const msg of chain) {
          if (checked) selectedIds.add(msg.id);
          else selectedIds.delete(msg.id);
        }
        syncList();
      }
    });
    countLabel = el("span", "gmd-export__count");
    countLabel.textContent = "0 条消息";
    refreshBtn = createIconButton({
      icon: ICON_REFRESH,
      title: "刷新会话",
      variant: "standard",
      onClick: () => {
        refreshBtn.classList.add("gmd-icon-btn--spinning");
        spinStartTime = Date.now();
        loadSession(cb);
      }
    });
    append(toolbar, selectAllCb, countLabel, refreshBtn);
    sessionContainer.appendChild(toolbar);
    listEl = el("div", "gmd-export__list");
    sessionContainer.appendChild(listEl);
    root.appendChild(sessionContainer);
    freetextContainer = el("div", "gmd-export__freetext");
    freetextContainer.style.display = "none";
    freetextFilenameInput = createInput({
      label: "导出文件名",
      value: freetextFilename,
      placeholder: "export",
      onChange: (v) => {
        freetextFilename = v;
        persistFreetextFilename(cb, v);
        syncExportBtnState();
      }
    });
    freetextContainer.appendChild(freetextFilenameInput);
    freetextTextarea = createTextarea({
      label: "Markdown 内容",
      value: freetextMd,
      placeholder: "在此粘贴或输入 Markdown 文本…",
      rows: 12,
      onChange: (v) => {
        freetextMd = v;
        persistFreetextMd(cb, v);
        syncExportBtnState();
      }
    });
    freetextTextarea.classList.add("gmd-export__freetext-editor");
    freetextContainer.appendChild(freetextTextarea);
    root.appendChild(freetextContainer);
    const bottomBar = el("div", "gmd-export__bottom");
    templateSelect = createSelect({
      options: [],
      placeholder: "选择模板…",
      onChange: (v) => {
        selectedTemplateId = v;
      }
    });
    exportBtn = createButton({
      label: "导出",
      icon: ICON_DOWNLOAD,
      variant: "filled",
      onClick: () => doExport(cb)
    });
    append(bottomBar, templateSelect, exportBtn);
    root.appendChild(bottomBar);
    loadSession(cb);
    loadTemplates(cb);
    loadFreetextState(cb);
    return root;
  }
  async function loadSession(cb) {
    try {
      session = await cb.getSession();
      if (!session) {
        listEl.innerHTML = `
        <div class="gmd-export__empty">
          <div class="gmd-export__empty-icon">${ICON_MESSAGES_SQUARE}</div>
          <div class="gmd-export__empty-text">未检测到消息,请打开一个对话。<br/>或者复制内容后粘贴到“自由输入”中导出。</div>
        </div>
      `;
        return;
      }
      chain = getActiveChain(session);
      selectedIds = new Set(chain.map((m) => m.id));
      renderList();
    } catch (err) {
      showToast({ message: `加载会话失败: ${err.message}`, level: "error" });
    } finally {
      const elapsed = Date.now() - spinStartTime;
      const remaining = Math.max(0, MIN_SPIN_MS - elapsed);
      setTimeout(() => refreshBtn?.classList.remove("gmd-icon-btn--spinning"), remaining);
    }
  }
  async function loadTemplates(cb) {
    try {
      templates$1 = await cb.getTemplateList();
      const config2 = await cb.getConfig();
      selectedTemplateId = config2.selectedTemplateId;
      rebuildTemplateSelect();
    } catch (err) {
      showToast({ message: `加载模板失败: ${err.message}`, level: "error" });
    }
  }
  async function loadFreetextState(cb) {
    try {
      const config2 = await cb.getConfig();
      if (config2.exportMode && config2.exportMode !== mode) {
        mode = config2.exportMode;
        const btns = segmentedEl.querySelectorAll(".gmd-segmented__btn");
        btns.forEach((btn) => {
          const el2 = btn;
          if (el2.dataset.segmentId === mode) {
            el2.classList.add("gmd-segmented__btn--active");
          } else {
            el2.classList.remove("gmd-segmented__btn--active");
          }
        });
        applyModeSwitch();
      }
      if (config2.freetextMd) {
        freetextMd = config2.freetextMd;
        const ta = freetextTextarea.querySelector("textarea");
        if (ta) ta.value = freetextMd;
      }
      if (config2.freetextFilename) {
        freetextFilename = config2.freetextFilename;
        const inp = freetextFilenameInput.querySelector("input");
        if (inp) inp.value = freetextFilename;
      }
      syncExportBtnState();
    } catch (_) {
    }
  }
  function rebuildTemplateSelect() {
    templateSelect.innerHTML = "";
    for (const t of templates$1) {
      const opt = document.createElement("option");
      opt.value = t.id;
      opt.textContent = t.name + (t.isBuiltin ? "" : " (自定义)");
      if (t.id === selectedTemplateId) opt.selected = true;
      templateSelect.appendChild(opt);
    }
  }
  function renderList() {
    listEl.innerHTML = "";
    for (const msg of chain) {
      listEl.appendChild(createMessageRow(msg));
    }
    syncCount();
  }
  function createMessageRow(msg) {
    const row = el("div", `gmd-export__row gmd-export__row--${msg.role}`);
    const cb = createCheckbox({
      checked: selectedIds.has(msg.id),
      onChange: (checked) => {
        if (checked) selectedIds.add(msg.id);
        else selectedIds.delete(msg.id);
        syncCount();
      }
    });
    row.appendChild(cb);
    const icon = html("span", "gmd-export__role-icon", msg.role === "user" ? ICON_USER : ICON_BOT);
    row.appendChild(icon);
    const summary = el("span", "gmd-export__summary");
    const plain = msg.content.replace(/[#*`>\-\[\]()]/g, "").trim();
    summary.textContent = plain.length > 80 ? plain.slice(0, 80) + "…" : plain;
    summary.title = plain.slice(0, 300);
    row.appendChild(summary);
    if (session && hasBranch(session, msg.parentId)) {
      const branchCtrl = createBranchSwitcher(msg);
      row.appendChild(branchCtrl);
    }
    return row;
  }
  function createBranchSwitcher(msg) {
    if (!session) return el("span", "");
    const parentId = msg.parentId;
    const childrenIds = parentId === null ? getRootIds(session) : session.messages.get(parentId).childrenIds;
    const idx = getChildIndex(session, parentId, msg.id);
    const total = childrenIds.length;
    const ctrl = el("span", "gmd-export__branch");
    const prevBtn = createIconButton({
      icon: ICON_CHEVRON_LEFT,
      title: "上一分支",
      variant: "standard",
      onClick: () => navigateBranch(parentId, idx - 1)
    });
    if (idx <= 0) prevBtn.disabled = true;
    const label = el("span", "gmd-export__branch-label");
    label.textContent = `${idx + 1}/${total}`;
    const nextBtn = createIconButton({
      icon: ICON_CHEVRON_RIGHT,
      title: "下一分支",
      variant: "standard",
      onClick: () => navigateBranch(parentId, idx + 1)
    });
    if (idx >= total - 1) nextBtn.disabled = true;
    append(ctrl, prevBtn, label, nextBtn);
    return ctrl;
  }
  function navigateBranch(parentId, targetIdx) {
    if (!session) return;
    const childrenIds = parentId === null ? getRootIds(session) : session.messages.get(parentId)?.childrenIds;
    if (!childrenIds || targetIdx < 0 || targetIdx >= childrenIds.length) return;
    const targetChildId = childrenIds[targetIdx];
    const prevSelectedIds = new Set(selectedIds);
    const prevChainIds = new Set(chain.map((m) => m.id));
    chain = switchBranch(session, parentId, targetChildId);
    selectedIds = new Set(
      chain.filter((m) => prevChainIds.has(m.id) ? prevSelectedIds.has(m.id) : true).map((m) => m.id)
    );
    renderList();
  }
  function applyModeSwitch() {
    if (mode === "session") {
      sessionContainer.style.display = "";
      freetextContainer.style.display = "none";
    } else {
      sessionContainer.style.display = "none";
      freetextContainer.style.display = "";
    }
    syncExportBtnState();
  }
  function syncExportBtnState() {
    if (mode === "freetext") {
      exportBtn.disabled = !freetextMd.trim();
    } else {
      exportBtn.disabled = selectedIds.size === 0;
    }
  }
  function syncList() {
    const rows = listEl.querySelectorAll(".gmd-export__row");
    rows.forEach((row, i) => {
      const cb = row.querySelector(".gmd-checkbox");
      if (cb && chain[i]) {
        setCheckboxState(cb, selectedIds.has(chain[i].id));
      }
    });
    syncCount();
  }
  function syncCount() {
    const total = chain.length;
    const selected = selectedIds.size;
    countLabel.textContent = `${selected}/${total} 条消息`;
    const allChecked = total > 0 && selected === total;
    setCheckboxState(selectAllCb, allChecked);
    syncExportBtnState();
  }
  async function doExport(cb) {
    if (mode === "freetext") {
      if (!freetextMd.trim()) {
        showToast({ message: "请输入 Markdown 文本", level: "warning" });
        return;
      }
      exportBtn.disabled = true;
      const origLabel2 = exportBtn.querySelector(".gmd-btn__label");
      const prevText2 = origLabel2.textContent;
      origLabel2.textContent = "导出中…";
      try {
        await cb.onExportRaw(freetextMd, selectedTemplateId, freetextFilename || "export");
        showToast({ message: "导出成功", level: "success" });
      } catch (err) {
        showToast({ message: `导出失败: ${err.message}`, level: "error" });
      } finally {
        origLabel2.textContent = prevText2;
        exportBtn.disabled = false;
      }
      return;
    }
    if (selectedIds.size === 0) {
      showToast({ message: "请至少选择一条消息", level: "warning" });
      return;
    }
    exportBtn.disabled = true;
    const origLabel = exportBtn.querySelector(".gmd-btn__label");
    const prevText = origLabel.textContent;
    origLabel.textContent = "导出中…";
    try {
      await cb.onExport(Array.from(selectedIds), selectedTemplateId);
      showToast({ message: "导出成功", level: "success" });
    } catch (err) {
      showToast({ message: `导出失败: ${err.message}`, level: "error" });
    } finally {
      origLabel.textContent = prevText;
      exportBtn.disabled = false;
    }
  }
  function refreshExportTab(cb) {
    loadSession(cb);
  }
  function refreshExportTemplates(cb) {
    if (!root) return;
    loadTemplates(cb);
  }
  function setupUrlWatcher(cb) {
    let lastSessionId = getCurrentSessionId();
    function checkUrlChange() {
      const newId = getCurrentSessionId();
      if (newId !== lastSessionId) {
        lastSessionId = newId;
        refreshExportTab(cb);
      }
    }
    const origPushState = history.pushState.bind(history);
    const origReplaceState = history.replaceState.bind(history);
    history.pushState = function(...args) {
      origPushState(...args);
      checkUrlChange();
    };
    history.replaceState = function(...args) {
      origReplaceState(...args);
      checkUrlChange();
    };
    window.addEventListener("popstate", checkUrlChange);
  }
  function createSwitch(opts) {
    const wrapper = el("label", "gmd-switch");
    const input = el("input", "gmd-switch__input", { type: "checkbox" });
    if (opts.checked) input.checked = true;
    const track = el("span", "gmd-switch__track");
    const thumb = el("span", "gmd-switch__thumb");
    track.appendChild(thumb);
    const thumbIcon = el("span", "gmd-switch__icon");
    thumbIcon.innerHTML = iconStroke(iconSize(ICON_CHECK, 14), 3);
    thumb.appendChild(thumbIcon);
    wrapper.appendChild(input);
    wrapper.appendChild(track);
    if (opts.label) {
      const lbl = el("span", "gmd-switch__label");
      lbl.textContent = opts.label;
      wrapper.appendChild(lbl);
    }
    function sync() {
      track.classList.toggle("gmd-switch__track--checked", input.checked);
    }
    sync();
    input.addEventListener("change", () => {
      sync();
      opts.onChange?.(input.checked);
    });
    return wrapper;
  }
  function setSwitchState(wrapper, checked) {
    const input = wrapper.querySelector("input");
    if (input) {
      input.checked = checked;
      const track = wrapper.querySelector(".gmd-switch__track");
      track?.classList.toggle("gmd-switch__track--checked", checked);
    }
  }
  function debounce(fn, ms) {
    let timer = null;
    return (...args) => {
      if (timer !== null) clearTimeout(timer);
      timer = setTimeout(() => {
        fn(...args);
        timer = null;
      }, ms);
    };
  }
  let config;
  let templates = [];
  let templateListEl;
  let fabSwitch;
  let thinkingSwitch;
  let singleExportSwitch;
  let autoRefreshSwitch;
  let lineBreaksSelect;
  let prefixTextarea;
  let userTplTextarea;
  let assistantTplTextarea;
  let editorSegmented;
  let activeEditorId = "prefix";
  let cdnSection = null;
  let cdnTextarea;
  function renderSettingsTab(cb) {
    const root2 = el("div", "gmd-settings");
    const tplSection = createSection("模板管理");
    templateListEl = el("div", "gmd-settings__tpl-list");
    tplSection.appendChild(templateListEl);
    const uploadBtn = createButton({
      label: "上传模板",
      icon: ICON_UPLOAD,
      variant: "tonal",
      onClick: () => handleUpload(cb)
    });
    tplSection.appendChild(uploadBtn);
    root2.appendChild(tplSection);
    const displaySection = createSection("显示设置");
    fabSwitch = createSwitch({
      label: "显示悬浮球",
      checked: true,
      onChange: (checked) => {
        config.showFab = checked;
        cb.onConfigChange({ showFab: checked });
        cb.onFabToggle?.(checked);
      }
    });
    displaySection.appendChild(fabSwitch);
    autoRefreshSwitch = createSwitch({
      label: "打开面板时自动刷新",
      checked: true,
      onChange: (checked) => {
        config.autoRefreshOnOpen = checked;
        cb.onConfigChange({ autoRefreshOnOpen: checked });
      }
    });
    displaySection.appendChild(autoRefreshSwitch);
    root2.appendChild(displaySection);
    const exportSettingsSection = createSection("导出设置");
    thinkingSwitch = createSwitch({
      label: "导出时包含思考内容",
      checked: false,
      onChange: (checked) => {
        config.includeThinking = checked;
        cb.onConfigChange({ includeThinking: checked });
      }
    });
    exportSettingsSection.appendChild(thinkingSwitch);
    singleExportSwitch = createSwitch({
      label: "单条导出时使用模板",
      checked: false,
      onChange: (checked) => {
        config.singleExportWithTemplate = checked;
        cb.onConfigChange({ singleExportWithTemplate: checked });
      }
    });
    exportSettingsSection.appendChild(singleExportSwitch);
    const lineBreaksLabel = el("label", "gmd-settings__select-label");
    lineBreaksLabel.textContent = "换行模式";
    exportSettingsSection.appendChild(lineBreaksLabel);
    lineBreaksSelect = createSelect({
      options: [
        { value: "hard", label: "强制换行(推荐)(每行保留,类似 Shift+Enter)" },
        { value: "default", label: "Pandoc 默认(标准 Markdown,换行合并)" },
        { value: "east_asian", label: "东亚优化(中日韩文字间换行忽略)" }
      ],
      value: "hard",
      onChange: (value) => {
        config.lineBreaks = value;
        cb.onConfigChange({ lineBreaks: config.lineBreaks });
      }
    });
    exportSettingsSection.appendChild(lineBreaksSelect);
    root2.appendChild(exportSettingsSection);
    const editorSection = createSection("模板文本编辑");
    editorSegmented = createSegmentedControl({
      segments: [
        { id: "prefix", label: "文档前缀" },
        { id: "user", label: "用户消息" },
        { id: "assistant", label: "助手消息" }
      ],
      activeId: activeEditorId,
      onChange: (id) => {
        activeEditorId = id;
        applyEditorSwitch();
      }
    });
    editorSection.appendChild(editorSegmented);
    prefixTextarea = createTextarea({
      label: "文档前缀(document prefix)",
      rows: 6,
      onChange: debounce((v) => {
        config.documentPrefix = v;
        cb.onConfigChange({ documentPrefix: v });
      }, 400)
    });
    editorSection.appendChild(prefixTextarea);
    userTplTextarea = createTextarea({
      label: "用户消息模板(user message)",
      rows: 6,
      onChange: debounce((v) => {
        config.userMessageTemplate = v;
        cb.onConfigChange({ userMessageTemplate: v });
      }, 400)
    });
    userTplTextarea.style.display = "none";
    editorSection.appendChild(userTplTextarea);
    assistantTplTextarea = createTextarea({
      label: "助手消息模板(assistant message)",
      rows: 6,
      onChange: debounce((v) => {
        config.assistantMessageTemplate = v;
        cb.onConfigChange({ assistantMessageTemplate: v });
      }, 400)
    });
    assistantTplTextarea.style.display = "none";
    editorSection.appendChild(assistantTplTextarea);
    const placeholderHint = el("p", "gmd-settings__hint");
    placeholderHint.textContent = "可用占位符: {title}, {output_date}, {content}, {thinking_content}";
    editorSection.appendChild(placeholderHint);
    root2.appendChild(editorSection);
    {
      cdnSection = createSection("Pandoc WASM CDN");
      cdnTextarea = createTextarea({
        label: "CDN URL 列表(每行一个,按顺序尝试)",
        rows: 3,
        wrap: "off",
        onChange: debounce((v) => {
          const urls = v.split("\n").map((l) => l.trim()).filter(Boolean);
          config.cdnUrls = urls;
          cb.onConfigChange({ cdnUrls: urls });
        }, 400)
      });
      cdnSection.appendChild(cdnTextarea);
      root2.appendChild(cdnSection);
    }
    const dangerSection = createSection("危险区域");
    const resetBtn = createButton({
      label: "重置设置",
      icon: ICON_REFRESH,
      variant: "tonal",
      onClick: async () => {
        try {
          config = await cb.onResetConfig();
          setSwitchState(fabSwitch, config.showFab);
          setSwitchState(autoRefreshSwitch, config.autoRefreshOnOpen);
          setSwitchState(thinkingSwitch, config.includeThinking);
          setSwitchState(singleExportSwitch, config.singleExportWithTemplate);
          lineBreaksSelect.value = config.lineBreaks;
          setTextareaValue(prefixTextarea, config.documentPrefix);
          setTextareaValue(userTplTextarea, config.userMessageTemplate);
          setTextareaValue(assistantTplTextarea, config.assistantMessageTemplate);
          if (cdnSection && true) {
            setTextareaValue(cdnTextarea, config.cdnUrls.join("\n"));
          }
          cb.onFabToggle?.(config.showFab);
          showToast({ message: "设置已重置为默认值", level: "success" });
        } catch (err) {
          showToast({ message: `重置失败: ${err.message}`, level: "error" });
        }
      }
    });
    dangerSection.appendChild(resetBtn);
    const clearCacheBtn = createButton({
      label: "清除缓存",
      icon: ICON_TRASH,
      variant: "tonal",
      onClick: async () => {
        try {
          await cb.onClearCache();
          showToast({ message: "缓存已清除,下次加载将重新下载", level: "success" });
        } catch (err) {
          showToast({ message: `清除缓存失败: ${err.message}`, level: "error" });
        }
      }
    });
    dangerSection.appendChild(clearCacheBtn);
    root2.appendChild(dangerSection);
    loadData(cb);
    return root2;
  }
  function renderTemplateList(cb) {
    templateListEl.innerHTML = "";
    for (const tpl of templates) {
      const isSelected = tpl.id === config.selectedTemplateId;
      const row = el("div", "gmd-settings__tpl-row");
      if (isSelected) row.classList.add("gmd-settings__tpl-row--selected");
      row.style.cursor = isSelected ? "default" : "pointer";
      row.addEventListener("click", async (e) => {
        if (e.target.closest(".gmd-icon-btn")) return;
        if (tpl.id === config.selectedTemplateId) return;
        config.selectedTemplateId = tpl.id;
        await cb.onConfigChange({ selectedTemplateId: tpl.id });
        renderTemplateList(cb);
        showToast({ message: `已将 "${tpl.name}" 设为默认模板`, level: "success" });
      });
      const nameEl = el("span", "gmd-settings__tpl-name");
      nameEl.textContent = tpl.name;
      row.appendChild(nameEl);
      if (tpl.description) {
        const descEl = el("span", "gmd-settings__tpl-desc");
        descEl.textContent = tpl.description;
        row.appendChild(descEl);
      }
      if (!tpl.isBuiltin) {
        const delBtn = createIconButton({
          icon: ICON_TRASH,
          title: "删除",
          variant: "standard",
          onClick: async () => {
            await cb.onTemplateDelete(tpl.id);
            templates = templates.filter((t) => t.id !== tpl.id);
            if (config.selectedTemplateId === tpl.id) {
              const fallback = templates.find((t) => t.isBuiltin) ?? templates[0];
              if (fallback) {
                config.selectedTemplateId = fallback.id;
                await cb.onConfigChange({ selectedTemplateId: fallback.id });
              }
            }
            renderTemplateList(cb);
            showToast({ message: `已删除 "${tpl.name}"`, level: "info" });
          }
        });
        row.appendChild(delBtn);
      }
      const badge = el("span", "gmd-settings__tpl-badge");
      badge.textContent = tpl.isBuiltin ? "内置" : "自定义";
      row.appendChild(badge);
      templateListEl.appendChild(row);
    }
  }
  function handleUpload(cb) {
    const input = document.createElement("input");
    input.type = "file";
    input.accept = ".docx";
    input.addEventListener("change", async () => {
      const file = input.files?.[0];
      if (!file) return;
      try {
        const buffer = await file.arrayBuffer();
        const name = file.name.replace(/\.docx$/i, "");
        await cb.onTemplateUpload(name, buffer);
        templates = await cb.getTemplateList();
        renderTemplateList(cb);
        showToast({ message: `已上传模板 "${name}"`, level: "success" });
      } catch (err) {
        showToast({ message: `上传失败: ${err.message}`, level: "error" });
      }
    });
    input.click();
  }
  async function loadData(cb) {
    try {
      config = await cb.getConfig();
      templates = await cb.getTemplateList();
      setSwitchState(fabSwitch, config.showFab);
      setSwitchState(autoRefreshSwitch, config.autoRefreshOnOpen);
      setSwitchState(thinkingSwitch, config.includeThinking);
      setSwitchState(singleExportSwitch, config.singleExportWithTemplate);
      lineBreaksSelect.value = config.lineBreaks;
      setTextareaValue(prefixTextarea, config.documentPrefix);
      setTextareaValue(userTplTextarea, config.userMessageTemplate);
      setTextareaValue(assistantTplTextarea, config.assistantMessageTemplate);
      if (cdnSection && true) {
        setTextareaValue(cdnTextarea, config.cdnUrls.join("\n"));
      }
      renderTemplateList(cb);
    } catch (err) {
      showToast({ message: `加载设置失败: ${err.message}`, level: "error" });
    }
  }
  function createSection(title) {
    const section = el("div", "gmd-settings__section");
    const heading = el("h3", "gmd-settings__section-title");
    heading.textContent = title;
    section.appendChild(heading);
    return section;
  }
  function setTextareaValue(wrapper, value) {
    const ta = wrapper.querySelector("textarea");
    if (ta) ta.value = value;
  }
  function applyEditorSwitch() {
    prefixTextarea.style.display = activeEditorId === "prefix" ? "" : "none";
    userTplTextarea.style.display = activeEditorId === "user" ? "" : "none";
    assistantTplTextarea.style.display = activeEditorId === "assistant" ? "" : "none";
  }
  function renderAboutTab(cb) {
    const root2 = el("div", "gmd-about");
    const statusCard = el("div", "gmd-about__card");
    const statusTitle = el("h3", "gmd-about__card-title");
    statusTitle.textContent = "系统状态";
    statusCard.appendChild(statusTitle);
    statusCard.appendChild(
      createInfoRow("运行环境", "Tampermonkey 油猴脚本")
    );
    const pandocStatusRow = createInfoRow("Pandoc 引擎", "检测中…");
    statusCard.appendChild(pandocStatusRow);
    const pandocVersionRow = createInfoRow("Pandoc 版本", "—");
    statusCard.appendChild(pandocVersionRow);
    root2.appendChild(statusCard);
    updatePandocStatus(cb, pandocStatusRow, pandocVersionRow);
    const infoCard = el("div", "gmd-about__card");
    const infoTitle = el("h3", "gmd-about__card-title");
    infoTitle.textContent = "项目信息";
    infoCard.appendChild(infoTitle);
    infoCard.appendChild(createInfoRow("名称", "Give Me Doc"));
    infoCard.appendChild(createInfoRow("版本", "1.0.0"));
    infoCard.appendChild(
      createInfoRow("描述", "将 AI 对话导出为 Word 文档 — 由 Pandoc WASM 驱动")
    );
    infoCard.appendChild(createInfoRow("许可证", "AGPL-3.0"));
    root2.appendChild(infoCard);
    const linksCard = el("div", "gmd-about__card");
    const linksTitle = el("h3", "gmd-about__card-title");
    linksTitle.textContent = "链接";
    linksCard.appendChild(linksTitle);
    linksCard.appendChild(
      createLinkRow(ICON_GITHUB, "GitHub 仓库", "https://github.com/Qalxry/GiveMeDoc")
    );
    linksCard.appendChild(
      createLinkRow(ICON_BUG, "报告问题", "https://github.com/Qalxry/GiveMeDoc/issues/new")
    );
    linksCard.appendChild(
      createLinkRow(ICON_EXTERNAL_LINK, "Pandoc 官网", "https://pandoc.org")
    );
    root2.appendChild(linksCard);
    return root2;
  }
  function createInfoRow(label, value) {
    const row = el("div", "gmd-about__row");
    const labelEl = el("span", "gmd-about__label");
    labelEl.textContent = label;
    row.appendChild(labelEl);
    const valueEl = el("span", "gmd-about__value");
    valueEl.textContent = value;
    row.appendChild(valueEl);
    return row;
  }
  function createLinkRow(icon, text2, href) {
    const row = el("a", "gmd-about__link");
    row.href = href;
    row.target = "_blank";
    row.rel = "noopener noreferrer";
    const iconEl = html("span", "gmd-about__link-icon", icon);
    row.appendChild(iconEl);
    const labelEl = el("span", "gmd-about__link-label");
    labelEl.textContent = text2;
    row.appendChild(labelEl);
    const arrow = html("span", "gmd-about__link-arrow", ICON_EXTERNAL_LINK);
    row.appendChild(arrow);
    return row;
  }
  async function updatePandocStatus(cb, statusRow, versionRow) {
    const statusVal = statusRow.querySelector(".gmd-about__value");
    const versionVal = versionRow.querySelector(".gmd-about__value");
    if (cb.isPandocReady()) {
      if (statusVal) {
        statusVal.textContent = "已就绪";
        statusVal.classList.add("gmd-about__value--ok");
      }
      if (versionVal) {
        const v = await cb.getPandocVersion();
        versionVal.textContent = v || "未知";
      }
    } else {
      if (statusVal) {
        statusVal.textContent = "未加载";
        statusVal.classList.add("gmd-about__value--warn");
      }
      if (versionVal) {
        versionVal.textContent = "—";
      }
    }
  }
  const LS_KEY = "gmd-fab-pos";
  const DEFAULT_PCT = 50;
  const MIN_PCT = 5;
  const MAX_PCT = 95;
  const DRAG_THRESHOLD = 4;
  let fabEl = null;
  let callbacks = null;
  function createFab(cb) {
    if (fabEl) return;
    callbacks = cb;
    fabEl = el("button", "gmd-fab");
    fabEl.type = "button";
    fabEl.title = "Give Me Doc";
    fabEl.setAttribute("aria-label", "Toggle Give Me Doc panel");
    fabEl.innerHTML = ICON_LOGO_FG;
    const savedPct = loadPosition();
    applyPosition(savedPct);
    let startY = 0;
    let dragging = false;
    let didDrag = false;
    function onPointerDown(e) {
      if (e.button !== 0) return;
      e.preventDefault();
      fabEl.setPointerCapture(e.pointerId);
      startY = e.clientY;
      loadPosition();
      dragging = true;
      didDrag = false;
      fabEl.classList.add("gmd-fab--dragging");
      document.addEventListener("pointermove", onPointerMove);
      document.addEventListener("pointerup", onPointerUp);
    }
    function onPointerMove(e) {
      if (!dragging) return;
      const dy = Math.abs(e.clientY - startY);
      if (dy >= DRAG_THRESHOLD) didDrag = true;
      const pct = e.clientY / window.innerHeight * 100;
      const clamped = clampPct(pct);
      applyPosition(clamped);
    }
    function onPointerUp(e) {
      if (!dragging) return;
      dragging = false;
      fabEl.classList.remove("gmd-fab--dragging");
      document.removeEventListener("pointermove", onPointerMove);
      document.removeEventListener("pointerup", onPointerUp);
      if (didDrag) {
        const pct = e.clientY / window.innerHeight * 100;
        const clamped = clampPct(pct);
        savePosition(clamped);
        applyPosition(clamped);
      } else {
        handleClick();
      }
    }
    fabEl.addEventListener("pointerdown", onPointerDown);
    if (isPanelVisible()) {
      fabEl.classList.add("gmd-fab--hidden");
    }
    document.body.appendChild(fabEl);
  }
  function hideFab() {
    fabEl?.classList.add("gmd-fab--hidden");
  }
  function showFab() {
    fabEl?.classList.remove("gmd-fab--hidden");
  }
  function destroyFab() {
    if (fabEl) {
      fabEl.remove();
      fabEl = null;
      callbacks = null;
    }
  }
  function isFabMounted() {
    return fabEl !== null;
  }
  function handleClick() {
    if (!callbacks) return;
    hideFab();
    showPanel(callbacks);
  }
  function clampPct(pct) {
    return Math.max(MIN_PCT, Math.min(MAX_PCT, pct));
  }
  function applyPosition(pct) {
    if (!fabEl) return;
    fabEl.style.top = `${pct}%`;
  }
  function loadPosition() {
    try {
      const raw = localStorage.getItem(LS_KEY);
      if (raw !== null) {
        const n = Number(raw);
        if (!Number.isNaN(n)) return clampPct(n);
      }
    } catch {
    }
    return DEFAULT_PCT;
  }
  function savePosition(pct) {
    try {
      localStorage.setItem(LS_KEY, String(pct));
    } catch {
    }
  }
  let panelEl = null;
  let isVisible = false;
  let clickOutsideHandler = null;
  function createPanel(cb, opts) {
    if (panelEl) return;
    const container2 = document.body;
    panelEl = el("div", "gmd-panel");
    panelEl.setAttribute("data-gmd-panel", "");
    const header = el("div", "gmd-panel__header");
    const titleWrap = el("div", "gmd-panel__title-wrap");
    const logoIcon = html("span", "gmd-panel__logo", ICON_LOGO);
    const titleText = el("span", "gmd-panel__title");
    titleText.textContent = "Give Me Doc";
    append(titleWrap, logoIcon, titleText);
    {
      const closeBtn = createIconButton({
        icon: ICON_X,
        title: "关闭面板",
        variant: "standard",
        onClick: () => hidePanel()
      });
      append(header, titleWrap, closeBtn);
      panelEl.appendChild(header);
    }
    const tabs = [
      {
        id: "export",
        label: "导出",
        icon: ICON_FILE_TEXT,
        render: () => renderExportTab(cb)
      },
      {
        id: "settings",
        label: "设置",
        icon: ICON_SETTINGS,
        render: () => renderSettingsTab(cb)
      },
      {
        id: "about",
        label: "关于",
        icon: ICON_INFO,
        render: () => renderAboutTab(cb)
      }
    ];
    const { root: tabsRoot } = createTabs({
      tabs,
      activeId: "export",
      onTabChange: (tabId) => {
        if (tabId === "export") refreshExportTemplates(cb);
      }
    });
    panelEl.appendChild(tabsRoot);
    container2.appendChild(panelEl);
    isVisible = true;
    {
      enableDrag(header, panelEl);
      clickOutsideHandler = (e) => {
        if (!isVisible || !panelEl) return;
        const target = e.target;
        if (panelEl.contains(target)) return;
        if (target.closest?.(".gmd-toast-container")) return;
        if (target.closest?.("[data-gmd-trigger]")) return;
        if (target.closest?.(".gmd-fab")) return;
        hidePanel();
      };
      document.addEventListener("mousedown", clickOutsideHandler);
    }
  }
  async function showPanel(cb, opts) {
    if (!panelEl) {
      createPanel(cb);
    } else {
      panelEl.classList.remove("gmd-panel--hidden");
      isVisible = true;
      const cfg = await cb.getConfig();
      if (cfg.autoRefreshOnOpen) refreshExportTab(cb);
    }
  }
  function hidePanel() {
    if (panelEl) {
      panelEl.classList.add("gmd-panel--hidden");
      isVisible = false;
      if (isFabMounted()) showFab();
    }
  }
  function togglePanel(cb) {
    if (isVisible) hidePanel();
    else showPanel(cb);
  }
  function isPanelVisible() {
    return isVisible;
  }
  function enableDrag(handle, target) {
    let startY = 0;
    let origY = 0;
    let dragging = false;
    handle.style.cursor = "grab";
    function onMouseDown(e) {
      if (e.button !== 0) return;
      if (e.target.closest("button, input, a")) return;
      dragging = true;
      startY = e.clientY;
      const rect = target.getBoundingClientRect();
      origY = rect.top;
      handle.style.cursor = "grabbing";
      document.addEventListener("mousemove", onMouseMove);
      document.addEventListener("mouseup", onMouseUp);
      e.preventDefault();
    }
    function onMouseMove(e) {
      if (!dragging) return;
      const dy = e.clientY - startY;
      const h = target.offsetHeight;
      const vh = window.innerHeight;
      const newTop = Math.max(0, Math.min(vh - h, origY + dy));
      target.style.top = `${newTop}px`;
      target.style.bottom = "auto";
    }
    function onMouseUp() {
      dragging = false;
      handle.style.cursor = "grab";
      document.removeEventListener("mousemove", onMouseMove);
      document.removeEventListener("mouseup", onMouseUp);
    }
    handle.addEventListener("mousedown", onMouseDown);
  }
  const DEFAULT_CONFIG = {
    locale: "zh-CN",
    includeThinking: false,
    singleExportWithTemplate: false,
    showFab: true,
    autoRefreshOnOpen: true,
    selectedTemplateId: "builtin-minimal",
    lineBreaks: "hard",
    documentPrefix: `---
title: {title}
date: {output_date}
---

`,
    userMessageTemplate: `***

**用户:**

{content}

`,
    assistantMessageTemplate: `***

**助手:**

> {thinking_content}

{content}

`,
    cdnUrls: [
      "https://pandoc.org/app/pandoc.wasm?sha1=2ab8055eb0803168da93d4b784fe40aa06551dfa"
    ],
    exportMode: "session",
    freetextMd: "",
    freetextFilename: "export"
  };
  const BUILTIN_TEMPLATES = {
    "builtin-minimal": {
      name: "简单样式",
      description: "宋体+Times,无额外格式",
      data: "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"
    },
    "builtin-academic": {
      name: "学术文档风格",
      description: "适合学术论文、技术报告",
      data: "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"
    },
    "builtin-manual": {
      name: "企业手册风格",
      description: "适合企业文档、操作手册",
      data: "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"
    },
    "builtin-gw": {
      name: "党政公文风格",
      description: "参考GB/T 9704公文风格",
      data: "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"
    },
    "builtin-modern": {
      name: "现代简约风格",
      description: "华文细黑,适合现代报告",
      data: "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"
    }
  };
  function b64ToArrayBuffer(b64) {
    const bin = atob(b64);
    const buf = new Uint8Array(bin.length);
    for (let i = 0; i < bin.length; i++) buf[i] = bin.charCodeAt(i);
    return buf.buffer;
  }
  function arrayBufferToB64(ab) {
    const bytes = new Uint8Array(ab);
    let s = "";
    for (let i = 0; i < bytes.length; i++) s += String.fromCharCode(bytes[i]);
    return btoa(s);
  }
  async function loadConfig(storage) {
    const saved = await storage.get("config");
    return { ...DEFAULT_CONFIG, ...saved };
  }
  async function saveConfigPartial(storage, partial) {
    const current = await loadConfig(storage);
    await storage.set("config", { ...current, ...partial });
  }
  const builtinTemplates = Object.entries(BUILTIN_TEMPLATES).map(
    ([id, tpl]) => ({ id, name: tpl.name, isBuiltin: true, description: tpl.description })
  );
  async function getTemplateList(storage) {
    const custom = await storage.get("custom-templates") ?? [];
    return [...builtinTemplates, ...custom];
  }
  async function uploadTemplate(storage, name, data) {
    const id = `custom-${Date.now()}`;
    const meta = { id, name, isBuiltin: false };
    const list = await storage.get("custom-templates") ?? [];
    list.push(meta);
    await storage.set("custom-templates", list);
    await storage.setBlob(`tpl-blob-${id}`, data);
  }
  async function deleteTemplate(storage, id) {
    const list = await storage.get("custom-templates") ?? [];
    await storage.set("custom-templates", list.filter((t) => t.id !== id));
    await storage.remove(`tpl-blob-${id}`);
  }
  async function getTemplateBlob(storage, id) {
    if (id.startsWith("builtin-")) {
      const tpl = BUILTIN_TEMPLATES[id];
      if (!tpl) return void 0;
      return b64ToArrayBuffer(tpl.data);
    }
    return await storage.getBlob(`tpl-blob-${id}`) ?? void 0;
  }
  function createCallbacks(storage, clearCacheFn, fabToggleFn, overrides) {
    const base = {
      async onExport(selectedIds2, templateId) {
        const sessionId = getCurrentSessionId();
        if (!sessionId) throw new Error("未检测到会话 ID");
        const session2 = await getSession(sessionId);
        const messages = [];
        for (const id of selectedIds2) {
          const msg = session2.messages.get(id);
          if (msg) messages.push(msg);
        }
        const depthOf = (m) => {
          let d = 0;
          let cur = m.parentId;
          while (cur) {
            const p = session2.messages.get(cur);
            if (!p) break;
            d++;
            cur = p.parentId;
          }
          return d;
        };
        messages.sort((a, b) => depthOf(a) - depthOf(b));
        if (messages.length === 0) throw new Error("没有选中任何消息");
        const config2 = await loadConfig(storage);
        const refDocx = await getTemplateBlob(storage, templateId);
        const { blob: blob2, filename } = await exportToDocx(messages, config2, session2.title, refDocx);
        downloadBlob(blob2, filename);
      },
      async onExportRaw(markdown, templateId, filename) {
        if (!isPandocReady()) throw new Error("Pandoc 尚未就绪");
        const config2 = await loadConfig(storage);
        const refDocx = await getTemplateBlob(storage, templateId);
        const { blob: blob2, filename: outName } = await exportRawToDocx(markdown, filename, refDocx, config2.lineBreaks);
        downloadBlob(blob2, outName);
      },
      async onTemplateUpload(name, data) {
        await uploadTemplate(storage, name, data);
      },
      async onTemplateDelete(id) {
        await deleteTemplate(storage, id);
      },
      async onConfigChange(partial) {
        await saveConfigPartial(storage, partial);
      },
      async onResetConfig() {
        const fresh = { ...DEFAULT_CONFIG };
        await storage.set("config", fresh);
        return fresh;
      },
      async onClearCache() {
        if (clearCacheFn) {
          await clearCacheFn();
        }
      },
      async getConfig() {
        return loadConfig(storage);
      },
      async getTemplateList() {
        return getTemplateList(storage);
      },
      async getSession() {
        const id = getCurrentSessionId();
        if (!id) return null;
        return getSession(id);
      },
      async getPandocVersion() {
        return getPandocVersion();
      },
      isPandocReady() {
        return isPandocReady();
      },
      onFabToggle: fabToggleFn
    };
    return base;
  }
  function createSingleExportHandler(storage) {
    return async (md, title) => {
      if (!isPandocReady()) {
        showToast({ message: "Pandoc 尚未就绪,请稍候…", level: "warning" });
        return;
      }
      try {
        const config2 = await loadConfig(storage);
        const refDocx = await getTemplateBlob(storage, config2.selectedTemplateId);
        const effectiveConfig = config2.singleExportWithTemplate ? config2 : { ...config2, documentPrefix: "", userMessageTemplate: "{content}\n", assistantMessageTemplate: "{content}\n" };
        const msg = {
          id: "0",
          parentId: null,
          role: "assistant",
          content: md,
          thinkingContent: "",
          timestamp: Date.now(),
          status: "finished",
          childrenIds: []
        };
        const { blob: blob2, filename } = await exportToDocx([msg], effectiveConfig, title, refDocx);
        downloadBlob(blob2, filename);
        showToast({ message: "导出成功", level: "success" });
      } catch (err) {
        showToast({ message: `导出失败: ${err.message}`, level: "error" });
      }
    };
  }
  function createShareExportHandler(storage) {
    return async (selectedIndices) => {
      if (!isPandocReady()) {
        showToast({ message: "Pandoc 尚未就绪,请稍候…", level: "warning" });
        return;
      }
      try {
        const sessionId = getCurrentSessionId();
        if (!sessionId) throw new Error("未检测到会话 ID");
        const session2 = await getSession(sessionId);
        const chain2 = getActiveChain(session2);
        const messages = selectedIndices.map((i) => chain2[i]).filter(Boolean);
        if (messages.length === 0) throw new Error("没有选中任何消息");
        const config2 = await loadConfig(storage);
        const refDocx = await getTemplateBlob(storage, config2.selectedTemplateId);
        const { blob: blob2, filename } = await exportToDocx(messages, config2, session2.title, refDocx);
        downloadBlob(blob2, filename);
        showToast({ message: "导出成功", level: "success" });
      } catch (err) {
        showToast({ message: `导出失败: ${err.message}`, level: "error" });
      }
    };
  }
  const gmStorage = {
    async get(key) {
      const v = GM_getValue(key, null);
      return v;
    },
    async set(key, value) {
      GM_setValue(key, value);
    },
    async remove(key) {
      GM_deleteValue(key);
    },
    async getBlob(key) {
      const b64 = GM_getValue(key, null);
      if (!b64) return null;
      return b64ToArrayBuffer(b64);
    },
    async setBlob(key, value) {
      GM_setValue(key, arrayBufferToB64(value));
    }
  };
  const jsContent = '(function() {\n  "use strict";\n  /**\n   * @license\n   * Copyright 2019 Google LLC\n   * SPDX-License-Identifier: Apache-2.0\n   */\n  const proxyMarker = Symbol("Comlink.proxy");\n  const createEndpoint = Symbol("Comlink.endpoint");\n  const releaseProxy = Symbol("Comlink.releaseProxy");\n  const finalizer = Symbol("Comlink.finalizer");\n  const throwMarker = Symbol("Comlink.thrown");\n  const isObject = (val) => typeof val === "object" && val !== null || typeof val === "function";\n  const proxyTransferHandler = {\n    canHandle: (val) => isObject(val) && val[proxyMarker],\n    serialize(obj) {\n      const { port1, port2 } = new MessageChannel();\n      expose(obj, port1);\n      return [port2, [port2]];\n    },\n    deserialize(port) {\n      port.start();\n      return wrap(port);\n    }\n  };\n  const throwTransferHandler = {\n    canHandle: (value) => isObject(value) && throwMarker in value,\n    serialize({ value }) {\n      let serialized;\n      if (value instanceof Error) {\n        serialized = {\n          isError: true,\n          value: {\n            message: value.message,\n            name: value.name,\n            stack: value.stack\n          }\n        };\n      } else {\n        serialized = { isError: false, value };\n      }\n      return [serialized, []];\n    },\n    deserialize(serialized) {\n      if (serialized.isError) {\n        throw Object.assign(new Error(serialized.value.message), serialized.value);\n      }\n      throw serialized.value;\n    }\n  };\n  const transferHandlers = /* @__PURE__ */ new Map([\n    ["proxy", proxyTransferHandler],\n    ["throw", throwTransferHandler]\n  ]);\n  function isAllowedOrigin(allowedOrigins, origin) {\n    for (const allowedOrigin of allowedOrigins) {\n      if (origin === allowedOrigin || allowedOrigin === "*") {\n        return true;\n      }\n      if (allowedOrigin instanceof RegExp && allowedOrigin.test(origin)) {\n        return true;\n      }\n    }\n    return false;\n  }\n  function expose(obj, ep = globalThis, allowedOrigins = ["*"]) {\n    ep.addEventListener("message", function callback(ev) {\n      if (!ev || !ev.data) {\n        return;\n      }\n      if (!isAllowedOrigin(allowedOrigins, ev.origin)) {\n        console.warn(`Invalid origin \'${ev.origin}\' for comlink proxy`);\n        return;\n      }\n      const { id, type, path } = Object.assign({ path: [] }, ev.data);\n      const argumentList = (ev.data.argumentList || []).map(fromWireValue);\n      let returnValue;\n      try {\n        const parent = path.slice(0, -1).reduce((obj2, prop) => obj2[prop], obj);\n        const rawValue = path.reduce((obj2, prop) => obj2[prop], obj);\n        switch (type) {\n          case "GET":\n            {\n              returnValue = rawValue;\n            }\n            break;\n          case "SET":\n            {\n              parent[path.slice(-1)[0]] = fromWireValue(ev.data.value);\n              returnValue = true;\n            }\n            break;\n          case "APPLY":\n            {\n              returnValue = rawValue.apply(parent, argumentList);\n            }\n            break;\n          case "CONSTRUCT":\n            {\n              const value = new rawValue(...argumentList);\n              returnValue = proxy(value);\n            }\n            break;\n          case "ENDPOINT":\n            {\n              const { port1, port2 } = new MessageChannel();\n              expose(obj, port2);\n              returnValue = transfer(port1, [port1]);\n            }\n            break;\n          case "RELEASE":\n            {\n              returnValue = void 0;\n            }\n            break;\n          default:\n            return;\n        }\n      } catch (value) {\n        returnValue = { value, [throwMarker]: 0 };\n      }\n      Promise.resolve(returnValue).catch((value) => {\n        return { value, [throwMarker]: 0 };\n      }).then((returnValue2) => {\n        const [wireValue, transferables] = toWireValue(returnValue2);\n        ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables);\n        if (type === "RELEASE") {\n          ep.removeEventListener("message", callback);\n          closeEndPoint(ep);\n          if (finalizer in obj && typeof obj[finalizer] === "function") {\n            obj[finalizer]();\n          }\n        }\n      }).catch((error) => {\n        const [wireValue, transferables] = toWireValue({\n          value: new TypeError("Unserializable return value"),\n          [throwMarker]: 0\n        });\n        ep.postMessage(Object.assign(Object.assign({}, wireValue), { id }), transferables);\n      });\n    });\n    if (ep.start) {\n      ep.start();\n    }\n  }\n  function isMessagePort(endpoint) {\n    return endpoint.constructor.name === "MessagePort";\n  }\n  function closeEndPoint(endpoint) {\n    if (isMessagePort(endpoint))\n      endpoint.close();\n  }\n  function wrap(ep, target) {\n    const pendingListeners = /* @__PURE__ */ new Map();\n    ep.addEventListener("message", function handleMessage(ev) {\n      const { data } = ev;\n      if (!data || !data.id) {\n        return;\n      }\n      const resolver = pendingListeners.get(data.id);\n      if (!resolver) {\n        return;\n      }\n      try {\n        resolver(data);\n      } finally {\n        pendingListeners.delete(data.id);\n      }\n    });\n    return createProxy(ep, pendingListeners, [], target);\n  }\n  function throwIfProxyReleased(isReleased) {\n    if (isReleased) {\n      throw new Error("Proxy has been released and is not useable");\n    }\n  }\n  function releaseEndpoint(ep) {\n    return requestResponseMessage(ep, /* @__PURE__ */ new Map(), {\n      type: "RELEASE"\n    }).then(() => {\n      closeEndPoint(ep);\n    });\n  }\n  const proxyCounter = /* @__PURE__ */ new WeakMap();\n  const proxyFinalizers = "FinalizationRegistry" in globalThis && new FinalizationRegistry((ep) => {\n    const newCount = (proxyCounter.get(ep) || 0) - 1;\n    proxyCounter.set(ep, newCount);\n    if (newCount === 0) {\n      releaseEndpoint(ep);\n    }\n  });\n  function registerProxy(proxy2, ep) {\n    const newCount = (proxyCounter.get(ep) || 0) + 1;\n    proxyCounter.set(ep, newCount);\n    if (proxyFinalizers) {\n      proxyFinalizers.register(proxy2, ep, proxy2);\n    }\n  }\n  function unregisterProxy(proxy2) {\n    if (proxyFinalizers) {\n      proxyFinalizers.unregister(proxy2);\n    }\n  }\n  function createProxy(ep, pendingListeners, path = [], target = function() {\n  }) {\n    let isProxyReleased = false;\n    const proxy2 = new Proxy(target, {\n      get(_target, prop) {\n        throwIfProxyReleased(isProxyReleased);\n        if (prop === releaseProxy) {\n          return () => {\n            unregisterProxy(proxy2);\n            releaseEndpoint(ep);\n            pendingListeners.clear();\n            isProxyReleased = true;\n          };\n        }\n        if (prop === "then") {\n          if (path.length === 0) {\n            return { then: () => proxy2 };\n          }\n          const r = requestResponseMessage(ep, pendingListeners, {\n            type: "GET",\n            path: path.map((p) => p.toString())\n          }).then(fromWireValue);\n          return r.then.bind(r);\n        }\n        return createProxy(ep, pendingListeners, [...path, prop]);\n      },\n      set(_target, prop, rawValue) {\n        throwIfProxyReleased(isProxyReleased);\n        const [value, transferables] = toWireValue(rawValue);\n        return requestResponseMessage(ep, pendingListeners, {\n          type: "SET",\n          path: [...path, prop].map((p) => p.toString()),\n          value\n        }, transferables).then(fromWireValue);\n      },\n      apply(_target, _thisArg, rawArgumentList) {\n        throwIfProxyReleased(isProxyReleased);\n        const last = path[path.length - 1];\n        if (last === createEndpoint) {\n          return requestResponseMessage(ep, pendingListeners, {\n            type: "ENDPOINT"\n          }).then(fromWireValue);\n        }\n        if (last === "bind") {\n          return createProxy(ep, pendingListeners, path.slice(0, -1));\n        }\n        const [argumentList, transferables] = processArguments(rawArgumentList);\n        return requestResponseMessage(ep, pendingListeners, {\n          type: "APPLY",\n          path: path.map((p) => p.toString()),\n          argumentList\n        }, transferables).then(fromWireValue);\n      },\n      construct(_target, rawArgumentList) {\n        throwIfProxyReleased(isProxyReleased);\n        const [argumentList, transferables] = processArguments(rawArgumentList);\n        return requestResponseMessage(ep, pendingListeners, {\n          type: "CONSTRUCT",\n          path: path.map((p) => p.toString()),\n          argumentList\n        }, transferables).then(fromWireValue);\n      }\n    });\n    registerProxy(proxy2, ep);\n    return proxy2;\n  }\n  function myFlat(arr) {\n    return Array.prototype.concat.apply([], arr);\n  }\n  function processArguments(argumentList) {\n    const processed = argumentList.map(toWireValue);\n    return [processed.map((v) => v[0]), myFlat(processed.map((v) => v[1]))];\n  }\n  const transferCache = /* @__PURE__ */ new WeakMap();\n  function transfer(obj, transfers) {\n    transferCache.set(obj, transfers);\n    return obj;\n  }\n  function proxy(obj) {\n    return Object.assign(obj, { [proxyMarker]: true });\n  }\n  function toWireValue(value) {\n    for (const [name, handler] of transferHandlers) {\n      if (handler.canHandle(value)) {\n        const [serializedValue, transferables] = handler.serialize(value);\n        return [\n          {\n            type: "HANDLER",\n            name,\n            value: serializedValue\n          },\n          transferables\n        ];\n      }\n    }\n    return [\n      {\n        type: "RAW",\n        value\n      },\n      transferCache.get(value) || []\n    ];\n  }\n  function fromWireValue(value) {\n    switch (value.type) {\n      case "HANDLER":\n        return transferHandlers.get(value.name).deserialize(value.value);\n      case "RAW":\n        return value.value;\n    }\n  }\n  function requestResponseMessage(ep, pendingListeners, msg, transfers) {\n    return new Promise((resolve) => {\n      const id = generateUUID();\n      pendingListeners.set(id, resolve);\n      if (ep.start) {\n        ep.start();\n      }\n      ep.postMessage(Object.assign({ id }, msg), transfers);\n    });\n  }\n  function generateUUID() {\n    return new Array(4).fill(0).map(() => Math.floor(Math.random() * Number.MAX_SAFE_INTEGER).toString(16)).join("-");\n  }\n  const CLOCKID_REALTIME = 0;\n  const CLOCKID_MONOTONIC = 1;\n  const ERRNO_SUCCESS = 0;\n  const ERRNO_BADF = 8;\n  const ERRNO_EXIST = 20;\n  const ERRNO_INVAL = 28;\n  const ERRNO_ISDIR = 31;\n  const ERRNO_NAMETOOLONG = 37;\n  const ERRNO_NOENT = 44;\n  const ERRNO_NOSYS = 52;\n  const ERRNO_NOTDIR = 54;\n  const ERRNO_NOTEMPTY = 55;\n  const ERRNO_NOTSUP = 58;\n  const ERRNO_PERM = 63;\n  const ERRNO_NOTCAPABLE = 76;\n  const RIGHTS_FD_WRITE = 1 << 6;\n  class Iovec {\n    static read_bytes(view, ptr) {\n      const iovec = new Iovec();\n      iovec.buf = view.getUint32(ptr, true);\n      iovec.buf_len = view.getUint32(ptr + 4, true);\n      return iovec;\n    }\n    static read_bytes_array(view, ptr, len) {\n      const iovecs = [];\n      for (let i = 0; i < len; i++) {\n        iovecs.push(Iovec.read_bytes(view, ptr + 8 * i));\n      }\n      return iovecs;\n    }\n  }\n  class Ciovec {\n    static read_bytes(view, ptr) {\n      const iovec = new Ciovec();\n      iovec.buf = view.getUint32(ptr, true);\n      iovec.buf_len = view.getUint32(ptr + 4, true);\n      return iovec;\n    }\n    static read_bytes_array(view, ptr, len) {\n      const iovecs = [];\n      for (let i = 0; i < len; i++) {\n        iovecs.push(Ciovec.read_bytes(view, ptr + 8 * i));\n      }\n      return iovecs;\n    }\n  }\n  const WHENCE_SET = 0;\n  const WHENCE_CUR = 1;\n  const WHENCE_END = 2;\n  const FILETYPE_CHARACTER_DEVICE = 2;\n  const FILETYPE_DIRECTORY = 3;\n  const FILETYPE_REGULAR_FILE = 4;\n  class Dirent {\n    head_length() {\n      return 24;\n    }\n    name_length() {\n      return this.dir_name.byteLength;\n    }\n    write_head_bytes(view, ptr) {\n      view.setBigUint64(ptr, this.d_next, true);\n      view.setBigUint64(ptr + 8, this.d_ino, true);\n      view.setUint32(ptr + 16, this.dir_name.length, true);\n      view.setUint8(ptr + 20, this.d_type);\n    }\n    write_name_bytes(view8, ptr, buf_len) {\n      view8.set(this.dir_name.slice(0, Math.min(this.dir_name.byteLength, buf_len)), ptr);\n    }\n    constructor(next_cookie, name, type) {\n      this.d_ino = 0n;\n      const encoded_name = new TextEncoder().encode(name);\n      this.d_next = next_cookie;\n      this.d_namlen = encoded_name.byteLength;\n      this.d_type = type;\n      this.dir_name = encoded_name;\n    }\n  }\n  const FDFLAGS_APPEND = 1 << 0;\n  class Fdstat {\n    write_bytes(view, ptr) {\n      view.setUint8(ptr, this.fs_filetype);\n      view.setUint16(ptr + 2, this.fs_flags, true);\n      view.setBigUint64(ptr + 8, this.fs_rights_base, true);\n      view.setBigUint64(ptr + 16, this.fs_rights_inherited, true);\n    }\n    constructor(filetype, flags) {\n      this.fs_rights_base = 0n;\n      this.fs_rights_inherited = 0n;\n      this.fs_filetype = filetype;\n      this.fs_flags = flags;\n    }\n  }\n  const OFLAGS_CREAT = 1 << 0;\n  const OFLAGS_DIRECTORY = 1 << 1;\n  const OFLAGS_EXCL = 1 << 2;\n  const OFLAGS_TRUNC = 1 << 3;\n  class Filestat {\n    write_bytes(view, ptr) {\n      view.setBigUint64(ptr, this.dev, true);\n      view.setBigUint64(ptr + 8, this.ino, true);\n      view.setUint8(ptr + 16, this.filetype);\n      view.setBigUint64(ptr + 24, this.nlink, true);\n      view.setBigUint64(ptr + 32, this.size, true);\n      view.setBigUint64(ptr + 38, this.atim, true);\n      view.setBigUint64(ptr + 46, this.mtim, true);\n      view.setBigUint64(ptr + 52, this.ctim, true);\n    }\n    constructor(filetype, size) {\n      this.dev = 0n;\n      this.ino = 0n;\n      this.nlink = 0n;\n      this.atim = 0n;\n      this.mtim = 0n;\n      this.ctim = 0n;\n      this.filetype = filetype;\n      this.size = size;\n    }\n  }\n  const PREOPENTYPE_DIR = 0;\n  class PrestatDir {\n    write_bytes(view, ptr) {\n      view.setUint32(ptr, this.pr_name.byteLength, true);\n    }\n    constructor(name) {\n      this.pr_name = new TextEncoder().encode(name);\n    }\n  }\n  class Prestat {\n    static dir(name) {\n      const prestat = new Prestat();\n      prestat.tag = PREOPENTYPE_DIR;\n      prestat.inner = new PrestatDir(name);\n      return prestat;\n    }\n    write_bytes(view, ptr) {\n      view.setUint32(ptr, this.tag, true);\n      this.inner.write_bytes(view, ptr + 4);\n    }\n  }\n  let Debug = class Debug {\n    enable(enabled) {\n      this.log = createLogger(enabled === void 0 ? true : enabled, this.prefix);\n    }\n    get enabled() {\n      return this.isEnabled;\n    }\n    constructor(isEnabled) {\n      this.isEnabled = isEnabled;\n      this.prefix = "wasi:";\n      this.enable(isEnabled);\n    }\n  };\n  function createLogger(enabled, prefix) {\n    if (enabled) {\n      const a = console.log.bind(console, "%c%s", "color: #265BA0", prefix);\n      return a;\n    } else {\n      return () => {\n      };\n    }\n  }\n  const debug = new Debug(false);\n  class WASIProcExit extends Error {\n    constructor(code) {\n      super("exit with exit code " + code);\n      this.code = code;\n    }\n  }\n  let WASI = class WASI {\n    start(instance2) {\n      this.inst = instance2;\n      try {\n        instance2.exports._start();\n        return 0;\n      } catch (e) {\n        if (e instanceof WASIProcExit) {\n          return e.code;\n        } else {\n          throw e;\n        }\n      }\n    }\n    initialize(instance2) {\n      this.inst = instance2;\n      if (instance2.exports._initialize) {\n        instance2.exports._initialize();\n      }\n    }\n    constructor(args, env, fds, options = {}) {\n      this.args = [];\n      this.env = [];\n      this.fds = [];\n      debug.enable(options.debug);\n      this.args = args;\n      this.env = env;\n      this.fds = fds;\n      const self = this;\n      this.wasiImport = { args_sizes_get(argc, argv_buf_size) {\n        const buffer = new DataView(self.inst.exports.memory.buffer);\n        buffer.setUint32(argc, self.args.length, true);\n        let buf_size = 0;\n        for (const arg of self.args) {\n          buf_size += arg.length + 1;\n        }\n        buffer.setUint32(argv_buf_size, buf_size, true);\n        debug.log(buffer.getUint32(argc, true), buffer.getUint32(argv_buf_size, true));\n        return 0;\n      }, args_get(argv, argv_buf) {\n        const buffer = new DataView(self.inst.exports.memory.buffer);\n        const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n        const orig_argv_buf = argv_buf;\n        for (let i = 0; i < self.args.length; i++) {\n          buffer.setUint32(argv, argv_buf, true);\n          argv += 4;\n          const arg = new TextEncoder().encode(self.args[i]);\n          buffer8.set(arg, argv_buf);\n          buffer.setUint8(argv_buf + arg.length, 0);\n          argv_buf += arg.length + 1;\n        }\n        if (debug.enabled) {\n          debug.log(new TextDecoder("utf-8").decode(buffer8.slice(orig_argv_buf, argv_buf)));\n        }\n        return 0;\n      }, environ_sizes_get(environ_count, environ_size) {\n        const buffer = new DataView(self.inst.exports.memory.buffer);\n        buffer.setUint32(environ_count, self.env.length, true);\n        let buf_size = 0;\n        for (const environ of self.env) {\n          buf_size += environ.length + 1;\n        }\n        buffer.setUint32(environ_size, buf_size, true);\n        debug.log(buffer.getUint32(environ_count, true), buffer.getUint32(environ_size, true));\n        return 0;\n      }, environ_get(environ, environ_buf) {\n        const buffer = new DataView(self.inst.exports.memory.buffer);\n        const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n        const orig_environ_buf = environ_buf;\n        for (let i = 0; i < self.env.length; i++) {\n          buffer.setUint32(environ, environ_buf, true);\n          environ += 4;\n          const e = new TextEncoder().encode(self.env[i]);\n          buffer8.set(e, environ_buf);\n          buffer.setUint8(environ_buf + e.length, 0);\n          environ_buf += e.length + 1;\n        }\n        if (debug.enabled) {\n          debug.log(new TextDecoder("utf-8").decode(buffer8.slice(orig_environ_buf, environ_buf)));\n        }\n        return 0;\n      }, clock_res_get(id, res_ptr) {\n        let resolutionValue;\n        switch (id) {\n          case CLOCKID_MONOTONIC: {\n            resolutionValue = 5000n;\n            break;\n          }\n          case CLOCKID_REALTIME: {\n            resolutionValue = 1000000n;\n            break;\n          }\n          default:\n            return ERRNO_NOSYS;\n        }\n        const view = new DataView(self.inst.exports.memory.buffer);\n        view.setBigUint64(res_ptr, resolutionValue, true);\n        return ERRNO_SUCCESS;\n      }, clock_time_get(id, precision, time) {\n        const buffer = new DataView(self.inst.exports.memory.buffer);\n        if (id === CLOCKID_REALTIME) {\n          buffer.setBigUint64(time, BigInt((/* @__PURE__ */ new Date()).getTime()) * 1000000n, true);\n        } else if (id == CLOCKID_MONOTONIC) {\n          let monotonic_time;\n          try {\n            monotonic_time = BigInt(Math.round(performance.now() * 1e6));\n          } catch (e) {\n            monotonic_time = 0n;\n          }\n          buffer.setBigUint64(time, monotonic_time, true);\n        } else {\n          buffer.setBigUint64(time, 0n, true);\n        }\n        return 0;\n      }, fd_advise(fd, offset, len, advice) {\n        if (self.fds[fd] != void 0) {\n          return ERRNO_SUCCESS;\n        } else {\n          return ERRNO_BADF;\n        }\n      }, fd_allocate(fd, offset, len) {\n        if (self.fds[fd] != void 0) {\n          return self.fds[fd].fd_allocate(offset, len);\n        } else {\n          return ERRNO_BADF;\n        }\n      }, fd_close(fd) {\n        if (self.fds[fd] != void 0) {\n          const ret = self.fds[fd].fd_close();\n          self.fds[fd] = void 0;\n          return ret;\n        } else {\n          return ERRNO_BADF;\n        }\n      }, fd_datasync(fd) {\n        if (self.fds[fd] != void 0) {\n          return self.fds[fd].fd_sync();\n        } else {\n          return ERRNO_BADF;\n        }\n      }, fd_fdstat_get(fd, fdstat_ptr) {\n        if (self.fds[fd] != void 0) {\n          const { ret, fdstat } = self.fds[fd].fd_fdstat_get();\n          if (fdstat != null) {\n            fdstat.write_bytes(new DataView(self.inst.exports.memory.buffer), fdstat_ptr);\n          }\n          return ret;\n        } else {\n          return ERRNO_BADF;\n        }\n      }, fd_fdstat_set_flags(fd, flags) {\n        if (self.fds[fd] != void 0) {\n          return self.fds[fd].fd_fdstat_set_flags(flags);\n        } else {\n          return ERRNO_BADF;\n        }\n      }, fd_fdstat_set_rights(fd, fs_rights_base, fs_rights_inheriting) {\n        if (self.fds[fd] != void 0) {\n          return self.fds[fd].fd_fdstat_set_rights(fs_rights_base, fs_rights_inheriting);\n        } else {\n          return ERRNO_BADF;\n        }\n      }, fd_filestat_get(fd, filestat_ptr) {\n        if (self.fds[fd] != void 0) {\n          const { ret, filestat } = self.fds[fd].fd_filestat_get();\n          if (filestat != null) {\n            filestat.write_bytes(new DataView(self.inst.exports.memory.buffer), filestat_ptr);\n          }\n          return ret;\n        } else {\n          return ERRNO_BADF;\n        }\n      }, fd_filestat_set_size(fd, size) {\n        if (self.fds[fd] != void 0) {\n          return self.fds[fd].fd_filestat_set_size(size);\n        } else {\n          return ERRNO_BADF;\n        }\n      }, fd_filestat_set_times(fd, atim, mtim, fst_flags) {\n        if (self.fds[fd] != void 0) {\n          return self.fds[fd].fd_filestat_set_times(atim, mtim, fst_flags);\n        } else {\n          return ERRNO_BADF;\n        }\n      }, fd_pread(fd, iovs_ptr, iovs_len, offset, nread_ptr) {\n        const buffer = new DataView(self.inst.exports.memory.buffer);\n        const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n        if (self.fds[fd] != void 0) {\n          const iovecs = Iovec.read_bytes_array(buffer, iovs_ptr, iovs_len);\n          let nread = 0;\n          for (const iovec of iovecs) {\n            const { ret, data } = self.fds[fd].fd_pread(iovec.buf_len, offset);\n            if (ret != ERRNO_SUCCESS) {\n              buffer.setUint32(nread_ptr, nread, true);\n              return ret;\n            }\n            buffer8.set(data, iovec.buf);\n            nread += data.length;\n            offset += BigInt(data.length);\n            if (data.length != iovec.buf_len) {\n              break;\n            }\n          }\n          buffer.setUint32(nread_ptr, nread, true);\n          return ERRNO_SUCCESS;\n        } else {\n          return ERRNO_BADF;\n        }\n      }, fd_prestat_get(fd, buf_ptr) {\n        const buffer = new DataView(self.inst.exports.memory.buffer);\n        if (self.fds[fd] != void 0) {\n          const { ret, prestat } = self.fds[fd].fd_prestat_get();\n          if (prestat != null) {\n            prestat.write_bytes(buffer, buf_ptr);\n          }\n          return ret;\n        } else {\n          return ERRNO_BADF;\n        }\n      }, fd_prestat_dir_name(fd, path_ptr, path_len) {\n        if (self.fds[fd] != void 0) {\n          const { ret, prestat } = self.fds[fd].fd_prestat_get();\n          if (prestat == null) {\n            return ret;\n          }\n          const prestat_dir_name = prestat.inner.pr_name;\n          const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n          buffer8.set(prestat_dir_name.slice(0, path_len), path_ptr);\n          return prestat_dir_name.byteLength > path_len ? ERRNO_NAMETOOLONG : ERRNO_SUCCESS;\n        } else {\n          return ERRNO_BADF;\n        }\n      }, fd_pwrite(fd, iovs_ptr, iovs_len, offset, nwritten_ptr) {\n        const buffer = new DataView(self.inst.exports.memory.buffer);\n        const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n        if (self.fds[fd] != void 0) {\n          const iovecs = Ciovec.read_bytes_array(buffer, iovs_ptr, iovs_len);\n          let nwritten = 0;\n          for (const iovec of iovecs) {\n            const data = buffer8.slice(iovec.buf, iovec.buf + iovec.buf_len);\n            const { ret, nwritten: nwritten_part } = self.fds[fd].fd_pwrite(data, offset);\n            if (ret != ERRNO_SUCCESS) {\n              buffer.setUint32(nwritten_ptr, nwritten, true);\n              return ret;\n            }\n            nwritten += nwritten_part;\n            offset += BigInt(nwritten_part);\n            if (nwritten_part != data.byteLength) {\n              break;\n            }\n          }\n          buffer.setUint32(nwritten_ptr, nwritten, true);\n          return ERRNO_SUCCESS;\n        } else {\n          return ERRNO_BADF;\n        }\n      }, fd_read(fd, iovs_ptr, iovs_len, nread_ptr) {\n        const buffer = new DataView(self.inst.exports.memory.buffer);\n        const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n        if (self.fds[fd] != void 0) {\n          const iovecs = Iovec.read_bytes_array(buffer, iovs_ptr, iovs_len);\n          let nread = 0;\n          for (const iovec of iovecs) {\n            const { ret, data } = self.fds[fd].fd_read(iovec.buf_len);\n            if (ret != ERRNO_SUCCESS) {\n              buffer.setUint32(nread_ptr, nread, true);\n              return ret;\n            }\n            buffer8.set(data, iovec.buf);\n            nread += data.length;\n            if (data.length != iovec.buf_len) {\n              break;\n            }\n          }\n          buffer.setUint32(nread_ptr, nread, true);\n          return ERRNO_SUCCESS;\n        } else {\n          return ERRNO_BADF;\n        }\n      }, fd_readdir(fd, buf, buf_len, cookie, bufused_ptr) {\n        const buffer = new DataView(self.inst.exports.memory.buffer);\n        const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n        if (self.fds[fd] != void 0) {\n          let bufused = 0;\n          while (true) {\n            const { ret, dirent } = self.fds[fd].fd_readdir_single(cookie);\n            if (ret != 0) {\n              buffer.setUint32(bufused_ptr, bufused, true);\n              return ret;\n            }\n            if (dirent == null) {\n              break;\n            }\n            if (buf_len - bufused < dirent.head_length()) {\n              bufused = buf_len;\n              break;\n            }\n            const head_bytes = new ArrayBuffer(dirent.head_length());\n            dirent.write_head_bytes(new DataView(head_bytes), 0);\n            buffer8.set(new Uint8Array(head_bytes).slice(0, Math.min(head_bytes.byteLength, buf_len - bufused)), buf);\n            buf += dirent.head_length();\n            bufused += dirent.head_length();\n            if (buf_len - bufused < dirent.name_length()) {\n              bufused = buf_len;\n              break;\n            }\n            dirent.write_name_bytes(buffer8, buf, buf_len - bufused);\n            buf += dirent.name_length();\n            bufused += dirent.name_length();\n            cookie = dirent.d_next;\n          }\n          buffer.setUint32(bufused_ptr, bufused, true);\n          return 0;\n        } else {\n          return ERRNO_BADF;\n        }\n      }, fd_renumber(fd, to) {\n        if (self.fds[fd] != void 0 && self.fds[to] != void 0) {\n          const ret = self.fds[to].fd_close();\n          if (ret != 0) {\n            return ret;\n          }\n          self.fds[to] = self.fds[fd];\n          self.fds[fd] = void 0;\n          return 0;\n        } else {\n          return ERRNO_BADF;\n        }\n      }, fd_seek(fd, offset, whence, offset_out_ptr) {\n        const buffer = new DataView(self.inst.exports.memory.buffer);\n        if (self.fds[fd] != void 0) {\n          const { ret, offset: offset_out } = self.fds[fd].fd_seek(offset, whence);\n          buffer.setBigInt64(offset_out_ptr, offset_out, true);\n          return ret;\n        } else {\n          return ERRNO_BADF;\n        }\n      }, fd_sync(fd) {\n        if (self.fds[fd] != void 0) {\n          return self.fds[fd].fd_sync();\n        } else {\n          return ERRNO_BADF;\n        }\n      }, fd_tell(fd, offset_ptr) {\n        const buffer = new DataView(self.inst.exports.memory.buffer);\n        if (self.fds[fd] != void 0) {\n          const { ret, offset } = self.fds[fd].fd_tell();\n          buffer.setBigUint64(offset_ptr, offset, true);\n          return ret;\n        } else {\n          return ERRNO_BADF;\n        }\n      }, fd_write(fd, iovs_ptr, iovs_len, nwritten_ptr) {\n        const buffer = new DataView(self.inst.exports.memory.buffer);\n        const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n        if (self.fds[fd] != void 0) {\n          const iovecs = Ciovec.read_bytes_array(buffer, iovs_ptr, iovs_len);\n          let nwritten = 0;\n          for (const iovec of iovecs) {\n            const data = buffer8.slice(iovec.buf, iovec.buf + iovec.buf_len);\n            const { ret, nwritten: nwritten_part } = self.fds[fd].fd_write(data);\n            if (ret != ERRNO_SUCCESS) {\n              buffer.setUint32(nwritten_ptr, nwritten, true);\n              return ret;\n            }\n            nwritten += nwritten_part;\n            if (nwritten_part != data.byteLength) {\n              break;\n            }\n          }\n          buffer.setUint32(nwritten_ptr, nwritten, true);\n          return ERRNO_SUCCESS;\n        } else {\n          return ERRNO_BADF;\n        }\n      }, path_create_directory(fd, path_ptr, path_len) {\n        const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n        if (self.fds[fd] != void 0) {\n          const path = new TextDecoder("utf-8").decode(buffer8.slice(path_ptr, path_ptr + path_len));\n          return self.fds[fd].path_create_directory(path);\n        } else {\n          return ERRNO_BADF;\n        }\n      }, path_filestat_get(fd, flags, path_ptr, path_len, filestat_ptr) {\n        const buffer = new DataView(self.inst.exports.memory.buffer);\n        const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n        if (self.fds[fd] != void 0) {\n          const path = new TextDecoder("utf-8").decode(buffer8.slice(path_ptr, path_ptr + path_len));\n          const { ret, filestat } = self.fds[fd].path_filestat_get(flags, path);\n          if (filestat != null) {\n            filestat.write_bytes(buffer, filestat_ptr);\n          }\n          return ret;\n        } else {\n          return ERRNO_BADF;\n        }\n      }, path_filestat_set_times(fd, flags, path_ptr, path_len, atim, mtim, fst_flags) {\n        const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n        if (self.fds[fd] != void 0) {\n          const path = new TextDecoder("utf-8").decode(buffer8.slice(path_ptr, path_ptr + path_len));\n          return self.fds[fd].path_filestat_set_times(flags, path, atim, mtim, fst_flags);\n        } else {\n          return ERRNO_BADF;\n        }\n      }, path_link(old_fd, old_flags, old_path_ptr, old_path_len, new_fd, new_path_ptr, new_path_len) {\n        const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n        if (self.fds[old_fd] != void 0 && self.fds[new_fd] != void 0) {\n          const old_path = new TextDecoder("utf-8").decode(buffer8.slice(old_path_ptr, old_path_ptr + old_path_len));\n          const new_path = new TextDecoder("utf-8").decode(buffer8.slice(new_path_ptr, new_path_ptr + new_path_len));\n          const { ret, inode_obj } = self.fds[old_fd].path_lookup(old_path, old_flags);\n          if (inode_obj == null) {\n            return ret;\n          }\n          return self.fds[new_fd].path_link(new_path, inode_obj, false);\n        } else {\n          return ERRNO_BADF;\n        }\n      }, path_open(fd, dirflags, path_ptr, path_len, oflags, fs_rights_base, fs_rights_inheriting, fd_flags, opened_fd_ptr) {\n        const buffer = new DataView(self.inst.exports.memory.buffer);\n        const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n        if (self.fds[fd] != void 0) {\n          const path = new TextDecoder("utf-8").decode(buffer8.slice(path_ptr, path_ptr + path_len));\n          debug.log(path);\n          const { ret, fd_obj } = self.fds[fd].path_open(dirflags, path, oflags, fs_rights_base, fs_rights_inheriting, fd_flags);\n          if (ret != 0) {\n            return ret;\n          }\n          self.fds.push(fd_obj);\n          const opened_fd = self.fds.length - 1;\n          buffer.setUint32(opened_fd_ptr, opened_fd, true);\n          return 0;\n        } else {\n          return ERRNO_BADF;\n        }\n      }, path_readlink(fd, path_ptr, path_len, buf_ptr, buf_len, nread_ptr) {\n        const buffer = new DataView(self.inst.exports.memory.buffer);\n        const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n        if (self.fds[fd] != void 0) {\n          const path = new TextDecoder("utf-8").decode(buffer8.slice(path_ptr, path_ptr + path_len));\n          debug.log(path);\n          const { ret, data } = self.fds[fd].path_readlink(path);\n          if (data != null) {\n            const data_buf = new TextEncoder().encode(data);\n            if (data_buf.length > buf_len) {\n              buffer.setUint32(nread_ptr, 0, true);\n              return ERRNO_BADF;\n            }\n            buffer8.set(data_buf, buf_ptr);\n            buffer.setUint32(nread_ptr, data_buf.length, true);\n          }\n          return ret;\n        } else {\n          return ERRNO_BADF;\n        }\n      }, path_remove_directory(fd, path_ptr, path_len) {\n        const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n        if (self.fds[fd] != void 0) {\n          const path = new TextDecoder("utf-8").decode(buffer8.slice(path_ptr, path_ptr + path_len));\n          return self.fds[fd].path_remove_directory(path);\n        } else {\n          return ERRNO_BADF;\n        }\n      }, path_rename(fd, old_path_ptr, old_path_len, new_fd, new_path_ptr, new_path_len) {\n        const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n        if (self.fds[fd] != void 0 && self.fds[new_fd] != void 0) {\n          const old_path = new TextDecoder("utf-8").decode(buffer8.slice(old_path_ptr, old_path_ptr + old_path_len));\n          const new_path = new TextDecoder("utf-8").decode(buffer8.slice(new_path_ptr, new_path_ptr + new_path_len));\n          let { ret, inode_obj } = self.fds[fd].path_unlink(old_path);\n          if (inode_obj == null) {\n            return ret;\n          }\n          ret = self.fds[new_fd].path_link(new_path, inode_obj, true);\n          if (ret != ERRNO_SUCCESS) {\n            if (self.fds[fd].path_link(old_path, inode_obj, true) != ERRNO_SUCCESS) {\n              throw "path_link should always return success when relinking an inode back to the original place";\n            }\n          }\n          return ret;\n        } else {\n          return ERRNO_BADF;\n        }\n      }, path_symlink(old_path_ptr, old_path_len, fd, new_path_ptr, new_path_len) {\n        const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n        if (self.fds[fd] != void 0) {\n          new TextDecoder("utf-8").decode(buffer8.slice(old_path_ptr, old_path_ptr + old_path_len));\n          new TextDecoder("utf-8").decode(buffer8.slice(new_path_ptr, new_path_ptr + new_path_len));\n          return ERRNO_NOTSUP;\n        } else {\n          return ERRNO_BADF;\n        }\n      }, path_unlink_file(fd, path_ptr, path_len) {\n        const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n        if (self.fds[fd] != void 0) {\n          const path = new TextDecoder("utf-8").decode(buffer8.slice(path_ptr, path_ptr + path_len));\n          return self.fds[fd].path_unlink_file(path);\n        } else {\n          return ERRNO_BADF;\n        }\n      }, poll_oneoff(in_, out, nsubscriptions) {\n        throw "async io not supported";\n      }, proc_exit(exit_code) {\n        throw new WASIProcExit(exit_code);\n      }, proc_raise(sig) {\n        throw "raised signal " + sig;\n      }, sched_yield() {\n      }, random_get(buf, buf_len) {\n        const buffer8 = new Uint8Array(self.inst.exports.memory.buffer);\n        for (let i = 0; i < buf_len; i++) {\n          buffer8[buf + i] = Math.random() * 256 | 0;\n        }\n      }, sock_recv(fd, ri_data, ri_flags) {\n        throw "sockets not supported";\n      }, sock_send(fd, si_data, si_flags) {\n        throw "sockets not supported";\n      }, sock_shutdown(fd, how) {\n        throw "sockets not supported";\n      }, sock_accept(fd, flags) {\n        throw "sockets not supported";\n      } };\n    }\n  };\n  class Fd {\n    fd_allocate(offset, len) {\n      return ERRNO_NOTSUP;\n    }\n    fd_close() {\n      return 0;\n    }\n    fd_fdstat_get() {\n      return { ret: ERRNO_NOTSUP, fdstat: null };\n    }\n    fd_fdstat_set_flags(flags) {\n      return ERRNO_NOTSUP;\n    }\n    fd_fdstat_set_rights(fs_rights_base, fs_rights_inheriting) {\n      return ERRNO_NOTSUP;\n    }\n    fd_filestat_get() {\n      return { ret: ERRNO_NOTSUP, filestat: null };\n    }\n    fd_filestat_set_size(size) {\n      return ERRNO_NOTSUP;\n    }\n    fd_filestat_set_times(atim, mtim, fst_flags) {\n      return ERRNO_NOTSUP;\n    }\n    fd_pread(size, offset) {\n      return { ret: ERRNO_NOTSUP, data: new Uint8Array() };\n    }\n    fd_prestat_get() {\n      return { ret: ERRNO_NOTSUP, prestat: null };\n    }\n    fd_pwrite(data, offset) {\n      return { ret: ERRNO_NOTSUP, nwritten: 0 };\n    }\n    fd_read(size) {\n      return { ret: ERRNO_NOTSUP, data: new Uint8Array() };\n    }\n    fd_readdir_single(cookie) {\n      return { ret: ERRNO_NOTSUP, dirent: null };\n    }\n    fd_seek(offset, whence) {\n      return { ret: ERRNO_NOTSUP, offset: 0n };\n    }\n    fd_sync() {\n      return 0;\n    }\n    fd_tell() {\n      return { ret: ERRNO_NOTSUP, offset: 0n };\n    }\n    fd_write(data) {\n      return { ret: ERRNO_NOTSUP, nwritten: 0 };\n    }\n    path_create_directory(path) {\n      return ERRNO_NOTSUP;\n    }\n    path_filestat_get(flags, path) {\n      return { ret: ERRNO_NOTSUP, filestat: null };\n    }\n    path_filestat_set_times(flags, path, atim, mtim, fst_flags) {\n      return ERRNO_NOTSUP;\n    }\n    path_link(path, inode, allow_dir) {\n      return ERRNO_NOTSUP;\n    }\n    path_unlink(path) {\n      return { ret: ERRNO_NOTSUP, inode_obj: null };\n    }\n    path_lookup(path, dirflags) {\n      return { ret: ERRNO_NOTSUP, inode_obj: null };\n    }\n    path_open(dirflags, path, oflags, fs_rights_base, fs_rights_inheriting, fd_flags) {\n      return { ret: ERRNO_NOTDIR, fd_obj: null };\n    }\n    path_readlink(path) {\n      return { ret: ERRNO_NOTSUP, data: null };\n    }\n    path_remove_directory(path) {\n      return ERRNO_NOTSUP;\n    }\n    path_rename(old_path, new_fd, new_path) {\n      return ERRNO_NOTSUP;\n    }\n    path_unlink_file(path) {\n      return ERRNO_NOTSUP;\n    }\n  }\n  class Inode {\n  }\n  class OpenFile extends Fd {\n    fd_allocate(offset, len) {\n      if (this.file.size > offset + len) ;\n      else {\n        const new_data = new Uint8Array(Number(offset + len));\n        new_data.set(this.file.data, 0);\n        this.file.data = new_data;\n      }\n      return ERRNO_SUCCESS;\n    }\n    fd_fdstat_get() {\n      return { ret: 0, fdstat: new Fdstat(FILETYPE_REGULAR_FILE, 0) };\n    }\n    fd_filestat_set_size(size) {\n      if (this.file.size > size) {\n        this.file.data = new Uint8Array(this.file.data.buffer.slice(0, Number(size)));\n      } else {\n        const new_data = new Uint8Array(Number(size));\n        new_data.set(this.file.data, 0);\n        this.file.data = new_data;\n      }\n      return ERRNO_SUCCESS;\n    }\n    fd_read(size) {\n      const slice = this.file.data.slice(Number(this.file_pos), Number(this.file_pos + BigInt(size)));\n      this.file_pos += BigInt(slice.length);\n      return { ret: 0, data: slice };\n    }\n    fd_pread(size, offset) {\n      const slice = this.file.data.slice(Number(offset), Number(offset + BigInt(size)));\n      return { ret: 0, data: slice };\n    }\n    fd_seek(offset, whence) {\n      let calculated_offset;\n      switch (whence) {\n        case WHENCE_SET:\n          calculated_offset = offset;\n          break;\n        case WHENCE_CUR:\n          calculated_offset = this.file_pos + offset;\n          break;\n        case WHENCE_END:\n          calculated_offset = BigInt(this.file.data.byteLength) + offset;\n          break;\n        default:\n          return { ret: ERRNO_INVAL, offset: 0n };\n      }\n      if (calculated_offset < 0) {\n        return { ret: ERRNO_INVAL, offset: 0n };\n      }\n      this.file_pos = calculated_offset;\n      return { ret: 0, offset: this.file_pos };\n    }\n    fd_tell() {\n      return { ret: 0, offset: this.file_pos };\n    }\n    fd_write(data) {\n      if (this.file.readonly) return { ret: ERRNO_BADF, nwritten: 0 };\n      if (this.file_pos + BigInt(data.byteLength) > this.file.size) {\n        const old = this.file.data;\n        this.file.data = new Uint8Array(Number(this.file_pos + BigInt(data.byteLength)));\n        this.file.data.set(old);\n      }\n      this.file.data.set(data, Number(this.file_pos));\n      this.file_pos += BigInt(data.byteLength);\n      return { ret: 0, nwritten: data.byteLength };\n    }\n    fd_pwrite(data, offset) {\n      if (this.file.readonly) return { ret: ERRNO_BADF, nwritten: 0 };\n      if (offset + BigInt(data.byteLength) > this.file.size) {\n        const old = this.file.data;\n        this.file.data = new Uint8Array(Number(offset + BigInt(data.byteLength)));\n        this.file.data.set(old);\n      }\n      this.file.data.set(data, Number(offset));\n      return { ret: 0, nwritten: data.byteLength };\n    }\n    fd_filestat_get() {\n      return { ret: 0, filestat: this.file.stat() };\n    }\n    constructor(file) {\n      super();\n      this.file_pos = 0n;\n      this.file = file;\n    }\n  }\n  class OpenDirectory extends Fd {\n    fd_seek(offset, whence) {\n      return { ret: ERRNO_BADF, offset: 0n };\n    }\n    fd_tell() {\n      return { ret: ERRNO_BADF, offset: 0n };\n    }\n    fd_allocate(offset, len) {\n      return ERRNO_BADF;\n    }\n    fd_fdstat_get() {\n      return { ret: 0, fdstat: new Fdstat(FILETYPE_DIRECTORY, 0) };\n    }\n    fd_readdir_single(cookie) {\n      if (debug.enabled) {\n        debug.log("readdir_single", cookie);\n        debug.log(cookie, this.dir.contents.keys());\n      }\n      if (cookie == 0n) {\n        return { ret: ERRNO_SUCCESS, dirent: new Dirent(1n, ".", FILETYPE_DIRECTORY) };\n      } else if (cookie == 1n) {\n        return { ret: ERRNO_SUCCESS, dirent: new Dirent(2n, "..", FILETYPE_DIRECTORY) };\n      }\n      if (cookie >= BigInt(this.dir.contents.size) + 2n) {\n        return { ret: 0, dirent: null };\n      }\n      const [name, entry] = Array.from(this.dir.contents.entries())[Number(cookie - 2n)];\n      return { ret: 0, dirent: new Dirent(cookie + 1n, name, entry.stat().filetype) };\n    }\n    path_filestat_get(flags, path_str) {\n      const { ret: path_err, path } = Path.from(path_str);\n      if (path == null) {\n        return { ret: path_err, filestat: null };\n      }\n      const { ret, entry } = this.dir.get_entry_for_path(path);\n      if (entry == null) {\n        return { ret, filestat: null };\n      }\n      return { ret: 0, filestat: entry.stat() };\n    }\n    path_lookup(path_str, dirflags) {\n      const { ret: path_ret, path } = Path.from(path_str);\n      if (path == null) {\n        return { ret: path_ret, inode_obj: null };\n      }\n      const { ret, entry } = this.dir.get_entry_for_path(path);\n      if (entry == null) {\n        return { ret, inode_obj: null };\n      }\n      return { ret: ERRNO_SUCCESS, inode_obj: entry };\n    }\n    path_open(dirflags, path_str, oflags, fs_rights_base, fs_rights_inheriting, fd_flags) {\n      const { ret: path_ret, path } = Path.from(path_str);\n      if (path == null) {\n        return { ret: path_ret, fd_obj: null };\n      }\n      let { ret, entry } = this.dir.get_entry_for_path(path);\n      if (entry == null) {\n        if (ret != ERRNO_NOENT) {\n          return { ret, fd_obj: null };\n        }\n        if ((oflags & OFLAGS_CREAT) == OFLAGS_CREAT) {\n          const { ret: ret2, entry: new_entry } = this.dir.create_entry_for_path(path_str, (oflags & OFLAGS_DIRECTORY) == OFLAGS_DIRECTORY);\n          if (new_entry == null) {\n            return { ret: ret2, fd_obj: null };\n          }\n          entry = new_entry;\n        } else {\n          return { ret: ERRNO_NOENT, fd_obj: null };\n        }\n      } else if ((oflags & OFLAGS_EXCL) == OFLAGS_EXCL) {\n        return { ret: ERRNO_EXIST, fd_obj: null };\n      }\n      if ((oflags & OFLAGS_DIRECTORY) == OFLAGS_DIRECTORY && entry.stat().filetype !== FILETYPE_DIRECTORY) {\n        return { ret: ERRNO_NOTDIR, fd_obj: null };\n      }\n      return entry.path_open(oflags, fs_rights_base, fd_flags);\n    }\n    path_create_directory(path) {\n      return this.path_open(0, path, OFLAGS_CREAT | OFLAGS_DIRECTORY, 0n, 0n, 0).ret;\n    }\n    path_link(path_str, inode, allow_dir) {\n      const { ret: path_ret, path } = Path.from(path_str);\n      if (path == null) {\n        return path_ret;\n      }\n      if (path.is_dir) {\n        return ERRNO_NOENT;\n      }\n      const { ret: parent_ret, parent_entry, filename, entry } = this.dir.get_parent_dir_and_entry_for_path(path, true);\n      if (parent_entry == null || filename == null) {\n        return parent_ret;\n      }\n      if (entry != null) {\n        const source_is_dir = inode.stat().filetype == FILETYPE_DIRECTORY;\n        const target_is_dir = entry.stat().filetype == FILETYPE_DIRECTORY;\n        if (source_is_dir && target_is_dir) {\n          if (allow_dir && entry instanceof Directory) {\n            if (entry.contents.size == 0) ;\n            else {\n              return ERRNO_NOTEMPTY;\n            }\n          } else {\n            return ERRNO_EXIST;\n          }\n        } else if (source_is_dir && !target_is_dir) {\n          return ERRNO_NOTDIR;\n        } else if (!source_is_dir && target_is_dir) {\n          return ERRNO_ISDIR;\n        } else if (inode.stat().filetype == FILETYPE_REGULAR_FILE && entry.stat().filetype == FILETYPE_REGULAR_FILE) ;\n        else {\n          return ERRNO_EXIST;\n        }\n      }\n      if (!allow_dir && inode.stat().filetype == FILETYPE_DIRECTORY) {\n        return ERRNO_PERM;\n      }\n      parent_entry.contents.set(filename, inode);\n      return ERRNO_SUCCESS;\n    }\n    path_unlink(path_str) {\n      const { ret: path_ret, path } = Path.from(path_str);\n      if (path == null) {\n        return { ret: path_ret, inode_obj: null };\n      }\n      const { ret: parent_ret, parent_entry, filename, entry } = this.dir.get_parent_dir_and_entry_for_path(path, true);\n      if (parent_entry == null || filename == null) {\n        return { ret: parent_ret, inode_obj: null };\n      }\n      if (entry == null) {\n        return { ret: ERRNO_NOENT, inode_obj: null };\n      }\n      parent_entry.contents.delete(filename);\n      return { ret: ERRNO_SUCCESS, inode_obj: entry };\n    }\n    path_unlink_file(path_str) {\n      const { ret: path_ret, path } = Path.from(path_str);\n      if (path == null) {\n        return path_ret;\n      }\n      const { ret: parent_ret, parent_entry, filename, entry } = this.dir.get_parent_dir_and_entry_for_path(path, false);\n      if (parent_entry == null || filename == null || entry == null) {\n        return parent_ret;\n      }\n      if (entry.stat().filetype === FILETYPE_DIRECTORY) {\n        return ERRNO_ISDIR;\n      }\n      parent_entry.contents.delete(filename);\n      return ERRNO_SUCCESS;\n    }\n    path_remove_directory(path_str) {\n      const { ret: path_ret, path } = Path.from(path_str);\n      if (path == null) {\n        return path_ret;\n      }\n      const { ret: parent_ret, parent_entry, filename, entry } = this.dir.get_parent_dir_and_entry_for_path(path, false);\n      if (parent_entry == null || filename == null || entry == null) {\n        return parent_ret;\n      }\n      if (!(entry instanceof Directory) || entry.stat().filetype !== FILETYPE_DIRECTORY) {\n        return ERRNO_NOTDIR;\n      }\n      if (entry.contents.size !== 0) {\n        return ERRNO_NOTEMPTY;\n      }\n      if (!parent_entry.contents.delete(filename)) {\n        return ERRNO_NOENT;\n      }\n      return ERRNO_SUCCESS;\n    }\n    fd_filestat_get() {\n      return { ret: 0, filestat: this.dir.stat() };\n    }\n    fd_filestat_set_size(size) {\n      return ERRNO_BADF;\n    }\n    fd_read(size) {\n      return { ret: ERRNO_BADF, data: new Uint8Array() };\n    }\n    fd_pread(size, offset) {\n      return { ret: ERRNO_BADF, data: new Uint8Array() };\n    }\n    fd_write(data) {\n      return { ret: ERRNO_BADF, nwritten: 0 };\n    }\n    fd_pwrite(data, offset) {\n      return { ret: ERRNO_BADF, nwritten: 0 };\n    }\n    constructor(dir) {\n      super();\n      this.dir = dir;\n    }\n  }\n  class PreopenDirectory extends OpenDirectory {\n    fd_prestat_get() {\n      return { ret: 0, prestat: Prestat.dir(this.prestat_name) };\n    }\n    constructor(name, contents) {\n      super(new Directory(contents));\n      this.prestat_name = name;\n    }\n  }\n  class File extends Inode {\n    path_open(oflags, fs_rights_base, fd_flags) {\n      if (this.readonly && (fs_rights_base & BigInt(RIGHTS_FD_WRITE)) == BigInt(RIGHTS_FD_WRITE)) {\n        return { ret: ERRNO_PERM, fd_obj: null };\n      }\n      if ((oflags & OFLAGS_TRUNC) == OFLAGS_TRUNC) {\n        if (this.readonly) return { ret: ERRNO_PERM, fd_obj: null };\n        this.data = new Uint8Array([]);\n      }\n      const file = new OpenFile(this);\n      if (fd_flags & FDFLAGS_APPEND) file.fd_seek(0n, WHENCE_END);\n      return { ret: ERRNO_SUCCESS, fd_obj: file };\n    }\n    get size() {\n      return BigInt(this.data.byteLength);\n    }\n    stat() {\n      return new Filestat(FILETYPE_REGULAR_FILE, this.size);\n    }\n    constructor(data, options) {\n      super();\n      this.data = new Uint8Array(data);\n      this.readonly = !!options?.readonly;\n    }\n  }\n  let Path = class Path2 {\n    static from(path) {\n      const self = new Path2();\n      self.is_dir = path.endsWith("/");\n      if (path.startsWith("/")) {\n        return { ret: ERRNO_NOTCAPABLE, path: null };\n      }\n      if (path.includes("\\0")) {\n        return { ret: ERRNO_INVAL, path: null };\n      }\n      for (const component of path.split("/")) {\n        if (component === "" || component === ".") {\n          continue;\n        }\n        if (component === "..") {\n          if (self.parts.pop() == void 0) {\n            return { ret: ERRNO_NOTCAPABLE, path: null };\n          }\n          continue;\n        }\n        self.parts.push(component);\n      }\n      return { ret: ERRNO_SUCCESS, path: self };\n    }\n    to_path_string() {\n      let s = this.parts.join("/");\n      if (this.is_dir) {\n        s += "/";\n      }\n      return s;\n    }\n    constructor() {\n      this.parts = [];\n      this.is_dir = false;\n    }\n  };\n  class Directory extends Inode {\n    path_open(oflags, fs_rights_base, fd_flags) {\n      return { ret: ERRNO_SUCCESS, fd_obj: new OpenDirectory(this) };\n    }\n    stat() {\n      return new Filestat(FILETYPE_DIRECTORY, 0n);\n    }\n    get_entry_for_path(path) {\n      let entry = this;\n      for (const component of path.parts) {\n        if (!(entry instanceof Directory)) {\n          return { ret: ERRNO_NOTDIR, entry: null };\n        }\n        const child = entry.contents.get(component);\n        if (child !== void 0) {\n          entry = child;\n        } else {\n          debug.log(component);\n          return { ret: ERRNO_NOENT, entry: null };\n        }\n      }\n      if (path.is_dir) {\n        if (entry.stat().filetype != FILETYPE_DIRECTORY) {\n          return { ret: ERRNO_NOTDIR, entry: null };\n        }\n      }\n      return { ret: ERRNO_SUCCESS, entry };\n    }\n    get_parent_dir_and_entry_for_path(path, allow_undefined) {\n      const filename = path.parts.pop();\n      if (filename === void 0) {\n        return { ret: ERRNO_INVAL, parent_entry: null, filename: null, entry: null };\n      }\n      const { ret: entry_ret, entry: parent_entry } = this.get_entry_for_path(path);\n      if (parent_entry == null) {\n        return { ret: entry_ret, parent_entry: null, filename: null, entry: null };\n      }\n      if (!(parent_entry instanceof Directory)) {\n        return { ret: ERRNO_NOTDIR, parent_entry: null, filename: null, entry: null };\n      }\n      const entry = parent_entry.contents.get(filename);\n      if (entry === void 0) {\n        if (!allow_undefined) {\n          return { ret: ERRNO_NOENT, parent_entry: null, filename: null, entry: null };\n        } else {\n          return { ret: ERRNO_SUCCESS, parent_entry, filename, entry: null };\n        }\n      }\n      if (path.is_dir) {\n        if (entry.stat().filetype != FILETYPE_DIRECTORY) {\n          return { ret: ERRNO_NOTDIR, parent_entry: null, filename: null, entry: null };\n        }\n      }\n      return { ret: ERRNO_SUCCESS, parent_entry, filename, entry };\n    }\n    create_entry_for_path(path_str, is_dir) {\n      const { ret: path_ret, path } = Path.from(path_str);\n      if (path == null) {\n        return { ret: path_ret, entry: null };\n      }\n      let { ret: parent_ret, parent_entry, filename, entry } = this.get_parent_dir_and_entry_for_path(path, true);\n      if (parent_entry == null || filename == null) {\n        return { ret: parent_ret, entry: null };\n      }\n      if (entry != null) {\n        return { ret: ERRNO_EXIST, entry: null };\n      }\n      debug.log("create", path);\n      let new_child;\n      if (!is_dir) {\n        new_child = new File(new ArrayBuffer(0));\n      } else {\n        new_child = new Directory(/* @__PURE__ */ new Map());\n      }\n      parent_entry.contents.set(filename, new_child);\n      entry = new_child;\n      return { ret: ERRNO_SUCCESS, entry };\n    }\n    constructor(contents) {\n      super();\n      if (contents instanceof Array) {\n        this.contents = new Map(contents);\n      } else {\n        this.contents = contents;\n      }\n    }\n  }\n  class ConsoleStdout extends Fd {\n    fd_filestat_get() {\n      const filestat = new Filestat(FILETYPE_CHARACTER_DEVICE, BigInt(0));\n      return { ret: 0, filestat };\n    }\n    fd_fdstat_get() {\n      const fdstat = new Fdstat(FILETYPE_CHARACTER_DEVICE, 0);\n      fdstat.fs_rights_base = BigInt(RIGHTS_FD_WRITE);\n      return { ret: 0, fdstat };\n    }\n    fd_write(data) {\n      this.write(data);\n      return { ret: 0, nwritten: data.byteLength };\n    }\n    static lineBuffered(write) {\n      const dec = new TextDecoder("utf-8", { fatal: false });\n      let line_buf = "";\n      return new ConsoleStdout((buffer) => {\n        line_buf += dec.decode(buffer, { stream: true });\n        const lines = line_buf.split("\\n");\n        for (const [i, line] of lines.entries()) {\n          if (i < lines.length - 1) {\n            write(line);\n          } else {\n            line_buf = line;\n          }\n        }\n      });\n    }\n    constructor(write) {\n      super();\n      this.write = write;\n    }\n  }\n  let instance = null;\n  let fileSystem = /* @__PURE__ */ new Map();\n  function mem() {\n    return new DataView(instance.exports.memory.buffer);\n  }\n  function memU8() {\n    return new Uint8Array(instance.exports.memory.buffer);\n  }\n  function exp() {\n    return instance.exports;\n  }\n  function addFileSync(name, data, readonly) {\n    const f = new File(data, { readonly });\n    fileSystem.set(name, f);\n  }\n  const api = {\n    async init(wasmBytes) {\n      const args = ["pandoc.wasm", "+RTS", "-H64m", "-RTS"];\n      const env = [];\n      fileSystem = /* @__PURE__ */ new Map();\n      const fds = [\n        new OpenFile(new File(new Uint8Array(), { readonly: true })),\n        // fd 0 stdin\n        ConsoleStdout.lineBuffered((m) => console.log(`[pandoc] ${m}`)),\n        // fd 1\n        ConsoleStdout.lineBuffered((m) => console.warn(`[pandoc] ${m}`)),\n        // fd 2\n        new PreopenDirectory("/", fileSystem)\n        // fd 3\n      ];\n      const wasi = new WASI(args, env, fds, { debug: false });\n      const { instance: inst } = await WebAssembly.instantiate(wasmBytes, {\n        wasi_snapshot_preview1: wasi.wasiImport\n      });\n      instance = inst;\n      wasi.initialize(instance);\n      exp().__wasm_call_ctors();\n      const argc_ptr = exp().malloc(4);\n      mem().setUint32(argc_ptr, args.length, true);\n      const argv = exp().malloc(4 * (args.length + 1));\n      for (let i = 0; i < args.length; i++) {\n        const arg = exp().malloc(args[i].length + 1);\n        new TextEncoder().encodeInto(\n          args[i],\n          new Uint8Array(instance.exports.memory.buffer, arg, args[i].length)\n        );\n        mem().setUint8(arg + args[i].length, 0);\n        mem().setUint32(argv + 4 * i, arg, true);\n      }\n      mem().setUint32(argv + 4 * args.length, 0, true);\n      const argv_ptr = exp().malloc(4);\n      mem().setUint32(argv_ptr, argv, true);\n      exp().hs_init_with_rtsopts(argc_ptr, argv_ptr);\n    },\n    async convert(markdown, referenceDocx, lineBreaks) {\n      if (!instance) throw new Error("Pandoc not initialized");\n      let fromFormat = "markdown+lists_without_preceding_blankline";\n      if (lineBreaks === "hard") {\n        fromFormat += "+hard_line_breaks";\n      } else if (lineBreaks === "east_asian") {\n        fromFormat += "+east_asian_line_breaks";\n      }\n      const options = {\n        from: fromFormat,\n        to: "docx",\n        "output-file": "output.docx",\n        standalone: true\n      };\n      if (referenceDocx) {\n        options["reference-doc"] = "reference.docx";\n      }\n      const optsStr = JSON.stringify(options);\n      const optsBytes = new TextEncoder().encode(optsStr);\n      const optsPtr = exp().malloc(optsBytes.length);\n      memU8().set(optsBytes, optsPtr);\n      fileSystem.clear();\n      addFileSync("stdin", new TextEncoder().encode(markdown), true);\n      addFileSync("stdout", new Uint8Array(), false);\n      addFileSync("stderr", new Uint8Array(), false);\n      addFileSync("warnings", new Uint8Array(), false);\n      addFileSync("output.docx", new Uint8Array(), false);\n      if (referenceDocx) {\n        addFileSync("reference.docx", new Uint8Array(referenceDocx), true);\n      }\n      exp().convert(optsPtr, optsBytes.length);\n      const outFile = fileSystem.get("output.docx");\n      if (!outFile || !outFile.data || outFile.data.byteLength === 0) {\n        const stderrFile = fileSystem.get("stderr");\n        const errText = stderrFile ? new TextDecoder().decode(stderrFile.data) : "Unknown error";\n        throw new Error(`Pandoc conversion failed: ${errText}`);\n      }\n      return outFile.data.slice().buffer;\n    },\n    async getVersion() {\n      if (!instance) throw new Error("Pandoc not initialized");\n      const queryObj = { query: "version" };\n      const qStr = JSON.stringify(queryObj);\n      const qBytes = new TextEncoder().encode(qStr);\n      const qPtr = exp().malloc(qBytes.length);\n      memU8().set(qBytes, qPtr);\n      fileSystem.clear();\n      addFileSync("stdin", new Uint8Array(), true);\n      addFileSync("stdout", new Uint8Array(), false);\n      addFileSync("stderr", new Uint8Array(), false);\n      addFileSync("warnings", new Uint8Array(), false);\n      exp().query(qPtr, qBytes.length);\n      const outFile = fileSystem.get("stdout");\n      if (!outFile) return "unknown";\n      return new TextDecoder().decode(outFile.data).trim();\n    }\n  };\n  expose(api);\n})();\n';
  const blob = typeof self !== "undefined" && self.Blob && new Blob([jsContent], { type: "text/javascript;charset=utf-8" });
  function WorkerWrapper(options) {
    let objURL;
    try {
      objURL = blob && (self.URL || self.webkitURL).createObjectURL(blob);
      if (!objURL) throw "";
      const worker2 = new Worker(objURL, {
        name: options?.name
      });
      worker2.addEventListener("error", () => {
        (self.URL || self.webkitURL).revokeObjectURL(objURL);
      });
      return worker2;
    } catch (e) {
      return new Worker(
        "data:text/javascript;charset=utf-8," + encodeURIComponent(jsContent),
        {
          name: options?.name
        }
      );
    } finally {
      objURL && (self.URL || self.webkitURL).revokeObjectURL(objURL);
    }
  }
  const css = `/*  ═══════════════════════════════════════════════════════════════════════
 *  Give Me Doc — CSS Entry Point
 *
 *  Import order matters: tokens → primitives → composites → page‑level.
 *  Vite will bundle all @import into one CSS string.
 *  ═══════════════════════════════════════════════════════════════════════ */

/* ── M3E Design tokens & reset ──────────────────────────────────────── */

/*  ═══════════════════════════════════════════════════════════════════════
 *  Give Me Doc — M3E Design Tokens & Base Reset
 *
 *  Material 3 Expressive inspired CSS custom properties.
 *  All components reference these tokens; override them to re‑theme.
 *  ═══════════════════════════════════════════════════════════════════════ */

:root {
  /* ── Surface / Background ────────────────────────────────────────── */
  --gmd-surface:            #fffbff;
  --gmd-surface-container:  #f3edf7;
  --gmd-surface-container-low: #f7f2fa;
  --gmd-surface-container-high: #ece6f0;
  --gmd-surface-variant:    #e7e0ec;
  --gmd-on-surface:         #1d1b20;
  --gmd-on-surface-variant: #49454f;
  --gmd-outline:            #79747e;
  --gmd-outline-variant:    #cac4d0;

  /* ── Primary ─────────────────────────────────────────────────────── */
  --gmd-primary:            #6750a4;
  --gmd-on-primary:         #ffffff;
  --gmd-primary-container:  #eaddff;
  --gmd-on-primary-container: #21005d;

  /* ── Secondary ───────────────────────────────────────────────────── */
  --gmd-secondary:          #625b71;
  --gmd-on-secondary:       #ffffff;
  --gmd-secondary-container:#e8def8;
  --gmd-on-secondary-container:#1d192b;

  /* ── Tertiary ────────────────────────────────────────────────────── */
  --gmd-tertiary:           #7d5260;
  --gmd-on-tertiary:        #ffffff;
  --gmd-tertiary-container: #ffd8e4;

  /* ── Error ───────────────────────────────────────────────────────── */
  --gmd-error:              #b3261e;
  --gmd-on-error:           #ffffff;
  --gmd-error-container:    #f9dedc;
  --gmd-on-error-container: #410e0b;

  /* ── Success (custom) ────────────────────────────────────────────── */
  --gmd-success:              #1b6e2d;
  --gmd-on-success:           #ffffff;
  --gmd-success-container:    #a8f5a2;
  --gmd-on-success-container: #002109;

  /* ── Warning (custom) ────────────────────────────────────────────── */
  --gmd-warning:              #7c5800;
  --gmd-on-warning:           #ffffff;
  --gmd-warning-container:    #ffdea6;
  --gmd-on-warning-container: #271900;

  /* ── Typography ──────────────────────────────────────────────────── */
  --gmd-font-family:        'Segoe UI', Roboto, 'Noto Sans SC', 'Helvetica Neue', Arial, sans-serif;
  --gmd-font-size-xs:       11px;
  --gmd-font-size-sm:       12px;
  --gmd-font-size-md:       14px;
  --gmd-font-size-lg:       16px;
  --gmd-font-size-title:    18px;
  --gmd-font-weight-regular: 400;
  --gmd-font-weight-medium:  500;
  --gmd-font-weight-bold:    600;
  --gmd-line-height:        1.5;

  /* ── Shape ───────────────────────────────────────────────────────── */
  --gmd-radius-xs:          4px;
  --gmd-radius-sm:          8px;
  --gmd-radius-md:          12px;
  --gmd-radius-lg:          16px;
  --gmd-radius-xl:          28px;
  --gmd-radius-full:        9999px;

  /* ── Elevation (shadow) ──────────────────────────────────────────── */
  --gmd-elevation-1: 0 1px 3px 1px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.3);
  --gmd-elevation-2: 0 2px 6px 2px rgba(0,0,0,.15), 0 1px 2px rgba(0,0,0,.3);
  --gmd-elevation-3: 0 4px 8px 3px rgba(0,0,0,.15), 0 1px 3px rgba(0,0,0,.3);

  /* ── Transition ──────────────────────────────────────────────────── */
  --gmd-duration-short:  150ms;
  --gmd-duration-medium: 250ms;
  --gmd-duration-long:   400ms;
  --gmd-easing-standard: cubic-bezier(0.2, 0, 0, 1);
  --gmd-easing-decelerate: cubic-bezier(0, 0, 0, 1);
  --gmd-easing-accelerate: cubic-bezier(0.3, 0, 1, 1);

  /* ── Spacing ─────────────────────────────────────────────────────── */
  --gmd-space-xs:  4px;
  --gmd-space-sm:  8px;
  --gmd-space-md:  12px;
  --gmd-space-lg:  16px;
  --gmd-space-xl:  24px;
  --gmd-space-xxl: 32px;
}

/* ── Minimal reset scoped to .gmd-panel ─────────────────────────────── */

.gmd-panel *,
.gmd-panel *::before,
.gmd-panel *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

/* ── Dark mode token overrides ──────────────────────────────────────── */

@media (prefers-color-scheme: dark) {
  :root {
    /* ── Surface / Background ──────────────────────────────────────── */
    --gmd-surface:            #141218;
    --gmd-surface-container:  #211f26;
    --gmd-surface-container-low: #1d1b20;
    --gmd-surface-container-high: #2b2930;
    --gmd-surface-variant:    #49454f;
    --gmd-on-surface:         #e6e0e9;
    --gmd-on-surface-variant: #cac4d0;
    --gmd-outline:            #938f99;
    --gmd-outline-variant:    #49454f;

    /* ── Primary ───────────────────────────────────────────────────── */
    --gmd-primary:            #d0bcff;
    --gmd-on-primary:         #381e72;
    --gmd-primary-container:  #4f378b;
    --gmd-on-primary-container: #eaddff;

    /* ── Secondary ─────────────────────────────────────────────────── */
    --gmd-secondary:          #ccc2dc;
    --gmd-on-secondary:       #332d41;
    --gmd-secondary-container:#4a4458;
    --gmd-on-secondary-container:#e8def8;

    /* ── Tertiary ──────────────────────────────────────────────────── */
    --gmd-tertiary:           #efb8c8;
    --gmd-on-tertiary:        #492532;
    --gmd-tertiary-container: #633b48;

    /* ── Error ─────────────────────────────────────────────────────── */
    --gmd-error:              #f2b8b5;
    --gmd-on-error:           #601410;
    --gmd-error-container:    #8c1d18;
    --gmd-on-error-container: #f9dedc;

    /* ── Success (custom) ──────────────────────────────────────────── */
    --gmd-success:              #7ddb7f;
    --gmd-on-success:           #003910;
    --gmd-success-container:    #0a5218;
    --gmd-on-success-container: #a8f5a2;

    /* ── Warning (custom) ──────────────────────────────────────────── */
    --gmd-warning:              #f5c34a;
    --gmd-on-warning:           #3f2e00;
    --gmd-warning-container:    #5a4100;
    --gmd-on-warning-container: #ffdea6;

    /* ── Elevation (shadow — reduced in dark mode) ─────────────────── */
    --gmd-elevation-1: 0 1px 3px 1px rgba(0,0,0,.30), 0 1px 2px rgba(0,0,0,.50);
    --gmd-elevation-2: 0 2px 6px 2px rgba(0,0,0,.30), 0 1px 2px rgba(0,0,0,.50);
    --gmd-elevation-3: 0 4px 8px 3px rgba(0,0,0,.30), 0 1px 3px rgba(0,0,0,.50);
  }
}

.gmd-panel {
  font-family: var(--gmd-font-family);
  font-size: var(--gmd-font-size-md);
  line-height: var(--gmd-line-height);
  color: var(--gmd-on-surface);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
}

/* ── M3E Primitive components ───────────────────────────────────────── */

/*  ═══════════════════════════════════════════════════════════════════════
 *  Give Me Doc — M3E Button & Icon‑Button
 *
 *  .gmd-btn          — regular button (filled / tonal / outlined / text / elevated)
 *  .gmd-icon-btn     — icon‑only button (standard / filled / tonal / outlined)
 *  ═══════════════════════════════════════════════════════════════════════ */

/* ── Common button reset ────────────────────────────────────────────── */

.gmd-btn,
.gmd-icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--gmd-space-sm);
  border: none;
  cursor: pointer;
  font-family: var(--gmd-font-family);
  font-weight: var(--gmd-font-weight-medium);
  transition:
    background var(--gmd-duration-short) var(--gmd-easing-standard),
    box-shadow var(--gmd-duration-short) var(--gmd-easing-standard),
    opacity var(--gmd-duration-short);
  -webkit-tap-highlight-color: transparent;
}

.gmd-btn:disabled,
.gmd-icon-btn:disabled {
  opacity: 0.38;
  cursor: default;
  pointer-events: none;
}

/* ════════════════════════════════════════════════════════════════════════
   Regular Button  .gmd-btn
   ════════════════════════════════════════════════════════════════════════ */

.gmd-btn {
  height: 40px;
  padding: 0 var(--gmd-space-xl);
  border-radius: var(--gmd-radius-full);
  font-size: var(--gmd-font-size-md);
  line-height: 40px;
  white-space: nowrap;
  position: relative;
  overflow: hidden;
}

.gmd-btn__icon {
  display: inline-flex;
  flex-shrink: 0;
}

.gmd-btn__icon svg {
  width: 18px;
  height: 18px;
}

.gmd-btn__label {
  white-space: nowrap;
}

/* ── Filled ─────────────────────────────────────────────────────────── */

.gmd-btn--filled {
  background: var(--gmd-primary);
  color: var(--gmd-on-primary);
}

.gmd-btn--filled:hover {
  box-shadow: var(--gmd-elevation-1);
  filter: brightness(1.08);
}

.gmd-btn--filled:active {
  filter: brightness(0.92);
}

/* ── Tonal ──────────────────────────────────────────────────────────── */

.gmd-btn--tonal {
  background: var(--gmd-secondary-container);
  color: var(--gmd-on-secondary-container);
}

.gmd-btn--tonal:hover {
  box-shadow: var(--gmd-elevation-1);
  filter: brightness(0.96);
}

/* ── Outlined ───────────────────────────────────────────────────────── */

.gmd-btn--outlined {
  background: transparent;
  color: var(--gmd-primary);
  border: 1px solid var(--gmd-outline);
}

.gmd-btn--outlined:hover {
  background: color-mix(in srgb, var(--gmd-primary) 8%, transparent);
}

/* ── Text ───────────────────────────────────────────────────────────── */

.gmd-btn--text {
  background: transparent;
  color: var(--gmd-primary);
  padding: 0 var(--gmd-space-md);
}

.gmd-btn--text:hover {
  background: color-mix(in srgb, var(--gmd-primary) 8%, transparent);
}

/* ── Elevated ───────────────────────────────────────────────────────── */

.gmd-btn--elevated {
  background: var(--gmd-surface-container-low);
  color: var(--gmd-primary);
  box-shadow: var(--gmd-elevation-1);
}

.gmd-btn--elevated:hover {
  box-shadow: var(--gmd-elevation-2);
}

/* ════════════════════════════════════════════════════════════════════════
   Icon Button  .gmd-icon-btn
   ════════════════════════════════════════════════════════════════════════ */

.gmd-icon-btn {
  width: 40px;
  height: 40px;
  border-radius: var(--gmd-radius-full);
  padding: 0;
  font-size: 0;
  background: transparent;
  color: var(--gmd-on-surface-variant);
  flex-shrink: 0;
}

.gmd-icon-btn svg {
  width: 20px;
  height: 20px;
}

/* ── Standard ───────────────────────────────────────────────────────── */

.gmd-icon-btn--standard:hover {
  background: color-mix(in srgb, var(--gmd-on-surface-variant) 8%, transparent);
}

/* ── Filled ─────────────────────────────────────────────────────────── */

.gmd-icon-btn--filled {
  background: var(--gmd-primary);
  color: var(--gmd-on-primary);
}

.gmd-icon-btn--filled:hover {
  filter: brightness(1.08);
}

/* ── Tonal ──────────────────────────────────────────────────────────── */

.gmd-icon-btn--tonal {
  background: var(--gmd-secondary-container);
  color: var(--gmd-on-secondary-container);
}

.gmd-icon-btn--tonal:hover {
  filter: brightness(0.96);
}

/* ── Outlined ───────────────────────────────────────────────────────── */

.gmd-icon-btn--outlined {
  border: 1px solid var(--gmd-outline);
  color: var(--gmd-on-surface-variant);
}

.gmd-icon-btn--outlined:hover {
  background: color-mix(in srgb, var(--gmd-on-surface-variant) 8%, transparent);
}

/* ── Spin animation for icon buttons ────────────────────────────────── */

@keyframes gmd-icon-spin {
  from { transform: rotate(0deg); }
  to   { transform: rotate(360deg); }
}

.gmd-icon-btn--spinning svg {
  animation: gmd-icon-spin 0.6s linear infinite;
}

/*  ═══════════════════════════════════════════════════════════════════════
 *  Give Me Doc — M3E Form Controls
 *
 *  .gmd-checkbox   — checkbox with animated check mark
 *  .gmd-select     — native <select> with M3E outline style
 *  .gmd-textarea   — labelled textarea
 *  .gmd-switch     — on/off toggle
 *  ═══════════════════════════════════════════════════════════════════════ */

/* ════════════════════════════════════════════════════════════════════════
   Checkbox  .gmd-checkbox
   ════════════════════════════════════════════════════════════════════════ */

.gmd-checkbox {
  display: inline-flex;
  align-items: center;
  gap: var(--gmd-space-sm);
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
}

.gmd-checkbox__input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.gmd-checkbox__box {
  width: 20px;
  height: 20px;
  border: 2px solid var(--gmd-outline);
  border-radius: var(--gmd-radius-xs);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition:
    background var(--gmd-duration-short) var(--gmd-easing-standard),
    border-color var(--gmd-duration-short) var(--gmd-easing-standard);
}

.gmd-checkbox__check {
  width: 14px;
  height: 14px;
  color: var(--gmd-on-primary);
  opacity: 0;
  transform: scale(0.5);
  transition:
    opacity var(--gmd-duration-short) var(--gmd-easing-standard),
    transform var(--gmd-duration-short) var(--gmd-easing-standard);
}

/* Checked state */

.gmd-checkbox__input:checked ~ .gmd-checkbox__box {
  background: var(--gmd-primary);
  border-color: var(--gmd-primary);
}

.gmd-checkbox__input:checked ~ .gmd-checkbox__box .gmd-checkbox__check {
  opacity: 1;
  transform: scale(1);
}

.gmd-checkbox__label {
  font-size: var(--gmd-font-size-md);
  color: var(--gmd-on-surface);
}

/* Hover ring */

.gmd-checkbox:hover .gmd-checkbox__box {
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--gmd-primary) 12%, transparent);
}

/* ════════════════════════════════════════════════════════════════════════
   Select  .gmd-select
   ════════════════════════════════════════════════════════════════════════ */

.gmd-select {
  appearance: none;
  -webkit-appearance: none;
  height: 40px;
  padding: 0 36px 0 var(--gmd-space-lg);
  border: 1px solid var(--gmd-outline);
  border-radius: var(--gmd-radius-xs);
  background: var(--gmd-surface);
  color: var(--gmd-on-surface);
  font-family: var(--gmd-font-family);
  font-size: var(--gmd-font-size-md);
  cursor: pointer;
  transition: border-color var(--gmd-duration-short) var(--gmd-easing-standard);

  /* Dropdown arrow */
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%2349454f' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 10px center;
}

.gmd-select:focus {
  outline: none;
  border-color: var(--gmd-primary);
  box-shadow: 0 0 0 1px var(--gmd-primary);
}

/* ════════════════════════════════════════════════════════════════════════
   Textarea  .gmd-textarea
   ════════════════════════════════════════════════════════════════════════ */

.gmd-textarea {
  display: flex;
  flex-direction: column;
  gap: var(--gmd-space-xs);
}

.gmd-textarea__label {
  font-size: var(--gmd-font-size-sm);
  font-weight: var(--gmd-font-weight-medium);
  color: var(--gmd-on-surface-variant);
}

.gmd-textarea__input {
  width: 100%;
  padding: var(--gmd-space-md);
  border: 1px solid var(--gmd-outline);
  border-radius: var(--gmd-radius-sm);
  background: var(--gmd-surface);
  color: var(--gmd-on-surface);
  font-family: 'Cascadia Code', 'Fira Code', 'Consolas', monospace;
  font-size: var(--gmd-font-size-sm);
  line-height: 1.6;
  resize: vertical;
  transition: border-color var(--gmd-duration-short) var(--gmd-easing-standard);
}

.gmd-textarea__input:focus {
  outline: none;
  border-color: var(--gmd-primary);
  box-shadow: 0 0 0 1px var(--gmd-primary);
}

.gmd-textarea__input::placeholder {
  color: var(--gmd-outline);
}

.gmd-textarea__input--nowrap {
  white-space: pre;
  overflow-x: auto;
  resize: horizontal;
}

/* ════════════════════════════════════════════════════════════════════════
   Switch  .gmd-switch
   ════════════════════════════════════════════════════════════════════════ */

.gmd-switch {
  display: inline-flex;
  align-items: center;
  gap: var(--gmd-space-md);
  cursor: pointer;
  -webkit-user-select: none;
  user-select: none;
}

.gmd-switch__input {
  position: absolute;
  opacity: 0;
  width: 0;
  height: 0;
}

.gmd-switch__track {
  position: relative;
  width: 52px;
  height: 32px;
  border-radius: var(--gmd-radius-full);
  background: var(--gmd-surface-variant);
  border: 2px solid var(--gmd-outline);
  transition:
    background var(--gmd-duration-medium) var(--gmd-easing-standard),
    border-color var(--gmd-duration-medium) var(--gmd-easing-standard);
}

.gmd-switch__thumb {
  position: absolute;
  top: 50%;
  left: 6px;
  width: 16px;
  height: 16px;
  border-radius: var(--gmd-radius-full);
  background: var(--gmd-outline);
  transform: translateY(-50%);
  transition:
    left var(--gmd-duration-medium) var(--gmd-easing-standard),
    width var(--gmd-duration-medium) var(--gmd-easing-standard),
    height var(--gmd-duration-medium) var(--gmd-easing-standard),
    background var(--gmd-duration-medium) var(--gmd-easing-standard);
  display: flex;
  align-items: center;
  justify-content: center;
}

.gmd-switch__icon {
  display: flex;
  color: var(--gmd-surface);
  opacity: 0;
  transform: scale(0);
  transition:
    opacity var(--gmd-duration-short) var(--gmd-easing-standard),
    transform var(--gmd-duration-short) var(--gmd-easing-standard);
}

/* Checked state */

.gmd-switch__track--checked {
  background: var(--gmd-primary);
  border-color: var(--gmd-primary);
}

.gmd-switch__track--checked .gmd-switch__thumb {
  left: calc(100% - 3px - 24px);
  width: 24px;
  height: 24px;
  background: var(--gmd-on-primary);
}

.gmd-switch__track--checked .gmd-switch__icon {
  opacity: 1;
  transform: scale(1);
  color: var(--gmd-primary);
}

.gmd-switch__label {
  font-size: var(--gmd-font-size-md);
  color: var(--gmd-on-surface);
}

/* Hover ring */

.gmd-switch:hover .gmd-switch__thumb {
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--gmd-on-surface) 8%, transparent);
}

.gmd-switch:hover .gmd-switch__track--checked .gmd-switch__thumb {
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--gmd-primary) 12%, transparent);
}

/* ════════════════════════════════════════════════════════════════════════
   Segmented Control  .gmd-segmented
   ════════════════════════════════════════════════════════════════════════ */

.gmd-segmented {
  display: inline-flex;
  border: 1px solid var(--gmd-outline);
  border-radius: var(--gmd-radius-full);
  overflow: hidden;
  background: var(--gmd-surface);
}

.gmd-segmented__btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--gmd-space-md);
  padding: var(--gmd-space-xs) var(--gmd-space-lg);
  height: 36px;
  border: none;
  background: transparent;
  color: var(--gmd-on-surface);
  font-family: var(--gmd-font-family);
  font-size: var(--gmd-font-size-sm);
  font-weight: var(--gmd-font-weight-medium);
  cursor: pointer;
  transition:
    background var(--gmd-duration-short) var(--gmd-easing-standard),
    color var(--gmd-duration-short) var(--gmd-easing-standard);
  position: relative;
}

/* Vertical divider between segments */

.gmd-segmented__btn + .gmd-segmented__btn {
  border-left: 1px solid var(--gmd-outline);
}

.gmd-segmented__btn:hover:not(.gmd-segmented__btn--active) {
  background: color-mix(in srgb, var(--gmd-on-surface) 8%, transparent);
}

.gmd-segmented__btn--active {
  background: var(--gmd-secondary-container);
  color: var(--gmd-on-secondary-container);
}

.gmd-segmented__icon {
  display: inline-flex;
  align-items: center;
}

.gmd-segmented__icon svg {
  width: 16px;
  height: 16px;
}

.gmd-segmented__label {
  white-space: nowrap;
}

/* ════════════════════════════════════════════════════════════════════════
   Input  .gmd-input
   ════════════════════════════════════════════════════════════════════════ */

.gmd-input {
  display: flex;
  flex-direction: column;
  gap: var(--gmd-space-xs);
}

.gmd-input__label {
  font-size: var(--gmd-font-size-sm);
  font-weight: var(--gmd-font-weight-medium);
  color: var(--gmd-on-surface-variant);
}

.gmd-input__field {
  height: 40px;
  padding: 0 var(--gmd-space-lg);
  border: 1px solid var(--gmd-outline);
  border-radius: var(--gmd-radius-sm);
  background: var(--gmd-surface);
  color: var(--gmd-on-surface);
  font-family: var(--gmd-font-family);
  font-size: var(--gmd-font-size-md);
  transition: border-color var(--gmd-duration-short) var(--gmd-easing-standard);
}

.gmd-input__field:focus {
  outline: none;
  border-color: var(--gmd-primary);
  box-shadow: 0 0 0 1px var(--gmd-primary);
}

.gmd-input__field::placeholder {
  color: var(--gmd-outline);
}

/*  ═══════════════════════════════════════════════════════════════════════
 *  Give Me Doc — M3E Tabs
 *
 *  .gmd-tabs           — root container
 *  .gmd-tabs__bar      — tab button strip
 *  .gmd-tabs__tab      — individual tab button
 *  .gmd-tabs__indicator— animated underline
 *  .gmd-tabs__panels   — panel container
 *  .gmd-tabs__panel    — individual tab content
 *  ═══════════════════════════════════════════════════════════════════════ */

.gmd-tabs {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

/* ── Tab Bar ────────────────────────────────────────────────────────── */

.gmd-tabs__bar {
  display: flex;
  position: relative;
  border-bottom: 1px solid var(--gmd-outline-variant);
  background: var(--gmd-surface);
  flex-shrink: 0;
}

.gmd-tabs__tab {
  flex: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--gmd-space-sm);
  padding: var(--gmd-space-md) var(--gmd-space-lg);
  height: 48px;
  border: none;
  background: transparent;
  font-family: var(--gmd-font-family);
  font-size: var(--gmd-font-size-md);
  font-weight: var(--gmd-font-weight-medium);
  color: var(--gmd-on-surface-variant);
  cursor: pointer;
  transition:
    color var(--gmd-duration-short) var(--gmd-easing-standard),
    background var(--gmd-duration-short) var(--gmd-easing-standard);
  position: relative;
  white-space: nowrap;
}

.gmd-tabs__tab:hover {
  background: color-mix(in srgb, var(--gmd-primary) 8%, transparent);
}

.gmd-tabs__tab--active {
  color: var(--gmd-primary);
}

.gmd-tabs__tab-icon {
  display: inline-flex;
}

.gmd-tabs__tab-icon svg {
  width: 18px;
  height: 18px;
}

.gmd-tabs__tab-label {
  white-space: nowrap;
}

/* ── Indicator (sliding underline) ──────────────────────────────────── */

.gmd-tabs__indicator {
  position: absolute;
  bottom: 0;
  left: 0;
  height: 3px;
  background: var(--gmd-primary);
  border-radius: 3px 3px 0 0;
  transition:
    left var(--gmd-duration-medium) var(--gmd-easing-standard),
    width var(--gmd-duration-medium) var(--gmd-easing-standard);
}

/* ── Panels ─────────────────────────────────────────────────────────── */

.gmd-tabs__panels {
  flex: 1;
  min-height: 0;
  overflow: hidden;
  position: relative;
}

.gmd-tabs__panel {
  display: none;
  height: 100%;
  overflow-y: auto;
  scrollbar-width: thin;
  scrollbar-color: var(--gmd-outline-variant) transparent;
}

.gmd-tabs__panel--active {
  display: block;
}

/* Webkit scrollbar */

.gmd-tabs__panel::-webkit-scrollbar {
  width: 6px;
}

.gmd-tabs__panel::-webkit-scrollbar-track {
  background: transparent;
}

.gmd-tabs__panel::-webkit-scrollbar-thumb {
  background: var(--gmd-outline-variant);
  border-radius: 3px;
}

/*  ═══════════════════════════════════════════════════════════════════════
 *  Give Me Doc — M3E Toast / Snack‑bar
 *
 *  .gmd-toast-container — fixed stack at bottom‑center
 *  .gmd-toast           — individual notification chip
 *  ═══════════════════════════════════════════════════════════════════════ */

/* ── Container ──────────────────────────────────────────────────────── */

.gmd-toast-container {
  position: fixed;
  bottom: var(--gmd-space-xl);
  left: 50%;
  transform: translateX(-50%);
  z-index: 100001;
  display: flex;
  flex-direction: column-reverse;
  gap: var(--gmd-space-sm);
  pointer-events: none;
}

/* ── Toast ──────────────────────────────────────────────────────────── */

.gmd-toast {
  display: flex;
  align-items: center;
  gap: var(--gmd-space-md);
  min-width: 280px;
  max-width: 480px;
  padding: var(--gmd-space-md) var(--gmd-space-lg);
  border-radius: var(--gmd-radius-sm);
  box-shadow: var(--gmd-elevation-3);
  pointer-events: auto;
  opacity: 0;
  transform: translateY(16px) scale(0.95);
  transition:
    opacity var(--gmd-duration-medium) var(--gmd-easing-decelerate),
    transform var(--gmd-duration-medium) var(--gmd-easing-decelerate);
}

.gmd-toast--visible {
  opacity: 1;
  transform: translateY(0) scale(1);
}

/* ── Level variants ─────────────────────────────────────────────────── */

.gmd-toast--info {
  background: var(--gmd-on-surface);
  color: var(--gmd-surface);
}

.gmd-toast--success {
  background: var(--gmd-success-container);
  color: var(--gmd-on-success-container);
}

.gmd-toast--warning {
  background: var(--gmd-warning-container);
  color: var(--gmd-on-warning-container);
}

.gmd-toast--error {
  background: var(--gmd-error-container);
  color: var(--gmd-on-error-container);
}

/* ── Inner elements ─────────────────────────────────────────────────── */

.gmd-toast__icon {
  display: inline-flex;
  flex-shrink: 0;
}

.gmd-toast__icon svg {
  width: 20px;
  height: 20px;
}

.gmd-toast__message {
  flex: 1;
  font-size: var(--gmd-font-size-md);
  line-height: var(--gmd-line-height);
}

.gmd-toast__action {
  border: none;
  background: transparent;
  color: inherit;
  font-family: var(--gmd-font-family);
  font-size: var(--gmd-font-size-md);
  font-weight: var(--gmd-font-weight-bold);
  cursor: pointer;
  padding: var(--gmd-space-xs) var(--gmd-space-sm);
  border-radius: var(--gmd-radius-xs);
  white-space: nowrap;
  opacity: 0.9;
}

.gmd-toast__action:hover {
  opacity: 1;
  background: rgba(255, 255, 255, 0.12);
}

.gmd-toast__close {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: transparent;
  color: inherit;
  cursor: pointer;
  padding: var(--gmd-space-xs);
  border-radius: var(--gmd-radius-full);
  opacity: 0.7;
  flex-shrink: 0;
}

.gmd-toast__close:hover {
  opacity: 1;
  background: rgba(255, 255, 255, 0.12);
}

/* ── FAB ────────────────────────────────────────────────────────────── */

/*  ═══════════════════════════════════════════════════════════════════════
 *  Give Me Doc — Floating Action Button (FAB)
 *
 *  .gmd-fab — fixed right-edge FAB for opening the panel.
 *  Half-hidden by default, reveals on hover/focus. Supports vertical drag.
 *  ═══════════════════════════════════════════════════════════════════════ */

.gmd-fab {
  position: fixed;
  right: -24px;
  top: 50%;
  transform: translateY(-50%);
  z-index: 99999;

  width: 48px;
  height: 48px;
  border: none;
  border-radius: var(--gmd-radius-xl);
  background: var(--gmd-primary);
  color: var(--gmd-on-primary);
  cursor: pointer;
  opacity: 0.4;

  display: flex;
  align-items: center;
  justify-content: center;
  box-shadow: var(--gmd-elevation-2);

  font-family: var(--gmd-font-family);
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;

  transition:
    right   var(--gmd-duration-medium) var(--gmd-easing-standard),
    opacity var(--gmd-duration-medium) var(--gmd-easing-standard),
    box-shadow var(--gmd-duration-short) var(--gmd-easing-standard);

  user-select: none;
  -webkit-user-select: none;
  touch-action: none;
}

.gmd-fab:hover,
.gmd-fab:focus-visible {
  right: 12px;
  opacity: 1;
  box-shadow: var(--gmd-elevation-3);
}

.gmd-fab:active {
  box-shadow: var(--gmd-elevation-1);
}

.gmd-fab svg {
  width: 22px;
  height: 22px;
  pointer-events: none;
}

/* Invisible proximity hit area — extends 32px to the left of the FAB
   so the hover triggers before the cursor reaches the visible button. */

.gmd-fab::before {
  content: '';
  position: absolute;
  top: -10px;
  right: -50px;
  bottom: -10px;
  left: -10px;
  border-radius: var(--gmd-radius-xl);  /* match FAB shape */
  transition: 
    background-color var(--gmd-duration-medium) var(--gmd-easing-standard),
    opacity var(--gmd-duration-medium) var(--gmd-easing-standard);
}

/* Dragging state — disable transitions to follow cursor smoothly */

.gmd-fab--dragging {
  transition: none;
  right: 12px;
  opacity: 1;
}

.gmd-fab--dragging::before {
  background-color: var(--gmd-primary);
  opacity: 0.3;
}

/* Slide-out animation when panel opens */

.gmd-fab--hidden {
  right: -60px;
  opacity: 0;
  pointer-events: none;
}

/* ── Panel shell ────────────────────────────────────────────────────── */

/*  ═══════════════════════════════════════════════════════════════════════
 *  Give Me Doc — Panel Shell
 *
 *  .gmd-panel          — floating side panel container
 *  .gmd-panel__header  — drag handle + title + close btn
 *  ═══════════════════════════════════════════════════════════════════════ */

.gmd-panel {
  position: fixed;
  top: var(--gmd-space-xl);
  right: var(--gmd-space-xl);
  width: 420px;
  max-width: calc(100vw - var(--gmd-space-xl) * 2);
  height: calc(100vh - 48px);
  max-height: 780px;
  z-index: 100000;
  display: flex;
  flex-direction: column;
  background: var(--gmd-surface);
  border-radius: var(--gmd-radius-lg);
  box-shadow: var(--gmd-elevation-3);
  overflow: hidden;
  transition:
    opacity var(--gmd-duration-medium) var(--gmd-easing-standard),
    transform var(--gmd-duration-medium) var(--gmd-easing-standard);
}

.gmd-panel--hidden {
  opacity: 0;
  pointer-events: none;
  transform: translateX(24px);
}

/* ── Embedded mode (popup) ──────────────────────────────────────────── */

.gmd-panel--embedded {
  position: static;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  border-radius: 0;
  box-shadow: none;
  z-index: auto;
}

/* ── Header ─────────────────────────────────────────────────────────── */

.gmd-panel__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--gmd-space-md) var(--gmd-space-lg);
  background: var(--gmd-surface-container-low);
  border-bottom: 1px solid var(--gmd-outline-variant);
  flex-shrink: 0;
  user-select: none;
}

.gmd-panel__title-wrap {
  display: flex;
  align-items: center;
  gap: var(--gmd-space-sm);
}

.gmd-panel__logo {
  display: inline-flex;
  color: var(--gmd-primary);
}

.gmd-panel__logo svg {
  width: 22px;
  height: 22px;
}

.gmd-panel__title {
  font-size: var(--gmd-font-size-title);
  font-weight: var(--gmd-font-weight-bold);
  color: var(--gmd-on-surface);
}

/* ── Tab pages ──────────────────────────────────────────────────────── */

/*  ═══════════════════════════════════════════════════════════════════════
 *  Give Me Doc — Export Tab
 *
 *  .gmd-export              — tab root
 *  .gmd-export__mode-switch — segmented control bar (session / freetext)
 *  .gmd-export__session     — session mode container (toolbar + list)
 *  .gmd-export__toolbar     — top bar (select‑all + count + refresh)
 *  .gmd-export__list        — scrollable message list
 *  .gmd-export__row         — single message row
 *  .gmd-export__freetext    — freetext mode container (filename + editor)
 *  .gmd-export__bottom      — action bar (template select + export btn)
 *  ═══════════════════════════════════════════════════════════════════════ */

.gmd-export {
  display: flex;
  flex-direction: column;
  height: 100%;
}

/* ── Mode switch ────────────────────────────────────────────────────── */

.gmd-export__mode-switch {
  display: flex;
  justify-content: center;
  padding: var(--gmd-space-lg) var(--gmd-space-lg) 0;
  flex-shrink: 0;
}

.gmd-export__mode-switch .gmd-segmented {
  width: 100%;
}

.gmd-export__mode-switch .gmd-segmented__btn {
  flex: 1;
}

/* ── Session mode container ─────────────────────────────────────────── */

.gmd-export__session {
  display: flex;
  flex-direction: column;
  flex: 1;
  min-height: 0;
}

/* ── Toolbar ────────────────────────────────────────────────────────── */

.gmd-export__toolbar {
  display: flex;
  align-items: center;
  gap: var(--gmd-space-md);
  padding: var(--gmd-space-lg) var(--gmd-space-lg) 0 var(--gmd-space-lg);
  flex-shrink: 0;
}

.gmd-export__count {
  margin-left: auto;
  font-size: var(--gmd-font-size-sm);
  color: var(--gmd-on-surface-variant);
}

/* ── Message list ───────────────────────────────────────────────────── */

.gmd-export__list {
  flex: 1;
  overflow-y: auto;
  padding: 0 0 var(--gmd-space-sm) 0;
  scrollbar-width: thin;
  scrollbar-color: var(--gmd-outline-variant) transparent;
}

.gmd-export__row {
  display: flex;
  align-items: center;
  gap: var(--gmd-space-sm);
  padding: var(--gmd-space-sm) var(--gmd-space-lg);
  transition: background var(--gmd-duration-short) var(--gmd-easing-standard);
}

.gmd-export__row:hover {
  background: var(--gmd-surface-container-low);
}

/* ── Role icon ──────────────────────────────────────────────────────── */

.gmd-export__role-icon {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--gmd-on-surface-variant);
}

.gmd-export__row--user .gmd-export__role-icon {
  color: var(--gmd-primary);
}

.gmd-export__row--assistant .gmd-export__role-icon {
  color: var(--gmd-tertiary);
}

.gmd-export__role-icon svg {
  width: 18px;
  height: 18px;
}

/* ── Summary text ───────────────────────────────────────────────────── */

.gmd-export__summary {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font-size: var(--gmd-font-size-sm);
  color: var(--gmd-on-surface);
}

/* ── Branch switcher ────────────────────────────────────────────────── */

.gmd-export__branch {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  flex-shrink: 0;
}

.gmd-export__branch .gmd-icon-btn {
  width: 28px;
  height: 28px;
}

.gmd-export__branch .gmd-icon-btn svg {
  width: 16px;
  height: 16px;
}

.gmd-export__branch-label {
  font-size: var(--gmd-font-size-xs);
  color: var(--gmd-on-surface-variant);
  min-width: 28px;
  text-align: center;
}

/* ── Bottom action bar ──────────────────────────────────────────────── */

.gmd-export__bottom {
  display: flex;
  align-items: center;
  gap: var(--gmd-space-md);
  padding: var(--gmd-space-md) var(--gmd-space-lg);
  border-top: 1px solid var(--gmd-outline-variant);
  background: var(--gmd-surface-container-low);
  flex-shrink: 0;
}

.gmd-export__bottom .gmd-select {
  flex: 1;
  min-width: 0;
}

.gmd-export__bottom .gmd-btn {
  flex-shrink: 0;
}

/* ── Freetext mode ──────────────────────────────────────────────────── */

.gmd-export__freetext {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: var(--gmd-space-md);
  padding: var(--gmd-space-lg) var(--gmd-space-lg);
  min-height: 0;
  overflow: hidden;
}

.gmd-export__freetext .gmd-input {
  flex-shrink: 0;
}

/* Make the editor textarea fill remaining vertical space */

.gmd-export__freetext-editor {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.gmd-export__freetext-editor .gmd-textarea__input {
  flex: 1;
  resize: none;
}

/* Empty session placeholder */

.gmd-export__empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100%;
  color: var(--gmd-on-surface-variant);
  padding: var(--gmd-space-lg);
  text-align: center;
}

.gmd-export__empty-icon svg {
  width: 64px;
  height: 64px;
  color: var(--gmd-on-surface-variant);
  margin-bottom: var(--gmd-space-md);
}

.gmd-export__empty-text {
  font-size: 14px;
  color: var(--gmd-on-surface-variant);
}

/*  ═══════════════════════════════════════════════════════════════════════
 *  Give Me Doc — Settings Tab
 *
 *  .gmd-settings              — tab root
 *  .gmd-settings__section     — grouped section with title
 *  .gmd-settings__tpl-*       — template management list
 *  .gmd-settings__hint        — help text
 *  ═══════════════════════════════════════════════════════════════════════ */

.gmd-settings {
  padding: var(--gmd-space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--gmd-space-xl);
}

/* ── Section ────────────────────────────────────────────────────────── */

.gmd-settings__section {
  display: flex;
  flex-direction: column;
  gap: var(--gmd-space-md);
}

.gmd-settings__section-title {
  font-size: var(--gmd-font-size-md);
  font-weight: var(--gmd-font-weight-bold);
  color: var(--gmd-primary);
  padding-bottom: var(--gmd-space-xs);
  border-bottom: 1px solid var(--gmd-outline-variant);
}

/* ── Template list ──────────────────────────────────────────────────── */

.gmd-settings__tpl-list {
  display: flex;
  flex-direction: column;
  gap: var(--gmd-space-xs);
}

.gmd-settings__tpl-row {
  display: flex;
  align-items: center;
  gap: var(--gmd-space-sm);
  padding: var(--gmd-space-sm) var(--gmd-space-md);
  border-radius: var(--gmd-radius-sm);
  background: var(--gmd-surface-container-low);
  transition: background var(--gmd-duration-short) var(--gmd-easing-standard);
  cursor: pointer;
}

.gmd-settings__tpl-row:hover {
  background: var(--gmd-surface-container);
}

.gmd-settings__tpl-row--selected {
  background: var(--gmd-primary-container);
  color: var(--gmd-on-primary-container);
}

.gmd-settings__tpl-row--selected:hover {
  background: var(--gmd-primary-container);
}

.gmd-settings__tpl-row--selected .gmd-settings__tpl-name {
  color: var(--gmd-on-primary-container);
  font-weight: var(--gmd-font-weight-bold);
}

.gmd-settings__tpl-name {
  font-size: var(--gmd-font-size-md);
  font-weight: var(--gmd-font-weight-medium);
  color: var(--gmd-on-surface);
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.gmd-settings__tpl-desc {
  font-size: var(--gmd-font-size-xs);
  color: var(--gmd-on-surface-variant);
  flex-shrink: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 180px;
}

.gmd-settings__tpl-badge {
  font-size: var(--gmd-font-size-xs);
  padding: 2px var(--gmd-space-sm);
  border-radius: var(--gmd-radius-full);
  background: var(--gmd-surface-variant);
  color: var(--gmd-on-surface-variant);
  white-space: nowrap;
  flex-shrink: 0;
}

/* ── Icon buttons in template rows (smaller) ────────────────────────── */

.gmd-settings__tpl-row .gmd-icon-btn {
  width: 32px;
  height: 32px;
}

.gmd-settings__tpl-row .gmd-icon-btn svg {
  width: 16px;
  height: 16px;
}

/* ── Hint text ──────────────────────────────────────────────────────── */

.gmd-settings__hint {
  font-size: var(--gmd-font-size-xs);
  color: var(--gmd-on-surface-variant);
  padding: var(--gmd-space-xs) 0;
  font-style: italic;
}

/* ── Template editor segmented control ──────────────────────────────── */

.gmd-settings__section .gmd-segmented {
  width: 100%;
}

.gmd-settings__section .gmd-segmented__btn {
  flex: 1;
}

/* ── Select label ───────────────────────────────────────────────────── */

.gmd-settings__select-label {
  font-size: var(--gmd-font-size-sm);
  color: var(--gmd-on-surface-variant);
  margin-bottom: calc(-1 * var(--gmd-space-xs));
}

/*  ═══════════════════════════════════════════════════════════════════════
 *  Give Me Doc — About Tab
 *
 *  .gmd-about        — tab root
 *  .gmd-about__card  — information card
 *  .gmd-about__row   — label → value key‑pair
 *  .gmd-about__link  — clickable external link row
 *  ═══════════════════════════════════════════════════════════════════════ */

.gmd-about {
  padding: var(--gmd-space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--gmd-space-lg);
}

/* ── Card ───────────────────────────────────────────────────────────── */

.gmd-about__card {
  background: var(--gmd-surface-container-low);
  border-radius: var(--gmd-radius-md);
  padding: var(--gmd-space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--gmd-space-md);
}

.gmd-about__card-title {
  font-size: var(--gmd-font-size-md);
  font-weight: var(--gmd-font-weight-bold);
  color: var(--gmd-on-surface);
  padding-bottom: var(--gmd-space-xs);
  border-bottom: 1px solid var(--gmd-outline-variant);
}

/* ── Info row ───────────────────────────────────────────────────────── */

.gmd-about__row {
  display: flex;
  align-items: center;
  gap: var(--gmd-space-md);
}

.gmd-about__label {
  font-size: var(--gmd-font-size-sm);
  color: var(--gmd-on-surface-variant);
  flex-shrink: 0;
  min-width: 72px;
}

.gmd-about__value {
  font-size: var(--gmd-font-size-md);
  color: var(--gmd-on-surface);
  word-break: break-word;
}

.gmd-about__value--ok {
  color: var(--gmd-success);
  font-weight: var(--gmd-font-weight-medium);
}

.gmd-about__value--warn {
  color: var(--gmd-warning);
  font-weight: var(--gmd-font-weight-medium);
}

/* ── Link row ───────────────────────────────────────────────────────── */

.gmd-about__link {
  display: flex;
  align-items: center;
  gap: var(--gmd-space-md);
  padding: var(--gmd-space-sm) var(--gmd-space-md);
  border-radius: var(--gmd-radius-sm);
  text-decoration: none;
  color: var(--gmd-on-surface);
  transition: background var(--gmd-duration-short) var(--gmd-easing-standard);
}

.gmd-about__link:hover {
  background: var(--gmd-surface-container);
}

.gmd-about__link-icon {
  display: inline-flex;
  color: var(--gmd-primary);
  flex-shrink: 0;
}

.gmd-about__link-icon svg {
  width: 20px;
  height: 20px;
}

.gmd-about__link-label {
  flex: 1;
  font-size: var(--gmd-font-size-md);
}

.gmd-about__link-arrow {
  display: inline-flex;
  color: var(--gmd-on-surface-variant);
  opacity: 0.5;
  flex-shrink: 0;
}

.gmd-about__link-arrow svg {
  width: 16px;
  height: 16px;
}
`;
  const WASM_DB_NAME = "gmd-wasm-cache";
  const WASM_DB_VERSION = 1;
  const WASM_STORE = "wasm";
  function openWasmDb() {
    return new Promise((resolve, reject) => {
      const req = indexedDB.open(WASM_DB_NAME, WASM_DB_VERSION);
      req.onupgradeneeded = () => {
        req.result.createObjectStore(WASM_STORE);
      };
      req.onsuccess = () => resolve(req.result);
      req.onerror = () => reject(req.error);
    });
  }
  async function getCachedWasm(url) {
    try {
      const db = await openWasmDb();
      return new Promise((resolve, reject) => {
        const tx = db.transaction(WASM_STORE, "readonly");
        const req = tx.objectStore(WASM_STORE).get(url);
        req.onsuccess = () => {
          db.close();
          resolve(req.result ?? null);
        };
        req.onerror = () => {
          db.close();
          reject(req.error);
        };
      });
    } catch {
      return null;
    }
  }
  async function setCachedWasm(url, bytes) {
    try {
      const db = await openWasmDb();
      await new Promise((resolve, reject) => {
        const tx = db.transaction(WASM_STORE, "readwrite");
        const req = tx.objectStore(WASM_STORE).put(bytes, url);
        req.onsuccess = () => {
          db.close();
          resolve();
        };
        req.onerror = () => {
          db.close();
          reject(req.error);
        };
      });
    } catch (err) {
      console.warn("[GiveMeDoc] Failed to cache WASM:", err);
    }
  }
  async function clearWasmCache() {
    const db = await openWasmDb();
    await new Promise((resolve, reject) => {
      const tx = db.transaction(WASM_STORE, "readwrite");
      const req = tx.objectStore(WASM_STORE).clear();
      req.onsuccess = () => {
        db.close();
        resolve();
      };
      req.onerror = () => {
        db.close();
        reject(req.error);
      };
    });
  }
  async function loadPandocWasm() {
    const config2 = await loadConfig(gmStorage);
    const urls = config2.cdnUrls;
    for (const url of urls) {
      try {
        const cached = await getCachedWasm(url);
        if (cached) {
          await initPandoc(cached, new WorkerWrapper());
          return;
        }
        showToast({ message: "正在下载 Pandoc WASM…", level: "info", duration: 2e3 });
        const wasmBytes = await fetchWasm(url);
        await setCachedWasm(url, wasmBytes);
        await initPandoc(wasmBytes, new WorkerWrapper());
        showToast({ message: `Pandoc 就绪 (${await getPandocVersion()})`, level: "success" });
        return;
      } catch (err) {
        console.warn(`[GiveMeDoc] Failed to load from ${url}:`, err);
      }
    }
    showToast({ message: "Pandoc WASM 加载失败,请检查 CDN 配置", level: "error", duration: 0 });
  }
  function fetchWasm(url) {
    return new Promise((resolve, reject) => {
      GM_xmlhttpRequest({
        method: "GET",
        url,
        responseType: "arraybuffer",
        onload: (resp) => {
          if (resp.status >= 200 && resp.status < 300) {
            resolve(resp.response);
          } else {
            reject(new Error(`HTTP ${resp.status}`));
          }
        },
        onerror: (err) => reject(err)
      });
    });
  }
  (function main() {
    GM_addStyle(css);
    const callbacks2 = createCallbacks(gmStorage, clearWasmCache, (show) => {
      if (show) {
        if (!isFabMounted()) createFab(callbacks2);
      } else {
        destroyFab();
      }
    });
    GM_registerMenuCommand("📄 Give Me Doc 面板", () => togglePanel(callbacks2));
    loadConfig(gmStorage).then((cfg) => {
      if (cfg.showFab) createFab(callbacks2);
    });
    injectSingleExportButtons(createSingleExportHandler(gmStorage));
    injectSharePanelButton(createShareExportHandler(gmStorage));
    setupUrlWatcher(callbacks2);
    loadPandocWasm().catch((err) => {
      console.error("[GiveMeDoc] Pandoc init failed:", err);
    });
    console.log("[GiveMeDoc] Userscript loaded ✓");
  })();
})();