SugarCube Game Engine Debugger

Advanced SugarCube debug panel with UI, configuration, and real-time variable inspection

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey, Greasemonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

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

คุณจะต้องติดตั้งส่วนขยาย เช่น Tampermonkey หรือ Violentmonkey เพื่อติดตั้งสคริปต์นี้

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         SugarCube Game Engine Debugger
// @version      1.0.5
// @description  Advanced SugarCube debug panel with UI, configuration, and real-time variable inspection
// @author       X Death
// @match        file:///*.html
// @match        file:///*.htm
// @icon         https://f95zone.to/data/avatars/l/1963/1963870.jpg
// @run-at       document-idle
// @license      GPL-3.0-or-later
// @namespace    https://github.com/Zenix-Al/SugarCube-Game-Engine-Debugger
// @homepage     https://github.com/Zenix-Al/SugarCube-Game-Engine-Debugger
// @supportURL   https://github.com/Zenix-Al/SugarCube-Game-Engine-Debugger
// ==/UserScript==
// ------------------------------------------------------------
// Built on 2026-05-31 14:54:17 UTC -- AUTO-GENERATED, edit /src then rebuild
// Mode: build
// ------------------------------------------------------------

(() => {
  // src/logger.js
  var debugLoggingEnabled = false;
  function safeCall(method, args) {
    try {
      method.apply(console, args);
    } catch {
    }
  }
  function setDebugLoggingEnabled(enabled) {
    debugLoggingEnabled = Boolean(enabled);
  }
  function debugLog(...args) {
    if (!debugLoggingEnabled) return;
    safeCall(console.log, args);
  }
  function debugInfo(...args) {
    if (!debugLoggingEnabled) return;
    safeCall(console.info || console.log, args);
  }
  function debugWarn(...args) {
    if (!debugLoggingEnabled) return;
    safeCall(console.warn || console.log, args);
  }
  function debugError(...args) {
    if (!debugLoggingEnabled) return;
    safeCall(console.error || console.log, args);
  }

  // src/config.js
  var DebuggerConfig = class {
    constructor() {
      this.enabled = true;
      this.settings = {
        showUnderlines: true,
        showConditions: true,
        showVariables: true,
        showLinks: true,
        showNavControls: false,
        debugLogging: true,
        forceHistory: false,
        historyMaxStates: 120,
        lastTab: "debug",
        underlineColor: "#00ff00",
        underlineStyle: "dashed"
      };
      this.load();
    }
    toggle() {
      this.enabled = !this.enabled;
      return this.enabled;
    }
    getSetting(key) {
      return this.settings[key] ?? null;
    }
    setSetting(key, value) {
      this.settings[key] = value;
      this.save();
    }
    updateSettings(obj) {
      Object.assign(this.settings, obj);
      this.save();
    }
    getSettings() {
      return { ...this.settings };
    }
    save() {
      try {
        localStorage.setItem("sugarcubeDebugger", JSON.stringify(this.settings));
      } catch (e) {
        debugWarn("Could not save debugger settings:", e);
      }
    }
    load() {
      try {
        const saved = localStorage.getItem("sugarcubeDebugger");
        if (saved) {
          this.settings = Object.assign(this.settings, JSON.parse(saved));
        }
      } catch (e) {
        debugWarn("Could not load debugger settings:", e);
      }
    }
  };
  var config_default = new DebuggerConfig();

  // src/modal.js
  var DebuggerModal = class {
    constructor(mountRoot = document.body) {
      this.isOpen = false;
      this.modalEl = null;
      this.backdropEl = null;
      this.onClose = null;
      this.onSettingsChange = null;
      this.mountRoot = mountRoot;
      this.suppressSettingSync = false;
    }
    create() {
      const backdrop = document.createElement("div");
      backdrop.id = "sc-debugger-backdrop";
      backdrop.className = "sc-debugger-backdrop";
      backdrop.addEventListener("click", () => this.close());
      const modal = document.createElement("div");
      modal.id = "sc-debugger-modal";
      modal.className = "sc-debugger-modal";
      modal.innerHTML = `
      <div class="sc-debugger-header">
        <h2>SugarCube Debugger</h2>
        <button class="sc-debugger-close" id="sc-debugger-close-btn" type="button" aria-label="Close debugger">&times;</button>
      </div>
      <div class="sc-debugger-tabs">
        <button class="sc-debugger-tab-btn active" data-tab="debug" type="button">Debug Info</button>
        <button class="sc-debugger-tab-btn" data-tab="editor" type="button">Editor</button>
        <button class="sc-debugger-tab-btn" data-tab="links" type="button">Links</button>
        <button class="sc-debugger-tab-btn" data-tab="settings" type="button">Settings</button>
      </div>
      <div class="sc-debugger-content">
        <div id="sc-debugger-debug-tab" class="sc-debugger-tab-pane active">
          <div id="sc-debugger-output"></div>
        </div>
        <div id="sc-debugger-editor-tab" class="sc-debugger-tab-pane">
          <div id="sc-debugger-editor-output"></div>
        </div>
        <div id="sc-debugger-links-tab" class="sc-debugger-tab-pane">
          <div class="sc-debugger-warning" id="sc-debugger-links-warning"></div>
          <div id="sc-debugger-links-explorer"></div>
        </div>
        <div id="sc-debugger-settings-tab" class="sc-debugger-tab-pane">
          <div class="sc-debugger-settings-group">
            <label class="sc-debugger-switch">
              <input type="checkbox" id="sc-show-underlines" />
              <span class="sc-debugger-slider"></span>
              Show Link Underlines
            </label>
            <label class="sc-debugger-switch">
              <input type="checkbox" id="sc-show-conditions" />
              <span class="sc-debugger-slider"></span>
              Show Conditions
            </label>
            <label class="sc-debugger-switch">
              <input type="checkbox" id="sc-show-variables" />
              <span class="sc-debugger-slider"></span>
              Show Variables
            </label>
            <label class="sc-debugger-switch">
              <input type="checkbox" id="sc-show-links" />
              <span class="sc-debugger-slider"></span>
              Show Links In Debug Tab
            </label>
            <label class="sc-debugger-switch">
              <input type="checkbox" id="sc-show-nav-controls" />
              <span class="sc-debugger-slider"></span>
              Show Back/Forward Buttons
            </label>
            <label class="sc-debugger-switch">
              <input type="checkbox" id="sc-debug-logging" />
              <span class="sc-debugger-slider"></span>
              Debug Logging (Console)
            </label>
            <label class="sc-debugger-switch">
              <input type="checkbox" id="sc-force-history" />
              <span class="sc-debugger-slider"></span>
              Force Enable History Navigation
            </label>
          </div>
          <div class="sc-debugger-number-group">
            <label for="sc-history-max-states">Forced History Max States:</label>
            <input
              type="number"
              id="sc-history-max-states"
              class="sc-debugger-number-input"
              min="2"
              max="2000"
              step="1"
              value="120"
            />
            <p class="sc-debugger-setting-note">
              Applies to future turns. Use at least 2 states for usable back/forward history.
            </p>
          </div>
          <div class="sc-debugger-color-group">
            <label for="sc-underline-color">Underline Color:</label>
            <input type="color" id="sc-underline-color" value="#00ff00" />
          </div>
        </div>
      </div>
    `;
      this.mountRoot.append(backdrop, modal);
      this.backdropEl = backdrop;
      this.modalEl = modal;
      this.attachEventListeners();
    }
    attachEventListeners() {
      const modal = this.modalEl;
      if (!modal) return;
      const closeBtn = modal.querySelector("#sc-debugger-close-btn");
      closeBtn?.addEventListener("click", () => this.close());
      modal.querySelectorAll(".sc-debugger-tab-btn").forEach((btn) => {
        btn.addEventListener("click", (e) => {
          const tab = e.currentTarget?.getAttribute("data-tab");
          if (tab) this.switchTab(tab);
        });
      });
      const bindCheckbox = (id, setting) => {
        const el = modal.querySelector(id);
        el?.addEventListener("change", (e) => {
          const checked = e.target?.checked ?? false;
          this.handleSettingChange(setting, checked);
        });
      };
      bindCheckbox("#sc-show-underlines", "showUnderlines");
      bindCheckbox("#sc-show-conditions", "showConditions");
      bindCheckbox("#sc-show-variables", "showVariables");
      bindCheckbox("#sc-show-links", "showLinks");
      bindCheckbox("#sc-show-nav-controls", "showNavControls");
      bindCheckbox("#sc-debug-logging", "debugLogging");
      bindCheckbox("#sc-force-history", "forceHistory");
      const historyMaxInput = modal.querySelector("#sc-history-max-states");
      historyMaxInput?.addEventListener("change", (e) => {
        const raw = Number.parseInt(String(e.target?.value ?? ""), 10);
        const nextValue = Number.isFinite(raw) ? Math.min(Math.max(raw, 2), 2e3) : 120;
        e.target.value = String(nextValue);
        this.handleSettingChange("historyMaxStates", nextValue);
      });
      const color = modal.querySelector("#sc-underline-color");
      color?.addEventListener("change", (e) => {
        this.handleSettingChange("underlineColor", e.target?.value);
      });
    }
    handleSettingChange(setting, value) {
      if (this.onSettingsChange) {
        this.onSettingsChange(setting, value);
      }
    }
    switchTab(tab) {
      if (!this.modalEl) return;
      const normalized = ["debug", "editor", "links", "settings"].includes(tab) ? tab : "debug";
      this.modalEl.querySelectorAll(".sc-debugger-tab-pane").forEach((el) => el.classList.remove("active"));
      this.modalEl.querySelectorAll(".sc-debugger-tab-btn").forEach((el) => el.classList.remove("active"));
      this.modalEl.querySelector(`#sc-debugger-${normalized}-tab`)?.classList.add("active");
      this.modalEl.querySelector(`[data-tab="${normalized}"]`)?.classList.add("active");
      if (!this.suppressSettingSync) {
        this.handleSettingChange("lastTab", normalized);
      }
    }
    updateSettings(settings) {
      if (!this.modalEl) return;
      const setChecked = (id, val) => {
        const el = this.modalEl.querySelector(id);
        if (el) el.checked = Boolean(val);
      };
      setChecked("#sc-show-underlines", settings.showUnderlines);
      setChecked("#sc-show-conditions", settings.showConditions);
      setChecked("#sc-show-variables", settings.showVariables);
      setChecked("#sc-show-links", settings.showLinks);
      setChecked("#sc-show-nav-controls", settings.showNavControls);
      setChecked("#sc-debug-logging", settings.debugLogging);
      setChecked("#sc-force-history", settings.forceHistory);
      const historyMaxInput = this.modalEl.querySelector("#sc-history-max-states");
      if (historyMaxInput) {
        const raw = Number.parseInt(String(settings.historyMaxStates ?? 120), 10);
        const clamped = Number.isFinite(raw) ? Math.min(Math.max(raw, 2), 2e3) : 120;
        historyMaxInput.value = String(clamped);
      }
      const color = this.modalEl.querySelector("#sc-underline-color");
      if (color) color.value = settings.underlineColor;
      this.suppressSettingSync = true;
      this.switchTab(settings.lastTab || "debug");
      this.suppressSettingSync = false;
    }
    setContent(html) {
      if (!this.modalEl) return;
      const out = this.modalEl.querySelector("#sc-debugger-output");
      if (out) out.innerHTML = html;
    }
    setEditorContent(html) {
      if (!this.modalEl) return;
      const out = this.modalEl.querySelector("#sc-debugger-editor-output");
      if (out) out.innerHTML = html;
    }
    setLinksExplorerContent(html, warningText) {
      if (!this.modalEl) return;
      const out = this.modalEl.querySelector("#sc-debugger-links-explorer");
      if (out) out.innerHTML = html;
      const warn = this.modalEl.querySelector("#sc-debugger-links-warning");
      if (warn) warn.textContent = warningText || "";
    }
    open() {
      if (!this.modalEl) {
        this.create();
      }
      if (this.backdropEl) this.backdropEl.style.display = "block";
      if (this.modalEl) this.modalEl.style.display = "flex";
      this.isOpen = true;
    }
    close() {
      if (this.backdropEl) {
        this.backdropEl.remove();
        this.backdropEl = null;
      }
      if (this.modalEl) {
        this.modalEl.remove();
        this.modalEl = null;
      }
      this.isOpen = false;
      if (this.onClose) {
        this.onClose();
      }
    }
    destroy() {
      if (this.backdropEl) {
        this.backdropEl.remove();
        this.backdropEl = null;
      }
      if (this.modalEl) {
        this.modalEl.remove();
        this.modalEl = null;
      }
      this.isOpen = false;
    }
  };
  var modal_default = DebuggerModal;

  // src/passageAnalyzer.js
  var PassageAnalyzer = class {
    static analyzeConditions(sourceText) {
      const conditionRegex = /<<(if|elseif|else|\/if)[\s\S]*?>>/gi;
      const matches = sourceText.match(conditionRegex) || [];
      return {
        found: matches.length > 0,
        conditions: matches.map((match) => ({
          raw: match,
          type: this.getConditionType(match)
        }))
      };
    }
    static analyzeConditionBlocks(sourceText) {
      const source = String(sourceText || "");
      const tokenRegex = /<<\s*(if|elseif|else|\/if)\b([\s\S]*?)>>/gi;
      const stack = [];
      const blocks = [];
      let match;
      while ((match = tokenRegex.exec(source)) !== null) {
        const tokenType = String(match[1] || "").toLowerCase();
        const tokenArgs = String(match[2] || "").trim();
        const tokenRaw = String(match[0] || "");
        const tokenStart = match.index;
        const tokenEnd = tokenRegex.lastIndex;
        if (tokenType === "if") {
          const block = {
            depth: stack.length,
            start: tokenStart,
            end: null,
            branches: [],
            openingToken: tokenRaw
          };
          const branch = {
            type: "if",
            condition: tokenArgs,
            tokenRaw,
            contentStart: tokenEnd,
            contentEnd: source.length,
            summary: null
          };
          block.branches.push(branch);
          block.currentBranch = branch;
          stack.push(block);
          continue;
        }
        const current = stack[stack.length - 1];
        if (!current) continue;
        if (tokenType === "elseif" || tokenType === "else") {
          if (current.currentBranch) {
            current.currentBranch.contentEnd = tokenStart;
          }
          const branch = {
            type: tokenType,
            condition: tokenArgs,
            tokenRaw,
            contentStart: tokenEnd,
            contentEnd: source.length,
            summary: null
          };
          current.branches.push(branch);
          current.currentBranch = branch;
          continue;
        }
        if (tokenType === "/if") {
          if (current.currentBranch) {
            current.currentBranch.contentEnd = tokenStart;
          }
          current.end = tokenEnd;
          stack.pop();
          blocks.push(current);
        }
      }
      while (stack.length > 0) {
        const dangling = stack.pop();
        dangling.end = source.length;
        if (dangling.currentBranch) {
          dangling.currentBranch.contentEnd = source.length;
        }
        blocks.push(dangling);
      }
      const enriched = blocks.sort((a, b) => a.start - b.start).map((block, idx) => ({
        id: idx + 1,
        depth: block.depth,
        start: block.start,
        end: block.end,
        openingToken: block.openingToken,
        branches: block.branches.map((branch) => {
          const body = source.slice(branch.contentStart, branch.contentEnd);
          return {
            type: branch.type,
            condition: branch.condition,
            tokenRaw: branch.tokenRaw,
            summary: this.summarizeBranchBody(body)
          };
        })
      }));
      return {
        found: enriched.length > 0,
        blocks: enriched
      };
    }
    static getConditionType(text) {
      if (text.startsWith("<<if")) return "if";
      if (text.startsWith("<<elseif")) return "elseif";
      if (text.startsWith("<<else")) return "else";
      if (text.startsWith("<</if")) return "endif";
      return "unknown";
    }
    static getPassageVariables() {
      const vars = SugarCube?.State?.variables || {};
      const cleaned = {};
      for (const key in vars) {
        if (!Object.prototype.hasOwnProperty.call(vars, key)) continue;
        if (typeof vars[key] === "function" || key === "widget" || key.toLowerCase().includes("widget")) {
          continue;
        }
        cleaned[key] = vars[key];
      }
      return cleaned;
    }
    static getTemporaryVariables() {
      return { ...SugarCube?.State?.temporary || {} };
    }
    static getReferencedVariableNamesFromSource(sourceText) {
      const story = /* @__PURE__ */ new Set();
      const temp = /* @__PURE__ */ new Set();
      const storyVarRegex = /\$([a-zA-Z0-9_]+)/g;
      const tempVarRegex = /_([a-zA-Z0-9_]+)/g;
      let match;
      while ((match = storyVarRegex.exec(sourceText)) !== null) {
        const name = match[1];
        if (!name || name === "widget") continue;
        story.add(name);
      }
      while ((match = tempVarRegex.exec(sourceText)) !== null) {
        const name = match[1];
        if (!name) continue;
        temp.add(name);
      }
      return {
        story: Array.from(story).sort(),
        temp: Array.from(temp).sort()
      };
    }
    static getReferencedVariables() {
      const source = this.getCurrentPassageSource();
      const names = this.getReferencedVariableNamesFromSource(source);
      const storyValues = {};
      const tempValues = {};
      for (const name of names.story) {
        storyValues[name] = SugarCube?.State?.variables?.[name];
      }
      for (const name of names.temp) {
        tempValues[name] = SugarCube?.State?.temporary?.[name];
      }
      return { storyValues, tempValues, names };
    }
    static getPassageMetadata() {
      const title = this.getCurrentPassageTitle();
      const passage = SugarCube?.Story?.get?.(title) || null;
      const tags = Array.isArray(passage?.tags) ? passage.tags : [];
      const state = SugarCube?.State || {};
      return {
        title,
        tags,
        turns: Number.isFinite(state.turns) ? state.turns : null,
        historyLength: Number.isFinite(state.length) ? state.length : null,
        historySize: Number.isFinite(state.size) ? state.size : null,
        sourceChars: String(passage?.text || "").length
      };
    }
    static analyzeMacroUsage(sourceText) {
      const usage = /* @__PURE__ */ new Map();
      const macroRegex = /<<\s*(\/?[A-Za-z][\w-]*)\b/g;
      let match;
      while ((match = macroRegex.exec(String(sourceText || ""))) !== null) {
        const rawName = String(match[1] || "").trim();
        if (!rawName) continue;
        const isClosing = rawName.startsWith("/");
        const name = isClosing ? rawName.slice(1) : rawName;
        if (!name) continue;
        if (!usage.has(name)) {
          usage.set(name, {
            name,
            count: 0,
            openingCount: 0,
            closingCount: 0
          });
        }
        const entry = usage.get(name);
        entry.count += 1;
        if (isClosing) {
          entry.closingCount += 1;
        } else {
          entry.openingCount += 1;
        }
      }
      return Array.from(usage.values()).sort((a, b) => b.count - a.count || a.name.localeCompare(b.name));
    }
    static getMacroDetails(sourceText, macroName, { maxSnippets = 6, snippetRadius = 170 } = {}) {
      const source = String(sourceText || "");
      const targetName = String(macroName || "").trim();
      if (!targetName) {
        return {
          name: "",
          total: 0,
          opening: 0,
          closing: 0,
          variables: { story: [], temp: [] },
          snippets: []
        };
      }
      const regex = new RegExp(`<<\\s*(\\/?)${this.escapeRegex(targetName)}\\b[\\s\\S]*?>>`, "gi");
      const snippets = [];
      let total = 0;
      let opening = 0;
      let closing = 0;
      let match;
      while ((match = regex.exec(source)) !== null) {
        total += 1;
        if (match[1] === "/") {
          closing += 1;
        } else {
          opening += 1;
        }
        if (snippets.length < maxSnippets) {
          const at = match.index;
          const from = Math.max(0, at - snippetRadius);
          const to = Math.min(source.length, regex.lastIndex + snippetRadius);
          const rawSnippet = source.slice(from, to).replace(/\s+/g, " ").trim();
          snippets.push({
            index: at,
            text: rawSnippet
          });
        }
      }
      const variableRefs = this.extractVariableRefs(source);
      return {
        name: targetName,
        total,
        opening,
        closing,
        variables: variableRefs,
        snippets
      };
    }
    static summarizeBranchBody(contentText) {
      const content = String(contentText || "");
      const normalized = content.replace(/\s+/g, " ").trim();
      const plainText = content.replace(/<<[\s\S]*?>>/g, " ").replace(/\[\[[\s\S]*?\]\]/g, " ").replace(/<[^>]+>/g, " ").replace(/&[a-z]+;/gi, " ").replace(/\s+/g, " ").trim();
      const words = plainText ? plainText.split(/\s+/).length : 0;
      const macroMatches = content.match(/<<\s*\/?[A-Za-z][\w-]*\b/g) || [];
      const linkMatches = content.match(/\[\[[^[\]]+?\]\]/g) || [];
      const macroLinkMatches = content.match(/<<\s*(link|button|linkgoto|goto)\b/gi) || [];
      const vars = this.extractVariableRefs(content);
      const preview = normalized ? normalized.length > 220 ? `${normalized.slice(0, 220).trimEnd()}...` : normalized : "(No body content.)";
      return {
        rawChars: content.length,
        textChars: plainText.length,
        words,
        macroCount: macroMatches.length,
        linkCount: linkMatches.length + macroLinkMatches.length,
        storyVars: vars.story,
        tempVars: vars.temp,
        preview
      };
    }
    static extractVariableRefs(text) {
      const source = String(text || "");
      const story = /* @__PURE__ */ new Set();
      const temp = /* @__PURE__ */ new Set();
      const storyVarRegex = /\$([a-zA-Z0-9_]+)/g;
      const tempVarRegex = /_([a-zA-Z0-9_]+)/g;
      let match;
      while ((match = storyVarRegex.exec(source)) !== null) {
        if (match[1]) story.add(match[1]);
      }
      while ((match = tempVarRegex.exec(source)) !== null) {
        if (match[1]) temp.add(match[1]);
      }
      return {
        story: Array.from(story).sort(),
        temp: Array.from(temp).sort()
      };
    }
    static escapeRegex(value) {
      return String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
    }
    static analyzeLinksFromDom() {
      const links = [];
      const root = document.getElementById("passages") || document;
      const elements = root.querySelectorAll("a, button, .link-internal");
      elements.forEach((el) => {
        const domId = this.ensureDomLinkId(el);
        const destination = this.resolveElementDestination(el);
        const text = this.resolveElementText(el);
        const mergeKey = this.getLinkMergeKey(text, destination);
        links.push({
          text,
          destination,
          isMacro: this.isMacroElement(el),
          id: el.getAttribute("id") || null,
          origin: "dom",
          isVisible: true,
          domId,
          actionType: "dom-click",
          mergeKey
        });
      });
      return links;
    }
    static analyzeLinksFromSource(sourceText) {
      const links = [];
      const pushSourceLink = (text, destination, { isMacro = false, actionType = "navigate" } = {}) => {
        const safeText = String(text || "").trim() || "(Untitled Link)";
        const safeDestination = String(destination || "").trim() || "Executes Code/Macro";
        links.push({
          text: safeText,
          destination: safeDestination,
          isMacro,
          id: null,
          origin: "source",
          isVisible: false,
          actionType,
          mergeKey: this.getLinkMergeKey(safeText, safeDestination)
        });
      };
      const bracketLinkRegex = /\[\[([^[\]]+?)\]\]/g;
      let match;
      while ((match = bracketLinkRegex.exec(sourceText)) !== null) {
        const body = (match[1] || "").trim();
        if (!body) continue;
        let text = body;
        let destination = body;
        if (body.includes("->")) {
          const [left, right] = body.split("->");
          text = (left || "").trim();
          destination = (right || "").trim();
        } else if (body.includes("<-")) {
          const [left, right] = body.split("<-");
          destination = (left || "").trim();
          text = (right || "").trim();
        } else if (body.includes("|")) {
          const [left, right] = body.split("|");
          text = (left || "").trim();
          destination = (right || "").trim();
        }
        pushSourceLink(text || destination, destination, { isMacro: false, actionType: "navigate" });
      }
      const linkGotoRegex = /<<\s*linkgoto\s+"([^"]+)"\s+"([^"]+)"\s*>>/gi;
      while ((match = linkGotoRegex.exec(sourceText)) !== null) {
        pushSourceLink((match[1] || "").trim() || "(linkgoto)", (match[2] || "").trim(), {
          isMacro: true,
          actionType: "navigate"
        });
      }
      const gotoRegex = /<<\s*goto\s+"([^"]+)"\s*>>/gi;
      while ((match = gotoRegex.exec(sourceText)) !== null) {
        const destination = (match[1] || "").trim();
        if (!destination) continue;
        pushSourceLink(destination, destination, { isMacro: true, actionType: "navigate" });
      }
      const linkButtonMacroRegex = /<<\s*(link|button)\s+([\s\S]*?)>>/gi;
      while ((match = linkButtonMacroRegex.exec(sourceText)) !== null) {
        const rawArgs = String(match[2] || "").trim();
        const tokens = this.tokenizeMacroArgs(rawArgs);
        if (!tokens.length) continue;
        const textToken = this.normalizeMacroToken(tokens[0]);
        const destinationToken = tokens.length > 1 ? this.normalizeMacroToken(tokens[1]) : "";
        const destination = destinationToken || "Executes Code/Macro";
        const actionType = this.isLikelyPassageTarget(destinationToken) ? "navigate" : "macro";
        pushSourceLink(textToken || destination || "(Untitled Link)", destination, {
          isMacro: true,
          actionType
        });
      }
      return links;
    }
    static analyzeAllLinks() {
      const sourceText = this.getCurrentPassageSource();
      const fromSource = this.analyzeLinksFromSource(sourceText);
      const fromDom = this.analyzeLinksFromDom();
      const merged = /* @__PURE__ */ new Map();
      for (const link of fromSource) {
        merged.set(link.mergeKey, link);
      }
      for (const link of fromDom) {
        const key = link.mergeKey;
        if (merged.has(key)) {
          const existing = merged.get(key);
          merged.set(key, {
            ...existing,
            ...link,
            isVisible: true,
            origin: "dom",
            actionType: "dom-click"
          });
        } else {
          const uniqueDomKey = `${key}::dom:${link.domId}`;
          merged.set(uniqueDomKey, link);
        }
      }
      return Array.from(merged.values());
    }
    static analyzeLinks() {
      return this.analyzeLinksFromDom();
    }
    static getCurrentPassageTitle() {
      try {
        const activeTitle = SugarCube?.State?.active?.title;
        if (typeof activeTitle === "string" && activeTitle.trim()) {
          return activeTitle;
        }
      } catch {
      }
      const fromDom = document.querySelector("#passages .passage")?.getAttribute?.("data-passage");
      if (typeof fromDom === "string" && fromDom.trim()) {
        return fromDom;
      }
      return "Story Loading...";
    }
    static getCurrentPassageSource() {
      try {
        const title = this.getCurrentPassageTitle();
        return SugarCube?.Story?.get?.(title)?.text || "";
      } catch (error) {
        debugError("Could not get passage source:", error);
        return "";
      }
    }
    static resolveElementDestination(el) {
      const dataPassage = (el.getAttribute("data-passage") || "").trim();
      if (dataPassage) return dataPassage;
      const dataSetter = (el.getAttribute("data-setter") || "").trim();
      if (dataSetter) return dataSetter;
      if (el.tagName === "A") {
        const href = (el.getAttribute("href") || "").trim();
        if (href && href !== "#") return href;
      }
      return "Executes Code/Macro";
    }
    static resolveElementText(el) {
      const fromText = (el.textContent || "").trim();
      if (fromText) return fromText;
      const aria = (el.getAttribute("aria-label") || "").trim();
      if (aria) return aria;
      const title = (el.getAttribute("title") || "").trim();
      if (title) return title;
      const html = String(el.innerHTML || "").replace(/\s+/g, " ").trim();
      if (html) {
        const cut = html.length > 120 ? `${html.slice(0, 120)}...` : html;
        return `[Markup] ${cut}`;
      }
      return "(Unnamed Link)";
    }
    static isMacroElement(el) {
      const classes = el.classList;
      if (!classes) return false;
      return classes.contains("macro-link") || classes.contains("macro-button") || classes.contains("link-internal");
    }
    static ensureDomLinkId(el) {
      const existing = el.getAttribute("data-sc-debugger-link-id");
      if (existing) return existing;
      this._domLinkCounter = (this._domLinkCounter || 0) + 1;
      const domId = `scdbg-link-${this._domLinkCounter}`;
      el.setAttribute("data-sc-debugger-link-id", domId);
      return domId;
    }
    static tokenizeMacroArgs(rawArgs) {
      const tokens = [];
      const tokenRegex = /"((?:\\.|[^"\\])*)"|'((?:\\.|[^'\\])*)'|`([^`]+)`|\[\[([^\]]+)\]\]|(\S+)/g;
      let match;
      while ((match = tokenRegex.exec(rawArgs)) !== null) {
        const token = match[1] != null ? match[1] : match[2] != null ? match[2] : match[3] != null ? `\`${match[3]}\`` : match[4] != null ? match[4] : match[5] != null ? match[5] : "";
        if (token) tokens.push(token);
      }
      return tokens;
    }
    static normalizeMacroToken(token) {
      const value = String(token || "").trim();
      if (!value) return "";
      if (value.startsWith("`") && value.endsWith("`")) {
        return value;
      }
      return value;
    }
    static isLikelyPassageTarget(destination) {
      const target = String(destination || "").trim();
      if (!target) return false;
      if (target.startsWith("`") && target.endsWith("`")) return false;
      if (target.includes("$(") || target.includes(";")) return false;
      if (target.toLowerCase().startsWith("http://") || target.toLowerCase().startsWith("https://")) {
        return false;
      }
      return true;
    }
    static getLinkMergeKey(text, destination) {
      return `${String(text || "").trim()}||${String(destination || "").trim()}`;
    }
  };
  var passageAnalyzer_default = PassageAnalyzer;

  // src/renderer.js
  var DebugRenderer = class {
    static render(config) {
      const sections = [this.renderPassageOverview(), this.renderPassageMetadata(), this.renderMacroUsage()];
      if (config.getSetting("showConditions")) {
        sections.push(this.renderConditions());
      }
      if (config.getSetting("showVariables")) {
        sections.push(this.renderVariables());
      }
      return sections.join("");
    }
    static renderPassageOverview() {
      const title = passageAnalyzer_default.getCurrentPassageTitle();
      return `
      <section class="sc-debugger-section sc-debugger-section-passage">
        <h3 class="sc-debugger-passage-heading">Current Passage</h3>
        <div class="sc-debugger-passage-name">${this.escapeHtml(String(title || "(Unknown Passage)"))}</div>
      </section>
    `;
    }
    static renderLinksExplorer() {
      const links = passageAnalyzer_default.analyzeAllLinks();
      const sorted = [...links].sort((a, b) => {
        const av = a.isVisible ? 0 : 1;
        const bv = b.isVisible ? 0 : 1;
        if (av !== bv) return av - bv;
        return String(a.destination || "").localeCompare(String(b.destination || ""));
      });
      if (!sorted.length) {
        return `
        <section class="sc-debugger-section">
          <h4 class="sc-debugger-section-title">Detected Links</h4>
          <p class="sc-debugger-empty">No links detected in this passage.</p>
        </section>
      `;
      }
      const rows = sorted.map((link) => {
        const visibility = link.isVisible ? "LIVE" : "HIDDEN";
        const origin = link.origin === "source" ? "SOURCE" : "DOM";
        const kind = link.isMacro ? "MACRO" : "LINK";
        const destination = String(link.destination || "").trim();
        const linkText = String(link.text || "").trim() || destination;
        const interaction = this.getLinkInteraction(link, destination);
        const searchText = `${linkText} ${destination} ${visibility} ${origin} ${kind}`.toLowerCase();
        const kindKey = kind.toLowerCase();
        const visibilityKey = visibility.toLowerCase();
        return `
          <article
            class="sc-debugger-link ${interaction.clickable ? "sc-debugger-link-clickable" : ""}"
            ${this.getLinkInteractionAttributes(interaction)}
            data-sc-search="${this.escapeHtml(searchText)}"
            data-sc-kind="${this.escapeHtml(kindKey)}"
            data-sc-visibility="${this.escapeHtml(visibilityKey)}"
          >
            <header class="sc-link-header">
              <span class="sc-link-label">${kind}</span>
              <span class="sc-link-label sc-link-visibility">${visibility}</span>
              <span class="sc-link-label sc-link-origin">${origin}</span>
            </header>
            <div class="sc-link-text">${this.escapeHtml(linkText)}</div>
            <div class="sc-link-dest">${this.escapeHtml(interaction.description)}</div>
          </article>
        `;
      }).join("");
      return `
      <section class="sc-debugger-section">
        <h4 class="sc-debugger-section-title">Detected Links</h4>
        <div class="sc-var-editor-search">
          <label class="sc-var-editor-label" for="sc-links-search">Filter Links</label>
          <input
            type="search"
            id="sc-links-search"
            autocomplete="off"
            spellcheck="false"
            placeholder="Filter by text, destination, type, or visibility..."
          />
        </div>
        <div class="sc-links-filter-bar" id="sc-links-filter-bar">
          <button type="button" class="sc-var-editor-btn sc-links-filter-btn is-active" data-sc-link-filter="all">All</button>
          <button type="button" class="sc-var-editor-btn sc-links-filter-btn" data-sc-link-filter="live">Live</button>
          <button type="button" class="sc-var-editor-btn sc-links-filter-btn" data-sc-link-filter="hidden">Hidden</button>
          <button type="button" class="sc-var-editor-btn sc-links-filter-btn" data-sc-link-filter="macro">Macro</button>
          <button type="button" class="sc-var-editor-btn sc-links-filter-btn" data-sc-link-filter="link">Link</button>
        </div>
        <p class="sc-debugger-empty" id="sc-links-search-count">Showing ${sorted.length} of ${sorted.length} links.</p>
        <p class="sc-debugger-empty" id="sc-links-search-empty" hidden>No links match this filter.</p>
        <div class="sc-debugger-links-list" id="sc-links-search-results">${rows}</div>
      </section>
    `;
    }
    static renderConditions() {
      const source = passageAnalyzer_default.getCurrentPassageSource();
      const analysis = passageAnalyzer_default.analyzeConditionBlocks(source);
      if (!analysis.found) {
        return `
        <section class="sc-debugger-section">
          <h4 class="sc-debugger-section-title">Logic Conditions</h4>
          <p class="sc-debugger-empty">No if/else conditions found in the current passage.</p>
        </section>
      `;
      }
      const blocks = analysis.blocks.map((block) => {
        const branches = block.branches.map((branch) => {
          const typeClass = `sc-cond-${branch.type}`;
          const readableType = String(branch.type || "unknown").toUpperCase();
          const conditionText = branch.type === "else" ? "Fallback branch" : branch.condition ? `Condition: ${branch.condition}` : "Condition: (not provided)";
          const summary = branch.summary || {};
          const metrics = [
            `${summary.textChars || 0} text chars`,
            `${summary.words || 0} words`,
            `${summary.macroCount || 0} macros`,
            `${summary.linkCount || 0} links`,
            `Vars: ${this.formatVariableChipText(summary.storyVars, summary.tempVars)}`
          ].join(" | ");
          return `
              <div class="sc-debugger-condition ${typeClass}">
                <span class="sc-cond-type">${this.escapeHtml(readableType)}</span>
                <div>
                  <div class="sc-cond-expression">${this.escapeHtml(conditionText)}</div>
                  <div class="sc-cond-meta">${this.escapeHtml(metrics)}</div>
                  <code>${this.escapeHtml(summary.preview || "(No branch body content.)")}</code>
                </div>
              </div>
            `;
        }).join("");
        return `
          <div class="sc-debugger-var-group">
            <h5>IF Block #${block.id}${block.depth > 0 ? ` (nested depth ${block.depth})` : ""}</h5>
            <div class="sc-debugger-conditions-list">${branches}</div>
          </div>
        `;
      }).join("");
      return `
      <section class="sc-debugger-section">
        <h4 class="sc-debugger-section-title">Logic Conditions</h4>
        ${blocks}
      </section>
    `;
    }
    static renderVariables() {
      const { storyValues, tempValues } = passageAnalyzer_default.getReferencedVariables();
      const hasStory = Object.keys(storyValues).length > 0;
      const hasTemp = Object.keys(tempValues).length > 0;
      if (!hasStory && !hasTemp) {
        return `
        <section class="sc-debugger-section">
          <h4 class="sc-debugger-section-title">Referenced Variables</h4>
          <p class="sc-debugger-empty">No variables referenced in this passage.</p>
        </section>
      `;
      }
      let html = `
      <section class="sc-debugger-section">
        <h4 class="sc-debugger-section-title">Referenced Variables</h4>
    `;
      if (hasStory) {
        html += `
        <div class="sc-debugger-var-group">
          <h5>Story Variables ($)</h5>
          <div class="sc-debugger-variables-list">
            ${Object.entries(storyValues).map(([key, value]) => this.renderVariable(key, value)).join("")}
          </div>
        </div>
      `;
      }
      if (hasTemp) {
        html += `
        <div class="sc-debugger-var-group">
          <h5>Temporary Variables (_)</h5>
          <div class="sc-debugger-variables-list">
            ${Object.entries(tempValues).map(([key, value]) => this.renderVariable(key, value)).join("")}
          </div>
        </div>
      `;
      }
      html += "</section>";
      return html;
    }
    static renderVariable(key, value) {
      const type = this.getValueType(value);
      const preview = this.formatValue(value, { maxChars: 400 });
      return `
      <article class="sc-debugger-variable">
        <span class="sc-var-name">${this.escapeHtml(String(key || ""))}</span>
        <span class="sc-var-type sc-var-${this.escapeHtml(type)}">${this.escapeHtml(type)}</span>
        <code class="sc-var-value">${this.escapeHtml(preview)}</code>
      </article>
    `;
    }
    static renderPassageMetadata() {
      const meta = passageAnalyzer_default.getPassageMetadata();
      const tagsText = meta.tags.length ? meta.tags.join(", ") : "(none)";
      const turnsText = meta.turns == null ? "n/a" : String(meta.turns);
      const histLenText = meta.historyLength == null ? "n/a" : String(meta.historyLength);
      const histSizeText = meta.historySize == null ? "n/a" : String(meta.historySize);
      return `
      <section class="sc-debugger-section">
        <h4 class="sc-debugger-section-title">Passage Metadata</h4>
        <div class="sc-debugger-variables-list">
          <article class="sc-debugger-variable">
            <span class="sc-var-name">Tags</span>
            <span class="sc-var-type">meta</span>
            <code class="sc-var-value">${this.escapeHtml(tagsText)}</code>
          </article>
          <article class="sc-debugger-variable">
            <span class="sc-var-name">Turns</span>
            <span class="sc-var-type">meta</span>
            <code class="sc-var-value">${this.escapeHtml(turnsText)}</code>
          </article>
          <article class="sc-debugger-variable">
            <span class="sc-var-name">History</span>
            <span class="sc-var-type">meta</span>
            <code class="sc-var-value">${this.escapeHtml(`${histLenText} / ${histSizeText}`)}</code>
          </article>
          <article class="sc-debugger-variable">
            <span class="sc-var-name">Source Size</span>
            <span class="sc-var-type">meta</span>
            <code class="sc-var-value">${this.escapeHtml(`${meta.sourceChars} chars`)}</code>
          </article>
        </div>
      </section>
    `;
    }
    static renderMacroUsage() {
      const source = passageAnalyzer_default.getCurrentPassageSource();
      const usage = passageAnalyzer_default.analyzeMacroUsage(source);
      if (!usage.length) {
        return `
        <section class="sc-debugger-section">
          <h4 class="sc-debugger-section-title">Macro Usage</h4>
          <p class="sc-debugger-empty">No macros detected in this passage source.</p>
        </section>
      `;
      }
      const items = usage.slice(0, 20).map((item) => {
        const classification = this.classifyMacro(item.name);
        const usageBits = [`${item.count} total`];
        if (item.openingCount > 0) usageBits.push(`open ${item.openingCount}`);
        if (item.closingCount > 0) usageBits.push(`close ${item.closingCount}`);
        usageBits.push(classification.detail);
        return `
          <article class="sc-macro-usage-row" data-sc-macro-name="${this.escapeHtml(item.name)}">
            <button type="button" class="sc-macro-usage-row-btn" aria-expanded="false">
              <span class="sc-var-name">${this.escapeHtml(`<<${item.name}>>`)}</span>
              <span class="sc-var-type">${this.escapeHtml(classification.label)}</span>
              <code class="sc-var-value">${this.escapeHtml(usageBits.join(" | "))}</code>
            </button>
            <div class="sc-macro-usage-details" hidden></div>
          </article>
        `;
      }).join("");
      return `
      <section class="sc-debugger-section">
        <h4 class="sc-debugger-section-title">Macro Usage</h4>
        <p class="sc-debugger-empty">Top ${Math.min(usage.length, 20)} macros in this passage source.</p>
        <div class="sc-debugger-variables-list">${items}</div>
      </section>
    `;
    }
    static renderMacroUsageDetails(macroName) {
      const source = passageAnalyzer_default.getCurrentPassageSource();
      const details = passageAnalyzer_default.getMacroDetails(source, macroName, {
        maxSnippets: 6,
        snippetRadius: 190
      });
      if (!details.total) {
        return `<p class="sc-debugger-empty">No occurrences found for <<${this.escapeHtml(macroName)}>>.</p>`;
      }
      const storyVars = details.variables.story.map((name) => `$${name}`);
      const tempVars = details.variables.temp.map((name) => `_${name}`);
      const varText = storyVars.length || tempVars.length ? [...storyVars, ...tempVars].join(", ") : "(none)";
      const snippets = details.snippets.map(
        (entry, idx) => `
          <article class="sc-macro-usage-snippet">
            <div class="sc-macro-usage-snippet-title">Occurrence ${idx + 1} at char ${entry.index}</div>
            <code>${this.escapeHtml(entry.text)}</code>
          </article>
        `
      ).join("");
      return `
      <div class="sc-macro-usage-detail-meta">
        <span>Total: ${details.total}</span>
        <span>Open: ${details.opening}</span>
        <span>Close: ${details.closing}</span>
      </div>
      <div class="sc-macro-usage-detail-vars">Variables referenced in passage: ${this.escapeHtml(varText)}</div>
      <div class="sc-macro-usage-snippets">${snippets || `<p class="sc-debugger-empty">No snippets available.</p>`}</div>
    `;
    }
    static classifyMacro(name) {
      const key = String(name || "").toLowerCase();
      const table = {
        if: { label: "logic", detail: "Conditional branch start." },
        elseif: { label: "logic", detail: "Conditional branch continuation." },
        else: { label: "logic", detail: "Conditional fallback branch." },
        switch: { label: "logic", detail: "Multi-branch selection." },
        case: { label: "logic", detail: "Switch case branch." },
        default: { label: "logic", detail: "Switch fallback branch." },
        set: { label: "state", detail: "Writes state variables." },
        unset: { label: "state", detail: "Removes variable values." },
        remember: { label: "state", detail: "Stores persistent value." },
        forget: { label: "state", detail: "Clears persistent value." },
        link: { label: "ui", detail: "Interactive text link block." },
        button: { label: "ui", detail: "Interactive button block." },
        linkappend: { label: "ui", detail: "Link that appends content." },
        linkreplace: { label: "ui", detail: "Link that replaces content." },
        linkrepeat: { label: "ui", detail: "Link that can run repeatedly." },
        goto: { label: "nav", detail: "Navigates to another passage." },
        linkgoto: { label: "nav", detail: "Link with explicit destination passage." },
        back: { label: "nav", detail: "Moves backward in history." },
        return: { label: "nav", detail: "Returns to prior passage context." },
        include: { label: "content", detail: "Injects another passage content." },
        display: { label: "content", detail: "Displays another passage." },
        print: { label: "output", detail: "Renders expression output." },
        script: { label: "code", detail: "Runs JavaScript block." },
        widget: { label: "code", detail: "Defines reusable macro widget." },
        timed: { label: "timing", detail: "Schedules delayed content." },
        repeat: { label: "timing", detail: "Repeats timed content block." },
        stop: { label: "timing", detail: "Stops repeat/timed behavior." }
      };
      return table[key] || { label: "macro", detail: "Custom or less-common macro." };
    }
    static formatVariableChipText(storyVars, tempVars) {
      const story = Array.isArray(storyVars) ? storyVars.map((name) => `$${name}`) : [];
      const temp = Array.isArray(tempVars) ? tempVars.map((name) => `_${name}`) : [];
      const all = [...story, ...temp];
      if (!all.length) return "(none)";
      if (all.length <= 6) return all.join(", ");
      return `${all.slice(0, 6).join(", ")}, +${all.length - 6} more`;
    }
    static renderVariableEditor() {
      return `
      <section class="sc-debugger-section sc-var-editor-shell">
        <h4 class="sc-debugger-section-title">Search And Edit Variables</h4>
        <p class="sc-var-editor-hint">Select a variable entry to edit it. Deep object and array paths are supported.</p>

        <div class="sc-var-editor-search">
          <label class="sc-var-editor-label" for="sc-var-editor-search">Search</label>
          <input
            type="search"
            id="sc-var-editor-search"
            autocomplete="off"
            spellcheck="false"
            placeholder="Try stamina, inventory[0], or nested key names..."
          />
        </div>

        <div class="sc-var-editor-loading" id="sc-var-editor-loading" hidden></div>

        <div class="sc-var-editor-results" id="sc-var-editor-results">
          <p class="sc-debugger-empty">Start typing to search variables.</p>
        </div>

        <div class="sc-var-editor-selected" id="sc-var-editor-selected" hidden>
          <h5 class="sc-var-editor-selected-title">Selected Variable</h5>
          <div class="sc-var-editor-path" id="sc-var-editor-path-display"></div>
          <div class="sc-var-editor-current" id="sc-var-editor-current-display"></div>

          <div class="sc-var-editor-input-group">
            <label for="sc-var-editor-input">New Value</label>
            <textarea
              id="sc-var-editor-input"
              rows="6"
              spellcheck="false"
              placeholder="Enter plain text, number, true/false, null, or JSON."
            ></textarea>
          </div>

          <div class="sc-var-editor-buttons">
            <button id="sc-var-editor-save" class="sc-var-editor-btn sc-var-editor-btn-save" type="button">Save</button>
            <button id="sc-var-editor-reset" class="sc-var-editor-btn sc-var-editor-btn-reset" type="button">Reset</button>
            <button id="sc-var-editor-pin" class="sc-var-editor-btn" type="button" title="Pin selected variable for quick access">Pin</button>
            <button id="sc-var-editor-clear" class="sc-var-editor-btn sc-var-editor-btn-clear" type="button">Clear</button>
          </div>

          <div id="sc-var-editor-status" role="status" aria-live="polite"></div>
        </div>

        <div class="sc-var-editor-pins" id="sc-var-editor-pins">
          <h5 class="sc-var-editor-selected-title">Pinned Variables</h5>
          <div class="sc-var-editor-results" id="sc-var-editor-pins-list">
            <p class="sc-debugger-empty">No pinned variables yet.</p>
          </div>
        </div>
      </section>
    `;
    }
    static getValueType(value) {
      if (value === null) return "null";
      if (Array.isArray(value)) return "array";
      return typeof value;
    }
    static getLinkInteraction(link, destination) {
      const isExpressionDestination = destination.startsWith("`") && destination.endsWith("`");
      const isMacroPlaceholder = !destination || destination === "Executes Code/Macro";
      if (link?.domId) {
        const isMacroAction = isMacroPlaceholder || isExpressionDestination;
        return {
          clickable: true,
          actionType: "dom-click",
          domId: String(link.domId),
          destination: destination || "",
          description: isMacroAction ? "Action: DOM click (runs live macro/script)" : `Target: ${destination}`
        };
      }
      const isNavigableDestination = destination.length > 0 && destination !== "Executes Code/Macro" && !destination.includes("$(") && !destination.includes(";") && !(destination.startsWith("`") && destination.endsWith("`"));
      if (isNavigableDestination) {
        return {
          clickable: true,
          actionType: "navigate",
          domId: "",
          destination,
          description: `Target: ${destination}`
        };
      }
      return {
        clickable: false,
        actionType: "none",
        domId: "",
        destination: "",
        description: isExpressionDestination ? `Dynamic target expression: ${destination}` : isMacroPlaceholder ? "Action: Macro or script (no direct passage target)" : `Target: ${destination}`
      };
    }
    static getLinkInteractionAttributes(interaction) {
      if (!interaction?.clickable) return "";
      const attrs = [`data-sc-action="${this.escapeHtml(interaction.actionType)}"`];
      if (interaction.destination) {
        attrs.push(`data-sc-destination="${this.escapeHtml(interaction.destination)}"`);
      }
      if (interaction.domId) {
        attrs.push(`data-sc-dom-id="${this.escapeHtml(interaction.domId)}"`);
      }
      return attrs.join(" ");
    }
    static formatValue(value, { maxChars = 2e3 } = {}) {
      let formatted;
      if (typeof value === "string") {
        formatted = value;
      } else if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
        formatted = String(value);
      } else if (value === null) {
        formatted = "null";
      } else if (typeof value === "undefined") {
        formatted = "undefined";
      } else if (typeof value === "function") {
        formatted = "[Function]";
      } else {
        formatted = this.safeStringify(value, 2);
      }
      if (formatted.length > maxChars) {
        return `${formatted.slice(0, maxChars)}...`;
      }
      return formatted;
    }
    static safeStringify(value, spacing = 0) {
      const seen = /* @__PURE__ */ new WeakSet();
      try {
        return JSON.stringify(
          value,
          (key, current) => {
            if (typeof current === "function") {
              return "[Function]";
            }
            if (typeof current === "object" && current !== null) {
              if (seen.has(current)) return "[Circular]";
              seen.add(current);
            }
            return current;
          },
          spacing
        );
      } catch (error) {
        return `[Unserializable: ${error.message}]`;
      }
    }
    static escapeHtml(text) {
      const map = {
        "&": "&amp;",
        "<": "&lt;",
        ">": "&gt;",
        '"': "&quot;",
        "'": "&#039;"
      };
      return String(text).replace(/[&<>"']/g, (char) => map[char]);
    }
  };
  var renderer_default = DebugRenderer;

  // src/variableEditor.js
  var CACHE_TTL_MS = 1e3;
  var MAX_VALUE_CHARS = 1800;
  var MAX_SEARCH_CHARS = 4e3;
  var MAX_DEPTH = 14;
  var MAX_INDEX_SIZE = 12e3;
  var VariableEditor = class {
    constructor() {
      this.selectedVar = null;
      this.searchCache = null;
      this.cacheTimestamp = 0;
      this._buildToken = 0;
    }
    /**
     * Build searchable index of variables and nested values.
     */
    buildSearchIndex() {
      if (this.isCacheFresh()) {
        return this.searchCache;
      }
      const root = this.getStoryVariablesRoot();
      const state = this.createTraversalState(root);
      while (state.cursor < state.queue.length && state.index.length < MAX_INDEX_SIZE) {
        this.consumeQueueItem(state);
      }
      this.commitCache(state.index);
      return state.index;
    }
    /**
     * Build index incrementally to avoid UI freezes.
     */
    async buildSearchIndexAsync({ frameBudgetMs = 8, onProgress } = {}) {
      if (this.isCacheFresh()) {
        return this.searchCache;
      }
      const root = this.getStoryVariablesRoot();
      const state = this.createTraversalState(root);
      while (state.cursor < state.queue.length && state.index.length < MAX_INDEX_SIZE) {
        const frameStart = this.now();
        while (state.cursor < state.queue.length && state.index.length < MAX_INDEX_SIZE && this.now() - frameStart < frameBudgetMs) {
          this.consumeQueueItem(state);
        }
        if (typeof onProgress === "function") {
          onProgress({
            processed: state.processed,
            indexed: state.index.length,
            queued: state.queue.length - state.cursor
          });
        }
        await this._yieldToBrowser();
      }
      this.commitCache(state.index);
      return state.index;
    }
    search(query) {
      const index = this.buildSearchIndex();
      const q = String(query || "").toLowerCase().trim();
      if (!q) {
        return index;
      }
      return index.filter((item) => item.searchText.includes(q));
    }
    async searchAsync(query, { frameBudgetMs = 8, onProgress } = {}) {
      const index = await this.buildSearchIndexAsync({ frameBudgetMs, onProgress });
      const q = String(query || "").toLowerCase().trim();
      if (!q) {
        return index;
      }
      return index.filter((item) => item.searchText.includes(q));
    }
    /**
     * Get variable value by path (supports array/object paths).
     */
    getVariable(path) {
      const tokens = this.parsePath(path);
      let current = this.getStoryVariablesRoot();
      for (const token of tokens) {
        if (current == null) {
          return void 0;
        }
        current = current[token];
      }
      return current;
    }
    /**
     * Parse text input into typed value.
     */
    parseValue(input) {
      if (typeof input !== "string") {
        return input;
      }
      const trimmed = input.trim();
      if (trimmed === "") return "";
      if (trimmed === "undefined") return void 0;
      if (trimmed === "null") return null;
      if (trimmed === "true") return true;
      if (trimmed === "false") return false;
      if (/^-?(?:\d+|\d+\.\d+|\.\d+)(?:[eE][+-]?\d+)?$/.test(trimmed)) {
        return Number(trimmed);
      }
      if (trimmed.startsWith("'") && trimmed.endsWith("'") && trimmed.length >= 2) {
        return trimmed.slice(1, -1);
      }
      if (trimmed.startsWith("{") || trimmed.startsWith("[") || trimmed.startsWith('"') || trimmed.startsWith("'")) {
        try {
          return JSON.parse(trimmed);
        } catch {
        }
      }
      return input;
    }
    /**
     * Set variable value by path. Creates missing containers when needed.
     */
    setValue(path, value) {
      const tokens = this.parsePath(path);
      if (!tokens.length) {
        throw new Error("Invalid variable path.");
      }
      let current = this.getStoryVariablesRoot();
      for (let i = 0; i < tokens.length - 1; i++) {
        const token = tokens[i];
        const nextToken = tokens[i + 1];
        if (!this.isObjectLike(current)) {
          throw new Error(`Cannot navigate into non-object segment "${String(token)}".`);
        }
        if (!this.isObjectLike(current[token])) {
          current[token] = typeof nextToken === "number" ? [] : {};
        }
        current = current[token];
      }
      const lastToken = tokens[tokens.length - 1];
      if (!this.isObjectLike(current)) {
        throw new Error("Cannot assign into non-object target.");
      }
      current[lastToken] = value;
      this.invalidateCache();
      return true;
    }
    /**
     * Format value for display with circular-reference safety.
     */
    formatValue(value, { maxChars = 8e3 } = {}) {
      if (typeof value === "string") {
        return value;
      }
      if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
        return String(value);
      }
      if (value === null) return "null";
      if (typeof value === "undefined") return "undefined";
      if (typeof value === "function") return "[Function]";
      const serialized = this.safeStringify(value, 2);
      if (serialized.length > maxChars) {
        return `${serialized.slice(0, maxChars)}...`;
      }
      return serialized;
    }
    invalidateCache() {
      this.searchCache = null;
      this.cacheTimestamp = 0;
      this._buildToken++;
    }
    isCacheFresh() {
      if (!Array.isArray(this.searchCache)) return false;
      return Date.now() - this.cacheTimestamp < CACHE_TTL_MS;
    }
    getStoryVariablesRoot() {
      return SugarCube?.State?.variables || {};
    }
    createTraversalState(root) {
      const queue = [];
      const index = [];
      const visited = /* @__PURE__ */ new WeakSet();
      const rootKeys = Object.keys(root);
      for (const key of rootKeys) {
        if (this.shouldSkipKey(key)) continue;
        const value = root[key];
        const path = this.composePath("", key, false);
        const item = this.buildIndexItem(path, value, key);
        index.push(item);
        queue.push({ value, path, depth: 0 });
      }
      return {
        queue,
        cursor: 0,
        index,
        visited,
        processed: 0
      };
    }
    consumeQueueItem(state) {
      const node = state.queue[state.cursor++];
      state.processed += 1;
      if (!this.isTraversable(node?.value)) {
        return;
      }
      if (state.visited.has(node.value)) {
        return;
      }
      state.visited.add(node.value);
      if (node.depth >= MAX_DEPTH) {
        return;
      }
      if (Array.isArray(node.value)) {
        for (let i = 0; i < node.value.length && state.index.length < MAX_INDEX_SIZE; i++) {
          const value = node.value[i];
          const path = this.composePath(node.path, String(i), true);
          const item = this.buildIndexItem(path, value, String(i));
          state.index.push(item);
          if (this.isTraversable(value)) {
            state.queue.push({ value, path, depth: node.depth + 1 });
          }
        }
        return;
      }
      for (const key of Object.keys(node.value)) {
        if (state.index.length >= MAX_INDEX_SIZE) break;
        if (this.shouldSkipKey(key)) continue;
        const value = node.value[key];
        const path = this.composePath(node.path, key, false);
        const item = this.buildIndexItem(path, value, key);
        state.index.push(item);
        if (this.isTraversable(value)) {
          state.queue.push({ value, path, depth: node.depth + 1 });
        }
      }
    }
    buildIndexItem(path, value, key) {
      const displayPath = `$${path}`;
      const valueStr = this.previewValue(value);
      const type = this.getValueType(value);
      const searchText = `${displayPath} ${key} ${type} ${valueStr}`.toLowerCase().slice(
        0,
        MAX_SEARCH_CHARS
      );
      return {
        path,
        displayPath,
        value,
        valueStr,
        key,
        type,
        searchText
      };
    }
    previewValue(value) {
      if (typeof value === "string") {
        return value.length > MAX_VALUE_CHARS ? `${value.slice(0, MAX_VALUE_CHARS)}...` : value;
      }
      if (typeof value === "number" || typeof value === "boolean" || typeof value === "bigint") {
        return String(value);
      }
      if (value === null) return "null";
      if (typeof value === "undefined") return "undefined";
      if (typeof value === "function") return "[Function]";
      if (Array.isArray(value)) return `[Array(${value.length})]`;
      if (this.isObjectLike(value)) {
        const keys = Object.keys(value);
        const preview = keys.slice(0, 6).join(", ");
        return `[Object(${keys.length})${preview ? `: ${preview}` : ""}${keys.length > 6 ? ", ..." : ""}]`;
      }
      return String(value);
    }
    getValueType(value) {
      if (value === null) return "null";
      if (Array.isArray(value)) return "array";
      return typeof value;
    }
    composePath(parentPath, key, isArrayIndex) {
      if (isArrayIndex) {
        return parentPath ? `${parentPath}[${key}]` : `[${key}]`;
      }
      const isIdentifier = /^[A-Za-z_$][A-Za-z0-9_$]*$/.test(key);
      if (!parentPath) {
        return isIdentifier ? key : `[${JSON.stringify(key)}]`;
      }
      return isIdentifier ? `${parentPath}.${key}` : `${parentPath}[${JSON.stringify(key)}]`;
    }
    parsePath(path) {
      const source = String(path || "").trim().replace(/^\$/, "");
      if (!source) return [];
      const tokens = [];
      let i = 0;
      while (i < source.length) {
        const char = source[i];
        if (char === ".") {
          i += 1;
          continue;
        }
        if (char === "[") {
          i += 1;
          while (i < source.length && /\s/.test(source[i])) i += 1;
          if (i >= source.length) break;
          if (source[i] === "'" || source[i] === '"') {
            const quote = source[i];
            i += 1;
            let token2 = "";
            while (i < source.length) {
              const current = source[i];
              if (current === "\\" && i + 1 < source.length) {
                token2 += source[i + 1];
                i += 2;
                continue;
              }
              if (current === quote) {
                i += 1;
                break;
              }
              token2 += current;
              i += 1;
            }
            while (i < source.length && /\s/.test(source[i])) i += 1;
            if (source[i] === "]") i += 1;
            tokens.push(token2);
            continue;
          }
          let raw = "";
          while (i < source.length && source[i] !== "]") {
            raw += source[i];
            i += 1;
          }
          if (source[i] === "]") i += 1;
          const trimmed = raw.trim();
          if (trimmed === "") continue;
          if (/^\d+$/.test(trimmed)) {
            tokens.push(Number(trimmed));
          } else {
            tokens.push(trimmed);
          }
          continue;
        }
        let identifier = "";
        while (i < source.length && source[i] !== "." && source[i] !== "[") {
          identifier += source[i];
          i += 1;
        }
        const token = identifier.trim();
        if (token) tokens.push(token);
      }
      return tokens;
    }
    shouldSkipKey(key) {
      if (typeof key !== "string") return true;
      if (key === "widget") return true;
      if (key.toLowerCase().includes("widget")) return true;
      if (key.startsWith("__scdbg_")) return true;
      return false;
    }
    isObjectLike(value) {
      return value !== null && (typeof value === "object" || typeof value === "function");
    }
    isTraversable(value) {
      return value !== null && typeof value === "object";
    }
    safeStringify(value, spacing = 0) {
      const seen = /* @__PURE__ */ new WeakSet();
      try {
        return JSON.stringify(
          value,
          (key, current) => {
            if (typeof current === "function") return "[Function]";
            if (typeof current === "bigint") return `${String(current)}n`;
            if (typeof current === "symbol") return String(current);
            if (typeof current === "object" && current !== null) {
              if (seen.has(current)) return "[Circular]";
              seen.add(current);
            }
            return current;
          },
          spacing
        );
      } catch (error) {
        return `[Unserializable: ${error.message}]`;
      }
    }
    now() {
      if (typeof performance !== "undefined" && typeof performance.now === "function") {
        return performance.now();
      }
      return Date.now();
    }
    _yieldToBrowser() {
      return new Promise((resolve) => {
        if (typeof requestIdleCallback === "function") {
          requestIdleCallback(() => resolve(), { timeout: 50 });
          return;
        }
        if (typeof requestAnimationFrame === "function") {
          requestAnimationFrame(() => resolve());
          return;
        }
        setTimeout(resolve, 0);
      });
    }
    commitCache(index) {
      this.searchCache = index;
      this.cacheTimestamp = Date.now();
    }
  };
  var variableEditor_default = new VariableEditor();

  // src/utils.js
  function ensureDebuggerStylesInjected(targetRoot = document.head) {
    if (targetRoot?.querySelector?.("#sc-debugger-inline-styles")) return;
    const css = `
  .sc-debugger-backdrop{position:fixed;inset:0;background:radial-gradient(circle at 18% 18%,rgba(77,196,255,.18),transparent 55%),rgba(2,8,20,.72);backdrop-filter:blur(4px);z-index:9998;display:none;pointer-events:auto}
  .sc-debugger-modal{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);width:min(1100px,95vw);height:min(86vh,760px);border:1px solid #2f435f;border-radius:16px;background:linear-gradient(160deg,#111926 0%,#101827 45%,#0d1523 100%);box-shadow:0 24px 56px rgba(2,8,20,.64);z-index:9999;display:none;flex-direction:column;color:#e6edf7;font-family:Segoe UI,Inter,Arial,sans-serif;overflow:hidden;pointer-events:auto}
  .sc-debugger-header{display:flex;justify-content:space-between;align-items:center;padding:14px 18px;border-bottom:1px solid #25344b;background:linear-gradient(180deg,rgba(18,32,52,.8),rgba(12,21,35,.75))}
  .sc-debugger-header h2{margin:0;font-size:20px;color:#92f8bf;font-weight:700}
  .sc-debugger-close{width:32px;height:32px;border:1px solid transparent;border-radius:8px;background:transparent;color:#9caec7;font-size:22px;cursor:pointer}
  .sc-debugger-close:hover{color:#ff9e9e;background:rgba(255,158,158,.12);border-color:rgba(255,158,158,.24)}
  .sc-debugger-tabs{display:grid;grid-template-columns:repeat(4,1fr);gap:8px;padding:10px 12px;border-bottom:1px solid #25344b;background:rgba(10,17,29,.55)}
  .sc-debugger-tab-btn{min-height:38px;padding:9px 12px;border:1px solid transparent;border-radius:10px;background:transparent;color:#9caec7;cursor:pointer;font-size:14px;font-weight:600}
  .sc-debugger-tab-btn:hover{color:#e6edf7;border-color:rgba(77,196,255,.28);background:rgba(77,196,255,.08)}
  .sc-debugger-tab-btn.active{color:#dff6ff;background:linear-gradient(180deg,rgba(44,165,255,.24),rgba(44,165,255,.09));border-color:rgba(44,165,255,.42)}
  .sc-debugger-content{flex:1;overflow:auto;padding:16px}
  .sc-debugger-tab-pane{display:none}
  .sc-debugger-tab-pane.active{display:block}
  .sc-debugger-section{margin-bottom:16px;padding:14px;border:1px solid #25344b;border-radius:12px;background:linear-gradient(180deg,rgba(24,37,56,.45),rgba(15,24,38,.55))}
  .sc-debugger-section:last-child{margin-bottom:0}
  .sc-debugger-section-title{margin:0 0 10px;color:#4dc4ff;font-size:12px;letter-spacing:.8px;text-transform:uppercase;font-weight:700}
  .sc-debugger-empty{margin:8px 0 0;color:#9caec7;font-size:13px}
  .sc-debugger-warning{margin:0 0 12px;padding:11px 12px;border-radius:10px;border:1px solid rgba(255,210,128,.35);background:rgba(255,210,128,.09);color:#ffd280;font-size:12px;line-height:1.45}
  .sc-debugger-section-passage{border-color:rgba(146,248,191,.28);background:linear-gradient(140deg,rgba(25,54,44,.3),rgba(18,31,47,.6))}
  .sc-debugger-passage-heading{margin:0;font-size:12px;letter-spacing:.8px;text-transform:uppercase;color:#9caec7}
  .sc-debugger-passage-name{margin-top:8px;font-size:18px;font-weight:700;color:#92f8bf;word-break:break-word}
  .sc-debugger-conditions-list{display:grid;gap:8px}
  .sc-debugger-condition{display:grid;grid-template-columns:78px 1fr;gap:8px;align-items:start;padding:9px 10px;border-radius:8px;border:1px solid rgba(255,255,255,.07);background:rgba(255,255,255,.03)}
  .sc-cond-type{color:#dfe9ff;font-size:10px;font-weight:700;letter-spacing:.5px;text-transform:uppercase}
  .sc-debugger-condition code{margin:0;font-family:Consolas,SFMono-Regular,Monaco,monospace;font-size:12px;color:#d4deec;line-height:1.4;word-break:break-word;white-space:pre-wrap}
  .sc-cond-expression{color:#dbe8fa;font-size:12px;line-height:1.4;margin-bottom:4px}
  .sc-cond-meta{color:#9fb3cf;font-size:11px;line-height:1.35;margin-bottom:6px}
  .sc-debugger-condition.sc-cond-if{border-color:rgba(125,213,255,.35);background:rgba(77,196,255,.08)}
  .sc-debugger-condition.sc-cond-elseif{border-color:rgba(255,210,128,.35);background:rgba(255,210,128,.08)}
  .sc-debugger-condition.sc-cond-else{border-color:rgba(179,194,215,.34);background:rgba(179,194,215,.08)}
  .sc-debugger-condition.sc-cond-endif{border-color:rgba(111,232,159,.35);background:rgba(111,232,159,.07)}
  .sc-debugger-var-group{margin-bottom:12px}
  .sc-debugger-var-group:last-child{margin-bottom:0}
  .sc-debugger-var-group h5{margin:0 0 8px;color:#9caec7;font-size:11px;font-weight:700;letter-spacing:.75px;text-transform:uppercase}
  .sc-debugger-variables-list{display:grid;gap:7px}
  .sc-debugger-variable{display:grid;grid-template-columns:minmax(100px,170px) minmax(68px,92px) 1fr;gap:10px;align-items:start;padding:9px 10px;border-radius:9px;border:1px solid rgba(255,255,255,.07);background:rgba(255,255,255,.025)}
  .sc-var-name{color:#f6f0ab;font-size:12px;font-weight:700;line-height:1.3;word-break:break-word}
  .sc-var-type{display:inline-flex;align-items:center;justify-content:center;min-height:20px;padding:0 7px;border-radius:999px;border:1px solid rgba(200,213,236,.25);font-size:10px;font-weight:700;letter-spacing:.5px;text-transform:uppercase;color:#c8d5ec;background:rgba(200,213,236,.08)}
  .sc-var-type.sc-var-string{color:#8efeb0;border-color:rgba(142,254,176,.36);background:rgba(142,254,176,.14)}
  .sc-var-type.sc-var-number{color:#8cd9ff;border-color:rgba(140,217,255,.4);background:rgba(140,217,255,.12)}
  .sc-var-type.sc-var-boolean{color:#ffdca6;border-color:rgba(255,220,166,.42);background:rgba(255,220,166,.12)}
  .sc-var-type.sc-var-object,.sc-var-type.sc-var-array{color:#c9bbff;border-color:rgba(201,187,255,.35);background:rgba(201,187,255,.1)}
  .sc-var-type.sc-var-null,.sc-var-type.sc-var-undefined{color:#f1b4cc;border-color:rgba(241,180,204,.3);background:rgba(241,180,204,.08)}
  .sc-var-value{margin:0;color:#d4deed;font-family:Consolas,SFMono-Regular,Monaco,monospace;font-size:12px;line-height:1.45;white-space:pre-wrap;word-break:break-word}
  .sc-macro-usage-row{border:1px solid rgba(255,255,255,.08);border-radius:9px;background:rgba(255,255,255,.02);padding:8px 9px;width:100%}
  .sc-macro-usage-row-btn{width:100%;border:0;background:transparent;color:inherit;text-align:left;display:grid;grid-template-columns:minmax(100px,170px) minmax(68px,92px) 1fr;gap:10px;align-items:start;padding:0;cursor:pointer}
  .sc-macro-usage-row-btn .sc-var-type{align-self:start}
  .sc-macro-usage-row-btn .sc-var-value{white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .sc-macro-usage-row-btn:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(77,196,255,.18);border-radius:8px}
  .sc-macro-usage-row.is-expanded{border-color:rgba(77,196,255,.42);background:rgba(77,196,255,.08)}
  .sc-macro-usage-details{margin-top:8px;padding-top:8px;border-top:1px dashed rgba(77,196,255,.28);width:100%}
  .sc-macro-usage-detail-meta{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:6px;color:#bee7ff;font-size:11px}
  .sc-macro-usage-detail-vars{color:#aecaeb;font-size:11px;line-height:1.35;margin-bottom:8px}
  .sc-macro-usage-snippets{display:grid;gap:7px}
  .sc-macro-usage-snippet{border:1px solid rgba(255,255,255,.1);border-radius:8px;background:rgba(5,12,22,.5);padding:7px 8px}
  .sc-macro-usage-snippet-title{color:#9ec4e4;font-size:10px;font-weight:700;margin-bottom:4px;letter-spacing:.35px;text-transform:uppercase}
  .sc-macro-usage-snippet code{margin:0;color:#d7e2f3;font-family:Consolas,SFMono-Regular,Monaco,monospace;font-size:11px;line-height:1.35;white-space:pre-wrap;word-break:break-word}
  .sc-debugger-links-list{display:grid;gap:9px}
  .sc-debugger-link{padding:11px 12px;border-radius:10px;border:1px solid rgba(77,196,255,.2);background:rgba(77,196,255,.07)}
  .sc-debugger-link-clickable{cursor:pointer}
  .sc-debugger-link-clickable:hover{border-color:rgba(77,196,255,.52);background:rgba(77,196,255,.14)}
  .sc-link-header{display:flex;gap:6px;margin-bottom:8px;flex-wrap:wrap}
  .sc-link-label{display:inline-flex;align-items:center;justify-content:center;min-height:18px;padding:0 7px;border-radius:999px;border:1px solid rgba(255,210,128,.4);background:rgba(255,210,128,.15);color:#ffe5ba;font-size:10px;font-weight:700;letter-spacing:.45px;text-transform:uppercase}
  .sc-link-visibility{border-color:rgba(140,217,255,.4);background:rgba(140,217,255,.12);color:#bee7ff}
  .sc-link-origin{border-color:rgba(184,194,216,.36);background:rgba(184,194,216,.1);color:#d4dbeb}
  .sc-link-text{color:#e3efff;font-weight:600;font-size:13px;line-height:1.35;word-break:break-word}
  .sc-link-dest{margin-top:6px;color:#9caec7;font-size:11px;font-family:Consolas,SFMono-Regular,Monaco,monospace;word-break:break-word}
  .sc-link-id{margin-top:6px;color:#b5c5dc;font-size:11px}
  .sc-debugger-settings-group{display:grid;gap:10px}
  .sc-debugger-switch{display:grid;grid-template-columns:46px 1fr;align-items:center;gap:10px;cursor:pointer;color:#d8e4f8;font-size:13px;user-select:none}
  .sc-debugger-switch input{position:absolute;opacity:0;pointer-events:none}
  .sc-debugger-slider{width:44px;height:24px;border-radius:999px;border:1px solid rgba(255,255,255,.2);background:rgba(188,201,222,.25);position:relative}
  .sc-debugger-slider::after{content:"";position:absolute;top:2px;left:2px;width:18px;height:18px;border-radius:50%;background:#f2f6ff;transition:transform .2s ease}
  .sc-debugger-switch input:checked + .sc-debugger-slider{border-color:rgba(111,232,159,.7);background:rgba(111,232,159,.45)}
  .sc-debugger-switch input:checked + .sc-debugger-slider::after{transform:translateX(19px);background:#f0fff6}
  .sc-debugger-color-group{margin-top:14px;padding-top:14px;border-top:1px solid #25344b;display:grid;gap:8px}
  .sc-debugger-number-group{margin-top:4px;display:grid;gap:8px}
  .sc-debugger-number-group label{color:#9caec7;font-size:13px}
  .sc-debugger-number-input{width:110px;min-height:34px;border-radius:8px;border:1px solid rgba(77,196,255,.34);background:rgba(8,15,27,.78);color:#e6edf7;font-family:Consolas,SFMono-Regular,Monaco,monospace;font-size:13px;padding:6px 8px}
  .sc-debugger-number-input:focus{outline:none;border-color:rgba(111,232,159,.7);box-shadow:0 0 0 3px rgba(111,232,159,.16)}
  .sc-debugger-setting-note{margin:0;color:#9caec7;font-size:11px;line-height:1.4}
  .sc-debugger-color-group label{color:#9caec7;font-size:13px}
  #sc-underline-color{width:68px;height:36px;border-radius:8px;border:1px solid rgba(255,255,255,.18);background:transparent;cursor:pointer}
  .sc-debugger-controls{position:fixed;right:20px;bottom:20px;display:flex;gap:8px;align-items:center;pointer-events:none;z-index:9997}
  #sc-debugger-toggle-btn,.sc-debugger-nav-btn{pointer-events:auto}
  #sc-debugger-toggle-btn{width:50px;height:50px;border:1px solid rgba(146,248,191,.42);border-radius:14px;background:linear-gradient(160deg,#8af5bb,#63e0a1);color:#0b2a1d;font-size:22px;font-weight:800;cursor:grab;touch-action:none;user-select:none;box-shadow:0 12px 26px rgba(11,42,29,.34)}
  #sc-debugger-toggle-btn.active{border-color:rgba(255,158,158,.5);background:linear-gradient(170deg,#ffa9a9,#ff7c7c);color:#3e1010}
  #sc-debugger-toggle-btn.sc-debugger-dragging{cursor:grabbing}
  .sc-debugger-nav-btn{width:42px;height:42px;border-radius:10px;border:1px solid rgba(77,196,255,.35);background:rgba(77,196,255,.13);color:#d6efff;font-size:18px;cursor:pointer}
  .sc-debugger-nav-btn:hover{background:rgba(77,196,255,.2)}
  #passages a.sc-debugger-underlined,#passages button.sc-debugger-underlined,#passages .link-internal.sc-debugger-underlined{border-bottom-width:2px!important;border-bottom-style:dashed!important;border-bottom-color:#4dc4ff!important}
  .sc-var-editor-hint{margin:0 0 12px;color:#9caec7;font-size:12px;line-height:1.45}
  .sc-var-editor-search{margin-bottom:10px}
  .sc-var-editor-label{display:block;margin-bottom:7px;color:#d7e7ff;font-size:12px;font-weight:700;letter-spacing:.45px;text-transform:uppercase}
  .sc-var-editor-search input{width:100%;min-height:40px;padding:9px 12px;border-radius:9px;border:1px solid rgba(77,196,255,.3);background:rgba(8,15,27,.72);color:#e6edf7;font-size:14px;font-family:Consolas,SFMono-Regular,Monaco,monospace}
  .sc-var-editor-search input:focus{outline:none;border-color:rgba(111,232,159,.72);box-shadow:0 0 0 3px rgba(111,232,159,.18)}
  .sc-var-editor-loading{margin-bottom:10px;color:#b9d8ef;font-size:12px;display:flex;align-items:center;gap:8px}
  .sc-var-editor-loading::before{content:"";width:11px;height:11px;border-radius:50%;border:2px solid rgba(77,196,255,.4);border-top-color:#92f8bf;animation:scdbgspin .8s linear infinite}
  .sc-var-editor-loading[hidden]{display:none}
  .sc-var-editor-results{max-height:280px;overflow-y:auto;border-radius:10px;border:1px solid rgba(77,196,255,.22);background:rgba(9,16,28,.56);padding:4px;margin-bottom:12px}
  .sc-var-editor-result-item{width:100%;display:grid;gap:4px;text-align:left;border:1px solid transparent;border-radius:8px;padding:9px 10px;background:transparent;color:#e6edf7;cursor:pointer}
  .sc-var-editor-result-item:hover{border-color:rgba(111,232,159,.36);background:rgba(111,232,159,.12)}
  .sc-var-editor-result-item:focus-visible{outline:none;border-color:rgba(111,232,159,.82);box-shadow:0 0 0 3px rgba(111,232,159,.18)}
  .sc-var-editor-result-item.is-selected{border-color:rgba(77,196,255,.65);background:rgba(77,196,255,.17)}
  .sc-var-editor-result-name{color:#f7efab;font-size:12px;font-weight:700;line-height:1.35;word-break:break-word}
  .sc-var-editor-result-meta{display:flex;flex-wrap:wrap;gap:6px}
  .sc-var-editor-result-type{display:inline-flex;align-items:center;justify-content:center;min-height:18px;padding:0 7px;border-radius:999px;border:1px solid rgba(140,217,255,.46);background:rgba(140,217,255,.13);color:#cbecff;font-size:10px;font-weight:700;letter-spacing:.45px;text-transform:uppercase}
  .sc-var-editor-result-value{color:#c0cce0;font-family:Consolas,SFMono-Regular,Monaco,monospace;font-size:11px;line-height:1.4;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
  .sc-var-editor-selected{border-radius:10px;border:1px solid rgba(111,232,159,.28);background:rgba(111,232,159,.08);padding:12px}
  .sc-var-editor-selected[hidden]{display:none}
  .sc-var-editor-selected-title{margin:0 0 10px;color:#d6ffe8;font-size:13px;font-weight:700;letter-spacing:.4px}
  .sc-var-editor-path{padding:8px 9px;border-radius:8px;border:1px solid rgba(77,196,255,.35);background:rgba(8,15,27,.7);color:#f7efab;font-family:Consolas,SFMono-Regular,Monaco,monospace;font-size:12px;margin-bottom:9px;word-break:break-word}
  .sc-var-editor-current{padding:8px 9px;border-radius:8px;border:1px solid rgba(220,229,245,.2);background:rgba(10,17,30,.82);color:#d7e3f8;font-size:12px;font-family:Consolas,SFMono-Regular,Monaco,monospace;white-space:pre-wrap;word-break:break-word;max-height:180px;overflow-y:auto;margin-bottom:10px}
  .sc-var-editor-input-group{margin-bottom:10px}
  .sc-var-editor-input-group label{display:block;margin-bottom:6px;color:#dce8fb;font-size:12px;font-weight:700;letter-spacing:.35px}
  .sc-var-editor-input-group textarea,.sc-var-editor-input-group input{width:100%;border-radius:9px;border:1px solid rgba(77,196,255,.33);background:rgba(8,15,27,.82);color:#e6edf7;font-family:Consolas,SFMono-Regular,Monaco,monospace;font-size:12px;line-height:1.45;padding:9px 11px;resize:vertical}
  .sc-var-editor-input-group textarea:focus,.sc-var-editor-input-group input:focus{outline:none;border-color:rgba(111,232,159,.78);box-shadow:0 0 0 3px rgba(111,232,159,.18)}
  .sc-var-editor-buttons{display:grid;grid-template-columns:repeat(3,minmax(90px,1fr));gap:8px;margin-bottom:8px}
  .sc-var-editor-btn{min-height:36px;border-radius:9px;border:1px solid rgba(255,255,255,.18);background:rgba(8,15,27,.75);color:#dbe8ff;font-size:12px;font-weight:700;cursor:pointer}
  .sc-var-editor-btn-save{border-color:rgba(111,232,159,.48);color:#b8ffd4;background:rgba(111,232,159,.14)}
  .sc-var-editor-btn-reset{border-color:rgba(255,210,128,.45);color:#ffe8bc;background:rgba(255,210,128,.14)}
  .sc-var-editor-btn-clear{border-color:rgba(255,158,158,.5);color:#ffd2d2;background:rgba(255,158,158,.15)}
  #sc-var-editor-status{min-height:34px;display:none;align-items:center;justify-content:center;text-align:center;border-radius:8px;padding:0 10px;font-size:12px;font-weight:700}
  #sc-var-editor-status.success{display:flex;border:1px solid rgba(111,232,159,.45);background:rgba(111,232,159,.14);color:#bfffd8}
  #sc-var-editor-status.error{display:flex;border:1px solid rgba(255,158,158,.45);background:rgba(255,158,158,.14);color:#ffd4d4}
  .sc-links-filter-bar{display:flex;flex-wrap:wrap;gap:8px;margin:4px 0 10px}
  .sc-links-filter-btn{min-width:66px}
  .sc-links-filter-btn.is-active{border-color:rgba(111,232,159,.52);background:rgba(111,232,159,.2);color:#d7ffe8}
  .sc-var-editor-result-pin{display:inline-flex;align-items:center;justify-content:center;min-height:18px;padding:0 7px;border-radius:999px;border:1px solid rgba(111,232,159,.42);background:rgba(111,232,159,.16);color:#c6ffda;font-size:10px;font-weight:700;letter-spacing:.42px;text-transform:uppercase}
  .sc-var-editor-buttons{grid-template-columns:repeat(4,minmax(90px,1fr))}
  .sc-var-editor-btn.is-active{border-color:rgba(111,232,159,.48);background:rgba(111,232,159,.22);color:#c7ffe0}
  .sc-var-editor-pins{margin-top:12px;padding-top:12px;border-top:1px dashed rgba(111,232,159,.32)}
  .sc-var-editor-pin-row{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px;align-items:stretch;margin-bottom:8px}
  .sc-var-editor-pin-row:last-child{margin-bottom:0}
  .sc-var-editor-pin-remove{min-width:74px;border-radius:8px;border:1px solid rgba(255,158,158,.48);background:rgba(255,158,158,.16);color:#ffd3d3;font-size:11px;font-weight:700;cursor:pointer;transition:background .18s ease,transform .16s ease}
  .sc-var-editor-pin-remove:hover{background:rgba(255,158,158,.26);transform:translateY(-1px)}
  .sc-var-editor-pin-remove:focus-visible{outline:none;box-shadow:0 0 0 3px rgba(255,158,158,.2)}
  @media (max-width:780px){.sc-debugger-modal{width:98vw;height:92vh;border-radius:12px}.sc-debugger-tabs{grid-template-columns:repeat(2,1fr);gap:6px}.sc-debugger-variable{grid-template-columns:1fr;gap:6px}.sc-macro-usage-row-btn{grid-template-columns:1fr;gap:6px}.sc-var-editor-buttons{grid-template-columns:1fr}.sc-debugger-controls{right:12px;bottom:12px}}
  @keyframes scdbgspin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}
  `;
    const styleEl = document.createElement("style");
    styleEl.id = "sc-debugger-inline-styles";
    styleEl.textContent = css;
    targetRoot.appendChild(styleEl);
  }
  function notify(message, duration = 2e3, mountRoot = document.body, styleRoot = document.head) {
    ensureDebuggerStylesInjected(styleRoot);
    let box = mountRoot?.querySelector?.("#sc-debugger-notify") || document.getElementById("sc-debugger-notify");
    if (!box) {
      box = document.createElement("div");
      box.id = "sc-debugger-notify";
      Object.assign(box.style, {
        position: "fixed",
        bottom: "84px",
        right: "20px",
        background: "#18253a",
        color: "#e6edf7",
        padding: "10px 14px",
        border: "1px solid #4dc4ff",
        borderRadius: "8px",
        boxShadow: "0 8px 24px rgba(2, 8, 20, 0.45)",
        zIndex: 9996,
        fontFamily: "Consolas, monospace",
        fontSize: "12px",
        display: "none",
        transition: "opacity 0.2s ease"
      });
      mountRoot.appendChild(box);
    }
    box.textContent = message;
    box.style.display = "block";
    box.style.opacity = "1";
    window.clearTimeout(box._scHideTimer);
    box._scHideTimer = window.setTimeout(() => {
      box.style.opacity = "0";
      window.setTimeout(() => {
        box.style.display = "none";
      }, 200);
    }, duration);
  }
  function applyLinkUnderlines(config) {
    const shouldUnderline = Boolean(config.getSetting("showUnderlines"));
    const color = config.getSetting("underlineColor") || "#4dc4ff";
    const style = config.getSetting("underlineStyle") || "dashed";
    const root = document.getElementById("passages") || document;
    const elements = root.querySelectorAll("a, button, .link-internal");
    elements.forEach((el) => {
      const dest = el.getAttribute("data-passage") || el.getAttribute("data-setter") || "Executes Code/Macro";
      if (shouldUnderline) {
        el.classList.add("sc-debugger-underlined");
        el.style.borderBottomColor = color;
        el.style.borderBottomStyle = style;
        el.setAttribute("title", `-> Target: ${dest}`);
      } else {
        el.classList.remove("sc-debugger-underlined");
        el.style.borderBottom = "none";
      }
    });
  }
  var clickCaptureBound = false;
  function captureClickEvents() {
    if (clickCaptureBound) return;
    clickCaptureBound = true;
    document.addEventListener(
      "click",
      (event) => {
        const root = document.getElementById("passages");
        if (!root) return;
        const target = event.target?.closest?.("a, button, .link-internal");
        if (!target || !root.contains(target)) return;
        const dest = target.getAttribute("data-passage") || target.getAttribute("data-setter") || "Executes Code/Macro";
        debugLog("%c[SC Debugger - Link Clicked]", "color: #ffcc88; font-weight: bold;", {
          text: (target.textContent || "").trim(),
          destination: dest,
          element: target
        });
      },
      true
    );
  }
  var modalNavBoundRoots = /* @__PURE__ */ new WeakSet();
  function wireModalLinkNavigation(root = document) {
    if (root && modalNavBoundRoots.has(root)) return;
    if (root) modalNavBoundRoots.add(root);
    root.addEventListener("click", (event) => {
      const modal = root.querySelector?.("#sc-debugger-modal") || document.getElementById("sc-debugger-modal");
      if (!modal) return;
      const row = event.target?.closest?.(".sc-debugger-link-clickable");
      if (!row || !modal.contains(row)) return;
      event.preventDefault();
      const action = row.getAttribute("data-sc-action") || "navigate";
      if (action === "dom-click") {
        const domId = row.getAttribute("data-sc-dom-id");
        if (domId) {
          const selectorId = typeof CSS !== "undefined" && CSS.escape ? CSS.escape(domId) : domId;
          const liveElement = document.querySelector(
            `#passages [data-sc-debugger-link-id="${selectorId}"]`
          );
          if (liveElement && typeof liveElement.click === "function") {
            liveElement.click();
            return;
          }
        }
      }
      const destination = row.getAttribute("data-sc-destination");
      if (!destination) return;
      const engine = typeof Engine !== "undefined" && Engine || typeof SugarCube !== "undefined" && SugarCube.Engine || null;
      if (engine && typeof engine.play === "function") {
        engine.play(destination);
      } else {
        debugWarn("SugarCube Engine not found; cannot navigate to:", destination);
      }
    });
  }
  function makeToggleButtonDraggable(btnEl, storageKey = "sugarcubeDebuggerBtnPos") {
    if (!btnEl) return;
    const applyPosition = (left, top) => {
      const maxLeft = Math.max(0, window.innerWidth - btnEl.offsetWidth);
      const maxTop = Math.max(0, window.innerHeight - btnEl.offsetHeight);
      const clampedLeft = Math.min(Math.max(0, left), maxLeft);
      const clampedTop = Math.min(Math.max(0, top), maxTop);
      btnEl.style.left = `${clampedLeft}px`;
      btnEl.style.top = `${clampedTop}px`;
      btnEl.style.right = "auto";
      btnEl.style.bottom = "auto";
      try {
        localStorage.setItem(storageKey, JSON.stringify({ left: clampedLeft, top: clampedTop }));
      } catch {
      }
    };
    try {
      const raw = localStorage.getItem(storageKey);
      if (raw) {
        const parsed = JSON.parse(raw);
        if (typeof parsed?.left === "number" && typeof parsed?.top === "number") {
          applyPosition(parsed.left, parsed.top);
        }
      }
    } catch {
    }
    let isDragging = false;
    let startLeft = 0;
    let startTop = 0;
    let startX = 0;
    let startY = 0;
    let moved = false;
    let pressedButton = null;
    let activePointerId = null;
    const onPointerMove = (event) => {
      if (!isDragging || event.pointerId !== activePointerId) return;
      const dx = event.clientX - startX;
      const dy = event.clientY - startY;
      if (!moved && (Math.abs(dx) > 4 || Math.abs(dy) > 4)) {
        moved = true;
      }
      if (moved) {
        applyPosition(startLeft + dx, startTop + dy);
      }
    };
    const detachDragListeners = () => {
      window.removeEventListener("pointermove", onPointerMove, true);
      window.removeEventListener("pointerup", onPointerEnd, true);
      window.removeEventListener("pointercancel", onPointerEnd, true);
    };
    const endDrag = () => {
      if (!isDragging) return;
      isDragging = false;
      btnEl.classList.remove("sc-debugger-dragging");
      if (moved && pressedButton) {
        const targetButton = pressedButton;
        targetButton._scJustDragged = true;
        window.setTimeout(() => {
          targetButton._scJustDragged = false;
        }, 120);
      }
      moved = false;
      pressedButton = null;
      activePointerId = null;
      detachDragListeners();
    };
    const onPointerEnd = (event) => {
      if (event.pointerId !== activePointerId) return;
      endDrag();
    };
    const ensureLeftTop = () => {
      const left = parseFloat(btnEl.style.left);
      const top = parseFloat(btnEl.style.top);
      if (!Number.isFinite(left) || !Number.isFinite(top)) {
        const rect = btnEl.getBoundingClientRect();
        applyPosition(rect.left, rect.top);
        return { left: rect.left, top: rect.top };
      }
      return { left, top };
    };
    btnEl.addEventListener("pointerdown", (event) => {
      if (event.button != null && event.button !== 0) return;
      const eventTarget = event.target;
      const targetElement = eventTarget instanceof Element ? eventTarget : eventTarget?.parentElement || null;
      pressedButton = targetElement?.closest?.("button") || null;
      isDragging = true;
      moved = false;
      activePointerId = event.pointerId;
      startX = event.clientX;
      startY = event.clientY;
      const pos = ensureLeftTop();
      startLeft = pos.left;
      startTop = pos.top;
      btnEl.classList.add("sc-debugger-dragging");
      window.addEventListener("pointermove", onPointerMove, true);
      window.addEventListener("pointerup", onPointerEnd, true);
      window.addEventListener("pointercancel", onPointerEnd, true);
    });
    window.addEventListener("resize", () => {
      const rect = btnEl.getBoundingClientRect();
      applyPosition(rect.left, rect.top);
    });
  }

  // src/main.js
  var SugarCubeDebugger = class {
    constructor() {
      this.uiHost = null;
      this.shadowRoot = null;
      this.modal = null;
      this.isInitialized = false;
      this.toggleBtn = null;
      this.editorUi = null;
      this.debuggerMetaVariableKey = "__scdbg_meta_b3f4a81d9427";
      this.debuggerMetaPersistentKey = "__scdbg_meta_pins_b3f4a81d9427";
      this.originalHistoryConfig = null;
      this.engineHistoryPatch = null;
    }
    init() {
      if (this.isInitialized) return;
      setDebugLoggingEnabled(Boolean(config_default.getSetting("debugLogging")));
      this.ensureUiRoot();
      ensureDebuggerStylesInjected(this.shadowRoot);
      this.modal = new modal_default(this.shadowRoot);
      this.createToggleButton();
      this.attachKeyboardShortcuts();
      this.attachPassageChangeListener();
      this.modal.onSettingsChange = (setting, value) => {
        config_default.setSetting(setting, value);
        this.applySettings();
        this.refreshDebugInfo();
      };
      this.modal.onClose = () => {
        this.cleanup();
      };
      this.isInitialized = true;
      notify(
        "SugarCube Debugger Ready (Press Ctrl+D to toggle)",
        2e3,
        this.shadowRoot,
        this.shadowRoot
      );
      this.applySettings();
    }
    ensureUiRoot() {
      let host = document.getElementById("sc-debugger-host");
      if (!host) {
        host = document.createElement("div");
        host.id = "sc-debugger-host";
        host.style.position = "fixed";
        host.style.right = "0";
        host.style.bottom = "0";
        host.style.width = "220px";
        host.style.height = "130px";
        host.style.overflow = "visible";
        host.style.background = "transparent";
        host.style.zIndex = "2147483647";
        host.style.pointerEvents = "none";
        document.body.appendChild(host);
      }
      if (!host.shadowRoot) {
        host.attachShadow({ mode: "open" });
      }
      this.uiHost = host;
      this.shadowRoot = host.shadowRoot;
    }
    createToggleButton() {
      if (!this.shadowRoot) return;
      if (this.shadowRoot.getElementById("sc-debugger-controls")) return;
      const controls = document.createElement("div");
      controls.id = "sc-debugger-controls";
      controls.className = "sc-debugger-controls";
      controls.style.pointerEvents = "auto";
      controls.innerHTML = `
      <button id="sc-debugger-back-btn" class="sc-debugger-nav-btn" type="button" title="Back (Engine.backward)" hidden>
        &#8592;
      </button>
      <button id="sc-debugger-toggle-btn" type="button" title="Toggle Debugger (Ctrl+D)">D</button>
      <button id="sc-debugger-forward-btn" class="sc-debugger-nav-btn" type="button" title="Forward (Engine.forward)" hidden>
        &#8594;
      </button>
    `;
      controls.addEventListener("click", (event) => {
        const eventTarget = event.target;
        const targetElement = eventTarget instanceof Element ? eventTarget : eventTarget?.parentElement || null;
        const button = targetElement?.closest?.("button");
        if (!button) return;
        if (button._scJustDragged) return;
        if (button.id === "sc-debugger-toggle-btn") {
          this.toggle();
          return;
        }
        if (button.id === "sc-debugger-back-btn") {
          this.navigateHistory("backward");
          return;
        }
        if (button.id === "sc-debugger-forward-btn") {
          this.navigateHistory("forward");
        }
      });
      this.shadowRoot.appendChild(controls);
      makeToggleButtonDraggable(controls);
      this.toggleBtn = controls.querySelector("#sc-debugger-toggle-btn");
      this.updateNavControls();
    }
    attachKeyboardShortcuts() {
      const isToggleShortcut = (event) => (event.ctrlKey || event.metaKey) && (event.key === "d" || event.key === "D");
      const stopPropagation = (event) => {
        if (typeof event.stopImmediatePropagation === "function") {
          event.stopImmediatePropagation();
        }
        event.stopPropagation();
      };
      document.addEventListener(
        "keydown",
        (event) => {
          if (isToggleShortcut(event)) {
            event.preventDefault();
            stopPropagation(event);
            this.toggle();
            return;
          }
          if (event.key === "Escape" && this.modal?.isOpen) {
            event.preventDefault();
            stopPropagation(event);
            this.modal.close();
          }
        },
        true
      );
      const trapModalKeyEvent = (event) => {
        if (!this.modal?.isOpen) return;
        if (isToggleShortcut(event)) return;
        if (event.key === "Escape") return;
        stopPropagation(event);
      };
      this.shadowRoot?.addEventListener("keydown", trapModalKeyEvent);
      this.shadowRoot?.addEventListener("keypress", trapModalKeyEvent);
      this.shadowRoot?.addEventListener("keyup", trapModalKeyEvent);
    }
    attachPassageChangeListener() {
      document.addEventListener(":passageend", () => this.onPassageEnd());
      document.addEventListener(":passagerender", () => this.onPassageEnd());
      if (typeof window.jQuery !== "undefined") {
        window.jQuery(document).on(":passageend.sc-debugger", () => this.onPassageEnd());
      }
    }
    onPassageEnd() {
      this.applySettings();
      if (this.modal?.isOpen) {
        this.refreshDebugInfo();
      }
    }
    toggle() {
      if (this.modal?.isOpen) {
        this.modal.close();
      } else {
        this.open();
      }
    }
    open() {
      if (!this.modal) return;
      this.modal.open();
      this.modal.updateSettings(config_default.getSettings());
      this.refreshDebugInfo();
      this.applySettings();
      this.shadowRoot?.getElementById("sc-debugger-toggle-btn")?.classList.add("active");
    }
    refreshDebugInfo() {
      if (!this.modal) return;
      this.modal.setContent(renderer_default.render(config_default));
      this.modal.setEditorContent(renderer_default.renderVariableEditor());
      const linksWarning = "Warning: Link actions try to click the live passage element first. If unavailable, debugger falls back to Engine.play(destination), which may skip original side-effects.";
      this.modal.setLinksExplorerContent(renderer_default.renderLinksExplorer(), linksWarning);
      this.bindDebugInfoEvents();
      this.bindEditorEvents();
      this.bindLinksExplorerEvents();
    }
    bindDebugInfoEvents() {
      if (!this.shadowRoot) return;
      const debugOutput = this.shadowRoot.getElementById("sc-debugger-output");
      if (!debugOutput || debugOutput._scDebugInfoBound) return;
      debugOutput._scDebugInfoBound = true;
      debugOutput.addEventListener("click", (event) => {
        const target = event.target;
        const targetElement = target instanceof Element ? target : target?.parentElement || null;
        const rowButton = targetElement?.closest?.(".sc-macro-usage-row-btn");
        if (!rowButton || !debugOutput.contains(rowButton)) return;
        const row = rowButton.closest(".sc-macro-usage-row");
        const detailBox = row?.querySelector(".sc-macro-usage-details");
        if (!row || !detailBox) return;
        const isExpanding = !row.classList.contains("is-expanded");
        row.classList.toggle("is-expanded", isExpanding);
        rowButton.setAttribute("aria-expanded", isExpanding ? "true" : "false");
        if (!isExpanding) {
          detailBox.hidden = true;
          return;
        }
        if (!row.dataset.scMacroLoaded) {
          const macroName = row.getAttribute("data-sc-macro-name") || "";
          detailBox.innerHTML = renderer_default.renderMacroUsageDetails(macroName);
          row.dataset.scMacroLoaded = "1";
        }
        detailBox.hidden = false;
      });
    }
    bindEditorEvents() {
      if (!this.shadowRoot) return;
      const searchInput = this.shadowRoot.getElementById("sc-var-editor-search");
      const resultsContainer = this.shadowRoot.getElementById("sc-var-editor-results");
      const selectedDiv = this.shadowRoot.getElementById("sc-var-editor-selected");
      const valueInput = this.shadowRoot.getElementById("sc-var-editor-input");
      const saveBtn = this.shadowRoot.getElementById("sc-var-editor-save");
      const resetBtn = this.shadowRoot.getElementById("sc-var-editor-reset");
      const pinBtn = this.shadowRoot.getElementById("sc-var-editor-pin");
      const clearBtn = this.shadowRoot.getElementById("sc-var-editor-clear");
      const statusDiv = this.shadowRoot.getElementById("sc-var-editor-status");
      const loadingDiv = this.shadowRoot.getElementById("sc-var-editor-loading");
      const pinsList = this.shadowRoot.getElementById("sc-var-editor-pins-list");
      if (!searchInput || !resultsContainer || !valueInput) return;
      let searchSeq = 0;
      let searchTimer = null;
      let statusTimer = null;
      const escapeHtml = (text) => renderer_default.escapeHtml(String(text || ""));
      const setLoading = (text = "") => {
        if (!loadingDiv) return;
        loadingDiv.textContent = text;
        loadingDiv.hidden = !text;
      };
      const setStatus = (message = "", kind = "") => {
        if (!statusDiv) return;
        statusDiv.classList.remove("success", "error");
        statusDiv.textContent = message;
        if (!message) return;
        if (kind) statusDiv.classList.add(kind);
        if (statusTimer) window.clearTimeout(statusTimer);
        statusTimer = window.setTimeout(() => {
          statusDiv.classList.remove("success", "error");
          statusDiv.textContent = "";
        }, 2200);
      };
      const renderResults = (results, maxResults = 200) => {
        if (!results.length) {
          resultsContainer.innerHTML = `<p class="sc-debugger-empty">No variables found.</p>`;
          return;
        }
        const pinnedSet = new Set(this.getPinnedPaths());
        const shown = results.slice(0, maxResults);
        let html = "";
        if (results.length > maxResults) {
          html += `<p class="sc-debugger-empty">Showing first ${maxResults} results. Refine your search for more precision.</p>`;
        }
        html += shown.map((item) => {
          const preview = item.valueStr.length > 95 ? `${item.valueStr.slice(0, 95).trimEnd()}...` : item.valueStr;
          const selectedClass = item.path === variableEditor_default.selectedVar ? " is-selected" : "";
          const pinnedBadge = pinnedSet.has(item.path) ? `<span class="sc-var-editor-result-pin">Pinned</span>` : "";
          return `
            <button
              type="button"
              class="sc-var-editor-result-item${selectedClass}"
              data-path="${escapeHtml(item.path)}"
              title="${escapeHtml(item.displayPath)}"
            >
              <div class="sc-var-editor-result-name">${escapeHtml(item.displayPath)}</div>
              <div class="sc-var-editor-result-meta">
                <span class="sc-var-editor-result-type">${escapeHtml(item.type)}</span>
                ${pinnedBadge}
              </div>
              <div class="sc-var-editor-result-value">${escapeHtml(preview)}</div>
            </button>
          `;
        }).join("");
        resultsContainer.innerHTML = html;
      };
      const updateVisibleResultPinBadges = () => {
        const pinnedSet = new Set(this.getPinnedPaths());
        const items = resultsContainer.querySelectorAll(".sc-var-editor-result-item");
        items.forEach((item) => {
          const path = item.getAttribute("data-path");
          if (!path) return;
          const meta = item.querySelector(".sc-var-editor-result-meta");
          if (!meta) return;
          const badge = meta.querySelector(".sc-var-editor-result-pin");
          const pinned = pinnedSet.has(path);
          if (pinned && !badge) {
            const newBadge = document.createElement("span");
            newBadge.className = "sc-var-editor-result-pin";
            newBadge.textContent = "Pinned";
            meta.appendChild(newBadge);
          }
          if (!pinned && badge) {
            badge.remove();
          }
        });
      };
      const updatePinButton = () => {
        if (!pinBtn) return;
        const selectedPath = this.normalizeVariablePath(variableEditor_default.selectedVar);
        if (!selectedPath) {
          pinBtn.disabled = true;
          pinBtn.textContent = "Pin";
          pinBtn.classList.remove("is-active");
          pinBtn.setAttribute("aria-pressed", "false");
          return;
        }
        const pinned = this.isPathPinned(selectedPath);
        pinBtn.disabled = false;
        pinBtn.textContent = pinned ? "Unpin" : "Pin";
        pinBtn.classList.toggle("is-active", pinned);
        pinBtn.setAttribute("aria-pressed", pinned ? "true" : "false");
      };
      const renderPinnedList = () => {
        if (!pinsList) return;
        const pinnedPaths = this.getPinnedPaths();
        if (!pinnedPaths.length) {
          pinsList.innerHTML = `<p class="sc-debugger-empty">No pinned variables yet.</p>`;
          return;
        }
        const rows = pinnedPaths.map((path) => {
          const value = variableEditor_default.getVariable(path);
          const type = variableEditor_default.getValueType(value);
          const selectedClass = path === variableEditor_default.selectedVar ? " is-selected" : "";
          const displayPath = `$${path}`;
          const preview = variableEditor_default.formatValue(value, { maxChars: 260 }).replace(/\s+/g, " ").trim();
          const compactPreview = preview.length > 110 ? `${preview.slice(0, 110).trimEnd()}...` : preview;
          return `
            <div class="sc-var-editor-pin-row${selectedClass}">
              <button
                type="button"
                class="sc-var-editor-result-item sc-var-editor-pin-select${selectedClass}"
                data-path="${escapeHtml(path)}"
                title="${escapeHtml(displayPath)}"
              >
                <div class="sc-var-editor-result-name">${escapeHtml(displayPath)}</div>
                <div class="sc-var-editor-result-meta">
                  <span class="sc-var-editor-result-type">${escapeHtml(type)}</span>
                  <span class="sc-var-editor-result-pin">Pinned</span>
                </div>
                <div class="sc-var-editor-result-value">${escapeHtml(compactPreview || "undefined")}</div>
              </button>
              <button
                type="button"
                class="sc-var-editor-pin-remove"
                data-path="${escapeHtml(path)}"
                title="Remove pin for ${escapeHtml(displayPath)}"
              >
                Unpin
              </button>
            </div>
          `;
        }).join("");
        pinsList.innerHTML = rows;
      };
      const handleSearch = () => {
        const query = searchInput.value;
        if (searchTimer) window.clearTimeout(searchTimer);
        const seq = ++searchSeq;
        searchTimer = window.setTimeout(async () => {
          setLoading("Indexing variables...");
          try {
            const results = await variableEditor_default.searchAsync(query, {
              frameBudgetMs: 6,
              onProgress: ({ processed }) => {
                if (seq !== searchSeq) return;
                setLoading(`Indexing variables... (${processed} scanned)`);
              }
            });
            if (seq !== searchSeq) return;
            setLoading("");
            renderResults(results);
          } catch (error) {
            if (seq !== searchSeq) return;
            setLoading("");
            setStatus(`Search failed: ${error.message}`, "error");
            resultsContainer.innerHTML = `<p class="sc-debugger-empty">Search failed. Check console for details.</p>`;
          }
        }, 150);
      };
      searchInput.addEventListener("input", handleSearch);
      resultsContainer.addEventListener("click", (event) => {
        const target = event.target?.closest?.(".sc-var-editor-result-item");
        if (!target || !resultsContainer.contains(target)) return;
        const path = target.getAttribute("data-path");
        if (!path) return;
        this.selectVariable(path);
        setStatus("");
      });
      saveBtn?.addEventListener("click", () => {
        if (!variableEditor_default.selectedVar) return;
        try {
          const newValue = variableEditor_default.parseValue(valueInput.value);
          variableEditor_default.setValue(variableEditor_default.selectedVar, newValue);
          this.selectVariable(variableEditor_default.selectedVar);
          setStatus(`Saved ${variableEditor_default.selectedVar}`, "success");
          if (searchInput.value.trim()) {
            handleSearch();
          }
        } catch (error) {
          setStatus(`Error: ${error.message}`, "error");
        }
      });
      resetBtn?.addEventListener("click", () => {
        if (!variableEditor_default.selectedVar) return;
        const currentVal = variableEditor_default.getVariable(variableEditor_default.selectedVar);
        valueInput.value = variableEditor_default.formatValue(currentVal);
      });
      pinBtn?.addEventListener("click", () => {
        const selectedPath = this.normalizeVariablePath(variableEditor_default.selectedVar);
        if (!selectedPath) return;
        const { pinned, success } = this.togglePinnedPath(selectedPath);
        if (!success) {
          setStatus("Pin storage is not ready yet. Open after SugarCube finishes loading.", "error");
          return;
        }
        renderPinnedList();
        updatePinButton();
        updateVisibleResultPinBadges();
        setStatus(`${pinned ? "Pinned" : "Unpinned"} $${selectedPath}`, "success");
      });
      clearBtn?.addEventListener("click", () => {
        variableEditor_default.selectedVar = null;
        selectedDiv?.setAttribute("hidden", "");
        searchInput.value = "";
        setLoading("");
        setStatus("");
        updatePinButton();
        resultsContainer.innerHTML = `<p class="sc-debugger-empty">Start typing to search variables.</p>`;
      });
      pinsList?.addEventListener("click", (event) => {
        const removeButton = event.target?.closest?.(".sc-var-editor-pin-remove");
        if (removeButton && pinsList.contains(removeButton)) {
          const path2 = this.normalizeVariablePath(removeButton.getAttribute("data-path"));
          if (!path2) return;
          const success = this.removePinnedPath(path2);
          if (!success) {
            setStatus("Could not remove pin from storage.", "error");
            return;
          }
          renderPinnedList();
          updatePinButton();
          updateVisibleResultPinBadges();
          setStatus(`Unpinned $${path2}`, "success");
          return;
        }
        const selectButton = event.target?.closest?.(".sc-var-editor-pin-select");
        if (!selectButton || !pinsList.contains(selectButton)) return;
        const path = this.normalizeVariablePath(selectButton.getAttribute("data-path"));
        if (!path) return;
        this.selectVariable(path);
        setStatus("");
      });
      this.editorUi = {
        setStatus,
        updatePinButton,
        renderPinnedList,
        updateVisibleResultPinBadges
      };
      renderPinnedList();
      updatePinButton();
      if (variableEditor_default.selectedVar) {
        this.selectVariable(variableEditor_default.selectedVar);
      }
    }
    selectVariable(path) {
      if (!this.shadowRoot) return;
      const normalizedPath = this.normalizeVariablePath(path);
      if (!normalizedPath) return;
      variableEditor_default.selectedVar = normalizedPath;
      const pathDisplay = this.shadowRoot.getElementById("sc-var-editor-path-display");
      const currentDisplay = this.shadowRoot.getElementById("sc-var-editor-current-display");
      const valueInput = this.shadowRoot.getElementById("sc-var-editor-input");
      const selectedDiv = this.shadowRoot.getElementById("sc-var-editor-selected");
      const resultsContainer = this.shadowRoot.getElementById("sc-var-editor-results");
      const fullPath = `$${normalizedPath}`;
      const currentValue = variableEditor_default.getVariable(normalizedPath);
      const formattedValue = variableEditor_default.formatValue(currentValue);
      if (pathDisplay) {
        pathDisplay.textContent = `Path: ${fullPath}`;
      }
      if (currentDisplay) {
        currentDisplay.textContent = `Current Value:
${formattedValue}`;
      }
      if (valueInput) {
        valueInput.value = formattedValue;
        valueInput.focus();
      }
      if (selectedDiv) {
        selectedDiv.removeAttribute("hidden");
      }
      if (resultsContainer) {
        resultsContainer.querySelectorAll(".sc-var-editor-result-item").forEach((item) => {
          const isCurrent = item.getAttribute("data-path") === normalizedPath;
          item.classList.toggle("is-selected", isCurrent);
        });
      }
      this.editorUi?.renderPinnedList?.();
      this.editorUi?.updatePinButton?.();
      this.editorUi?.updateVisibleResultPinBadges?.();
    }
    normalizeVariablePath(path) {
      const normalized = String(path || "").trim().replace(/^\$/, "");
      if (!normalized) return "";
      if (normalized === this.debuggerMetaVariableKey) return "";
      if (normalized.startsWith(`${this.debuggerMetaVariableKey}.`)) return "";
      if (normalized.startsWith(`${this.debuggerMetaVariableKey}[`)) return "";
      return normalized;
    }
    getStateVariablesRoot() {
      const root = SugarCube?.State?.variables;
      if (!root || typeof root !== "object") return null;
      return root;
    }
    getDebuggerMetaStore({ create = true } = {}) {
      const root = this.getStateVariablesRoot();
      if (!root) return null;
      let meta = root[this.debuggerMetaVariableKey];
      if (!meta || typeof meta !== "object" || Array.isArray(meta)) {
        if (!create) return null;
        meta = {
          version: 1,
          pins: [],
          updatedAt: 0
        };
        root[this.debuggerMetaVariableKey] = meta;
      }
      if (!Array.isArray(meta.pins)) {
        meta.pins = [];
      }
      if (!Number.isFinite(meta.updatedAt)) {
        meta.updatedAt = 0;
      }
      meta.pins = this.sanitizePinnedPaths(meta.pins);
      return meta;
    }
    getMetadataStoreApi() {
      const state = this.resolveStateObject();
      const metadata = state?.metadata;
      if (!metadata) return null;
      if (typeof metadata.get !== "function" || typeof metadata.set !== "function") {
        return null;
      }
      return metadata;
    }
    readPinnedPathsFromMetadata() {
      const metadata = this.getMetadataStoreApi();
      if (!metadata) return null;
      try {
        const raw = metadata.get(this.debuggerMetaPersistentKey);
        if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
        const pins = this.sanitizePinnedPaths(raw.pins);
        const updatedAt = Number.isFinite(raw.updatedAt) ? raw.updatedAt : 0;
        return { pins, updatedAt };
      } catch {
        return null;
      }
    }
    writePinnedPathsToMetadata(paths, updatedAt) {
      const metadata = this.getMetadataStoreApi();
      if (!metadata) return false;
      try {
        metadata.set(this.debuggerMetaPersistentKey, {
          version: 1,
          pins: this.sanitizePinnedPaths(paths),
          updatedAt: Number.isFinite(updatedAt) ? updatedAt : Date.now()
        });
        return true;
      } catch {
        return false;
      }
    }
    sanitizePinnedPaths(paths) {
      if (!Array.isArray(paths)) return [];
      const seen = /* @__PURE__ */ new Set();
      const cleaned = [];
      for (const rawPath of paths) {
        const path = this.normalizeVariablePath(rawPath);
        if (!path) continue;
        if (seen.has(path)) continue;
        seen.add(path);
        cleaned.push(path);
        if (cleaned.length >= 250) break;
      }
      return cleaned;
    }
    getPinnedPaths() {
      const meta = this.getDebuggerMetaStore({ create: false });
      const currentPins = Array.isArray(meta?.pins) ? this.sanitizePinnedPaths(meta.pins) : [];
      const currentUpdatedAt = Number.isFinite(meta?.updatedAt) ? meta.updatedAt : 0;
      const persistent = this.readPinnedPathsFromMetadata();
      if (persistent && persistent.updatedAt > currentUpdatedAt) {
        const synced = this.setPinnedPaths(persistent.pins, {
          updatedAt: persistent.updatedAt,
          persistMetadata: false
        });
        if (Array.isArray(synced)) return synced;
      }
      if (persistent && currentUpdatedAt > persistent.updatedAt) {
        this.writePinnedPathsToMetadata(currentPins, currentUpdatedAt);
      }
      return [...currentPins];
    }
    setPinnedPaths(paths, { updatedAt = Date.now(), persistMetadata = true } = {}) {
      const meta = this.getDebuggerMetaStore({ create: true });
      if (!meta) return null;
      meta.pins = this.sanitizePinnedPaths(paths);
      meta.updatedAt = Number.isFinite(updatedAt) ? updatedAt : Date.now();
      if (persistMetadata) {
        this.writePinnedPathsToMetadata(meta.pins, meta.updatedAt);
      }
      variableEditor_default.invalidateCache();
      return [...meta.pins];
    }
    isPathPinned(path) {
      const normalizedPath = this.normalizeVariablePath(path);
      if (!normalizedPath) return false;
      return this.getPinnedPaths().includes(normalizedPath);
    }
    addPinnedPath(path) {
      const normalizedPath = this.normalizeVariablePath(path);
      if (!normalizedPath) return false;
      const pinned = this.getPinnedPaths();
      if (pinned.includes(normalizedPath)) return true;
      pinned.push(normalizedPath);
      const next = this.setPinnedPaths(pinned);
      return Array.isArray(next) && next.includes(normalizedPath);
    }
    removePinnedPath(path) {
      const normalizedPath = this.normalizeVariablePath(path);
      if (!normalizedPath) return false;
      const pinned = this.getPinnedPaths();
      const next = pinned.filter((item) => item !== normalizedPath);
      if (next.length === pinned.length) return false;
      const updated = this.setPinnedPaths(next);
      return Array.isArray(updated);
    }
    togglePinnedPath(path) {
      const normalizedPath = this.normalizeVariablePath(path);
      if (!normalizedPath) {
        return { pinned: false, success: false };
      }
      if (this.isPathPinned(normalizedPath)) {
        const success2 = this.removePinnedPath(normalizedPath);
        return { pinned: false, success: success2 };
      }
      const success = this.addPinnedPath(normalizedPath);
      return { pinned: true, success };
    }
    bindLinksExplorerEvents() {
      if (!this.shadowRoot) return;
      const searchInput = this.shadowRoot.getElementById("sc-links-search");
      const rowsContainer = this.shadowRoot.getElementById("sc-links-search-results");
      const countLabel = this.shadowRoot.getElementById("sc-links-search-count");
      const emptyLabel = this.shadowRoot.getElementById("sc-links-search-empty");
      const filterButtons = Array.from(
        this.shadowRoot.querySelectorAll("[data-sc-link-filter]")
      );
      if (!searchInput || !rowsContainer) return;
      const rows = Array.from(rowsContainer.querySelectorAll(".sc-debugger-link"));
      const total = rows.length;
      let activeFilter = "all";
      const updateCount = (shown, query) => {
        if (!countLabel) return;
        const filterLabel = activeFilter === "all" ? "all" : activeFilter;
        const queryLabel = query ? ` and search "${query}"` : "";
        countLabel.textContent = `Showing ${shown} of ${total} links (filter: ${filterLabel}${queryLabel}).`;
      };
      const matchesActiveFilter = (row) => {
        if (activeFilter === "all") return true;
        if (activeFilter === "live" || activeFilter === "hidden") {
          return row.getAttribute("data-sc-visibility") === activeFilter;
        }
        if (activeFilter === "macro" || activeFilter === "link") {
          return row.getAttribute("data-sc-kind") === activeFilter;
        }
        return true;
      };
      const applyFilter = () => {
        const query = String(searchInput.value || "").trim().toLowerCase();
        let shown = 0;
        rows.forEach((row) => {
          const haystack = (row.getAttribute("data-sc-search") || row.textContent || "").toLowerCase();
          const queryMatch = !query || haystack.includes(query);
          const chipMatch = matchesActiveFilter(row);
          const visible = queryMatch && chipMatch;
          row.hidden = !visible;
          if (visible) shown += 1;
        });
        if (emptyLabel) {
          emptyLabel.hidden = shown !== 0;
        }
        updateCount(shown, query);
      };
      searchInput.addEventListener("input", applyFilter);
      searchInput.addEventListener("keydown", (event) => {
        if (event.key !== "Escape") return;
        if (!searchInput.value) return;
        searchInput.value = "";
        applyFilter();
      });
      filterButtons.forEach((button) => {
        button.addEventListener("click", () => {
          const nextFilter = String(button.getAttribute("data-sc-link-filter") || "all").trim().toLowerCase();
          activeFilter = nextFilter || "all";
          filterButtons.forEach((candidate) => {
            candidate.classList.toggle("is-active", candidate === button);
          });
          applyFilter();
        });
      });
      applyFilter();
    }
    resolveEngineObject() {
      return typeof Engine !== "undefined" && Engine || typeof SugarCube !== "undefined" && SugarCube.Engine || null;
    }
    resolveStateObject() {
      return typeof State !== "undefined" && State || typeof SugarCube !== "undefined" && SugarCube.State || null;
    }
    resolveHistoryConfigObject() {
      const root = typeof Config !== "undefined" && Config || typeof SugarCube !== "undefined" && SugarCube.Config || null;
      return root?.history || null;
    }
    sanitizeHistoryMaxStates(value) {
      const parsed = Number.parseInt(String(value ?? ""), 10);
      if (!Number.isFinite(parsed)) return 120;
      return Math.min(Math.max(parsed, 2), 2e3);
    }
    captureOriginalHistoryConfig(historyConfig) {
      if (!historyConfig || this.originalHistoryConfig) return;
      this.originalHistoryConfig = {
        controls: historyConfig.controls,
        maxStates: historyConfig.maxStates
      };
    }
    restoreOriginalHistoryConfig(historyConfig) {
      if (!historyConfig || !this.originalHistoryConfig) return;
      try {
        historyConfig.controls = this.originalHistoryConfig.controls;
      } catch {
      }
      try {
        historyConfig.maxStates = this.originalHistoryConfig.maxStates;
      } catch {
      }
    }
    unpatchEngineHistoryMethods() {
      const patch = this.engineHistoryPatch;
      if (!patch?.engine) {
        this.engineHistoryPatch = null;
        return;
      }
      const {
        engine,
        originalBackward,
        originalForward,
        patchedBackward,
        patchedForward,
        hadBackward,
        hadForward
      } = patch;
      if (engine.backward === patchedBackward) {
        try {
          if (hadBackward) {
            engine.backward = originalBackward;
          } else {
            delete engine.backward;
          }
        } catch {
        }
      }
      if (engine.forward === patchedForward) {
        try {
          if (hadForward) {
            engine.forward = originalForward;
          } else {
            delete engine.forward;
          }
        } catch {
        }
      }
      this.engineHistoryPatch = null;
    }
    patchEngineHistoryMethods(engine) {
      if (!engine) return;
      if (this.engineHistoryPatch?.engine === engine) return;
      this.unpatchEngineHistoryMethods();
      const hadBackward = typeof engine.backward === "function";
      const hadForward = typeof engine.forward === "function";
      const originalBackward = hadBackward ? engine.backward : null;
      const originalForward = hadForward ? engine.forward : null;
      const tryOffset = (offset) => {
        if (typeof engine.go === "function") {
          try {
            const goResult = engine.go(offset);
            if (goResult !== false) return true;
          } catch {
          }
        }
        const state = this.resolveStateObject();
        if (state && typeof state.go === "function") {
          try {
            const goResult = state.go(offset);
            if (goResult !== false) return true;
          } catch {
          }
        }
        return false;
      };
      const patchedBackward = (...args) => {
        if (typeof originalBackward === "function") {
          try {
            const result = originalBackward.apply(engine, args);
            if (result !== false) return result;
          } catch {
          }
        }
        return tryOffset(-1);
      };
      const patchedForward = (...args) => {
        if (typeof originalForward === "function") {
          try {
            const result = originalForward.apply(engine, args);
            if (result !== false) return result;
          } catch {
          }
        }
        return tryOffset(1);
      };
      try {
        engine.backward = patchedBackward;
        engine.forward = patchedForward;
        this.engineHistoryPatch = {
          engine,
          originalBackward,
          originalForward,
          patchedBackward,
          patchedForward,
          hadBackward,
          hadForward
        };
      } catch (error) {
        debugWarn("Could not patch engine history methods:", error);
        this.engineHistoryPatch = null;
      }
    }
    applyHistoryOverrides() {
      const historyConfig = this.resolveHistoryConfigObject();
      const forceHistory = Boolean(config_default.getSetting("forceHistory"));
      const maxStates = this.sanitizeHistoryMaxStates(config_default.getSetting("historyMaxStates"));
      if (historyConfig) {
        this.captureOriginalHistoryConfig(historyConfig);
        if (forceHistory) {
          try {
            historyConfig.controls = true;
          } catch {
          }
          try {
            historyConfig.maxStates = maxStates;
          } catch (error) {
            debugWarn("Could not force Config.history.maxStates:", error);
          }
        } else {
          this.restoreOriginalHistoryConfig(historyConfig);
        }
      }
      const engine = this.resolveEngineObject();
      if (forceHistory && engine) {
        this.patchEngineHistoryMethods(engine);
      } else {
        this.unpatchEngineHistoryMethods();
      }
    }
    applySettings() {
      setDebugLoggingEnabled(Boolean(config_default.getSetting("debugLogging")));
      this.applyHistoryOverrides();
      applyLinkUnderlines(config_default);
      captureClickEvents();
      wireModalLinkNavigation(this.shadowRoot);
      this.updateNavControls();
    }
    updateNavControls() {
      if (!this.shadowRoot) return;
      const showNavControls = Boolean(config_default.getSetting("showNavControls"));
      const forceHistory = Boolean(config_default.getSetting("forceHistory"));
      const backBtn = this.shadowRoot.getElementById("sc-debugger-back-btn");
      const forwardBtn = this.shadowRoot.getElementById("sc-debugger-forward-btn");
      const state = this.resolveStateObject();
      const canGoBackward = typeof state?.length === "number" ? state.length >= 1 : true;
      const canGoForward = typeof state?.length === "number" && typeof state?.size === "number" ? state.length < state.size : true;
      if (backBtn) {
        backBtn.hidden = !showNavControls;
        backBtn.disabled = !forceHistory && !canGoBackward;
      }
      if (forwardBtn) {
        forwardBtn.hidden = !showNavControls;
        forwardBtn.disabled = !forceHistory && !canGoForward;
      }
    }
    navigateHistory(direction) {
      this.applyHistoryOverrides();
      const engine = this.resolveEngineObject();
      if (!engine) {
        debugWarn("SugarCube engine not found for history navigation.");
        return;
      }
      const directionOffset = direction === "backward" ? -1 : 1;
      if (direction === "backward" && typeof engine.backward === "function") {
        const result = engine.backward();
        if (result !== false) return;
      }
      if (direction === "forward" && typeof engine.forward === "function") {
        const result = engine.forward();
        if (result !== false) return;
      }
      if (typeof engine.go === "function") {
        const result = engine.go(directionOffset);
        if (result !== false) return;
      }
      const state = this.resolveStateObject();
      if (state && typeof state.go === "function") {
        const result = state.go(directionOffset);
        if (result !== false) return;
      }
      debugWarn(`History ${direction} navigation failed (no available moment or story blocked it).`);
    }
    cleanup() {
      this.shadowRoot?.getElementById("sc-debugger-toggle-btn")?.classList.remove("active");
    }
    destroy() {
      this.cleanup();
      this.modal?.destroy();
      this.shadowRoot?.getElementById("sc-debugger-controls")?.remove();
      this.isInitialized = false;
    }
  };
  function boot() {
    const scDebugger = new SugarCubeDebugger();
    setDebugLoggingEnabled(Boolean(config_default.getSetting("debugLogging")));
    const pageWindow = typeof unsafeWindow !== "undefined" && unsafeWindow ? unsafeWindow : globalThis;
    const getSugarCubeRef = () => globalThis?.SugarCube || pageWindow?.SugarCube || null;
    const hasStateVariables = () => Boolean(getSugarCubeRef()?.State?.variables);
    let observer = null;
    const bootTrace = [];
    const pushBootTrace = (stage, data = {}) => {
      const entry = {
        stage,
        at: (/* @__PURE__ */ new Date()).toISOString(),
        ...data
      };
      bootTrace.push(entry);
      try {
        globalThis.__SCDBG_BOOT_TRACE = bootTrace;
      } catch {
      }
      try {
        console.log("[SCDBG][boot]", stage, data);
      } catch {
      }
      debugInfo("[SCDBG][boot]", stage, data);
    };
    const pushBootWarn = (stage, data = {}) => {
      const entry = {
        stage: `WARN:${stage}`,
        at: (/* @__PURE__ */ new Date()).toISOString(),
        ...data
      };
      bootTrace.push(entry);
      try {
        globalThis.__SCDBG_BOOT_TRACE = bootTrace;
      } catch {
      }
      try {
        console.warn("[SCDBG][boot]", stage, data);
      } catch {
      }
      debugWarn("[SCDBG][boot]", stage, data);
    };
    const bridgeSugarCubeGlobals = () => {
      const sugar = getSugarCubeRef();
      if (!sugar) return false;
      try {
        if (!globalThis.SugarCube) {
          globalThis.SugarCube = sugar;
        }
      } catch {
      }
      try {
        if (typeof globalThis.State === "undefined" && sugar.State) {
          globalThis.State = sugar.State;
        }
      } catch {
      }
      try {
        if (typeof globalThis.Engine === "undefined" && sugar.Engine) {
          globalThis.Engine = sugar.Engine;
        }
      } catch {
      }
      try {
        if (typeof globalThis.Config === "undefined" && sugar.Config) {
          globalThis.Config = sugar.Config;
        }
      } catch {
      }
      return true;
    };
    pushBootTrace("boot-start", {
      readyState: document.readyState,
      hasJquery: Boolean(window.jQuery),
      hasSugarCube: Boolean(getSugarCubeRef()),
      hasState: Boolean(getSugarCubeRef()?.State),
      hasVariables: hasStateVariables(),
      debugLoggingSetting: Boolean(config_default.getSetting("debugLogging")),
      usingUnsafeWindow: pageWindow !== globalThis
    });
    const cleanupObserver = () => {
      if (!observer) return;
      observer.disconnect();
      observer = null;
    };
    const waitForCondition = (check, { intervalMs = 50, timeoutMs = 45e3 } = {}) => new Promise((resolve, reject) => {
      if (check()) {
        resolve();
        return;
      }
      const startedAt = Date.now();
      const timer = window.setInterval(() => {
        if (check()) {
          window.clearInterval(timer);
          resolve();
          return;
        }
        if (Date.now() - startedAt > timeoutMs) {
          window.clearInterval(timer);
          reject(new Error("SugarCube.State.variables wait timed out."));
        }
      }, intervalMs);
    });
    const initDebugger = () => {
      if (scDebugger.isInitialized) {
        pushBootTrace("init-skipped-already-initialized");
        return true;
      }
      if (!hasStateVariables()) {
        return false;
      }
      try {
        bridgeSugarCubeGlobals();
        pushBootTrace("init-attempt");
        scDebugger.init();
        scDebugger.applyHistoryOverrides();
        pushBootTrace("init-success");
        return true;
      } catch (error) {
        pushBootWarn("init-failed", { message: error?.message || String(error) });
        debugWarn("SugarCubeDebugger initialization failed:", error);
        return false;
      }
    };
    const ensureStarted = async () => {
      pushBootTrace("ensure-started-begin");
      if (initDebugger()) return;
      try {
        pushBootTrace("wait-start", { target: "SugarCube.State.variables" });
        await waitForCondition(hasStateVariables, { intervalMs: 60, timeoutMs: 45e3 });
        pushBootTrace("wait-complete", { target: "SugarCube.State.variables" });
        initDebugger();
      } catch (error) {
        pushBootWarn("wait-timeout", {
          message: error?.message || "SugarCube debugger prerequisites timed out.",
          hasSugarCube: Boolean(getSugarCubeRef()),
          hasState: Boolean(getSugarCubeRef()?.State),
          hasVariables: hasStateVariables()
        });
        debugWarn(error?.message || "SugarCube debugger prerequisites timed out.");
      } finally {
        cleanupObserver();
      }
    };
    if (!hasStateVariables()) {
      pushBootTrace("observer-attach");
      observer = new MutationObserver(() => {
        if (!hasStateVariables()) return;
        pushBootTrace("observer-detected-variables");
        initDebugger();
        cleanupObserver();
      });
      observer.observe(document.documentElement, { childList: true, subtree: true });
      window.setTimeout(cleanupObserver, 45e3);
    }
    if (typeof window.jQuery !== "undefined") {
      window.jQuery(document).one(":storyready", () => {
        pushBootTrace("storyready-fired");
        initDebugger();
        cleanupObserver();
      });
    }
    if (document.readyState === "loading") {
      document.addEventListener("DOMContentLoaded", ensureStarted, { once: true });
    } else {
      ensureStarted();
    }
    window.sugarcubeDebugger = scDebugger;
  }
  boot();
})();