Cube Engine Uptaded New Options

The ultimate enhancement for your Drawaria.online experience. Redefining possibilities!

// ==UserScript==
// @name         Cube Engine Uptaded New Options
// @version      10.0.0
// @description  The ultimate enhancement for your Drawaria.online experience. Redefining possibilities!
// @namespace    drawaria.modded.fullspec
// @homepage     https://drawaria.online/profile/?uid=63196790-c7da-11ec-8266-c399f90709b7
// @author       ≺ᴄᴜʙᴇ³≻ And YouTubeDrawaria
// @match        https://drawaria.online/
// @match        https://drawaria.online/test
// @match        https://drawaria.online/room/*
// @icon         https://drawaria.online/avatar/cache/e53693c0-18b1-11ec-b633-b7649fa52d3f.jpg
// @grant        none
// @license      GNU GPLv3
// @run-at       document-end
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js
// ==/UserScript==

(function () {
  (function CodeMaid(callback) {
    class TypeChecker {
      constructor() {}
      isArray(value) {
        return this.isA("Array", value);
      }
      isObject(value) {
        return !this.isUndefined(value) && value !== null && this.isA("Object", value);
      }
      isString(value) {
        return this.isA("String", value);
      }
      isNumber(value) {
        return this.isA("Number", value);
      }
      isFunction(value) {
        return this.isA("Function", value);
      }
      isAsyncFunction(value) {
        return this.isA("AsyncFunction", value);
      }
      isGeneratorFunction(value) {
        return this.isA("GeneratorFunction", value);
      }
      isTypedArray(value) {
        return (
          this.isA("Float32Array", value) ||
          this.isA("Float64Array", value) ||
          this.isA("Int16Array", value) ||
          this.isA("Int32Array", value) ||
          this.isA("Int8Array", value) ||
          this.isA("Uint16Array", value) ||
          this.isA("Uint32Array", value) ||
          this.isA("Uint8Array", value) ||
          this.isA("Uint8ClampedArray", value)
        );
      }
      isA(typeName, value) {
        return this.getType(value) === "[object " + typeName + "]";
      }
      isError(value) {
        if (!value) {
          return false;
        }

        if (value instanceof Error) {
          return true;
        }

        return typeof value.stack === "string" && typeof value.message === "string";
      }
      isUndefined(obj) {
        return obj === void 0;
      }
      getType(value) {
        return Object.prototype.toString.apply(value);
      }
    }

    class DOMCreate {
      #validate;
      constructor() {
        this.#validate = new TypeChecker();
      }
      exportNodeTree(node = document.createElement("div")) {
        let referenceTolocalThis = this;

        let json = {
          nodeName: node.nodeName,
          attributes: {},
          children: [],
        };

        Array.from(node.attributes).forEach(function (attribute) {
          json.attributes[attribute.name] = attribute.value;
        });

        if (node.children.length <= 0) {
          json.children.push(node.textContent.replaceAll("\t", ""));
          return json;
        }

        Array.from(node.children).forEach(function (childNode) {
          json.children.push(referenceTolocalThis.exportNodeTree(childNode));
        });

        return json;
      }

      importNodeTree(json = { nodeName: "", attributes: {}, children: [] }) {
        let referenceTolocalThis = this;

        if (referenceTolocalThis.#validate.isString(json)) {
          return this.TextNode(json);
        }

        let node = this.Tree(json.nodeName, json.attributes);

        json.children.forEach(function (child) {
          node.appendChild(referenceTolocalThis.importNodeTree(child));
        });

        return node;
      }

      Element() {
        return document.createElement.apply(document, arguments);
      }
      TextNode() {
        return document.createTextNode.apply(document, arguments);
      }
      Tree(type, attrs, childrenArrayOrVarArgs) {
        const el = this.Element(type);
        let children;
        if (this.#validate.isArray(childrenArrayOrVarArgs)) {
          children = childrenArrayOrVarArgs;
        } else {
          children = [];

          for (let i = 2; i < arguments.length; i++) {
            children.push(arguments[i]);
          }
        }

        for (let i = 0; i < children.length; i++) {
          const child = children[i];

          if (typeof child === "string") {
            el.appendChild(this.TextNode(child));
          } else {
            if (child) {
              el.appendChild(child);
            }
          }
        }
        for (const attr in attrs) {
          if (attr == "className") {
            el[attr] = attrs[attr];
          } else {
            el.setAttribute(attr, attrs[attr]);
          }
        }

        el.appendAll = function (...nodes) {
          nodes.forEach((node) => {
            el.appendChild(node);
          });
        };

        return el;
      }
    }

    class CookieManager {
      constructor() {}
      set(name, value = "") {
        document.cookie =
          name + "=" + value + "; expires=" + new Date("01/01/2024").toUTCString().replace("GMT", "UTC") + "; path=/";
      }
      get(name) {
        var nameEQ = name + "=";
        var ca = document.cookie.split(";");
        for (var i = 0; i < ca.length; i++) {
          var c = ca[i];
          while (c.charAt(0) == " ") c = c.substring(1, c.length);
          if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
        }
        return null;
      }
      clear(name) {
        document.cookie = name + "=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;";
      }
    }

    class DocumentCleaner {
      document;
      constructor() {
        this.document = new DOMCreate();
      }
      scripts(remove = true) {
        try {
          let array = document.querySelectorAll('script[src]:not([data-codemaid="ignore"])');
          array.forEach((script) => {
            if (script.src != "") document.head.appendChild(script);
          });
        } catch (error) {
          console.error(error);
        }

        try {
          let unifiedScript = this.document.Tree("script");

          let scripts = document.querySelectorAll('script:not([src]):not([data-codemaid="ignore"])');
          let unifiedScriptContent = "";
          scripts.forEach((script) => {
            let content = script.textContent; //.replaceAll(/\s/g, '');

            unifiedScriptContent += `try{${content}}catch(e){console.warn(e);}`;
            script.remove();
          });

          unifiedScript.textContent = unifiedScriptContent;

          if (!remove) document.head.appendChild(unifiedScript);
        } catch (error) {
          console.error(error);
        }
      }
      styles(remove = false) {
        try {
          let unifiedStyles = this.document.Tree("style");
          unifiedStyles.textContet = "";

          let styles = document.querySelectorAll('style:not([data-codemaid="ignore"])');
          styles.forEach((style) => {
            unifiedStyles.textContent += style.textContent;
            style.remove();
          });
          if (!remove) document.head.appendChild(unifiedStyles);
        } catch (error) {
          console.error(error);
        }
      }
      embeds() {
        try {
          let array = document.querySelectorAll("iframe");
          array.forEach((iframe) => {
            iframe.remove();
          });
        } catch (error) {
          console.error(error);
        }
      }
    }

    class CustomGenerator {
      constructor() {}
      uuidv4() {
        return crypto.randomUUID();
      }
    }

    globalThis.typecheck = new TypeChecker();
    globalThis.cookies = new CookieManager();
    globalThis.domMake = new DOMCreate();
    globalThis.domClear = new DocumentCleaner();
    globalThis.generate = new CustomGenerator();

    if (window.location.pathname === "/") window.location.assign("/test");
  })();

  (function CubicEngine() {
    domMake.Button = function (content) {
      let btn = domMake.Tree("button", { class: "btn btn-outline-secondary" });
      btn.innerHTML = content;
      return btn;
    };
    domMake.Row = function () {
      return domMake.Tree("div", { class: "_row" });
    };
    domMake.IconList = function () {
      return domMake.Tree("div", { class: "icon-list" });
    };

    const sockets = [];
    const originalSend = WebSocket.prototype.send;
    WebSocket.prototype.send = function (...args) {
      let socket = this;
      if (sockets.indexOf(socket) === -1) {
        sockets.push(socket);
      }
      socket.addEventListener("close", function () {
        const pos = sockets.indexOf(socket);
        if (~pos) sockets.splice(pos, 1);
      });
      return originalSend.call(socket, ...args);
    };

    const identifier = "🧊";

    class Stylizer {
      constructor() {
        this.element = domMake.Tree("style", { "data-codemaid": "ignore" }, []);
        document.head.appendChild(this.element);
        this.initialize();
      }

      initialize() {
        this.addRules([
          `body * {margin: 0; padding: 0; box-sizing: border-box; line-height: normal;}`,
          `#${identifier} {--CE-bg_color: var(--light); --CE-color: var(--dark); line-height: 2rem; font-size: 1rem;}`,
          `#${identifier}>details {position:relative; overflow:visible; z-index: 999; background-color: var(--CE-bg_color); border: var(--CE-color) 1px solid; border-radius: .25rem;}`,
          `#${identifier} details>summary::marker {content:"📘";}`,
          `#${identifier} details[open]>summary::marker {content:"📖";}`,
          `#${identifier} details details {margin: 1px 0; border-top: var(--CE-color) 1px solid;}`,
          `#${identifier} input.toggle[name][hidden]:not(:checked) + * {display: none !important;}`,
          `#${identifier} header>.icon {margin: 1px;}`,
          `#${identifier} header>.icon.active {color: var(--success);}`,
          `#${identifier} header>.icon:not(.active) {color:var(--danger); opacity:.6;}`,
          `#${identifier} header:not(:has([title='Unselect'] + *)) > [title='Unselect'] {display:none;}`,
          `#${identifier} .btn {padding: 0;}`,
          `#${identifier} .icon-list {display: flex; flex-flow: wrap;}`,
          `#${identifier} .nowrap {overflow-x: scroll; padding-bottom: 12px; flex-flow: nowrap;}`,
          `#${identifier} .icon {display: flex; flex: 0 0 auto; max-width: 1.6rem; min-width: 1.6rem; height: 1.6rem; border-radius: .25rem; border: 1px solid var(--CE-color); aspect-ratio: 1/1;}`,
          `#${identifier} .icon > * {margin: auto; text-align: center; max-height: 100%; max-width: 100%;}`,
          `#${identifier} .itext {text-align: center; -webkit-appearance: none; -moz-appearance: textfield;}`,
          `#${identifier} ._row {display: flex; width: 100%;}`,
          `#${identifier} ._row > * {width: 100%;}`,
          `hr {margin: 5px 0;}`,
          `.playerlist-row::after {content: attr(data-playerid); position: relative; float: right; top: -20px;}`,
          `[hidden] {display: none !important;}`,
          `.noselect {-webkit-touch-callout: none; -webkit-user-select: none; -moz-user-select: none; user-select: none;}`,
        ]);
      }

      addRules(rules = []) {
        let reference = this;
        rules.forEach(function (rule) {
          reference.addRule(rule);
        });
      }
      addRule(rule) {
        let TextNode = domMake.TextNode(rule);
        this.element.appendChild(TextNode);
      }
    }

    class ModBase {
      static globalListOfExtensions = [];
      static localListOfExtensions = [];
      static Styles = new Stylizer();

      static register = function (extension) {
        extension.localListOfExtensions = [];
        ModBase.globalListOfExtensions.push(extension);
        return ModBase;
      };
      static bind = function (extension, target) {
        let parent;
        if (typecheck.isFunction(target)) parent = target;
        else if (typecheck.isString(target))
          parent = ModBase.globalListOfExtensions.find((entry) => entry.name === target);
        else if (typecheck.isObject(target)) parent = target.constructor;
        else {
          console.log(typecheck.getType(target));
        }
        if (!parent) return new Error(`${parent}`);

        parent.localListOfExtensions.push(extension);
        parent.autostart = true;

        return parent;
      };
      static findGlobal = function (extensionName) {
        return ModBase.globalListOfExtensions.find((entry) => entry.name === extensionName);
      };

      #id;
      #name;
      #icon;

      htmlElements;
      children;
      parent;

      constructor(name, icon) {
        this.#id = generate.uuidv4();
        this.#name = this.constructor.name;
        this.#icon = "📦";
        this.children = [];
        this.htmlElements = {};

        this.#onStartup();

        this.setName(name || this.#name);
        this.setIcon(icon || this.#icon);
      }

      #onStartup() {
        this.#loadInterface();

        if (this.constructor.autostart)
          this.constructor.localListOfExtensions.forEach((extension) => {
            this.loadExtension(extension);
          });
      }

      #loadInterface() {
        this.htmlElements.details = domMake.Tree("details", {
          class: "noselect",
          open: false, // Changed from true to false to make it closed by default
          "data-reference": this.constructor.name,
        });
        this.htmlElements.summary = domMake.Tree("summary");
        this.htmlElements.header = domMake.Tree("header", { class: "icon-list" });
        this.htmlElements.section = domMake.Tree("section");
        this.htmlElements.children = domMake.Tree("section");

        this.htmlElements.details.appendChild(this.htmlElements.summary);
        this.htmlElements.details.appendChild(this.htmlElements.header);
        this.htmlElements.details.appendChild(this.htmlElements.section);
        this.htmlElements.details.appendChild(this.htmlElements.children);

        this.htmlElements.input = domMake.Tree(
          "input",
          { type: "radio", id: this.#id, name: "QBit", class: "toggle", hidden: true, title: this.#name },
          [this.#name]
        );
        this.htmlElements.label = domMake.Tree("label", { for: this.#id, class: "icon" });

        {
          const input = this.htmlElements.input;
          const label = this.htmlElements.label;

          input.addEventListener("change", (event) => {
            this.parent?.children.forEach((child) => {
              child.htmlElements.label.classList.remove("active");
            });

            label.classList[input.checked ? "add" : "remove"]("active");
          });

          label.classList[input.checked ? "add" : "remove"]("active");
        }

      }

      loadExtension(extension, referenceHandler) {
        let activeExtension = new extension();
        activeExtension.parent = this;

        activeExtension.htmlElements.input.name = this.getName();

        if (referenceHandler) referenceHandler(activeExtension);
        else this.children.push(activeExtension);

        if (!extension.siblings) extension.siblings = [];
        extension.siblings.push(activeExtension);

        if (extension.isFavorite) {
          activeExtension.htmlElements.input.click();
          if (activeExtension.enable) activeExtension.enable();
        }

        this.htmlElements.header.appendChild(activeExtension.htmlElements.label);
        this.htmlElements.children.appendChild(activeExtension.htmlElements.input);
        this.htmlElements.children.appendChild(activeExtension.htmlElements.details);

        return activeExtension;
      }

      notify(level, message) {
        if (typeof message != "string") {
          try {
            message = JSON.stringify(message);
          } catch (error) {
            throw error;
          }
        }

        let color = "";
        if ([5, "error"].includes(level)) {
          color = "#dc3545";
        } else if ([4, "warning"].includes(level)) {
          color = "#ffc107";
        } else if ([3, "info"].includes(level)) {
          color = "#17a2b8";
        } else if ([2, "success"].includes(level)) {
          color = "#28a745";
        } else if ([1, "log"].includes(level)) {
          color = "#6c757d";
        } else if ([0, "debug"].includes(level)) {
          color = "purple";
        }

        console.log(`%c${this.#name}: ${message}`, `color: ${color}`);
        let chatmessage = domMake.Tree(
          "div",
          { class: `chatmessage systemchatmessage5`, "data-ts": Date.now(), style: `color: ${color}` },
          [`${this.#name}: ${message}`]
        );

        let loggingContainer = document.getElementById("chatbox_messages");
        if (!loggingContainer) loggingContainer = document.body;
        loggingContainer.appendChild(chatmessage);
      }

      findGlobal(extensionName) {
        return this.referenceToBase.findGlobal(extensionName);
      }

      findLocal(extensionName) {
        return this.children.filter((child) => child.constructor.name === extensionName);
      }

      setName(name) {
        if (!name) return;

        this.#name = name;
        this.htmlElements.label.title = name;

        this.htmlElements.summary.childNodes.forEach((child) => child.remove());

        if (typecheck.isString(name)) {
          if (name.startsWith("<")) return (this.htmlElements.summary.innerHTML = name);
          name = domMake.TextNode(name);
        }

        this.htmlElements.summary.appendChild(name);
      }

      getName() {
        return this.#name;
      }

      setIcon(icon) {
        if (!icon) return;

        this.#icon = icon;

        this.htmlElements.label.childNodes.forEach((child) => child.remove());

        if (typecheck.isString(icon)) {
          if (icon.startsWith("<")) return (this.htmlElements.label.innerHTML = icon);
          icon = domMake.TextNode(icon);
        }

        this.htmlElements.label.appendChild(icon);
      }

      getIcon() {
        return this.#icon;
      }

      get referenceToBase() {
        return this.constructor.dummy1;
      }
      get referenceToMaster() {
        return this.constructor.dummy2;
      }

      _EXP_destroy(youSure = false) {
        if (!youSure) return;

        this.children.forEach((child) => {
          child._EXP_destroy(youSure);
          delete [child];
        });
        this.children = null;

        let pos = this.parent.children.indexOf(this);
        if (~pos) {
          this.parent.children.splice(pos, 1);
        }

        this.htmlElements.children.remove();
        this.htmlElements.section.remove();
        this.htmlElements.header.remove();
        this.htmlElements.summary.remove();
        this.htmlElements.details.remove();
        this.htmlElements.input.remove();
        this.htmlElements.label.remove();

        this.htmlElements = null;

        let pos2 = this.constructor.siblings.indexOf(this);
        if (~pos2) {
          this.constructor.siblings.splice(pos2, 1);
        }
      }
    }

    class CubeEngine extends ModBase {
      static dummy1 = ModBase.register(this);

      constructor() {
        super("CubeEngine");
      }
    }

    class Await {
      static dummy1 = ModBase.register(this);

      #interval;
      #handler;
      #callback;
      constructor(callback, interval) {
        this.#interval = interval;
        this.#callback = callback;
      }

      call() {
        const localThis = this;
        clearTimeout(this.#handler);

        this.#handler = setTimeout(function () {
          localThis.#callback();
        }, this.#interval);
      }
    }

    globalThis[arguments[0]] = ModBase;

    return function (when = "load") {
      setTimeout(() => {
        const ModMenu = new CubeEngine();
        ModMenu.htmlElements.details.open = false; // This line is now redundant as it's set in ModBase
        const target = document.getElementById("accountbox");
        const container = domMake.Tree("div", { id: identifier, style: "height: 1.6rem; flex: 0 0 auto;" });
        container.appendChild(ModMenu.htmlElements.details);
        target.after(container);
        target.after(domMake.Tree("hr"));

        globalThis["CubeEngine"] = ModMenu;
        globalThis["sockets"] = sockets;

        domClear.embeds();
        domClear.scripts();
        domClear.styles();
      }, 200);
    };
  })("QBit")();

// ... (final del módulo CubicEngine, p.ej. })("QBit")(); )

// --- Global readiness promises for external libraries ---
// Estas promesas se resolverán cuando la librería respectiva esté completamente cargada e inicializada.
// Su colocación es CRÍTICA: Deben estar en el scope más externo de tu userscript
// pero después de la inicialización de CubicEngine para que 'QBit' y 'globalThis' estén disponibles.
// NOTA: image-js ha sido ELIMINADO de aquí y del código del módulo ImageAnalyzer.

let _cvReadyPromise = new Promise(resolve => {
    // OpenCV.js requiere `cv.onRuntimeInitialized` para asegurar que WebAssembly se ha cargado.
    if (typeof cv !== 'undefined') {
        if (cv.Mat) { // Si ya está listo (poco probable tan temprano), resolvemos.
            resolve();
            console.log("Cube Engine: OpenCV.js ya estaba listo al inicio.");
        } else {
            cv.onRuntimeInitialized = () => {
                resolve();
                console.log("Cube Engine: OpenCV.js onRuntimeInitialized disparado. Librería lista.");
            };
        }
    }
});
// --- End Global readiness promises ---


// --- START NEW MODULE: ImageAnalyzer (CORREGIDO) ---
// (Pega el código completo del módulo ImageAnalyzer aquí)
// --- END NEW MODULE: ImageAnalyzer ---

// --- START NEW MODULE: ShapeDetector (CORREGIDO) ---
// (Pega el código completo del módulo ShapeDetector aquí)
// --- END NEW MODULE: ShapeDetector ---

// START BOT

(function BotClient() {
    const QBit = globalThis[arguments[0]];

    function parseServerUrl(any) {
      var prefix = String(any).length == 1 ? `sv${any}.` : "";
      let baseUrl = `wss://${prefix}drawaria.online/socket.io/?`;
      let params = `EIO=3&transport=websocket`;

      // SOLUCIÓN ROBUSTA: Construye la URL de los parámetros según el tipo de servidor.
      // Si no hay prefijo (servidor principal, como las salas de plantillas), no añade 'sid1=undefined' ni 'hostname=drawaria.online'.
      // Si hay prefijo (servidores svX.), añade 'sid1=undefined' y 'hostname=drawaria.online' como en el comportamiento original esperado.
      if (prefix === "") {
          // Servidor principal (e.g., salas de plantillas)
          params = `EIO=3&transport=websocket`;
      } else {
          // Servidores svX. (e.g., salas de adivinanzas, patio de recreo, pixel)
          params = `sid1=undefined&hostname=drawaria.online&EIO=3&transport=websocket`;
      }
      return baseUrl + params;
    }

    function parseRoomId(any) {
      // Asegura que 'any' sea tratado como una cadena para evitar errores con .match()
      if (!typecheck.isString(any)) {
        any = String(any);
      }
      const match = any.match(/([a-f0-9.-]+?)$/gi);
      if (match && match[0]) {
        return match[0];
      }
      return any; // Fallback al original si no se pudo extraer el ID específico
    }

    function parseSocketIOEvent(prefix_length, event_data) {
      try {
        return JSON.parse(event_data.slice(prefix_length));
      } catch (error) {}
    }

    function parseAvatarURL(arr = []) {
      return `https://drawaria.online/avatar/cache/${arr.length > 0 ? arr.join(".") : "default"}.jpg`;
    }

    class BotClient extends QBit {
      static dummy1 = QBit.register(this);

      constructor(name = "JavaScript", avatar = ["86e33830-86ea-11ec-8553-bff27824cf71"]) {
        super(name, `<img src="${parseAvatarURL(avatar)}">`);

        this.name = name;
        this.avatar = avatar;
        this.attributes = { spawned: false, rounded: false, status: false };

        this.url = "";
        this.socket = null;
        this.interval_id = 0;
        this.interval_ms = 25000;

        this.room = {
          id: null,
          config: null,
          type: 2, // Valor por defecto. Será sobrescrito si se usa enterRoom(..., roomTypeOverride)
          players: [],
        };

        this.customObservers = [
          {
            event: "mc_roomplayerschange",
            callback: (data) => {
              this.room.players = data[2];
            },
          },
        ];
      }

      getReadyState() {
        const localThis = this;
        if (!localThis.socket) return false;
        return localThis.socket.readyState == localThis.socket.OPEN;
      }

      // Métodos básicos que serán sobrescritos en BotClientModifications
      connect(url) {
        console.warn("Base connect called. This should be overridden.");
      }

      disconnect() {
        console.warn("Base disconnect called. This should be overridden.");
      }

      reconnect() {
        console.warn("Base reconnect called. This should be overridden.");
      }

      enterRoom(roomid, roomTypeOverride = null) {
        console.warn("Base enterRoom called. This should be overridden.");
      }
      // Fin de métodos básicos

      addEventListener(eventname, callback) {
        this.customObservers.push({ event: eventname, callback });
      }

      send(data) {
        if (!this.getReadyState()) return /*console.warn(data)*/;
        this.socket.send(data);
      }

      emit(event, ...data) {
        var emitter = emits[event];
        if (emitter) this.send(emitter(...data));
      }
    }

    const emits = {
      chatmsg: function (message) {
        let data = ["chatmsg", message];
        return `${42}${JSON.stringify(data)}`;
      },
      passturn: function () {
        let data = ["passturn"];
        return `${42}${JSON.stringify(data)}`;
      },
      pgdrawvote: function (playerid) {
        let data = ["pgdrawvote", playerid, 0];
        return `${42}${JSON.stringify(data)}`;
      },
      pgswtichroom: function () {
        let data = ["pgswtichroom"];
        return `${42}${JSON.stringify(data)}`;
      },
      playerafk: function () {
        let data = ["playerafk"];
        return `${42}${JSON.stringify(data)}`;
      },
      playerrated: function () {
        let data = ["playerrated"];
        return `${42}${JSON.stringify(data)}`;
      },
      sendgesture: function (gestureid) {
        let data = ["sendgesture", gestureid];
        return `${42}${JSON.stringify(data)}`;
      },
      sendvote: function () {
        let data = ["sendvote"];
        return `${42}${JSON.stringify(data)}`;
      },
      sendvotekick: function (playerid) {
        let data = ["sendvotekick", playerid];
        return `${42}${JSON.stringify(data)}`;
      },
      wordselected: function (wordid) {
        let data = ["sendvotekick", wordid];
        return `${42}${JSON.stringify(data)}`;
      },
      activateitem: function (itemid, isactive) {
        let data = ["clientcmd", 12, [itemid, isactive]];
        return `${42}${JSON.stringify(data)}`;
      },
      buyitem: function (itemid) {
        let data = ["clientcmd", 11, [itemid]];
        return `${42}${JSON.stringify(data)}`;
      },
      canvasobj_changeattr: function (itemid, target, value) {
        let data = ["clientcmd", 234, [itemid, target, value]];
        return `${42}${JSON.stringify(data)}`;
      },
      canvasobj_getobjects: function () {
        let data = ["clientcmd", 233];
        return `${42}${JSON.stringify(data)}`;
      },
      canvasobj_remove: function (itemid) {
        let data = ["clientcmd", 232, [itemid]];
        return `${42}${JSON.stringify(data)}`;
      },
      canvasobj_setposition: function (itemid, positionX, positionY, speed) {
        let data = ["clientcmd", 230, [itemid, 100 / positionX, 100 / positionY, { movespeed: speed }]];
        return `${42}${JSON.stringify(data)}`;
      },
      canvasobj_setrotation: function (itemid, rotation) {
        let data = ["clientcmd", 231, [itemid, rotation]];
        return `${42}${JSON.stringify(data)}`;
      },
      customvoting_setvote: function (value) {
        let data = ["clientcmd", 301, [value]];
        return `${42}${JSON.stringify(data)}`;
      },
      getfpid: function (value) {
        let data = ["clientcmd", 901, [value]];
        return `${42}${JSON.stringify(data)}`;
      },
      getinventory: function () {
        let data = ["clientcmd", 10, [true]];
        return `${42}${JSON.stringify(data)}`;
      },
      getspawnsstate: function () {
        let data = ["clientcmd", 102];
        return `${42}${JSON.stringify(data)}`;
      },
      moveavatar: function (positionX, positionY) {
        let data = [
          "clientcmd",
          103,
          [1e4 * Math.floor((positionX / 100) * 1e4) + Math.floor((positionY / 100) * 1e4), false],
        ];
        return `${42}${JSON.stringify(data)}`;
      },
      setavatarprop: function () {
        let data = ["clientcmd", 115];
        return `${42}${JSON.stringify(data)}`;
      },
      setstatusflag: function (flagid, isactive) {
        let data = ["clientcmd", 3, [flagid, isactive]];
        return `${42}${JSON.stringify(data)}`;
      },
      settoken: function (playerid, tokenid) {
        let data = ["clientcmd", 2, [playerid, tokenid]];
        return `${42}${JSON.stringify(data)}`;
      },
      snapchatmessage: function (playerid, value) {
        let data = ["clientcmd", 330, [playerid, value]];
        return `${42}${JSON.stringify(data)}`;
      },
      spawnavatar: function () {
        let data = ["clientcmd", 101];
        return `${42}${JSON.stringify(data)}`;
      },
      startrollbackvoting: function () {
        let data = ["clientcmd", 320];
        return `${42}${JSON.stringify(data)}`;
      },
      trackforwardvoting: function () {
        let data = ["clientcmd", 321];
        return `${42}${JSON.stringify(data)}`;
      },
      startplay: function (room, name, avatar) {
        let data = `${420}${JSON.stringify([
          "startplay",
          name,
          room.type,
          "en",
          room.id,
          null,
          [null, "https://drawaria.online/", 1000, 1000, [null, avatar[0], avatar[1]], null],
        ])}`;
        return data;
      },
      votetrack: function (trackid) {
        let data = ["clientcmd", 1, [trackid]];
        return `${42}${JSON.stringify(data)}`;
      },
      requestcanvas: function (playerid) {
        let data = ["clientnotify", playerid, 10001];
        return `${42}${JSON.stringify(data)}`;
      },
      respondcanvas: function (playerid, base64) {
        let data = ["clientnotify", playerid, 10002, [base64]];
        return `${42}${JSON.stringify(data)}`;
      },
      galleryupload: function (playerid, imageid) {
        let data = ["clientnotify", playerid, 11, [imageid]];
        return `${42}${JSON.stringify(data)}`;
      },
      warning: function (playerid, type) {
        let data = ["clientnotify", playerid, 100, [type]];
        return `${42}${JSON.stringify(data)}`;
      },
      mute: function (playerid, targetname, mute = 0) {
        let data = ["clientnotify", playerid, 1, [mute, targetname]];
        return `${42}${JSON.stringify(data)}`;
      },
      hide: function (playerid, targetname, hide = 0) {
        let data = ["clientnotify", playerid, 3, [hide, targetname]];
        return `${42}${JSON.stringify(data)}`;
      },
      report: function (playerid, reason, targetname) {
        let data = ["clientnotify", playerid, 2, [targetname, reason]];
        return `${42}${JSON.stringify(data)}`;
      },
      line: function (playerid, lastx, lasty, x, y, isactive, size, color, ispixel) {
        let data = [
          "drawcmd",
          0,
          [lastx / 100, lasty / 100, x / 100, y / 100, isactive, -size, color, playerid, ispixel],
        ];
        return `${42}${JSON.stringify(data)}`;
      },
      erase: function (playerid, lastx, lasty, x, y, isactive, size, color) {
        let data = ["drawcmd", 1, [lastx / 100, lasty / 100, x / 100, y / 100, isactive, -size, color, playerid]];
        return `${42}${JSON.stringify(data)}`;
      },
      flood: function (x, y, color, size, r, g, b, a) {
        let data = ["drawcmd", 2, [x / 100, y / 100, color, { 0: r, 1: g, 2: b, 3: a }, size]];
        return `${42}${JSON.stringify(data)}`;
      },
      undo: function (playerid) {
        let data = ["drawcmd", 3, [playerid]];
        return `${42}${JSON.stringify(data)}`;
      },
      clear: function () {
        let data = ["drawcmd", 4, []];
        return `${42}${JSON.stringify(data)}`;
      },
      noop: function () {
      },
    };
    const events = {
      bc_announcement: function (data) { }, bc_chatmessage: function (data) { }, bc_clearcanvasobj: function (data) { }, bc_clientnotify: function (data) { }, bc_createcanvasobj: function (data) { }, bc_customvoting_abort: function (data) { }, bc_customvoting_error: function (data) { }, bc_customvoting_results: function (data) { }, bc_customvoting_start: function (data) { }, bc_customvoting_vote: function (data) { }, bc_exp: function (data) { }, bc_extannouncement: function (data) { }, bc_freedrawsession_reset: function (data) { }, bc_gesture: function (data) { }, bc_musicbox_play: function (data) { }, bc_musicbox_vote: function (data) { }, bc_pgdrawallow_results: function (data) { }, bc_pgdrawallow_startvoting: function (data) { }, bc_pgdrawallow_vote: function (data) { }, bc_playerafk: function (data) { }, bc_playerrated: function (data) { }, bc_removecanvasobj: function (data) { }, bc_resetplayername: function (data) { }, bc_round_results: function (data) { }, bc_setavatarprop: function (data) { }, bc_setobjattr: function (data) { }, bc_setstatusflag: function (data) { }, bc_spawnavatar: function (data) { }, bc_startinground: function (data) { }, bc_token: function (data) { }, bc_turn_abort: function (data) { }, bc_turn_fastout: function (data) { }, bc_turn_results: function (data) { }, bc_turn_waitplayers: function (data) { }, bc_uc_freedrawsession_changedroom: function (data) { }, bc_uc_freedrawsession_start: function (data) { }, bc_votekick: function (data) { }, bc_votingtimeout: function (data) { }, bcmc_playervote: function (data) { }, bcuc_getfpid: function (data) { }, bcuc_itemactivated: function (data) { }, bcuc_itemactivationabort: function (data) { }, bcuc_moderatormsg: function (data) { }, bcuc_snapchatmessage: function (data) { }, uc_avatarspawninfo: function (data) { }, uc_buyitemerror: function (data) { }, uc_canvasobjs: function (data) { }, uc_chatmuted: function (data) { }, uc_coins: function (data) { }, uc_inventoryitems: function (data) { }, uc_resetavatar: function (data) { }, uc_serverserstart: function (data) { }, uc_snapshot: function (data) { }, uc_tokenerror: function (data) { }, uc_turn_begindraw: function (data) { }, uc_turn_selectword: function (data) { }, uc_turn_selectword_refreshlist: function (data) { }, uc_turn_wordguessedlocalThis: function (data) { },
    };

    globalThis["_io"] = { events, emits };
})("QBit");
// YOUTUBEDRAWARIA

// ... (Tu código existente hasta aquí, incluyendo las promesas globales y otros módulos) ...


// --- START NEW MODULE: MagicTools (COMBINADO - SOLUCIÓN DEFINITIVA BOTÓN "COLOR RÁPIDO") ---
(function MagicToolsModule() {
    const QBit = globalThis[arguments[0]];

    // All Known Elements for Hide/Show Menus (copied from HideShowMenusModule)
    const allKnownElements = [
        { selector: '#canvas', label: 'Canvas' },
        { selector: '#leftbar', label: 'Left Sidebar' },
        { selector: '#rightbar', label: 'Right Sidebar' },
        { selector: '#playerlist', label: 'Player List' },
        { selector: '#cyclestatus', label: 'Cycle Status' },
        { selector: '#votingbox', label: 'Voting Box' },
        { selector: '#passturnbutton', label: 'Pass Turn Button' },
        { selector: '.timer', label: 'Round Timer' },
        { selector: '#roomcontrols', label: 'Room Controls' },
        { selector: '#infotext', label: 'Info Text' },
        { selector: '#gesturespickerselector', label: 'Gestures Picker' },
        { selector: '#chatbox_messages', label: 'Chat Messages' },
        { selector: '#drawcontrols', label: 'Draw Controls' },
        { selector: '#turnresults', label: 'Turn Results' },
        { selector: '#roundresults', label: 'Round Results' },
        { selector: '#snapmessage_container', label: 'Snap Message Container' },
        { selector: '#accountbox', label: 'Account Box' },
        { selector: '#customvotingbox', label: 'Custom Voting Box' },
        { selector: '#showextmenu', label: 'Show Ext Menu Button' },
        { selector: '#playgroundroom_next', label: 'Playground Next Button' },
        { selector: '#homebutton', label: 'Home Button' },
        { selector: '.invbox', label: 'Invitation Box' },
        { selector: '#howtoplaydialog', label: 'How to Play' },
        { selector: '#newroomdialog', label: 'New Room Options' },
        { selector: '#musictracks', label: 'Music Tracks' },
        { selector: '#inventorydlg', label: 'Inventory' },
        { selector: '#extmenu', label: 'Extra Menu' },
        { selector: '#pressureconfigdlg', label: 'Pressure Settings' },
        { selector: '#palettechooser', label: 'Palette Chooser' },
        { selector: '#wordchooser', label: 'Word Chooser' },
        { selector: '#targetword', label: 'Target Word Info' },
        { selector: '#invitedlg', label: 'Invite Dialog' },
        { selector: '#reportdlg', label: 'Report Dialog' },
        { selector: '#turtabortedmsg', label: 'Turn Aborted Msg' },
        { selector: '#drawsettings', label: 'Draw Settings' },
        { selector: '.modal-header', label: 'Header (Any)' },
        { selector: '.modal-body', label: 'Body (Any)' },
        { selector: '.modal-footer', label: 'Footer (Any)' },
        { selector: '.form-group', label: 'Form Group (Any)' },
        { selector: '.table', label: 'Table (Any)' },
        { selector: '.spinner-border', label: 'Spinner/Loading Icon (Any)' },
    ];

    QBit.Styles.addRules([
        `#${QBit.identifier} .magic-tools-section {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 10px;
            background-color: var(--CE-bg_color);
        }`,
        `#${QBit.identifier} .magic-tools-section-title {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--dark-blue-title);
            text-align: center;
        }`,
        `#${QBit.identifier} .magic-tools-toggle-button { /* Base style for all toggle buttons */
            background-color: var(--secondary);
            color: var(--dark);
        }`,
        `#${QBit.identifier} .magic-tools-toggle-button.active { /* Active state for toggle buttons */
            background-color: var(--info);
            color: white;
        }`,
        `#${QBit.identifier} .magic-tools-controls-row > * {
            flex: 1;
        }`,
        `#${QBit.identifier} .magic-tools-selector {
            border: 1px solid var(--input-border-blue);
            background-color: var(--input-bg);
            color: var(--dark-text);
            padding: 5px;
            width: 100%;
            box-sizing: border-box;
            margin-bottom: 5px;
        }`
        // OLD CSS related to .magic-tool-icon-toggle and .custom-checkbox-indicator has been removed
    ]);

    class MagicTools extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        // BiggerBrush / BetterBrush
        #biggerBrushActive = false;
        #betterBrushActive = false;
        #drawwidthrangeSlider; // For BiggerBrush
        #betterBrushVisibilityObserver; // For BetterBrush popuplist
        #rapidColorChangeButton; // Reference to the new button for Rapid Color Change

        // BiggerStencil
        #biggerStencilActive = false;
        #biggerStencilAccessibilityObserver;

        // Hide/Show Menus
        #hideShowElementSelector;
        #hideShowMenusObserver;

        constructor() {
            super("Herramientas Mágicas", '<i class="fas fa-magic"></i>');
            this.#onStartup();
        }

        #onStartup() {
            this.#loadInterface();
            this.#setupDrawControlsObservers(); // For BiggerBrush, BetterBrush, BiggerStencil
            this.#setupHideShowMenusObserver(); // For Hide/Show Menus
            this.#populateHideShowSelector(); // Initial population
        }

        #loadInterface() {
            const container = domMake.Tree("div");

            // --- Section: Herramientas de Pincel ---
            const brushToolsSection = domMake.Tree("div", { class: "magic-tools-section" });
            brushToolsSection.appendChild(domMake.Tree("div", { class: "magic-tools-section-title" }, ["Herramientas de Pincel"]));

// BiggerBrush Toggle
const biggerBrushRow = domMake.Row();
const biggerBrushButton = domMake.Button('<i class="fas fa-brush"></i> Pincel<br>Grande');
biggerBrushButton.classList.add("magic-tools-toggle-button");
biggerBrushButton.addEventListener("click", () => this.#toggleBiggerBrush(biggerBrushButton));
biggerBrushRow.appendChild(biggerBrushButton);
brushToolsSection.appendChild(biggerBrushRow);

// BetterBrush Toggle
const betterBrushRow = domMake.Row();
const betterBrushButton = domMake.Button('<i class="fas fa-magic"></i> Pincel<br>Mejorado');
betterBrushButton.classList.add("magic-tools-toggle-button");
betterBrushButton.addEventListener("click", () => this.#toggleBetterBrush(betterBrushButton));
betterBrushRow.appendChild(betterBrushButton);
brushToolsSection.appendChild(betterBrushRow);

// NEW: Rapid Color Change button in its own row, directly below BetterBrush
const rapidColorChangeRow = domMake.Row();
this.#rapidColorChangeButton = domMake.Button('<i class="fas fa-adjust"></i> Color<br>Rápido'); // Use <br> for newline
this.#rapidColorChangeButton.classList.add("magic-tools-toggle-button");
this.#rapidColorChangeButton.addEventListener("click", () => this.#toggleRapidColorChange(this.#rapidColorChangeButton));
rapidColorChangeRow.appendChild(this.#rapidColorChangeButton);
brushToolsSection.appendChild(rapidColorChangeRow);

// BiggerStencil Toggle
const biggerStencilRow = domMake.Row();
const biggerStencilButton = domMake.Button('<i class="fas fa-object-group"></i> Plantillas<br>Grandes');
biggerStencilButton.classList.add("magic-tools-toggle-button");
biggerStencilButton.addEventListener("click", () => this.#toggleBiggerStencil(biggerStencilButton));
biggerStencilRow.appendChild(biggerStencilButton);
brushToolsSection.appendChild(biggerStencilRow);
container.appendChild(brushToolsSection);

// --- Section: Exportar Chat ---
const exportChatSection = domMake.Tree("div", { class: "magic-tools-section" });
exportChatSection.appendChild(domMake.Tree("div", { class: "magic-tools-section-title" }, ["Exportar Chat"]));
const exportChatRow = domMake.Row();
const exportChatButton = domMake.Button('<i class="fas fa-file-export"></i> Exportar<br>Chat (TXT)');
exportChatButton.title = "Exporta todos los mensajes del chat a un archivo de texto.";
exportChatButton.addEventListener("click", () => this.#exportChatMessages());
exportChatRow.appendChild(exportChatButton);
exportChatSection.appendChild(exportChatRow);
container.appendChild(exportChatSection);

            // --- Section: Ocultar/Mostrar Menús ---
            const hideShowMenusSection = domMake.Tree("div", { class: "magic-tools-section" });
            hideShowMenusSection.appendChild(domMake.Tree("div", { class: "magic-tools-section-title" }, ["Ocultar/Mostrar Menús"]));

            this.#hideShowElementSelector = domMake.Tree("select", { id: "magic-tools-element-selector", class: "magic-tools-selector" });
            hideShowMenusSection.appendChild(this.#hideShowElementSelector);

            const hideShowButtonRow = domMake.Row({ class: "action-buttons" });
            const showButton = domMake.Button("Mostrar");
            showButton.addEventListener('click', () => this.#toggleElementVisibility(false));
            const hideButton = domMake.Button("Ocultar");
            hideButton.addEventListener('click', () => this.#toggleElementVisibility(true));
            hideShowButtonRow.appendAll(showButton, hideButton);
            hideShowMenusSection.appendChild(hideShowButtonRow);
            container.appendChild(hideShowMenusSection);


            this.htmlElements.section.appendChild(container);
        }

// --- BiggerBrush Logic ---
#toggleBiggerBrush(button) {
    this.#biggerBrushActive = !this.#biggerBrushActive;
    button.classList.toggle("active", this.#biggerBrushActive);

    if (!this.drawwidthrangeSlider) {
        this.drawwidthrangeSlider = document.querySelector("#drawwidthrange");
        if (!this.drawwidthrangeSlider) {
            this.notify("error", "Slider de ancho de dibujo no encontrado.");
            this.#biggerBrushActive = false;
            button.classList.remove("active");
            return;
        }
    }

    if (this.#biggerBrushActive) {
        document.querySelectorAll(".drawcontrols-button").forEach((n) => {
            n.classList.remove("drawcontrols-disabled");
        });
        button.innerHTML = '<i class="fas fa-paint-brush"></i> Pincel Grande<br>Activo';
        this.drawwidthrangeSlider.parentElement.previousElementSibling.lastElementChild.click();
        this.drawwidthrangeSlider.parentElement.style.display = "flex";
        this.drawwidthrangeSlider.max = 48;
        this.drawwidthrangeSlider.min = -2000;
        this.notify("success", "Pincel Grande activado.");
    } else {
        button.innerHTML = '<i class="fas fa-paint-brush"></i> Pincel<br>Grande';
        this.drawwidthrangeSlider.max = 45;
        this.drawwidthrangeSlider.min = -100;
        this.notify("warning", "Pincel Grande desactivado.");
    }
}

// --- BetterBrush Logic ---
#toggleBetterBrush(button) {
    this.#betterBrushActive = !this.#betterBrushActive;
    button.classList.toggle("active", this.#betterBrushActive);
    button.innerHTML = this.#betterBrushActive
        ? '<i class="fas fa-magic"></i> Pincel Mejorado<br>Activo'
        : '<i class="fas fa-magic"></i> Pincel<br>Mejorado';
    this.notify("info", `Pincel Mejorado ${this.#betterBrushActive ? 'activado' : 'desactivado'}.`);
}

// --- Rapid Color Change Logic ---
#toggleRapidColorChange(button) {
    const colorflowSpeedInput = document.querySelector('input[data-localprop="colorflow"]');
    const colorflowTypeSelect = document.querySelector('select[data-localprop="colorautochange"]');
    const settingsContainer = document.querySelector(".drawcontrols-settingscontainer:has([data-localprop='colorautochange'])");

    if (!colorflowSpeedInput || !colorflowTypeSelect || !settingsContainer) {
        this.notify("warning", "Controles de flujo de color no encontrados. Asegúrate de que los controles de dibujo están visibles.");
        return;
    }

    const isActive = button.classList.contains("active");
    const newActiveState = !isActive;

    button.classList.toggle("active", newActiveState);
    button.innerHTML = newActiveState
        ? '<i class="fas fa-adjust"></i> Color Rápido<br>Activo'
        : '<i class="fas fa-adjust"></i> Color<br>Rápido';

    if (newActiveState) {
        colorflowTypeSelect.value = "2";
        colorflowSpeedInput.max = 10;
        colorflowSpeedInput.value = 10;
        this.notify("info", "Cambio Rápido de Color activado.");
    } else {
        colorflowTypeSelect.value = "0";
        colorflowSpeedInput.max = 1;
        colorflowSpeedInput.value = 0;
        this.notify("info", "Cambio Rápido de Color desactivado.");
    }
    settingsContainer.dispatchEvent(new CustomEvent("change"));
}

// --- BiggerStencil Logic ---
#toggleBiggerStencil(button) {
    this.#biggerStencilActive = !this.#biggerStencilActive;
    button.classList.toggle("active", this.#biggerStencilActive);
    button.innerHTML = this.#biggerStencilActive
        ? '<i class="fas fa-object-group"></i> Plantillas Grandes<br>Activo'
        : '<i class="fas fa-object-group"></i> Plantillas<br>Grandes';
    this.notify("info", `Plantillas Grandes ${this.#biggerStencilActive ? 'activado' : 'desactivado'}.`);

    const targetStencilButton = document.querySelector(".fa-parachute-box")?.parentElement;
    if (!targetStencilButton) {
        this.notify("warning", "Botón de Plantillas no encontrado.");
        return;
    }

    if (this.#biggerStencilActive) {
        if (targetStencilButton.disabled) {
            targetStencilButton.disabled = "";
        }
    }
}

        #setupDrawControlsObservers() {
            const betterBrushTarget = document.querySelector(".drawcontrols-popuplist");
            if (betterBrushTarget) {
                this.#betterBrushVisibilityObserver = new MutationObserver((mutations) => {
                    if (this.#betterBrushActive) {
                        if (mutations[0].target.style.display !== "none") {
                            mutations[0].target.querySelectorAll("div").forEach((n) => {
                                n.removeAttribute("style");
                            });
                        }
                    }
                });
                this.#betterBrushVisibilityObserver.observe(betterBrushTarget, { attributes: true, attributeFilter: ['style'] });
            } else {
                this.notify("warning", "Contenedor de pop-up de pincel no encontrado para BetterBrush.");
            }

            const biggerStencilTarget = document.querySelector(".fa-parachute-box")?.parentElement;
            if (biggerStencilTarget) {
                this.#biggerStencilAccessibilityObserver = new MutationObserver((mutations) => {
                    if (this.#biggerStencilActive) {
                        if (mutations[0].target.disabled) {
                            mutations[0].target.disabled = "";
                        }
                    }
                });
                this.#biggerStencilAccessibilityObserver.observe(biggerStencilTarget, { attributes: true, attributeFilter: ['disabled'] });
            } else {
                this.notify("warning", "Botón de esténcil no encontrado para BiggerStencil.");
            }

            this.drawwidthrangeSlider = document.querySelector("#drawwidthrange");
            if (!this.drawwidthrangeSlider) {
                this.notify("warning", "Slider de ancho de dibujo no encontrado para BiggerBrush.");
            }
        }


        // --- Export Chat Logic ---
        #exportChatMessages() {
            const chatbox = document.getElementById('chatbox_messages');
            if (!chatbox) {
                this.notify("warning", "Contenedor de chat no encontrado.");
                return;
            }

            const messages = chatbox.querySelectorAll('div.chatmessage');
            let exportedMessages = [];

            messages.forEach(message => {
                let timestamp = message.dataset.ts ? new Date(parseInt(message.dataset.ts)).toLocaleTimeString() : 'N/A';
                if (message.classList.contains('systemchatmessage') || message.classList.contains('systemchatmessage5')) {
                    exportedMessages.push(`[${timestamp}] [Sistema] ${message.textContent.trim().replace(/^System: /, '')}`);
                } else if (message.classList.contains('playerchatmessage-highlightable') || message.classList.contains('chatmessage')) {
                    const playerName = message.querySelector('.playerchatmessage-name')?.textContent?.trim() || 'Desconocido';
                    const playerMessage = message.querySelector('.playerchatmessage-text')?.textContent?.trim() || '';
                    exportedMessages.push(`[${timestamp}] ${playerName}: ${playerMessage}`);
                }
            });

            if (exportedMessages.length === 0) {
                this.notify("info", "No hay mensajes en el chat para exportar.");
                return;
            }

            const blob = new Blob([exportedMessages.join('\n')], { type: 'text/plain;charset=utf-8' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `drawaria_chat_${new Date().toISOString().slice(0, 10)}.txt`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            this.notify("success", "Chat exportado exitosamente.");
        }

        // --- Hide/Show Menus Logic ---
        #populateHideShowSelector() {
            const currentSelectedValue = this.#hideShowElementSelector.value;
            this.#hideShowElementSelector.innerHTML = '';
            const addedSelectors = new Set();

            const placeholderOption = domMake.Tree('option', { value: '' }, ['-- Selecciona un elemento --']);
            this.#hideShowElementSelector.appendChild(placeholderOption);

            allKnownElements.forEach(item => {
                try {
                    if (document.querySelector(item.selector) && !addedSelectors.has(item.selector)) {
                        const option = domMake.Tree('option', { value: item.selector }, [item.label]);
                        this.#hideShowElementSelector.appendChild(option);
                        addedSelectors.add(item.selector);
                    }
                } catch (e) {
                    console.warn(`[MagicTools - HideShow] Selector inválido: ${item.selector}. Error: ${e.message}`);
                }
            });

            if (currentSelectedValue && Array.from(this.#hideShowElementSelector.options).some(opt => opt.value === currentSelectedValue)) {
                this.#hideShowElementSelector.value = currentSelectedValue;
            } else {
                this.#hideShowElementSelector.value = '';
            }
        }

        #toggleElementVisibility(hide) {
            const selectedValue = this.#hideShowElementSelector.value;
            if (!selectedValue) {
                this.notify("warning", "No hay elemento seleccionado.");
                return;
            }

            try {
                document.querySelectorAll(selectedValue).forEach(el => {
                    if (hide) {
                        if (el.style.display && el.style.display !== 'none') {
                            el.dataset.originalDisplay = el.style.display;
                        }
                        el.style.display = 'none';
                        el.style.visibility = 'hidden';
                        if (selectedValue.includes('.modal-backdrop')) {
                             el.remove();
                        }
                        this.notify("info", `Ocultando: ${selectedValue}`);
                    } else {
                        if (el.dataset.originalDisplay) {
                            el.style.display = el.dataset.originalDisplay;
                            delete el.dataset.originalDisplay;
                        } else {
                            el.style.display = '';
                        }
                        el.style.visibility = '';
                        this.notify("info", `Mostrando: ${selectedValue}`);
                    }
                });
            } catch (e) {
                this.notify("error", `Error al ${hide ? 'ocultar' : 'mostrar'} el elemento ${selectedValue}: ${e.message}`);
            }
        }

        #setupHideShowMenusObserver() {
            if (this.#hideShowMenusObserver) {
                this.#hideShowMenusObserver.disconnect();
            }

            this.#hideShowMenusObserver = new MutationObserver(() => {
                clearTimeout(this.#hideShowMenusObserver._timer);
                this.#hideShowMenusObserver._timer = setTimeout(() => {
                    this.#populateHideShowSelector();
                }, 500);
            });

            this.#hideShowMenusObserver.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });
        }
    }
})("QBit");
// --- END NEW MODULE: MagicTools ---

// ... (Tu código existente continúa con los demás módulos) ...


(function GhostCanvas() {
    const QBit = globalThis[arguments[0]];
    const Await = QBit.findGlobal("Await");

    QBit.Styles.addRule(
      ".ghostimage { position:fixed; top:50%; left:50%; opacity:.6; box-shadow: 0 0 1px 1px cornflowerblue inset; }"
    );

    function getBoundingClientRect(htmlElement) {
      let { top, right, bottom, left, width, height, x, y } = htmlElement.getBoundingClientRect();

      top = Number(top).toFixed();
      right = Number(right).toFixed();
      bottom = Number(bottom).toFixed();
      left = Number(left).toFixed();
      width = Number(width).toFixed();
      height = Number(height).toFixed();
      x = Number(x).toFixed();
      y = Number(y).toFixed();

      return { top, right, bottom, left, width, height, x, y };
    }

    function makeDragable(draggableElement, update) {
      var pos1 = 0,
        pos2 = 0,
        pos3 = 0,
        pos4 = 0;
      draggableElement.onmousedown = dragMouseDown;

      function dragMouseDown(e) {
        e = e || window.event;
        e.preventDefault();
        // get the mouse cursor position at startup:
        pos3 = e.clientX;
        pos4 = e.clientY;
        document.onmouseup = closeDragElement;
        // call a function whenever the cursor moves:
        document.onmousemove = elementDrag;
      }

      function elementDrag(e) {
        e = e || window.event;
        e.preventDefault();
        // calculate the new cursor position:
        pos1 = pos3 - e.clientX;
        pos2 = pos4 - e.clientY;
        pos3 = e.clientX;
        pos4 = e.clientY;
        // set the element's new position:
        draggableElement.style.top = Number(draggableElement.offsetTop - pos2).toFixed() + "px";
        draggableElement.style.left = Number(draggableElement.offsetLeft - pos1).toFixed() + "px";
        update();
      }

      function closeDragElement() {
        /* stop moving when mouse button is released:*/
        document.onmouseup = null;
        document.onmousemove = null;
      }
    }

    const radios = [];

    class GhostCanvas extends QBit {
      static dummy1 = QBit.register(this);
      static dummy2 = QBit.bind(this, "CubeEngine");
      static isFavorite = true;

      GameCanvas;
      DrawCanvas;
      DrawCanvasContext;
      DrawCanvasRect;
      loadedImages;
      drawingManager;

      constructor() {
        super("GhostCanvas", '<i class="fas fa-images"></i>');
        this.GameCanvas = document.body.querySelector("canvas#canvas");
        this.DrawCanvas = document.createElement("canvas");
        this.DrawCanvasRect = {};
        this.loadedImages = [];
        this.DrawCanvasContext = this.DrawCanvas.getContext("2d");
        this.drawingManager = new TaskManager(this);

        this.#onStartup();
        this.resetAllSettings();
      }

      #onStartup() {
        this.#loadInterface();
        this.DrawCanvas.width = this.GameCanvas.width;
        this.DrawCanvas.height = this.GameCanvas.height;
        this.DrawCanvas.style =
          "position:fixed; opacity:.6; box-shadow: 0 0 1px 1px firebrick inset; pointer-events: none;";

        const onResize = new Await(this.alignDrawCanvas.bind(this), 500);
        window.addEventListener("resize", (event) => {
          onResize.call();
        });

        this.htmlElements.input.addEventListener("change", (event) => {
          if (this.htmlElements.input.checked) this.alignDrawCanvas();
        });
      }

      #loadInterface() {
        this.#row1();
        this.#row2();
        this.#row3();
        this.#row4();
        this.#row5();
      }

      #row1() {
        const row = domMake.Row();
        {
          const resetSettingsButton = domMake.Button("Reset");
          const showCanvasInput = domMake.Tree("input", { type: "checkbox", title: "Toggle Canvas", class: "icon" });
          const clearCanvasButton = domMake.Button("Clear");

          resetSettingsButton.title = "Reset Settings";
          clearCanvasButton.title = "Clear GameCanvas";

          resetSettingsButton.addEventListener("click", (event) => {
            this.resetAllSettings();
          });

          showCanvasInput.addEventListener("change", () => {
            this.DrawCanvas.style.display = showCanvasInput.checked ? "block" : "none";
          });

          clearCanvasButton.addEventListener("click", (event) => {
            let data = ["drawcmd", 0, [0.5, 0.5, 0.5, 0.5, !0, -2000, "#FFFFFF", -1, !1]];
            this.findGlobal("BotClientInterface").siblings[0].bot.send(`${42}${JSON.stringify(data)}`);
          });

          document.body.appendChild(this.DrawCanvas);
          row.appendAll(resetSettingsButton, showCanvasInput, clearCanvasButton);
        }
        this.htmlElements.section.appendChild(row);
      }

      #row2() {
        const row = domMake.Row();
        {
          const loadPixelDataButton = domMake.Button("Load");
          const pixelsLeftToDraw = domMake.Tree("input", {
            type: "text",
            readonly: true,
            style: "text-align: center;",
            value: "0",
          });
          const clearPixelListButton = domMake.Button("Clear");

          this.htmlElements.pixelsLeftToDraw = pixelsLeftToDraw;
          loadPixelDataButton.title = "Load Pixels to draw";
          clearPixelListButton.title = "Clear Pixels to draw";

          loadPixelDataButton.addEventListener("click", (event) => {
            this.saveCanvas();
          });

          clearPixelListButton.addEventListener("click", (event) => {
            this.setPixelList([]);
          });

          row.appendAll(loadPixelDataButton, pixelsLeftToDraw, clearPixelListButton);
        }
        this.htmlElements.section.appendChild(row);
      }

      #row3() {
        const row = domMake.Row();
        {
          const startDrawingButton = domMake.Button("Start");
          const stopDrawingButton = domMake.Button("Stop");

          startDrawingButton.addEventListener("click", (event) => {
            this.drawingManager.startDrawing();
          });
          stopDrawingButton.addEventListener("click", (event) => {
            this.drawingManager.stopDrawing();
          });

          row.appendAll(startDrawingButton, stopDrawingButton);
        }
        this.htmlElements.section.appendChild(row);
      }

      #row4() {
        const row = domMake.Row();
        {
          const brushSizeInput = domMake.Tree("input", { type: "number", min: 2, value: 2, max: 200, step: 1 });
          const singleColorModeInput = domMake.Tree("input", { type: "checkbox", class: "icon" });
          const brushColorInput = domMake.Tree("input", { type: "text", value: "blue" });

          brushSizeInput.addEventListener("change", (event) => {
            this.drawingManager.brushSize = Number(brushSizeInput.value);
          });
          singleColorModeInput.addEventListener("change", (event) => {
            this.drawingManager.singleColor = Boolean(singleColorModeInput.checked);
          });
          brushColorInput.addEventListener("change", (event) => {
            this.drawingManager.brushColor = brushColorInput.value; // Store the actual color value
          });

          row.appendAll(brushSizeInput, singleColorModeInput, brushColorInput);
        }
        this.htmlElements.section.appendChild(row);
      }

      #row5() {
        const row = domMake.IconList();
        {
          const id = generate.uuidv4();
          const chooseGhostlyImageInput = domMake.Tree("input", { type: "file", id: id, hidden: true });
          const chooseGhostlyImageLabel = domMake.Tree("label", { for: id, class: "icon", title: "Add Image" }, [
            domMake.Tree("i", { class: "fas fa-plus" }),
          ]);

          const localThis = this;
          function onChange() {
            if (!this.files || !this.files[0]) return;
            const myFileReader = new FileReader();
            myFileReader.addEventListener("load", (event) => {
              const base64 = event.target.result.replace("image/gif", "image/png");
              localThis.createGhostImage(base64, row);
            });
            myFileReader.readAsDataURL(this.files[0]);
          }
          chooseGhostlyImageInput.addEventListener("change", onChange);

          row.appendAll(chooseGhostlyImageLabel, chooseGhostlyImageInput);
        }
        {
          const resetImageSelectionLabel = domMake.Tree("div", { class: "icon", title: "Unselect" }, [
            domMake.Tree("i", { class: "fas fa-chevron-left" }),
          ]);
          resetImageSelectionLabel.addEventListener("click", () => {
            document.body.querySelectorAll('input[name="ghostimage"]').forEach((node) => {
              node.checked = false;
            });
          });
          row.appendChild(resetImageSelectionLabel);
        }
        this.htmlElements.section.appendChild(row);
      }

      createGhostImage(imageSource, row) {
        this.alignDrawCanvas();
        const image = this.loadExtension(GhostImage, (reference) => {
          this.loadedImages.push(reference);
        });
        row.appendChild(image.htmlElements.label);
        image.setImageSource(imageSource);
      }

      clearCanvas() {
        this.DrawCanvasContext.clearRect(0, 0, this.DrawCanvas.width, this.DrawCanvas.height);
      }

      saveCanvas() {
        this.getAllPixels();
      }

      resetAllSettings() {
        this.clearCanvas();
        this.loadedImages.forEach((image, index) => {
          // Remove elements and instances associated with GhostImage
          if (image && typeof image.reduceToAtoms === 'function') {
            setTimeout(() => {
              image.reduceToAtoms();
            }, 10 * index);
          }
        });
        this.loadedImages = []; // Ensure the array is cleared
        this.drawingManager.stopDrawing();
        this.setPixelList([]);
        this.alignDrawCanvas(true);
      }

      alignDrawCanvas() {
        if (arguments[0]) {
          this.DrawCanvas.width = this.GameCanvas.width;
          this.DrawCanvas.height = this.GameCanvas.height;
        }

        const GameCanvasRect = getBoundingClientRect(this.GameCanvas);

        this.DrawCanvas.style.top = `${GameCanvasRect.top}px`;
        this.DrawCanvas.style.left = `${GameCanvasRect.left}px`;
        this.DrawCanvas.style.width = `${GameCanvasRect.width}px`;
        this.DrawCanvas.style.height = `${GameCanvasRect.height}px`;

        const DrawCanvasRect = getBoundingClientRect(this.DrawCanvas);

        if (DrawCanvasRect.width <= 0 || DrawCanvasRect.height <= 0)
          return Object.assign(this.DrawCanvasRect, DrawCanvasRect);
        // DrawCanvasRect.alignModifierX = Number(this.DrawCanvas.width / DrawCanvasRect.width).toFixed(2);
        // DrawCanvasRect.alignModifierY = Number(this.DrawCanvas.height / DrawCanvasRect.height).toFixed(2);

        DrawCanvasRect.drawModifierX = 100 / DrawCanvasRect.width;
        DrawCanvasRect.drawModifierY = 100 / DrawCanvasRect.height;
        Object.assign(this.DrawCanvasRect, DrawCanvasRect);
      }

      getAllPixels() {
        const image = this.DrawCanvasContext.getImageData(
          0,
          0,
          this.DrawCanvasContext.canvas.width,
          this.DrawCanvasContext.canvas.height
        );
        const pixels = [];

        for (let index = 0; index < image.data.length; index += 4) {
          const x = (index / 4) % image.width;
          const y = Math.floor((index / 4) / image.width);

          const r = image.data[index + 0];
          const g = image.data[index + 1];
          const b = image.data[index + 2];
          const a = image.data[index + 3];

          pixels.push({ x1: x, y1: y, x2: x, y2: y, color: [r, g, b, a] }); // Store RGBA array
        }

        this.setPixelList(pixels);
        this.notify("info", `Total de ${pixels.length} píxeles cargados del lienzo fantasma.`);
      }

      getNoneTransparentPixels() {
        // This method relies on rgbaArrayToHex for filtering, but current `minifyPixelsArray`
        // uses direct alpha channel comparison.
        // If this function is intended to filter, it needs to be updated to use current `minOpacity` or similar.
        // For now, it's not directly called by minify.
        this.getAllPixels();

        const newPixelArray = this.drawingManager.pixelList.filter((pixel) => {
          // Assuming rgbaArrayToHex converts to #RRGGBBAA and filter is for full transparency (alpha=00)
          // The pixel.color is an RGBA array.
          return pixel.color[3] > 0; // Filter out fully transparent pixels
        });

        this.setPixelList(newPixelArray);
        this.notify("info", `Filtrados ${this.drawingManager.pixelList.length - newPixelArray.length} píxeles transparentes. Ahora: ${newPixelArray.length} píxeles.`);
      }

      setPixelList(pixelArray) {
        this.drawingManager.pixelList = pixelArray;
        this.htmlElements.pixelsLeftToDraw.value = pixelArray.length;
      }
    }

    class GhostImage extends QBit {
      image;
      rect;

      constructor() {
        super("GhostImage", '<i class="fas fa-image-polaroid"></i>');
        this.#onStartup();
      }

      #onStartup() {
        this.#loadInterface();

        this.image = domMake.Tree("img", { class: "ghostimage" });
        this.image.addEventListener("mousedown", (event) => {
          this.htmlElements.label.click();
        });

        this.htmlElements.input.type = "radio";
        this.htmlElements.input.name = "ghostimage";

        radios.push(this.htmlElements.input);
        this.htmlElements.input.addEventListener("change", (event) => {
          radios.forEach(function (radio) {
            document.body.querySelector(`label[for="${radio.id}"]`).classList.remove("active");
          });
          this.htmlElements.label.classList.add("active");
        });

        document.body.appendChild(this.image);
        makeDragable(this.image, this.updatePosition.bind(this));
        this.updatePosition();
      }

      #loadInterface() {
        this.#row1();
        this.#row2();
      }

      #row1() {
        const row = domMake.Row();
        {
          const paintCanvasButton = domMake.Button("Place");

          paintCanvasButton.addEventListener("click", (event) => {
            this.drawImage();
          });

          row.appendAll(paintCanvasButton);
        }
        {
          const enableButton = domMake.Button("Delete");

          enableButton.addEventListener("click", (event) => {
            this.reduceToAtoms();
          });
          row.appendChild(enableButton);
          this.htmlElements.toggleStatusButton = enableButton;
        }
        this.htmlElements.section.appendChild(row);
      }

      #row2() {
        const row = domMake.Row();
        {
          const scaleInput = domMake.Tree("input", {
            type: "number",
            title: "scale", // Corrected title
            min: 0.1,
            max: 10,
            value: 1,
            step: 0.02,
          });

          scaleInput.addEventListener("change", () => {
            this.image.style.scale = scaleInput.value;
          });

          this.htmlElements.scaleInput = scaleInput;

          row.appendAll(scaleInput);
        }
        {
          const rotationInput = domMake.Tree("input", { type: "number", title: "rotation", value: 0, step: 1 });

          rotationInput.addEventListener("change", () => {
            this.image.style.rotate = `${rotationInput.value}deg`;
          });

          this.htmlElements.rotationInput = rotationInput;

          row.appendChild(rotationInput);
        }
        this.htmlElements.section.appendChild(row);
      }

      drawImage() {
        this.updatePosition();
        const ctx = this.parent.DrawCanvasContext;

        const offsetTop = Number(this.rect.top) - Number(this.parent.DrawCanvasRect.top);
        const offsetLeft = Number(this.rect.left) - Number(this.parent.DrawCanvasRect.left);

        const angle = (Math.PI / 180) * Number(this.htmlElements.rotationInput.value);
        const scale = Number(this.htmlElements.scaleInput.value);

        const imageWidth = this.image.width * scale;
        const imageHeight = this.image.height * scale;
        const imgHalfWidth = imageWidth * 0.5;
        const imgHalfHeight = imageHeight * 0.5;

        ctx.save();
        ctx.translate(offsetLeft + imgHalfWidth, offsetTop + imgHalfHeight);
        ctx.rotate(angle);
        ctx.translate(-imgHalfWidth, -imgHalfHeight);
        ctx.drawImage(this.image, 0, 0, imageWidth, imageHeight);
        ctx.restore();
        this.parent.notify("success", "Imagen colocada en el lienzo fantasma.");
      }

      setImageSource(imageSource) {
        this.image.src = imageSource;
        this.setIcon(`<img src="${this.image.src}">`);
      }

      updatePosition() {
        this.rect = getBoundingClientRect(this.image);
      }

      reduceToAtoms() {
        this.image.remove();
        const pos = radios.indexOf(this.htmlElements.input);
        if (~pos) radios.splice(pos, 1);

        // Parent's loadedImages list is cleared in resetAllSettings, so no need to splice here.
        // But if `reduceToAtoms` is called directly, this needs to be here.
        // Let's keep it here for robustness, assuming it might be called independently.
        let pos2 = this.parent.loadedImages.indexOf(this);
        if (~pos2) {
          this.parent.loadedImages.splice(pos2, 1);
        }

        this._EXP_destroy(!0); // Call base class destroy
        this.parent.notify("info", "Imagen fantasma eliminada.");
      }
    }

    class TaskManager {
      isRunning;
      pixelList;
      parent;
      BotClientManager;
      singleColor;
      brushColor;
      brushSize;

      constructor(parent) {
        this.pixelList = [];
        this.singleColor = false;
        this.brushColor = "blue";
        this.brushSize = 2;
        this.parent = parent;
      }

      startDrawing() {
        this.BotClientManager = this.parent.findGlobal("BotClientManager")?.siblings[0];
        if (!this.BotClientManager || this.BotClientManager.children.length <= 0) {
            this.parent.notify("error", "No hay bots disponibles. Crea y conecta un bot primero.");
            return;
        }
        if (this.pixelList.length === 0) {
            this.parent.notify("warning", "La lista de píxeles para dibujar está vacía. Carga una imagen primero.");
            return;
        }

        this.isRunning = true;
        this.doTasks();
        this.parent.notify("info", "Dibujo iniciado.");
      }

      stopDrawing() {
        this.isRunning = false;
      }

      doTasks() {
        if (!this.BotClientManager || this.BotClientManager.children.length <= 0) {
            this.stopDrawing();
            this.parent.notify("warning", "Bots no disponibles, deteniendo dibujo.");
            return;
        }
        if (!this.isRunning) {
            this.parent.notify("info", "Dibujo detenido.");
            return;
        }
        if (this.pixelList.length <= 0) {
            this.stopDrawing();
            this.parent.notify("success", "Lista de píxeles completada. Dibujo finalizado.");
            return;
        }

        // Distribute tasks among available bots
        this.BotClientManager.children.forEach((botClientInterface, index) => {
          this.parseAndSendPixel(botClientInterface, index);
        });

        setTimeout(() => {
          this.doTasks();
        }, 1); // Small delay to prevent blocking main thread
      }

      parseAndSendPixel(botClientInterface, index) {
        if (this.pixelList.length <= 0) return;
        if (!botClientInterface.bot || !botClientInterface.bot.getReadyState()) return;

        // Take a pixel from the list (alternate ends to distribute workload)
        const task = index % 2 === 0 ? this.pixelList.shift() : this.pixelList.pop();
        if (task) {
            botClientInterface.bot.send(this.convertTasks(task));
            this.parent.htmlElements.pixelsLeftToDraw.value = this.pixelList.length;
        }
      }

      convertTasks(pixel) {
        const playerid = -1; // Assuming drawing as 'self' or a generic ID

        // Convert pixel coordinates (0-width/height) to game coordinates (0-100)
        // Ensure that drawModifierX/Y are correctly set up and not zero.
        if (!this.parent.DrawCanvasRect.drawModifierX || !this.parent.DrawCanvasRect.drawModifierY) {
            this.parent.notify("error", "Error: DrawCanvasRect no inicializado correctamente para escalar píxeles.");
            return null; // Or throw an error
        }

        const x1_game = pixel.x1 * this.parent.DrawCanvasRect.drawModifierX;
        const y1_game = pixel.y1 * this.parent.DrawCanvasRect.drawModifierY;
        const x2_game = pixel.x2 * this.parent.DrawCanvasRect.drawModifierX;
        const y2_game = pixel.y2 * this.parent.DrawCanvasRect.drawModifierY;

        const isactive = true;
        const size = pixel.size ?? this.brushSize;
        const pxColor = pixel.color; // This is an RGBA array [r,g,b,a]

        const color = this.singleColor
          ? this.brushColor // Use the user-defined single color
          : `rgba(${pxColor[0]},${pxColor[1]},${pxColor[2]},${parseFloat(pxColor[3] / 255).toFixed(2)})`; // Use original pixel color with alpha scaled 0-1

        const ispixel = false; // Sending as lines, not individual pixels in Drawaria terms

        // Drawaria's drawcmd expects coordinates as 0-1 range for X and Y, not 0-100.
        // It's usually `x/100` as seen in other scripts.
        let data = [
          "drawcmd",
          0, // Line command
          [x1_game / 100, y1_game / 100, x2_game / 100, y2_game / 100, isactive, -size, color, playerid, ispixel],
        ];

        return `${42}${JSON.stringify(data)}`;
      }
    }
})("QBit");


(function GhostCanvasAlgorithms() {
    const QBit = globalThis[arguments[0]];

    function sortByColor(pixel1, pixel2) {
      // Assuming pixel.color is an RGBA array
      const color1 = (pixel1.color[0] << 24) | (pixel1.color[1] << 16) | (pixel1.color[2] << 8) | pixel1.color[3];
      const color2 = (pixel2.color[0] << 24) | (pixel2.color[1] << 16) | (pixel2.color[2] << 8) | pixel2.color[3];
      return color1 - color2;
    }

    // These functions are utility but not directly used by the minifier
    function intToHex(number) {
      return number.toString(16).padStart(2, "0");
    }

    function rgbaArrayToHex(rgbaArray) {
      const r = intToHex(rgbaArray[0]);
      const g = intToHex(rgbaArray[1]);
      const b = intToHex(rgbaArray[2]);
      const a = intToHex(rgbaArray[3]); // Alpha channel
      return `#${r}${g}${b}${a}`;
    }

    // Function to compare colors with a given tolerance
    function areSameColor(colorArray1, colorArray2, allowedDifference = 8) {
      // Check if alpha is also within tolerance, important for transparency levels
      if (Math.abs(colorArray1[3] - colorArray2[3]) > allowedDifference) return false;

      var red = Math.abs(colorArray1[0] - colorArray2[0]);
      var green = Math.abs(colorArray1[1] - colorArray2[1]);
      var blue = Math.abs(colorArray1[2] - colorArray2[2]);

      if (blue > allowedDifference || green > allowedDifference || red > allowedDifference) return false;
      return true;
    }

    class GhostCanvasMinify extends QBit {
      static dummy1 = QBit.register(this);
      static dummy2 = QBit.bind(this, "GhostCanvas");

      constructor() {
        super("Minify", '<i class="fas fa-compress-arrows-alt"></i>');
        this.minOpacity = 20; // Pixels with alpha less than this are considered transparent
        this.maxFuzzyness = 32; // Color difference threshold for grouping pixels
        this.#onStartup();
      }

      #onStartup() {
        this.#loadInterface();
      }

      #loadInterface() {
        this.#row1();
        this.#row2();
        // this.#row3(); // These rows are empty in the original code, can be omitted if not needed
        // this.#row4();
      }

      #row1() {
        const row = domMake.Row();
        {
          const fuzzynessInput = domMake.Tree("input", {
            type: "number",
            title: "Fuzzyness",
            step: 1,
            min: 0,
            max: 255,
            value: this.maxFuzzyness, // Set initial value
          });
          const opacityInput = domMake.Tree("input", {
            type: "number",
            title: "Min Opacity", // Changed title for clarity
            step: 1,
            min: 0,
            max: 255,
            value: this.minOpacity, // Set initial value
          });

          fuzzynessInput.addEventListener("change", () => {
            this.maxFuzzyness = Number(fuzzynessInput.value);
            this.parent.notify("info", `Fuzzyness de color ajustada a: ${this.maxFuzzyness}`);
          });

          opacityInput.addEventListener("change", () => {
            this.minOpacity = Number(opacityInput.value);
            this.parent.notify("info", `Opacidad mínima para considerar sólido ajustada a: ${this.minOpacity}`);
          });

          row.appendAll(fuzzynessInput, opacityInput);
        }
        this.htmlElements.section.appendChild(row);
      }
      #row2() {
        const row = domMake.Row();
        {
          const minifyPixelsArrayButton = domMake.Button("Minify");

          minifyPixelsArrayButton.addEventListener("click", (event) => {
            this.minifyPixelsArray();
          });

          row.appendAll(minifyPixelsArrayButton);
        }
        this.htmlElements.section.appendChild(row);
      }
      // Removed #row3() and #row4() as they are empty in the original script

      /**
       * Groups individual pixels into horizontal line segments to reduce the number of draw commands.
       * Pixels are grouped if they are consecutive, on the same row, and have similar colors.
       * Transparent pixels are ignored.
       */
        minifyPixelsArray() {
            const pixelArray = this.parent.drawingManager.pixelList;
            if (pixelArray.length === 0) {
                this.parent.notify("info", "No hay píxeles para minificar. Carga una imagen en el lienzo fantasma primero.");
                return;
            }

            // Ensure pixels are sorted correctly for line grouping (by Y then by X)
            // The `getAllPixels` method already provides this ordering.
            // If the source of `pixelList` ever changes, this sort might be necessary:
            // pixelArray.sort((a, b) => a.y1 - b.y1 || a.x1 - b.x1);

            const newPixelArray = [];
            let currentSegmentStartPixel = null; // Stores the object of the first pixel in the current segment
            let currentSegmentColor = null;       // Stores the RGBA array of the current segment's color

            for (let i = 0; i < pixelArray.length; i++) {
                const currentPixel = pixelArray[i];
                const pixelColor = currentPixel.color; // RGBA array
                const isTransparent = pixelColor[3] < this.minOpacity; // Check against minOpacity

                if (isTransparent) {
                    // If currently building a solid segment, close it before moving to transparent pixel
                    if (currentSegmentStartPixel !== null) {
                        newPixelArray.push({
                            x1: currentSegmentStartPixel.x1,
                            y1: currentSegmentStartPixel.y1,
                            x2: pixelArray[i - 1].x1, // The X-coordinate of the last solid pixel of the segment
                            y2: currentSegmentStartPixel.y1,
                            color: currentSegmentColor,
                        });
                        currentSegmentStartPixel = null;
                        currentSegmentColor = null;
                    }
                    continue; // Skip transparent pixels
                }

                // If current pixel is solid
                if (currentSegmentStartPixel === null) {
                    // Start a new solid segment
                    currentSegmentStartPixel = currentPixel;
                    currentSegmentColor = pixelColor;
                } else {
                    // Check if current pixel can extend the existing segment
                    // To check consecutiveness, we look at the pixel *before* the current one in the original list.
                    const prevPixelInList = pixelArray[i - 1];

                    // Conditions for a segment break:
                    // 1. Not consecutive in X (there's a gap or previous was transparent)
                    // 2. Not on the same Y row as the segment start (new line)
                    // 3. Color changes significantly
                    const isConsecutiveX = currentPixel.x1 === (prevPixelInList.x1 + 1);
                    const sameRow = currentPixel.y1 === currentSegmentStartPixel.y1;
                    const similarColor = areSameColor(currentSegmentColor, pixelColor, this.maxFuzzyness);

                    if (!isConsecutiveX || !sameRow || !similarColor) {
                        // Segment ends here (at prevPixelInList), push the completed segment
                        newPixelArray.push({
                            x1: currentSegmentStartPixel.x1,
                            y1: currentSegmentStartPixel.y1,
                            x2: prevPixelInList.x1, // The X-coordinate of the last pixel *included* in the just-finished segment
                            y2: currentSegmentStartPixel.y1,
                            color: currentSegmentColor,
                        });

                        // Start a new segment with the current pixel
                        currentSegmentStartPixel = currentPixel;
                        currentSegmentColor = pixelColor;
                    }
                    // If conditions pass, the segment simply continues. No action needed here.
                }
            }

            // After loop, push any remaining active segment
            if (currentSegmentStartPixel !== null) {
                newPixelArray.push({
                    x1: currentSegmentStartPixel.x1,
                    y1: currentSegmentStartPixel.y1,
                    x2: pixelArray[pixelArray.length - 1].x1, // The X-coordinate of the very last pixel in the list
                    y2: currentSegmentStartPixel.y1,
                    color: currentSegmentColor,
                });
            }

            this.parent.setPixelList(newPixelArray);
            this.parent.notify("success", `Minificación completada. Líneas reducidas de ${pixelArray.length} a ${newPixelArray.length}.`);
        }


      minifyPixelsArray_alt() {
        const pixelArray = this.parent.drawingManager.pixelList;
        if (pixelArray.length === 0) {
            this.parent.notify("info", "No hay píxeles para minificar (alt).");
            this.parent.setPixelList([]);
            return;
        }

        const newPixelArray = [];
        let lastPixel = pixelArray[0];
        let currentLine = {
            x1: lastPixel.x1,
            y1: lastPixel.y1,
            x2: lastPixel.x2,
            y2: lastPixel.y2,
            color: lastPixel.color
        };
        const stepsize = this.parent.stepsize ?? 1; // Assuming parent has a stepsize, default to 1

        for (let i = 0; i < pixelArray.length; i += stepsize) {
          const currentPixel = pixelArray[i];

          // Check for row change or significant color change
          if (
              currentPixel.y1 !== currentLine.y1 ||
              !areSameColor(currentPixel.color, currentLine.color, this.maxFuzzyness) // Use fuzziness for alt method too
          ) {
            // End the current line segment
            currentLine.x2 = lastPixel.x2; // Last pixel's x2 (which is its x1 for single pixels)
            if (currentLine.color[3] >= this.minOpacity) { // Only add if solid enough
                newPixelArray.push(currentLine);
            }
            // Start a new line segment
            currentLine = {
                x1: currentPixel.x1,
                y1: currentPixel.y1,
                x2: currentPixel.x2,
                y2: currentPixel.y2,
                color: currentPixel.color
            };
          } else {
              // Extend the current line segment
              currentLine.x2 = currentPixel.x2;
          }
          lastPixel = currentPixel; // Update lastPixel for the next iteration
        }
        // Push the very last segment after the loop finishes
        if (currentLine.color[3] >= this.minOpacity) {
            newPixelArray.push(currentLine);
        }

        this.parent.setPixelList(newPixelArray);
        this.parent.notify("success", `Minificación (alt) completada. Líneas reducidas de ${pixelArray.length} a ${newPixelArray.length}.`);
      }
    }

    class GhostCanvasSort extends QBit {
      static dummy1 = QBit.register(this);
      static dummy2 = QBit.bind(this, "GhostCanvas");

      constructor() {
        super("Sort", '<i class="fas fa-sort-numeric-down"></i>');
        this.#onStartup();
      }

      #onStartup() {
        this.#loadInterface();
      }

      #loadInterface() {
        this.#row1();
        // this.#row2(); // Empty in original
        // this.#row3(); // Empty in original
        // this.#row4(); // Empty in original
      }

      #row1() {
        const row = domMake.Row();
        {
          const sortPixelsArrayButton = domMake.Button("Sort");

          sortPixelsArrayButton.addEventListener("click", (event) => {
            this.sortPixelsArray();
          });

          row.appendAll(sortPixelsArrayButton);
        }
        this.htmlElements.section.appendChild(row);
      }
      // Removed empty rows

      sortPixelsArray() {
        const pixelArray = this.parent.drawingManager.pixelList;
        if (pixelArray.length === 0) {
            this.parent.notify("info", "No hay píxeles para ordenar.");
            return;
        }

        // Sort by Y first, then by X, then by color for optimal line grouping and drawing order
        const newPixelArray = [...pixelArray].sort((a, b) => {
            if (a.y1 !== b.y1) {
                return a.y1 - b.y1;
            }
            if (a.x1 !== b.x1) {
                return a.x1 - b.x1;
            }
            // If pixels are at same (x,y), sort by color (e.g., if multiple layers of pixels exist)
            return sortByColor(a, b);
        });

        this.parent.setPixelList(newPixelArray);
        this.parent.notify("success", `Píxeles ordenados. Total: ${newPixelArray.length}.`);
      }
    }
})("QBit");

  (function BotClientInterface() {
    const QBit = globalThis[arguments[0]];
    const BotClient = QBit.findGlobal("BotClient");

    let botcount = 0;
    const radios = [];

    function getMasterId() {
      return document.querySelector(".playerlist-name-self")?.parentElement.dataset.playerid || 0;
    }

    function parseAvatarURL(arr = []) {
      return `https://drawaria.online/avatar/cache/${arr.length > 0 ? arr.join(".") : "default"}.jpg`;
    }

    class BotClientManager extends QBit {
      static dummy1 = QBit.register(this);
      static dummy2 = QBit.bind(this, "CubeEngine");

      constructor() {
        super(`BotClientManager`, '<i class="fas fa-robot"></i>');
        this.#onStartup();
      }

      #onStartup() {
        this.#loadInterface();
      }

      #loadInterface() {
        this.#row1();
      }

      #row1() {
        const row = domMake.IconList();
        {
          const id = generate.uuidv4();
          const createBotClientInterfaceInput = domMake.Tree("input", { type: "button", id: id, hidden: true });
          const createBotClientInterfaceLabel = domMake.Tree("label", { for: id, class: "icon", title: "Add Image" }, [
            domMake.Tree("i", { class: "fas fa-plus" }),
          ]);

          createBotClientInterfaceInput.addEventListener("click", () => {
            this.createBotClientInterface();
          });

          row.appendAll(createBotClientInterfaceLabel);
          row.appendAll(createBotClientInterfaceInput);
        }
        {
          const id = generate.uuidv4();
          const removeBotClientInterfaceInput = domMake.Tree("input", { type: "button", id: id, hidden: true });
          const removeBotClientInterfaceLabel = domMake.Tree("label", { for: id, class: "icon", title: "Add Image" }, [
            domMake.Tree("i", { class: "fas fa-minus" }),
          ]);

          removeBotClientInterfaceInput.addEventListener("click", () => {
            this.deleteBotClientInterface();
          });

          row.appendAll(removeBotClientInterfaceLabel);
          row.appendAll(removeBotClientInterfaceInput);
        }
        this.htmlElements.header.before(row);
      }

      createBotClientInterface() {
        const instance = this.loadExtension(BotClientInterface);
        instance.htmlElements.input.type = "radio";
        instance.htmlElements.input.name = "botClient";

        radios.push(instance.htmlElements.input);
        instance.htmlElements.input.addEventListener("change", (event) => {
          radios.forEach(function (radio) {
            document.body.querySelector(`label[for="${radio.id}"]`).classList.remove("active");
          });
          instance.htmlElements.label.classList.add("active");
        });

        return instance;
      }

      deleteBotClientInterface() {
        const input = document.body.querySelector(`input[name="botClient"]:checked`);

        const matches = this.children.filter((child) => {
          return child.htmlElements.input === input;
        });
        if (matches.length <= 0) return;

        const instance = matches[0];

        instance.bot.disconnect();

        const labelPos = radios.indexOf(instance.htmlElements.input);
        if (~labelPos) radios.splice(labelPos, 1);

        instance._EXP_destroy(!0);
      }
    }

    class BotClientInterface extends QBit {
      static dummy1 = QBit.register(this);
      // static dummy2 = QBit.bind(this, 'CubeEngine');
      // static dummy3 = QBit.bind(this, 'CubeEngine');

      constructor() {
        super(`Bot${botcount}`, '<i class="fas fa-robot"></i>');
        this.bot = new BotClient();
        this.#onStartup();
      }

      #onStartup() {
        this.#loadInterface();
        this.setClientName(this.bot.name);
        this.setClientIcon(this.bot.avatar);
      }

      #loadInterface() {
        this.#row1();
      }

      #row1() {
        const row = domMake.Row();
        {
          let join_button = domMake.Button("Enter");
          let leave_button = domMake.Button("Leave");

          join_button.addEventListener("click", (event) => {
            this.bot.enterRoom(document.querySelector("#invurl").value);
          });

          leave_button.addEventListener("click", (event) => {
            this.bot.disconnect();
          });

          row.appendAll(join_button, leave_button);
        }
        this.htmlElements.section.appendChild(row);
      }

      setClientName(name) {
        this.setName(name);
        this.bot.name = name;
      }

      setClientIcon(icon) {
        this.setIcon(`<img src="${parseAvatarURL(this.bot.avatar)}">`);
        this.bot.avatar = icon;
      }
    }
  })("QBit");

  (function BotClientInteractions() {
    const QBit = globalThis[arguments[0]];

    class BotPersonality extends QBit {
      static dummy1 = QBit.register(this);
      static dummy2 = QBit.bind(this, "BotClientInterface");

      constructor() {
        super("Personality", '<i class="fas fa-user-cog"></i>');
        this.#onStartup();
      }

      #onStartup() {
        this.#loadInterface();
      }

      #loadInterface() {
        this.#row1();
        this.#row2();
      }

      #row1() {
        const row = domMake.Row();
        {
          let botName = domMake.Tree("input", { type: "text", placeholder: "Your Bots name" });
          let botNameAccept = domMake.Tree("button", { class: "icon" }, [domMake.Tree("i", { class: "fas fa-check" })]);

          botNameAccept.addEventListener("click", (event) => {
            this.parent.setClientName(botName.value);
          });

          row.appendAll(botName, botNameAccept);
        }
        this.htmlElements.section.appendChild(row);
      }

      #row2() {
        let id = generate.uuidv4();
        const row = domMake.Row();
        {
          let botAvatarUpload = domMake.Tree("input", { type: "file", id: id, hidden: true });
          let botAvatarAccept = domMake.Tree("label", { for: id, class: "btn btn-outline-secondary" }, [
            "Upload BotAvatar",
          ]);

          const localThis = this;
          function onChange() {
            if (!this.files || !this.files[0]) return;
            let myFileReader = new FileReader();
            myFileReader.addEventListener("load", (e) => {
              let a = e.target.result.replace("image/gif", "image/png");
              fetch("https://drawaria.online/uploadavatarimage", {
                method: "POST",
                body: "imagedata=" + encodeURIComponent(a) + "&fromeditor=true",
                headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
              }).then((res) =>
                res.text().then((body) => {
                  localThis.parent.setClientIcon(body.split("."));
                })
              );
            });
            myFileReader.readAsDataURL(this.files[0]);
          }
          botAvatarUpload.addEventListener("change", onChange);

          row.appendAll(botAvatarUpload, botAvatarAccept);
        }
        this.htmlElements.section.appendChild(row);
      }
    }

    class BotSozials extends QBit {
      static dummy1 = QBit.register(this);
      static dummy2 = QBit.bind(this, "BotClientInterface");

      constructor() {
        super("Socialize", '<i class="fas fa-comment-dots"></i>');
        this.#onStartup();
      }

      #onStartup() {
        this.#loadInterface();
      }

      #loadInterface() {
        this.#row1();
        this.#row2();
      }

      #row1() {
        const row = domMake.Row();
        {
          let messageClear_button = domMake.Button('<i class="fas fa-strikethrough"></i>');
          let messageSend_button = domMake.Button('<i class="fas fa-paper-plane"></i>');
          let message_input = domMake.Tree("input", { type: "text", placeholder: "message..." });

          messageClear_button.classList.add("icon");
          messageSend_button.classList.add("icon");

          messageClear_button.addEventListener("click", (event) => {
            message_input.value = "";
          });

          messageSend_button.addEventListener("click", (event) => {
            this.parent.bot.emit("chatmsg", message_input.value);
          });

          message_input.addEventListener("keypress", (event) => {
            if (event.keyCode != 13) return;
            this.parent.bot.emit("chatmsg", message_input.value);
          });

          row.appendAll(messageClear_button, message_input, messageSend_button);
        }
        this.htmlElements.section.appendChild(row);
      }

      #row2() {
        const row = domMake.IconList();
        // row.classList.add('nowrap');
        {
          document
            .querySelectorAll("#gesturespickerselector .gesturespicker-container .gesturespicker-item")
            .forEach((node, index) => {
              let clone = node.cloneNode(true);
              clone.classList.add("icon");
              clone.addEventListener("click", (event) => {
                this.parent.bot.emit("sendgesture", index);
              });
              row.appendChild(clone);
            });
        }
        this.htmlElements.section.appendChild(row);
      }
    }

    class BotTokenGiver extends QBit {
      static dummy1 = QBit.register(this);
      static dummy2 = QBit.bind(this, "BotClientInterface");

      constructor() {
        super("Tokken", '<i class="fas fa-thumbs-up"></i>');
        this.#onStartup();
      }

      #onStartup() {
        this.#loadInterface();
      }

      #loadInterface() {
        this.#row1();
        this.#row2();
      }

      #row1() {
        const row = domMake.IconList();
        // row.classList.add('nowrap');
        {
          let listOfTokens = [
            '<i class="fas fa-thumbs-up"></i>',
            '<i class="fas fa-heart"></i>',
            '<i class="fas fa-paint-brush"></i>',
            '<i class="fas fa-cocktail"></i>',
            '<i class="fas fa-hand-peace"></i>',
            '<i class="fas fa-feather-alt"></i>',
            '<i class="fas fa-trophy"></i>',
            '<i class="fas fa-mug-hot"></i>',
            '<i class="fas fa-gift"></i>',
          ];
          listOfTokens.forEach((token, index) => {
            let tokenSend_button = domMake.Button(token);
            tokenSend_button.classList.add("icon");
            tokenSend_button.addEventListener("click", () => {
              this.parent.bot.room.players.forEach((player) => {
                this.parent.bot.emit("settoken", player.id, index);
              });
            });
            row.appendChild(tokenSend_button);
          });
        }
        this.htmlElements.section.appendChild(row);
      }

      #row2() {
        const row = domMake.Row();
        {
          let toggleStatus_button = domMake.Button("Toggle Status");
          toggleStatus_button.addEventListener("click", () => {
            this.parent.bot.attributes.status = !this.parent.bot.attributes.status;
            let status = this.parent.bot.attributes.status;
            toggleStatus_button.classList[status ? "add" : "remove"]("active");
            this.parent.bot.emit("setstatusflag", 0, status);
            this.parent.bot.emit("setstatusflag", 1, status);
            this.parent.bot.emit("setstatusflag", 2, status);
            this.parent.bot.emit("setstatusflag", 3, status);
            this.parent.bot.emit("setstatusflag", 4, status);
          });
          row.appendChild(toggleStatus_button);
        }
        this.htmlElements.section.appendChild(row);
      }
    }

    class BotCanvasAvatar extends QBit {
      static dummy1 = QBit.register(this);
      static dummy2 = QBit.bind(this, "BotClientInterface");

      constructor() {
        super("SpawnAvatar", '<i class="fas fa-user-circle"></i>');
        this.#onStartup();
      }

      #onStartup() {
        this.#loadInterface();
      }

      #loadInterface() {
        this.#row1();
      }

      #row1() {
        const row = domMake.Row();
        {
          // Spawn && Move Avatar
          let avatarPosition = { x: 0, y: 0 };

          let avatarSpawn_button = domMake.Button('<i class="fas fa-exchange-alt"></i>');
          let avatarChange_button = domMake.Button('<i class="fas fa-retweet"></i>');
          let avatarPositionX_button = domMake.Tree("input", {
            type: "number",
            value: 2,
            min: 2,
            max: 98,
            title: "Left",
          });
          let avatarPositionY_button = domMake.Tree("input", {
            type: "number",
            value: 2,
            min: 2,
            max: 98,
            title: "Top",
          });

          avatarSpawn_button.addEventListener("click", (event) => {
            this.parent.bot.emit("spawnavatar");
            this.parent.bot.attributes.spawned = !this.parent.bot.attributes.spawned;
          });

          avatarChange_button.addEventListener("click", (event) => {
            this.parent.bot.emit("setavatarprop");
            this.parent.bot.attributes.rounded = !this.parent.bot.attributes.rounded;
          });

          avatarPositionX_button.addEventListener("change", (event) => {
            avatarPosition.x = avatarPositionX_button.value;
            this.parent.bot.emit("moveavatar", avatarPosition.x, avatarPosition.y);
          });

          avatarPositionY_button.addEventListener("change", (event) => {
            avatarPosition.y = avatarPositionY_button.value;
            this.parent.bot.emit("moveavatar", avatarPosition.x, avatarPosition.y);
          });

          avatarSpawn_button.title = "Spawn Avatar";
          avatarChange_button.title = "Toggle Round Avatar";

          row.appendAll(avatarSpawn_button, avatarPositionX_button, avatarPositionY_button, avatarChange_button);
        }
        this.htmlElements.section.appendChild(row);
      }
    }

    function getMyId() {
      return document.querySelector(".playerlist-name-self")?.parentElement.dataset.playerid || 0;
    }

    function parseAvatarURL(arr = []) {
      return `https://drawaria.online/avatar/cache/${arr.length > 0 ? arr.join(".") : "default"}.jpg`;
    }
  })("QBit");


  // --- NEW FUNCTIONALITIES START HERE ---

// START SMART

(function BotSentinel_PersonalityAI_Module() {
    const QBit = globalThis[arguments[0]];

    // Helper function to create a labeled toggle switch manually
    function createToggle(labelText, callback, initialChecked = false) {
        const row = domMake.Row();
        row.style.alignItems = 'center';
        row.style.justifyContent = 'space-between';

        const labelSpan = domMake.Tree("span", {}, [labelText]);

        const checkboxId = "sentinel-toggle-" + (Math.random() * 1e9 | 0);
        const checkbox = domMake.Tree("input", { type: "checkbox", id: checkboxId, hidden: true });
        // Set initial state
        if (initialChecked) {
            checkbox.checked = true;
        }

        const indicatorLabel = domMake.Tree("label", {
            for: checkboxId,
            class: "icon",
            style: `
                width: 24px;
                height: 24px;
                min-width: unset;
                min-height: unset;
                border: 1px solid var(--CE-color);
                border-radius: .25rem;
                display: flex;
                align-items: center;
                justify-content: center;
                cursor: pointer;
                transition: background-color 0.2s ease, border-color 0.2s ease;
                background-color: ${initialChecked ? 'var(--info)' : 'var(--secondary)'};
            `
        });

        // Set initial icon based on initialChecked
        indicatorLabel.innerHTML = initialChecked
            ? '<i class="fas fa-check-square" style="font-size: 1.2em; color: var(--success);"></i>'
            : '<i class="fas fa-square" style="font-size: 1.2em;"></i>';

        checkbox.addEventListener('change', (event) => {
            const checked = event.target.checked;
            if (checked) {
                indicatorLabel.innerHTML = '<i class="fas fa-check-square" style="font-size: 1.2em; color: var(--success);"></i>';
                indicatorLabel.style.backgroundColor = 'var(--info)';
            } else {
                indicatorLabel.innerHTML = '<i class="fas fa-square" style="font-size: 1.2em;"></i>';
                indicatorLabel.style.backgroundColor = 'var(--secondary)';
            }
            if (callback && typeof callback === 'function') {
                callback(checked);
            }
        });

        row.appendAll(labelSpan, domMake.Tree("div", {style: "flex-shrink: 0;"}, [checkbox, indicatorLabel]));
        return row;
    }

    QBit.Styles.addRules([
        `#${QBit.identifier} .sentinel-section {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 10px;
            background-color: var(--CE-bg_color);
        }`,
        `#${QBit.identifier} .sentinel-section-title {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--dark-blue-title);
            text-align: center;
        }`,
        `#${QBit.identifier} .sentinel-control-group {
            display: flex;
            gap: 10px;
            margin-top: 8px;
            align-items: center;
        }`,
        `#${QBit.identifier} .sentinel-control-group > select,
         #${QBit.identifier} .sentinel-control-group > input[type="number"],
         #${QBit.identifier} .sentinel-control-group > button {
            flex-grow: 1;
            padding: 5px;
            box-sizing: border-box;
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            background-color: var(--CE-bg_color);
            color: var(--CE-color);
        }`,
        `#${QBit.identifier} .sentinel-control-group label {
             flex-shrink: 0;
             margin-right: 5px;
        }`,
        `#${QBit.identifier} .sentinel-follow-button.active {
             background-color: var(--warning);
             color: white;
        }`,
        // Styles for Bad Girl section
        `#${QBit.identifier} .bad-girl-section-title {
            color: var(--danger);
        }`,
        `#${QBit.identifier} .bad-girl-toggle-button.active {
            background-color: var(--danger);
            color: white;
        }`,
        `#${QBit.identifier} .bad-girl-textarea {
            width: 100%;
            min-height: 80px;
            margin-top: 5px;
            background-color: var(--input-bg);
            color: var(--dark-text);
            border: 1px solid var(--input-border-blue);
            padding: 5px;
            box-sizing: border-box;
        }`,
         `#${QBit.identifier} .bad-girl-controls {
            display: flex;
            gap: 10px;
            align-items: center;
            margin-top: 5px;
        }`,
        `#${QBit.identifier} .bad-girl-controls label {
             margin-right: 5px;
        }`
    ]);

    class BotSentinel extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        // Sentinel Properties
        _bots = [];
        _botManagerInstance = null;
        _gameCanvas = null;
        _followTarget = { id: null, interval: null };
        _naturalMovementInterval = null;
        _activeToggles = {
            naturalMovement: false,
            reactiveChat: false,
            smartGestures: false,
            badGirlSpam: false // New toggle for Bad Girl functionality
        };
        _chatCooldown = new Map();
        _gestureCooldown = new Map();

        // UI references
        _ui = {
            playerDropdown: null,
            followButton: null,
            naturalMovementToggleCheckbox: null,
            reactiveChatToggleCheckbox: null,
            smartGesturesToggleCheckbox: null,
            personalitySelect: null,
            // Bad Girl UI elements
            badGirlToggleButton: null,
            messageTextarea: null,
            intervalInput: null,
            saveMessagesButton: null,
        };

        // Bad Girl Properties
        _spamInterval = null;
        _intervalTime = 700;
        _messageList = [
            "Eres muy lento", "Novato", "Jaja, qué mal", "Inténtalo de nuevo",
            "¿Eso es todo lo que tienes?", "Aburrido...", "Me duermo", "Puedes hacerlo mejor",
            "...", "Casi, pero no"
        ];

        // Personality Chat & Gesture Data
        _personalities = {
            Amigable: {
                spanish_greetings: ["¡Hola a todos!", "¡Buenas!", "¿Qué tal?", "Hey! Un gusto estar aquí 😊"],
                spanish_acknowledgements: ["Si!", "Claro!", "Entendido!", "Asi es!"],
                spanish_questions: ["Como estás?", "Y tu?", "¿Que tal?"],
                spanish_laughter: ["XD", "Jaja", "LOL"],
                spanish_general: ["Que?", "Bueno...", "Pero..."],
                spanish_congrats: ["¡Bien hecho, {player}!", "¡Excelente!", "¡Esa era!", "Felicidades, {player}!"],
                spanish_farewell: ["¡Adiós!", "Nos vemos", "¡Hasta la próxima!", "Chao 👋"],
                spanish_playerJoin: ["¡Bienvenido, {player}!", "Hola {player}!", "Mira quién llegó, {player} 👋"],
                spanish_playerLeave: ["Adiós, {player}!", "{player} se fue 😔", "Chao {player}"],

                english_greetings: ["Hi!", "Hello!", "Hey there!", "Nice to see you 😊"],
                english_acknowledgements: ["Yes!", "Got it!", "Right!"],
                english_questions: ["How are you?", "And you?", "What's up?"],
                english_laughter: ["LOL", "Haha", "XD", "Omg!"],
                english_general: ["What?", "Well...", "But..."],
                english_congrats: ["Good job, {player}!", "Excellent!", "That was it!", "Congrats, {player}!"],
                english_farewell: ["Bye!", "See ya!", "Later!", "So long 👋"],
                english_playerJoin: ["Welcome, {player}!", "Hi {player}!", "Look who's here, {player} 👋"],
                english_playerLeave: ["Bye, {player}!", "{player} left 😔", "See ya {player}"],

                gestures: {
                    greeting: 5,
                    acknowledgement: 11,
                    question: 10,
                    laughter: 7,
                    general: 17,
                    congrats: 19,
                    playerJoin: 5,
                    playerLeave: 3,
                    drawing: 4,
                    goodjob_drawing: 0
                }
            },
            Competitivo: {
                spanish_greetings: ["He llegado.", "Prepárense para dibujar.", "A ver quién gana."],
                spanish_acknowledgements: ["Si.", "Ok.", "Correcto."],
                spanish_questions: ["¿Estás listo?", "Quién sigue?", "¿Qué dibujas?"],
                spanish_laughter: ["Jaja.", "Easy."],
                spanish_general: ["..."],
                spanish_congrats: ["Nada mal, {player}.", "Correcto.", "Uno menos.", "Ok, adivinaste."],
                spanish_farewell: ["Me retiro.", "Suficiente por hoy.", "GG."],
                spanish_playerJoin: ["Otro rival...", "Llegó {player}...", "Hola {player}."],
                spanish_playerLeave: ["Uno menos.", "{player} se fue.", "OK, {player}."],

                english_greetings: ["I'm here.", "Get ready to draw.", "Who's next?"],
                english_acknowledgements: ["Yes.", "Ok.", "Correct."],
                english_questions: ["You ready?", "Who's drawing?", "What is it?"],
                english_laughter: ["Haha.", "Easy."],
                english_general: ["..."],
                english_congrats: ["Not bad, {player}.", "Correct.", "One less.", "Okay, you got it."],
                english_farewell: ["I'm out.", "Enough for today.", "GG."],
                english_playerJoin: ["Another rival...", "{player} arrived...", "Hi {player}."],
                english_playerLeave: ["One less.", "{player} left.", "Okay {player}."],

                gestures: {
                    greeting: 1,
                    acknowledgement: 12,
                    question: 10,
                    laughter: 6,
                    general: 16,
                    congrats: 0,
                    playerJoin: 13,
                    playerLeave: 3,
                    drawing: 12,
                    goodjob_drawing: 0
                }
            },
            Neutral: {
                spanish_greetings: ["Hola.", "Saludos."],
                spanish_acknowledgements: ["Si.", "Ok."],
                spanish_questions: ["?", "Cómo?"],
                spanish_laughter: ["Jeje."],
                spanish_general: ["..."],
                spanish_congrats: ["Bien, {player}.", "Correcto."],
                spanish_farewell: ["Adiós."],
                spanish_playerJoin: ["{player} se unió."],
                spanish_playerLeave: ["{player} se fue."],

                english_greetings: ["Hi.", "Greetings."],
                english_acknowledgements: ["Yes.", "Ok."],
                english_questions: ["?", "How?"],
                english_laughter: ["Hehe."],
                english_general: ["..."],
                english_congrats: ["Good, {player}.", "Correct."],
                english_farewell: ["Bye."],
                english_playerJoin: ["{player} joined."],
                english_playerLeave: ["{player} left."],

                gestures: {
                    greeting: 11,
                    acknowledgement: 11,
                    question: 10,
                    laughter: 5,
                    general: 17,
                    congrats: 11,
                    playerJoin: 11,
                    playerLeave: 11,
                    drawing: 8,
                    goodjob_drawing: 11
                }
            }
        };

        // Bound Handlers
        _toggleNaturalMovement = this._toggleNaturalMovement.bind(this);
        _toggleSmartFollow = this._toggleSmartFollow.bind(this);
        _updatePlayerDropdown = this._updatePlayerDropdown.bind(this);
        _handleReactiveChat = this._handleReactiveChat.bind(this);
        _handleCorrectGuess = this._handleCorrectGuess.bind(this);
        _handlePlayerJoin = this._handlePlayerJoin.bind(this);
        _handlePlayerLeave = this._handlePlayerLeave.bind(this);
        _handleTurnEnd = this._handleTurnEnd.bind(this);
        _executeNaturalMovement = this._executeNaturalMovement.bind(this);
        _followLogic = this._followLogic.bind(this);
        _moveBotSmoothly = this._moveBotSmoothly.bind(this);
        _handleTurnBeginDraw = this._handleTurnBeginDraw.bind(this);
        _handleWordSelected = this._handleWordSelected.bind(this);
        _toggleBadGirlSpam = this._toggleBadGirlSpam.bind(this); // New bound handler
        _updateMessageList = this._updateMessageList.bind(this); // New bound handler

        constructor() {
            super("Bot Sentinel: Personalidad AI", '<i class="fas fa-cog"></i>');
            this._loadInterface();
            this._initializeBotListeners();
            this._gameCanvas = document.getElementById('canvas');
            // Populate initial message list from properties
            this._ui.messageTextarea.value = this._messageList.join("\n");
            this._ui.intervalInput.value = this._intervalTime;
        }

        _initializeBotListeners() {
            const botManagerClass = this.findGlobal("BotClientManager");

            if (!botManagerClass || !botManagerClass.siblings || botManagerClass.siblings.length === 0) {
                setTimeout(() => this._initializeBotListeners(), 500);
                return;
            }

            this._botManagerInstance = botManagerClass.siblings[0];
            this.notify("info", "BotClientManager encontrado. Intentando rastrear bots...");

            // Initial scan for bots and add listeners
            setTimeout(() => {
                 this._botManagerInstance.children.forEach(botInterface => {
                      if (botInterface.bot) {
                           this._addBot(botInterface.bot);
                      } else {
                           this.notify("warning", "Se encontró un elemento en BotClientManager que no parece ser un BotClientInterface o no tiene una instancia de bot.");
                      }
                 });

                 if (this._bots.length === 0) {

                 } else {
                      this.notify("info", `Se encontraron ${this._bots.length} bots iniciales.`);
                      this._updatePlayerDropdown();
                 }
            }, 1000);

            // Observe player list for updates to dropdown and bot tracking
            const playerListElement = document.getElementById("playerlist");
             if (playerListElement) {
                 let playerListObserverTimer;
                 const playerListObserver = new MutationObserver(() => {
                     clearTimeout(playerListObserverTimer);
                     playerListObserverTimer = setTimeout(() => {
                         this._updatePlayerDropdown();
                     }, 200);
                 });
                 playerListObserver.observe(playerListElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['data-playerid', 'data-loggedin', 'style'] });
                 this.notify("info", "Observador de lista de jugadores adjunto para actualizaciones de dropdown y spawnedavatar.");
             } else {
                  this.notify("warning", "Elemento de lista de jugadores no encontrado. El dropdown de jugadores podría no actualizarse correctamente.");
             }

             this.notify("info", "Inicialización de Bot Sentinel completa.");
        }

        _loadInterface() {
            const container = domMake.Tree("div");

            // --- Section: Movimiento Avanzado ---
            const movementSection = domMake.Tree("div", { class: "sentinel-section" });
            movementSection.appendChild(domMake.Tree("div", { class: "sentinel-section-title" }, ["Movimiento Avanzado"]));

            const naturalMovementToggleRow = createToggle("Movimiento Natural", (checked) => this._toggleNaturalMovement(checked));
            this._ui.naturalMovementToggleCheckbox = naturalMovementToggleRow.querySelector('input[type="checkbox"]');
            movementSection.appendChild(naturalMovementToggleRow);

            const followGroup = domMake.Tree("div", { class: "sentinel-control-group" });
            this._ui.playerDropdown = domMake.Tree("select", {style: "flex-grow: 1;"});
            followGroup.appendChild(this._ui.playerDropdown);

            this._ui.followButton = domMake.Button("Seguir");
            this._ui.followButton.classList.add("sentinel-follow-button");
            this._ui.followButton.addEventListener("click", this._toggleSmartFollow);
            followGroup.appendChild(this._ui.followButton);
            movementSection.appendChild(followGroup);
            container.appendChild(movementSection);

            // --- Section: Interacción Inteligente ---
            const interactionSection = domMake.Tree("div", { class: "sentinel-section" });
            interactionSection.appendChild(domMake.Tree("div", { class: "sentinel-section-title" }, ["Interacción Inteligente"]));

            const reactiveChatToggle = createToggle("Chat Reactivo", (checked) => this._activeToggles.reactiveChat = checked);
            this._ui.reactiveChatToggleCheckbox = reactiveChatToggle.querySelector('input[type="checkbox"]');
            interactionSection.appendChild(reactiveChatToggle);

            const smartGesturesToggle = createToggle("Gestos Inteligentes", (checked) => this._activeToggles.smartGestures = checked);
            this._ui.smartGesturesToggleCheckbox = smartGesturesToggle.querySelector('input[type="checkbox"]');
            interactionSection.appendChild(smartGesturesToggle);

            const personalityGroup = domMake.Tree("div", { class: "sentinel-control-group" });
            personalityGroup.appendChild(domMake.Tree("label", {}, ["Personalidad:"]));
            this._ui.personalitySelect = domMake.Tree("select", {style: "flex-grow: 1;"});
            Object.keys(this._personalities).forEach(p => {
                this._ui.personalitySelect.appendChild(domMake.Tree("option", { value: p }, [p]));
            });
            personalityGroup.appendChild(this._ui.personalitySelect);
            interactionSection.appendChild(personalityGroup);
            container.appendChild(interactionSection);

            // --- Section: Modo 'Chica Mala' (Spam) ---
            const badGirlSection = domMake.Tree("div", { class: "sentinel-section" });
            badGirlSection.appendChild(domMake.Tree("div", { class: "sentinel-section-title bad-girl-section-title" }, ["Modo 'Chica Mala' (Spam)"]));

            this._ui.badGirlToggleButton = domMake.Button('<i class="fas fa-play-circle"></i> Iniciar Spam');
            this._ui.badGirlToggleButton.classList.add("bad-girl-toggle-button");
            this._ui.badGirlToggleButton.addEventListener("click", () => this._toggleBadGirlSpam());
            badGirlSection.appendChild(this._ui.badGirlToggleButton);

            badGirlSection.appendChild(domMake.Tree("label", { style: "margin-top: 10px; display: block;" }, ["Lista de mensajes (uno por línea):"]));
            this._ui.messageTextarea = domMake.Tree("textarea", {
                class: "bad-girl-textarea",
                placeholder: "Escribe aquí tus mensajes, uno por línea."
            });
            badGirlSection.appendChild(this._ui.messageTextarea);

            const controlsDiv = domMake.Tree("div", { class: "bad-girl-controls" });
            const intervalLabel = domMake.Tree("label", { for: "bad-girl-interval" }, ["Intervalo (ms):"]);
            this._ui.intervalInput = domMake.Tree("input", {
                type: "number",
                id: "bad-girl-interval",
                value: this._intervalTime,
                min: "100",
                step: "50",
                style: "width: 80px;"
            });
            this._ui.intervalInput.addEventListener("change", (e) => {
                const newTime = parseInt(e.target.value);
                if (newTime >= 100) {
                    this._intervalTime = newTime;
                    this.notify("info", `Intervalo de spam actualizado a ${this._intervalTime}ms.`);
                    if (this._spamInterval) {
                        this._toggleBadGirlSpam(); // Stop
                        this._toggleBadGirlSpam(); // Start with new value
                    }
                }
            });

            this._ui.saveMessagesButton = domMake.Button("Guardar Lista");
            this._ui.saveMessagesButton.addEventListener("click", this._updateMessageList);

            controlsDiv.appendAll(intervalLabel, this._ui.intervalInput, this._ui.saveMessagesButton);
            badGirlSection.appendChild(controlsDiv);
            container.appendChild(badGirlSection);

            this.htmlElements.section.appendChild(container);
        }

        _addBot(botInstance) {
            if (!botInstance || this._bots.some(b => b === botInstance)) {
                return;
            }

            const isManagedBot = this._botManagerInstance?.children.some(bi => bi.bot === botInstance);
            if (!isManagedBot) {
                this.notify("warning", `Se intentó añadir una instancia de bot (${botInstance?.name || 'Desconocido'}) que no es gestionada por BotClientManager.`);
                return;
            }

            this._bots.push(botInstance);
            this.notify("log", `Bot Sentinel ahora vigila a: ${botInstance.name}`);

            const sentinelListeners = [
                { event: "bc_chatmessage", callback: (data) => this._handleReactiveChat(botInstance, { id: data[0], name: data[1], message: data[2] }) },
                { event: "uc_turn_wordguessedlocalThis", callback: (data) => this._handleCorrectGuess(data) },
                { event: "bc_playernew", callback: (data) => this._handlePlayerJoin({ id: data[0], name: data[1] }) },
                { event: "bc_playerleft", callback: (data) => this._handlePlayerLeave({ id: data[0], name: data[1] }) },
                { event: "bc_turn_results", callback: (data) => this._handleTurnEnd(botInstance, data) },
                { event: "uc_turn_begindraw", callback: (data) => this._handleTurnBeginDraw(botInstance, data) },
                { event: "uc_turn_selectword", callback: (data) => this._handleWordSelected(botInstance, data) },
            ];

            sentinelListeners.forEach(listener => {
                 const exists = botInstance.customObservers.some(obs => obs.event === listener.event && obs.callback === listener.callback);
                 if (!exists) {
                      botInstance.customObservers.push(listener);
                 }
            });
        }

        _removeBot(botInstance) {
            const initialCount = this._bots.length;
            this._bots = this._bots.filter(b => b !== botInstance);
            if (this._bots.length < initialCount) {
                this.notify("log", `Bot Sentinel ya no vigila a: ${botInstance.name}`);
                 if (this._followTarget.id === botInstance.id) {
                      this.notify("info", `El bot seguido (${botInstance.name}) ha sido removido. Deteniendo seguimiento.`);
                      this._toggleSmartFollow();
                 }
                 if (this._bots.length === 0) {
                      this.notify("warning", "Todos los bots rastreados han sido removidos. Bot Sentinel en espera.");
                      // Reset all toggles if no bots are managed
                      this._toggleNaturalMovement(false);
                      this._activeToggles.reactiveChat = false;
                      this._activeToggles.smartGestures = false;
                      this._toggleBadGirlSpam(false);

                      if(this._ui.naturalMovementToggleCheckbox) this._ui.naturalMovementToggleCheckbox.checked = false;
                      if(this._ui.reactiveChatToggleCheckbox) this._ui.reactiveChatToggleCheckbox.checked = false;
                      if(this._ui.smartGesturesToggleCheckbox) this._ui.smartGesturesToggleCheckbox.checked = false;
                      if(this._ui.badGirlToggleButton) this._ui.badGirlToggleButton.classList.remove("active");
                      this._ui.badGirlToggleButton.innerHTML = '<i class="fas fa-play-circle"></i> Iniciar Spam';
                 }
            }
        }

        // --- Helper: Get Bot Instance ---
        _getBot(requireConnected = true, skipNotification = false) {
            if (!this._botManagerInstance) {
                if (!skipNotification) this.notify("warning", "No hay instancias de 'BotClientManager'. Por favor, crea un bot desde 'CubeEngine'.");
                return null;
            }

            const botClientInterfaces = this._botManagerInstance.children;
            let activeBotClientInterface = null;

            const selectedBotInput = document.querySelector('input[name="botClient"]:checked');
            if (selectedBotInput) {
                activeBotClientInterface = botClientInterfaces.find(bci => bci.htmlElements.input === selectedBotInput);
            }

            if (!activeBotClientInterface && botClientInterfaces.length > 0) {
                activeBotClientInterface = botClientInterfaces[0]; // Default to the first bot
                if (!skipNotification) this.notify("info", `No se seleccionó un bot. Usando el primero: ${activeBotClientInterface.getName()}.`);
            }

            if (!activeBotClientInterface) {
                 if (!skipNotification) this.notify("warning", "No se encontró ningún bot activo.");
                 return null;
            }

            if (requireConnected && (!activeBotClientInterface.bot || !activeBotClientInterface.bot.getReadyState())) {
                if (!skipNotification) this.notify("warning", `El bot "${activeBotClientInterface.getName()}" no está conectado y listo.`);
                return null;
            }

            return activeBotClientInterface.bot;
        }

        _updatePlayerDropdown() {
            if (!this._ui.playerDropdown) return;

            if (this._botManagerInstance) {
                 this._botManagerInstance.children.forEach(botInterface => {
                      if (botInterface.bot) {
                           this._addBot(botInterface.bot);
                      }
                 });
                 // Clean up bots that are no longer managed
                 const managedBotInstances = new Set(this._botManagerInstance.children.map(bi => bi.bot).filter(b => b !== undefined));
                 this._bots.filter(bot => !managedBotInstances.has(bot)).forEach(botToRemove => this._removeBot(botToRemove));
            }

            const anyConnectedBot = this._bots.find(bot => bot.getReadyState());
            const players = anyConnectedBot?.room?.players || [];

            const myBotIds = new Set(this._bots.map(b => b.id));
            const humanPlayers = players.filter(p => !myBotIds.has(p.id) && p.id !== 0 && p.name && p.id !== undefined);

            const currentSelection = this._ui.playerDropdown.value;
            this._ui.playerDropdown.innerHTML = "";

             if (humanPlayers.length === 0) {
                 this._ui.playerDropdown.appendChild(domMake.Tree("option", { value: "" }, ["No hay jugadores"]));
                 this._ui.playerDropdown.disabled = true;
                 this._ui.followButton.disabled = true;
                 if (this._followTarget.id !== null && !humanPlayers.some(p => String(p.id) === currentSelection)) {
                     this.notify("info", `Jugador seguido (${currentSelection}) abandonó o no válido. Deteniendo seguimiento.`);
                     this._toggleSmartFollow();
                 }
                 return;
             }

            this._ui.playerDropdown.disabled = false;
            this._ui.followButton.disabled = false;

            humanPlayers.forEach(p => {
                const option = domMake.Tree("option", { value: p.id }, [p.name]);
                this._ui.playerDropdown.appendChild(option);
            });

            if (humanPlayers.some(p => String(p.id) === currentSelection)) {
                this._ui.playerDropdown.value = currentSelection;
            } else {
                 this._ui.playerDropdown.selectedIndex = 0;
                 if (this._followTarget.id !== null && !humanPlayers.some(p => String(p.id) === currentSelection)) {
                     this.notify("info", `Jugador seguido (${this._followTarget.id}) abandonó o no válido. Deteniendo seguimiento.`);
                     this._toggleSmartFollow();
                 }
            }
             // Ensure natural movement restarts if it was active and follow was stopped
             if (this._activeToggles.naturalMovement && this._followTarget.id === null) {
                  this._toggleNaturalMovement(true); // Re-trigger to ensure interval is running
             }
        }

        _toggleNaturalMovement(isActive) {
            this._activeToggles.naturalMovement = isActive;

             if (isActive && this._followTarget.id !== null) {
                 this.notify("info", "Movimiento Natural activado. Deteniendo seguimiento.");
                 this._toggleSmartFollow(); // Stop follow if natural movement is activated
             }

            const connectedBots = this._bots.filter(bot => bot.getReadyState());
            const canRun = isActive && connectedBots.length > 0 && this._followTarget.id === null;

            if (canRun && !this._naturalMovementInterval) {
                this.notify("info", `Movimiento Natural iniciado con ${connectedBots.length} bots.`);
                this._naturalMovementInterval = setInterval(this._executeNaturalMovement, 3000 + Math.random() * 2000);
                this._executeNaturalMovement();
            } else if (!canRun && this._naturalMovementInterval) {
                this.notify("info", "Movimiento Natural Desactivado.");
                clearInterval(this._naturalMovementInterval);
                this._naturalMovementInterval = null;
            } else if (isActive && connectedBots.length === 0) {
                 this.notify("warning", "Movimiento Natural requiere al menos un bot conectado.");
                 if(this._ui.naturalMovementToggleCheckbox) this._ui.naturalMovementToggleCheckbox.checked = false;
                 this._activeToggles.naturalMovement = false;
            }
        }

        _executeNaturalMovement() {
            if (this._followTarget.id || !this._activeToggles.naturalMovement || !this._gameCanvas) return;

             const connectedBots = this._bots.filter(bot => bot.getReadyState());
             if (connectedBots.length === 0) {
                  this.notify("warning", "No hay bots conectados para Movimiento Natural. Deteniendo.");
                  this._toggleNaturalMovement(false);
                  return;
             }

            const centerX = 50;
            const centerY = 50;
            const driftAmount = 15;

            connectedBots.forEach(bot => {
                 if (bot.attributes?.spawned === false) {
                      this.notify("debug", `Bot ${bot.name} no generado para movimiento natural. Intentando generar.`);
                      bot.emit("spawnavatar");
                      return;
                 }

                let targetX = centerX + (Math.random() - 0.5) * driftAmount;
                let targetY = centerY + (Math.random() - 0.5) * driftAmount;

                targetX = Math.max(5, Math.min(95, targetX));
                targetY = Math.max(5, Math.min(95, targetY));

                this._moveBotSmoothly(bot, targetX, targetY);
            });
        }

        _toggleSmartFollow() {
            const targetIdString = this._ui.playerDropdown.value;
            const targetId = parseInt(targetIdString);
            const targetName = this._ui.playerDropdown.querySelector(`option[value="${targetIdString}"]`)?.textContent || 'jugador desconocido';

            const anyConnectedBot = this._bots.find(bot => bot.getReadyState());
            const currentPlayerList = anyConnectedBot?.room?.players || [];
            const targetPlayerExists = currentPlayerList.some(p => String(p.id) === targetIdString);

             if (isNaN(targetId) || !targetPlayerExists) {
                 this.notify("warning", `Selecciona un jugador válido de la lista para seguir.`);
                 return;
             }

            if (String(this._followTarget.id) === targetIdString) {
                clearInterval(this._followTarget.interval);
                this._followTarget = { id: null, interval: null };
                this._ui.followButton.textContent = "Seguir";
                this._ui.followButton.classList.remove("active");
                this.notify("info", `Dejando de seguir a ${targetName}.`);
                if (this._activeToggles.naturalMovement) {
                    this._toggleNaturalMovement(true);
                }
            } else {
                const connectedBots = this._bots.filter(b => b.getReadyState());
                if (connectedBots.length === 0) {
                     this.notify("warning", "Necesitas al menos un bot conectado para seguir a un jugador.");
                     return;
                }

                if (this._followTarget.interval) {
                    clearInterval(this._followTarget.interval);
                }
                if (this._naturalMovementInterval) {
                    this._toggleNaturalMovement(false);
                    if(this._ui.naturalMovementToggleCheckbox) this._ui.naturalMovementToggleCheckbox.checked = false;
                }

                this._followTarget.id = targetId;
                this._ui.followButton.textContent = `Siguiendo: ${targetName}`;
                this._ui.followButton.classList.add("active");
                this.notify("info", `Iniciando seguimiento a ${targetName}.`);

                this._followTarget.interval = setInterval(this._followLogic, 500);
                this._followLogic();
            }
        }

        _followLogic() {
            if (this._followTarget.id === null || !this._gameCanvas) return;

            const connectedBots = this._bots.filter(bot => bot.getReadyState());
            if (connectedBots.length === 0) {
                  this.notify("warning", "No hay bots conectados para seguir. Deteniendo seguimiento.");
                  this._toggleSmartFollow();
                  return;
             }

            const targetPlayerElement = document.querySelector(`.spawnedavatar[data-playerid="${this._followTarget.id}"]`);

            if (!targetPlayerElement) {
                this.notify("warning", `Avatar del jugador seguido (ID: ${this._followTarget.id}) no encontrado. Deteniendo seguimiento.`);
                this._toggleSmartFollow();
                return;
            }

            const canvasRect = this._gameCanvas.getBoundingClientRect();
            const avatarRect = targetPlayerElement.getBoundingClientRect();

            let targetXGameCoords = ((avatarRect.left + (avatarRect.width / 2) - canvasRect.left) / canvasRect.width) * 100;
            let targetYGameCoords = ((avatarRect.top + (avatarRect.height / 2) - canvasRect.top) / canvasRect.height) * 100;

            targetXGameCoords = Math.max(0, Math.min(100, targetXGameCoords));
            targetYGameCoords = Math.max(0, Math.min(100, targetYGameCoords));

            connectedBots.forEach((bot, index) => {
                 if (bot.attributes?.spawned === false) {
                      this.notify("debug", `Bot ${bot.name} no generado para seguir. Intentando generar.`);
                      bot.emit("spawnavatar");
                      return;
                 }

                const offsetDistance = 10 + index * 5;
                const offsetAngle = (index * 1.5 + Math.random()) * Math.PI * 2 / connectedBots.length;

                const offsetX = offsetDistance * Math.cos(offsetAngle);
                const offsetY = offsetDistance * Math.sin(offsetAngle);

                let moveX = targetXGameCoords + offsetX;
                let moveY = targetYGameCoords + offsetY;

                moveX = Math.max(5, Math.min(95, moveX));
                moveY = Math.max(5, Math.min(95, moveY));

                this._moveBotSmoothly(bot, moveX, moveY);
            });
        }

        _moveBotSmoothly(bot, targetX, targetY) {
            if (!bot || !bot.getReadyState() || typeof bot.emit !== 'function' || bot.attributes?.spawned === false) {
                this.notify("debug", `Saltando movimiento suave para bot ${bot?.name || 'Desconocido'}: No está listo o no ha sido generado.`);
                return;
            }

             bot._lastCommandedX = bot._lastCommandedX ?? 50;
             bot._lastCommandedY = bot._lastCommandedY ?? 50;
             const currentX = bot._lastCommandedX;
             const currentY = bot._lastCommandedY;

             const steps = 10;
             const stepDelay = 50;
             for (let i = 1; i <= steps; i++) {
                 setTimeout(() => {
                     if (bot.getReadyState()) {
                        const interX = currentX + (targetX - currentX) * (i / steps);
                        const interY = currentY + (targetY - currentY) * (i / steps);
                         bot.emit("moveavatar", interX, interY);
                         bot._lastCommandedX = interX;
                         bot._lastCommandedY = interY;
                     }
                 }, i * stepDelay);
             }
        }

        _canBotChat(bot) {
            const now = Date.now();
            const lastChat = this._chatCooldown.get(bot.id) || 0;
            const canChat = this._activeToggles.reactiveChat && (now - lastChat > 7000);
            if (canChat) {
                 this._chatCooldown.set(bot.id, now);
            }
            return canChat;
        }

         _sendBotChat(bot, message) {
             if (bot && bot.getReadyState() && this._activeToggles.reactiveChat) {
                 setTimeout(() => {
                      if (bot.getReadyState() && this._activeToggles.reactiveChat) {
                          bot.emit("chatmsg", message);
                          this.notify("log", `${bot.name} (${this._ui.personalitySelect.value}): "${message}"`);
                      }
                 }, 500 + Math.random() * 500);
             }
         }

         _canBotGesture(bot) {
             const now = Date.now();
             const lastGesture = this._gestureCooldown.get(bot.id) || 0;
             const canGesture = this._activeToggles.smartGestures && (now - lastGesture > 500);
             if (canGesture) {
                 this._gestureCooldown.set(bot.id, now);
             }
             return canGesture;
         }

         _sendBotGesture(bot, gestureId) {
             if (gestureId === undefined || gestureId === null) {
                 this.notify("debug", `Saltando gesto: ID inválido para bot ${bot.name}.`);
                 return;
             }

             if (bot && bot.getReadyState() && this._activeToggles.smartGestures && this._canBotGesture(bot)) {
                 setTimeout(() => {
                     if (bot.getReadyState() && this._activeToggles.smartGestures) {
                          bot.emit("sendgesture", gestureId);
                          this.notify("log", `${bot.name} usó el gesto ${gestureId}.`);
                     }
                 }, 100 + Math.random() * 200);
             }
         }

        _getMessageLanguage(message) {
            const lowerCaseMsg = message.toLowerCase();
            // Ampliada la lista de palabras clave en español
            const spanishKeywords = [
                'hola', 'buenas', 'que', 'qué', 'tal', 'como', 'cómo', 'estás', 'estas', 'estoy', 'bien', 'mal', 'sí', 'si', 'no', 'por favor', 'gracias', 'adiós', 'chao',
                'tú', 'vos', 'usted', 'nosotros', 'ustedes', 'ellos', 'ellas', 'mi', 'mí', 'tu', 'su', 'nuestro', 'vuestro', 'suya',
                'es', 'son', 'está', 'están', 'ser', 'estar', 'tener', 'hacer', 'ir', 'ver', 'decir', 'poder', 'saber', 'querer', 'hay',
                'un', 'una', 'unos', 'unas', 'el', 'la', 'los', 'las',
                'y', 'o', 'pero', 'mas', 'más', 'también', 'aún', 'así', 'solo', 'mucho', 'poco', 'nada', 'siempre', 'nunca', 'quizás', 'tal vez', 'claro', 'verdad',
                'ahora', 'después', 'antes', 'hoy', 'mañana', 'ayer', 'aquí', 'allí', 'donde', 'dónde', 'cuando', 'cuándo', 'cómo', 'cuánto',
                'jaja', 'jeje', 'xd', 'lol', 'emoji', 'dibujo', 'dibujar', 'jugador', 'adivinar', 'palabra', 'ronda', 'juego', 'ganar', 'perder', 'score', 'puntos',
                'genial', 'excelente', 'increíble', 'bonito', 'lindo', 'feo', 'divertido', 'aburrido', 'perfecto', 'error', 'ayuda', 'ayúdame',
                'amigo', 'amiga', 'gente', 'persona', 'puedes', 'quieres', 'sabes', 'entiendes', 'entiendo', 'no sé', 'no entiendo', 'por qué', 'porque',
                'listo', 'lista', 'empezar', 'terminar', 'rápido', 'lento', 'espera', 'esperar', 'ya', 'ya voy', 'llegar', 'llegué', 'fuiste', 'saludos', 'bienvenido', 'bienvenida',
                'jugar', 'ganaste', 'perdiste', 'buen', 'mala', 'mal', 'muy', 'gracioso', 'divertida', 'pista', 'pistas', 'qué tal', 'qué onda', 'a ver', 'dale', 'vamos', 'va',
                'eso', 'esto', 'aquello', 'acá', 'allá', 'dentro', 'fuera', 'arriba', 'abajo', 'cerca', 'lejos', 'antes', 'después', 'durante', 'mientras', 'entonces', 'luego', 'así que',
                'para', 'con', 'sin', 'sobre', 'bajo', 'entre', 'hacia', 'hasta', 'desde', 'según', 'contra', 'tras', 'mediante', 'durante',
                'yo', 'tú', 'él', 'ella', 'usted', 'nosotros', 'vosotros', 'ustedes', 'ellos', 'ellas', 'me', 'te', 'se', 'lo', 'la', 'le', 'les', 'nos', 'os',
                'mi', 'tu', 'su', 'nuestro', 'vuestro', 'sus', 'mis', 'tus', 'sus', 'nuestros', 'vuestros',
                'este', 'esta', 'estos', 'estas', 'ese', 'esa', 'esos', 'esas', 'aquel', 'aquella', 'aquellos', 'aquellas',
                'uno', 'dos', 'tres', 'cuatro', 'cinco', 'seis', 'siete', 'ocho', 'nueve', 'diez',
                'cero', 'cien', 'mil', 'millón', 'primero', 'segundo', 'tercero', 'último'
            ];

            // Ampliada la lista de palabras clave en inglés
            const englishKeywords = [
                'hi', 'hello', 'hey', 'what', 'how', 'are', 'you', 'i', 'am', 'fine', 'good', 'bad', 'yes', 'no', 'please', 'thank', 'thanks', 'bye', 'goodbye', 'see', 'ya',
                'me', 'my', 'your', 'his', 'her', 'its', 'our', 'their', 'we', 'us', 'they', 'them',
                'is', 'are', 'am', 'be', 'been', 'was', 'were', 'have', 'has', 'had', 'do', 'does', 'did', 'make', 'go', 'see', 'say', 'can', 'could', 'would', 'should', 'will', 'must',
                'a', 'an', 'the',
                'and', 'or', 'but', 'also', 'too', 'still', 'even', 'so', 'then', 'as', 'if', 'when', 'where', 'why', 'how', 'much', 'many', 'little', 'none', 'always', 'never', 'maybe', 'perhaps', 'of course', 'true', 'really',
                'now', 'later', 'before', 'after', 'today', 'tomorrow', 'yesterday', 'here', 'there', 'where', 'when', 'how', 'why',
                'lol', 'lmao', 'rofl', 'xd', 'emoji', 'draw', 'drawing', 'drawer', 'player', 'guess', 'word', 'round', 'game', 'win', 'lose', 'score', 'points', 'xp', 'level',
                'great', 'excellent', 'amazing', 'beautiful', 'ugly', 'funny', 'boring', 'perfect', 'error', 'help', 'help me', 'bug',
                'friend', 'people', 'person', 'can you', 'do you want', 'do you know', 'understand', 'i know', 'i dont know', 'i dont understand', 'why', 'because',
                'ready', 'start', 'end', 'finish', 'quick', 'slow', 'wait', 'waiting', 'already', 'almost', 'coming', 'arrived', 'left', 'welcome',
                'play', 'won', 'lost', 'nice', 'bad', 'very', 'clue', 'hint', 'whatsup', 'whats up', 'go ahead', 'come on',
                'this', 'that', 'these', 'those', 'it', 'them', 'they', 'here', 'there', 'inside', 'outside', 'up', 'down', 'near', 'far',
                'for', 'with', 'without', 'on', 'under', 'between', 'towards', 'until', 'from', 'according', 'against', 'behind', 'through', 'during',
                'one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten',
                'zero', 'hundred', 'thousand', 'million', 'first', 'second', 'third', 'last'
            ];


            let spanishMatches = spanishKeywords.filter(k => lowerCaseMsg.includes(k)).length;
            let englishMatches = englishKeywords.filter(k => lowerCaseMsg.includes(k)).length;

            if (spanishMatches > englishMatches && spanishMatches > 0) return 'spanish';
            if (englishMatches > spanishMatches && englishMatches > 0) return 'english';
            return 'neutral';
        }

        _handleReactiveChat(bot, msg) {
            if (msg.id === bot.id || msg.id === 0 || !this._activeToggles.reactiveChat) return;

            const personality = this._personalities[this._ui.personalitySelect.value];
            const lowerCaseMsg = msg.message.toLowerCase().trim();
            const lang = this._getMessageLanguage(msg.message);

            let responseType = null;
            let responseMessage = null;

            if (/\b(hola|buenas|hey|hi|hello)\b/.test(lowerCaseMsg)) {
                responseType = 'greeting';
                responseMessage = lang === 'english' ? personality.english_greetings : personality.spanish_greetings;
            } else if (/\b(si|yes|ok|claro|yeah)\b/.test(lowerCaseMsg)) {
                responseType = 'acknowledgement';
                responseMessage = lang === 'english' ? personality.english_acknowledgements : personality.spanish_acknowledgements;
            } else if (/\b(como\sestas|y\stu|how\sare\syou|what's\sup|what)\b/.test(lowerCaseMsg)) {
                responseType = 'question';
                responseMessage = lang === 'english' ? personality.english_questions : personality.spanish_questions;
            } else if (/\b(xd|lol|jaja|haha|omg)\b/.test(lowerCaseMsg)) {
                responseType = 'laughter';
                responseMessage = lang === 'english' ? personality.english_laughter : personality.spanish_laughter;
            } else if (/\b(que|but|well|pero|bueno)\b/.test(lowerCaseMsg)) {
                responseType = 'general';
                responseMessage = lang === 'english' ? personality.english_general : personality.spanish_general;
            } else if (/\b(lindo|hermoso|dibujas\sbien|buen\sdibujo|buen\strabajo|good\sjob|nice\sdraw)\b/.test(lowerCaseMsg)) {
                responseType = 'goodjob_drawing';
                responseMessage = lang === 'english' ? [`Thanks, {player}!`, `Glad you liked it, {player}!`] : [`Gracias, {player}!`, `Me alegro que te guste, {player}!`];
            }

            if (responseType && responseMessage) {
                const selectedResponse = responseMessage[Math.floor(Math.random() * responseMessage.length)];
                if (this._canBotChat(bot)) { // Check cooldown before sending chat
                    this._sendBotChat(bot, selectedResponse.replace("{player}", msg.name));
                }

                if (personality.gestures[responseType]) {
                    this._sendBotGesture(bot, personality.gestures[responseType]);
                }
            }
        }

        _handleCorrectGuess(data) {
            const player = { id: data[0], name: data[1] };
            if (this._bots.some(b => b.id === player.id)) return; // If one of our bots guessed

            const personality = this._personalities[this._ui.personalitySelect.value];
            const response = personality.spanish_congrats[Math.floor(Math.random() * personality.spanish_congrats.length)].replace("{player}", player.name);

            this._bots.forEach((bot) => {
                if (bot.getReadyState()) {
                     if (this._activeToggles.reactiveChat && Math.random() < 0.7 && this._canBotChat(bot)) {
                         this._sendBotChat(bot, response);
                     }
                    if (this._activeToggles.smartGestures && personality.gestures.congrats) {
                         this._sendBotGesture(bot, personality.gestures.congrats);
                    }
                }
            });
        }

         _handlePlayerJoin(player) {
             if (this._bots.some(b => b.id === player.id) || player.id === 0) return;

             const personality = this._personalities[this._ui.personalitySelect.value];
             const response = personality.spanish_playerJoin[Math.floor(Math.random() * personality.spanish_playerJoin.length)].replace("{player}", player.name);

             this._bots.forEach((bot) => {
                 if (bot.getReadyState()) {
                      if (this._activeToggles.reactiveChat && Math.random() < 0.6 && this._canBotChat(bot)) {
                           this._sendBotChat(bot, response);
                      }
                      if (this._activeToggles.smartGestures && personality.gestures.playerJoin) {
                          this._sendBotGesture(bot, personality.gestures.playerJoin);
                      }
                 }
             });
         }

         _handlePlayerLeave(player) {
             if (this._bots.some(b => b.id === player.id) || player.id === 0) return;

              if (this._followTarget.id === player.id) {
                  this.notify("info", `Jugador seguido (${player.name}) ha abandonado la sala. Deteniendo seguimiento.`);
                  this._toggleSmartFollow();
              }

             const personality = this._personalities[this._ui.personalitySelect.value];
             const response = personality.spanish_playerLeave[Math.floor(Math.random() * personality.spanish_playerLeave.length)].replace("{player}", player.name);

             this._bots.forEach((bot) => {
                 if (bot.getReadyState()) {
                      if (this._activeToggles.reactiveChat && Math.random() < 0.5 && this._canBotChat(bot)) {
                              this._sendBotChat(bot, response);
                          }
                      if (this._activeToggles.smartGestures && personality.gestures.playerLeave) {
                           this._sendBotGesture(bot, personality.gestures.playerLeave);
                      }
                 }
             });
         }

         _handleTurnEnd(botInstance, data) {
             if (!this._activeToggles.reactiveChat) return;

             // Ensure this check is for a bot managed by Sentinel
             if (!this._bots.some(b => b.id === botInstance.id)) return;

             if (Math.random() < 0.3 && this._canBotChat(botInstance)) {
                  const generalEndMessages = ["Turno terminado.", "Bien jugado.", "A ver qué sigue."];
                  const message = generalEndMessages[Math.floor(Math.random() * generalEndMessages.length)];
                  this._sendBotChat(botInstance, message);
             }
         }

         _handleTurnBeginDraw(botInstance, data) {
             const drawingPlayerId = data[0];
             const drawingPlayerName = botInstance.room?.players?.find(p => p.id === drawingPlayerId)?.name || 'alguien';

             if (this._bots.some(b => b.id === drawingPlayerId)) {
                 return; // One of our bots is drawing
             }

             const personality = this._personalities[this._ui.personalitySelect.value];
             if (this._activeToggles.smartGestures && personality.gestures.drawing) {
                 this._bots.forEach(bot => {
                     this._sendBotGesture(bot, personality.gestures.drawing);
                 });
             }
             if (this._activeToggles.reactiveChat && Math.random() < 0.4 && this._canBotChat(botInstance)) {
                 const chatMessage = `¡Buena suerte, ${drawingPlayerName}!`;
                 this._sendBotChat(botInstance, chatMessage);
             }
         }

         _handleWordSelected(botInstance, data) {
             const drawingPlayerElement = document.querySelector('.playerlist-row[data-turn="true"]');
             const drawingPlayerId = drawingPlayerElement ? parseInt(drawingPlayerElement.dataset.playerid) : null;

             if (this._bots.some(b => b.id === drawingPlayerId)) {
                 return; // Our bot is drawing
             }

             const personality = this._personalities[this._ui.personalitySelect.value];
             if (this._activeToggles.smartGestures && personality.gestures.acknowledgement) {
                 this._bots.forEach(bot => {
                     this._sendBotGesture(bot, personality.gestures.acknowledgement);
                 });
             }
         }

        // --- Bad Girl Spam Methods ---
        _updateMessageList() {
            const newMessages = this._ui.messageTextarea.value.split('\n').filter(msg => msg.trim() !== '');
            if (newMessages.length > 0) {
                this._messageList = newMessages;
                this.notify("success", `Lista de mensajes actualizada con ${this._messageList.length} mensajes.`);
            } else {
                this.notify("warning", "La lista de mensajes no puede estar vacía.");
            }
        }

        _toggleBadGirlSpam() {
            this._activeToggles.badGirlSpam = !this._activeToggles.badGirlSpam;

            if (this._spamInterval) {
                // Stop spam
                clearInterval(this._spamInterval);
                this._spamInterval = null;
                this._ui.badGirlToggleButton.classList.remove("active");
                this._ui.badGirlToggleButton.innerHTML = '<i class="fas fa-play-circle"></i> Iniciar Spam';
                this.notify("info", "Spam de 'Chica Mala' detenido.");
            } else {
                // Start spam
                const bot = this._getBot(); // Get the currently selected/first bot
                if (!bot) {
                    this.notify("error", "No se puede iniciar el spam: no hay un bot válido y conectado.");
                    this._activeToggles.badGirlSpam = false;
                    return;
                }

                if (this._messageList.length === 0) {
                    this.notify("error", "No se puede iniciar el spam: la lista de mensajes está vacía.");
                    this._activeToggles.badGirlSpam = false;
                    return;
                }

                this._ui.badGirlToggleButton.classList.add("active");
                this._ui.badGirlToggleButton.innerHTML = '<i class="fas fa-stop-circle"></i> Detener Spam';
                this.notify("info", `Iniciando spam cada ${this._intervalTime}ms.`);

                this._spamInterval = setInterval(() => {
                    const currentBot = this._getBot(); // Re-check bot status in each cycle
                    if (!currentBot) {
                        this.notify("error", "El bot se ha desconectado. Deteniendo el spam.");
                        this._toggleBadGirlSpam();
                        return;
                    }

                    const randomMessage = this._messageList[Math.floor(Math.random() * this._messageList.length)];
                    currentBot.emit("chatmsg", randomMessage);

                }, this._intervalTime);
            }
        }
    }
})("QBit");

// END SMART

// --- START Swarm

(function SwarmCommanderModule() {
    const QBit = globalThis[arguments[0]];

    QBit.Styles.addRules([
        `#${QBit.identifier} .swarm-commander-section {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 10px;
            background-color: var(--CE-bg_color);
        }`,
        `#${QBit.identifier} .swarm-commander-section-title {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--dark-blue-title);
            text-align: center;
        }`,
        `#${QBit.identifier} .swarm-commander-toggle-button.active {
            background-color: var(--info);
            color: white;
        }`,
        `#${QBit.identifier} .swarm-commander-control-group {
            display: flex;
            flex-wrap: wrap;
            gap: 5px;
            margin-top: 5px;
            padding-top: 5px;
            border-top: 1px solid rgba(0,0,0,0.1);
        }`,
        `#${QBit.identifier} .swarm-commander-control-group > div {
            flex: 1 1 48%; /* For responsiveness */
            display: flex;
            flex-direction: column;
            align-items: flex-start;
        }`,
        `#${QBit.identifier} .swarm-commander-control-group input,
         #${QBit.identifier} .swarm-commander-control-group select {
            width: 100%;
        }`
    ]);

    class SwarmCommander extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        // BugGeneratorModule properties
        #lagInterval = null;
        #secretSpamInterval = null;
        #bugExperienceInterval = null;
        #playerChaosInterval = null;
        #visualGlitchInterval = null;

        constructor() {
            super("Manipulacion avanzada de Bots", '<i class="fas fa-gamepad"></i>');
            this.#onStartup();
        }

        #onStartup() {
            this.#loadInterface();
        }

        #loadInterface() {
            const container = domMake.Tree("div");

            // --- Section: Tácticas de Enjambre (TacticalBotSwarm) ---
            const swarmTacticsSection = domMake.Tree("div", { class: "swarm-commander-section" });
            swarmTacticsSection.appendChild(domMake.Tree("div", { class: "swarm-commander-section-title" }, ["Modos de bot"]));

            const collaborativeDrawRow = domMake.Row();
            const collaborativeDrawButton = domMake.Button("Dibujo Colaborativo");
            collaborativeDrawButton.title = "Divide el lienzo en zonas para que cada bot dibuje una parte (usa Autodraw V2 para cargar imagen).";
            collaborativeDrawButton.addEventListener("click", () => this.#startCollaborativeDrawing());
            collaborativeDrawRow.appendChild(collaborativeDrawButton);
            swarmTacticsSection.appendChild(collaborativeDrawRow);

            const smartGuessRow = domMake.Row();
            const smartGuessButton = domMake.Button("Bot de Adivinanza");
            smartGuessButton.title = "Un bot intentará adivinar la palabra (simulado).";
            smartGuessButton.addEventListener("click", () => this.#smartGuess());
            smartGuessRow.appendChild(smartGuessButton);
            swarmTacticsSection.appendChild(smartGuessRow);

            const personalityGroup = domMake.Tree("div", { class: "swarm-commander-control-group" });
            personalityGroup.appendChild(domMake.Tree("label", { style: "width: 100%; text-align: center; font-weight: bold; margin-bottom: 5px;" }, ["Personalidad del Bot"]));

            const drawingSpeedDiv = domMake.Tree("div");
            drawingSpeedDiv.appendChild(domMake.Tree("label", {}, ["Velocidad Dibujo (ms/línea):"]));
            const drawingSpeedInput = domMake.Tree("input", { type: "number", min: "1", max: "100", value: "10" });
            drawingSpeedInput.addEventListener("change", (e) => this.#setBotDrawingSpeed(parseInt(e.target.value)));
            drawingSpeedDiv.appendChild(drawingSpeedInput);
            personalityGroup.appendChild(drawingSpeedDiv);

            const verbosityDiv = domMake.Tree("div");
            verbosityDiv.appendChild(domMake.Tree("label", {}, ["Verbosidad Mensajes:"]));
            const verbositySelect = domMake.Tree("select");
            ['Silencioso', 'Normal', 'Charlatán'].forEach(level => {
                verbositySelect.appendChild(domMake.Tree("option", { value: level }, [level]));
            });
            verbositySelect.addEventListener("change", (e) => this.#setBotChatVerbosity(e.target.value));
            verbosityDiv.appendChild(verbositySelect);
            personalityGroup.appendChild(verbosityDiv);

            swarmTacticsSection.appendChild(personalityGroup);
            container.appendChild(swarmTacticsSection);

            // --- Section: Herramientas de Disrupción (BugGeneratorModule) ---
            const disruptionSection = domMake.Tree("div", { class: "swarm-commander-section" });
            disruptionSection.appendChild(domMake.Tree("div",  { class: "swarm-commander-section-title" }, ["Herramientas de Caos"]));

            const createToggleButton = (icon, text, toggleFunction) => {
                const row = domMake.Row();
                const button = domMake.Button(`<i class="fas ${icon}"></i> ${text}`);
                button.classList.add("swarm-commander-toggle-button");
                button.addEventListener("click", () => toggleFunction(button));
                row.appendChild(button);
                return row;
            };

            disruptionSection.appendChild(createToggleButton('fa-dizzy', 'Generar Lag', (btn) => this.#toggleLag(btn)));
            disruptionSection.appendChild(createToggleButton('fa-gamepad', 'Bugear Experiencia', (btn) => this.#toggleBugExperience(btn)));
            disruptionSection.appendChild(createToggleButton('fa-running', 'Caos de Jugador', (btn) => this.#togglePlayerChaos(btn)));
            disruptionSection.appendChild(createToggleButton('fa-ghost', 'Glitch Visual', (btn) => this.#toggleVisualGlitch(btn)));
            disruptionSection.appendChild(createToggleButton('fa-mask', 'Spam Visual Secreto', (btn) => this.#toggleSecretSpam(btn)));

            container.appendChild(disruptionSection);

            this.htmlElements.section.appendChild(container);
        }

        // --- Helper: Get Bot Instance ---
        #getBot() {
            const botManagerClass = this.findGlobal("BotClientManager");
            if (!botManagerClass || !botManagerClass.siblings || botManagerClass.siblings.length === 0) {
                this.notify("warning", "No hay instancias de 'BotClientManager'. Por favor, crea un bot desde 'CubeEngine'.");
                return null;
            }

            const botManagerInstance = botManagerClass.siblings[0];
            const botClientInterfaces = botManagerInstance.children;
            let activeBotClientInterface = null;

            const selectedBotInput = document.querySelector('input[name="botClient"]:checked');
            if (selectedBotInput) {
                activeBotClientInterface = botClientInterfaces.find(bci => bci.htmlElements.input === selectedBotInput);
            }

            if (!activeBotClientInterface && botClientInterfaces.length > 0) {
                activeBotClientInterface = botClientInterfaces[0]; // Default to the first bot
                this.notify("info", `No se seleccionó un bot. Usando el primero: ${activeBotClientInterface.getName()}.`);
            }

            if (!activeBotClientInterface) {
                 this.notify("warning", "No se encontró ningún bot activo.");
                 return null;
            }

            // ### CORRECCIÓN CLAVE: La lógica que faltaba estaba aquí ###
            // Validar que el bot esté conectado y listo.
            if (!activeBotClientInterface.bot || !activeBotClientInterface.bot.getReadyState()) {
                this.notify("warning", `El bot "${activeBotClientInterface.getName()}" no está conectado y listo.`);
                return null;
            }

            return activeBotClientInterface.bot;
        }

        // --- TacticalBotSwarm Methods ---
        #startCollaborativeDrawing() {
            const autodrawV2Class = this.findGlobal("AutodrawV2");
            if (!autodrawV2Class || !autodrawV2Class.siblings || autodrawV2Class.siblings.length === 0) {
                 this.notify("warning", "El módulo 'Autodraw V2' no está activo. No se puede iniciar el dibujo colaborativo.");
                 return;
            }
            const autodrawV2Instance = autodrawV2Class.siblings[0];

            if (autodrawV2Instance && typeof autodrawV2Instance.startDrawing === 'function') {
                autodrawV2Instance.startDrawing();
                this.notify("info", "Iniciando dibujo colaborativo a través del módulo Autodraw V2.");
            } else {
                this.notify("warning", "La instancia del módulo 'Autodraw V2' no está lista.");
            }
        }

        #smartGuess() {
            const bot = this.#getBot();
            if (!bot) return;

            const commonWords = ["casa", "flor", "mesa", "sol", "perro", "gato", "arbol", "coche", "libro"];
            const randomWord = commonWords[Math.floor(Math.random() * commonWords.length)];

            bot.emit("chatmsg", randomWord);
            this.notify("info", `Bot ${bot.name} intentó adivinar: "${randomWord}" (simulado).`);
        }

        #setBotProperty(propertyName, value, logMessage) {
            const botManagerClass = this.findGlobal("BotClientManager");
            if (botManagerClass && botManagerClass.siblings.length > 0) {
                botManagerClass.siblings.forEach(manager => {
                    if (manager && manager.children) {
                        manager.children.forEach(botInterface => {
                            if (botInterface.bot) {
                                botInterface.bot[propertyName] = value;
                                this.notify("log", logMessage(botInterface.getName(), value));
                            }
                        });
                    }
                });
            } else {
                 this.notify("warning", "No se encontró el gestor de bots para aplicar la configuración.");
            }
        }

        #setBotDrawingSpeed(speed) {
            this.#setBotProperty("drawingDelay", speed, (name, val) => `Velocidad de dibujo de ${name}: ${val}ms/línea.`);
        }

        #setBotChatVerbosity(verbosity) {
             this.#setBotProperty("chatVerbosity", verbosity, (name, val) => `Verbosidad de ${name}: ${val}.`);
        }

        // --- BugGeneratorModule Methods ---
        #toggleLag(button) {
            if (this.#lagInterval) {
                clearInterval(this.#lagInterval);
                this.#lagInterval = null;
                button.classList.remove("active");
                button.innerHTML = '<i class="fas fa-dizzy"></i> Generar Lag';
                this.notify("info", "Generador de Lag Detenido.");
            } else {
                const bot = this.#getBot();
                if (!bot) return;

                button.classList.add("active");
                button.innerHTML = '<i class="fas fa-stop"></i> Detener Lag';
                this.notify("info", "Generador de Lag Iniciado.");

                let counter = 0;
                this.#lagInterval = setInterval(() => {
                    if (!bot.getReadyState()) {
                        this.notify("warning", "Bot desconectado, deteniendo Lag.");
                        this.#toggleLag(button);
                        return;
                    }
                    if (counter % 50 === 0) bot.emit("clear");

                    for (let i = 0; i < 5; i++) {
                        bot.emit("line", -1, Math.random() * 100, Math.random() * 100, Math.random() * 100, Math.random() * 100, true, Math.floor(Math.random() * 70) + 20, `#${Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0')}`, false);
                    }
                    counter++;
                }, 25);
            }
        }

        #toggleBugExperience(button) {
            if (this.#bugExperienceInterval) {
                clearInterval(this.#bugExperienceInterval);
                this.#bugExperienceInterval = null;
                button.classList.remove("active");
                button.innerHTML = '<i class="fas fa-gamepad"></i> Bugear Experiencia';
                this.notify("info", "Deteniendo 'Bugear Experiencia'.");
            } else {
                const bot = this.#getBot();
                if (!bot) return;

                button.classList.add("active");
                button.innerHTML = '<i class="fas fa-stop"></i> Detener Bug';
                this.notify("info", "Iniciando 'Bugear Experiencia'.");

                this.#bugExperienceInterval = setInterval(() => {
                    if (!bot.getReadyState()) {
                        this.notify("warning", "Bot desconectado, deteniendo 'Bugear Experiencia'.");
                        this.#toggleBugExperience(button);
                        return;
                    }
                    bot.emit("moveavatar", Math.random() * 100, Math.random() * 100);
                    bot.emit("sendgesture", Math.floor(Math.random() * 32));
                }, 100);
            }
        }

        #togglePlayerChaos(button) {
            if (this.#playerChaosInterval) {
                clearInterval(this.#playerChaosInterval);
                this.#playerChaosInterval = null;
                button.classList.remove("active");
                button.innerHTML = '<i class="fas fa-running"></i> Caos de Jugador';
                this.notify("info", "Deteniendo 'Caos de Jugador'.");
            } else {
                const bot = this.#getBot();
                if (!bot) return;

                button.classList.add("active");
                button.innerHTML = '<i class="fas fa-stop"></i> Detener Caos';
                this.notify("info", "Iniciando 'Caos de Jugador' (Agresivo).");

                this.#playerChaosInterval = setInterval(() => {
                    if (!bot.getReadyState()) {
                        this.notify("warning", "Bot desconectado, deteniendo 'Caos de Jugador'.");
                        this.#togglePlayerChaos(button);
                        return;
                    }
                    bot.emit("moveavatar", Math.random() * 100, Math.random() * 100);
                    bot.emit("sendgesture", Math.floor(Math.random() * 32));
                    bot.emit("playerafk");
                    bot.emit("setstatusflag", Math.floor(Math.random() * 5), Math.random() < 0.5);
                }, 100);
            }
        }

        #toggleVisualGlitch(button) {
            if (this.#visualGlitchInterval) {
                clearInterval(this.#visualGlitchInterval);
                this.#visualGlitchInterval = null;
                button.classList.remove("active");
                button.innerHTML = '<i class="fas fa-ghost"></i> Glitch Visual';
                this.notify("info", "Deteniendo 'Glitch Visual'.");
            } else {
                const bot = this.#getBot();
                if (!bot) return;

                button.classList.add("active");
                button.innerHTML = '<i class="fas fa-stop"></i> Detener Glitch';
                this.notify("info", "Iniciando 'Glitch Visual' (Extremo).");

                const chatMessages = ["!! GLITCH DETECTED !!", "ERROR CODE 404: REALITY NOT FOUND", "SYSTEM OVERLOAD", "// VISUAL ANOMALY //", "PACKET CORRUPTION", "DISCONNECTING...", "RECALIBRATING... X_X"];

                this.#visualGlitchInterval = setInterval(() => {
                    if (!bot.getReadyState()) {
                        this.notify("warning", "Bot desconectado, deteniendo 'Glitch Visual'.");
                        this.#toggleVisualGlitch(button);
                        return;
                    }
                    bot.emit("spawnavatar");
                    bot.emit("setavatarprop");
                    bot.emit("chatmsg", chatMessages[Math.floor(Math.random() * chatMessages.length)]);

                    const otherPlayers = (bot.room.players || []).filter(p => p.id !== bot.id && p.id !== 0);
                    if (otherPlayers.length > 0) {
                        const randomPlayer = otherPlayers[Math.floor(Math.random() * otherPlayers.length)];
                        bot.emit("sendvotekick", randomPlayer.id);
                        bot.emit("settoken", randomPlayer.id, Math.floor(Math.random() * 9));
                    }
                    bot.emit("sendvote");
                }, 200);
            }
        }

        #toggleSecretSpam(button) {
            if (this.#secretSpamInterval) {
                clearInterval(this.#secretSpamInterval);
                this.#secretSpamInterval = null;
                button.classList.remove("active");
                button.innerHTML = '<i class="fas fa-mask"></i> Spam Visual Secreto';
                this.notify("info", "Spam Visual 'Secreto' Detenido.");
            } else {
                const bot = this.#getBot();
                if (!bot) return;

                button.classList.add("active");
                button.innerHTML = '<i class="fas fa-stop"></i> Detener Spam';
                this.notify("info", "Spam Visual 'Secreto' Iniciado.");

                this.#secretSpamInterval = setInterval(() => {
                    if (!bot.getReadyState()) {
                        this.notify("warning", "Bot desconectado, deteniendo Spam.");
                        this.#toggleSecretSpam(button);
                        return;
                    }
                    for (let i = 0; i < 10; i++) {
                        const x1 = Math.random() * 100, y1 = Math.random() * 100;
                        const x2 = x1 + (Math.random() * 2 - 1) * 5;
                        const y2 = y1 + (Math.random() * 2 - 1) * 5;
                        bot.emit("line", -1, x1, y1, x2, y2, true, Math.floor(Math.random() * 3) + 1, `#${Math.floor(Math.random() * 16777215).toString(16).padStart(6, '0')}`, false);
                    }
                }, 100);
            }
        }
    }
})("QBit");

// END SWARM

// START KICK

(function PlayerKickToolsModule() {
    const QBit = globalThis[arguments[0]];

    QBit.Styles.addRules([
        `#${QBit.identifier} .kick-tools-section {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 10px;
            background-color: var(--CE-bg_color);
        }`,
        `#${QBit.identifier} .kick-tools-section-title {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--dark-blue-title);
            text-align: center;
        }`,
        `#${QBit.identifier} .kick-tools-player-list {
            display: flex;
            flex-wrap: wrap;
            gap: 5px;
            max-height: 150px; /* Limit height to show scrollbar if many players */
            overflow-y: auto;
            padding: 5px;
            border: 1px dashed var(--CE-color);
            border-radius: .25rem;
        }`,
        `#${QBit.identifier} .kick-tools-player-list .btn {
            flex: 0 0 auto; /* Prevent stretching */
            margin: 0; /* Remove default margin from btn class */
            padding: 3px 8px; /* Compact padding */
            font-size: 0.8em;
        }`,
        `#${QBit.identifier} .auto-action-toggle.active { /* Generic style for automation toggles */
            background-color: var(--info); /* Consistent active color */
            color: white;
        }`
    ]);

    class PlayerKickTools extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        _humanKickPlayerListContainer; // Changed from #
        _botKickPlayerListContainer;   // Changed from #
        _cachedPlayers = []; // Changed from # // To store player IDs and names for both sections, including drawing status

        // Auto Kick properties
        _isAutoKickActive = false;       // Changed from #
        _autoKickInterval = null;        // Changed from #
        _kickedPlayersThisSession = new Set(); // Changed from # // To track players already sent a kick vote

        // Auto Prohibit Drawing properties
        _isAutoProhibitDrawingActive = false; // Changed from #
        _autoProhibitDrawingInterval = null;  // Changed from #
        _prohibitedPlayersThisSession = new Set(); // Changed from # // To track players already sent a prohibit vote

        constructor() {
            super("Herramientas de Expulsión", '<i class="fas fa-gavel"></i>');
            this._onStartup(); // Changed from #
        }

        _onStartup() { // Changed from #
            this._loadInterface(); // Changed from #
            this._setupObservers(); // Changed from #
        }

        _loadInterface() { // Changed from #
            const container = domMake.Tree("div");

            // --- Section 1: Voto para Expulsar (Tú) ---
            const humanKickSection = domMake.Tree("div", { class: "kick-tools-section" });
            humanKickSection.appendChild(domMake.Tree("div", { class: "kick-tools-section-title" }, ["Voto para Expulsar (Tú)"]));
            this._humanKickPlayerListContainer = domMake.IconList({ class: "kick-tools-player-list" }); // Changed from #
            humanKickSection.appendChild(this._humanKickPlayerListContainer); // Changed from #
            container.appendChild(humanKickSection);

            // --- Section 2: Expulsar con Bot ---
            const botKickSection = domMake.Tree("div", { class: "kick-tools-section" });
            botKickSection.appendChild(domMake.Tree("div", { class: "kick-tools-section-title" }, ["Expulsar con Bot"]));
            this._botKickPlayerListContainer = domMake.IconList({ class: "kick-tools-player-list" }); // Changed from #
            botKickSection.appendChild(this._botKickPlayerListContainer); // Changed from #
            container.appendChild(botKickSection);

            // --- Section 3: Automatización de Acciones de Bots (Combined Section) ---
            const automationSection = domMake.Tree("div", { class: "kick-tools-section" });
            automationSection.appendChild(domMake.Tree("div", { class: "kick-tools-section-title" }, ["Automatización de Acciones de Bots"]));

            // Auto Expulsar Toggle Button
            const autoKickRow = domMake.Row();
            const autoKickButton = domMake.Button('<i class="fas fa-user-minus"></i> Auto Expulsar'); // Using user-minus icon for kick
            autoKickButton.classList.add("auto-action-toggle");
            autoKickButton.addEventListener("click", () => this._toggleAutoKick(autoKickButton)); // Changed from #
            autoKickRow.appendChild(autoKickButton);
            automationSection.appendChild(autoKickRow);

            // Auto Prohibir Dibujo Toggle Button (placed directly below Auto Expulsar)
            const autoProhibitDrawingRow = domMake.Row();
            const autoProhibitDrawingButton = domMake.Button('<i class="fas fa-user-slash"></i> Auto Prohibir Dibujo');
            autoProhibitDrawingButton.classList.add("auto-action-toggle");
            autoProhibitDrawingButton.addEventListener("click", () => this._toggleAutoProhibitDrawing(autoProhibitDrawingButton)); // Changed from #
            autoProhibitDrawingRow.appendChild(autoProhibitDrawingButton);
            automationSection.appendChild(autoProhibitDrawingRow);

            container.appendChild(automationSection); // Append this new combined section

            this.htmlElements.section.appendChild(container);
        }

        _setupObservers() { // Changed from #
            const playerListElement = document.getElementById("playerlist");
            if (playerListElement) {
                const observer = new MutationObserver(() => {
                    this._updatePlayerLists(); // Changed from #
                    // Explicitly call the continuous checks if toggles are active
                    if (this._isAutoKickActive) { // Changed from #
                        this._performAutoKick(); // Changed from #
                    }
                    if (this._isAutoProhibitDrawingActive) { // Changed from #
                        this._performAutoProhibitDrawing(); // Changed from #
                    }
                });
                // Observe childList for player additions/removals and attributes for drawing status changes
                observer.observe(playerListElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['data-playerid', 'style'] });
                this._updatePlayerLists(); // Changed from # // Initial update
            }
        }

        _updatePlayerLists() { // Changed from #
            this._humanKickPlayerListContainer.innerHTML = ''; // Changed from #
            this._botKickPlayerListContainer.innerHTML = '';   // Changed from #
            this._cachedPlayers = []; // Changed from # // This is correctly reset each time.

            const playerRows = document.querySelectorAll("#playerlist .playerlist-row");
            const myPlayerIdElement = document.querySelector(".playerlist-name-self")?.parentElement;
            const myPlayerId = myPlayerIdElement ? myPlayerIdElement.dataset.playerid : null;

            if (playerRows.length <= 1) { // If only self or no players
                const noPlayersMessage = domMake.Tree("span", {}, ["No hay otros jugadores."]);
                this._humanKickPlayerListContainer.appendChild(noPlayersMessage.cloneNode(true)); // Changed from #
                this._botKickPlayerListContainer.appendChild(noPlayersMessage.cloneNode(true));   // Changed from #
                return;
            }

            playerRows.forEach(playerRow => {
                const playerId = playerRow.dataset.playerid;
                // Exclude self from the list of kickable players
                if (playerId === myPlayerId) {
                    return;
                }

                const playerName = playerRow.querySelector(".playerlist-name a")?.textContent || `Jugador ${playerId}`;
                const isDrawing = playerRow.querySelector(".playerlist-draw")?.style.display !== 'none';

                this._cachedPlayers.push({ id: parseInt(playerId), name: playerName, isDrawing: isDrawing }); // Changed from #
            });

            this._renderHumanKickButtons(); // Changed from #
            this._renderBotKickButtons();   // Changed from #
        }

        _renderHumanKickButtons() { // Changed from #
            this._humanKickPlayerListContainer.innerHTML = ''; // Changed from #
            if (this._cachedPlayers.length === 0) { // Changed from #
                this._humanKickPlayerListContainer.appendChild(domMake.TextNode("No hay jugadores.")); // Changed from #
                return;
            }

            this._cachedPlayers.forEach(player => { // Changed from #
                const playerButton = domMake.Button(player.name);
                playerButton.title = `Votar para expulsar a ${player.name} (ID: ${player.id}).`;
                playerButton.addEventListener("click", () => this._sendHumanVoteKick(player.id, player.name)); // Changed from #
                this._humanKickPlayerListContainer.appendChild(playerButton); // Changed from #
            });
        }

        _renderBotKickButtons() { // Changed from #
            this._botKickPlayerListContainer.innerHTML = ''; // Changed from #
            if (this._cachedPlayers.length === 0) { // Changed from #
                this._botKickPlayerListContainer.appendChild(domMake.TextNode("No hay jugadores.")); // Changed from #
                return;
            }

            this._cachedPlayers.forEach(player => { // Changed from #
                const playerButton = domMake.Button(player.name);
                playerButton.title = `Expulsar a ${player.name} (ID: ${player.id}) con el bot seleccionado.`;
                playerButton.addEventListener("click", () => this._sendBotKick(player.id, player.name)); // Changed from #
                this._botKickPlayerListContainer.appendChild(playerButton); // Changed from #
            });
        }

        _sendHumanVoteKick(targetPlayerId, targetPlayerName) { // Changed from #
            if (globalThis.sockets && globalThis.sockets.length > 0) {
                const data = _io.emits.sendvotekick(targetPlayerId);
                globalThis.sockets[0].send(data);
                this.notify("info", `Voto para expulsar enviado para ${targetPlayerName} (ID: ${targetPlayerId}).`);
            } else {
                this.notify("warning", "No hay conexión WebSocket activa para enviar el voto de expulsión.");
            }
        }

        _getBot(skipNotification = false) { // Changed from #, added skipNotification
            const botManagerClass = this.findGlobal("BotClientManager");
            if (!botManagerClass || !botManagerClass.siblings || botManagerClass.siblings.length === 0) {
                if (!skipNotification) this.notify("warning", "No hay instancias activas de 'BotClientManager'. Por favor, crea uno desde 'CubeEngine'.");
                return null;
            }

            const botManagerInstance = botManagerClass.siblings[0];
            const botClientInterfaces = botManagerInstance.children;
            let activeBotClientInterface = null;

            const selectedBotInput = document.querySelector('input[name="botClient"]:checked');
            if (selectedBotInput) {
                activeBotClientInterface = botClientInterfaces.find(bci => bci.htmlElements.input === selectedBotInput);
            }

            if (!activeBotClientInterface && botClientInterfaces.length > 0) {
                activeBotClientInterface = botClientInterfaces[0];
                if (!skipNotification) this.notify("info", `No se seleccionó un bot. Usando el primer bot disponible: ${activeBotClientInterface.getName()}.`);
            }

            if (!activeBotClientInterface || !activeBotClientInterface.bot || !activeBotClientInterface.bot.getReadyState()) {
                if (!skipNotification) this.notify("warning", `El bot "${activeBotClientInterface ? activeBotClientInterface.getName() : 'desconocido'}" no está conectado y listo para enviar comandos.`);
                return null;
            }
            return activeBotClientInterface.bot;
        }

        _sendBotKick(targetPlayerId, targetPlayerName) { // Changed from #
            const bot = this._getBot(); // Changed from #
            if (!bot) return;

            const data = _io.emits.sendvotekick(targetPlayerId);
            bot.send(data);
            this.notify("success", `Bot "${bot.name}" envió solicitud de expulsión para ${targetPlayerName}.`);
        }

        // --- Auto Expulsar Logic ---
        _toggleAutoKick(button) { // Changed from #
            this._isAutoKickActive = !this._isAutoKickActive; // Changed from #
            button.classList.toggle("active", this._isAutoKickActive); // Changed from #
            button.innerHTML = this._isAutoKickActive // Changed from #
                ? '<i class="fas fa-user-minus"></i> Auto Expulsar Activo'
                : '<i class="fas fa-user-minus"></i> Auto Expulsar';

            if (this._isAutoKickActive) { // Changed from #
                const bot = this._getBot(true); // Changed from #, pass true to suppress initial bot not ready notification
                const humanSocketReady = globalThis.sockets && globalThis.sockets.length > 0 && globalThis.sockets[0].readyState === WebSocket.OPEN;

                if (!bot && !humanSocketReady) {
                    this.notify("error", "Necesitas al menos una conexión activa (humana o bot) para usar 'Auto Expulsar'.");
                    this._isAutoKickActive = false; // Changed from #
                    button.classList.remove("active");
                    button.innerHTML = '<i class="fas fa-user-minus"></i> Auto Expulsar';
                    return;
                }
                this.notify("success", "Automatización 'Auto Expulsar' activada. Se intentará expulsar a todos los jugadores.");
                this._kickedPlayersThisSession.clear(); // Changed from # // Clear for a new session
                this._performAutoKick(); // Changed from # // Perform initial sweep immediately
                // The periodic check is now handled by the observer triggering _updatePlayerLists, which in turn calls _performAutoKick if active.
                // No need for a separate setInterval here, as that would duplicate effort and potentially create race conditions.
            } else {
                clearInterval(this._autoKickInterval); // Changed from #
                this._autoKickInterval = null;         // Changed from #
                this.notify("info", "Automatización 'Auto Expulsar' desactivada.");
            }
        }

        _performAutoKick() { // Changed from #
            if (!this._isAutoKickActive) return; // Changed from #

            const bot = this._getBot(true); // Changed from #, pass true to suppress notification if not ready
            const myPlayerIdElement = document.querySelector(".playerlist-name-self")?.parentElement;
            const myPlayerId = myPlayerIdElement ? parseInt(myPlayerIdElement.dataset.playerid) : null;
            const humanSocketReady = globalThis.sockets && globalThis.sockets.length > 0 && globalThis.sockets[0].readyState === WebSocket.OPEN;

            // If neither human nor bot is ready, stop the auto-kick
            if (!bot && !humanSocketReady) {
                this.notify("warning", "Ni el bot ni la conexión humana están listos. Deteniendo 'Auto Expulsar'.");
                const button = document.querySelector('.auto-action-toggle:has(.fa-user-minus)');
                if (button) {
                    this._toggleAutoKick(button); // Deactivate the toggle
                }
                return;
            }

            // Filter players to target: exclude self and already targeted players in this session
            const playersToTarget = this._cachedPlayers.filter(player => { // Changed from #
                return player.id !== myPlayerId && !this._kickedPlayersThisSession.has(player.id); // Changed from #
            });

            if (playersToTarget.length === 0) {
                return; // No new players to target
            }

            playersToTarget.forEach(player => {
                const data = _io.emits.sendvotekick(player.id);
                let kickSent = false;

                if (humanSocketReady) {
                    globalThis.sockets[0].send(data);
                    this.notify("info", `Humano envió voto para expulsar a ${player.name} (ID: ${player.id}).`);
                    kickSent = true;
                }

                if (bot && bot.getReadyState()) {
                    bot.send(data);
                    this.notify("info", `Bot "${bot.name}" envió voto para expulsar a ${player.name} (ID: ${player.id}).`);
                    kickSent = true;
                }

                if (kickSent) {
                    this._kickedPlayersThisSession.add(player.id); // Changed from # // Mark as targeted even if only one sent
                } else {
                    this.notify("warning", `No se pudo enviar voto de expulsión para ${player.name}. Ni humano ni bot listos.`);
                }
            });
        }

        // --- Auto Prohibit Drawing Logic ---
        _toggleAutoProhibitDrawing(button) { // Changed from #
            this._isAutoProhibitDrawingActive = !this._isAutoProhibitDrawingActive; // Changed from #
            button.classList.toggle("active", this._isAutoProhibitDrawingActive); // Changed from #
            button.innerHTML = this._isAutoProhibitDrawingActive // Changed from #
                ? '<i class="fas fa-user-slash"></i> Auto Prohibir Dibujo Activo'
                : '<i class="fas fa-user-slash"></i> Auto Prohibir Dibujo';

            if (this._isAutoProhibitDrawingActive) { // Changed from #
                const bot = this._getBot(); // Changed from #
                if (!bot) {
                    this.notify("error", "Necesitas un bot activo y conectado para usar 'Auto Prohibir Dibujo'.");
                    this._isAutoProhibitDrawingActive = false; // Changed from #
                    button.classList.remove("active");
                    button.innerHTML = '<i class="fas fa-user-slash"></i> Auto Prohibir Dibujo';
                    return;
                }
                this.notify("success", "Automatización 'Auto Prohibir Dibujo' activada. Los jugadores que dibujen serán prohibidos periódicamente.");
                this._prohibitedPlayersThisSession.clear(); // Changed from # // Clear for a new session
                this._performAutoProhibitDrawing(); // Changed from # // Perform initial sweep
                this._autoProhibitDrawingInterval = setInterval(() => { // Changed from #
                    this._performAutoProhibitDrawing(); // Changed from #
                }, 5000); // Check every 5 seconds for new players drawing
            } else {
                clearInterval(this._autoProhibitDrawingInterval); // Changed from #
                this._autoProhibitDrawingInterval = null;         // Changed from #
                this.notify("info", "Automatización 'Auto Prohibir Dibujo' desactivada.");
            }
        }

        _performAutoProhibitDrawing() { // Changed from #
            if (!this._isAutoProhibitDrawingActive) return; // Changed from #

            const bot = this._getBot(); // Changed from #
            if (!bot || !bot.getReadyState()) {
                this.notify("warning", "Bot desconectado, deteniendo 'Auto Prohibir Dibujo'.");
                const button = document.querySelector('.auto-action-toggle:has(.fa-user-slash)');
                if (button) {
                    this._toggleAutoProhibitDrawing(button); // Deactivate the toggle
                }
                return;
            }

            const playersToProhibit = this._cachedPlayers.filter(player => { // Changed from #
                // Only consider players who are currently drawing AND have not been prohibited yet in this session
                return player.isDrawing && !this._prohibitedPlayersThisSession.has(player.id); // Changed from #
            });

            if (playersToProhibit.length === 0) {
                return;
            }

            playersToProhibit.forEach(player => {
                // pgdrawvote with value 0 means "prohibit drawing"
                const data = _io.emits.pgdrawvote(player.id, 0);
                bot.send(data);
                this._prohibitedPlayersThisSession.add(player.id); // Changed from # // Mark as prohibited
                this.notify("info", `Bot "${bot.name}" envió voto para prohibir dibujar a ${player.name} (ID: ${player.id}).`);
            });
        }
    }
})("QBit");
//  END Kick

//  START Socials
(function PlayerSocialsModule() {
    const QBit = globalThis[arguments[0]];

    // Define token names here to be self-contained
    const TOKEN_NAMES = {
        0: "Thumbs Up",
        1: "Heart",
        2: "Paint Brush",
        3: "Cocktail",
        4: "Peace Sign",
        5: "Feather",
        6: "Trophy",
        7: "Mug",
        8: "Gift"
    };

    QBit.Styles.addRules([
        `#${QBit.identifier} .player-socials-section {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 10px;
            background-color: var(--CE-bg_color);
        }`,
        `#${QBit.identifier} .player-socials-section-title {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--dark-blue-title); /* Use existing style var */
            text-align: center;
        }`,
        `#${QBit.identifier} .player-socials-afk-toggle,
         #${QBit.identifier} .player-socials-status-flags .status-flag-button,
         #${QBit.identifier} .player-socials-auto-friend-request-toggle { /* Added for new button */
            background-color: var(--secondary);
            color: var(--dark);
            width: 100%;
            padding: 5px 10px;
            box-sizing: border-box;
        }`,
        `#${QBit.identifier} .player-socials-afk-toggle.active,
         #${QBit.identifier} .player-socials-status-flags .status-flag-button.active,
         #${QBit.identifier} .player-socials-auto-friend-request-toggle.active { /* Added for new button */
            background-color: var(--info); /* Consistent active color */
            color: white;
        }`,
        `#${QBit.identifier} .player-socials-token-list .icon {
            width: 38px; /* Slightly larger icons */
            height: 38px;
            min-width: 38px;
            min-height: 38px;
            font-size: 1.2em; /* Larger icon itself */
        }`,
        /* Styles for Manual Friend Request List (NEW) */
        `#${QBit.identifier} .manual-friend-request-list {
            max-height: 200px;
            overflow-y: auto;
            border: 1px dashed var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            display: flex;
            flex-direction: column;
            gap: 5px;
        }`,
        `#${QBit.identifier} .manual-friend-request-item {
            display: flex;
            align-items: center;
            justify-content: space-between;
            background-color: rgba(0,0,0,0.1);
            padding: 5px;
            border-radius: .15rem;
            font-size: 0.9em;
        }`,
        `#${QBit.identifier} .manual-friend-request-item .player-name {
            flex-grow: 1;
            font-weight: bold;
            margin-right: 5px;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }`,
        `#${QBit.identifier} .manual-friend-request-item .action-buttons button {
            padding: 3px 8px;
            font-size: 0.8em;
            margin-left: 5px;
            white-space: nowrap;
        }`,
        `#${QBit.identifier} .manual-friend-request-item .status-text {
            color: var(--info); /* Consistent active color */
            font-weight: bold;
            margin-right: 5px;
            white-space: nowrap;
        }`,
        `#${QBit.identifier} .manual-friend-request-item .status-text.guest {
            color: var(--warning); /* Yellow for guests/non-logged */
        }`,
        `#${QBit.identifier} .manual-friend-request-item .status-text.already-friend {
            color: var(--success); /* Green for already friends */
        }`,
        `#${QBit.identifier} .manual-friend-request-item .status-text.pending {
            color: var(--orange); /* Orange for pending requests */
        }`
    ]);

    class PlayerSocials extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        // Custom Chat
        #messageInput;

        // Toggle AFK
        #isAfkActive = false;
        #afkToggleButton;

        // Global Token Giver
        #playerList = []; // Will store { id, name, avatarUid, isLoggedIn }

        // Set Status Flag
        #statusFlagsState = {}; // To keep track of active flags

        // Auto Friend Request
        #isAutoFriendRequestActive = false;
        #autoFriendRequestToggleButton;
        #autoFriendRequestSentUids = new Set(); // To prevent duplicate requests per session
        #autoFriendRequestTimer = null; // For delayed sending

        // Manual Friend Request (NEW)
        #manualFriendRequestListContainer;
        #manualFriendRequestSentUids = new Set(); // Tracks UIDs for manually sent requests in this session

        constructor() {
            super("Sociales del Jugador", '<i class="fas fa-comments-dollar"></i>'); // New icon
            this.#onStartup();
        }

        #onStartup() {
            this.#loadInterface();
            this.#setupObservers(); // Setup MutationObserver for player list
        }

        #loadInterface() {
            const container = domMake.Tree("div");

            // --- Section: Custom Chat Message ---
            const chatSection = domMake.Tree("div", { class: "player-socials-section" });
            chatSection.appendChild(domMake.Tree("div", { class: "player-socials-section-title" }, ["Enviar Mensaje"]));
            const chatRow = domMake.Row();
            this.#messageInput = domMake.Tree("input", { type: "text", placeholder: "Tu mensaje..." });
            const sendButton = domMake.Button('<i class="fas fa-paper-plane"></i>');
            sendButton.classList.add("icon");
            sendButton.addEventListener("click", () => {
                this.#sendMessage(this.#messageInput.value);
                this.#messageInput.value = '';
            });
            this.#messageInput.addEventListener("keypress", (event) => {
                if (event.keyCode === 13) {
                    this.#sendMessage(this.#messageInput.value);
                    this.#messageInput.value = '';
                }
            });
            chatRow.appendAll(this.#messageInput, sendButton);
            chatSection.appendChild(chatRow);
            container.appendChild(chatSection);

            // --- Section: Toggle AFK ---
            const afkSection = domMake.Tree("div", { class: "player-socials-section" });
            afkSection.appendChild(domMake.Tree("div", { class: "player-socials-section-title" }, ["Estado AFK"]));
            const afkRow = domMake.Row();
            this.#afkToggleButton = domMake.Button("Toggle AFK");
            this.#afkToggleButton.classList.add("player-socials-afk-toggle");
            this.#afkToggleButton.addEventListener("click", () => this.#toggleAfkStatus());
            afkRow.appendChild(this.#afkToggleButton);
            afkSection.appendChild(afkRow);
            container.appendChild(afkSection);

            // --- Section: Global Token Giver ---
            const tokensSection = domMake.Tree("div", { class: "player-socials-section" });
            tokensSection.appendChild(domMake.Tree("div", { class: "player-socials-section-title" }, ["Dar Emblemas"]));
            const tokensRow = domMake.IconList({ class: "player-socials-token-list" });
            const tokens = [
                { id: 0, icon: '<i class="fas fa-thumbs-up"></i>' },
                { id: 1, icon: '<i class="fas fa-heart"></i>' },
                { id: 2, icon: '<i class="fas fa-paint-brush"></i>' },
                { id: 3, icon: '<i class="fas fa-cocktail"></i>' },
                { id: 4, icon: '<i class="fas fa-hand-peace"></i>' },
                { id: 5, icon: '<i class="fas fa-feather-alt"></i>' },
                { id: 6, icon: '<i class="fas fa-trophy"></i>' },
                { id: 7, icon: '<i class="fas fa-mug-hot"></i>' },
                { id: 8, icon: '<i class="fas fa-gift"></i>' }
            ];
            tokens.forEach(token => {
                const tokenButton = domMake.Button(token.icon);
                tokenButton.classList.add("icon");
                tokenButton.title = `Dar Emblema: ${TOKEN_NAMES[token.id]}`;
                tokenButton.addEventListener("click", () => this.#giveToken(token.id));
                tokensRow.appendChild(tokenButton);
            });
            tokensSection.appendChild(tokensRow);
            container.appendChild(tokensSection);

            // --- Section: Set Status Flag ---
            const statusSection = domMake.Tree("div", { class: "player-socials-section" });
            statusSection.appendChild(domMake.Tree("div", { class: "player-socials-section-title" }, ["Establecer Estado"]));
            const flags = [
                { id: 0, name: "Música", icon: '<i class="fas fa-music"></i>' },
                { id: 1, name: "AFK 1", icon: '<i class="fas fa-bed"></i>' },
                { id: 2, name: "AFK 2", icon: '<i class="fas fa-couch"></i>' },
                { id: 3, name: "Inventario Abierto", icon: '<i class="fas fa-box-open"></i>' },
                { id: 4, name: "Lista Amigos Abierta", icon: '<i class="fas fa-user-friends"></i>' }
            ];
            flags.forEach(flag => {
                const flagRow = domMake.Row();
                const toggleButton = domMake.Button(flag.icon + " " + flag.name);
                toggleButton.classList.add("status-flag-button");
                toggleButton.dataset.flagId = flag.id;
                toggleButton.dataset.isActive = "false"; // Initial state
                this.#statusFlagsState[flag.id] = false; // Initialize internal state

                toggleButton.addEventListener("click", () => this.#toggleStatusFlag(flag.id, toggleButton));
                flagRow.appendChild(toggleButton);
                statusSection.appendChild(flagRow);
            });
            container.appendChild(statusSection);

            // --- Section: Auto Friend Request (NEW) ---

            const manualFriendRequestSection = domMake.Tree("div", { class: "player-socials-section" });
            manualFriendRequestSection.appendChild(domMake.Tree("div", { class: "player-socials-section-title" }, ["Solicitudes de Amistad Automaticas"]));
            this.#manualFriendRequestListContainer = domMake.Tree("div", { class: "manual-friend-request-list" });
            manualFriendRequestSection.appendChild(this.#manualFriendRequestListContainer);
            container.appendChild(manualFriendRequestSection);

            this.htmlElements.section.appendChild(container);
        }

        #setupObservers() {
            // Observer for player list changes (for Token Giver and Auto/Manual Friend Request)
            const playerListElement = document.getElementById("playerlist");
            if (playerListElement) {
                const observer = new MutationObserver(() => {
                    this.#updatePlayerList();
                    // If auto friend request is active, try to send requests to new players
                    if (this.#isAutoFriendRequestActive) {
                        this.#sendAutoFriendRequests();
                    }
                    // Always re-render manual list on player changes
                    this.#renderManualFriendRequestList();
                });
                observer.observe(playerListElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['data-playerid', 'data-loggedin'] });
                this.#updatePlayerList(); // Initial update
                this.#renderManualFriendRequestList(); // Initial render of manual list
            }
        }

        // --- Custom Chat Message Methods ---
        #sendMessage(message) {
            if (!message.trim()) return;
            if (globalThis.sockets && globalThis.sockets.length > 0) {
                const data = _io.emits.chatmsg(message);
                globalThis.sockets[0].send(data);
                this.notify("info", `Mensaje enviado: "${message}"`);
            } else {
                this.notify("warning", "No hay conexión WebSocket activa.");
            }
        }

        // --- Toggle AFK Methods ---
        #toggleAfkStatus() {
            this.#isAfkActive = !this.#isAfkActive;
            if (globalThis.sockets && globalThis.sockets.length > 0) {
                const data = _io.emits.playerafk();
                globalThis.sockets[0].send(data);
                this.#afkToggleButton.classList[this.#isAfkActive ? "add" : "remove"]("active");
                this.#afkToggleButton.textContent = this.#isAfkActive ? "AFK Activo" : "Toggle AFK"; // Changed text for clarity
                this.notify("info", `Estado AFK cambiado a: ${this.#isAfkActive}`);
            } else {
                this.notify("warning", "No hay conexión WebSocket activa.");
            }
        }

        // --- Global Token Giver Methods ---
        #updatePlayerList() {
            this.#playerList = [];
            const playerRows = document.querySelectorAll("#playerlist .playerlist-row");
            const myPlayerIdElement = document.querySelector(".playerlist-name-self")?.parentElement;
            const myPlayerId = myPlayerIdElement ? myPlayerIdElement.dataset.playerid : null;

            playerRows.forEach(playerRow => {
                const playerId = playerRow.dataset.playerid;
                const playerNameElement = playerRow.querySelector(".playerlist-name a");
                const playerName = playerNameElement ? playerNameElement.textContent.trim() : `Jugador ${playerId}`;
                const avatarImg = playerRow.querySelector(".playerlist-avatar");
                const avatarUrl = avatarImg ? avatarImg.src : null;

                // Extract UID from avatar URL. If it's "default.jpg" or "undefined.jpg", it's not a real account UID.
                const avatarUidMatch = avatarUrl ? avatarUrl.match(/\/([a-f0-9-]+)\.jpg$/i) : null;
                const avatarUid = (avatarUidMatch && avatarUidMatch[1] && avatarUidMatch[1] !== 'default' && avatarUidMatch[1] !== 'undefined') ? avatarUidMatch[1] : null;

                // Check data-loggedin attribute directly from playerlist-row element
                const isLoggedIn = playerRow.dataset.loggedin === "true";

                if (playerId && playerId !== myPlayerId) { // Exclude self
                    this.#playerList.push({
                        id: parseInt(playerId),
                        name: playerName,
                        avatarUid: avatarUid,
                        isLoggedIn: isLoggedIn
                    });
                }
            });
        }

        #giveToken(tokenId) {
            if (globalThis.sockets && globalThis.sockets.length > 0) {
                if (this.#playerList.length > 0) {
                    this.#playerList.forEach(player => {
                        const data = _io.emits.settoken(player.id, tokenId);
                        globalThis.sockets[0].send(data);
                        this.notify("info", `Emblema '${TOKEN_NAMES[tokenId]}' enviado a ${player.name} (ID: ${player.id}).`);
                    });
                } else {
                    this.notify("warning", "No se encontraron jugadores en la sala para dar emblemas.");
                }
            } else {
                this.notify("warning", "No hay conexión WebSocket activa.");
            }
        }

        // --- Set Status Flag Methods ---
        #toggleStatusFlag(flagId, buttonElement) {
            const newActiveState = !this.#statusFlagsState[flagId];
            this.#statusFlagsState[flagId] = newActiveState;
            buttonElement.classList[newActiveState ? "add" : "remove"]("active");
            buttonElement.dataset.isActive = newActiveState; // Update dataset

            if (globalThis.sockets && globalGlobal.sockets.length > 0) {
                const data = _io.emits.setstatusflag(flagId, newActiveState);
                globalThis.sockets[0].send(data);
                this.notify("info", `Estado '${buttonElement.textContent.trim()}' cambiado a: ${newActiveState}`);
            } else {
                this.notify("warning", "No hay conexión WebSocket activa.");
            }
        }

        // --- Auto Friend Request Methods ---
        #toggleAutoFriendRequest(button) {
            this.#isAutoFriendRequestActive = !this.#isAutoFriendRequestActive;
            button.classList.toggle("active", this.#isAutoFriendRequestActive);
            button.innerHTML = this.#isAutoFriendRequestActive
                ? '<i class="fas fa-user-check"></i> Auto Solicitud de Amistad Activo'
                : '<i class="fas fa-user-plus"></i> Auto Solicitud de Amistad';

            if (this.#isAutoFriendRequestActive) {
                if (typeof window.LOGGEDIN === 'undefined' || !window.LOGGEDIN) {
                    this.notify("error", "Debes iniciar sesión para usar la función de Solicitud de Amistad Automática.");
                    this.#isAutoFriendRequestActive = false;
                    button.classList.remove("active");
                    button.innerHTML = '<i class="fas fa-user-plus"></i> Auto Solicitud de Amistad';
                    return;
                }
                // Check for window.friendswg (Drawaria's friends module) before attempting to use it
                // We need isfriend to check if they are ALREADY friends.
                if (typeof window.friendswg === 'undefined' || typeof window.friendswg.isfriend !== 'function') {
                    this.notify("error", "El módulo de amigos de Drawaria (friends.js) no está cargado o no está listo. La función de Solicitud de Amistad Automática no funcionará.");
                    this.#isAutoFriendRequestActive = false;
                    button.classList.remove("active");
                    button.innerHTML = '<i class="fas fa-user-plus"></i> Auto Solicitud de Amistad';
                    return;
                }
                this.notify("success", "Solicitud de Amistad Automática activada. Se enviarán solicitudes a los jugadores logueados de la sala.");
                this.#autoFriendRequestSentUids.clear(); // Clear previous session's sent UIDs for auto-sender
                this.#sendAutoFriendRequests(); // Immediately send requests to current players
            } else {
                clearInterval(this.#autoFriendRequestTimer); // Stop any pending requests
                this.#autoFriendRequestTimer = null;
                this.notify("info", "Solicitud de Amistad Automática desactivada.");
            }
        }

        async #_sendFriendRequest(playerUid) {
            if (!playerUid) {
                // This case should be handled by calling code filtering out null UIDs
                // but as a fallback, explicitly notify that a request cannot be sent without a valid UID.
                this.notify("error", "Error: UID de jugador no válido para enviar solicitud (probablemente invitado).");
                return { success: false, status: 'invalid_uid' };
            }

            try {
                const response = await fetch('/friendsapi/friendrequest', {
                    method: 'POST',
                    headers: {
                        'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8',
                    },
                    body: `action=outsend&uid=${playerUid}`,
                });

                if (!response.ok) {
                    throw new Error(`HTTP error! Status: ${response.status}`);
                }

                const data = await response.json();

                if (data && data.res === 'ok') {
                    // This implies the request was successfully sent or is pending
                    return { success: true, status: 'ok' };
                } else if (data && data.error) {
                    let status = data.error;
                    let errorMessage = `Error al enviar solicitud a ${playerUid}: `;
                    switch (status) {
                        case 'alreadyinlist':
                            errorMessage += "Ya son amigos.";
                            break;
                        case 'requestduplicate':
                            errorMessage += "Solicitud pendiente.";
                            break;
                        case 'self':
                            errorMessage += "No puedes enviarte solicitud a ti mismo.";
                            break;
                        case 'friendlimit':
                            errorMessage += "Límite de amigos alcanzado.";
                            break;
                        case 'toofast':
                            errorMessage += "Demasiadas solicitudes, espera un momento.";
                            break;
                        default:
                            errorMessage += `Error desconocido (${status}).`;
                    }
                    this.notify("warning", errorMessage);
                    return { success: false, status: status };
                } else {
                    this.notify("warning", `Respuesta inesperada del servidor al enviar solicitud a ${playerUid}.`);
                    return { success: false, status: 'unexpected_response' };
                }
            } catch (error) {
                this.notify("error", `Fallo de red o en el servidor al enviar solicitud a ${playerUid}: ${error.message}`);
                console.error("Friend Request Fetch Error:", error);
                return { success: false, status: 'fetch_error' };
            }
        }

        async #sendAutoFriendRequests() {
            if (!this.#isAutoFriendRequestActive || !window.LOGGEDIN || !window.friendswg) {
                clearInterval(this.#autoFriendRequestTimer);
                this.#autoFriendRequestTimer = null;
                return;
            }

            // Players to consider for sending requests: Must be logged-in, have a valid UID,
            // and not already processed by the AUTO-sender in this session.
            const playersToProcess = this.#playerList.filter(player =>
                player.isLoggedIn &&
                player.avatarUid && // Ensure UID is not null (i.e., not a guest)
                !this.#autoFriendRequestSentUids.has(player.avatarUid) // Crucial: Not already processed by auto-sender in THIS session
            );

            if (playersToProcess.length === 0) {
                return; // No new eligible players to process automatically
            }

            clearInterval(this.#autoFriendRequestTimer);
            this.#autoFriendRequestTimer = null;

            let index = 0;
            const processNextRequest = async () => {
                if (index < playersToProcess.length && this.#isAutoFriendRequestActive) {
                    const player = playersToProcess[index];

                    // Check friend status using Drawaria's internal friendswg module
                    // This is for frontend logging; the server will do its own checks anyway.
                    if (window.friendswg.isfriend(player.avatarUid)) {
                        this.notify("info", `Saltando (automático): ${player.name} ya es tu amigo.`);
                        this.#autoFriendRequestSentUids.add(player.avatarUid); // Mark as processed by auto-sender
                    } else {
                        // Attempt to send the request
                        const result = await this.#_sendFriendRequest(player.avatarUid);
                        if (result.success || result.status === 'requestduplicate' || result.status === 'alreadyinlist') {
                            this.notify("success", `Solicitud auto a ${player.name} (${result.status}).`);
                            this.#autoFriendRequestSentUids.add(player.avatarUid); // Mark as processed by auto-sender
                        } else {
                            this.notify("warning", `Fallo auto a ${player.name} (${result.status}).`);
                            // If sending failed (e.g., server error, rate limit), do NOT add to #autoFriendRequestSentUids
                            // This allows retrying later if conditions improve and they reappear in the list.
                        }
                    }

                    index++;
                    this.#autoFriendRequestTimer = setTimeout(processNextRequest, 500); // 500ms delay between requests
                } else if (index >= playersToProcess.length) {
                    this.notify("info", "Todas las solicitudes automáticas de amistad de esta ronda han sido procesadas.");
                    this.#autoFriendRequestTimer = null;
                }
            };

            this.notify("info", `Iniciando envío de ${playersToProcess.length} solicitudes automáticas de amistad.`);
            this.#autoFriendRequestTimer = setTimeout(processNextRequest, 0);
        }

        // --- Manual Friend Request List (NEW) Methods ---
        #renderManualFriendRequestList() {
            this.#manualFriendRequestListContainer.innerHTML = ''; // Clear existing list

            if (typeof window.LOGGEDIN === 'undefined' || !window.LOGGEDIN) {
                this.#manualFriendRequestListContainer.appendChild(domMake.TextNode("Debes iniciar sesión para ver esta lista."));
                return;
            }

            // Check for window.friendswg (Drawaria's friends module)
            if (typeof window.friendswg === 'undefined' || typeof window.friendswg.isfriend !== 'function') {
                this.#manualFriendRequestListContainer.appendChild(domMake.TextNode("El módulo de amigos de Drawaria no está cargado."));
                return;
            }

            // Display ALL players (excluding self) in the room
            const playersToList = [...this.#playerList]; // Create a copy to iterate

            if (playersToList.length === 0) {
                this.#manualFriendRequestListContainer.appendChild(domMake.TextNode("No hay jugadores en la sala."));
                return;
            }

            playersToList.forEach(player => {
                const playerItem = domMake.Tree("div", { class: "manual-friend-request-item" });
                playerItem.appendChild(domMake.Tree("span", { class: "player-name", title: player.name }, [player.name]));

                const actionButtons = domMake.Tree("div", { class: "action-buttons" });

                let statusTextNode = null;
                // Removed: let sendButtonDisabled = false; (now always true if conditions below met)
                let sendButtonInitialText = '<i class="fas fa-user-plus"></i> Enviar';
                let statusClass = '';

                // Determine status and button state
                if (!player.isLoggedIn || !player.avatarUid) {
                    statusTextNode = domMake.TextNode('Invitado');
                    statusClass = 'guest';
                    // Removed: sendButtonDisabled = true;
                } else if (window.friendswg.isfriend(player.avatarUid)) {
                    statusTextNode = domMake.TextNode('Ya Amigo');
                    statusClass = 'already-friend';
                    // Removed: sendButtonDisabled = true;
                } else if (this.#autoFriendRequestSentUids.has(player.avatarUid)) {
                    statusTextNode = domMake.TextNode('Auto Enviada');
                    statusClass = 'pending';
                    // Removed: sendButtonDisabled = true;
                } else if (this.#manualFriendRequestSentUids.has(player.avatarUid)) {
                    statusTextNode = domMake.TextNode('Manual Enviada');
                    statusClass = 'pending';
                    // Removed: sendButtonDisabled = true;
                }

                if (statusTextNode) {
                    const statusSpan = domMake.Tree("span", { class: `status-text ${statusClass}` }, [statusTextNode]);
                    playerItem.appendChild(statusSpan);
                }

                const sendRequestButton = domMake.Button(sendButtonInitialText);
                sendRequestButton.title = `Enviar solicitud de amistad a ${player.name}`;
                // sendRequestButton.disabled = sendButtonDisabled; // Removed this line
                sendRequestButton.addEventListener('click', async (e) => {
                    const button = e.currentTarget;
                    button.disabled = true; // Still good to disable on click to prevent spam
                    button.innerHTML = '<i class="fas fa-hourglass-half"></i>'; // Indicate processing

                    // Attempt to send the request
                    const result = await this.#_sendFriendRequest(player.avatarUid);

                    if (result.success || result.status === 'requestduplicate' || result.status === 'alreadyinlist') {
                        this.notify("success", `Solicitud de amistad manual a: ${player.name} (${result.status}).`);
                        this.#manualFriendRequestSentUids.add(player.avatarUid); // Mark as processed manually
                        // Re-render the list after successful/processed manual send
                        this.#renderManualFriendRequestList();
                    } else {
                        this.notify("warning", `Fallo manual a ${player.name} (${result.status}).`);
                        button.disabled = false; // Re-enable if sending failed
                        button.innerHTML = '<i class="fas fa-user-plus"></i> Reintentar';
                    }
                });
                actionButtons.appendChild(sendRequestButton);

                const viewProfileButton = domMake.Button('<i class="fas fa-user-circle"></i> Perfil');
                viewProfileButton.title = `Ver perfil de ${player.name}`;
                // Removed: viewProfileButton.disabled = !player.avatarUid;
                viewProfileButton.addEventListener('click', () => {
                    if (player.avatarUid) { // Still good to check for valid UID before opening
                        window.open(`https://drawaria.online/profile/?uid=${player.avatarUid}`, '_blank');
                    } else {
                         this.notify("warning", "No se puede abrir el perfil de un jugador invitado.");
                    }
                });
                actionButtons.appendChild(viewProfileButton);

                playerItem.appendChild(actionButtons);
                this.#manualFriendRequestListContainer.appendChild(playerItem);
            });
        }
    }
})("QBit");
// --- END NEW MODULE: PlayerSocials ---

// --- END NEW MODULE: PlayerSocials ---

// START CLIENT

(function BotClientModifications() {
    const QBit = globalThis[arguments[0]];

    // Re-declare parseServerUrl and parseRoomId for consistent local scope and functionality
    function parseServerUrl(any) {
        var prefix = String(any).length == 1 ? `sv${any}.` : "";
        let baseUrl = `wss://${prefix}drawaria.online/socket.io/?`;
        let params = `EIO=3&transport=websocket`;

        if (prefix === "") {
            params = `EIO=3&transport=websocket`;
        } else {
            params = `sid1=undefined&hostname=drawaria.online&EIO=3&transport=websocket`;
        }
        return baseUrl + params;
    }

    function parseRoomId(any) {
        if (!typecheck.isString(any)) {
          any = String(any);
        }
        const match = any.match(/([a-f0-9.-]+?)$/gi);
        if (match && match[0]) {
          return match[0];
        }
        return any;
    }

    // AÑADIDO: RE-DECLARACIÓN DE parseSocketIOEvent para su accesibilidad
    function parseSocketIOEvent(prefix_length, event_data) {
        try {
            return JSON.parse(event_data.slice(prefix_length));
        } catch (error) {
            // Puedes añadir un console.error aquí para depuración si es necesario
            // console.error("Error parsing socket event data:", error, "Data:", event_data);
            return null; // Retorna null o un array vacío si falla el parseo
        }
    }
    // FIN AÑADIDO

    const emits = _io.emits;
    const OriginalBotClient = QBit.findGlobal("BotClient");

    if (OriginalBotClient) {
        Object.assign(OriginalBotClient.prototype, {
            _onSocketOpenHandler(event) {
                const localThis = this;
                clearInterval(localThis.interval_id);
                localThis.interval_id = setInterval(function () {
                    if (!localThis.getReadyState()) {
                        clearInterval(localThis.interval_id);
                        localThis.notify("info", `Bot ${localThis.name} desconectado (ping fallido).`);
                        return;
                    }
                    localThis.send(2); // Keep-alive ping
                }, localThis.interval_ms);

                if (localThis.room.id) {
                    localThis.send(emits.startplay(localThis.room, localThis.name, localThis.avatar));
                    localThis.notify("success", `Bot ${localThis.name} conectado y en sala ${localThis.room.id} (Tipo: ${localThis.room.type}).`);
                } else {
                    localThis.notify("warning", `Bot ${localThis.name} conectado, pero sin ID de sala para iniciar juego.`);
                }
            },

            _onSocketMessageHandler(message_event) {
                var prefix = String(message_event.data).match(/(^\d+)/gi)?.[0] || "";
                var data = parseSocketIOEvent(prefix.length, message_event.data) || [];

                if (data && data.length === 1) {
                    if (data[0].players) this.room.players = data[0].players;
                }
                if (data && data.length > 1) {
                    var event = data.shift();

                    if (event === "drawcmd") {
                        this.customObservers.forEach((listener) => {
                            if (listener.event === "bc_drawcommand") {
                                if (listener.callback) listener.callback(data);
                            }
                        });
                    }

                    this.customObservers.forEach((listener) => {
                        if (listener.event === event) if (listener.callback) listener.callback(data);
                    });
                }
            },

            _onSocketCloseHandler(event) {
                clearInterval(this.interval_id);
                this.socket = null;
                this.notify("info", `Bot ${this.name} socket cerrado. Código: ${event.code}, Razón: ${event.reason}`);
            },

            _onSocketErrorHandler(event) {
                this.notify("error", `Error de socket para bot ${this.name}.`);
                console.error(`WebSocket Error for ${this.name}:`, event);
                clearInterval(this.interval_id);
                this.socket = null;
            },

            // SOBREESCRITO: connect method
            connect(serverUrlSegment = "") {
                if (this.getReadyState()) {
                    // Si ya está conectado, no hace nada o lo registra.
                    // enterRoom se encargará de desconectar antes de llamar a connect.
                    this.notify("info", `Bot ${this.name} ya está en estado de conexión o conectado. No se iniciará una nueva conexión desde connect().`);
                    return;
                }

                const fullServerUrl = parseServerUrl(serverUrlSegment);
                this.socket = new WebSocket(fullServerUrl);

                this.socket.addEventListener("open", this._onSocketOpenHandler.bind(this));
                this.socket.addEventListener("message", this._onSocketMessageHandler.bind(this));
                this.socket.addEventListener("close", this._onSocketCloseHandler.bind(this));
                this.socket.addEventListener("error", this._onSocketErrorHandler.bind(this));

                this.notify("info", `Bot ${this.name} intentando conectar a: ${fullServerUrl}`);
            },

            // SOBREESCRITO: enterRoom method
            enterRoom(roomid, roomTypeOverride = null) {
                // 1. Limpiar cualquier conexión anterior para evitar estados inconsistentes
                if (this.getReadyState()) {
                    this.notify("info", `Bot ${this.name} ya está conectado. Desconectando para unirse a la nueva sala.`);
                    this.disconnect(); // Esto cerrará el socket y limpiará el interval_id
                }

                // 2. Establecer la información de la sala (ID y Tipo)
                this.room.id = parseRoomId(roomid);
                if (roomTypeOverride !== null) {
                    this.room.type = roomTypeOverride;
                } else {
                    // Fallback para cuando enterRoom es llamado sin roomTypeOverride
                    // (ej. desde el botón "Enter" de la interfaz del bot)
                    const parts = String(roomid).split('.');
                    if (parts.length === 1 && !isNaN(parseInt(parts[0], 16))) { // Asume que un UUID sin serverId es del servidor principal
                        this.room.type = 0; // Tipo común para dibujo libre/plantillas
                        this.notify("info", `Bot ${this.name}: Tipo de sala no especificado, asumiendo tipo 0 para ${this.room.id}.`);
                    } else {
                        this.room.type = 2; // Default para salas con serverId si no se especifica o es un ID desconocido
                        this.notify("info", `Bot ${this.name}: Tipo de sala no especificado, asumiendo tipo 2 para ${this.room.id}.`);
                    }
                }
                this.notify("info", `Bot ${this.name}: Preparando para unirse a sala ${this.room.id} (Tipo: ${this.room.type}).`);


                // 3. Iniciar la conexión WebSocket
                // Determina el serverIdSegment para la conexión.
                let serverIdSegment = "";
                const roomParts = String(roomid).split('.');
                if (roomParts.length > 1 && !isNaN(parseInt(roomParts[roomParts.length - 1]))) {
                    serverIdSegment = roomParts[roomParts.length - 1];
                }
                this.connect(serverIdSegment); // Llama a connect para establecer el socket

                // Nota: el comando 'startplay' se enviará automáticamente en _onSocketOpenHandler una vez que el socket esté listo.
            },

            // SOBREESCRITO: disconnect method
            disconnect() {
                if (!this.getReadyState()) {
                    this.notify("info", `Bot ${this.name} ya está desconectado.`);
                    return;
                }
                clearInterval(this.interval_id);
                this.send(41); // Enviar mensaje de desconexión explícito al servidor
                this.socket.close(); // Cerrar la conexión WebSocket
                this.socket = null; // Limpiar la referencia del socket
                this.notify("info", `Bot ${this.name} se ha desconectado de la sala.`);
            },

            // SOBREESCRITO: reconnect method
            reconnect() {
                if (this.getReadyState()) {
                    // Si el socket está abierto, simplemente intenta reanudar el juego en la misma sala
                    this.notify("info", `Bot ${this.name} intentando reanudar en la sala ${this.room.id}.`);
                    this.send(41); // Desconectar sesión actual
                    this.send(40); // Re-enviar startplay
                } else {
                    // Si el socket está cerrado, iniciar un proceso completo de unión a la sala
                    this.notify("info", `Bot ${this.name} socket cerrado, intentando reconectar completamente a la sala ${this.room.id}.`);
                    // Llama a enterRoom, que limpiará el estado y creará una nueva conexión
                    this.enterRoom(this.room.id, this.room.type);
                }
            }
        });
    } else {
        console.error("BotClient class not found for modification. Join room functionality may not work.");
    }
})("QBit");

(function AutoManagerV3() {
    const QBit = window.QBit; // Directly reference window.QBit for robustness

    // Panther Mode Text Font Map
    const FONT_MAP = {
        'A': [[0,1,1,0,0],[1,0,0,1,0],[1,0,0,1,0],[1,1,1,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0]],
        'B': [[1,1,1,0,0],[1,0,0,1,0],[1,0,0,1,0],[1,1,1,0,0],[1,0,0,1,0],[1,0,0,1,0],[1,1,1,0,0]],
        'C': [[0,1,1,1,0],[1,0,0,0,0],[1,0,0,0,0],[1,0,0,0,0],[1,0,0,0,0],[1,0,0,0,0],[0,1,1,1,0]],
        'D': [[1,1,1,0,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,1,1,0,0]],
        'E': [[1,1,1,1,0],[1,0,0,0,0],[1,0,0,0,0],[1,1,1,0,0],[1,0,0,0,0],[1,0,0,0,0],[1,1,1,1,0]],
        'F': [[1,1,1,1,0],[1,0,0,0,0],[1,0,0,0,0],[1,1,1,0,0],[1,0,0,0,0],[1,0,0,0,0],[1,0,0,0,0]],
        'G': [[0,1,1,1,0],[1,0,0,0,0],[1,0,0,0,0],[1,0,1,1,0],[1,0,0,1,0],[1,0,0,1,0],[0,1,1,1,0]],
        'H': [[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,1,1,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0]],
        'I': [[1,1,1,0,0],[0,1,0,0,0],[0,1,0,0,0],[0,1,0,0,0],[0,1,0,0,0],[0,1,0,0,0],[1,1,1,0,0]],
        'J': [[0,0,1,0,0],[0,0,1,0,0],[0,0,1,0,0],[0,0,1,0,0],[1,0,1,0,0],[1,0,1,0,0],[0,1,0,0,0]],
        'K': [[1,0,0,1,0],[1,0,1,0,0],[1,1,0,0,0],[1,0,0,0,0],[1,1,0,0,0],[1,0,1,0,0],[1,0,0,1,0]],
        'L': [[1,0,0,0,0],[1,0,0,0,0],[1,0,0,0,0],[1,0,0,0,0],[1,0,0,0,0],[1,0,0,0,0],[1,1,1,1,0]],
        'M': [[1,0,0,1,0],[1,1,1,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0]],
        'N': [[1,0,0,1,0],[1,1,0,1,0],[1,0,1,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0]],
        'O': [[0,1,1,0,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[0,1,1,0,0]],
        'P': [[1,1,1,0,0],[1,0,0,1,0],[1,0,0,1,0],[1,1,1,0,0],[1,0,0,0,0],[1,0,0,0,0],[1,0,0,0,0]],
        'Q': [[0,1,1,0,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,1,1,0],[1,0,1,0,0],[0,1,0,1,0]],
        'R': [[1,1,1,0,0],[1,0,0,1,0],[1,0,0,1,0],[1,1,1,0,0],[1,0,1,0,0],[1,0,0,1,0],[1,0,0,1,0]],
        'S': [[0,1,1,1,0],[1,0,0,0,0],[1,0,0,0,0],[0,1,1,0,0],[0,0,0,1,0],[0,0,0,1,0],[1,1,1,0,0]],
        'T': [[1,1,1,1,1],[0,0,1,0,0],[0,0,1,0,0],[0,0,1,0,0],[0,0,1,0,0],[0,0,1,0,0],[0,0,1,0,0]],
        'U': [[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[0,1,1,0,0]],
        'V': [[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[0,1,1,0,0],[0,0,1,0,0]],
        'W': [[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[1,1,1,1,0],[1,0,0,1,0]],
        'X': [[1,0,0,1,0],[1,0,0,1,0],[0,1,1,0,0],[0,0,1,0,0],[0,1,1,0,0],[1,0,0,1,0],[1,0,0,1,0]],
        'Y': [[1,0,0,1,0],[1,0,0,1,0],[1,0,0,1,0],[0,1,1,0,0],[0,0,1,0,0],[0,0,1,0,0],[0,0,1,0,0]],
        'Z': [[1,1,1,1,0],[0,0,0,1,0],[0,0,1,0,0],[0,1,0,0,0],[1,0,0,0,0],[1,0,0,0,0],[1,1,1,1,0]]
    };

    class AutoManagerV3 extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        #previewCanvas;
        #originalCanvas;
        #imageData = null;
        #executionLine = [];
        #drawingActive = false;
        #currentDrawingIndex = 0;

        #currentDrawingMode = 'rainMode'; // Default mode
        #rainColumns = []; // For rain mode
        #spiralAngle = 0; // For spiral mode

        #imageSizeInput;
        #brushSizeInput;
        #pixelSizeInput;
        #offsetXInput;
        #offsetYInput;
        #drawingSpeedInput;
        #modeButtons = {};
        #imageFileInput;
        #statusMessageElement;

        #currentHue = 0;
        #rainbowMode = false;
        #rainbowSpeed = 50;
        #rainbowDirection = 'forward';
        #rainbowSpeedInput;
        #rainbowDirectionSelect;
        #lastRainbowUpdate = 0;

        #pantherTextInput;
        #pantherTextSizeInput;
        #pantherTextColorInput;

        #colorToleranceInput;

        // New fields for MiniCanvas and Ruler
        #miniCanvas;
        #miniCtx;
        #isMiniDrawing = false;
        #miniLastX;
        #miniLastY;

        #rulerMode = false;
        #rulerHorizontal = false;
        #rulerVertical = false;
        #rulerV2 = false;
        #rulerAngle = 0;
        #rulerX = 100; // Initial X position for ruler line on miniCanvas
        #rulerY = 100; // Initial Y position for ruler line on miniCanvas
        #rulerLineElement; // DOM element for the ruler line visualization
        #rulerAngleInput;
        #rulerXInput;
        #rulerYInput;
        #rulerHorizontalToggle;
        #rulerVerticalToggle;
        #rulerV2Toggle;

        #defaultSettings = {
            imageSize: 1,
            brushSize: 32,
            pixelSize: 1,
            offsetX: 10,
            offsetY: 0,
            drawingSpeed: 10,
            colorTolerance: 15,
            pantherTextSize: 4,
            pantherTextColor: "#000000"
        };

        constructor() {
            super("Auto Manager V3", '<i class="fas fa-pen"></i>');
            // Add styles inside the constructor, after super() is called,
            // to ensure QBit.Styles is properly initialized.
            QBit.Styles.addRules([
                `#${QBit.identifier} .autodraw-controls {
                    display: flex;
                    flex-direction: column;
                    gap: 10px;
                }`,
                `#${QBit.identifier} .autodraw-controls .section-title {
                    font-weight: bold;
                    color: var(--dark-blue-title);
                    text-align: center;
                    margin-bottom: 5px;
                    border-bottom: 1px solid var(--CE-color);
                    padding-bottom: 5px;
                }`,
                `#${QBit.identifier} .autodraw-controls .control-group {
                    border: 1px solid var(--CE-color);
                    border-radius: .25rem;
                    padding: 5px;
                    background-color: var(--CE-bg_color);
                }`,
                `#${QBit.identifier} .autodraw-controls .cheat-row {
                    display: flex;
                    width: 100%;
                    flex-wrap: wrap;
                    gap: 5px;
                }`,
                `#${QBit.identifier} .autodraw-controls .cheat-row > div,
                 #${QBit.identifier} .autodraw-controls .cheat-row > input,
                 #${QBit.identifier} .autodraw-controls .cheat-row > select,
                 #${QBit.identifier} .autodraw-controls .cheat-row > button,
                 #${QBit.identifier} .autodraw-controls .cheat-row > label {
                    flex: 1 1 auto;
                    min-width: 80px;
                }`,
                `#${QBit.identifier} .autodraw-controls input[type="number"],
                 #${QBit.identifier} .autodraw-controls input[type="text"],
                 #${QBit.identifier} .autodraw-controls input[type="file"],
                 #${QBit.identifier} .autodraw-controls select,
                 #${QBit.identifier} .autodraw-controls textarea {
                    width: 100%;
                    padding: 5px;
                    box-sizing: border-box;
                    border: 1px solid var(--CE-color);
                    border-radius: .25rem;
                    background-color: var(--CE-bg_color);
                    color: var(--CE-color);
                }`,
                `#${QBit.identifier} .autodraw-controls label {
                    font-size: 0.85em;
                    margin-bottom: 3px;
                    display: block;
                }`,
                `#${QBit.identifier} .autodraw-controls .btn {
                    padding: 5px;
                    box-sizing: border-box;
                    background-color: var(--secondary);
                }`,
                `#${QBit.identifier} .autodraw-controls .btn.active {
                    background-color: var(--success);
                }`,
                `#${QBit.identifier} .autodraw-controls .effect-toggle.active {
                    background-color: var(--info);
                    color: white;
                }`,
                `#${QBit.identifier} .autodraw-controls .gradient-strip {
                    width: 100%;
                    height: 20px;
                    border: 1px solid var(--CE-color);
                    cursor: pointer;
                    margin-top: 5px;
                }`,
                `#${QBit.identifier} .autodraw-controls .status-message {
                    margin-top: 5px;
                    font-size: 0.9em;
                    color: var(--info);
                }`,
                `#${QBit.identifier} .autodraw-controls .loading-spinner {
                    border: 4px solid rgba(0, 0, 0, 0.1);
                    border-left-color: var(--info);
                    border-radius: 50%;
                    width: 20px;
                    height: 20px;
                    animation: spin 1s linear infinite;
                    display: inline-block;
                    vertical-align: middle;
                    margin-left: 5px;
                }`,
                `@keyframes spin {
                    0% { transform: rotate(0deg); }
                    100% { transform: rotate(360deg); }
                }`,
                // Styles for miniCanvas and Ruler
                `#${QBit.identifier} .mini-canvas-container {
                    border: 1px solid var(--CE-color);
                    background: #fff;
                    position: relative;
                    width: 100%; /* Make it responsive within its container */
                    max-width: 200px; /* Max size for visual appeal */
                    height: 200px; /* Fixed height for consistent aspect ratio */
                    margin: 5px auto; /* Center it */
                    display: block;
                }`,
                `#${QBit.identifier} .ruler-container {
                    display: flex;
                    flex-wrap: wrap;
                    gap: 5px;
                    align-items: center;
                    margin-top: 5px;
                    padding-top: 5px;
                    border-top: 1px dashed rgba(0,0,0,0.1);
                }`,
                `#${QBit.identifier} .ruler-line {
                    position: absolute;
                    border: 1px dashed var(--dark); /* Dashed line for ruler */
                    pointer-events: none;
                    width: 200px; /* Length of the ruler line */
                    transform-origin: center center; /* Rotate around its center */
                    z-index: 10; /* Ensure it's above canvas content */
                }`,
                `#${QBit.identifier} .ruler-container input[type="number"] {
                    width: 60px; /* Smaller inputs for ruler controls */
                }`,
                `#${QBit.identifier} .ruler-container label {
                    white-space: nowrap; /* Prevent labels from wrapping */
                }`
            ]);
            this.#onStartup();
        }

        #onStartup() {
            this.#loadInterface();
            this.#setupCanvas();
            this.#setInitialSettings();
            this.#initGradientStrip();
            this.#setupMiniCanvasDrawing(); // Initialize miniCanvas drawing
            this.#setupRuler(); // Initialize ruler functionality
            requestAnimationFrame(this.#updateRainbowColor.bind(this));
        }

        #loadInterface() {
            const container = domMake.Tree("div", { class: "autodraw-controls" });

            // --- Section: Carga de Imagen y Configuración de Dibujo ---
            const imageSettingsGroup = domMake.Tree("div", { class: "control-group" });
            imageSettingsGroup.appendChild(domMake.Tree("div", { class: "section-title" }, ["Imagen y Configuración de Dibujo"]));

            const imageLoadRow = domMake.Row({ class: "cheat-row" });
            this.#imageFileInput = domMake.Tree("input", { type: "file", id: "autodraw-image-input", title: "Cargar Imagen (PNG/JPG)" });
            imageLoadRow.appendChild(this.#imageFileInput);
            imageSettingsGroup.appendChild(imageLoadRow);

            const settingsRow1 = domMake.Row({ class: "cheat-row" });
            this.#imageSizeInput = domMake.Tree("input", { type: "number", min: "1", max: "20", value: this.#defaultSettings.imageSize, title: "Tamaño de Imagen (1=grande, 20=pequeña)" });
            this.#brushSizeInput = domMake.Tree("input", { type: "number", min: "1", max: "100", value: this.#defaultSettings.brushSize, title: "Tamaño del Pincel" });
            this.#pixelSizeInput = domMake.Tree("input", { type: "number", min: "1", max: "50", value: this.#defaultSettings.pixelSize, title: "Espacio entre Píxeles" });
            settingsRow1.appendAll(
                domMake.Tree("div", {}, [domMake.Tree("label", {}, ["Tamaño Img:"]), this.#imageSizeInput]),
                domMake.Tree("div", {}, [domMake.Tree("label", {}, ["Espacio Px:"]), this.#pixelSizeInput]),
                domMake.Tree("div", {}, [domMake.Tree("label", {}, ["Tamaño Pincel:"]), this.#brushSizeInput])
            );
            imageSettingsGroup.appendChild(settingsRow1);

            const settingsRow2 = domMake.Row({ class: "cheat-row" });
            this.#offsetXInput = domMake.Tree("input", { type: "number", min: "-50", max: "150", value: this.#defaultSettings.offsetX, title: "Desplazamiento X (0-100)" });
            this.#offsetYInput = domMake.Tree("input", { type: "number", min: "-50", max: "150", value: this.#defaultSettings.offsetY, title: "Desplazamiento Y (0-100)" });
            this.#drawingSpeedInput = domMake.Tree("input", { type: "number", min: "1", max: "100", value: this.#defaultSettings.drawingSpeed, title: "Velocidad de Dibujo (ms/línea)" });
            this.#colorToleranceInput = domMake.Tree("input", { type: "number", min: "0", max: "255", value: this.#defaultSettings.colorTolerance, title: "Tolerancia de Color para Agrupación de Líneas" });
            settingsRow2.appendAll(
                domMake.Tree("div", {}, [domMake.Tree("label", {}, ["Offset X:"]), this.#offsetXInput]),
                domMake.Tree("div", {}, [domMake.Tree("label", {}, ["Offset Y:"]), this.#offsetYInput]),
                domMake.Tree("div", {}, [domMake.Tree("label", {}, ["Vel. Dibujo (ms):"]), this.#drawingSpeedInput]),
                domMake.Tree("div", {}, [domMake.Tree("label", {}, ["Tol. Color (0-255):"]), this.#colorToleranceInput])
            );
            imageSettingsGroup.appendChild(settingsRow2);
            container.appendChild(imageSettingsGroup);

            // --- Section: Modos de Dibujo de Imagen ---
            const modesGroup = domMake.Tree("div", { class: "control-group" });
            modesGroup.appendChild(domMake.Tree("div", { class: "section-title" }, ["Modos de Dibujo de Imagen"]));
            const modesRow = domMake.Row({ class: "cheat-row" });

            const addModeButton = (label, modeKey, iconClass) => {
                const button = domMake.Button(`<i class="${iconClass}"></i> ${label}`);
                button.classList.add("effect-toggle");
                button.addEventListener("click", () => this.#setDrawingMode(modeKey));
                this.#modeButtons[modeKey] = button;
                modesRow.appendChild(button);
            };

            addModeButton("Modo Lluvia", "rainMode", "fas fa-cloud-rain");
            addModeButton("Onda", "waveDraw", "fas fa-wave-square");
            addModeButton("Espiral", "spiralDraw", "fas fa-circle-notch");
            addModeButton("Píxel Aleatorio", "randomPixelDraw", "fas fa-dice");
            addModeButton("Modo Formas", "shapeDrawMode", "fas fa-bezier-curve");
            modesGroup.appendChild(modesRow);
            container.appendChild(modesGroup);


            // --- Section: Dibujo de Texto (Panther Mode) ---
            const textDrawingGroup = domMake.Tree("div", { class: "control-group" });
            textDrawingGroup.appendChild(domMake.Tree("div", { class: "section-title" }, ["Dibujo de Texto"]));

            const textInputRow = domMake.Row({ class: "cheat-row" });
            this.#pantherTextInput = domMake.Tree("input", { type: "text", placeholder: "Texto para dibujar" });
            textInputRow.appendChild(this.#pantherTextInput);
            textDrawingGroup.appendChild(textInputRow);

            const textSettingsRow = domMake.Row({ class: "cheat-row" });
            this.#pantherTextSizeInput = domMake.Tree("input", { type: "number", min: "1", max: "10", value: this.#defaultSettings.pantherTextSize, title: "Tamaño del texto (1=pequeño, 10=grande)" });
            this.#pantherTextColorInput = domMake.Tree("input", { type: "color", value: this.#defaultSettings.pantherTextColor, title: "Color del texto" });
            textSettingsRow.appendAll(
                domMake.Tree("div", {}, [domMake.Tree("label", {}, ["Tamaño Texto:"]), this.#pantherTextSizeInput]),
                domMake.Tree("div", {}, [domMake.Tree("label", {}, ["Color Texto:"]), this.#pantherTextColorInput])
            );
            textDrawingGroup.appendChild(textSettingsRow);

            const drawTextButtonRow = domMake.Row({ class: "cheat-row" });
            const drawTextButton = domMake.Button('<i class="fas fa-font"></i> Dibujar Texto');
            drawTextButton.addEventListener("click", () => this.#startTextDrawing());
            drawTextButtonRow.appendChild(drawTextButton);
            textDrawingGroup.appendChild(drawTextButtonRow);
            container.appendChild(textDrawingGroup);

            // --- Section: Tableta de Dibujo y Regla ---
            const drawingTabletGroup = domMake.Tree("div", { class: "control-group" });
            drawingTabletGroup.appendChild(domMake.Tree("div", { class: "section-title" }, ["Tableta de Dibujo y Regla"]));

            this.#miniCanvas = domMake.Tree("canvas", { id: "miniCanvas", width: "200", height: "200", class: "mini-canvas-container" });
            this.#miniCtx = this.#miniCanvas.getContext('2d');
            drawingTabletGroup.appendChild(this.#miniCanvas);

            // Ruler Controls
            const rulerContainer = domMake.Tree("div", { class: "ruler-container" });
            rulerContainer.appendAll(
                domMake.Tree("label", {}, [
                    domMake.Tree("input", { type: "checkbox", id: "rulerModeToggle" }),
                    " Regla"
                ]),
                domMake.Tree("label", {}, [
                    domMake.Tree("input", { type: "checkbox", id: "rulerHorizontalToggle" }),
                    " Horizontal"
                ]),
                domMake.Tree("label", {}, [
                    domMake.Tree("input", { type: "checkbox", id: "rulerVerticalToggle" }),
                    " Vertical"
                ]),
                domMake.Tree("label", {}, [
                    domMake.Tree("input", { type: "checkbox", id: "rulerV2Toggle" }),
                    " Regla V2"
                ]),
                domMake.Tree("div", {}, [
                    domMake.Tree("label", {}, ["Ángulo (º):"]),
                    this.#rulerAngleInput = domMake.Tree("input", { type: "number", min: "0", max: "360", value: "0", title: "Ángulo de la regla" })
                ]),
                domMake.Tree("div", {}, [
                    domMake.Tree("label", {}, ["Pos X (px):"]),
                    this.#rulerXInput = domMake.Tree("input", { type: "number", min: "0", max: "200", value: "100", title: "Posición X de la regla" })
                ]),
                domMake.Tree("div", {}, [
                    domMake.Tree("label", {}, ["Pos Y (px):"]),
                    this.#rulerYInput = domMake.Tree("input", { type: "number", min: "0", max: "200", value: "100", title: "Posición Y de la regla" })
                ])
            );
            drawingTabletGroup.appendChild(rulerContainer);
            container.appendChild(drawingTabletGroup);

            // --- Section: Efectos de Color (Rainbow) ---
            const colorEffectsGroup = domMake.Tree("div", { class: "control-group" });
            colorEffectsGroup.appendChild(domMake.Tree("div", { class: "section-title" }, ["Efectos de Color"]));

            const rainbowControlRow = domMake.Row({ class: "cheat-row" });
            const rainbowModeToggle = domMake.Tree("label", {}, [
                domMake.Tree("input", { type: "checkbox", id: "rainbowModeToggle" }),
                " Modo Arcoíris"
            ]);
            this.#rainbowSpeedInput = domMake.Tree("input", { type: "number", min: "0", max: "500", value: "50", title: "Velocidad de cambio de color (ms)" });
            this.#rainbowDirectionSelect = domMake.Tree("select", { title: "Dirección del cambio de color" });
            ["forward", "reverse"].forEach(dir => {
                this.#rainbowDirectionSelect.appendChild(domMake.Tree("option", { value: dir }, [dir === "forward" ? "Hacia adelante" : "Hacia atrás"]));
            });
            rainbowControlRow.appendAll(
                rainbowModeToggle,
                domMake.Tree("div", {}, [domMake.Tree("label", {}, ["Velocidad:"]), this.#rainbowSpeedInput]),
                domMake.Tree("div", {}, [domMake.Tree("label", {}, ["Dirección:"]), this.#rainbowDirectionSelect])
            );
            colorEffectsGroup.appendChild(rainbowControlRow);

            const gradientStripCanvas = domMake.Tree("canvas", { id: "gradientStrip", width: "200", height: "20", style: "width:100%;" });
            colorEffectsGroup.appendChild(gradientStripCanvas);
            container.appendChild(colorEffectsGroup);

            // --- Control Buttons (Start/Stop Drawing Task) ---
            const controlButtonsRow = domMake.Row({ class: "cheat-row" });
            const startButton = domMake.Button('<i class="fas fa-play-circle"></i> Iniciar Tarea de Dibujo');
            startButton.addEventListener("click", () => this.#startDrawing());
            const stopButton = domMake.Button('<i class="fas fa-stop-circle"></i> Detener Tarea de Dibujo');
            stopButton.addEventListener("click", () => this.#stopDrawing());
            controlButtonsRow.appendAll(startButton, stopButton);
            container.appendChild(controlButtonsRow);

            // Status Message
            this.#statusMessageElement = domMake.Tree("div", { class: "status-message" }, ["Estado: Listo."]);
            container.appendChild(this.#statusMessageElement);


            this.htmlElements.section.appendChild(container);

            // Event Listeners for new elements
            rainbowModeToggle.querySelector('input').addEventListener('change', (e) => this.#toggleRainbowMode(e.target.checked));
            gradientStripCanvas.addEventListener('click', (e) => this.#handleGradientStripClick(e));
            this.#rainbowSpeedInput.addEventListener('input', (e) => this.#rainbowSpeed = parseInt(e.target.value));
            this.#rainbowDirectionSelect.addEventListener('change', (e) => this.#rainbowDirection = e.target.value);

            this.#imageFileInput.addEventListener("change", (e) => this.#readImage(e.target));
        }

        #setInitialSettings() {
            this.#imageSizeInput.value = this.#defaultSettings.imageSize;
            this.#brushSizeInput.value = this.#defaultSettings.brushSize;
            this.#pixelSizeInput.value = this.#defaultSettings.pixelSize;
            this.#offsetXInput.value = this.#defaultSettings.offsetX;
            this.#offsetYInput.value = this.#defaultSettings.offsetY;
            this.#drawingSpeedInput.value = this.#defaultSettings.drawingSpeed;
            this.#colorToleranceInput.value = this.#defaultSettings.colorTolerance;

            this.#pantherTextSizeInput.value = this.#defaultSettings.pantherTextSize;
            this.#pantherTextColorInput.value = this.#defaultSettings.pantherTextColor;

            this.#rainbowSpeedInput.value = this.#rainbowSpeed;
            this.#rainbowDirectionSelect.value = this.#rainbowDirection;

            // Set initial ruler values
            this.#rulerAngleInput.value = this.#rulerAngle;
            this.#rulerXInput.value = this.#rulerX;
            this.#rulerYInput.value = this.#rulerY;

            this.#setDrawingMode('rainMode');
        }

        #setupCanvas() {
            this.#previewCanvas = document.createElement('canvas');
            this.#originalCanvas = document.getElementById('canvas');

            if (this.#originalCanvas) {
                this.#previewCanvas.width = this.#originalCanvas.width;
                this.#previewCanvas.height = this.#originalCanvas.height;
            } else {
                this.#previewCanvas.width = 1000;
                this.#previewCanvas.height = 1000;
                this.notify("warning", "Canvas del juego no encontrado. Usando dimensiones por defecto para el lienzo interno.");
            }
        }

        #readImage(fileInput) {
            if (!fileInput.files || !fileInput.files[0]) {
                this.#imageData = null;
                this.#updateStatus("No se seleccionó ninguna imagen.");
                return;
            }

            this.#updateStatus('Cargando imagen... <span class="loading-spinner"></span>');
            this.#imageData = null;

            const FR = new FileReader();
            FR.addEventListener('load', (evt) => {
                const img = new Image();
                img.addEventListener('load', async () => {
                    this.notify("info", "Imagen cargada. Procesando pixeles...");
                    const ctx = this.#previewCanvas.getContext('2d');
                    ctx.clearRect(0, 0, this.#previewCanvas.width, this.#previewCanvas.height);

                    let imgWidth = img.width;
                    let imgHeight = img.height;
                    const canvasAspectRatio = this.#previewCanvas.width / this.#previewCanvas.height;
                    const imgAspectRatio = imgWidth / imgHeight;

                    if (imgWidth > this.#previewCanvas.width || imgHeight > this.#previewCanvas.height) {
                        if (imgAspectRatio > canvasAspectRatio) {
                            imgHeight = imgHeight * (this.#previewCanvas.width / imgWidth);
                            imgWidth = this.#previewCanvas.width;
                        } else {
                            imgWidth = imgWidth * (this.#previewCanvas.height / imgHeight);
                            imgHeight = this.#previewCanvas.height;
                        }
                    }

                    ctx.drawImage(img, 0, 0, imgWidth, imgHeight);

                    this.#imageData = ctx.getImageData(0, 0, imgWidth, imgHeight);
                    this.notify("success", "Imagen lista para dibujar.");

                    // Prepare commands based on the selected mode
                    await this.#prepareDrawingCommands();
                    this.#updateStatus(`Imagen lista: ${this.#executionLine.length} líneas.`);

                });
                img.crossOrigin = 'Anonymous';
                img.src = evt.target.result;
            });
            FR.readAsDataURL(fileInput.files[0]);
        }

        // Helper to convert RGBA array to RGBA string for CSS/Canvas
        #rgbaArrayToString(rgbaArray) {
            return `rgba(${rgbaArray[0]},${rgbaArray[1]},${rgbaArray[2]},${rgbaArray[3] / 255})`;
        }

        // Helper to check if two RGBA colors are "similar enough"
        #areColorsSimilar(color1, color2, threshold = 15) {
            if (!color1 || !color2) return false;
            return (
                Math.abs(color1[0] - color2[0]) <= threshold &&
                Math.abs(color1[1] - color2[1]) <= threshold &&
                Math.abs(color1[2] - color2[2]) <= threshold &&
                Math.abs(color1[3] - color2[3]) <= threshold
            );
        }

        // Helper to convert pixel coords to game coords (0-100)
        #toGameCoords(x_pixel, y_pixel, originalImgWidth, originalImgHeight) {
            const size = parseInt(this.#imageSizeInput.value);
            const offsetX = parseFloat(this.#offsetXInput.value);
            const offsetY = parseFloat(this.#offsetYInput.value);

            const finalDrawWidth = 100 / size;
            const finalDrawHeight = 100 / size;

            const gameX = (x_pixel / originalImgWidth) * finalDrawWidth + offsetX;
            const gameY = (y_pixel / originalImgHeight) * finalDrawHeight + offsetY;
            return [gameX, gameY];
        }

        // Helper to get pixel color from imageData
        #getPixelColor(x, y) {
            const width = this.#imageData.width;
            const height = this.#imageData.height;
            const pixels = this.#imageData.data;

            const originalPxX = Math.floor(x);
            const originalPxY = Math.floor(y);

            if (originalPxX < 0 || originalPxX >= width || originalPxY < 0 || originalPxY >= height) return null;
            const index = (originalPxY * width + originalPxX) * 4;
            const a = pixels[index + 3];
            if (a < 20) return null; // Treat mostly transparent pixels as no color
            const r = pixels[index + 0];
            const g = pixels[index + 1];
            const b = pixels[index + 2];
            return [r, g, b, a]; // Return as RGBA array
        }

        async #prepareDrawingCommands() {
            if (!this.#imageData) {
                this.notify("warning", "Carga una imagen primero.");
                return false;
            }

            this.#executionLine = []; // Clear previous commands
            this.#rainColumns = []; // Clear for rain mode

            const { width, height } = this.#imageData;
            const pixelDensity = parseInt(this.#pixelSizeInput.value) || 1;
            const thickness = parseInt(this.#brushSizeInput.value) || 5;
            const colorTolerance = parseInt(this.#colorToleranceInput.value) || 15;

            this.notify("info", `Preparando comandos para modo: '${this.#currentDrawingMode}'...`);

            switch (this.#currentDrawingMode) {
                case 'rainMode':
                    for (let x = 0; x < width; x += pixelDensity) {
                        let columnPixels = [];
                        for (let y = 0; y < height; y += pixelDensity) {
                            const color = this.#getPixelColor(x, y);
                            if (color) {
                                columnPixels.push({ x, y, color });
                            }
                        }
                        if (columnPixels.length > 0) {
                            this.#rainColumns.push({ x, pixels: columnPixels });
                        }
                    }
                    this.#shuffleArray(this.#rainColumns);

                    for (let col of this.#rainColumns) {
                        let pixelsInColumn = col.pixels;
                        if (pixelsInColumn.length === 0) continue;

                        pixelsInColumn.sort((a, b) => a.y - b.y); // Sort vertically

                        let startPixel = pixelsInColumn[0];
                        let prevPixel = pixelsInColumn[0];

                        for (let i = 1; i < pixelsInColumn.length; i++) {
                            let currentPixel = pixelsInColumn[i];
                            // Check for discontinuity or significant color change
                            if (currentPixel.y !== prevPixel.y + pixelDensity || !this.#areColorsSimilar(startPixel.color, currentPixel.color, colorTolerance)) {
                                this.#executionLine.push({
                                    pos1: this.#toGameCoords(startPixel.x, startPixel.y, width, height),
                                    pos2: this.#toGameCoords(prevPixel.x, prevPixel.y, width, height),
                                    color: this.#rgbaArrayToString(startPixel.color),
                                    thickness
                                });
                                startPixel = currentPixel;
                            }
                            prevPixel = currentPixel;
                        }
                        // Add the last segment of the column
                        this.#executionLine.push({
                            pos1: this.#toGameCoords(startPixel.x, startPixel.y, width, height),
                            pos2: this.#toGameCoords(prevPixel.x, prevPixel.y, width, height),
                            color: this.#rgbaArrayToString(startPixel.color),
                            thickness
                        });
                    }
                    break;

                case 'waveDraw':
                    const waveAmplitude = 15; // Max vertical displacement
                    const waveFrequency = 0.05; // How many waves across the image

                    for (let y = 0; y < height; y += pixelDensity) {
                        let startPoint = null;
                        let lastColor = null;

                        for (let x = 0; x < width; x += pixelDensity) {
                            const currentXForWave = x;
                            const currentYForWave = y + waveAmplitude * Math.sin(x * waveFrequency); // Apply sine wave

                            const actualColor = this.#getPixelColor(currentXForWave, currentYForWave);

                            if (actualColor) {
                                if (!startPoint) {
                                    startPoint = { x: currentXForWave, y: currentYForWave, color: actualColor };
                                    lastColor = actualColor;
                                } else if (!this.#areColorsSimilar(actualColor, lastColor, colorTolerance)) {
                                    this.#executionLine.push({
                                        pos1: this.#toGameCoords(startPoint.x, startPoint.y, width, height),
                                        pos2: this.#toGameCoords(currentXForWave - pixelDensity, currentYForWave, width, height), // End before color change
                                        color: this.#rgbaArrayToString(lastColor),
                                        thickness
                                    });
                                    startPoint = { x: currentXForWave, y: currentYForWave, color: actualColor };
                                    lastColor = actualColor;
                                }
                            } else if (startPoint) {
                                // Transparency encountered, end current segment
                                this.#executionLine.push({
                                    pos1: this.#toGameCoords(startPoint.x, startPoint.y, width, height),
                                    pos2: this.#toGameCoords(currentXForWave - pixelDensity, currentYForWave, width, height),
                                    color: this.#rgbaArrayToString(lastColor),
                                    thickness
                                });
                                startPoint = null;
                                lastColor = null;
                            }
                        }
                        // End any remaining segment at the end of the row
                        if (startPoint) {
                            this.#executionLine.push({
                                pos1: this.#toGameCoords(startPoint.x, startPoint.y, width, height),
                                pos2: this.#toGameCoords(width - pixelDensity, y + waveAmplitude * Math.sin((width - pixelDensity) * waveFrequency), width, height),
                                color: this.#rgbaArrayToString(lastColor),
                                thickness
                            });
                        }
                    }
                    break;

                case 'spiralDraw':
                    const centerX = width / 2;
                    const centerY = height / 2;
                    const maxRadius = Math.min(width, height) / 2;
                    const density = 0.5; // Controls how tight the spiral is

                    for (let r = 0; r < maxRadius; r += pixelDensity * density) {
                        const numPoints = Math.floor(2 * Math.PI * r / pixelDensity); // Number of points on this spiral turn
                        if (numPoints === 0) continue;

                        let prevX = -1, prevY = -1;
                        let startPoint = null;
                        let lastColor = null;

                        for (let i = 0; i < numPoints; i++) {
                            const angle = (i / numPoints) * 2 * Math.PI + this.#spiralAngle; // Add offset angle for continuous spiral effect
                            const spiralX = centerX + r * Math.cos(angle);
                            const spiralY = centerY + r * Math.sin(angle);

                            const color = this.#getPixelColor(spiralX, spiralY);

                            if (color) {
                                if (startPoint === null) {
                                    startPoint = { x: spiralX, y: spiralY, color: color };
                                    lastColor = color;
                                } else if (!this.#areColorsSimilar(color, lastColor, colorTolerance)) {
                                    this.#executionLine.push({
                                        pos1: this.#toGameCoords(startPoint.x, startPoint.y, width, height),
                                        pos2: this.#toGameCoords(prevX, prevY, width, height),
                                        color: this.#rgbaArrayToString(lastColor),
                                        thickness
                                    });
                                    startPoint = { x: spiralX, y: spiralY, color: color };
                                    lastColor = color;
                                }
                                prevX = spiralX;
                                prevY = spiralY;
                            } else if (startPoint !== null) {
                                this.#executionLine.push({
                                    pos1: this.#toGameCoords(startPoint.x, startPoint.y, width, height),
                                    pos2: this.#toGameCoords(prevX, prevY, width, height),
                                    color: this.#rgbaArrayToString(lastColor),
                                    thickness
                                });
                                startPoint = null;
                            }
                        }
                        if (startPoint) { // Add the last segment of the current spiral turn
                             this.#executionLine.push({
                                pos1: this.#toGameCoords(startPoint.x, startPoint.y, width, height),
                                pos2: this.#toGameCoords(prevX, prevY, width, height),
                                color: this.#rgbaArrayToString(lastColor),
                                thickness
                            });
                        }
                    }
                    this.#spiralAngle += 0.1; // Increment for a "rotating" spiral effect on subsequent runs
                    break;

                case 'randomPixelDraw':
                    let allPixels = [];
                    for (let y = 0; y < height; y += pixelDensity) {
                        for (let x = 0; x < width; x += pixelDensity) {
                            const color = this.#getPixelColor(x, y);
                            if (color) {
                                allPixels.push({ x, y, color });
                            }
                        }
                    }
                    this.#shuffleArray(allPixels);

                    allPixels.forEach(p => {
                        this.#executionLine.push({
                            pos1: this.#toGameCoords(p.x, p.y, width, height),
                            pos2: this.#toGameCoords(p.x + pixelDensity / 2, p.y + pixelDensity / 2, width, height), // Draw a small dot
                            color: this.#rgbaArrayToString(p.color),
                            thickness
                        });
                    });
                    break;

                case 'shapeDrawMode':
                    const shapeDetectorClass = this.findGlobal("ShapeDetector");
                    if (!shapeDetectorClass || !shapeDetectorClass.siblings || shapeDetectorClass.siblings.length === 0) {
                        this.notify("error", "El módulo 'Detector de Formas' no está activo. Asegúrate de que está cargado y enlazado.");
                        return false;
                    }
                    const shapeDetectorInstance = shapeDetectorClass.siblings[0];

                    if (!shapeDetectorInstance || typeof shapeDetectorInstance.analyzeImageDataForShapes !== 'function') {
                        this.notify("error", "El módulo 'Detector de Formas' no está listo o le falta el método 'analyzeImageDataForShapes'.");
                        return false;
                    }

                    this.notify("info", "Analizando imagen para formas con Detector de Formas...");
                    // Assuming analyzeImageDataForShapes returns commands in game coords (0-100) or similar
                    const { drawingCommands: shapeCommands } = await shapeDetectorInstance.analyzeImageDataForShapes(
                        this.#imageData,
                        this.#imageData.width,
                        this.#imageData.height,
                        false // Pass false if you don't want it to draw directly
                    );

                    if (shapeCommands.length === 0) {
                        this.notify("warning", "No se detectaron formas significativas en la imagen para dibujar.");
                        return false;
                    }

                    shapeCommands.forEach(cmd => {
                        this.#executionLine.push({
                            pos1: [cmd.x1, cmd.y1],
                            pos2: [cmd.x2, cmd.y2],
                            color: cmd.color || this.#pantherTextColorInput.value, // Use detected color or default
                            thickness: thickness
                        });
                    });
                    this.notify("success", `Modo Formas: Se prepararon ${this.#executionLine.length} líneas desde formas detectadas.`);
                    break;

                default:
                    this.notify("warning", `Modo de dibujo desconocido: '${this.#currentDrawingMode}'. Usando un modo de línea por defecto.`);
                    // Fallback to simple horizontal line scan if mode is unknown
                    await this.#generateHorizontalLineCommands();
                    break;
            }

            this.notify("info", `Comandos de dibujo preparados para el modo '${this.#currentDrawingMode}': ${this.#executionLine.length} líneas.`);
            return true;
        }

        // Generic horizontal line scanning mode (can be used as default or specific mode)
        async #generateHorizontalLineCommands() {
            if (!this.#imageData) {
                this.notify("warning", "No hay datos de imagen para generar comandos (horizontal).");
                return;
            }

            const pixels = this.#imageData.data;
            const width = this.#imageData.width;
            const height = this.#imageData.height;

            const brushSize = parseInt(this.#brushSizeInput.value) || 2;
            const offsetX = parseFloat(this.#offsetXInput.value) || 0;
            const offsetY = parseFloat(this.#offsetYInput.value) || 0;
            const pixelDensity = parseInt(this.#pixelSizeInput.value) || 1;
            const colorTolerance = parseInt(this.#colorToleranceInput.value) || 15;

            const imageRenderSize = 100 / parseInt(this.#imageSizeInput.value);
            const scaleX = imageRenderSize / width;
            const scaleY = imageRenderSize / height;

            for (let y = 0; y < height; y += pixelDensity) {
                let currentLineColor = null;
                let lineStartX = -1;

                for (let x = 0; x < width; x += pixelDensity) {
                    const index = (y * width + x) * 4;
                    const r = pixels[index];
                    const g = pixels[index + 1];
                    const b = pixels[index + 2];
                    const a = pixels[index + 3];

                    const currentColor = [r, g, b, a];

                    if (a > 20) {
                        if (currentLineColor === null) {
                            currentLineColor = currentColor;
                            lineStartX = x;
                        } else if (!this.#areColorsSimilar(currentLineColor, currentColor, colorTolerance)) {
                            const gameX1 = lineStartX * scaleX + offsetX;
                            const gameY1 = y * scaleY + offsetY;
                            const gameX2 = (x - pixelDensity) * scaleX + offsetX;

                            this.#executionLine.push({
                                x1: gameX1,
                                y1: gameY1,
                                x2: gameX2 + (brushSize * scaleX * 0.5),
                                y2: gameY1,
                                color: this.#rgbaArrayToString(currentLineColor),
                                thickness: brushSize
                            });

                            currentLineColor = currentColor;
                            lineStartX = x;
                        }
                    } else {
                        if (currentLineColor !== null) {
                            const gameX1 = lineStartX * scaleX + offsetX;
                            const gameY1 = y * scaleY + offsetY;
                            const gameX2 = (x - pixelDensity) * scaleX + offsetX;
                            this.#executionLine.push({
                                x1: gameX1,
                                y1: gameY1,
                                x2: gameX2 + (brushSize * scaleX * 0.5),
                                y2: gameY1,
                                color: this.#rgbaArrayToString(currentLineColor),
                                thickness: brushSize
                            });
                            currentLineColor = null;
                            lineStartX = -1;
                        }
                    }
                }
                if (currentLineColor !== null && lineStartX !== -1) {
                    const gameX1 = lineStartX * scaleX + offsetX;
                    const gameY1 = y * scaleY + offsetY;
                    const gameX2 = (width - pixelDensity) * scaleX + offsetX;
                    this.#executionLine.push({
                        x1: gameX1,
                        y1: gameY1,
                        x2: gameX2 + (brushSize * scaleX * 0.5),
                        y2: gameY1,
                        color: this.#rgbaArrayToString(currentLineColor),
                        thickness: brushSize
                    });
                }
            }
        }


        #shuffleArray(array) {
            for (let i = array.length - 1; i > 0; i--) {
                const j = Math.floor(Math.random() * (i + 1));
                [array[i], array[j]] = [array[j], array[i]];
            }
        }

        async #startDrawing() { // This method now initiates the drawing task, could be image or text
            if (this.#imageData && this.#executionLine.length === 0) { // If image is loaded but commands not generated
                 await this.#prepareDrawingCommands(); // Ensure commands are generated for the current image
            }

            if (this.#executionLine.length === 0) {
                this.notify("warning", "No hay comandos de dibujo preparados. Carga una imagen o texto primero.");
                return;
            }

            const botManager = this.findGlobal("BotClientManager")?.siblings[0];
            if (!botManager || botManager.children.length === 0) {
                this.notify("warning", "Necesitas al menos 1 bot activo en 'BotClientManager' para dibujar.");
                return;
            }

            const activeBots = botManager.children.filter(b => b.bot && b.bot.getReadyState());
            if (activeBots.length === 0) {
                this.notify("warning", "Ningún bot está conectado y listo para dibujar. Conecta un bot primero.");
                return;
            }

            this.#drawingActive = true;
            this.#currentDrawingIndex = 0;
            this.notify("info", `Iniciando tarea de dibujo con ${activeBots.length} bots...`);
            this.#updateStatus(`Dibujando... 0/${this.#executionLine.length} líneas.`);

            const delayMs = parseInt(this.#drawingSpeedInput.value);
            let currentLineIndex = 0;

            while (this.#drawingActive && currentLineIndex < this.#executionLine.length) {
                const line = this.#executionLine[currentLineIndex];
                const botIndex = currentLineIndex % activeBots.length;
                const botInterface = activeBots[botIndex];

                if (botInterface && botInterface.bot && botInterface.bot.getReadyState()) {
                    let drawColor = line.color;
                    if (this.#rainbowMode) {
                        drawColor = `hsl(${this.#currentHue}, 100%, 50%)`;
                    }

                    botInterface.bot.emit(
                        "line",
                        -1, // playerid
                        line.pos1[0], line.pos1[1], // Use line.pos1/pos2 from prepared commands
                        line.pos2[0], line.pos2[1],
                        true, // isactive
                        line.thickness,
                        drawColor,
                        false // ispixel
                    );
                    currentLineIndex++;
                } else {
                    this.notify("warning", `Bot ${botInterface ? botInterface.getName() : 'desconocido'} no está listo. Saltando...`);
                    currentLineIndex++;
                }

                this.#updateStatus(`Dibujando... ${currentLineIndex}/${this.#executionLine.length} líneas.`);
                await new Promise(resolve => setTimeout(resolve, delayMs));
            }

            if (!this.#drawingActive) {
                this.notify("info", `Tarea de dibujo detenida manualmente. Completado ${currentLineIndex} de ${this.#executionLine.length} líneas.`);
            } else {
                this.notify("success", "Tarea de dibujo completada!");
            }
            this.#drawingActive = false;
            this.#updateStatus("Estado: Listo.");
        }

        #stopDrawing() {
            this.#drawingActive = false;
            this.notify("info", "Tarea de dibujo detenida.");
            this.#updateStatus("Estado: Detenido.");
        }

        #setDrawingMode(mode) {
            // Remove 'active' class from all mode buttons
            for (const key in this.#modeButtons) {
                if (this.#modeButtons.hasOwnProperty(key)) {
                    this.#modeButtons[key].classList.remove("active");
                }
            }
            // Add 'active' class to the clicked button
            if (this.#modeButtons[mode]) {
                this.#modeButtons[mode].classList.add("active");
            }

            this.#currentDrawingMode = mode;
            this.notify("info", `Modo de dibujo de imagen cambiado a: '${mode}'`);

            if (this.#imageData && this.#imageData.data && this.#imageData.data.length > 0) {
                this.#prepareDrawingCommands(); // Regenerate commands for the new mode
            }
        }

        async #startTextDrawing() {
            const text = this.#pantherTextInput.value.toUpperCase();
            if (!text) {
                this.notify("warning", "Introduce texto para dibujar.");
                return;
            }

            const botManager = this.findGlobal("BotClientManager")?.siblings[0];
            if (!botManager || botManager.children.length === 0) {
                this.notify("warning", "Necesitas al menos 1 bot activo en 'BotClientManager' para dibujar texto.");
                return;
            }

            const activeBots = botManager.children.filter(b => b.bot && b.bot.getReadyState());
            if (activeBots.length === 0) {
                this.notify("warning", "Ningún bot está conectado y listo para dibujar. Conecta un bot primero.");
                return;
            }

            this.notify("info", `Iniciando dibujo de texto: "${text}"`);
            this.#updateStatus(`Dibujando texto...`);

            this.#drawingActive = true;
            this.#executionLine = []; // Clear current image commands

            const charWidth = 5;
            const charHeight = 7;
            const pixelSize = parseInt(this.#pixelSizeInput.value) || 1;
            const textSize = parseInt(this.#pantherTextSizeInput.value) || 4;
            const textColor = this.#pantherTextColorInput.value || "#000000";
            const thickness = parseInt(this.#brushSizeInput.value) || 5;

            const scaledPixelSize = pixelSize * (textSize / 2);
            let currentXOffset = parseFloat(this.#offsetXInput.value) || 0;
            const startYOffset = parseFloat(this.#offsetYInput.value) || 0;
            const delayMs = parseInt(this.#drawingSpeedInput.value) || 10;

            for (let char of text) {
                if (!this.#drawingActive) break;
                if (!FONT_MAP[char]) {
                    this.notify("warning", `Carácter '${char}' no soportado, saltando.`);
                    currentXOffset += (charWidth + 1) * scaledPixelSize;
                    continue;
                }

                const bitmap = FONT_MAP[char];
                for (let y = 0; y < charHeight; y++) {
                    for (let x = 0; x < charWidth; x++) {
                        if (!this.#drawingActive) break;
                        if (bitmap[y][x] === 1) {
                            const gameX = currentXOffset + x * scaledPixelSize;
                            const gameY = startYOffset + y * scaledPixelSize;

                            const botIndex = (this.#executionLine.length) % activeBots.length;
                            const botInterface = activeBots[botIndex];

                            if (botInterface && botInterface.bot && botInterface.bot.getReadyState()) {
                                let drawColor = textColor;
                                if (this.#rainbowMode) {
                                    drawColor = `hsl(${this.#currentHue}, 100%, 50%)`;
                                }

                                botInterface.bot.emit("line", -1, gameX, gameY, gameX, gameY, true, thickness, drawColor, false);
                                this.#executionLine.push({}); // Dummy entry to count commands
                            }

                            await new Promise(resolve => setTimeout(resolve, delayMs));
                        }
                    }
                }
                currentXOffset += (charWidth + 1) * scaledPixelSize;
            }

            if (!this.#drawingActive) {
                this.notify("info", `Dibujo de texto detenido manualmente.`);
            } else {
                this.notify("success", "Dibujo de texto completado!");
            }
            this.#drawingActive = false;
            this.#updateStatus("Estado: Listo.");
        }

        #initGradientStrip() {
            const strip = this.htmlElements.section.querySelector('#gradientStrip');
            if (!strip) return;
            const ctx = strip.getContext('2d');
            strip.width = strip.offsetWidth;
            strip.height = strip.offsetHeight;

            for (let x = 0; x < strip.width; x++) {
                const hue = (x / strip.width) * 360;
                ctx.fillStyle = `hsl(${hue}, 100%, 50%)`;
                ctx.fillRect(x, 0, 1, strip.height);
            }
        }

        #handleGradientStripClick(e) {
            const strip = e.target;
            const rect = strip.getBoundingClientRect();
            const x = e.clientX - rect.left;
            this.#currentHue = (x / rect.width) * 360;
            this.#rainbowMode = false;
            this.htmlElements.section.querySelector('#rainbowModeToggle').checked = false;
            this.notify("info", `Color fijo seleccionado (Hue: ${Math.round(this.#currentHue)}).`);
        }

        #toggleRainbowMode(checked) {
            this.#rainbowMode = checked;
            if (this.#rainbowMode) {
                this.notify("info", "Modo Arcoíris activado.");
            } else {
                this.notify("info", "Modo Arcoíris desactivado.");
            }
        }

        #updateRainbowColor(timestamp) {
            if (!this.#rainbowMode) {
                requestAnimationFrame(this.#updateRainbowColor.bind(this));
                return;
            }
            if (timestamp - this.#lastRainbowUpdate < this.#rainbowSpeed) {
                requestAnimationFrame(this.#updateRainbowColor.bind(this));
                return;
            }
            this.#lastRainbowUpdate = timestamp;
            const step = this.#rainbowDirection === 'forward' ? 1 : -1;
            this.#currentHue = (this.#currentHue + step) % 360;
            if (this.#currentHue < 0) this.#currentHue += 360;
            requestAnimationFrame(this.#updateRainbowColor.bind(this));
        }

        #updateStatus(message) {
            this.#statusMessageElement.innerHTML = `Estado: ${message}`;
        }

        // --- MiniCanvas Drawing and Ruler Functionality (Adapted from original Manager V3) ---

        #setupMiniCanvasDrawing() {
            this.#miniCtx.lineCap = 'round';
            this.#miniCtx.lineJoin = 'round';

            this.#miniCanvas.addEventListener('mousedown', (e) => this.#handleMiniCanvasMouseDown(e));
            this.#miniCanvas.addEventListener('mousemove', (e) => this.#handleMiniCanvasMouseMove(e));
            this.#miniCanvas.addEventListener('mouseup', () => this.#handleMiniCanvasMouseUp());
            this.#miniCanvas.addEventListener('mouseout', () => this.#handleMiniCanvasMouseUp()); // Stop drawing if mouse leaves
        }

        #getMiniCanvasPos(e) {
            const rect = this.#miniCanvas.getBoundingClientRect();
            return {
                x: e.clientX - rect.left,
                y: e.clientY - rect.top
            };
        }

        #handleMiniCanvasMouseDown(e) {
            this.#isMiniDrawing = true;
            const pos = this.#getMiniCanvasPos(e);
            let x = pos.x;
            let y = pos.y;

            if (this.#rulerMode) {
                const effectiveAngle = this.#getRulerEffectiveAngle(x, y);
                const angleRad = (effectiveAngle * Math.PI) / 180;
                const dx = pos.x - this.#rulerX;
                const dy = pos.y - this.#rulerY;
                const proj = dx * Math.cos(angleRad) + dy * Math.sin(angleRad);
                x = this.#rulerX + proj * Math.cos(angleRad);
                y = this.#rulerY + proj * Math.sin(angleRad);
            }
            this.#miniLastX = x;
            this.#miniLastY = y;
            // For first point, draw a dot (line from itself to itself)
            this.#drawOnMiniCanvas(pos.x, pos.y, this.#miniLastX, this.#miniLastY, this.#brushSizeInput.value);
        }

        #handleMiniCanvasMouseMove(e) {
            if (!this.#isMiniDrawing) return;
            const pos = this.#getMiniCanvasPos(e);
            let x = pos.x;
            let y = pos.y;

            if (this.#rulerMode) {
                const effectiveAngle = this.#getRulerEffectiveAngle(x, y, this.#miniLastX, this.#miniLastY);
                const angleRad = (effectiveAngle * Math.PI) / 180;
                const dx = pos.x - this.#rulerX;
                const dy = pos.y - this.#rulerY;
                const proj = dx * Math.cos(angleRad) + dy * Math.sin(angleRad);
                x = this.#rulerX + proj * Math.cos(angleRad);
                y = this.#rulerY + proj * Math.sin(angleRad);
            }
            this.#drawOnMiniCanvas(x, y, this.#miniLastX, this.#miniLastY, this.#brushSizeInput.value);
            this.#miniLastX = x;
            this.#miniLastY = y;
        }

        #handleMiniCanvasMouseUp() {
            this.#isMiniDrawing = false;
        }

        #drawOnMiniCanvas(x, y, lastX, lastY, thickness) {
            const bot = this.#getBot();
            if (!bot || !bot.getReadyState()) {
                this.notify("warning", "No hay bot activo para dibujar en el juego.");
                return;
            }

            const miniCanvasWidth = this.#miniCanvas.width;
            const miniCanvasHeight = this.#miniCanvas.height;

            // Convert miniCanvas coordinates (0-miniCanvas.width/height) to game coordinates (0-100)
            const gameX = (x / miniCanvasWidth) * 100;
            const gameY = (y / miniCanvasHeight) * 100;
            const gameLastX = (lastX / miniCanvasWidth) * 100;
            const gameLastY = (lastY / miniCanvasHeight) * 100;

            let drawColor = `hsl(${this.#currentHue}, 100%, 50%)`;

            bot.emit("line", -1, gameLastX, gameLastY, gameX, gameY, true, thickness, drawColor, false);

            // Also draw on the miniCanvas itself for visual feedback
            this.#miniCtx.beginPath();
            this.#miniCtx.moveTo(lastX, lastY);
            this.#miniCtx.lineTo(x, y);
            this.#miniCtx.strokeStyle = drawColor;
            this.#miniCtx.lineWidth = thickness / 2; // Scale down thickness for miniCanvas
            this.#miniCtx.stroke();
        }

        // --- Ruler Logic ---
        #setupRuler() {
            this.#rulerLineElement = domMake.Tree('div', { class: 'ruler-line' });
            this.#miniCanvas.parentNode.insertBefore(this.#rulerLineElement, this.#miniCanvas.nextSibling);

            // Event listeners for ruler controls
            this.htmlElements.section.querySelector('#rulerModeToggle').addEventListener('change', (e) => {
                this.#rulerMode = e.target.checked;
                this.#updateRulerDisplay();
            });
            this.htmlElements.section.querySelector('#rulerHorizontalToggle').addEventListener('change', (e) => {
                this.#rulerHorizontal = e.target.checked;
                if (this.#rulerHorizontal) {
                    this.htmlElements.section.querySelector('#rulerVerticalToggle').checked = false;
                    this.htmlElements.section.querySelector('#rulerV2Toggle').checked = false;
                    this.#rulerVertical = false;
                    this.#rulerV2 = false;
                }
                this.#updateRulerDisplay();
            });
            this.htmlElements.section.querySelector('#rulerVerticalToggle').addEventListener('change', (e) => {
                this.#rulerVertical = e.target.checked;
                if (this.#rulerVertical) {
                    this.htmlElements.section.querySelector('#rulerHorizontalToggle').checked = false;
                    this.htmlElements.section.querySelector('#rulerV2Toggle').checked = false;
                    this.#rulerHorizontal = false;
                    this.#rulerV2 = false;
                }
                this.#updateRulerDisplay();
            });
            this.htmlElements.section.querySelector('#rulerV2Toggle').addEventListener('change', (e) => {
                this.#rulerV2 = e.target.checked;
                if (this.#rulerV2) {
                    this.htmlElements.section.querySelector('#rulerHorizontalToggle').checked = false;
                    this.htmlElements.section.querySelector('#rulerVerticalToggle').checked = false;
                    this.#rulerHorizontal = false;
                    this.#rulerVertical = false;
                }
                this.#updateRulerDisplay();
            });

            this.#rulerAngleInput.addEventListener('input', (e) => {
                this.#rulerAngle = parseInt(e.target.value);
                this.#updateRulerDisplay();
            });
            this.#rulerXInput.addEventListener('input', (e) => {
                this.#rulerX = parseInt(e.target.value);
                this.#updateRulerDisplay();
            });
            this.#rulerYInput.addEventListener('input', (e) => {
                this.#rulerY = parseInt(e.target.value);
                this.#updateRulerDisplay();
            });

            this.#updateRulerDisplay(); // Initial display update
        }

        #updateRulerDisplay() {
            if (!this.#rulerLineElement) return;

            this.#rulerLineElement.style.left = `${this.#rulerX}px`;
            this.#rulerLineElement.style.top = `${this.#rulerY}px`;

            let effectiveAngle = this.#rulerAngle;
            if (this.#rulerHorizontal) {
                effectiveAngle = 0;
            } else if (this.#rulerVertical) {
                effectiveAngle = 90;
            }
            // For Ruler V2, angle is calculated dynamically in getRulerEffectiveAngle
            // Here, we just ensure it's not overridden by stored angle if V2 is active.
            if (this.#rulerV2) {
                // If V2 is active, the static angle input is ignored for display
                // The actual snap logic happens during mousemove. We just hide the line for simplicity.
                this.#rulerLineElement.style.display = 'none';
                return;
            }

            this.#rulerLineElement.style.transform = `rotate(${effectiveAngle}deg)`;
            this.#rulerLineElement.style.display = this.#rulerMode ? 'block' : 'none';
        }

        #getRulerEffectiveAngle(currentX, currentY, lastX = 0, lastY = 0) {
            if (this.#rulerHorizontal) return 0;
            if (this.#rulerVertical) return 90;
            if (this.#rulerV2) {
                // Snaps to nearest 0, 45, 90, 135, 180, 225, 270, 315 degree angle
                const dx = currentX - lastX;
                const dy = currentY - lastY;
                const angle = Math.atan2(dy, dx) * (180 / Math.PI); // Angle in degrees
                const snappedAngle = Math.round(angle / 45) * 45;
                return snappedAngle < 0 ? snappedAngle + 360 : snappedAngle; // Ensure positive angle
            }
            return this.#rulerAngle;
        }

        // Helper to get the currently selected bot
        #getBot() {
            const botManagerClass = this.findGlobal("BotClientManager");
            if (!botManagerClass || !botManagerClass.siblings || botManagerClass.siblings.length === 0) {
                this.notify("warning", "No hay instancias de 'BotClientManager'. Por favor, crea un bot desde 'CubeEngine'.");
                return null;
            }

            const botManagerInstance = botManagerClass.siblings[0];
            const botClientInterfaces = botManagerInstance.children;
            let activeBotClientInterface = null;

            const selectedBotInput = document.querySelector('input[name="botClient"]:checked');
            if (selectedBotInput) {
                activeBotClientInterface = botClientInterfaces.find(bci => bci.htmlElements.input === selectedBotInput);
            }

            if (!activeBotClientInterface && botClientInterfaces.length > 0) {
                activeBotClientInterface = botClientInterfaces[0]; // Default to the first bot
                this.notify("info", `No se seleccionó un bot. Usando el primero: ${activeBotClientInterface.getName()}.`);
            }

            if (!activeBotClientInterface || !activeBotClientInterface.bot || !activeBotClientInterface.bot.getReadyState()) {
                 this.notify("warning", `El bot "${activeBotClientInterface ? activeBotClientInterface.getName() : 'seleccionado'}" no está conectado y listo.`);
                 return null;
            }
            return activeBotClientInterface.bot;
        }
    }
})();


// START INTELLIGENT ARTIST + DRAWING ASSISTANT (FUSIONADO)
(function ArtisanStudioModule() {
    const QBit = globalThis[arguments[0]];

    // --- Consolidated Styles ---
    QBit.Styles.addRules([
        // Styles for Intelligent Artist sections
        `#${QBit.identifier} .intelligent-artist-section {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 10px;
            background-color: var(--CE-bg_color);
        }`,
        `#${QBit.identifier} .intelligent-artist-section-title {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--dark-blue-title);
            text-align: center;
        }`,
        `#${QBit.identifier} .intelligent-artist-section input[type="text"],
         #${QBit.identifier} .intelligent-artist-section input[type="number"],
         #${QBit.identifier} .intelligent-artist-section input[type="color"],
         #${QBit.identifier} .drawing-assistant-controls input[type="number"],
         #${QBit.identifier} .drawing-assistant-controls input[type="color"] {
            width: 100%; padding: 5px; box-sizing: border-box;
            border: 1px solid var(--CE-color); border-radius: .25rem;
            background-color: var(--CE-bg_color); color: var(--CE-color);
         }`,
        `#${QBit.identifier} .intelligent-artist-section ._row > *,
         #${QBit.identifier} .drawing-assistant-controls ._row > * {
            margin: 0 2px;
        }`,
        // UPDATED: Styles for the horizontal scrolling sketch list
        `#${QBit.identifier} .intelligent-artist-sketch-list {
            display: flex;
            flex-wrap: nowrap; /* Forces horizontal layout */
            overflow-x: auto;  /* Adds the scrollbar when content overflows */
            gap: 5px;
            padding: 5px; /* Add padding inside the box */
            margin-top: 5px; /* Space from label above */
            border: 1px solid var(--CE-color); /* The "cuadrito" border */
            border-radius: .25rem;
            background-color: var(--CE-bg_color); /* Background for the box */
            line-height: normal; /* Ensure text within buttons is normal */
        }`,
        `#${QBit.identifier} .intelligent-artist-sketch-list .btn {
            flex: 0 0 auto; /* Prevents stretching and ensures they stay put */
            margin: 0; /* Remove default margin from btn class */
            padding: 2px 5px; /* Compact padding */
            font-size: 0.7em;
            white-space: nowrap; /* Prevents button text from wrapping */
        }`,

        // Styles for Drawing Assistant sections
        `#drawing-assistant-overlay {
            position: absolute;
            top: 0;
            left: 0;
            z-index: 1000; /* Above game canvas, below main UI elements */
            pointer-events: none; /* Crucial to allow clicks to pass through to game canvas */
        }`,
        `#drawing-assistant-grid-toggle.active,
         #drawing-assistant-symmetry-toggle.active,
         #drawing-assistant-pixel-perfect-toggle.active {
             background-color: var(--info);
             color: white;
         }`
    ]);

    class ArtisanStudio extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        // --- Intelligent Artist Properties ---
        _currentBrushSize = 5;
        _currentSketchColor = "#888888";
        _SKETCH_DATABASE = {}; // Will be loaded dynamically
        _sketchTextInput;
        _sketchListContainer; // Reference to the container for quick sketch buttons
        _sketchListCountLabel; // Reference to the label that displays the sketch count

        // --- Drawing Assistant Properties ---
        _overlayCanvas;
        _overlayCtx;
        _gameCanvas; // Reference to the actual game canvas

        _isSymmetryActive = false;
        _isPixelPerfectActive = false;
        _isGridVisible = false;

        _drawingColor = "#000000"; // Default drawing color for assistant tools
        _drawingThickness = 5;    // Default drawing thickness for assistant tools

        // State for drawing assistant
        _isDrawingLocal = false;
        _lastX = 0;
        _lastY = 0;

        // Ensure bound handlers are available for add/remove events from Drawing Assistant
        _boundMouseDownHandler = this._handleMouseDown.bind(this);
        _boundMouseMoveHandler = this._handleMouseMove.bind(this);
        _boundMouseUpHandler = this._handleMouseUp.bind(this);

        constructor() {
            super("Artisan Studio", '<i class="fas fa-palette"></i>'); // New name and icon
            this._onStartup();
        }

        async _onStartup() {
            this._findGameCanvas(); // Shared setup for gameCanvas
            this._setupOverlayCanvas(); // Drawing Assistant specific setup
            this._loadInterface(); // Combined UI loading
            this._hookGameDrawingEvents(); // Drawing Assistant specific hooks
            await this._loadSketchesFromGithub(); // Intelligent Artist specific loading
            this.notify("info", "Módulo 'Artisan Studio' cargado. Explora las herramientas de dibujo asistido y generación de bocetos.");
        }

        _loadInterface() {
            const container = domMake.Tree("div");

            // --- Section: Bocetos Asistidos y Limpieza (Intelligent Artist) ---
            const intelligentArtistSection = domMake.Tree("div", { class: "intelligent-artist-section" });
            intelligentArtistSection.appendChild(domMake.Tree("div", { class: "intelligent-artist-section-title" }, ["Bocetos Asistidos"]));

            // Row 1: Generate Sketch
            const generateSketchRow = domMake.Row();
            this._sketchTextInput = domMake.Tree("input", { type: "text", placeholder: "Concepto de boceto (ej. 'árbol')" });
            const generateSketchButton = domMake.Button("Generar Boceto");
            generateSketchButton.title = "Dibuja un boceto predefinido para la palabra ingresada.";
            generateSketchButton.addEventListener("click", () => {
                this._simulateAISketch(this._sketchTextInput.value.toUpperCase());
            });
            generateSketchRow.appendAll(this._sketchTextInput, generateSketchButton);
            intelligentArtistSection.appendChild(generateSketchRow);

            // Row 2: Clear Canvas
            const clearCanvasRow = domMake.Row();
            const clearCanvasButton = domMake.Button("Limpiar Lienzo");
            clearCanvasButton.title = "Limpia el lienzo con una línea blanca muy grande.";
            clearCanvasButton.addEventListener("click", () => {
                const bot = this._getBot(); // Using the combined getBot
                if (bot && bot.getReadyState()) {
                    bot.emit("line", -1, 0, 0, 100, 100, true, 900, "#FFFFFF", false); // Diagonal line 1
                    bot.emit("line", -1, 100, 0, 0, 100, true, 900, "#FFFFFF", false); // Diagonal line 2
                    this.notify("success", "El lienzo ha sido limpiado.");
                } else {
                    this.notify("warning", "Se necesita al menos 1 bot activo para limpiar el lienzo.");
                }
            });
            clearCanvasRow.appendChild(clearCanvasButton);
            intelligentArtistSection.appendChild(clearCanvasRow);

            // Row 3: Sketch Color and Size
            const sketchConfigRow = domMake.Row();
            const sketchColorLabel = domMake.Tree("label", {}, ["Color Boceto:"]);
            const sketchColorInput = domMake.Tree("input", { type: "color", value: this._currentSketchColor });
            sketchColorInput.title = "Define el color del boceto.";
            sketchColorInput.addEventListener("change", (e) => {
                this._currentSketchColor = e.target.value;
                this.notify("info", `Color del boceto cambiado a: ${this._currentSketchColor}`);
            });
            const brushSizeLabel = domMake.Tree("label", {}, ["Tamaño Pincel:"]);
            const brushSizeInput = domMake.Tree("input", { type: "number", min: 1, max: 50, value: this._currentBrushSize });
            brushSizeInput.title = "Define el tamaño del pincel para el boceto.";
            brushSizeInput.addEventListener("change", (e) => {
                this._currentBrushSize = parseInt(e.target.value);
                this.notify("info", `Tamaño del pincel para boceto cambiado a: ${this._currentBrushSize}`);
            });
            sketchConfigRow.appendAll(sketchColorLabel, sketchColorInput, brushSizeLabel, brushSizeInput);
            intelligentArtistSection.appendChild(sketchConfigRow);

            // Row 4: Sketch List - Adjusted structure for horizontal scrolling box
            this._sketchListCountLabel = domMake.Tree("label", { id: "artisan-studio-sketch-count-label" }, ["Bocetos Rápidos (0):"]);
            // Append label directly, not in a domMake.Row() with the container to ensure stacking
            intelligentArtistSection.appendChild(this._sketchListCountLabel);

            this._sketchListContainer = domMake.IconList({ class: 'intelligent-artist-sketch-list' }); // Uses the new CSS for the box and slider
            // Append the container directly
            intelligentArtistSection.appendChild(this._sketchListContainer);


            container.appendChild(intelligentArtistSection);

            // --- Section: Herramientas de Dibujo Asistido (Drawing Assistant) ---
            const drawingAssistantSection = domMake.Tree("div", { class: "intelligent-artist-section" });
            drawingAssistantSection.appendChild(domMake.Tree("div", { class: "intelligent-artist-section-title" }, ["Herramientas Asistidas"]));

            // Drawing parameters (Color, Thickness)
            const assistantParamsRow = domMake.Row();
            assistantParamsRow.style.gap = "5px";

            const assistantColorInput = domMake.Tree("input", { type: "color", value: this._drawingColor, title: "Color de Dibujo" });
            assistantColorInput.addEventListener("change", (e) => this._drawingColor = e.target.value);
            assistantParamsRow.appendAll(domMake.Tree("label", {}, ["Color:"]), assistantColorInput);

            const assistantThicknessInput = domMake.Tree("input", { type: "number", min: "1", max: "100", value: this._drawingThickness, title: "Grosor de Dibujo" });
            assistantThicknessInput.addEventListener("change", (e) => this._drawingThickness = parseInt(e.target.value));
            assistantParamsRow.appendAll(domMake.Tree("label", {}, ["Grosor:"]), assistantThicknessInput);
            drawingAssistantSection.appendChild(assistantParamsRow);

            // Geometric Shapes
            const assistantShapesRow = domMake.Row();
            assistantShapesRow.style.gap = "5px";

            const drawLineButton = domMake.Button('<i class="fas fa-grip-lines"></i> Línea');
            drawLineButton.addEventListener("click", () => this._enableShapeDrawing('line'));
            assistantShapesRow.appendChild(drawLineButton);

            const drawRectButton = domMake.Button('<i class="fas fa-vector-square"></i> Rect.');
            drawRectButton.addEventListener("click", () => this._enableShapeDrawing('rect'));
            assistantShapesRow.appendChild(drawRectButton);

            const drawCircleButton = domMake.Button('<i class="fas fa-circle"></i> Círculo');
            drawCircleButton.addEventListener("click", () => this._enableShapeDrawing('circle'));
            assistantShapesRow.appendChild(drawCircleButton);
            drawingAssistantSection.appendChild(assistantShapesRow);

            // Toggles (Grid, Symmetry, Pixel Perfect)
            const assistantTogglesRow = domMake.Row();
            assistantTogglesRow.style.gap = "5px";

            const toggleGridButton = domMake.Button('<i class="fas fa-th"></i> Cuadrícula');
            toggleGridButton.id = "drawing-assistant-grid-toggle";
            toggleGridButton.addEventListener("click", () => this._toggleGrid(toggleGridButton));
            assistantTogglesRow.appendChild(toggleGridButton);

            const toggleSymmetryButton = domMake.Button('<i class="fas fa-arrows-alt-h"></i> Simetría');
            toggleSymmetryButton.id = "drawing-assistant-symmetry-toggle";
            toggleSymmetryButton.addEventListener("click", () => this._toggleSymmetry(toggleSymmetryButton));
            assistantTogglesRow.appendChild(toggleSymmetryButton);

            const togglePixelPerfectButton = domMake.Button('<i class="fas fa-compress-arrows-alt"></i> Píxel Perfect');
            togglePixelPerfectButton.id = "drawing-assistant-pixel-perfect-toggle";
            togglePixelPerfectButton.addEventListener("click", () => this._togglePixelPerfect(togglePixelPerfectButton));
            assistantTogglesRow.appendChild(togglePixelPerfectButton);
            drawingAssistantSection.appendChild(assistantTogglesRow);

            // Collaborative Drawing Trigger
            const assistantCollabRow = domMake.Row();
            const startCollabDrawButton = domMake.Button('<i class="fas fa-users-cog"></i> Iniciar Dibujo Colaborativo');
            startCollabDrawButton.title = "Activa el módulo Autodraw V2 para que los bots colaboren en el dibujo (requiere imagen cargada en Autodraw V2).";
            startCollabDrawButton.addEventListener("click", () => this._triggerAutodrawV2Collab());
            assistantCollabRow.appendChild(startCollabDrawButton);
            drawingAssistantSection.appendChild(assistantCollabRow);

            container.appendChild(drawingAssistantSection);
            this.htmlElements.section.appendChild(container);
        }

        // --- General Bot Helper (centralized for both modules) ---
        _getBot() {
            const botManagerClass = this.findGlobal("BotClientManager");
            if (!botManagerClass || !botManagerClass.siblings || botManagerClass.siblings.length === 0) {
                this.notify("warning", "No hay instancias activas de 'BotClientManager'. Por favor, crea uno desde 'CubeEngine'.");
                return null;
            }
            const botManagerInstance = botManagerClass.siblings[0];

            if (!botManagerInstance || !botManagerInstance.children || botManagerInstance.children.length === 0) {
                this.notify("warning", "No hay bots activos en 'BotClientManager'. Por favor, crea y conecta uno.");
                return null;
            }

            const botClientInterfaces = botManagerInstance.children;
            let activeBotClientInterface = null;

            const selectedBotInput = document.querySelector('input[name="botClient"]:checked');
            if (selectedBotInput) {
                activeBotClientInterface = botClientInterfaces.find(bci => bci.htmlElements.input === selectedBotInput);
            }
            if (!activeBotClientInterface && botClientInterfaces.length > 0) {
                activeBotClientInterface = botClientInterfaces[0];
                this.notify("info", `No se seleccionó un bot. Usando el primer bot disponible: ${activeBotClientInterface.getName()}.`);
            }

            if (!activeBotClientInterface || !activeBotClientInterface.bot || !activeBotClientInterface.bot.getReadyState()) {
                this.notify("warning", `El bot "${activeBotClientInterface ? activeBotClientInterface.getName() : 'desconocido'}" no está conectado y listo para enviar comandos.`);
                return null;
            }
            return activeBotClientInterface.bot;
        }

        // --- Intelligent Artist Methods (now part of ArtisanStudio) ---
        async _loadSketchesFromGithub() {
            const githubUrl = "https://raw.githubusercontent.com/NuevoMundoOficial/SKETCH_DATABASE/main/sketches.json";
            this.notify("info", "Cargando base de datos de bocetos desde GitHub...");
            try {
                const response = await fetch(githubUrl);
                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }
                this._SKETCH_DATABASE = await response.json();
                this.notify("success", "Base de datos de bocetos cargada exitosamente.");
                this._populateSketchList(); // Now populate the list
            } catch (error) {
                this.notify("error", `Error al cargar la base de datos de bocetos: ${error.message}`);
                console.error("Error loading SKETCH_DATABASE:", error);
            }
        }

        _populateSketchList() {
            if (!this._sketchListContainer || !this._sketchListCountLabel) return;

            this._sketchListContainer.innerHTML = ''; // Clear previous content
            const words = Object.keys(this._SKETCH_DATABASE);

            // Update the label with the count using the stored reference
            this._sketchListCountLabel.textContent = `Bocetos Rápidos (50):`;

            words.forEach(word => {
                const sketchButton = domMake.Button(word);
                sketchButton.title = `Generar boceto para: ${word}`;
                sketchButton.style.flex = '0 0 auto';
                sketchButton.style.margin = '0'; // Use 0 margin as gap handles spacing
                sketchButton.style.padding = '2px 5px';
                sketchButton.style.fontSize = '0.7em';

                sketchButton.addEventListener("click", () => {
                    this._simulateAISketch(word);
                });
                this._sketchListContainer.appendChild(sketchButton);
            });
        }

        _simulateAISketch(concept) {
            const sketchData = this._SKETCH_DATABASE[concept];

            if (!sketchData) {
                this.notify("warning", `Boceto no disponible para: "${concept}".`);
                return;
            }

            this.notify("info", `Generando boceto para: "${concept}" (conceptual).`);
            const bot = this._getBot(); // Using the combined getBot
            if (!bot) {
                return;
            }

            this.notify("info", "Dibujando el boceto conceptual con el bot...");
            sketchData.forEach(line => {
                if (line.type === "circle") {
                    // Simulate a circle with multiple small lines
                    const centerX = line.x1;
                    const centerY = line.y1;
                    const radius = line.radius;
                    const segments = 24; // More segments for a smoother circle
                    for (let i = 0; i < segments; i++) {
                        const angle1 = (i / segments) * Math.PI * 2;
                        const angle2 = ((i + 1) / segments) * Math.PI * 2;
                        const x1_circ = centerX + radius * Math.cos(angle1);
                        const y1_circ = centerY + radius * Math.sin(angle1);
                        const x2_circ = centerX + radius * Math.cos(angle2);
                        const y2_circ = centerY + radius * Math.sin(angle2);
                        bot.emit("line", -1, x1_circ, y1_circ, x2_circ, y2_circ, true, this._currentBrushSize, this._currentSketchColor, false);
                    }
                } else {
                    bot.emit("line", -1, line.x1, line.y1, line.x2, line.y2, true, this._currentBrushSize, this._currentSketchColor, false);
                }
            });
            this.notify("success", `Boceto de "${concept}" dibujado. ¡Ahora puedes calcarlo o mejorarlo!`);
        }

        // --- Drawing Assistant Methods (now part of ArtisanStudio) ---
        _findGameCanvas() {
            this._gameCanvas = document.getElementById('canvas');
            if (!this._gameCanvas) {
                this.notify("error", "Canvas del juego no encontrado. Algunas funciones de dibujo no estarán disponibles.");
            }
        }

        _setupOverlayCanvas() {
            this._overlayCanvas = domMake.Tree('canvas', { id: 'drawing-assistant-overlay' });
            if (this._gameCanvas) {
                this._overlayCanvas.width = this._gameCanvas.width;
                this._overlayCanvas.height = this._gameCanvas.height;
                this._overlayCanvas.style.width = this._gameCanvas.style.width; // Match CSS size
                this._overlayCanvas.style.height = this._gameCanvas.style.height;
                this._gameCanvas.parentNode.insertBefore(this._overlayCanvas, this._gameCanvas.nextSibling); // Place right after game canvas
            } else {
                this._overlayCanvas.width = 1000;
                this._overlayCanvas.height = 1000;
                this._overlayCanvas.style.width = '700px';
                this._overlayCanvas.style.height = '700px';
                document.body.appendChild(this._overlayCanvas);
            }
            this._overlayCtx = this._overlayCanvas.getContext('2d');
            this._updateOverlaySizeAndPosition();

            window.addEventListener('resize', this._updateOverlaySizeAndPosition.bind(this));
        }

        _updateOverlaySizeAndPosition() {
            if (!this._gameCanvas || !this._overlayCanvas) return;

            const gameCanvasRect = this._gameCanvas.getBoundingClientRect();

            this._overlayCanvas.style.top = `${gameCanvasRect.top}px`;
            this._overlayCanvas.style.left = `${gameCanvasRect.left}px`;
            this._overlayCanvas.style.width = `${gameCanvasRect.width}px`;
            this._overlayCanvas.style.height = `${gameCanvasRect.height}px`;

            if (this._overlayCanvas.width !== this._gameCanvas.width) {
                 this._overlayCanvas.width = this._gameCanvas.width;
            }
            if (this._overlayCanvas.height !== this._gameCanvas.height) {
                 this._overlayCanvas.height = this._gameCanvas.height;
            }

            this._clearOverlay();
            if (this._isGridVisible) {
                this._drawGrid();
            }
            if (this._isSymmetryActive) {
                this._drawSymmetryLines();
            }
        }

        _clearOverlay() {
            this._overlayCtx.clearRect(0, 0, this._overlayCanvas.width, this._overlayCanvas.height);
        }

        _hookGameDrawingEvents() {
            if (!this._gameCanvas) return;

            this._gameCanvas.removeEventListener('mousedown', this._boundMouseDownHandler);
            this._gameCanvas.removeEventListener('mousemove', this._boundMouseMoveHandler);
            this._gameCanvas.removeEventListener('mouseup', this._boundMouseUpHandler);
            this._gameCanvas.removeEventListener('mouseout', this._boundMouseUpHandler);

            this._gameCanvas.addEventListener('mousedown', this._boundMouseDownHandler);
            this._gameCanvas.addEventListener('mousemove', this._boundMouseMoveHandler);
            this._gameCanvas.addEventListener('mouseup', this._boundMouseUpHandler);
            this._gameCanvas.addEventListener('mouseout', this._boundMouseUpHandler);
        }

        _handleMouseDown(e) {
            this._isDrawingLocal = true;
            const rect = this._gameCanvas.getBoundingClientRect();
            this._lastX = e.clientX - rect.left;
            this._lastY = e.clientY - rect.top;
            this._overlayCtx.beginPath();
            this._overlayCtx.moveTo(this._lastX, this._lastY);
        }

        _handleMouseMove(e) {
            if (!this._isDrawingLocal) return;
            const rect = this._gameCanvas.getBoundingClientRect();
            const currentX = e.clientX - rect.left;
            const currentY = e.clientY - rect.top;

            this._sendLineCommand(this._lastX, this._lastY, currentX, currentY);

            this._overlayCtx.lineTo(currentX, currentY);
            this._overlayCtx.strokeStyle = this._drawingColor;
            this._overlayCtx.lineWidth = this._drawingThickness;
            this._overlayCtx.lineCap = 'round';
            this._overlayCtx.lineJoin = 'round';
            this._overlayCtx.stroke();
            this._overlayCtx.beginPath();
            this._overlayCtx.moveTo(currentX, currentY);

            this._lastX = currentX;
            this._lastY = currentY;
        }

        _handleMouseUp() {
            this._isDrawingLocal = false;
            this._clearOverlay();
            if (this._isGridVisible) this._drawGrid();
            if (this._isSymmetryActive) this._drawSymmetryLines();
        }

        _sendLineCommand(startX, startY, endX, endY) {
            const bot = this._getBot();
            if (!bot) return;

            const rect = this._gameCanvas.getBoundingClientRect();
            const scaleX = 100 / rect.width;
            const scaleY = 100 / rect.height;

            let gameX1 = startX * scaleX;
            let gameY1 = startY * scaleY;
            let gameX2 = endX * scaleX;
            let gameY2 = endY * scaleY;

            if (this._isPixelPerfectActive) {
                const snapSize = 2;
                gameX1 = Math.round(gameX1 / snapSize) * snapSize;
                gameY1 = Math.round(gameY1 / snapSize) * snapSize;
                gameX2 = Math.round(gameX2 / snapSize) * snapSize;
                gameY2 = Math.round(gameY2 / snapSize) * snapSize;
            }

            gameX1 = Math.max(0, Math.min(100, gameX1));
            gameY1 = Math.max(0, Math.min(100, gameY1));
            gameX2 = Math.max(0, Math.min(100, gameX2));
            gameY2 = Math.max(0, Math.min(100, gameY2));

            bot.emit("line", -1, gameX1, gameY1, gameX2, gameY2, true, this._drawingThickness, this._drawingColor, false);

            if (this._isSymmetryActive) {
                const midX = 50;
                const mirroredGameX1 = midX + (midX - gameX1);
                const mirroredGameX2 = midX + (midX - gameX2);
                bot.emit("line", -1, mirroredGameX1, gameY1, mirroredGameX2, gameY2, true, this._drawingThickness, this._drawingColor, false);
            }
        }

        _enableShapeDrawing(shapeType) {
            this.notify("info", `Modo '${shapeType}' activado. Haz clic en el lienzo para definir la forma.`);
            const bot = this._getBot();
            if (!bot) return;

            let startCoords = null;
            let currentShapeOverlayListener = null;

            const drawShapePreview = (x1, y1, x2, y2) => {
                this._clearOverlay();
                if (this._isGridVisible) this._drawGrid();
                if (this._isSymmetryActive) this._drawSymmetryLines();

                const ctx = this._overlayCtx;
                ctx.strokeStyle = this._drawingColor;
                ctx.lineWidth = 1;
                ctx.setLineDash([5, 5]);
                ctx.beginPath();

                const width = x2 - x1;
                const height = y2 - y1;

                if (shapeType === 'line') {
                    ctx.moveTo(x1, y1);
                    ctx.lineTo(x2, y2);
                } else if (shapeType === 'rect') {
                    ctx.rect(x1, y1, width, height);
                } else if (shapeType === 'circle') {
                    const dx = x2 - x1;
                    const dy = y2 - y1;
                    const radius = Math.sqrt(dx * dx + dy * dy);
                    ctx.arc(x1, y1, radius, 0, 2 * Math.PI);
                }
                ctx.stroke();
                ctx.setLineDash([]);
            };

            const onClick = (e) => {
                const rect = this._gameCanvas.getBoundingClientRect();
                const currentX = e.clientX - rect.left;
                const currentY = e.clientY - rect.top;

                if (!startCoords) {
                    startCoords = { x: currentX, y: currentY };
                    this.notify("info", "Haz clic de nuevo para definir el final de la forma.");

                    currentShapeOverlayListener = (moveEvent) => {
                        const moveRect = this._gameCanvas.getBoundingClientRect();
                        const moveX = moveEvent.clientX - moveRect.left;
                        const moveY = moveEvent.clientY - moveRect.top;
                        drawShapePreview(startCoords.x, startCoords.y, moveX, moveY);
                    };
                    this._gameCanvas.addEventListener('mousemove', currentShapeOverlayListener);

                } else {
                    const bot = this._getBot();
                    if (!bot) {
                        this.notify("warning", "Bot no disponible, no se puede dibujar la forma.");
                        this._gameCanvas.removeEventListener('click', onClick);
                        this._gameCanvas.removeEventListener('mousemove', currentShapeOverlayListener);
                        this._clearOverlay();
                        return;
                    }

                    const scaleX = 100 / rect.width;
                    const scaleY = 100 / rect.height;

                    const x1 = startCoords.x * scaleX;
                    const y1 = startCoords.y * scaleY;
                    const x2 = currentX * scaleX;
                    const y2 = currentY * scaleY;

                    const thickness = this._drawingThickness;
                    const color = this._drawingColor;

                    if (shapeType === 'line') {
                        bot.emit("line", -1, x1, y1, x2, y2, true, thickness, color, false);
                    } else if (shapeType === 'rect') {
                        bot.emit("line", -1, x1, y1, x2, y1, true, thickness, color, false);
                        bot.emit("line", -1, x2, y1, x2, y2, true, thickness, color, false);
                        bot.emit("line", -1, x2, y2, x1, y2, true, thickness, color, false);
                        bot.emit("line", -1, x1, y2, x1, y1, true, thickness, color, false);
                    } else if (shapeType === 'circle') {
                        const centerX = x1;
                        const centerY = y1;
                        const dx = x2 - x1;
                        const dy = y2 - y1;
                        const radius = Math.sqrt(dx * dx + dy * dy);

                        const segments = 48;
                        for (let i = 0; i < segments; i++) {
                            const angle1 = (i / segments) * Math.PI * 2;
                            const angle2 = ((i + 1) / segments) * Math.PI * 2;
                            const cx1 = centerX + radius * Math.cos(angle1);
                            const cy1 = centerY + radius * Math.sin(angle1);
                            const cx2 = centerX + radius * Math.cos(angle2);
                            const cy2 = centerY + radius * Math.sin(angle2);
                            bot.emit("line", -1, cx1, cy1, cx2, cy2, true, thickness, color, false);
                        }
                    }

                    this._gameCanvas.removeEventListener('click', onClick);
                    this._gameCanvas.removeEventListener('mousemove', currentShapeOverlayListener);
                    this._clearOverlay();
                    this.notify("success", `${shapeType} dibujado.`);
                    startCoords = null;
                }
            };
            this._gameCanvas.addEventListener('click', onClick);
        }

        _toggleGrid(button) {
            this._isGridVisible = !this._isGridVisible;
            button.classList.toggle("active", this._isGridVisible);
            this._clearOverlay();
            if (this._isGridVisible) {
                this._drawGrid();
                this.notify("info", "Cuadrícula visible.");
            } else {
                this.notify("info", "Cuadrícula oculta.");
            }
            if (this._isSymmetryActive) this._drawSymmetryLines();
        }

        _drawGrid() {
            if (!this._gameCanvas) return;
            const ctx = this._overlayCtx;
            const rect = this._gameCanvas.getBoundingClientRect();
            const cellSize = 50;

            ctx.strokeStyle = "rgba(100, 100, 100, 0.5)";
            ctx.lineWidth = 1;
            ctx.setLineDash([2, 2]);

            for (let x = 0; x <= rect.width; x += cellSize) {
                ctx.beginPath();
                ctx.moveTo(x, 0);
                ctx.lineTo(x, rect.height);
                ctx.stroke();
            }

            for (let y = 0; y <= rect.height; y += cellSize) {
                ctx.beginPath();
                ctx.moveTo(0, y);
                ctx.lineTo(rect.width, y);
                ctx.stroke();
            }
            ctx.setLineDash([]);
        }

        _toggleSymmetry(button) {
            this._isSymmetryActive = !this._isSymmetryActive;
            button.classList.toggle("active", this._isSymmetryActive);
            this._clearOverlay();
            if (this._isGridVisible) this._drawGrid();
            if (this._isSymmetryActive) {
                this._drawSymmetryLines();
                this.notify("info", "Modo Simetría Activo.");
            } else {
                this.notify("info", "Modo Simetría Inactivo.");
            }
        }

        _drawSymmetryLines() {
            if (!this._gameCanvas) return;
            const ctx = this._overlayCtx;
            const rect = this._gameCanvas.getBoundingClientRect();

            ctx.strokeStyle = "rgba(255, 0, 0, 0.7)";
            ctx.lineWidth = 2;
            ctx.setLineDash([5, 5]);
            ctx.beginPath();
            ctx.moveTo(rect.width / 2, 0);
            ctx.lineTo(rect.width / 2, rect.height);
            ctx.stroke();
            ctx.setLineDash([]);
        }

        _togglePixelPerfect(button) {
            this._isPixelPerfectActive = !this._isPixelPerfectActive;
            button.classList.toggle("active", this._isPixelPerfectActive);
            this.notify("info", `Modo 'Píxel Perfect' ${this._isPixelPerfectActive ? 'Activado' : 'Desactivado'}.`);
        }

        _triggerAutodrawV2Collab() {
            const autodrawV2Class = this.findGlobal("AutodrawV2");
            if (!autodrawV2Class || !autodrawV2Class.siblings || autodrawV2Class.siblings.length === 0) {
                 this.notify("warning", "El módulo 'Autodraw V2' no está activo o no se encontró. No se puede iniciar el dibujo colaborativo.");
                 return;
            }
            const autodrawV2Instance = autodrawV2Class.siblings[0];

            if (autodrawV2Instance && typeof autodrawV2Instance.startDrawing === 'function') {
                autodrawV2Instance.startDrawing();
                this.notify("info", "Iniciando dibujo colaborativo a través del módulo Autodraw V2.");
            } else {
                this.notify("warning", "La instancia del módulo 'Autodraw V2' no está lista. Asegúrate de que Autodraw V2 se inicializó correctamente.");
            }
        }
    }
})("QBit");
// END INTELLIGENT ARTIST + DRAWING ASSISTANT (FUSIONADO)
    // START Game Ultra Log

(function GameUltraLogModule() {
    const QBit = globalThis[arguments[0]];

    // Combined Styles from both modules
    QBit.Styles.addRules([
        // Styles for CanvasImageInserter (Dibujo Ultra Rapido)
        `#game-ultra-log-container .drawing-settings {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 5px;
            margin-top: 10px;
            padding: 5px;
            border: 1px dashed var(--CE-color);
            border-radius: .25rem;
        }`,
        `#game-ultra-log-container .drawing-settings > div {
            display: flex;
            flex-direction: column;
        }`,
        `#game-ultra-log-container .drawing-settings label {
            font-size: 0.8em;
            margin-bottom: 2px;
            color: var(--CE-color);
        }`,
        `#game-ultra-log-container .drawing-settings input[type="number"] {
            width: 100%;
            padding: 5px;
            box-sizing: border-box;
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            background-color: var(--CE-bg_color);
            color: var(--CE-color);
        }`,
        `#game-ultra-log-container .action-buttons {
            display: flex;
            gap: 5px;
            margin-top: 10px;
        }`,
        `#game-ultra-log-container .action-buttons button {
            flex: 1;
        }`,
        `#game-ultra-log-container .loading-spinner {
            border: 4px solid rgba(0, 0, 0, 0.1);
            border-left-color: var(--info);
            border-radius: 50%;
            width: 20px;
            height: 20px;
            animation: spin 1s linear infinite;
            display: inline-block;
            vertical-align: middle;
            margin-left: 5px;
        }`,
        `@keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }`,

        // Styles for GameLog (Registro del Juego)
        `#game-ultra-log-container .log-section {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 10px;
            background-color: var(--CE-bg_color);
        }`,
        `#game-ultra-log-container .log-section-title {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--dark-blue-title);
            text-align: center;
        }`,
        `#game-ultra-log-container .log-display-area {
            max-height: 250px;
            overflow-y: auto;
            border: 1px solid var(--CE-color);
            padding: 5px;
            font-size: 0.75em;
            background-color: var(--CE-bg_color);
            color: var(--CE-color);
            margin-top: 5px;
            margin-bottom: 5px;
        }`,
        `#game-ultra-log-container .log-control-buttons {
            display: flex;
            gap: 5px;
            margin-top: 5px;
        }`,
        `#game-ultra-log-container .log-control-buttons button {
            flex: 1;
            padding: 5px;
        }`,
        `#game-ultra-log-container .log-control-buttons button.active {
            background-color: var(--info);
            color: white;
        }`
    ]);

    // Helper to convert RGBA array to RGBA string for CSS/Canvas
    function _rgbaArrayToString(rgbaArray) {
        return `rgba(${rgbaArray[0]},${rgbaArray[1]},${rgbaArray[2]},${rgbaArray[3] / 255})`;
    }

    // Helper to check if two RGBA colors are "similar enough"
    function _areColorsSimilar(color1, color2, threshold = 15) {
        if (!color1 || !color2) return false;
        return (
            Math.abs(color1[0] - color2[0]) <= threshold &&
            Math.abs(color1[1] - color2[1]) <= threshold &&
            Math.abs(color1[2] - color2[2]) <= threshold &&
            Math.abs(color1[3] - color2[3]) <= threshold
        );
    }

    class GameUltraLog extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        // --- Properties for Dibujo Ultra Rápido (CanvasImageInserter) ---
        _fileInput;
        _drawingStatusLabel;
        _insertButton;
        _stopDrawingButton;
        _currentDrawingIndex = 0;
        _drawingActive = false;

        _processingCanvas;
        _processingCtx;
        _imageData = null;

        _brushSizeInput;
        _drawingSpeedInput;
        _offsetXInput;
        _offsetYInput;
        _pixelDensityInput;
        _colorToleranceInput;

        _drawingCommands = [];

        // --- Properties for Registro del Juego (GameLog) ---
        _gameLog = [];
        _logDisplayElement;
        _isLoggingActive = true;

        constructor() {
            super("Game Ultra Log", '<i class="fas fa-fire"></i>'); // Keep original log icon
            this._onStartup();
        }

        _onStartup() {
            this._loadInterface();
            this._setupProcessingCanvas(); // For image drawing
            this._setupLogHooks(); // For game logging
            this.notify("info", "Módulo 'Game Ultra Log' cargado.");
        }

        _setupProcessingCanvas() {
            this._processingCanvas = document.createElement('canvas');
            this._processingCtx = this._processingCanvas.getContext('2d', { willReadFrequently: true });
        }

        _loadInterface() {
            const container = domMake.Tree("div", { id: "game-ultra-log-container" });

            // --- Section: Dibujo Ultra Rápido ---
            const drawingSection = domMake.Tree("div", { class: "log-section" });
            drawingSection.appendChild(domMake.Tree("div", { class: "log-section-title" }, ["Dibujo Ultra Rápido"]));

            const fileInputId = "image-inserter-fileinput-" + (Math.random() * 1e8 | 0);
            this._fileInput = domMake.Tree("input", { type: "file", accept: "image/*", id: fileInputId, hidden: true });
            const uploadLabel = domMake.Tree("label", { for: fileInputId, class: "btn btn-outline-secondary" }, [
                domMake.Tree("i", { class: "fas fa-upload" }), " Seleccionar Imagen"
            ]);
            uploadLabel.title = "Selecciona una imagen PNG/JPG para procesar y dibujar en el canvas.";
            drawingSection.appendAll(uploadLabel, this._fileInput);

            const settingsGroup = domMake.Tree("div", { class: "drawing-settings" });

            const brushSizeDiv = domMake.Tree("div");
            brushSizeDiv.appendAll(domMake.Tree("label", {}, ["Tamaño Pincel (px):"]),
                this._brushSizeInput = domMake.Tree("input", { type: "number", value: "2", min: "1", max: "100", title: "Grosor de línea para dibujar cada píxel o segmento." })
            );
            settingsGroup.appendChild(brushSizeDiv);

            const pixelDensityDiv = domMake.Tree("div");
            pixelDensityDiv.appendAll(domMake.Tree("label", {}, ["Densidad Píxel (salto):"]),
                this._pixelDensityInput = domMake.Tree("input", { type: "number", value: "1", min: "1", max: "10", title: "Cada cuántos píxeles se tomará una muestra (mayor = más rápido, menos detalle)." })
            );
            settingsGroup.appendChild(pixelDensityDiv);

            const offsetXDiv = domMake.Tree("div");
            offsetXDiv.appendAll(domMake.Tree("label", {}, ["Offset X (%):"]),
                this._offsetXInput = domMake.Tree("input", { type: "number", value: "0", min: "-100", max: "100", title: "Desplazamiento horizontal del dibujo en el canvas (0-100%)." })
            );
            settingsGroup.appendChild(offsetXDiv);

            const offsetYDiv = domMake.Tree("div");
            offsetYDiv.appendAll(domMake.Tree("label", {}, ["Offset Y (%):"]),
                this._offsetYInput = domMake.Tree("input", { type: "number", value: "0", min: "-100", max: "100", title: "Desplazamiento vertical del dibujo en el canvas (0-100%)." })
            );
            settingsGroup.appendChild(offsetYDiv);

            const drawingSpeedDiv = domMake.Tree("div");
            drawingSpeedDiv.appendAll(domMake.Tree("label", {}, ["Vel. Dibujo (ms/línea):"]),
                this._drawingSpeedInput = domMake.Tree("input", { type: "number", value: "5", min: "1", max: "500", title: "Retraso en milisegundos entre cada comando de dibujo enviado." })
            );
            settingsGroup.appendChild(drawingSpeedDiv);

            const colorToleranceDiv = domMake.Tree("div");
            colorToleranceDiv.appendAll(domMake.Tree("label", {}, ["Tolerancia Color (0-255):"]),
                this._colorToleranceInput = domMake.Tree("input", { type: "number", value: "0", min: "0", max: "255", title: "Define cuán similares deben ser dos píxeles para agruparse en una misma línea. Menor valor = más detalle, más líneas." })
            );
            settingsGroup.appendChild(colorToleranceDiv);
            drawingSection.appendChild(settingsGroup);

            this._drawingStatusLabel = domMake.Tree("div", { style: "margin: 8px 0; min-height: 20px; color: var(--info);" }, ["Sin imagen cargada."]);
            drawingSection.appendChild(this._drawingStatusLabel);

            const drawingActionButtonsRow = domMake.Row({ class: "action-buttons" });
            this._insertButton = domMake.Button('<i class="fas fa-play"></i> Iniciar Dibujo');
            this._insertButton.disabled = true;
            this._insertButton.title = "Dibuja la imagen seleccionada en el canvas del juego.";
            drawingActionButtonsRow.appendChild(this._insertButton);

            this._stopDrawingButton = domMake.Button('<i class="fas fa-stop"></i> Detener Dibujo');
            this._stopDrawingButton.disabled = true;
            this._stopDrawingButton.title = "Detiene el proceso de dibujo actual.";
            drawingActionButtonsRow.appendChild(this._stopDrawingButton);
            drawingSection.appendChild(drawingActionButtonsRow);

            container.appendChild(drawingSection);

            // --- Section: Registro del Juego ---
            const logSection = domMake.Tree("div", { class: "log-section" });
            logSection.appendChild(domMake.Tree("div", { class: "log-section-title" }, ["Registro del Juego"]));

            const toggleLogButton = domMake.Button("Desactivar Registro");
            toggleLogButton.addEventListener("click", () => this._toggleLogging(toggleLogButton));
            logSection.appendChild(toggleLogButton);

            this._logDisplayElement = domMake.Tree("div", { class: "log-display-area" }, ["Registro de eventos vacío."]);
            logSection.appendChild(this._logDisplayElement);

            const logControlButtonsRow = domMake.Row({ class: "log-control-buttons" });
            const clearButton = domMake.Button("Limpiar Log");
            clearButton.addEventListener("click", () => this._clearLog());
            logControlButtonsRow.appendChild(clearButton);

            const exportTxtButton = domMake.Button("Exportar TXT");
            exportTxtButton.addEventListener("click", () => this._exportLog('txt'));
            logControlButtonsRow.appendChild(exportTxtButton);

            const exportJsonButton = domMake.Button("Exportar JSON");
            exportJsonButton.addEventListener("click", () => this._exportLog('json'));
            logControlButtonsRow.appendChild(exportJsonButton);
            logSection.appendChild(logControlButtonsRow);

            container.appendChild(logSection);
            this.htmlElements.section.appendChild(container);

            // Event Listeners for drawing functionality
            this._fileInput.addEventListener("change", (ev) => this._handleFileInput(ev));
            this._insertButton.addEventListener("click", () => this._startDrawing());
            this._stopDrawingButton.addEventListener("click", () => this._stopDrawing());
        }

        // --- Methods for Dibujo Ultra Rápido (CanvasImageInserter) ---

        async _handleFileInput(ev) {
            const file = this._fileInput.files[0];
            if (!file) {
                this._drawingStatusLabel.textContent = "No se seleccionó ningún archivo.";
                this._insertButton.disabled = true;
                return;
            }

            this._drawingStatusLabel.innerHTML = 'Cargando imagen... <span class="loading-spinner"></span>';
            this._insertButton.disabled = true;
            this._stopDrawingButton.disabled = true;
            this._drawingActive = false;

            try {
                const base64 = await this._fileToBase64(file);
                const img = new Image();
                img.crossOrigin = "Anonymous";
                img.onload = async () => {
                    const maxDim = 150;
                    let width = img.width;
                    let height = img.height;

                    if (width > maxDim || height > maxDim) {
                        if (width / maxDim > height / maxDim) {
                            height = Math.round(height * (maxDim / width));
                            width = maxDim;
                        } else {
                            width = Math.round(width * (maxDim / height));
                            height = maxDim;
                        }
                    }

                    this._processingCanvas.width = width;
                    this._processingCanvas.height = height;
                    this._processingCtx.clearRect(0, 0, width, height);
                    this._processingCtx.drawImage(img, 0, 0, width, height);
                    this._imageData = this._processingCtx.getImageData(0, 0, width, height);

                    await this._generateDrawingCommands();

                    this._drawingStatusLabel.textContent = `Imagen '${file.name}' cargada y lista (${this._drawingCommands.length} comandos).`;
                    this._insertButton.disabled = false;
                    this.notify("success", "Imagen cargada y comandos generados.");
                };
                img.onerror = (err) => {
                    throw new Error("Fallo al cargar la imagen: " + err.type);
                };
                img.src = base64;

            } catch (e) {
                this._drawingStatusLabel.textContent = `Error: ${e.message}`;
                this.notify("error", `Fallo al procesar imagen: ${e.message}`);
            }
        }

        _fileToBase64(file) {
            return new Promise((resolve, reject) => {
                const reader = new FileReader();
                reader.onload = (ev) => resolve(ev.target.result);
                reader.onerror = (err) => reject(new Error("Error leyendo archivo: " + err.message));
                reader.readAsDataURL(file);
            });
        }

        _getBot() {
            const botManagerClass = this.findGlobal("BotClientManager");
            if (!botManagerClass || !botManagerClass.siblings || botManagerClass.siblings.length === 0) {
                this.notify("warning", "No hay instancias activas de 'BotClientManager'. Por favor, crea uno desde 'CubeEngine'.");
                return null;
            }
            const botManagerInstance = botManagerClass.siblings[0];
            const botClientInterfaces = botManagerInstance.children;

            let activeBotClientInterface = null;
            const selectedBotInput = document.querySelector('input[name="botClient"]:checked');
            if (selectedBotInput) {
                activeBotClientInterface = botClientInterfaces.find(bci => bci.htmlElements.input === selectedBotInput);
            }
            if (!activeBotClientInterface && botClientInterfaces.length > 0) {
                activeBotClientInterface = botClientInterfaces[0];
            }

            if (!activeBotClientInterface || !activeBotClientInterface.bot || !activeBotClientInterface.bot.getReadyState()) {
                this.notify("warning", `El bot "${activeBotClientInterface ? activeBotClientInterface.getName() : 'seleccionado'}" no está conectado y listo para enviar comandos.`);
                return null;
            }
            return activeBotClientInterface.bot;
        }

        async _generateDrawingCommands() {
            if (!this._imageData) {
                this.notify("warning", "No hay datos de imagen para generar comandos.");
                return;
            }

            this._drawingCommands = [];
            const pixels = this._imageData.data;
            const width = this._imageData.width;
            const height = this._imageData.height;

            const brushSize = parseInt(this._brushSizeInput.value) || 2;
            const offsetX = parseFloat(this._offsetXInput.value) || 0;
            const offsetY = parseFloat(this._offsetYInput.value) || 0;
            const pixelDensity = parseInt(this._pixelDensityInput.value) || 1;
            const colorTolerance = parseInt(this._colorToleranceInput.value) || 15;

            const scaleX = 100 / width;
            const scaleY = 100 / height;

            for (let y = 0; y < height; y += pixelDensity) {
                let currentLineColor = null;
                let lineStartX = -1;

                for (let x = 0; x < width; x += pixelDensity) {
                    const index = (y * width + x) * 4;
                    const r = pixels[index];
                    const g = pixels[index + 1];
                    const b = pixels[index + 2];
                    const a = pixels[index + 3];

                    const currentColor = [r, g, b, a];

                    if (a > 20) {
                        if (currentLineColor === null) {
                            currentLineColor = currentColor;
                            lineStartX = x;
                        } else if (!_areColorsSimilar(currentLineColor, currentColor, colorTolerance)) {
                            const gameX1 = lineStartX * scaleX + offsetX;
                            const gameY1 = y * scaleY + offsetY;
                            const gameX2 = (x - pixelDensity) * scaleX + offsetX;
                            const gameY2 = y * scaleY + offsetY;

                            this._drawingCommands.push({
                                x1: gameX1,
                                y1: gameY1,
                                x2: gameX2 + (brushSize * scaleX * 0.5),
                                y2: gameY2,
                                color: _rgbaArrayToString(currentLineColor),
                                thickness: brushSize
                            });

                            currentLineColor = currentColor;
                            lineStartX = x;
                        }
                    } else {
                        if (currentLineColor !== null) {
                            const gameX1 = lineStartX * scaleX + offsetX;
                            const gameY1 = y * scaleY + offsetY;
                            const gameX2 = (x - pixelDensity) * scaleX + offsetX;
                            const gameY2 = y * scaleY + offsetY;

                            this._drawingCommands.push({
                                x1: gameX1,
                                y1: gameY1,
                                x2: gameX2 + (brushSize * scaleX * 0.5),
                                y2: gameY2,
                                color: _rgbaArrayToString(currentLineColor),
                                thickness: brushSize
                            });
                            currentLineColor = null;
                            lineStartX = -1;
                        }
                    }
                }
                if (currentLineColor !== null && lineStartX !== -1) {
                    const gameX1 = lineStartX * scaleX + offsetX;
                    const gameY1 = y * scaleY + offsetY;
                    const gameX2 = (width - pixelDensity) * scaleX + offsetX;
                    const gameY2 = y * scaleY + offsetY;

                    this._drawingCommands.push({
                        x1: gameX1,
                        y1: gameY1,
                        x2: gameX2 + (brushSize * scaleX * 0.5),
                        y2: gameY2,
                        color: _rgbaArrayToString(currentLineColor),
                        thickness: brushSize
                    });
                }
            }
            this.notify("info", `Comandos de dibujo generados: ${this._drawingCommands.length} líneas.`);
        }

        async _startDrawing() {
            if (this._drawingCommands.length === 0) {
                this.notify("warning", "No hay comandos de dibujo. Carga una imagen y genera los comandos primero.");
                return;
            }

            const bot = this._getBot();
            if (!bot) {
                this.notify("error", "Un bot conectado es necesario para dibujar. Asegúrate de que el módulo 'BotClientManager' está activo y hay un bot listo.");
                return;
            }

            this._drawingActive = true;
            this._currentDrawingIndex = 0;
            this._insertButton.disabled = true;
            this._stopDrawingButton.disabled = false;
            this._fileInput.disabled = true;

            this.notify("info", "Iniciando dibujo de imagen...");
            this._drawingStatusLabel.innerHTML = `Dibujando... ${this._currentDrawingIndex}/${this._drawingCommands.length}`;

            const delayMs = parseInt(this._drawingSpeedInput.value) || 5;

            while (this._drawingActive && this._currentDrawingIndex < this._drawingCommands.length) {
                const cmd = this._drawingCommands[this._currentDrawingIndex];

                if (!bot.getReadyState()) {
                    this.notify("warning", "Bot desconectado. Deteniendo dibujo.");
                    this._stopDrawing();
                    break;
                }

                const clippedX1 = Math.max(0, Math.min(100, cmd.x1));
                const clippedY1 = Math.max(0, Math.min(100, cmd.y1));
                const clippedX2 = Math.max(0, Math.min(100, cmd.x2));
                const clippedY2 = Math.max(0, Math.min(100, cmd.y2));

                bot.emit("line", -1, clippedX1, clippedY1, clippedX2, clippedY2, true, cmd.thickness, cmd.color, false);

                this._currentDrawingIndex++;
                this._drawingStatusLabel.textContent = `Dibujando... ${this._currentDrawingIndex}/${this._drawingCommands.length}`;

                await new Promise(resolve => setTimeout(resolve, delayMs));
            }

            if (this._drawingActive) {
                this.notify("success", "Dibujo de imagen completado!");
                this._drawingStatusLabel.textContent = `Dibujo completado (${this._drawingCommands.length} líneas).`;
            } else {
                this.notify("info", `Dibujo detenido manualmente. ${this._currentDrawingIndex} de ${this._drawingCommands.length} líneas dibujadas.`);
            }

            this._insertButton.disabled = false;
            this._stopDrawingButton.disabled = true;
            this._fileInput.disabled = false;
        }

        _stopDrawing() {
            this._drawingActive = false;
            this._insertButton.disabled = false;
            this._stopDrawingButton.disabled = true;
            this._fileInput.disabled = false;
            this.notify("info", "Dibujo detenido.");
            this._drawingStatusLabel.textContent = `Dibujo detenido. ${this._currentDrawingIndex}/${this._drawingCommands.length} líneas.`;
        }

        // --- Methods for Registro del Juego (GameLog) ---

        _setupLogHooks() {
            if (globalThis._io && globalThis._io.events) {
                const eventsToLog = [
                    "bc_chatmessage", "uc_turn_begindraw", "uc_turn_selectword",
                    "bc_round_results", "bc_turn_results", "bc_votekick",
                    "bc_clientnotify", "bc_announcement", "bc_extannouncement",
                    "mc_roomplayerschange"
                ];

                eventsToLog.forEach(eventName => {
                    const originalEventCallback = globalThis._io.events[eventName];
                    globalThis._io.events[eventName] = (...args) => {
                        this._logEvent(eventName, args);
                        if (originalEventCallback) {
                            originalEventCallback.apply(this, args);
                        }
                    };
                });
            }

            const chatboxMessages = document.getElementById("chatbox_messages");
            if (chatboxMessages) {
                const chatObserver = new MutationObserver((mutations) => {
                    if (!this._isLoggingActive) return;
                    mutations.forEach(mutation => {
                        mutation.addedNodes.forEach(node => {
                            if (node.nodeType === 1 && node.classList.contains('chatmessage')) {
                                this._logChatMessage(node);
                            }
                        });
                    });
                });
                chatObserver.observe(chatboxMessages, { childList: true });
            }
        }

        _logEvent(type, data) {
            if (!this._isLoggingActive) return;
            const timestamp = new Date().toISOString();
            this._gameLog.push({ timestamp, type, data: JSON.parse(JSON.stringify(data)) });
            this._updateLogDisplay();
        }

        _logChatMessage(messageNode) {
            if (!this._isLoggingActive) return;
            const timestamp = messageNode.dataset.ts ? new Date(parseInt(messageNode.dataset.ts)).toISOString() : new Date().toISOString();
            let entry = { timestamp, type: "chatmsg" };

            if (messageNode.classList.contains('systemchatmessage') || messageNode.classList.contains('systemchatmessage5')) {
                entry.subtype = "system";
                entry.content = messageNode.textContent.trim();
            } else {
                entry.subtype = "player";
                entry.playerName = messageNode.querySelector('.playerchatmessage-name')?.textContent?.trim() || 'Unknown';
                entry.playerId = messageNode.querySelector('.playerchatmessage-name')?.parentElement?.dataset?.playerid || 'N/A';
                entry.content = messageNode.querySelector('.playerchatmessage-text')?.textContent?.trim() || '';
                entry.isSelf = messageNode.classList.contains('playerchatmessage-selfname');
            }
            this._gameLog.push(entry);
            this._updateLogDisplay();
        }

        _updateLogDisplay() {
            const maxDisplayEntries = 50;
            const entriesToDisplay = this._gameLog.slice(-maxDisplayEntries);

            this._logDisplayElement.innerHTML = '';
            entriesToDisplay.forEach(entry => {
                const logLine = domMake.Tree("div", {
                    style: `
                        white-space: nowrap;
                        overflow: hidden;
                        text-overflow: ellipsis;
                        color: ${entry.type.includes('error') ? 'var(--danger)' : entry.type.includes('warning') ? 'var(--warning)' : entry.type.includes('system') ? 'var(--info)' : 'var(--CE-color)'};
                    `,
                    title: JSON.stringify(entry)
                });
                let displayTxt = `[${new Date(entry.timestamp).toLocaleTimeString()}] `;
                if (entry.type === "chatmsg") {
                    if (entry.subtype === "system") {
                        displayTxt += `[SISTEMA] ${entry.content}`;
                    } else {
                        displayTxt += `[CHAT] ${entry.playerName} (${entry.playerId}): ${entry.content}`;
                    }
                } else if (entry.type === "uc_turn_begindraw" && entry.data && entry.data) {
                    displayTxt += `[TURNO] Comienza dibujo. Palabra: ${entry.data[1] || 'Desconocida'}`;
                } else if (entry.type === "uc_turn_selectword" && entry.data && entry.data) {
                    displayTxt += `[TURNO] Seleccionar palabra: [${entry.data[2]?.join(', ') || 'N/A'}]`;
                } else if (entry.type === "bc_round_results") {
                    displayTxt += `[RONDA] Resultados de ronda.`;
                } else if (entry.type === "bc_turn_results") {
                    displayTxt += `[TURNO] Resultados de turno.`;
                } else {
                    displayTxt += `[${entry.type}] ${JSON.stringify(entry.data).substring(0, 50)}...`;
                }
                logLine.textContent = displayTxt;
                this._logDisplayElement.appendChild(logLine);
            });
            this._logDisplayElement.scrollTop = this._logDisplayElement.scrollHeight;
        }

        _toggleLogging(button) {
            this._isLoggingActive = !this._isLoggingActive;
            button.classList.toggle("active", !this._isLoggingActive);
            button.textContent = this._isLoggingActive ? "Desactivar Registro" : "Activar Registro";
            this.notify("info", `Registro de Juego: ${this._isLoggingActive ? 'Activo' : 'Inactivo'}`);
        }

        _clearLog() {
            if (confirm("¿Estás seguro de que quieres limpiar todo el registro del juego?")) {
                this._gameLog = [];
                this._updateLogDisplay();
                this.notify("info", "Registro de Juego limpiado.");
            }
        }

        _exportLog(format) {
            if (this._gameLog.length === 0) {
                this.notify("warning", "No hay datos en el registro para exportar.");
                return;
            }

            let dataString;
            let mimeType;
            let filename = `drawaria_game_log_${new Date().toISOString().slice(0, 10)}`;

            if (format === 'json') {
                dataString = JSON.stringify(this._gameLog, null, 2);
                mimeType = 'application/json';
                filename += '.json';
            } else {
                dataString = this._gameLog.map(entry => {
                    const time = new Date(entry.timestamp).toLocaleTimeString();
                    if (entry.type === "chatmsg") {
                        if (entry.subtype === "system") {
                            return `[${time}] [SISTEMA] ${entry.content}`;
                        } else {
                            return `[${time}] [CHAT] ${entry.playerName} (${entry.playerId}): ${entry.content}`;
                        }
                    } else if (entry.type === "uc_turn_begindraw" && entry.data && entry.data) {
                        return `[${time}] [TURNO_INICIO] Palabra: ${entry.data[1] || 'Desconocida'}`;
                    } else if (entry.type === "uc_turn_selectword" && entry.data && entry.data) {
                        return `[${time}] [TURNO_PALABRA] Opciones: [${entry.data[2]?.join(', ') || 'N/A'}]`;
                    } else if (entry.type === "bc_round_results") {
                        return `[${time}] [RONDA_FIN] Resultados: ${JSON.stringify(entry.data[0].map(p => ({name: p[1], score: p[2]})))}`;
                    }
                    return `[${time}] [${entry.type}] ${JSON.stringify(entry.data)}`;
                }).join('\n');
                mimeType = 'text/plain';
                filename += '.txt';
            }

            const blob = new Blob([dataString], { type: mimeType });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = filename;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            this.notify("success", `Log exportado como ${filename}.`);
        }
    }
})("QBit");

// END Game Ultra Log

// --- START NEW MODULE: PaletteMaster (CORREGIDO: Gradient Fills y Robustez) ---
(function PaletteMasterModule() {
    const QBit = globalThis[arguments[0]];

    const LOCAL_STORAGE_KEY = 'cubicEngineCustomColors';
    const DEFAULT_CUSTOM_COLORS = [
        { name: "Teal", hex: "#008080" }, { name: "Lime", hex: "#AAFF00" },
        { name: "Cyan", hex: "#00FFFF" }, { name: "Magenta", hex: "#FF00FF" },
        { name: "Olive", hex: "#808000" }, { name: "Maroon", hex: "#800000" }
    ];

    QBit.Styles.addRules([
        // General section styling
        `#${QBit.identifier} .palette-master-section {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 10px;
            background-color: var(--CE-bg_color);
        }`,
        `#${QBit.identifier} .palette-master-section-title {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--dark-blue-title);
            text-align: center;
        }`,
        // MoreColorPalettes specific
        `#${QBit.identifier} .custom-color-button {
            box-shadow: 0 0 2px rgba(0,0,0,0.3);
            cursor: pointer;
            border: 1px solid transparent;
        }`,
        `#${QBit.identifier} .custom-color-button.custom-active-color {
            box-shadow: 0 0 5px 2px var(--info);
            border: 1px solid var(--info);
        }`,
        // StrokeMaster specific
        `#${QBit.identifier} .stroke-master-toggle-button.active {
            background-color: var(--info);
            color: white;
        }`,
        `#${QBit.identifier} .stroke-master-control-group {
            display: flex;
            flex-wrap: wrap;
            gap: 5px;
            margin-top: 5px;
            padding-top: 5px;
            border-top: 1px solid rgba(0,0,0,0.1);
        }`,
        `#${QBit.identifier} .stroke-master-control-group > div {
            flex: 1 1 48%; /* For responsiveness */
            display: flex;
            flex-direction: column;
            align-items: flex-start;
        }`,
        `#${QBit.identifier} .stroke-master-control-group input[type="number"],
         #${QBit.identifier} .stroke-master-control-group input[type="range"] {
            width: 100%;
        }`
    ]);

    class PaletteMaster extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        // Convert ALL private fields (#) to protected fields (_) for robustness
        _customColors = [];
        _colorButtonsContainer;
        _colorInput;
        _colorPaletteObserver;
        _gameTriangleElement = null;
        _proxyGameButton = null;

        _isPressureActive = false;
        _isTextureActive = false;
        _lastMousePos = { x: 0, y: 0 };
        _lastTimestamp = 0;
        _lastDrawThickness = 5;

        // Bound Handlers: Declare and bind methods that will be used as callbacks
        _handleMouseDown = this._handleMouseDown.bind(this);
        _handleMouseMove = this._handleMouseMove.bind(this);
        _handleMouseUp = this._handleMouseUp.bind(this);
        _addNewCustomColor = this._addNewCustomColor.bind(this);
        _clearAllCustomColors = this._clearAllCustomColors.bind(this);
        _handleCustomColorClick = this._handleCustomColorClick.bind(this);
        _handleGameColorClick = this._handleGameColorClick.bind(this);
        _togglePressureControl = this._togglePressureControl.bind(this);
        _toggleTextureBrush = this._toggleTextureBrush.bind(this);
        _simulateGradientFill = this._simulateGradientFill.bind(this); // This method needs to be async

        constructor() {
            super("Maestro de Paletas", '<i class="fas fa-brush"></i>');
            this._onStartup();
        }

        _onStartup() {
            this._loadInterface();
            this._loadCustomColors();
            this._setupColorPaletteObserver();
            this._hookDrawingEvents(); // Attach event listeners
        }

        _loadInterface() {
            const container = domMake.Tree("div");

            // --- Section: Gestión de Paletas ---
            const paletteSection = domMake.Tree("div", { class: "palette-master-section" });
            paletteSection.appendChild(domMake.Tree("div", { style: "width: 100%; text-align: center; font-weight: bold; margin-bottom: 5px;" },  ["Gestión de Paletas"]));

            const addColorRow = domMake.Row();
            const addColorLabel = domMake.Tree("label", {}, ["Añadir Color:"]);
            this._colorInput = domMake.Tree("input", { type: "color", value: "#FF0000" });
            const addColorButton = domMake.Button("Añadir");
            addColorButton.addEventListener("click", () => this._addNewCustomColor(this._colorInput.value));
            addColorRow.appendAll(addColorLabel, this._colorInput, addColorButton);
            paletteSection.appendChild(addColorRow);

            const clearColorsRow = domMake.Row();
            const clearAllColorsButton = domMake.Button("Limpiar Todos");
            clearAllColorsButton.addEventListener("click", this._clearAllCustomColors);
            clearColorsRow.appendChild(clearAllColorsButton);
            paletteSection.appendChild(clearColorsRow);

            const customColorsDisplayRow = domMake.Row();
            this._colorButtonsContainer = domMake.IconList();
            customColorsDisplayRow.appendChild(this._colorButtonsContainer);
            paletteSection.appendChild(customColorsDisplayRow);
            container.appendChild(paletteSection);

            // --- Section: Herramientas de Trazo ---
            const strokeSection = domMake.Tree("div", { class: "palette-master-section" });
            strokeSection.appendChild(domMake.Tree("div", { class: "palette-master-section-title" }, ["Herramientas de Trazo"])); // Added proper title

            // Pressure Control
            const pressureRow = domMake.Row();
            const pressureButton = domMake.Button("Control de Presión");
            pressureButton.classList.add("stroke-master-toggle-button");
            pressureButton.addEventListener("click", () => this._togglePressureControl(pressureButton));
            pressureRow.appendChild(pressureButton);
            strokeSection.appendChild(pressureRow);

            // Texture Brush
            const textureRow = domMake.Row();
            const textureButton = domMake.Button("Pincel Texturizado");
            textureButton.classList.add("stroke-master-toggle-button");
            textureButton.addEventListener("click", () => this._toggleTextureBrush(textureButton));
            textureRow.appendChild(textureButton);
            strokeSection.appendChild(textureRow);


            // Gradient Fills (conceptual buttons)
            const gradientGroup = domMake.Tree("div", { class: "stroke-master-control-group" });
            gradientGroup.appendChild(domMake.Tree("label", { style: "width: 100%; text-align: center; font-weight: bold; margin-bottom: 5px;" }, ["Rellenos Degradados"]));

            const diamondGradientButton = domMake.Button('<i class="fas fa-gem"></i>Efecto de Degradado<br> Diamante');
            diamondGradientButton.addEventListener("click", () => this._simulateGradientFill("diamond")); // Use bound _simulateGradientFill
            gradientGroup.appendChild(domMake.Tree("div", {}, [diamondGradientButton]));

            const radialGradientButton = domMake.Button('<i class="fas fa-bullseye"></i>Efecto de Degradado<br> Radial');
            radialGradientButton.addEventListener("click", () => this._simulateGradientFill("radial")); // Use bound _simulateGradientFill
            gradientGroup.appendChild(domMake.Tree("div", {}, [radialGradientButton]));

            const linearGradientButton = domMake.Button('<i class="fas fa-grip-lines"></i>Efecto de Degradado<br> Lineal');
            linearGradientButton.addEventListener("click", () => this._simulateGradientFill("linear")); // Use bound _simulateGradientFill
            gradientGroup.appendChild(domMake.Tree("div", {}, [linearGradientButton]));

            const verticalGradientButton = domMake.Button('<i class="fas fa-arrows-alt-v"></i>Efecto de Degradado<br> Vertical');
            verticalGradientButton.addEventListener("click", () => this._simulateGradientFill("vertical")); // Use bound _simulateGradientFill
            gradientGroup.appendChild(domMake.Tree("div", {}, [verticalGradientButton]));

            const conicalGradientButton = domMake.Button('<i class="fas fa-circle-notch"></i>Efecto de Degradado<br> Cónico');
            conicalGradientButton.addEventListener("click", () => this._simulateGradientFill("conical")); // Use bound _simulateGradientFill
            gradientGroup.appendChild(domMake.Tree("div", {}, [conicalGradientButton]));

            const waveGradientButton = domMake.Button('<i class="fas fa-water"></i>Efecto de Degradado<br> Ondulado');
            waveGradientButton.addEventListener("click", () => this._simulateGradientFill("wave")); // Use bound _simulateGradientFill
            gradientGroup.appendChild(domMake.Tree("div", {}, [waveGradientButton]));


            strokeSection.appendChild(gradientGroup);
            container.appendChild(strokeSection);

            this.htmlElements.section.appendChild(container);
        }

        // --- MoreColorPalettes Methods ---
        _loadCustomColors() {
            try {
                const storedColors = localStorage.getItem(LOCAL_STORAGE_KEY);
                this._customColors = storedColors ? JSON.parse(storedColors) : [...DEFAULT_CUSTOM_COLORS];
            } catch (e) {
                this.notify("error", `Error al cargar colores: ${e.message}. Usando colores por defecto.`);
                this._customColors = [...DEFAULT_CUSTOM_COLORS];
            }
            this._renderCustomColorButtons();
        }

        _saveCustomColors() {
            try {
                localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(this._customColors));
                this.notify("success", "Colores personalizados guardados.");
            } catch (e) {
                this.notify("error", `Error al guardar colores: ${e.message}`);
            }
        }

        _renderCustomColorButtons() {
            this._colorButtonsContainer.innerHTML = '';
            this._customColors.forEach(color => {
                this._createColorButton(color.hex, color.name);
            });
        }

        _addNewCustomColor(hexColor) {
            if (this._customColors.some(color => color.hex.toLowerCase() === hexColor.toLowerCase())) {
                this.notify("info", `El color ${hexColor} ya existe en tu paleta.`);
                return;
            }
            const newColor = { name: `Custom-${hexColor}`, hex: hexColor };
            this._customColors.push(newColor);
            this._saveCustomColors();
            this._createColorButton(newColor.hex, newColor.name);
            this.notify("info", `Color ${hexColor} añadido.`);
        }

        addCustomColorFromExternal(hexColor) { // Public method for other modules
            this._addNewCustomColor(hexColor);
        }

        _clearAllCustomColors() {
            if (confirm("¿Estás seguro de que quieres eliminar todos los colores personalizados?")) {
                this._customColors = [...DEFAULT_CUSTOM_COLORS];
                this._saveCustomColors();
                this._renderCustomColorButtons();
                this.notify("info", "Colores personalizados reiniciados a los valores por defecto.");
            }
        }

        _createColorButton(hexColor, name) {
            const newButton = domMake.Tree("div", {
                class: "drawcontrols-button drawcontrols-color custom-color-button",
                style: `background-color: ${hexColor};`,
                title: name,
                "data-hex": hexColor
            });

            newButton.addEventListener('click', this._handleCustomColorClick);
            this._colorButtonsContainer.appendChild(newButton);
        }

        _findGameElementsForColorPalette() {
            if (!this._gameTriangleElement || !document.body.contains(this._gameTriangleElement)) {
                this._gameTriangleElement = document.getElementById('colorpicker-cursor');
            }
            if (!this._proxyGameButton || !document.body.contains(this._proxyGameButton)) {
                const drawControls = document.getElementById('drawcontrols-colors') || document.getElementById('drawcontrols');
                if (drawControls) {
                    this._proxyGameButton = drawControls.querySelector('.drawcontrols-button.drawcontrols-color:not(.drawcontrols-colorpicker):not(.custom-color-button)');
                }
            }
        }

        _handleCustomColorClick(event) {
            const clickedButton = event.currentTarget;
            this._findGameElementsForColorPalette();

            if (!this._proxyGameButton) {
                this.notify("warning", "No se encontró un botón de color de juego para proxy. La funcionalidad de paleta puede ser limitada.");
                return;
            }

            const customColor = clickedButton.dataset.hex;
            const originalProxyColor = this._proxyGameButton.style.backgroundColor;

            this._proxyGameButton.style.backgroundColor = customColor;
            this._proxyGameButton.click();

            requestAnimationFrame(() => {
                this._proxyGameButton.style.backgroundColor = originalProxyColor;
                this._updateTrianglePosition(clickedButton);

                document.querySelectorAll('.custom-color-button.custom-active-color').forEach(btn => {
                    btn.classList.remove('custom-active-color');
                });
                clickedButton.classList.add('custom-active-color');
            });
        }

        _updateTrianglePosition(targetButton) {
            const triangle = this._gameTriangleElement;
            if (!triangle || !targetButton) return;
            const buttonContainer = document.getElementById('drawcontrols-colors') || document.getElementById('drawcontrols');
            if (!buttonContainer) return;

            const buttonRect = targetButton.getBoundingClientRect();
            const containerRect = buttonContainer.getBoundingClientRect();

            const buttonCenterRelativeToContainer = (buttonRect.left - containerRect.left) + (buttonRect.width / 2);
            const triangleWidth = triangle.offsetWidth || 8;
            const newLeft = buttonCenterRelativeToContainer - (triangleWidth / 2);

            triangle.style.left = `${newLeft}px`;
        }

        _setupColorPaletteObserver() {
            const observerTarget = document.getElementById('drawcontrols-colors') || document.getElementById('drawcontrols');
            if (!observerTarget) {
                this.notify("warning", "Contenedor de controles de dibujo no encontrado. Los colores personalizados pueden no funcionar bien.");
                setTimeout(() => this._setupColorPaletteObserver(), 1000);
                return;
            }

            this._colorPaletteObserver = new MutationObserver((mutations) => {
                mutations.forEach(mutation => {
                    if (mutation.type === 'childList' || mutation.type === 'attributes') {
                        this._addListenersToGameColorButtons();
                    }
                });
            });
            this._colorPaletteObserver.observe(observerTarget, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] });
            this._addListenersToGameColorButtons();
        }

        _addListenersToGameColorButtons() {
            this._findGameElementsForColorPalette();
            const gameColorButtons = document.querySelectorAll('.drawcontrols-button.drawcontrols-color:not(.custom-color-button)');
            gameColorButtons.forEach(gameBtn => {
                gameBtn.removeEventListener('click', this._handleGameColorClick);
                gameBtn.addEventListener('click', this._handleGameColorClick);
            });
        }

        _handleGameColorClick() {
            document.querySelectorAll('.custom-color-button.custom-active-color').forEach(customBtn => {
                customBtn.classList.remove('custom-active-color');
            });
        }

        // --- StrokeMaster Methods ---
        _getBot() {
            const botManagerClass = this.findGlobal("BotClientManager");
            if (!botManagerClass || !botManagerClass.siblings || botManagerClass.siblings.length === 0) {
                this.notify("warning", "No hay instancias activas de 'BotClientManager'. Por favor, crea uno desde 'CubeEngine'.");
                return null;
            }
            const botManagerInstance = botManagerClass.siblings[0];
            const botClientInterfaces = botManagerInstance.children;
            let activeBotClientInterface = null;

            const selectedBotInput = document.querySelector('input[name="botClient"]:checked');
            if (selectedBotInput) {
                activeBotClientInterface = botClientInterfaces.find(bci => bci.htmlElements.input === selectedBotInput);
            }
            if (!activeBotClientInterface && botClientInterfaces.length > 0) {
                activeBotClientInterface = botClientInterfaces[0];
                this.notify("info", `No se seleccionó un bot. Usando el primer bot disponible: ${activeBotClientInterface.getName()}.`);
            }

            // CORRECTED: Return the actual bot instance
            if (!activeBotClientInterface || !activeBotClientInterface.bot || !activeBotClientInterface.bot.getReadyState()) {
                this.notify("warning", `El bot "${activeBotClientInterface ? activeBotClientInterface.getName() : 'seleccionado'}" no está conectado y listo para enviar comandos.`);
                return null;
            }
            return activeBotClientInterface.bot;
        }

        _hookDrawingEvents() {
            const gameCanvas = document.querySelector("#canvas");
            if (!gameCanvas) return;

            // Remove previous listeners to prevent duplicates
            gameCanvas.removeEventListener("mousedown", this._boundMouseDownHandler);
            gameCanvas.removeEventListener("mousemove", this._boundMouseMoveHandler);
            gameCanvas.removeEventListener("mouseup", this._boundMouseUpHandler);
            gameCanvas.removeEventListener("mouseout", this._boundMouseUpHandler);

            // Attach listeners using the pre-bound handlers
            gameCanvas.addEventListener("mousedown", this._boundMouseDownHandler);
            gameCanvas.addEventListener("mousemove", this._boundMouseMoveHandler);
            gameCanvas.addEventListener("mouseup", this._boundMouseUpHandler);
            gameCanvas.addEventListener("mouseout", this._boundMouseUpHandler);
        }

        _handleMouseDown(e) {
            this.isDrawingLocal = true;
            const rect = e.target.getBoundingClientRect();
            this._lastMousePos.x = ((e.clientX - rect.left) / rect.width) * 100;
            this._lastMousePos.y = ((e.clientY - rect.top) / rect.height) * 100;
            this._lastTimestamp = performance.now();
        }

        _handleMouseMove(e) {
            if (!this.isDrawingLocal) return;

            const rect = e.target.getBoundingClientRect();
            const currentX = ((e.clientX - rect.left) / rect.width) * 100;
            const currentY = ((e.clientY - rect.top) / rect.height) * 100;

            let currentThickness = this._lastDrawThickness;
            let currentColor = this._getCurrentBrushColor();

            if (this._isPressureActive) {
                const currentTimestamp = performance.now();
                const dx = e.clientX - this._lastMousePos.x;
                const dy = e.clientY - this._lastMousePos.y;
                const distance = Math.sqrt(dx * dx + dy * dy);
                const timeDelta = currentTimestamp - this._lastTimestamp;
                const speed = distance / timeDelta;

                const minThickness = 2;
                const maxThickness = 20;
                const speedFactor = 0.5;

                currentThickness = maxThickness - (speed * speedFactor);
                currentThickness = Math.max(minThickness, Math.min(maxThickness, currentThickness));
                this._lastDrawThickness = currentThickness;
            }

            if (this._isTextureActive) {
                currentColor = this._applyColorNoise(currentColor, 10);
            }

            const bot = this._getBot();
            if (bot && bot.getReadyState()) {
                bot.emit("line", -1, this._lastMousePos.x, this._lastMousePos.y, currentX, currentY, true, currentThickness, currentColor, false);
            }

            this._lastMousePos.x = currentX;
            this._lastMousePos.y = currentY;
            this._lastTimestamp = performance.now();
        }

        _handleMouseUp() {
            this.isDrawingLocal = false;
            this._lastDrawThickness = 5;
        }

        _getCurrentBrushColor() {
            const colorPicker = document.querySelector('.drawcontrols-color.active');
            if (colorPicker) {
                const rgb = colorPicker.style.backgroundColor;
                if (rgb) return rgb;
            }
            return "#000000";
        }

        _applyColorNoise(color, noiseAmount) {
            let r, g, b;
            if (color.startsWith("rgb")) {
                const parts = color.match(/\d+/g).map(Number);
                r = parts[0]; g = parts[1]; b = parts[2];
            } else if (color.startsWith("#")) {
                const hex = color.slice(1);
                r = parseInt(hex.substring(0, 2), 16);
                g = parseInt(hex.substring(2, 4), 16);
                b = parseInt(hex.substring(4, 6), 16);
            } else {
                return color;
            }

            const addNoise = (value) => {
                const noise = (Math.random() - 0.5) * 2 * noiseAmount;
                return Math.max(0, Math.min(255, Math.floor(value + noise)));
            };
            return `rgb(${addNoise(r)},${addNoise(g)},${addNoise(b)})`;
        }

        _togglePressureControl(button) {
            this._isPressureActive = !this._isPressureActive;
            button.classList.toggle("active", this._isPressureActive);
            button.textContent = this._isPressureActive ? "Control de Presión Activo" : "Control de Presión";
            this.notify("info", `Control de Presión ${this._isPressureActive ? 'activado' : 'desactivado'}.`);
        }

        _toggleTextureBrush(button) {
            this._isTextureActive = !this._isTextureActive;
            button.classList.toggle("active", this._isTextureActive);
            button.textContent = this._isTextureActive ? "Pincel Texturizado Activo" : "Pincel Texturizado";
            this.notify("info", `Pincel Texturizado ${this._isTextureActive ? 'activado' : 'desactivado'}.`);
        }

        // ADDED async keyword here to allow await
        async _simulateGradientFill(type) {
            this.notify("info", `Simulando relleno degradado tipo '${type}' (conceptual).`);
            const bot = this._getBot();
            if (!bot) return;

            const startX = 20, endX = 80;
            const startY = 20, endY = 80;
            const steps = 20;
            const thickness = 25;
            const delayMs = 50;

            for (let i = 0; i <= steps; i++) {
                const ratio = i / steps;
                let r, g, b;
                let currentColor;

                switch (type) {
                    case "diamond": {
                        r = Math.floor(0 + (255 - 0) * (1 - ratio));
                        g = Math.floor(0 + (215 - 0) * (1 - ratio));
                        b = Math.floor(139 + (0 - 139) * (1 - ratio));
                        currentColor = `rgb(${r},${g},${b})`;
                        const currentDistance = 40 * ratio;
                        const centerX = 50, centerY = 50;
                        const p1x = centerX, p1y = centerY - currentDistance;
                        const p2x = centerX + currentDistance, p2y = centerY;
                        const p3x = centerX, p3y = centerY + currentDistance;
                        const p4x = centerX - currentDistance, p4y = centerY;
                        bot.emit("line", -1, p1x, p1y, p2x, p2y, true, thickness, currentColor, false);
                        bot.emit("line", -1, p2x, p2y, p3x, p3y, true, thickness, currentColor, false);
                        bot.emit("line", -1, p3x, p3y, p4x, p4y, true, thickness, currentColor, false);
                        bot.emit("line", -1, p4x, p4y, p1x, p1y, true, thickness, currentColor, false);
                        break;
                    }
                    case "radial": {
                        r = 255;
                        g = Math.floor(165 + (255 - 165) * (1 - ratio));
                        b = 0;
                        currentColor = `rgb(${r},${g},${b})`;
                        const currentRadius = 30 * ratio;
                        const numSegments = 36;
                        for (let j = 0; j < numSegments; j++) {
                            const angle = (j / numSegments) * 2 * Math.PI;
                            const x = 50 + currentRadius * Math.cos(angle);
                            const y = 50 + currentRadius * Math.sin(angle);
                            bot.emit("line", -1, x, y, x + 0.1, y + 0.1, true, thickness, currentColor, false);
                        }
                        break;
                    }
                    case "linear": {
                        r = Math.floor(255 * (1 - ratio));
                        g = 0;
                        b = Math.floor(255 * ratio);
                        currentColor = `rgb(${r},${g},${b})`;
                        const currentY = startY + (endY - startY) * ratio;
                        bot.emit("line", -1, startX, currentY, endX, currentY, true, thickness, currentColor, false);
                        break;
                    }
                    case "vertical": {
                        r = Math.floor(128 + (255 - 128) * ratio);
                        g = Math.floor(0 + (192 - 0) * ratio);
                        b = Math.floor(128 + (203 - 128) * ratio);
                        currentColor = `rgb(${r},${g},${b})`;
                        const vertY = startY + (endY - startY) * ratio;
                        bot.emit("line", -1, startX, vertY, endX, vertY, true, thickness, currentColor, false);
                        break;
                    }
                    case "conical": {
                        r = Math.floor(0 + (255 - 0) * ratio);
                        g = 0;
                        b = 255;
                        currentColor = `rgb(${r},${g},${b})`;
                        const angle = (ratio * 2 * Math.PI);
                        const radius = 40;
                        const cx = 50, cy = 50;
                        const x2 = cx + radius * Math.cos(angle);
                        const y2 = cy + radius * Math.sin(angle);
                        bot.emit("line", -1, cx, cy, x2, y2, true, thickness, currentColor, false);
                        break;
                    }
                    case "wave": {
                        r = Math.floor(0 + (255 - 0) * ratio);
                        g = Math.floor(255 + (127 - 255) * ratio);
                        b = Math.floor(255 + (80 - 255) * ratio);
                        currentColor = `rgb(${r},${g},${b})`;
                        const waveAmplitude = 10;
                        const waveFrequency = 0.1;
                        const wavyY = startY + (endY - startY) * ratio + waveAmplitude * Math.sin(ratio * Math.PI * 2 * waveFrequency);
                        bot.emit("line", -1, startX, wavyY, endX, wavyY, true, thickness, currentColor, false);
                        break;
                    }
                }
                await new Promise(resolve => setTimeout(resolve, delayMs));
            }
            this.notify("success", `Degradado tipo '${type}' dibujado.`);
        }
    }
})("QBit");

// --- END Palette


// START EXTRACTOR

(function PlayerProfileExtractorModule() {
    const QBit = globalThis[arguments[0]];

    // Define token names for better readability. These are from Drawaria's common.js.
    const TOKEN_NAMES = {
        0: "Thumbs Up",
        1: "Heart",
        2: "Paint Brush",
        3: "Cocktail",
        4: "Peace Sign",
        5: "Feather",
        6: "Trophy",
        7: "Mug",
        8: "Gift"
    };

    class PlayerProfileExtractor extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        #profileUrlInput;
        #extractedDataDisplay;
        #downloadButton;
        #lastExtractedData = null; // Store data for download

        constructor() {
            super("Extractor de Perfil (Pegar URL de perfil)", '<i class="fas fa-id-card"></i>');
            this.#onStartup();
        }

        #onStartup() {
            this.#loadInterface();
            this.notify("info", "Módulo Extractor de Perfil cargado.");
        }

        #loadInterface() {
            const container = domMake.Tree("div");

            // URL Input Row
            const urlRow = domMake.Row();
            this.#profileUrlInput = domMake.Tree("input", {
                type: "text",
                placeholder: "https://drawaria.online/profile/?uid=...)",
                style: "width: 100%; padding: 5px; box-sizing: border-box;"
            });
            urlRow.appendChild(this.#profileUrlInput);
            container.appendChild(urlRow);

            // Search Button Row
            const searchRow = domMake.Row();
            const searchButton = domMake.Button('<i class="fas fa-search"></i> Buscar Perfil');
            searchButton.addEventListener("click", () => this.#fetchAndExtractProfile());
            searchRow.appendChild(searchButton);
            container.appendChild(searchRow);

            // Extracted Data Display Area
            const displayRow = domMake.Row();
            this.#extractedDataDisplay = domMake.Tree("div", {
                style: `
                    max-height: 400px;
                    overflow-y: auto;
                    border: 1px solid var(--CE-color);
                    padding: 8px;
                    font-size: 0.85em;
                    background-color: var(--CE-bg_color);
                    color: var(--CE-color);
                    margin-top: 5px;
                    white-space: pre-wrap; /* Preserve whitespace and wrap lines */
                    font-family: monospace; /* For better readability of structured data */
                `
            }, ["Datos del perfil aparecerán aquí."]);
            displayRow.appendChild(this.#extractedDataDisplay);
            container.appendChild(displayRow);

            // Download Button Row
            const downloadRow = domMake.Row();
            this.#downloadButton = domMake.Button('<i class="fas fa-download"></i> Descargar Datos (JSON)');
            this.#downloadButton.disabled = true; // Disabled until data is extracted
            this.#downloadButton.addEventListener("click", () => this.#downloadExtractedData());
            downloadRow.appendChild(this.#downloadButton);
            container.appendChild(downloadRow);

            this.htmlElements.section.appendChild(container);
        }

        /**
         * Fetches the profile page HTML and extracts data.
         */
        async #fetchAndExtractProfile() {
            const profileUrl = this.#profileUrlInput.value.trim();
            if (!profileUrl) {
                this.notify("warning", "Por favor, introduce una URL de perfil.");
                return;
            }

            // Basic URL validation
            const uidMatch = profileUrl.match(/uid=([a-f0-9-]+)/i);
            if (!uidMatch || !uidMatch[1]) {
                this.notify("error", "URL inválida. No se pudo extraer el UID. Asegúrate de que es una URL de perfil de Drawaria (ej. https://drawaria.online/profile/?uid=...).");
                return;
            }
            const uid = uidMatch[1];


            this.notify("info", `Extrayendo datos de: ${profileUrl}...`);
            this.#extractedDataDisplay.textContent = "Cargando...";
            this.#downloadButton.disabled = true;

            // Declare DOMParser once here so it's accessible to all inner parsing blocks
            const parser = new DOMParser();

            try {
                // --- 1. Fetch Main Profile Page ---
                const profileResponse = await fetch(profileUrl);
                if (!profileResponse.ok) {
                    throw new Error(`Error HTTP (${profileUrl}): ${profileResponse.status} ${profileResponse.statusText}`);
                }
                const profileHtmlContent = await profileResponse.text();
                // Pass the parser instance to the parsing function
                const extracted = this._parseMainProfileHTML(profileHtmlContent, parser); // Changed to _parseMainProfileHTML
                extracted.uid = uid; // Ensure UID is set

                // --- 2. Fetch Gallery Count (from HTML page) ---
                const galleryPageUrl = `https://drawaria.online/gallery/?uid=${uid}`;
                try {
                    const galleryResponse = await fetch(galleryPageUrl);
                    if (galleryResponse.ok) {
                        const galleryHtmlContent = await galleryResponse.text();
                        // Use the shared parser instance
                        const galleryDoc = parser.parseFromString(galleryHtmlContent, 'text/html');
                        // Count all .grid-item elements within the .grid container
                        extracted.galleryImagesCount = galleryDoc.querySelectorAll('.grid .grid-item.galleryimage').length;
                    } else {
                        extracted.galleryImagesCount = `Error (${galleryResponse.status})`;
                        this.notify("warning", `Fallo al cargar la página de galería: ${galleryResponse.status}`);
                    }
                } catch (e) {
                    extracted.galleryImagesCount = `Error al parsear galería (${e.message.substring(0, 50)})`;
                    this.notify("warning", `Fallo al consultar página de galería: ${e.message}`);
                }

                // --- 3. Fetch Friends Count (from HTML page) ---
                const friendsPageUrl = `https://drawaria.online/friends/?uid=${uid}`;
                try {
                    const friendsResponse = await fetch(friendsPageUrl);
                    if (friendsResponse.ok) {
                        const friendsHtmlContent = await friendsResponse.text();
                        // Use the shared parser instance
                        const friendsDoc = parser.parseFromString(friendsHtmlContent, 'text/html');
                        extracted.friendsCount = friendsDoc.querySelectorAll('#friendscontainer .friendcard').length || 0;
                    } else {
                        extracted.friendsCount = `Error (${friendsResponse.status})`;
                        this.notify("warning", `Fallo al cargar la página de amigos: ${friendsResponse.status}`);
                    }
                } catch (e) {
                    extracted.friendsCount = `Error al parsear amigos (${e.message.substring(0, 50)})`;
                    this.notify("warning", `Fallo al consultar página de amigos: ${e.message}`);
                }

                // --- 4. Fetch Palettes Count (from HTML page) ---
                const palettesPageUrl = `https://drawaria.online/palettes/?uid=${uid}`;
                try {
                    const palettesResponse = await fetch(palettesPageUrl);
                    if (palettesResponse.ok) {
                        const palettesHtmlContent = await palettesResponse.text();
                        // Use the shared parser instance
                        const palettesDoc = parser.parseFromString(palettesHtmlContent, 'text/html');
                        extracted.palettesCount = palettesDoc.querySelectorAll('.palettelist .rowitem').length || 0;
                    } else {
                        extracted.palettesCount = `Error (${palettesResponse.status})`;
                        this.notify("warning", `Fallo al cargar la página de paletas: ${palettesResponse.status}`);
                    }
                } catch (e) {
                    extracted.palettesCount = `Error al parsear paletas (${e.message.substring(0, 50)})`;
                    this.notify("warning", `Fallo al consultar página de paletas: ${e.message}`);
                }


                // Final check: if primary player name was not found, notify as invalid profile
                // We re-parse here just for this specific final check, using the same parser instance.
                const doc = parser.parseFromString(profileHtmlContent, 'text/html');
                if (extracted.playerName === 'N/A' && !doc.querySelector('h1')) {
                     this.#extractedDataDisplay.textContent = "No se pudo encontrar el perfil o extraer datos. Asegúrate de que la URL es correcta y el perfil existe.";
                     this.#lastExtractedData = null;
                     this.#downloadButton.disabled = true;
                     this.notify("error", "Perfil no encontrado o datos no extraíbles.");
                     return;
                 }


                // Display extracted data
                this.#lastExtractedData = extracted;
                this.#displayExtractedData();
                this.#downloadButton.disabled = false;
                this.notify("success", "Datos del perfil extraídos exitosamente.");

            } catch (error) {
                this.notify("error", `Fallo general al cargar o procesar el perfil: ${error.message}`);
                this.#extractedDataDisplay.textContent = `Error: ${error.message}`;
                this.#lastExtractedData = null;
            }
        }

        /**
         * Parses the HTML content of the main profile page and extracts relevant information.
         * Changed from private to protected to allow access from other modules.
         * @param {string} htmlContent - The HTML content of the profile page.
         * @param {DOMParser} parser - The shared DOMParser instance.
         * @returns {object} Extracted data.
         */
        _parseMainProfileHTML(htmlContent, parser) { // Changed from #parseMainProfileHTML to _parseMainProfileHTML
            const doc = parser.parseFromString(htmlContent, 'text/html');
            const extracted = {};

            // --- Player Info (from profile page) ---
            const playerInfoAnchor = doc.querySelector('h1 a[href*="profile/?uid="]');
            if (playerInfoAnchor) {
                extracted.avatarUrl = playerInfoAnchor.querySelector('img.turnresults-avatar')?.src || 'N/A';
                // Player name is the text content of the anchor AFTER the image
                const playerNameNode = Array.from(playerInfoAnchor.childNodes).find(node => node.nodeType === Node.TEXT_NODE && node.textContent.trim().length > 0);
                extracted.playerName = playerNameNode?.textContent.trim() || 'N/A';
            } else {
                extracted.playerName = doc.querySelector('h1')?.textContent.trim() || 'N/A'; // Fallback to just H1 text if no anchor
                extracted.avatarUrl = 'N/A';
            }

            // --- Level & Experience (from profile page) ---
            extracted.level = doc.getElementById('levelval')?.textContent.trim() || 'N/A'; // Kept for JSON export, not displayed in text output
            extracted.experience = doc.getElementById('exp-val')?.textContent.trim() || 'N/A';

            // --- Pictionary / Guessing Stats (from profile page) ---
            extracted.pictionaryStats = {};
            const playStatsTableBody = doc.querySelector('#playstats table tbody');
            if (playStatsTableBody) {
                playStatsTableBody.querySelectorAll('tr').forEach(row => {
                    const label = row.querySelector('td:first-child')?.textContent.trim();
                    const value = row.querySelector('td:last-child')?.textContent.trim();
                    if (label && value) {
                        const cleanLabel = label.replace(/[:\s]/g, '').toLowerCase(); // "Total score:" -> "totalscore"
                        extracted.pictionaryStats[cleanLabel] = value;
                    }
                });
            }

            // --- Playground Accolades (Tokens) (from profile page) ---
            extracted.accusedTokens = {};
            const tokensTableBody = doc.querySelector('#tokens table tbody');
            if (tokensTableBody) {
                tokensTableBody.querySelectorAll('i[data-tokenid]').forEach(iconElement => {
                    const tokenId = parseInt(iconElement.dataset.tokenid);
                    const tokenName = TOKEN_NAMES[tokenId] || `Unknown Token ${tokenId}`;
                    const count = parseInt(iconElement.textContent.trim()) || 0; // Text content holds the number
                    extracted.accusedTokens[tokenName] = count;
                });
            }

            return extracted;
        }

        /**
         * Formats and displays the extracted data in the UI.
         */
        #displayExtractedData() {
            if (!this.#lastExtractedData) {
                this.#extractedDataDisplay.textContent = "No hay datos para mostrar.";
                return;
            }

            let displayTxt = `--- Datos del Perfil ---\n`;
            displayTxt += `  UID: ${this.#lastExtractedData.uid}\n`;
            displayTxt += `  Nombre del Jugador: ${this.#lastExtractedData.playerName}\n`;
            // Level is removed from text display, but still in #lastExtractedData for JSON export
            displayTxt += `  Experiencia: ${this.#lastExtractedData.experience}\n`;
            displayTxt += `  URL del Avatar: ${this.#lastExtractedData.avatarUrl}\n`;
            // GalleryImagesCount is removed from text display, but still in #lastExtractedData for JSON export
            displayTxt += `  Cantidad de Amigos: ${this.#lastExtractedData.friendsCount}\n`;
            displayTxt += `  Cantidad de Paletas: ${this.#lastExtractedData.palettesCount}\n\n`;

            displayTxt += `--- Estadísticas de Pictionary / Adivinanza ---\n`;
            if (Object.keys(this.#lastExtractedData.pictionaryStats).length > 0) {
                for (const key in this.#lastExtractedData.pictionaryStats) {
                    const displayKey = key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase());
                    displayTxt += `  ${displayKey}: ${this.#lastExtractedData.pictionaryStats[key]}\n`;
                }
            } else {
                displayTxt += `  No se encontraron estadísticas detalladas de Pictionary.\n`;
            }
            displayTxt += `\n`;

            displayTxt += `--- Menciones de Homenaje (Tokens) ---\n`;
            if (Object.keys(this.#lastExtractedData.accusedTokens).length > 0) {
                for (const tokenName in this.#lastExtractedData.accusedTokens) {
                    displayTxt += `  ${tokenName}: ${this.#lastExtractedData.accusedTokens[tokenName]}\n`;
                }
            } else {
                displayTxt += `  No se encontraron Menciones de Homenaje.\n`;
            }

            this.#extractedDataDisplay.textContent = displayTxt;
        }

        /**
         * Downloads the extracted data as a JSON file.
         */
        #downloadExtractedData() {
            if (!this.#lastExtractedData) {
                this.notify("warning", "No hay datos extraídos para descargar.");
                return;
            }

            const dataStr = JSON.stringify(this.#lastExtractedData, null, 2);
            const blob = new Blob([dataStr], { type: 'application/json' });
            const url = URL.createObjectURL(blob);
            const playerNameForFilename = this.#lastExtractedData.playerName.replace(/[^a-zA-Z0-9]/g, '_').substring(0, 30); // Clean for filename
            const filename = `drawaria_profile_${playerNameForFilename}_${this.#lastExtractedData.uid ? this.#lastExtractedData.uid.substring(0, 8) : 'data'}.json`;

            const a = document.createElement('a');
            a.href = url;
            a.download = filename;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            this.notify("success", `Datos del perfil descargados como ${filename}.`);
        }
    }
})("QBit");
// --- END NEW MODULE: PlayerProfileExtractor (CORREGIDO) ---


// --- START ANALIZER
(function ImageAnalyzerModule() {
    const QBit = globalThis[arguments[0]];

    QBit.Styles.addRules([
        `#image-analyzer-container {
            display: flex;
            flex-direction: column;
            gap: 10px;
            padding: 5px;
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            background-color: var(--CE-bg_color);
        }`,
        `#image-analyzer-container input[type="text"],
         #image-analyzer-container button {
            width: 100%;
            padding: 5px;
            box-sizing: border-box;
         }`,
        `#image-preview-canvas {
            border: 1px dashed var(--CE-color);
            max-width: 100%;
            height: auto;
            display: block;
            margin: 5px auto;
        }`,
        `#dominant-colors-display {
            display: flex;
            flex-wrap: wrap;
            gap: 5px;
            margin-top: 5px;
        }`,
        `#dominant-colors-display .color-swatch {
            width: 30px;
            height: 30px;
            border: 1px solid #ccc;
            border-radius: 3px;
            cursor: pointer;
            box-shadow: 0 0 3px rgba(0,0,0,0.2);
        }`,
        `#analysis-results {
            background-color: var(--CE-bg_color);
            border: 1px solid var(--CE-color);
            padding: 8px;
            margin-top: 10px;
            font-size: 0.8em;
            max-height: 150px;
            overflow-y: auto;
            white-space: pre-wrap;
            font-family: monospace;
        }`,
        `#image-analyzer-container .action-buttons {
            display: flex;
            gap: 5px;
            margin-top: 5px;
        }`,
        `#image-analyzer-container .action-buttons button {
            flex: 1;
        }`
    ]);

    class ImageAnalyzer extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        #imageUrlInput;
        #loadAndAnalyzeButton;
        #imagePreviewCanvas;
        #imagePreviewCtx;
        #dominantColorsDisplay;
        #analysisResultsDisplay;
        #copyResultsButton;
        #downloadResultsButton;

        constructor() {
            super("Analizador de Imágenes", '<i class="fas fa-camera-retro"></i>');
            this.#onStartup();
        }

        #onStartup() {
            this.#loadInterface();
            this.notify("info", "Módulo Analizador de Imágenes cargado.");
            this.#loadAndAnalyzeButton.disabled = false;
            this.#loadAndAnalyzeButton.textContent = 'Cargar y Analizar';
            this.#copyResultsButton.disabled = true;
            this.#downloadResultsButton.disabled = true;
        }

        #loadInterface() {
            const container = domMake.Tree("div", { id: "image-analyzer-container" });

            this.#imageUrlInput = domMake.Tree("input", {
                type: "text",
                placeholder: "Pegar URL de imagen (ej. avatar)",
                value: "https://drawaria.online/avatar/cache/1a5f4450-7153-11ef-acaf-250da20bac69.jpg"
            });
            container.appendChild(this.#imageUrlInput);

            this.#loadAndAnalyzeButton = domMake.Button('<i class="fas fa-chart-pie"></i> Cargar y Analizar');
            this.#loadAndAnalyzeButton.addEventListener("click", () => this.#loadImageAndAnalyze());
            container.appendChild(this.#loadAndAnalyzeButton);

            this.#imagePreviewCanvas = domMake.Tree("canvas", { id: "image-preview-canvas" });
            container.appendChild(this.#imagePreviewCanvas);
            this.#imagePreviewCtx = this.#imagePreviewCanvas.getContext('2d');

            container.appendChild(domMake.Tree("div", {}, ["Colores Dominantes (clic para añadir a Paletas):"]));
            this.#dominantColorsDisplay = domMake.Tree("div", { id: "dominant-colors-display" });
            container.appendChild(this.#dominantColorsDisplay);

            this.#analysisResultsDisplay = domMake.Tree("pre", { id: "analysis-results" }, ["Resultados del análisis aparecerán aquí."]);
            container.appendChild(this.#analysisResultsDisplay);

            // Action Buttons Row (Copy/Download)
            const actionButtonsRow = domMake.Row({ class: "action-buttons" });
            this.#copyResultsButton = domMake.Button('<i class="fas fa-copy"></i> Copiar');
            this.#copyResultsButton.addEventListener("click", () => this.#copyResultsToClipboard());
            actionButtonsRow.appendChild(this.#copyResultsButton);

            this.#downloadResultsButton = domMake.Button('<i class="fas fa-download"></i> Descargar');
            this.#downloadResultsButton.addEventListener("click", () => this.#downloadResults());
            actionButtonsRow.appendChild(this.#downloadResultsButton);
            container.appendChild(actionButtonsRow);


            this.htmlElements.section.appendChild(container);
        }

        async #loadImageAndAnalyze() {
            const imageUrl = this.#imageUrlInput.value.trim();
            if (!imageUrl) {
                this.notify("warning", "Por favor, introduce una URL de imagen.");
                return;
            }

            this.notify("info", "Cargando imagen para análisis...");
            this.#analysisResultsDisplay.textContent = "Cargando...";
            this.#dominantColorsDisplay.innerHTML = '';
            this.#imagePreviewCtx.clearRect(0, 0, this.#imagePreviewCanvas.width, this.#imagePreviewCanvas.height);
            this.#copyResultsButton.disabled = true;
            this.#downloadResultsButton.disabled = true;

            const img = new window.Image();
            img.crossOrigin = "Anonymous"; // Crucial for CORS
            img.src = imageUrl;

            img.onload = async () => {
                try {
                    const maxWidth = 300;
                    const maxHeight = 300;
                    let width = img.width;
                    let height = img.height;

                    if (width > maxWidth || height > maxHeight) {
                        if (width / maxWidth > height / maxHeight) {
                            height = height * (maxWidth / width);
                            width = maxWidth;
                        } else {
                            width = width * (maxHeight / height);
                            height = maxHeight;
                        }
                    }

                    this.#imagePreviewCanvas.width = width;
                    this.#imagePreviewCanvas.height = height;
                    this.#imagePreviewCtx.drawImage(img, 0, 0, width, height);

                    const imageData = this.#imagePreviewCtx.getImageData(0, 0, width, height);
                    const pixels = imageData.data;

                    const results = {};

                    const colorMap = new Map();
                    const sampleStep = Math.max(1, Math.floor(pixels.length / 4 / 5000));

                    for (let i = 0; i < pixels.length; i += 4 * sampleStep) {
                        const r = pixels[i];
                        const g = pixels[i + 1];
                        const b = pixels[i + 2];
                        const a = pixels[i + 3];

                        if (a > 20) {
                            const colorKey = `${r},${g},${b}`;
                            colorMap.set(colorKey, (colorMap.get(colorKey) || 0) + 1);
                        }
                    }

                    const sortedColors = Array.from(colorMap.entries())
                        .sort((a, b) => b[1] - a[1])
                        .slice(0, 5);

                    results.dominantColors = sortedColors.map(([rgbStr, count]) => {
                        const [r, g, b] = rgbStr.split(',').map(Number);
                        return { r, g, b, hex: `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}` };
                    });

                    this.#dominantColorsDisplay.innerHTML = '';
                    results.dominantColors.forEach(color => {
                        const swatch = domMake.Tree("div", {
                            class: "color-swatch",
                            style: `background-color: ${color.hex};`,
                            title: `Clic para añadir ${color.hex} a Paletas de Color.`
                        });
                        swatch.addEventListener('click', () => this.#addDominantColorToPalette(color.hex));
                        this.#dominantColorsDisplay.appendChild(swatch);
                    });

                    let totalBrightness = 0;
                    let nonTransparentPixelCount = 0;
                    for (let i = 0; i < pixels.length; i += 4) {
                        const r = pixels[i];
                        const g = pixels[i + 1];
                        const b = pixels[i + 2];
                        const a = pixels[i + 3];
                        if (a > 0) {
                            totalBrightness += (0.299 * r + 0.587 * g + 0.114 * b);
                            nonTransparentPixelCount++;
                        }
                    }
                    results.averageBrightness = nonTransparentPixelCount > 0 ? (totalBrightness / nonTransparentPixelCount).toFixed(2) : 'N/A';
                    results.imageDimensions = `${width}x${height}`;
                    results.totalPixels = width * height;
                    results.nonTransparentPixels = nonTransparentPixelCount;

                    let resultsText = "--- Resultados del Análisis de Imagen ---\n";
                    resultsText += `Dimensiones: ${results.imageDimensions} píxeles\n`;
                    resultsText += `Píxeles no transparentes: ${results.nonTransparentPixels}\n`;
                    resultsText += `Brillo promedio: ${results.averageBrightness}\n`;
                    resultsText += `Colores Dominantes (HEX): ${results.dominantColors.map(c => c.hex).join(', ')}\n`;
                    resultsText += "\n¡Análisis completado!";
                    this.#analysisResultsDisplay.textContent = resultsText;

                    this.notify("success", "Análisis de imagen completado.");
                    this.#copyResultsButton.disabled = false;
                    this.#downloadResultsButton.disabled = false;

                } catch (e) {
                    if (e.name === "SecurityError" || (e.message && e.message.includes("tainted"))) {
                        this.notify("error", "Error de CORS: No se pudo acceder a los píxeles de la imagen. La imagen debe estar en el mismo origen o permitir CORS.");
                        this.#analysisResultsDisplay.textContent = "Error: No se pudo leer la imagen debido a restricciones de seguridad (CORS).";
                    } else {
                        this.notify("error", `Error al analizar la imagen: ${e.message}`);
                        this.#analysisResultsDisplay.textContent = `Error: ${e.message}`;
                        console.error("Image analysis error:", e);
                    }
                }
            };

            img.onerror = () => {
                this.notify("error", "Fallo al cargar la imagen. ¿URL correcta o problema de red?");
                this.#analysisResultsDisplay.textContent = "Error: Fallo al cargar la imagen.";
            };
        }

        // --- Nuevas funciones de Utilidad ---
        async #copyResultsToClipboard() {
            const resultsText = this.#analysisResultsDisplay.textContent;
            if (!resultsText.trim()) {
                this.notify("warning", "No hay resultados para copiar.");
                return;
            }
            try {
                await navigator.clipboard.writeText(resultsText);
                this.notify("success", "Resultados copiados al portapapeles.");
            } catch (err) {
                this.notify("error", `Error al copiar: ${err.message}`);
                console.error("Copy to clipboard failed:", err);
            }
        }

        #downloadResults() {
            const resultsText = this.#analysisResultsDisplay.textContent;
            if (!resultsText.trim()) {
                this.notify("warning", "No hay resultados para descargar.");
                return;
            }

            const blob = new Blob([resultsText], { type: 'text/plain;charset=utf-8' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `drawaria_image_analysis_${Date.now()}.txt`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            this.notify("success", "Resultados descargados como TXT.");
        }

        #addDominantColorToPalette(hexColor) {
            const moreColorPalettesModule = this.findGlobal("MoreColorPalettes");
            if (moreColorPalettesModule && moreColorPalettesModule.siblings && moreColorPalettesModule.siblings.length > 0) {
                const paletteInstance = moreColorPalettesModule.siblings[0]; // Assuming first instance
                if (paletteInstance && typeof paletteInstance.addCustomColorFromExternal === 'function') {
                    paletteInstance.addCustomColorFromExternal(hexColor);
                    this.notify("info", `Color ${hexColor} enviado a 'Paletas de Color'.`);
                } else {
                    this.notify("warning", "El módulo 'Paletas de Color' no está listo o le falta la función para añadir colores.");
                }
            } else {
                this.notify("warning", "El módulo 'Paletas de Color' no se encontró o no está activo.");
            }
        }
    }
})("QBit");
// --- END Analyzer

// --- START RANDOM PROFILE
(function RandomProfileSelectorModule() {
    const QBit = globalThis[arguments[0]];

    QBit.Styles.addRules([
        `#${QBit.identifier} .random-profile-section {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 10px;
            background-color: var(--CE-bg_color);
        }`,
        `#${QBit.identifier} .random-profile-section-title {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--dark-blue-title);
            text-align: center;
        }`,
        `#${QBit.identifier} .profile-display-area {
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 10px;
            padding: 10px;
            border: 1px dashed var(--CE-color);
            border-radius: .25rem;
            margin-bottom: 10px;
            flex-wrap: wrap;
        }`,
        // Estilos para el canvas de avatar
        `#${QBit.identifier} #avatar-canvas {
            width: 150px;
            height: 150px;
            border-radius: 50%;
            border: 2px solid var(--info);
            object-fit: cover; /* Aunque es canvas, mantiene la intención visual */
            flex-shrink: 0;
            background-color: #f0f0f0; /* Fondo para cuando no hay imagen */
        }`,
        `#${QBit.identifier} .profile-display-info {
            flex-grow: 1;
            text-align: center;
            min-width: 150px;
            margin-top: 5px;
        }`,
        `#${QBit.identifier} .profile-display-name {
            font-weight: bold;
            font-size: 1.2em;
            color: var(--dark-blue-title);
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }`,
        `#${QBit.identifier} .profile-display-stats {
            font-size: 0.9em;
            color: var(--CE-color);
        }`,
        /* Geometry Dash style navigation buttons */
        `#${QBit.identifier} .gd-nav-button {
            border-radius: 8px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 1.8em;
            background-color: var(--secondary);
            color: var(--dark);
            cursor: pointer;
            border: 2px solid var(--CE-color);
            box-shadow: inset 0 2px 4px rgba(255,255,255,0.2), 0 2px 5px rgba(0,0,0,0.3);
            transition: transform 0.2s ease-in-out, background-color 0.2s ease, box-shadow 0.2s ease;
            flex-shrink: 0;
        }`,
        `#${QBit.identifier} .gd-nav-button:hover {
            background-color: var(--info);
            color: white;
            transform: translateY(-2px) scale(1.05);
            box-shadow: inset 0 2px 4px rgba(255,255,255,0.3), 0 5px 10px rgba(0,0,0,0.4);
        }`,
        `#${QBit.identifier} .gd-nav-button:active {
            transform: translateY(0) scale(1);
            box-shadow: inset 0 1px 2px rgba(0,0,0,0.3);
        }`,
        `#${QBit.identifier} .button-row .gd-nav-button {
            flex: none;
            margin: 0 5px;
        }`,
        `#${QBit.identifier} .button-row {
            display: flex;
            flex-wrap: wrap;
            gap: 5px;
            margin-bottom: 10px;
            justify-content: center;
        }`,
        `#${QBit.identifier} .button-row .btn {
            flex: 1 1 48%;
            min-width: 120px;
        }`,
        `#${QBit.identifier} .profile-details-display {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 10px;
            margin-top: 10px;
            background-color: var(--CE-bg_color);
            color: var(--CE-color);
            font-family: monospace;
            font-size: 0.9em;
            white-space: pre-wrap;
            max-height: 400px;
            overflow-y: auto;
            display: none; /* Controlled by #displayProfileDetails */
        }`,
        `#${QBit.identifier} .profile-details-loader {
            text-align: center;
            padding: 20px;
            color: var(--info);
        }`
    ]);

    class RandomProfileSelector extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        #playersData = [];
        #currentIndex = -1;
        #currentPage = 1;
        #totalPages = 1;

        // UI elements
        #avatarCanvas;
        #avatarCtx;
        #playerNameDisplay;
        #playerStatsDisplay;
        #prevButton;
        #nextButton;
        #downloadProfileButton;
        #openPlayerProfileButton;
        #openProfileDetailsButton;
        #randomPlayerButton;
        #loadingIndicator;
        #profileDetailsContent;
        #modalLoader;
        #analyzeAvatarButton;

        // Dependencies
        #playerProfileExtractorModule = null;
        #imageAnalyzerModule = null;

        constructor() {
            super("Selector de Perfil Aleatorio", '<i class="fas fa-user-circle"></i>');
            this.#onStartup();
        }

        async #onStartup() {
            this.#loadInterface();
            await this.#fetchScoreboardData();
            // Ensure modules are found after CubeEngine might have loaded them
            this.#playerProfileExtractorModule = this.findGlobal("PlayerProfileExtractor");
            this.#imageAnalyzerModule = this.findGlobal("ImageAnalyzer");
        }

        #loadInterface() {
            const container = domMake.Tree("div", { class: "random-profile-section" });

            container.appendChild(domMake.Tree("div", { class: "random-profile-section-title" }, ["Explorador de Perfiles"]));

            this.#loadingIndicator = domMake.Tree("div", { class: "profile-details-loader" }, ["Cargando datos del marcador..."]);
            container.appendChild(this.#loadingIndicator); // Append loading indicator directly

            const profileDisplayArea = domMake.Tree("div", { class: "profile-display-area" });

            this.#avatarCanvas = domMake.Tree("canvas", { id: "avatar-canvas", width: "150", height: "150" });
            this.#avatarCtx = this.#avatarCanvas.getContext('2d');

            this.#playerNameDisplay = domMake.Tree("div", { class: "profile-display-name" }, ["Nombre del Jugador"]);
            this.#playerStatsDisplay = domMake.Tree("div", { class: "profile-display-stats" }, ["Puntuación: N/A | Victorias: N/A"]);

            this.#prevButton = domMake.Button('<i class="fas fa-chevron-left"></i>', { class: "gd-nav-button" });
            this.#prevButton.addEventListener("click", () => this.#navigateProfile(-1));

            this.#nextButton = domMake.Button('<i class="fas fa-chevron-right"></i>', { class: "gd-nav-button" });
            this.#nextButton.addEventListener("click", () => this.#navigateProfile(1));

            profileDisplayArea.appendAll(this.#prevButton, domMake.Tree("div", { style: "display: flex; flex-direction: column; align-items: center;" }, [
                this.#avatarCanvas,
                this.#playerNameDisplay,
                this.#playerStatsDisplay
            ]), this.#nextButton);
            container.appendChild(profileDisplayArea);

            const actionButtonsRow1 = domMake.Row({ class: "button-row" });
            this.#downloadProfileButton = domMake.Button('<i class="fas fa-file-download"></i> Descargar Perfil (JSON)');
            this.#downloadProfileButton.addEventListener("click", () => this.#downloadProfileData());

            this.#openPlayerProfileButton = domMake.Button('<i class="fas fa-user"></i> Abrir Perfil de Jugador');
            this.#openPlayerProfileButton.addEventListener("click", () => this.#openPlayerProfilePage());
            actionButtonsRow1.appendAll(this.#downloadProfileButton, this.#openPlayerProfileButton);
            container.appendChild(actionButtonsRow1);

            const actionButtonsRow2 = domMake.Row({ class: "button-row" });
            this.#openProfileDetailsButton = domMake.Button('<i class="fas fa-info-circle"></i> Mostrar Detalles Completos');
            this.#openProfileDetailsButton.addEventListener("click", () => this.#displayProfileDetails());
            this.#randomPlayerButton = domMake.Button('<i class="fas fa-random"></i> Jugador al Azar');
            this.#randomPlayerButton.addEventListener("click", () => this.#selectRandomProfile());
            actionButtonsRow2.appendAll(this.#openProfileDetailsButton, this.#randomPlayerButton);
            container.appendChild(actionButtonsRow2);

            this.#modalLoader = domMake.Tree("div", { class: "profile-details-loader" }, ["Cargando datos del perfil extendidos..."]);
            this.#profileDetailsContent = domMake.Tree("pre", { class: "profile-details-display", style: "margin: 0;" });
            container.appendAll(this.#modalLoader, this.#profileDetailsContent);

            this.htmlElements.section.appendChild(container);

            this.#setInterfaceEnabled(false);
        }

        #setInterfaceEnabled(enabled) {
            this.#loadingIndicator.style.display = enabled ? 'none' : 'block';

            const mainContent = this.htmlElements.section.querySelector('.profile-display-area');
            const buttonRows = this.htmlElements.section.querySelectorAll('.button-row');

            if (mainContent) {
                mainContent.style.display = enabled ? 'flex' : 'none';
            }

            buttonRows.forEach(row => {
                row.style.display = enabled ? 'flex' : 'none';
            });

            if (this.#downloadProfileButton) this.#downloadProfileButton.disabled = !enabled;
            if (this.#openPlayerProfileButton) this.#openPlayerProfileButton.disabled = !enabled;
            if (this.#openProfileDetailsButton) this.#openProfileDetailsButton.disabled = !enabled;
            if (this.#randomPlayerButton) this.#randomPlayerButton.disabled = !enabled;
            if (this.#analyzeAvatarButton) this.#analyzeAvatarButton.disabled = !enabled;

            if (this.#prevButton) this.#prevButton.disabled = !enabled;
            if (this.#nextButton) this.#nextButton.disabled = !enabled;
        }

        async #fetchScoreboardData() {
            console.log("RandomProfileSelector: Obteniendo datos del marcador...");
            this.#setInterfaceEnabled(false);

            try {
                let allPlayers = [];
                // Fetch up to 5 pages of the scoreboard
                for (let page = 1; page <= 5; page++) {
                    this.#loadingIndicator.textContent = `Cargando datos del marcador (Página ${page})...`;
                    const url = `https://drawaria.online/scoreboards/?page=${page}`;
                    const response = await fetch(url);
                    if (!response.ok) {
                        if (response.status === 404) {
                            console.log(`RandomProfileSelector: Página de marcador ${page} no encontrada, deteniendo la carga.`);
                            break;
                        }
                        throw new Error(`HTTP error! status: ${response.status}`);
                    }
                    const html = await response.text();
                    const parser = new DOMParser();
                    const doc = parser.parseFromString(html, 'text/html');

                    // Select all rows with IDs r1 to r100, assuming a pattern
                    const rows = doc.querySelectorAll('table.table tbody tr[id^="r"]');

                    if (rows.length === 0) {
                        console.log(`RandomProfileSelector: No más jugadores encontrados en la página ${page}.`);
                        break; // Stop if a page returns no rows
                    }

                    rows.forEach(row => {
                        const rank = row.querySelector('th[scope="row"]')?.textContent.trim();
                        const playerId = row.dataset.avatarid; // Get data-avatarid from the row
                        const playerLink = row.querySelector('td:nth-child(2) a');
                        const playerName = playerLink?.textContent.trim();
                        const avatarUrl = playerLink?.querySelector('img')?.src;

                        const score = row.querySelector('td:nth-child(3)')?.textContent.trim();
                        const stars = row.querySelector('td:nth-child(4)')?.textContent.trim();
                        const wins = row.querySelector('td:nth-child(5)')?.textContent.trim();
                        const matches = row.querySelector('td:nth-child(6)')?.textContent.trim();
                        const guesses = row.querySelector('td:nth-child(7)')?.textContent.trim();
                        const avgGuessTime = row.querySelector('td:nth-child(8)')?.textContent.trim();

                        // Only add if essential data is present
                        if (playerId && playerName && avatarUrl) {
                            allPlayers.push({
                                rank: rank,
                                uid: playerId,
                                name: playerName,
                                avatarUrl: avatarUrl,
                                score: score,
                                stars: stars,
                                wins: wins,
                                matches: matches,
                                guesses: guesses,
                                avgGuessTime: avgGuessTime,
                                profileUrl: `https://drawaria.online/profile/?uid=${playerId}`
                            });
                        } else {
                            console.warn(`RandomProfileSelector: Faltan datos esenciales para la fila del jugador: ID=${playerId}, Nombre=${playerName}, Avatar=${avatarUrl}`);
                        }
                    });
                }

                if (allPlayers.length === 0) {
                    console.warn("RandomProfileSelector: No se encontraron jugadores en el marcador.");
                    this.#loadingIndicator.textContent = "No se encontraron jugadores.";
                    return;
                }

                this.#playersData = allPlayers;
                this.#setInterfaceEnabled(true);
                this.#currentIndex = 0;
                this.#updateProfileDisplay();

            } catch (error) {
                console.error(`RandomProfileSelector: Error al cargar datos del marcador: ${error.message}`);
                this.#loadingIndicator.textContent = `Error: ${error.message}`;
            }
        }

        async #updateProfileDisplay() {
            if (this.#playersData.length === 0) {
                this.#avatarCtx.clearRect(0, 0, this.#avatarCanvas.width, this.#avatarCanvas.height);
                this.#playerNameDisplay.textContent = "No hay datos de jugadores.";
                this.#playerStatsDisplay.textContent = "";
                this.#setInterfaceEnabled(false);
                this.#profileDetailsContent.style.display = 'none';
                return;
            }

            const player = this.#playersData[this.#currentIndex];
            this.#playerNameDisplay.textContent = player.name;
            this.#playerStatsDisplay.textContent = `Puntuación: ${player.score} | Victorias: ${player.wins} | Estrellas: ${player.stars}`;

            this.#avatarCtx.clearRect(0, 0, this.#avatarCanvas.width, this.#avatarCanvas.height);
            const img = new Image();
            img.crossOrigin = "Anonymous";
            img.onload = () => {
                this.#avatarCtx.drawImage(img, 0, 0, this.#avatarCanvas.width, this.#avatarCanvas.height);
            };
            img.onerror = () => {
                console.warn(`RandomProfileSelector: No se pudo cargar el avatar para ${player.name}.`);
                this.#avatarCtx.fillStyle = '#ccc';
                this.#avatarCtx.fillRect(0, 0, this.#avatarCanvas.width, this.#avatarCanvas.height);
                this.#avatarCtx.font = '10px Arial';
                this.#avatarCtx.fillStyle = '#666';
                this.#avatarCtx.textAlign = 'center';
                this.#avatarCtx.fillText('No Avatar', this.#avatarCanvas.width / 2, this.#avatarCanvas.height / 2);
            };
            img.src = player.avatarUrl;

            this.#prevButton.disabled = this.#currentIndex === 0;
            this.#nextButton.disabled = this.#currentIndex === this.#playersData.length - 1;

            this.#profileDetailsContent.textContent = '';
            this.#profileDetailsContent.style.display = 'none';
        }

        #navigateProfile(direction) {
            this.#currentIndex += direction;
            if (this.#currentIndex < 0) {
                this.#currentIndex = 0;
            } else if (this.#currentIndex >= this.#playersData.length) {
                this.#currentIndex = this.#playersData.length - 1;
            }
            this.#updateProfileDisplay();
            console.log(`RandomProfileSelector: Mostrando perfil: ${this.#playersData[this.#currentIndex].name}`);
        }

        #selectRandomProfile() {
            if (this.#playersData.length === 0) {
                console.warn("RandomProfileSelector: No hay jugadores cargados para seleccionar al azar.");
                return;
            }
            const randomIndex = Math.floor(Math.random() * this.#playersData.length);
            this.#currentIndex = randomIndex;
            this.#updateProfileDisplay();
            console.log(`RandomProfileSelector: Jugador al azar seleccionado: ${this.#playersData[this.#currentIndex].name}`);
        }

        #downloadProfileData() {
            if (this.#playersData.length === 0 || this.#currentIndex === -1) {
                console.warn("RandomProfileSelector: No hay perfil seleccionado para descargar.");
                return;
            }
            const player = this.#playersData[this.#currentIndex];
            const dataStr = JSON.stringify(player, null, 2);
            const blob = new Blob([dataStr], { type: 'application/json' });
            const url = URL.createObjectURL(blob);
            const filename = `drawaria_player_${player.name.replace(/[^a-zA-Z0-9]/g, '_')}_${player.uid.substring(0, 8)}.json`;

            const a = document.createElement('a');
            a.href = url;
            a.download = filename;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            console.log(`RandomProfileSelector: Datos de ${player.name} descargados.`);
        }

        #openPlayerProfilePage() {
            if (this.#playersData.length === 0 || this.#currentIndex === -1) {
                console.warn("RandomProfileSelector: No hay perfil seleccionado para abrir.");
                return;
            }
            const player = this.#playersData[this.#currentIndex];
            window.open(player.profileUrl, '_blank');
            console.log(`RandomProfileSelector: Abriendo perfil de ${player.name} en una nueva pestaña.`);
        }

        async #displayProfileDetails() {
            if (this.#playersData.length === 0 || this.#currentIndex === -1) {
                console.warn("RandomProfileSelector: No hay perfil seleccionado para mostrar detalles.");
                this.#profileDetailsContent.style.display = 'none';
                return;
            }

            const player = this.#playersData[this.#currentIndex];
            const profileUrl = player.profileUrl;

            this.#profileDetailsContent.textContent = '';
            this.#profileDetailsContent.style.display = 'none';
            this.#modalLoader.style.display = 'block';

            console.log(`RandomProfileSelector: Cargando detalles completos para ${player.name}...`);

            if (!this.#playerProfileExtractorModule || !this.#playerProfileExtractorModule.siblings || this.#playerProfileExtractorModule.siblings.length === 0) {
                console.error("RandomProfileSelector: El módulo 'Extractor de Perfil' no está activo. No se pueden cargar detalles extendidos.");
                this.#profileDetailsContent.textContent = "Error: El módulo Extractor de Perfil no está cargado. Asegúrate de que está habilitado.";
                this.#modalLoader.style.display = 'none';
                this.#profileDetailsContent.style.display = 'block';
                return;
            }

            const extractorInstance = this.#playerProfileExtractorModule.siblings[0];

            if (typeof extractorInstance._parseMainProfileHTML !== 'function') {
                console.error("RandomProfileSelector: El módulo Extractor de Perfil no tiene el método de parseo necesario. Puede que esté desactualizado o modificado incorrectamente.");
                this.#profileDetailsContent.textContent = "Error: El módulo Extractor de Perfil no está completamente funcional.";
                this.#modalLoader.style.display = 'none';
                this.#profileDetailsContent.style.display = 'block';
                return;
            }

            try {
                const response = await fetch(profileUrl);
                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }
                const htmlContent = await response.text();
                const parser = new DOMParser();
                const extractedData = extractorInstance._parseMainProfileHTML(htmlContent, parser);

                const combinedData = {
                    ...player,
                    ...extractedData,
                };

                let displayTxt = `--- Detalles Completos del Perfil de ${combinedData.name} ---\n`;
                displayTxt += `UID: ${combinedData.uid || 'N/A'}\n`;
                displayTxt += `Nombre: ${combinedData.name || 'N/A'}\n`;
                displayTxt += `Avatar URL: ${combinedData.avatarUrl || 'N/A'}\n`;
                displayTxt += `Rank: ${combinedData.rank || 'N/A'}\n`;
                displayTxt += `Experiencia: ${combinedData.experience || 'N/A'}\n`;

                displayTxt += `--- Estadísticas de Juego ---\n`;
                displayTxt += `Puntuación Total: ${combinedData.score || 'N/A'}\n`;
                displayTxt += `Estrellas: ${combinedData.stars || 'N/A'}\n`;
                displayTxt += `Victorias: ${combinedData.wins || 'N/A'}\n`;
                displayTxt += `Partidas Jugadas: ${combinedData.matches || 'N/A'}\n`;
                displayTxt += `Adivinanzas Correctas: ${combinedData.guesses || 'N/A'}\n`;
                displayTxt += `Tiempo Promedio de Adivinanza: ${combinedData.avgGuessTime || 'N/A'}\n`;

                if (combinedData.pictionaryStats && Object.keys(combinedData.pictionaryStats).length > 0) {
                    displayTxt += `\n--- Estadísticas Detalladas (Pictionary) ---\n`;
                    for (const key in combinedData.pictionaryStats) {
                        const displayKey = key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase());
                        displayTxt += `  ${displayKey}: ${combinedData.pictionaryStats[key]}\n`;
                    }
                }

                if (combinedData.accusedTokens && Object.keys(combinedData.accusedTokens).length > 0) {
                    displayTxt += `\n--- Menciones de Homenaje (Tokens) ---\n`;
                    for (const tokenName in combinedData.accusedTokens) {
                        displayTxt += `  ${tokenName}: ${combinedData.accusedTokens[tokenName]}\n`;
                    }
                }

                this.#profileDetailsContent.textContent = displayTxt;
                console.log(`RandomProfileSelector: Detalles completos de ${player.name} cargados.`);

            } catch (error) {
                console.error(`RandomProfileSelector: Error al cargar detalles de ${player.name}: ${error.message}`);
                this.#profileDetailsContent.textContent = `Error al cargar detalles: ${error.message}.`;
            } finally {
                this.#modalLoader.style.display = 'none';
                this.#profileDetailsContent.style.display = 'block';
            }
        }

        async #analyzeCurrentAvatar() {
            if (this.#playersData.length === 0 || this.#currentIndex === -1) {
                console.warn("RandomProfileSelector: No hay avatar seleccionado para analizar.");
                return;
            }

            const player = this.#playersData[this.#currentIndex];
            const avatarUrl = player.avatarUrl;

            if (!this.#imageAnalyzerModule || !this.#imageAnalyzerModule.siblings || this.#imageAnalyzerModule.siblings.length === 0) {
                console.error("RandomProfileSelector: El módulo 'Analizador de Imágenes' no está activo. No se puede analizar el avatar.");
                return;
            }

            const analyzerInstance = this.#imageAnalyzerModule.siblings[0];
            if (typeof analyzerInstance.performAnalysisFromExternalUrl === 'function') {
                console.log(`RandomProfileSelector: Enviando avatar de ${player.name} al Analizador de Imágenes...`);
                analyzerInstance.performAnalysisFromExternalUrl(avatarUrl);
            } else {
                console.error("RandomProfileSelector: El módulo 'Analizador de Imágenes' no tiene el método de análisis requerido (performAnalysisFromExternalUrl).");
            }
        }
    }
})("QBit");

// --- END RANDOM PROFILE

// START NAVIGATOR

(function RoomNavigatorModule() {
    const QBit = globalThis[arguments[0]]; // Corrected access to QBit
// Helper to fetch JSON (re-declaring if not globally accessible or just for clarity)
function fetchJson(url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        xhr.onload = () => {
            if (xhr.status >= 200 && xhr.status < 300) {
                try {
                    let parsedData = JSON.parse(xhr.responseText);
                    // Handle potential double-encoded JSON
                    if (typeof parsedData === 'string') {
                        parsedData = JSON.parse(parsedData);
                    }
                    resolve(parsedData);
                } catch (e) {
                    reject(new Error('Fallo al analizar la respuesta JSON: ' + e.message + ` (Raw: ${xhr.responseText.substring(0, 100)}...)`));
                }
            } else {
                reject(new Error(`La respuesta de la red no fue correcta, estado: ${xhr.status} ${xhr.statusText}`));
            }
        };
        xhr.onerror = () => reject(new Error('Fallo la solicitud de red. Revisa tu conexión o el servidor.'));
        xhr.send();
    });
}

class RoomNavigator extends QBit {
    static dummy1 = QBit.register(this);
    static dummy2 = QBit.bind(this, "CubeEngine");

    #roomListContainer;
    #refreshButton;
    #loadingIndicator;
    #filterNameInput;
    #filterMinPlayersInput;
    #filterMaxPlayersInput;
    #roomDataCache = []; // Store fetched raw room data

    constructor() {
        super("Navegador de Salas", '<i class="fas fa-search-location"></i>');
        this.#onStartup();
    }

    #onStartup() {
        this.#loadInterface();
        this.#fetchAndApplyFilters(); // Initial fetch and display
    }

    #loadInterface() {
        const container = domMake.Tree("div");

        // Refresh and Loading Row
        const headerRow = domMake.Row();
        headerRow.style.marginBottom = "10px";

        this.#refreshButton = domMake.Button('<i class="fas fa-sync-alt"></i> Actualizar Salas');
        this.#refreshButton.title = "Actualiza la lista de salas disponibles.";
        this.#refreshButton.addEventListener("click", () => this.#fetchAndApplyFilters());
        headerRow.appendChild(this.#refreshButton);

        this.#loadingIndicator = domMake.Tree("span", { style: "margin-left: 10px; color: var(--info); display: none;" }, ['Cargando...']);
        headerRow.appendChild(this.#loadingIndicator);
        container.appendChild(headerRow);

        // Filters Row
        const filtersRow = domMake.Row();
        filtersRow.style.flexWrap = "wrap";
        filtersRow.style.gap = "5px";

        this.#filterNameInput = domMake.Tree("input", { type: "text", placeholder: "Filtrar por nombre", style: "flex: 1 1 120px;" });
        this.#filterNameInput.addEventListener("input", () => this.#applyFiltersAndDisplayRooms());
        filtersRow.appendChild(this.#filterNameInput);

        this.#filterMinPlayersInput = domMake.Tree("input", { type: "number", min: "0", placeholder: "Min Jugadores", style: "width: 80px;" });
        this.#filterMinPlayersInput.addEventListener("input", () => this.#applyFiltersAndDisplayRooms());
        filtersRow.appendChild(this.#filterMinPlayersInput);

        this.#filterMaxPlayersInput = domMake.Tree("input", { type: "number", min: "0", placeholder: "Max Jugadores", style: "width: 80px;" });
        this.#filterMaxPlayersInput.addEventListener("input", () => this.#applyFiltersAndDisplayRooms());
        filtersRow.appendChild(this.#filterMaxPlayersInput);

        container.appendChild(filtersRow);

        // Room List Display Area
        this.#roomListContainer = domMake.Tree("div", {
            class: "room-list-display",
            style: `
                display: grid;
                grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
                gap: 10px;
                padding: 5px;
                max-height: 400px; /* Limits height and adds scrollbar */
                overflow-y: auto;
                border: 1px solid var(--CE-color);
                border-radius: .25rem;
                margin-top: 10px;
            `
        });
        container.appendChild(this.#roomListContainer);

        this.htmlElements.section.appendChild(container);
    }

    async #fetchAndApplyFilters() {
        this.#roomListContainer.innerHTML = '';
        this.#loadingIndicator.style.display = 'inline';
        this.#refreshButton.disabled = true;

        try {
            const roomData = await fetchJson('https://drawaria.online/getroomlist');
            if (!Array.isArray(roomData)) {
                 throw new Error('La respuesta no es un array de salas válido.');
            }
            this.#roomDataCache = roomData; // Cache the raw data
            this.notify("info", `Se encontraron ${roomData.length} salas.`);
            this.#applyFiltersAndDisplayRooms();
        } catch (error) {
            this.notify("error", `Error al cargar la lista de salas: ${error.message}`);
            this.#roomListContainer.appendChild(domMake.TextNode("Error al cargar las salas. Inténtalo de nuevo."));
            console.error("Fetch room list error:", error);
        } finally {
            this.#loadingIndicator.style.display = 'none';
            this.#refreshButton.disabled = false;
        }
    }

    #applyFiltersAndDisplayRooms() {
        let filteredRooms = [...this.#roomDataCache]; // Start with cached data

        const nameFilter = this.#filterNameInput.value.toLowerCase();
        const minPlayers = parseInt(this.#filterMinPlayersInput.value);
        const maxPlayers = parseInt(this.#filterMaxPlayersInput.value);

        if (nameFilter) {
            filteredRooms = filteredRooms.filter(room => {
                const roomName = room[3] ? room[3].toLowerCase() : ''; // Access roomName by index 3
                return roomName.includes(nameFilter);
            });
        }

        if (!isNaN(minPlayers) && minPlayers >= 0) {
            filteredRooms = filteredRooms.filter(room => room[1] >= minPlayers); // Access currentPlayers by index 1
        }

        if (!isNaN(maxPlayers) && maxPlayers >= 0) {
            filteredRooms = filteredRooms.filter(room => room[1] <= maxPlayers); // Access currentPlayers by index 1
        }

        this.#displayRooms(filteredRooms);
    }


    #displayRooms(rooms) {
        this.#roomListContainer.innerHTML = '';
        if (rooms.length === 0) {
            this.#roomListContainer.appendChild(domMake.TextNode("No hay salas que coincidan con los filtros."));
            return;
        }

        rooms.forEach(roomArray => {
            // Structure observed from the provided JSON example:
            // [0] roomId: string (e.g., "f0e1c44b-fc49-4160-91c9-b297fb662692" or "f49bf487-e96a-45a9-9616-c4b71662ac50.3")
            // [1] currentPlayers: number
            // [2] maxPlayers: number
            // [3] roomName: string (e.g., "жду друзей", "sos :)", or "")
            // [4] gameModeType: number (2 for public, 3 for private/friend/custom)
            // [5] unknown_number: number (e.g., 273052, 4176 - seems like a server-side counter or ID)
            // [6] flags_array: Array (e.g., [null,true,null,null,null,true] or [null,true])
            //      - flags_array[0]: boolean (possibly password protected if true)
            //      - flags_array[1]: boolean (this flag seems always true in sample, not a public/private indicator)
            // [7] roundsPlayed: number
            // [8] serverId: number or null (e.g., 5 or null if main server, part of room ID for specific servers)

            const roomId = roomArray[0];
            const currentPlayers = roomArray[1];
            const maxPlayers = roomArray[2];
            const roomName = roomArray[3];
            const gameModeType = roomArray[4];
            const flags = roomArray[6]; // Flags are at index 6
            const roundsPlayed = roomArray[7];
            const serverId = roomArray[8];

            let roomModeLabel = 'Desconocido';
            if (gameModeType === 2) {
                roomModeLabel = 'Público';
            } else if (gameModeType === 3) {
                roomModeLabel = 'Amigos/Privado';
            }

            // Access flags_array[0] for password protected status
            const isPasswordProtected = flags && flags.length > 0 && flags[0] === true;

            const isFull = currentPlayers >= maxPlayers;
            const statusColor = isFull ? 'var(--danger)' : 'var(--success)';
            const joinableStatus = isFull ? 'LLENA' : 'DISPONIBLE';

            const roomCard = domMake.Tree("div", {
                class: "room-card",
                style: `
                    border: 1px solid var(--CE-color);
                    border-radius: .25rem;
                    padding: 8px;
                    background-color: var(--CE-bg_color);
                    display: flex;
                    flex-direction: column;
                    justify-content: space-between;
                    gap: 5px;
                    font-size: 0.85em;
                    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
                `
            });

            const nodesToAppend = [
                domMake.Tree("div", { style: "font-weight: bold; color: var(--dark-blue-title); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;", title: roomName }, [`Sala: ${roomName || '(Sin Nombre)'}`]),
                domMake.Tree("div", {}, [`Jugadores: ${currentPlayers}/${maxPlayers}`]),
                domMake.Tree("div", {}, [`Rondas jugadas: ${roundsPlayed || 'N/A'}`]),
                domMake.Tree("div", {}, [`Modo: ${roomModeLabel}`]),
                isPasswordProtected ? domMake.Tree("div", {style: "color: var(--warning);"}, [`Contraseña: Sí`]) : null,
                domMake.Tree("div", { style: `color: ${statusColor}; font-weight: bold;` }, [`Estado: ${joinableStatus}`])
            ].filter(node => node instanceof Node);

            roomCard.appendAll(...nodesToAppend);

            const joinButton = domMake.Button("Unirse");
            joinButton.classList.add("btn-primary");
            joinButton.style.width = "100%";
            joinButton.style.marginTop = "5px";
            // joinButton.disabled = isFull; // REMOVED: Allow joining even if full
            joinButton.addEventListener("click", () => this.#joinRoom(roomId, serverId));
            roomCard.appendChild(joinButton);

            this.#roomListContainer.appendChild(roomCard);
        });
    }

    #joinRoom(roomId, serverId) {
        let fullRoomIdentifier = roomId;
        // Construct full room ID, e.g., "uuid.serverId"
        if (serverId !== null && serverId !== undefined && !String(roomId).includes(`.${serverId}`)) {
            fullRoomIdentifier = `${roomId}.${serverId}`;
        }
        const roomUrl = `https://drawaria.online/room/${fullRoomIdentifier}`;
        window.location.assign(roomUrl); // Navigate to the room
    }
}

})("QBit");

// END NAVIGATOR

// START CMD

(function UserInterfaceCMDSysyem() { // Renamed the module
    const QBit = globalThis[arguments[0]];

    // Store original jQuery functions (moved here to be accessible within the module scope)
    let _originalJQueryFnModal;
    let _originalJQueryFnPopover;
    let _originalJQueryFnDropdown; // For Bootstrap dropdowns
    let _originalJQueryFnCollapse;

    // Combined Styles for both functionalities
    QBit.Styles.addRules([
        // Styles for Client Command Sender part
        `#${QBit.identifier} .cmd-center-section {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 10px;
            background-color: var(--CE-bg_color);
            margin-top: 10px; /* Add some space from the previous section */
        }`,
        `#${QBit.identifier} .cmd-center-title {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--dark-blue-title);
            text-align: center;
        }`,
        `#${QBit.identifier} .cmd-center-links {
            display: flex;
            flex-direction: column;
            gap: 5px;
        }`,
        `#${QBit.identifier} .cmd-center-links .btn {
            width: 100%;
            padding: 8px 15px;
            box-sizing: border-box;
            font-size: 0.95em;
            display: flex;
            align-items: center;
            justify-content: center;
        }`,
        `#${QBit.identifier} .cmd-center-links .btn i {
            margin-right: 8px;
        }`,
        `#${QBit.identifier} .client-cmd-controls input[type="number"],
         #${QBit.identifier} .client-cmd-controls input[type="text"] {
            width: 100%;
            padding: 5px;
            box-sizing: border-box;
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            background-color: var(--CE-bg_color);
            color: var(--CE-color);
        }`,
        `#${QBit.identifier} .client-cmd-controls .btn {
            width: 100%;
            padding: 5px;
        }`,

        // Styles for UI Persistence part
        `#${QBit.identifier} .ui-persistence-section {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 10px;
            background-color: var(--CE-bg_color);
        }`,
        `#${QBit.identifier} .ui-persistence-section-title {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--dark-blue-title);
            text-align: center;
        }`,
        `#${QBit.identifier} .ui-persistence-toggle-button {
            background-color: var(--secondary);
            color: var(--dark);
            width: 100%;
            min-width: unset;
            padding: 10px 15px;
            box-sizing: border-box;
            font-size: 1rem;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            cursor: pointer;
            text-align: center;
            flex: none;
            min-height: 70px;
        }`,
        `#${QBit.identifier} .ui-persistence-toggle-button i {
            margin-right: 0;
            margin-bottom: 5px;
            font-size: 1.5em;
        }`,
        `#${QBit.identifier} .ui-persistence-toggle-button.active {
            background-color: var(--info);
            color: white;
        }`,
        `#${QBit.identifier} .ui-persistence-toggle-button.active i {
            color: #fff;
        }`,
        `#${QBit.identifier} .ui-persistence-element-list {
            display: flex;
            flex-direction: column;
            flex-wrap: nowrap;
            gap: 10px;
            margin-top: 5px;
            justify-content: flex-start;
            align-items: stretch;
        }`,
    ]);

    class UserInterfaceCMDSysyem extends QBit { // Renamed class
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        // Properties from ClientCommandSender
        _cmdIdInput;
        _param1Input;
        _param2Input;
        _param3Input;

        // Properties from UIPersistenceModule
        _persistentElements = new Map();
        _chatattopNewMessageElement;
        _drawControlsPopuplistElement;
        _friendsTabFriendlistElement;
        _friendsContainerElement;
        // _cubeEngineMenuObserver; // Not used as Global Toggle is removed

        constructor() {
            super("User Interface CMD System", '<i class="fas fa-desktop"></i>'); // New name and icon
            this._onStartup();
        }

        _onStartup() {
            // UI Persistence: Capture original jQuery functions and find elements early
            this._captureOriginalFunctions();
            this._findSpecificElements(); // Ensure elements are found before patching/observing
            this._patchJQueryFunctions();

            // Load the combined UI
            this._loadInterface();

            // UI Persistence: Setup observers after UI is loaded
            this._setupObservers();
        }

        _loadInterface() {
            const container = domMake.Tree("div");

            // --- Section: Client Command Sender Controls ---
            const clientCmdSection = domMake.Tree("div", { class: "cmd-center-section" }); // Reuse section style
            clientCmdSection.appendChild(domMake.Tree("div", { class: "cmd-center-title" }, ["Enviar Client Cmd"]));
            clientCmdSection.classList.add("client-cmd-controls"); // Add specific class for inputs

            const row1 = domMake.Row();
            this._cmdIdInput = domMake.Tree("input", { type: "number", placeholder: "Command ID (e.g., 10)" });
            row1.appendChild(this._cmdIdInput);
            clientCmdSection.appendChild(row1);

            const row2 = domMake.Row();
            this._param1Input = domMake.Tree("input", { type: "text", placeholder: "Param 1 (e.g., true)" });
            this._param2Input = domMake.Tree("input", { type: "text", placeholder: "Param 2 (e.g., 1)" });
            row2.appendAll(this._param1Input, this._param2Input);
            clientCmdSection.appendChild(row2);

            const row3 = domMake.Row();
            this._param3Input = domMake.Tree("input", { type: "text", placeholder: "Param 3 (e.g., 'text')" });
            const sendButton = domMake.Button("Send CMD");
            sendButton.addEventListener("click", () => this.sendCommand());
            row3.appendAll(this._param3Input, sendButton);
            clientCmdSection.appendChild(row3);

            container.appendChild(clientCmdSection);

            // --- Section: CMD Center (Resources) ---
            const cmdCenterSection = domMake.Tree("div", { class: "cmd-center-section" });
            cmdCenterSection.appendChild(domMake.Tree("div", { class: "cmd-center-title" }, ["CMD Center (Recursos)"]));
            cmdCenterSection.appendChild(domMake.Tree("div", { style: "font-size: 0.85em; margin-bottom: 10px; text-align: center;" }, ["Haz clic para abrir recursos de comandos y propiedades del juego en una nueva pestaña."]));

            const linksContainer = domMake.Tree("div", { class: "cmd-center-links" });

            const listCmdButton = domMake.Button('<i class="fas fa-list"></i> List Command');
            listCmdButton.title = "Abre una lista de comandos comunes de Drawaria.";
            listCmdButton.addEventListener("click", () => this._openExternalLink('https://raw.githubusercontent.com/NuevoMundoOficial/DrawariaWordList/main/list%20command.txt', 'Comandos de Drawaria'));
            linksContainer.appendChild(listCmdButton);

            const dataCenterButton = domMake.Button('<i class="fas fa-database"></i> Data Center (Propiedades del Juego)');
            dataCenterButton.title = "Abre un listado de propiedades y datos internos del juego.";
            dataCenterButton.addEventListener("click", () => this._openExternalLink('https://raw.githubusercontent.com/NuevoMundoOficial/DrawariaWordList/main/data%20center.txt', 'Propiedades de Juego Drawaria'));
            linksContainer.appendChild(dataCenterButton);

            const linksCenterButton = domMake.Button('<i class="fas fa-link"></i> Links Center (Enlaces del Juego)');
            linksCenterButton.title = "Abre una lista de enlaces útiles relacionados con el juego.";
            linksCenterButton.addEventListener("click", () => this._openExternalLink('https://raw.githubusercontent.com/NuevoMundoOficial/DrawariaWordList/main/links%20center.txt', 'Enlaces Útiles Drawaria'));
            linksContainer.appendChild(linksCenterButton);

            cmdCenterSection.appendChild(linksContainer);
            container.appendChild(cmdCenterSection);

            // --- Section: UI Persistence Controls ---
            const uiPersistenceSection = domMake.Tree("div", { class: "ui-persistence-section" });
            uiPersistenceSection.appendChild(domMake.Tree("div", { class: "ui-persistence-section-title" }, ["Control de Visibilidad de UI"]));

            const elementList = domMake.Tree("div", { class: "ui-persistence-element-list" });

            const addToggleButton = (id, labelHtml, iconClass) => {
                const button = domMake.Button('');
                button.classList.add("ui-persistence-toggle-button");
                button.setAttribute("data-persistence-id", id);
                button.setAttribute("data-original-label", labelHtml);

                const icon = domMake.Tree("i");
                icon.className = `fas ${iconClass}`;
                const labelSpan = domMake.Tree("span");
                labelSpan.innerHTML = labelHtml;

                button.appendChild(icon);
                button.appendChild(labelSpan);

                if (this._persistentElements.has(id) && this._persistentElements.get(id) === true) {
                    button.classList.add("active");
                    labelSpan.innerHTML = labelHtml.replace('Mantener', 'Liberar');
                }
                button.addEventListener("click", (e) => this._toggleElementPersistence(id, button, labelHtml));
                elementList.appendChild(button);
                return button;
            };

            addToggleButton('all-popovers', 'Mantener Todos los<br>Popovers', 'fa-comment-dots');
            addToggleButton('all-dropdowns', 'Mantener Todos los<br>Dropdowns', 'fa-caret-square-down');
            addToggleButton('draw-controls-popup', 'Mantener Panel de<br>Pincel', 'fa-paint-brush');
            addToggleButton('chat-new-message-notification', 'Mantener Notif. Chat<br>Nueva', 'fa-bell');
            addToggleButton('top-messages', 'Mantener Mensajes<br>Superiores', 'fa-comment-alt');
            addToggleButton('friends-tabfriendlist', 'Mantener Lista de<br>Amigos', 'fa-user-friends');

            uiPersistenceSection.appendChild(elementList);
            container.appendChild(uiPersistenceSection);

            this.htmlElements.section.appendChild(container);
        }

        // Methods from ClientCommandSender
        parseParam(paramStr) {
            if (paramStr.toLowerCase() === 'true') return true;
            if (paramStr.toLowerCase() === 'false') return false;
            if (!isNaN(paramStr) && paramStr.trim() !== '') return Number(paramStr);
            if (paramStr.startsWith('[') && paramStr.endsWith(']')) {
                try {
                    return JSON.parse(paramStr); // For arrays
                } catch (e) {
                    return paramStr;
                }
            }
            if (paramStr.startsWith('{') && paramStr.endsWith('}')) {
                try {
                    return JSON.parse(paramStr); // For objects
                } catch (e) {
                    return paramStr;
                }
            }
            return paramStr; // Default to string
        }

        sendCommand() {
            const cmdId = parseInt(this._cmdIdInput.value);
            if (isNaN(cmdId)) {
                this.notify("warning", "Command ID must be a number.");
                return;
            }

            const params = [];
            const param1 = this._param1Input.value.trim();
            const param2 = this._param2Input.value.trim();
            const param3 = this._param3Input.value.trim();

            if (param1) params.push(this.parseParam(param1));
            if (param2) params.push(this.parseParam(param2));
            if (param3) params.push(this.parseParam(param3));

            if (globalThis.sockets && globalThis.sockets.length > 0) {
                const payload = ["clientcmd", cmdId, params];
                const dataToSend = `${42}${JSON.stringify(payload)}`;

                globalThis.sockets[0].send(dataToSend);
                this.notify("info", `Custom clientcmd ${cmdId} sent with params: ${JSON.stringify(params)}.`);
            } else {
                this.notify("warning", "No active WebSocket connection found.");
            }
        }

        _openExternalLink(url, title) {
            try {
                const newWindow = window.open(url, '_blank');
                if (newWindow) {
                    this.notify("success", `Abriendo ${title} en una nueva pestaña.`);
                } else {
                    this.notify("warning", "No se pudo abrir la nueva pestaña. Asegúrate de que los bloqueadores de pop-ups estén desactivados.");
                }
            } catch (error) {
                this.notify("error", `Error al intentar abrir el enlace para ${title}: ${error.message}`);
                console.error(`Error opening external link for ${title}:`, error);
            }
        }

        // Methods from UIPersistenceModule
        _captureOriginalFunctions() {
            if (typeof jQuery !== 'undefined' && jQuery.fn) {
                _originalJQueryFnModal = jQuery.fn.modal;
                _originalJQueryFnPopover = jQuery.fn.popover;
                _originalJQueryFnDropdown = jQuery.fn.dropdown;
                _originalJQueryFnCollapse = jQuery.fn.collapse;
            } else {
                this.notify("error", "jQuery o sus plugins Bootstrap no están disponibles. La persistencia de UI puede no funcionar.");
            }
        }

        _patchJQueryFunctions() {
            const self = this;

            if (_originalJQueryFnModal) {
                jQuery.fn.modal = function(action, ...args) {
                    if (action === 'hide' && self._isPersistent(this, 'all-modals')) {
                        self.notify('info', `[UI Persistencia] Bloqueando intento de ocultar modal: #${this.attr('id') || this.attr('class')}.`);
                        if (this.hasClass('show')) {
                            self._forceVisibility(this);
                            const backdrop = jQuery('.modal-backdrop');
                            if (backdrop.length) {
                                backdrop.off('click.dismiss.bs.modal');
                                self._forceVisibility(backdrop);
                            }
                        }
                        return this;
                    }
                    return _originalJQueryFnModal.apply(this, [action, ...args]);
                };
            }

            if (_originalJQueryFnPopover) {
                jQuery.fn.popover = function(action, ...args) {
                    if (typeof action === 'string' && (action === 'hide' || action === 'destroy') && self._isPersistent(this, 'all-popovers')) {
                        self.notify('info', `[UI Persistencia] Bloqueando intento de ocultar popover: ${this.attr('aria-describedby') || this.attr('title')}.`);
                        self._forceVisibility(jQuery(this.attr('aria-describedby') ? `#${this.attr('aria-describedby')}` : this));
                        return this;
                    }
                    return _originalJQueryFnPopover.apply(this, [action, ...args]);
                };
            }

            if (_originalJQueryFnDropdown) {
                jQuery.fn.dropdown = function(action, ...args) {
                    if (typeof action === 'string' && (action === 'hide' || (action === 'toggle' && this.hasClass('show'))) && self._isPersistent(this, 'all-dropdowns')) {
                        self.notify('info', `[UI Persistencia] Bloqueando intento de ocultar dropdown: ${this.attr('id') || this.attr('class')}.`);
                        const menuId = this.attr('aria-labelledby') || this.next('.dropdown-menu').attr('id');
                        self._forceVisibility(jQuery(`#${menuId}, .${menuId}`));
                        return this;
                    }
                    return _originalJQueryFnDropdown.apply(this, [action, ...args]);
                };
            }

            if (_originalJQueryFnCollapse) {
                jQuery.fn.collapse = function(action, ...args) {
                    if (this.is(self._friendsContainerElement) && self._isPersistent(this, 'friends-tabfriendlist')) {
                        if (action === 'hide') {
                            self.notify('info', '[UI Persistencia] Bloqueando intento de ocultar el panel de amigos.');
                            self._forceVisibility(this);
                            return this;
                        }
                    }
                    return _originalJQueryFnCollapse.apply(this, [action, ...args]);
                };
            }
        }

        _findSpecificElements() {
            this._chatattopNewMessageElement = document.getElementById('chatattop-newmessage');
            this._drawControlsPopuplistElement = document.querySelector('.drawcontrols-popuplist');
            this._friendsTabFriendlistElement = document.getElementById('friends-tabfriendlist');
            this._friendsContainerElement = document.getElementById('friends-container');
        }

        _setupObservers() {
            const self = this;

            if (this._drawControlsPopuplistElement) {
                jQuery(this._drawControlsPopuplistElement).off('focusout.persistence').on('focusout.persistence', function(e) {
                    if (self._isPersistent(this, 'draw-controls-popup')) {
                        e.stopImmediatePropagation();
                        self.notify('info', '[UI Persistencia] Previniendo focusout en panel de pincel.');
                        self._forceVisibility(jQuery(this));
                    }
                });
            }

            if (this._chatattopNewMessageElement) {
                const chatNotificationObserver = new MutationObserver((mutations) => {
                    if (self._isPersistent(self._chatattopNewMessageElement, 'chat-new-message-notification')) {
                        for (const mutation of mutations) {
                            if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
                                if (jQuery(self._chatattopNewMessageElement).is(':hidden')) {
                                    self.notify('info', '[UI Persistencia] Forzando visibilidad de notificación de chat.');
                                    self._forceVisibility(jQuery(self._chatattopNewMessageElement));
                                }
                            }
                        }
                    }
                });
                chatNotificationObserver.observe(this._chatattopNewMessageElement, { attributes: true, attributeFilter: ['style'] });
            }

            if (this._friendsTabFriendlistElement && this._friendsContainerElement) {
                const friendsVisibilityObserver = new MutationObserver((mutations) => {
                    if (self._isPersistent(self._friendsTabFriendlistElement, 'friends-tabfriendlist')) {
                        for (const mutation of mutations) {
                            if (mutation.type === 'attributes' && (mutation.attributeName === 'style' || mutation.attributeName === 'class')) {
                                if (jQuery(self._friendsTabFriendlistElement).is(':hidden')) {
                                    self.notify('info', '[UI Persistencia] Forzando visibilidad de la lista de amigos.');
                                    self._forceVisibility(jQuery(self._friendsTabFriendlistElement));
                                }
                                if (jQuery(self._friendsContainerElement).is(':hidden')) {
                                    self.notify('info', '[UI Persistencia] Forzando apertura del contenedor de amigos.');
                                    jQuery(self._friendsContainerElement).collapse('show');
                                }
                            }
                        }
                    }
                });
                friendsVisibilityObserver.observe(this._friendsTabFriendlistElement, { attributes: true, attributeFilter: ['style', 'class'] });
                friendsVisibilityObserver.observe(this._friendsContainerElement, { attributes: true, attributeFilter: ['style', 'class'] });

                const friendsWgElement = jQuery('#friends-wg');
                if (friendsWgElement.length) {
                    friendsWgElement.off('focusout.friendsHide').on('focusout.friendsHide', function(e) {
                         if (self._isPersistent(self._friendsTabFriendlistElement, 'friends-tabfriendlist')) {
                             e.stopImmediatePropagation();
                             self.notify('debug', '[UI Persistencia] Bloqueando focusout de #friends-wg para mantener la lista abierta.');
                             jQuery(self._friendsContainerElement).collapse('show');
                         }
                    });
                }
            }
        }

        _toggleElementPersistence(id, buttonElement, originalLabelHtml) {
            const isCurrentlyPersistent = this._persistentElements.has(id);
            const newActiveState = !isCurrentlyPersistent;

            if (newActiveState) {
                this._persistentElements.set(id, true);
                this.notify("info", `[UI Persistencia] Activada para: ${originalLabelHtml.replace('<br>', ' ')}.`);
            } else {
                this._persistentElements.delete(id);
                this.notify("info", `[UI Persistencia] Desactivada para: ${originalLabelHtml.replace('<br>', ' ')}.`);
            }

            buttonElement.classList.toggle("active", newActiveState);

            const labelSpan = buttonElement.querySelector("span");
            if (labelSpan) {
                labelSpan.innerHTML = newActiveState
                    ? originalLabelHtml.replace('Mantener', 'Liberar')
                    : originalLabelHtml.replace('Liberar', 'Mantener');
            }

            this._applyPersistenceRulesForElement(id, newActiveState);
        }

        _applyPersistenceRulesForElement(id, isActive) {
            const targetElements = this._getElementsForPersistenceId(id);

            targetElements.forEach(el => {
                if (isActive) {
                    if (id === 'all-modals' && jQuery(el).hasClass('modal')) {
                        if (jQuery(el).hasClass('show')) {
                            this._forceVisibility(jQuery(el));
                        }
                    } else if (id === 'all-popovers' || id === 'all-dropdowns' || id === 'draw-controls-popup' || id === 'chat-new-message-notification' || id === 'top-messages') {
                        this._forceVisibility(jQuery(el));
                    } else if (id === 'friends-tabfriendlist' && jQuery(el).is(this._friendsTabFriendlistElement)) {
                        this._forceVisibility(jQuery(el));
                        jQuery(this._friendsContainerElement).collapse('show');
                    }

                    if (el.tagName === 'DETAILS') {
                        jQuery(el).attr('open', true);
                    }
                } else {
                    this._revertVisibility(jQuery(el));
                    if (el.tagName === 'DETAILS') {
                        jQuery(el).attr('open', false);
                    }
                }
            });
        }

        _getElementsForPersistenceId(id) {
            switch (id) {
                case 'all-modals':
                    return [...document.querySelectorAll('.modal.show, .modal-backdrop.show')];
                case 'all-popovers':
                    return [...document.querySelectorAll('.popover.show')];
                case 'all-dropdowns':
                    return [...document.querySelectorAll('.dropdown-menu.show')];
                case 'draw-controls-popup':
                    return this._drawControlsPopuplistElement ? [this._drawControlsPopuplistElement] : [];
                case 'chat-new-message-notification':
                    return this._chatattopNewMessageElement ? [this._chatattopNewMessageElement] : [];
                case 'top-messages':
                    return [...document.querySelectorAll('.topbox')];
                case 'friends-tabfriendlist':
                    return this._friendsTabFriendlistElement ? [this._friendsTabFriendlistElement] : [];
                default:
                    return [];
            }
        }

        _isPersistent(element, categoryId) {
            return this._persistentElements.has(categoryId) && this._persistentElements.get(categoryId) === true;
        }

        _forceVisibility($element) {
            if ($element && $element.length > 0) {
                $element.each((idx, el) => {
                    jQuery(el).css({ 'display': 'block', 'visibility': 'visible', 'opacity': '1' });
                    jQuery(el).removeClass('hide').addClass('show');
                });
            }
        }

        _revertVisibility($element) {
             if ($element && $element.length > 0) {
                 $element.each((idx, el) => {
                     jQuery(el).css({ 'display': '', 'visibility': '', 'opacity': '' });
                     jQuery(el).removeClass('show').addClass('hide');
                 });
             }
        }
    }
})("QBit");

// END CMD

// START Telemetry

(function AdvancedTranslateTelemetryModule() {
    const QBit = globalThis[arguments[0]];

    // Helper for making XHR GET requests and parsing JSON (from original AutoTranslate Chat)
    function _xhrGetJson(url, callback) {
        const req = new XMLHttpRequest();
        req.onload = (e) => {
            const response = req.response;
            if (!callback) return;
            try {
                callback(JSON.parse(response));
            } catch (error) {
                console.error("AdvancedTranslateTelemetry: Error parsing JSON response", error);
            }
        };
        req.onerror = (e) => {
            console.error("AdvancedTranslateTelemetry: XHR error", e);
        };
        req.open("GET", url);
        req.send();
    }

    // Language data (from original Translator Menu Full)
    const _translationLanguages = {
        "en": "English", "es": "Spanish", "fr": "French", "de": "German", "it": "Italian", "pt": "Portuguese",
        "ru": "Russian", "zh-CN": "Chinese (Simplified)", "ja": "Japanese", "ko": "Korean", "ar": "Arabic",
        "hi": "Hindi", "bn": "Bengali", "tr": "Turkish", "pl": "Polish", "nl": "Dutch", "sv": "Swedish",
        "da": "Danish", "no": "Norwegian", "fi": "Finnish", "el": "Greek", "he": "Hebrew", "id": "Indonesian",
        "ms": "Malay", "th": "Thai", "vi": "Vietnamese", "uk": "Ukrainian", "cs": "Czech", "hu": "Hungarian",
        "ro": "Romanian", "af": "Afrikaans", "sq": "Albanian", "am": "Amharic", "hy": "Armenian", "az": "Azerbaijani",
        "eu": "Basque", "be": "Belarusian", "bs": "Bosnian", "bg": "Bulgarian", "ca": "Catalan", "ceb": "Cebuano",
        "ny": "Chichewa", "co": "Corsican", "hr": "Croatian", "cz": "Czech (Legacy)", "eo": "Esperanto", "et": "Estonian",
        "tl": "Filipino", "fy": "Frisian", "gl": "Galician", "ka": "Georgian", "gu": "Gujarati", "ht": "Haitian Creole",
        "ha": "Hausa", "haw": "Hawaiian", "iw": "Hebrew (Legacy)", "hmn": "Hmong", "is": "Icelandic", "ig": "Igbo",
        "ga": "Irish", "jw": "Javanese", "kn": "Kannada", "kk": "Kazakh", "km": "Khmer", "ku": "Kurdish (Kurmanji)",
        "ky": "Kyrgyz", "lo": "Lao", "la": "Latin", "lv": "Latvian", "lt": "Lithuanian", "lb": "Luxembourgish",
        "mk": "Macedonian", "mg": "Malagasy", "ml": "Malayalam", "mt": "Maltese", "mi": "Maori", "mr": "Marathi",
        "mn": "Mongolian", "my": "Myanmar (Burmese)", "ne": "Nepali", "ps": "Pashto", "fa": "Persian", "pa": "Punjabi",
        "sm": "Samoan", "gd": "Scots Gaelic", "sr": "Serbian", "st": "Sesotho", "sn": "Shona", "sd": "Sindhi",
        "si": "Sinhala", "sk": "Slovak", "sl": "Slovenian", "so": "Somali", "su": "Sundanese", "sw": "Swahili",
        "tg": "Tajik", "ta": "Tamil", "te": "Telugu", "uz": "Uzbek", "xh": "Xhosa", "yi": "Yiddish", "yo": "Yoruba",
        "zu": "Zulu"
    };

    QBit.Styles.addRules([
        `#${QBit.identifier} .telemetry-section {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 10px;
            background-color: var(--CE-bg_color);
            max-width: 350px; /* NEW: Limit the maximum width of each section */
            margin-left: auto; /* Center the sections within their parent */
            margin-right: auto;
        }`,
        `#${QBit.identifier} .telemetry-section-title {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--dark-blue-title);
            text-align: center;
        }`,
        // AutoTranslate Chat specific styles
        `#${QBit.identifier} .auto-translate-status {
            font-size: 0.9em;
            color: var(--success);
            text-align: center;
            margin-bottom: 10px;
        }`,
        // Translator Menu specific styles
        `#${QBit.identifier} .dtr-textarea {
            width: 100%;
            padding: 8px;
            box-sizing: border-box;
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            resize: vertical;
            min-height: 60px;
            background-color: var(--CE-bg_color);
            color: var(--CE-color);
            font-size: 0.95em;
            line-height: 1.4;
        }`,
        `#${QBit.identifier} .dtr-custom-dropdown {
            position: relative;
            width: 100%; /* Occupy full width of its parent section */
            font-size: 0.95em;
            margin-bottom: 10px;
        }`,
        `#${QBit.identifier} .dtr-selected-language-display {
            width: calc(100% - 18px);
            padding: 8px 10px;
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            background-color: var(--CE-bg_color);
            color: var(--CE-color);
            cursor: pointer;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }`,
        `#${QBit.identifier} .dtr-dropdown-panel {
            position: absolute;
            top: 100%;
            left: 0;
            width: 100%; /* Occupy full width of dtr-custom-dropdown */
            /* max-width is now handled by the parent .telemetry-section */
            background-color: var(--CE-bg_color);
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
            z-index: 1000;
            max-height: 250px;
            overflow-y: auto;
            display: none;
            flex-direction: column;
            padding: 5px;
        }`,
        `#${QBit.identifier} .dtr-language-search-input {
            width: calc(100% - 16px);
            padding: 8px;
            box-sizing: border-box;
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            margin-bottom: 5px;
            background-color: var(--light-gray-bg);
            color: var(--dark-text);
            font-size: 0.9em;
        }`,
        `#${QBit.identifier} .dtr-lang-item {
            padding: 8px 10px;
            cursor: pointer;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            border-radius: .20rem;
        }`,
        `#${QBit.identifier} .dtr-lang-item:hover {
            background-color: var(--light);
        }`,
        `#${QBit.identifier} .dtr-button {
            padding: 10px 15px;
            border: none;
            border-radius: .25rem;
            color: white;
            cursor: pointer;
            transition: background-color 0.2s ease;
            font-size: 1em;
            font-weight: 600;
            margin-bottom: 5px;
        }`,
        `#${QBit.identifier} .dtr-translate-button { background-color: var(--info); }`,
        `#${QBit.identifier} .dtr-translate-button:hover { background-color: var(--dark-info); }`,
        `#${QBit.identifier} .dtr-send-button { background-color: var(--primary); }`,
        `#${QBit.identifier} .dtr-send-button:hover { background-color: var(--dark-primary); }`,

        // Advanced Telemetry specific styles
        `#${QBit.identifier} .player-metrics-list {
            max-height: 150px;
            overflow-y: auto;
            border: 1px solid var(--CE-color);
            padding: 5px;
            font-size: 0.85em;
            background-color: var(--CE-bg_color);
            color: var(--CE-color);
        }`,
        `#${QBit.identifier} .snapshot-previews {
            display: flex;
            flex-wrap: wrap;
            gap: 5px;
            margin-top: 5px;
            border: 1px dashed var(--CE-color);
            padding: 5px;
            min-height: 60px;
            align-items: center;
        }`,
        `#${QBit.identifier} .snapshot-previews img {
            width: 50px;
            height: 50px;
            border: 1px solid #ccc;
            cursor: pointer;
            object-fit: cover;
        }`,
        `#${QBit.identifier} .hud-controls {
            display: flex;
            gap: 10px;
            align-items: center;
            margin-top: 5px;
        }`,
        `#${QBit.identifier} .hud-controls label {
            flex-shrink: 0;
        }`,
        `#${QBit.identifier} .hud-controls input[type="color"] {
            flex-grow: 1;
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            background-color: var(--CE-bg_color);
        }`
    ]);

    class AdvancedTranslateTelemetry extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        // --- AutoTranslate Chat Properties ---
        _chatObserver = null;

        // --- Translator Menu Properties ---
        _currentSelectedLanguageCode = "es";
        _translatorUi = { // Renamed to avoid conflict with QBit's _ui
            inputText: null,
            selectedLanguageDisplay: null,
            dropdownPanel: null,
            languageSearchInput: null,
            languageList: null,
            translateButton: null,
            outputText: null,
            sendButton: null,
        };
        _languageItems = {};

        // --- Advanced Telemetry Properties ---
        _playerMetricsContainer;
        _snapshotContainer;
        _snapshots = [];
        _maxSnapshots = 3;

        constructor() {
            super("Traductor y Telemetría", '<i class="fas fa-chart-line"></i>'); // Combined icon
            this._onStartup();
        }

        _onStartup() {
            this._loadInterface();
            this._setupAutoTranslateHooks(); // For automatic chat translation
            this._attachMenuEventListeners(); // For the manual translation menu
            this._listenToGameEvents(); // For telemetry data
            this.updatePlayerMetrics(); // Initial update for player metrics
            this.updateSnapshotPreviews(); // Initial update for snapshots
            this.notify("info", "Módulo 'Traductor y Telemetría' cargado.");
        }

        _loadInterface() {
            const container = domMake.Tree("div");

            // --- Section: AutoTranslate Chat ---
            const autoTranslateSection = domMake.Tree("div", { class: "telemetry-section" });
            autoTranslateSection.appendChild(domMake.Tree("div", { class: "telemetry-section-title" }, ["Traducción Automática de Chat"]));
            autoTranslateSection.appendChild(domMake.Tree("div", { class: "auto-translate-status" }, ["Estado: Activo y escuchando mensajes."]));
            container.appendChild(autoTranslateSection);

            // --- Section: Translator Menu Full ---
            const translatorMenuSection = domMake.Tree("div", { class: "telemetry-section" });
            translatorMenuSection.appendChild(domMake.Tree("div", { class: "telemetry-section-title" }, ["Menú de Traducción Completo"]));

            this._translatorUi.inputText = domMake.Tree('textarea', {
                placeholder: "Ingresa texto para traducir...", rows: 3, id: "dtrInputText",
                class: "dtr-textarea"
            });
            this._translatorUi.inputText.addEventListener('keydown', (e) => e.stopPropagation());
            translatorMenuSection.appendChild(this._translatorUi.inputText);

            const dtrCustomLanguageDropdown = domMake.Tree('div', { class: 'dtr-custom-dropdown' });
            this._translatorUi.selectedLanguageDisplay = domMake.Tree('div', { class: 'dtr-selected-language-display' }, [
                domMake.Tree('span', {}, [_translationLanguages[this._currentSelectedLanguageCode]]),
                domMake.Tree('span', {style: 'font-size: 0.8em; margin-left: 5px;'}, ['▼'])
            ]);
            this._translatorUi.dropdownPanel = domMake.Tree('div', { class: 'dtr-dropdown-panel' });
            this._translatorUi.languageSearchInput = domMake.Tree('input', {
                type: 'text', placeholder: 'Buscar idiomas...', id: 'dtrLanguageSearchInput',
                class: 'dtr-language-search-input'
            });
            this._translatorUi.languageSearchInput.addEventListener('keydown', (e) => e.stopPropagation());
            this._translatorUi.languageList = domMake.Tree('div', { id: 'dtrLanguageList', style: 'display: flex; flex-direction: column; gap: 2px;' });

            this._populateLanguageList();

            this._translatorUi.dropdownPanel.appendAll(this._translatorUi.languageSearchInput, this._translatorUi.languageList);
            dtrCustomLanguageDropdown.appendAll(this._translatorUi.selectedLanguageDisplay, this._translatorUi.dropdownPanel);
            translatorMenuSection.appendChild(dtrCustomLanguageDropdown);

            this._translatorUi.translateButton = domMake.Tree('button', { class: "dtr-button dtr-translate-button" }, ["Traducir"]);
            translatorMenuSection.appendChild(this._translatorUi.translateButton);

            this._translatorUi.outputText = domMake.Tree('textarea', {
                placeholder: "La traducción aparecerá aquí...", rows: 3, readonly: true, id: "dtrOutputText",
                class: "dtr-textarea"
            });
            translatorMenuSection.appendChild(this._translatorUi.outputText);

            this._translatorUi.sendButton = domMake.Tree('button', { class: "dtr-button dtr-send-button" }, ["Enviar Traducción"]);
            translatorMenuSection.appendChild(this._translatorUi.sendButton);

            container.appendChild(translatorMenuSection);

            // --- Section: Player Metrics (from AdvancedTelemetry) ---
            const playerMetricsSection = domMake.Tree("div", { class: "telemetry-section" });
            playerMetricsSection.appendChild(domMake.Tree("div", { class: "telemetry-section-title" }, ["Métricas de Jugadores"]));
            this._playerMetricsContainer = domMake.Tree("div", { class: "player-metrics-list" });
            playerMetricsSection.appendChild(this._playerMetricsContainer);
            container.appendChild(playerMetricsSection);

            // --- Section: Visual History (from AdvancedTelemetry) ---
            const visualHistorySection = domMake.Tree("div", { class: "telemetry-section" });
            visualHistorySection.appendChild(domMake.Tree("div", { class: "telemetry-section-title" }, ["Historial Visual de Lienzo"]));
            const captureSnapshotButton = domMake.Button("Capturar Lienzo");
            captureSnapshotButton.title = "Guarda una imagen del lienzo actual.";
            captureSnapshotButton.addEventListener("click", () => this.captureCanvasSnapshot());
            visualHistorySection.appendChild(captureSnapshotButton);
            this._snapshotContainer = domMake.Tree("div", { class: "snapshot-previews icon-list" });
            visualHistorySection.appendChild(this._snapshotContainer);
            container.appendChild(visualHistorySection);

            // --- Section: Dynamic HUD Themes (from AdvancedTelemetry) ---
            const hudThemesSection = domMake.Tree("div", { class: "telemetry-section" });
            hudThemesSection.appendChild(domMake.Tree("div", { class: "telemetry-section-title" }, ["Temas Dinámicos del HUD"]));
            const hudColorControls = domMake.Tree("div", { class: "hud-controls" });
            const hudColorLabel = domMake.Tree("label", {}, ["Color del HUD:"]);
            const hudColorInput = domMake.Tree("input", { type: "color", value: "#007bff" });
            hudColorInput.addEventListener("change", (e) => {
                const newColor = e.target.value;
                document.documentElement.style.setProperty('--primary', newColor);
                document.documentElement.style.setProperty('--success', newColor);
                this.notify("info", `Color del HUD cambiado a: ${newColor}`);
            });
            hudColorControls.appendAll(hudColorLabel, hudColorInput);
            hudThemesSection.appendChild(hudColorControls);
            container.appendChild(hudThemesSection);

            this.htmlElements.section.appendChild(container);
        }

        // --- AutoTranslate Chat Methods ---
        _setupAutoTranslateHooks() {
            const chatboxMessages = document.querySelector("#chatbox_messages");
            if (chatboxMessages) {
                this._chatObserver = new MutationObserver((mutations) => {
                    mutations.forEach((record) => {
                        record.addedNodes.forEach((node) => {
                            if (node.nodeType === 1 && !node.classList.contains("systemchatmessage5")) {
                                this._processChatMessageNode(node);
                            }
                        });
                    });
                });
                this._chatObserver.observe(chatboxMessages, { childList: true, subtree: false });
                this._addSystemMessage("Traducción automática de chat: activada (con info. de idioma).");
            } else {
                this.notify("warning", "Contenedor de mensajes de chat (#chatbox_messages) no encontrado. La traducción automática podría no funcionar.");
            }
        }

        _processChatMessageNode(node) {
            const messageElement = node.querySelector(".playerchatmessage-text");
            if (messageElement) {
                const textToTranslate = messageElement.textContent;
                this._translateText(textToTranslate, "auto", "en", (translation, detectedLang) => {
                    this._applyTranslationAsTooltip(translation, detectedLang, messageElement);
                });
            }

            const nameElement = node.querySelector(".playerchatmessage-name");
            if (nameElement) {
                const nameToTranslate = nameElement.textContent;
                this._translateText(nameToTranslate, "auto", "en", (translation, detectedLang) => {
                    this._applyTranslationAsTooltip(translation, detectedLang, nameElement);
                });
            }

            const selfnameElement = node.querySelector(".playerchatmessage-selfname");
            if (selfnameElement) {
                const nameToTranslate = selfnameElement.textContent;
                this._translateText(nameToTranslate, "auto", "en", (translation, detectedLang) => {
                    this._applyTranslationAsTooltip(translation, detectedLang, selfnameElement);
                });
            }
        }

        _applyTranslationAsTooltip(translatedText, detectedLangCode, targetNode) {
            const langName = new Intl.DisplayNames(['en'], { type: 'language' }).of(detectedLangCode);
            let tooltipText = translatedText;

            if (detectedLangCode !== 'en' && detectedLangCode !== 'auto' && langName && langName !== detectedLangCode) {
                tooltipText += ` (${langName})`;
            }
            targetNode.title = tooltipText;
        }

        _addSystemMessage(message) {
            const loggingContainer = document.getElementById("chatbox_messages");
            if (loggingContainer) {
                const chatmessage = domMake.Tree(
                    "div",
                    { class: `chatmessage systemchatmessage5`, "data-ts": Date.now(), style: `color: var(--info);` },
                    [message]
                );
                loggingContainer.appendChild(chatmessage);
                loggingContainer.scrollTop = loggingContainer.scrollHeight;
            }
        }

        // --- Shared Translation Function ---
        _translateText(textToTranslate, fromLang = "auto", toLang = "en", callback) {
            const url =
                "https://translate.googleapis.com/translate_a/single?client=gtx&sl=" +
                fromLang +
                "&tl=" +
                toLang +
                "&dt=t&q=" +
                encodeURI(textToTranslate);

            _xhrGetJson(url, (data) => {
                if (data && data[0] && data[0][0] && data[0][0][0]) {
                    const translatedText = data[0][0][0];
                    const detectedSourceLanguage = data[2] || 'unknown';
                    callback(translatedText, detectedSourceLanguage);
                } else {
                    console.warn("AdvancedTranslateTelemetry: Could not get translation for:", textToTranslate, data);
                    callback(textToTranslate, 'unknown');
                }
            });
        }

        // --- Translator Menu Methods ---
        _populateLanguageList() {
            this._translatorUi.languageList.innerHTML = '';
            for (const langCode in _translationLanguages) {
                const langName = _translationLanguages[langCode];
                const langItem = domMake.Tree('div', {
                    class: 'dtr-lang-item',
                    'data-lang-code': langCode,
                }, [langName]);

                langItem.addEventListener('click', (e) => {
                    e.stopPropagation();
                    this._currentSelectedLanguageCode = langCode;
                    this._translatorUi.selectedLanguageDisplay.querySelector('span:first-child').textContent = langName;
                    this._translatorUi.dropdownPanel.style.display = 'none';
                    this.notify("info", `Idioma de traducción manual configurado a: ${langName} (${langCode}).`);
                });
                this._translatorUi.languageList.appendChild(langItem);
                this._languageItems[langCode] = langItem;
            }
        }

        _attachMenuEventListeners() {
            this._translatorUi.selectedLanguageDisplay.addEventListener('click', (e) => {
                e.stopPropagation();
                this._translatorUi.dropdownPanel.style.display = this._translatorUi.dropdownPanel.style.display === 'none' ? 'flex' : 'none';
                if (this._translatorUi.dropdownPanel.style.display === 'flex') {
                    this._translatorUi.languageSearchInput.focus();
                    this._translatorUi.languageSearchInput.value = '';
                    this._translatorUi.languageSearchInput.dispatchEvent(new Event('input'));
                }
            });

            document.addEventListener('click', (e) => {
                const dropdownContainer = this._translatorUi.selectedLanguageDisplay.parentNode;
                if (dropdownContainer && !dropdownContainer.contains(e.target) && this._translatorUi.dropdownPanel.style.display === 'flex') {
                    this._translatorUi.dropdownPanel.style.display = 'none';
                }
            });

            this._translatorUi.languageSearchInput.addEventListener('input', () => {
                const searchTerm = this._translatorUi.languageSearchInput.value.toLowerCase();
                for (const langCode in _translationLanguages) {
                    const langName = _translationLanguages[langCode].toLowerCase();
                    if (langName.startsWith(searchTerm)) {
                        this._languageItems[langCode].style.display = 'block';
                    } else {
                        this._languageItems[langCode].style.display = 'none';
                    }
                }
            });

            this._translatorUi.translateButton.addEventListener("click", () => {
                const textToTranslate = this._translatorUi.inputText.value.trim();
                const toLang = this._currentSelectedLanguageCode;
                if (textToTranslate) {
                    this.notify("info", `Traduciendo texto a ${_translationLanguages[toLang]} (${toLang})...`);
                    this._translateText(textToTranslate, "auto", toLang, (translatedText) => {
                        this._translatorUi.outputText.value = translatedText;
                        this.notify("success", "Traducción completa.");
                    });
                } else {
                    this._translatorUi.outputText.value = "Por favor, introduce texto para traducir.";
                    this.notify("warning", "No hay texto para traducir.");
                }
            });

            this._translatorUi.sendButton.addEventListener("click", () => {
                const translatedText = this._translatorUi.outputText.value;
                if (translatedText) {
                    const chatInput = document.getElementById('chatbox_textinput');
                    if (chatInput) {
                        chatInput.value = translatedText;
                        const event = new KeyboardEvent('keydown', {
                            key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true
                        });
                        chatInput.dispatchEvent(event);
                        this.notify("info", "Traducción enviada al chat.");

                        const originalText = this._translatorUi.sendButton.textContent;
                        this._translatorUi.sendButton.textContent = "¡Enviado!";
                        setTimeout(() => {
                            this._translatorUi.sendButton.textContent = originalText;
                            this._translatorUi.inputText.value = "";
                            this._translatorUi.outputText.value = "";
                        }, 1500);

                    } else {
                        this.notify("error", "No se encontró el elemento de entrada de chat (#chatbox_textinput).");
                        const originalText = this._translatorUi.sendButton.textContent;
                        this._translatorUi.sendButton.textContent = "¡Chat no encontrado!";
                        setTimeout(() => { this._translatorUi.sendButton.textContent = originalText; }, 1500);
                    }
                } else {
                    this.notify("warning", "No hay nada que enviar.");
                }
            });
        }

        // --- Advanced Telemetry Methods ---
        _listenToGameEvents() {
            const playerListElement = document.getElementById("playerlist");
            if (playerListElement) {
                const observer = new MutationObserver(() => this.updatePlayerMetrics());
                observer.observe(playerListElement, { childList: true, subtree: true });
            }

            const chatboxMessages = document.getElementById("chatbox_messages");
            if (chatboxMessages) {
                const chatObserver = new MutationObserver((mutations) => {
                    mutations.forEach(mutation => {
                        mutation.addedNodes.forEach(node => {
                            if (node.classList && node.classList.contains('chatmessage') && !node.classList.contains('systemchatmessage5')) {
                                const playerNameElement = node.querySelector('.playerchatmessage-name a');
                                const playerName = playerNameElement ? playerNameElement.textContent : 'Unknown';
                                // this.updatePlayerActivity(playerName); // Commented out as it's a visual flash
                            }
                        });
                    });
                });
                chatObserver.observe(chatboxMessages, { childList: true });
            }
        }

        updatePlayerMetrics() {
            this._playerMetricsContainer.innerHTML = '';
            const playerRows = document.querySelectorAll("#playerlist .playerlist-row");
            if (playerRows.length === 0) {
                this._playerMetricsContainer.appendChild(domMake.TextNode("No hay jugadores en la sala."));
                return;
            }

            playerRows.forEach(playerRow => {
                const playerId = playerRow.dataset.playerid;
                const playerName = playerRow.querySelector(".playerlist-name a")?.textContent || `Player ${playerId}`;
                const score = playerRow.querySelector(".playerlist-rank")?.textContent || 'N/A';
                const turnScore = playerRow.querySelector(".playerlist-turnscore")?.textContent || 'N/A';

                const metricItem = domMake.Tree("div", { style: "margin: 2px 0; font-size: 0.8rem;" }, [
                    domMake.Tree("strong", {}, [`${playerName} (ID: ${playerId}): `]),
                    domMake.TextNode(`Puntuación: ${score}, Turno: ${turnScore}`)
                ]);
                this._playerMetricsContainer.appendChild(metricItem);
            });
        }

        // _updatePlayerActivity(playerName) { // Kept for reference but not directly used in UI
        //     this.notify("debug", `Player activity detected: ${playerName}`);
        //     const playerElements = document.querySelectorAll(`#playerlist .playerlist-row .playerlist-name a`);
        //     playerElements.forEach(el => {
        //         if (el.textContent === playerName) {
        //             el.closest('.playerlist-row').style.backgroundColor = 'rgba(0, 255, 0, 0.1)';
        //             setTimeout(() => {
        //                 el.closest('.playerlist-row').style.backgroundColor = '';
        //             }, 500);
        //         }
        //     });
        // }

        captureCanvasSnapshot() {
            const gameCanvas = document.body.querySelector("canvas#canvas");
            if (!gameCanvas) {
                this.notify("error", "Lienzo de juego no encontrado para capturar.");
                return;
            }

            try {
                const base64Image = gameCanvas.toDataURL("image/png");
                const timestamp = new Date().toLocaleString();

                this._snapshots.push({ data: base64Image, timestamp: timestamp });
                if (this._snapshots.length > this._maxSnapshots) {
                    this._snapshots.shift();
                }
                this.updateSnapshotPreviews();
                this.notify("success", `Instantánea del lienzo capturada: ${timestamp}`);
            } catch (e) {
                this.notify("error", `Error al capturar el lienzo: ${e.message}`);
                console.error("Canvas snapshot error:", e);
            }
        }

        updateSnapshotPreviews() {
            this._snapshotContainer.innerHTML = '';
            if (this._snapshots.length === 0) {
                this._snapshotContainer.appendChild(domMake.TextNode("No hay instantáneas guardadas."));
                return;
            }

            this._snapshots.forEach((snapshot, index) => {
                const img = domMake.Tree("img", {
                    src: snapshot.data,
                    style: "width: 50px; height: 50px; border: 1px solid #ccc; margin: 2px; cursor: pointer;",
                    title: `Instantánea ${index + 1}: ${snapshot.timestamp}`
                });
                img.addEventListener("click", () => this.displaySnapshot(snapshot.data));
                this._snapshotContainer.appendChild(img);
            });
        }

        displaySnapshot(imageData) {
            const overlay = domMake.Tree("div", {
                style: `
                    position: fixed; top: 0; left: 0; width: 100%; height: 100%;
                    background: rgba(0,0,0,0.8); z-index: 10000;
                    display: flex; justify-content: center; align-items: center;
                `
            });
            const img = domMake.Tree("img", {
                src: imageData,
                style: `max-width: 90%; max-height: 90%; border: 2px solid white;`
            });
            overlay.appendChild(img);
            overlay.addEventListener("click", () => overlay.remove());
            document.body.appendChild(overlay);
        }
    }
})("QBit");

// END AdvancedTranslateTelemetry


(function DrawariaStoreModule() {
    const QBit = globalThis[arguments[0]];

    QBit.Styles.addRules([
        `#${QBit.identifier} .drawaria-store-container-embedded {
            background: var(--CE-bg_color);
            border: 1px solid var(--CE-color);
            padding: 4px;
            margin: 2px;
            border-radius: 4px;
            max-height: 70vh;
            overflow-y: auto;
            color: var(--CE-color);
            line-height: normal;
        }`,
        `#${QBit.identifier} .drawaria-store-container-embedded h2 {
            margin: 0 0 4px 0;
            color: var(--dark-blue-title);
            text-align: center;
            font-size: 1.1em;
        }`,
        `#${QBit.identifier} .drawaria-store-products-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(70px, 1fr));
            gap: 4px;
            margin-top: 4px;
        }`,
        `#${QBit.identifier} .drawaria-store-product-card {
            border: 1px solid var(--CE-color);
            border-radius: 4px;
            padding: 3px;
            background: var(--CE-bg_color);
            display: flex;
            flex-direction: column;
            align-items: center;
        }`,
        `#${QBit.identifier} .drawaria-store-product-card canvas {
            width: 50px;
            height: 50px;
            background-color: #f0f0f0;
            border: 1px solid #ccc;
            margin-bottom: 2px;
            image-rendering: pixelated;
        }`,
        `#${QBit.identifier} .drawaria-store-product-card .title {
            font-weight: bold;
            margin: 1px 0;
            font-size: 0.7em;
            text-align: center;
            color: var(--dark-blue-title);
            line-height: 1.2;
            max-height: 2.1em;
            overflow: hidden;
            text-overflow: ellipsis;
        }`,
        `#${QBit.identifier} .drawaria-store-product-card .price {
            color: var(--success);
            font-weight: bold;
            font-size: 0.75em;
            margin-bottom: 2px;
        }`,
        `#${QBit.identifier} .drawaria-store-product-card button {
            background-color: var(--primary);
            color: white;
            border: none;
            border-radius: 2px;
            padding: 2px 4px;
            cursor: pointer;
            font-size: 0.7em;
            width: 100%;
        }`,
        `#${QBit.identifier} .drawaria-store-cart {
            margin-top: 6px;
            border-top: 1px dashed var(--CE-color);
            padding-top: 4px;
        }`,
        `#${QBit.identifier} .drawaria-store-cart h3 {
            margin-bottom: 4px;
            color: var(--dark-blue-title);
            text-align: center;
            font-size: 1em;
        }`,
        `#${QBit.identifier} .drawaria-store-cart .empty-message {
            color: var(--secondary-text);
            text-align: center;
            padding: 2px 0;
            font-size: 0.75em;
        }`,
        `#${QBit.identifier} .drawaria-store-cart .cart-item-row {
            display: flex;
            align-items: center;
            gap: 3px;
            margin-bottom: 1px;
            background: rgba(0,0,0,0.03);
            padding: 3px;
            border-radius: 3px;
        }`,
        `#${QBit.identifier} .drawaria-store-cart .cart-item-row canvas {
            width: 20px;
            height: 20px;
            background-color: #f0f0f0;
            border: 1px solid #eee;
            image-rendering: pixelated;
        }`,
        `#${QBit.identifier} .drawaria-store-cart .item-title {
            flex: 1;
            font-size: 0.7em;
        }`,
        `#${QBit.identifier} .drawaria-store-cart .item-price {
            color: var(--success);
            font-weight: bold;
            font-size: 0.75em;
            white-space: nowrap;
        }`,
        `#${QBit.identifier} .drawaria-store-cart .delete-btn {
            background-color: var(--danger);
            color: white;
            border: none;
            border-radius: 2px;
            padding: 1px 3px;
            cursor: pointer;
            font-size: 0.6em;
        }`,
        `#${QBit.identifier} .drawaria-store-cart .cart-total {
            margin-top: 4px;
            font-weight: bold;
            font-size: 0.8em;
            text-align: right;
            color: var(--dark-blue-title);
        }`,
        `#${QBit.identifier} .drawaria-store-cart .buy-btn {
            background-color: var(--success);
            color: white;
            border: none;
            border-radius: 4px;
            padding: 4px 6px;
            cursor: pointer;
            font-size: 0.75em;
            width: 100%;
            margin-top: 4px;
        }`,
        `#${QBit.identifier} .loading-message {
            text-align: center;
            padding: 6px;
            color: var(--info);
            font-size: 0.8em;
        }`
    ]);

    class DrawariaStore extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        _storeContainer;
        _productsContainer;
        _cartDiv;

        _cart = [];
        _products = [];

        constructor() {
            super("Tienda Drawaria", '<i class="fas fa-store"></i>');
            this._onStartup();
        }

        _onStartup() {
            this._loadInterface();
            this._fetchProducts();
        }

        _loadInterface() {
            this._storeContainer = domMake.Tree('div', { class: 'drawaria-store-container-embedded' });

            this._storeContainer.appendChild(domMake.Tree('h2', {}, ['🛒 Drawaria Store']));
            this._productsContainer = domMake.Tree('div', { class: 'drawaria-store-products-grid' });
            this._cartDiv = domMake.Tree('div', { class: 'drawaria-store-cart' });

            this._storeContainer.appendChild(this._productsContainer);
            this._storeContainer.appendChild(this._cartDiv);

            this.htmlElements.section.appendChild(this._storeContainer);

            this._productsContainer.innerHTML = '<div class="loading-message">Cargando productos...</div>';
            this._cartDiv.innerHTML = '<h3>👜 Carrito</h3><div class="empty-message">Muchas gracias por comprar!</div>';
        }

        _drawImageOnCanvas(canvas, imageUrl, altText = '') {
            const ctx = canvas.getContext('2d');
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.fillStyle = '#f0f0f0';
            ctx.fillRect(0, 0, canvas.width, canvas.height);

            const img = new Image();
            img.crossOrigin = 'Anonymous';

            img.onload = () => {
                const aspectRatio = img.width / img.height;
                let drawWidth = canvas.width;
                let drawHeight = canvas.height;

                if (img.width > canvas.width || img.height > canvas.height) {
                    if (aspectRatio > 1) {
                        drawHeight = canvas.width / aspectRatio;
                        drawWidth = canvas.width;
                    } else {
                        drawWidth = canvas.height * aspectRatio;
                        drawHeight = canvas.height;
                    }
                } else {
                    drawWidth = img.width;
                    drawHeight = img.height;
                }

                const offsetX = (canvas.width - drawWidth) / 2;
                const offsetY = (canvas.height - drawHeight) / 2;
                ctx.drawImage(img, offsetX, offsetY, drawWidth, drawHeight);
            };

            img.onerror = () => {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                ctx.fillStyle = '#ccc';
                ctx.fillRect(0, 0, canvas.width, canvas.height);
                ctx.font = '10px Arial';
                ctx.fillStyle = '#666';
                ctx.textAlign = 'center';
                ctx.textBaseline = 'middle';
                ctx.fillText('No Img', canvas.width / 2, canvas.height / 2);
            };

            img.src = imageUrl;
        }

        async _fetchProducts() {
            try {
                const res = await fetch('https://fakestoreapi.com/products');
                if (!res.ok) throw new Error(`HTTP error! Status: ${res.status}`);
                this._products = await res.json();
                this._renderStore();
                this.notify("success", `Productos cargados: ${this._products.length}`);
            } catch (error) {
                this._productsContainer.innerHTML = `<div class="loading-message" style="color:var(--danger);">Error al cargar productos: ${error.message}</div>`;
                this.notify("error", `Fallo al cargar productos: ${error.message}`);
            }
        }

        _renderStore() {
            this._productsContainer.innerHTML = '';

            this._products.slice(0, 4).forEach(product => {
                const card = domMake.Tree('div', { class: 'drawaria-store-product-card' });
                const productCanvas = domMake.Tree('canvas', { width: "50", height: "50" });
                this._drawImageOnCanvas(productCanvas, product.image, product.title);

                card.appendAll(
                    productCanvas,
                    domMake.Tree('div', { class: 'title' }, [product.title]),
                    domMake.Tree('div', { class: 'price' }, [`$${product.price}`])
                );

                const addBtn = domMake.Tree('button', {}, ['Agregar al carrito']);
                addBtn.addEventListener('click', () => this._addToCart(product));
                card.appendChild(addBtn);

                this._productsContainer.appendChild(card);
            });

            this._renderCart();
        }

        _renderCart() {
            this._cartDiv.innerHTML = '<h3>👜 Carrito</h3>';

            if (this._cart.length === 0) {
                this._cartDiv.innerHTML += '<div class="empty-message">Muchas gracias por comprar!</div>';
            } else {
                this._cart.forEach((prod, idx) => {
                    const row = domMake.Tree('div', { class: 'cart-item-row' });
                    const cartCanvas = domMake.Tree('canvas', { width: "20", height: "20" });
                    this._drawImageOnCanvas(cartCanvas, prod.image, prod.title);

                    row.appendAll(
                        cartCanvas,
                        domMake.Tree('span', { class: 'item-title' }, [prod.title]),
                        domMake.Tree('span', { class: 'item-price' }, [`$${prod.price}`])
                    );

                    const delBtn = domMake.Tree('button', { class: 'delete-btn' }, ['Eliminar']);
                    delBtn.addEventListener('click', () => this._removeFromCart(idx));
                    row.appendChild(delBtn);

                    this._cartDiv.appendChild(row);
                });

                const total = this._cart.reduce((a, b) => a + b.price, 0);
                this._cartDiv.appendChild(domMake.Tree('div', { class: 'cart-total' }, [`Total: $${total.toFixed(2)}`]));

                const buyBtn = domMake.Tree('button', { class: 'buy-btn' }, ['Comprar']);
                buyBtn.addEventListener('click', () => this._simulatePurchase());
                this._cartDiv.appendChild(buyBtn);
            }
        }

        _addToCart(product) {
            this._cart.push(product);
            this._renderCart();
            this.notify("info", `"${product.title}" añadido al carrito.`);
        }

        _removeFromCart(idx) {
            const removed = this._cart.splice(idx, 1);
            this._renderCart();
            this.notify("info", `"${removed[0].title}" eliminado del carrito.`);
        }

        _simulatePurchase() {
            if (this._cart.length === 0) {
                this.notify("warning", "Tienda de drawaria compre sus productos.");
                return;
            }
            alert('¡Compra simulada con éxito! Gracias por comprar en Drawaria Store 😊');
            this.notify("success", `Compra simulada de ${this._cart.length} ítems. Carrito vaciado.`);
            this._cart = [];
            this._renderCart();
        }
    }
})("QBit");


})();