Cube Engine Uptaded New Options

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

As of 2025-07-13. See the latest version.

// ==UserScript==
// @name         Cube Engine Uptaded New Options
// @version      9.0.4
// @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");
        }
        {
          const resetImageSelectionLabel = domMake.Tree("div", { class: "icon", title: "Unselect" }, [
            domMake.Tree("i", { class: "fas fa-chevron-left" }),
          ]);
          resetImageSelectionLabel.addEventListener("click", () => {
            this.children.forEach((child) => {
              child.htmlElements.label.classList.remove("active");
              child.htmlElements.input.checked = !1;
            });
          });
          this.htmlElements.header.appendChild(resetImageSelectionLabel);
        }
      }

      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;
          });

          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) => {
          setTimeout(() => {
            image.reduceToAtoms();
          }, 10 * index);
        });
        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 * 0.25) % image.width;
          // const y = Math.floor((index * 0.25) / image.width);
          const x = (index * 0.25) % image.width;
          const y = Math.floor((index * 0.25) / 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];
          // const color = rgbaArrayToHex([r, g, b, a]);
          const color = [r, g, b, a];
          pixels.push({ x1: x, y1: y, x2: x, y2: y, color });
        }

        this.setPixelList(pixels);
      }

      getNoneTransparentPixels() {
        this.getAllPixels();

        const newPixelArray = this.drawingManager.pixelList.filter((pixel) => {
          return pixel.color !== "#000000";
          // return /^#0[0-8]0[0-8]0[0-8]$/g.test(pixel.color);
        });

        this.setPixelList(newPixelArray);
      }

      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: "rotation",
            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 multiX = Number(this.parent.DrawCanvasRect.alignModifierX);
        // const multiY = Number(this.parent.DrawCanvasRect.alignModifierY);

        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();
      }

      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);

        let pos2 = this.parent.loadedImages.indexOf(this);
        if (~pos2) {
          this.parent.loadedImages.splice(pos2, 1);
        }
        this._EXP_destroy(!0);
      }
    }

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

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

      startDrawing() {
        this.BotClientManager = this.parent.findGlobal("BotClientManager")?.siblings[0];
        this.isRunning = true;
        this.doTasks();
        this.parent.notify("info", "Started");
      }

      stopDrawing() {
        this.isRunning = false;
      }

      doTasks() {
        if (!this.BotClientManager || this.BotClientManager.children.length <= 0) this.stopDrawing();
        if (!this.isRunning) return this.parent.notify("info", "Stopped");

        this.BotClientManager.children.forEach((botClientInterface, index) => {
          this.parseAndSendPixel(botClientInterface, index);
        });

        setTimeout(() => {
          this.doTasks();
        }, 1);
      }

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

        const task = index % 2 == 0 ? this.pixelList.shift() : this.pixelList.pop();
        botClientInterface.bot.send(this.convertTasks(task));
        this.parent.htmlElements.pixelsLeftToDraw.value = this.pixelList.length;
      }

      convertTasks(pixel) {
        const playerid = -1;
        const lastx = pixel.x1 * this.parent.DrawCanvasRect.drawModifierX;
        const lasty = pixel.y1 * this.parent.DrawCanvasRect.drawModifierY;
        const x = pixel.x2 * this.parent.DrawCanvasRect.drawModifierX;
        const y = pixel.y2 * this.parent.DrawCanvasRect.drawModifierY;
        const isactive = !0;
        const size = pixel.size ?? this.brushSize;
        const pxColor = pixel.color;
        const color = this.singleColor
          ? this.brushColor
          : `rgba(${pxColor[0]},${pxColor[1]},${pxColor[2]},${parseFloat(pxColor[3] * 0.390625).toFixed(2)})`;
        const ispixel = !1;

        let data = [
          "drawcmd",
          0,
          [lastx * 0.01, lasty * 0.01, x * 0.01, y * 0.01, isactive, -size, color, playerid, ispixel],
        ];

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




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

    function sortByColor(pixel1, pixel2) {
      const color1 = rgbaArrayToHex(pixel1.color);
      const color2 = rgbaArrayToHex(pixel2.color);
      if (color1 < color2) {
        return -1;
      }
      if (color1 > color2) {
        return 1;
      }
      return 0;
    }

    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]);
      return "#" + r + g + b + a;
    }

    function areSameColor(colorArray1, colorArray2, allowedDifference = 8) {
      var red = colorArray1[0] - colorArray2[0];
      var green = colorArray1[1] - colorArray2[1];
      var blue = colorArray1[2] - colorArray2[2];

      if (red < 0) red = red * -1;
      if (green < 0) green = green * -1;
      if (blue < 0) blue = blue * -1;

      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;
        this.maxFuzzyness = 32;
        this.#onStartup();
      }

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

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

      #row1() {
        const row = domMake.Row();
        {
          const fuzzynessInput = domMake.Tree("input", {
            type: "number",
            title: "Fuzzyness",
            step: 1,
            min: 0,
            max: 255,
            value: 1,
          });
          const opacityInput = domMake.Tree("input", {
            type: "number",
            title: "Opacity",
            step: 1,
            min: 0,
            max: 255,
            value: 0,
          });

          fuzzynessInput.addEventListener("change", () => {
            this.maxFuzzyness = Number(fuzzynessInput.value);
          });

          opacityInput.addEventListener("change", () => {
            this.minOpacity = Number(opacityInput.value);
          });

          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);
      }
      #row3() {}
      #row4() {}

      minifyPixelsArray() {
        const pixelArray = this.parent.drawingManager.pixelList;
        const newPixelArray = [];

        let currentPixel = pixelArray[0];
        let lastPixel = currentPixel;
        let currentLine = currentPixel;

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

          if (lastPixel.color[3] < 10 && currentPixel.color[3] >= 10) {
            // From Transparent To Solid

            currentLine = currentPixel;
          } else if (lastPixel.color[3] >= 10 && currentPixel.color[3] < 10) {
            // From Solid To Transparent

            currentLine.x2 = lastPixel.x2;
            newPixelArray.push(currentLine);
            currentLine = currentPixel;
          } else if (currentPixel.color[3] >= 10 && lastPixel.color[3] >= 10) {
            // From Solid To Solid

            if (
              currentLine.y1 !== currentPixel.y1 ||
              lastPixel.x2 !== currentPixel.x1 - 1 ||
              !areSameColor(lastPixel.color, currentPixel.color, this.maxFuzzyness)
            ) {
              currentLine.x2 = lastPixel.x2;
              newPixelArray.push(currentLine);
              currentLine = currentPixel;
            }
          } else {
            // From Transparent To Transparent
          }

          lastPixel = currentPixel;
        }
        // if (currentLine.color[3] >= 10) newPixelArray.push(currentLine);

        this.parent.setPixelList(newPixelArray);
      }

      minifyPixelsArray_alt() {
        const pixelArray = this.parent.drawingManager.pixelList;
        const newPixelArray = [];
        var lastPixel = pixelArray[0];
        var currentLine = lastPixel;
        const stepsize = this.parent.stepsize ?? 1;

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

          if (currentPixel.y1 !== currentLine.y1 || currentPixel.color !== lastPixel.color) {
            currentLine.x2 = lastPixel.x2;
            if (!/^#[0-9a-fA-F]{6}[0-4]{2}$/.test(lastPixel.color)) newPixelArray.push(currentLine);
            currentLine = currentPixel;
          }

          lastPixel = currentPixel;
        }
        newPixelArray.push(currentLine);

        this.parent.setPixelList(newPixelArray);
      }
    }

    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();
        this.#row3();
        this.#row4();
      }

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

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

          row.appendAll(sortPixelsArrayButton);
        }
        this.htmlElements.section.appendChild(row);
      }
      #row2() {}
      #row3() {}
      #row4() {}

      sortPixelsArray() {
        const pixelArray = this.parent.drawingManager.pixelList;

        const newPixelArray = [...pixelArray].sort(sortByColor);

        this.parent.setPixelList(newPixelArray);
      }
    }
  })("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 NEW MODULE: PlayerKickTools (COMBINED MODULE) ---

// START KICK

// --- START NEW MODULE: PlayerKickTools (COMBINED MODULE) ---
(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;
        #botKickPlayerListContainer;
        #cachedPlayers = []; // To store player IDs and names for both sections, including drawing status

        // Auto Kick properties
        #isAutoKickActive = false;
        #autoKickInterval = null;
        #kickedPlayersThisSession = new Set(); // To track players already sent a kick vote

        // Auto Prohibit Drawing properties
        #isAutoProhibitDrawingActive = false;
        #autoProhibitDrawingInterval = null;
        #prohibitedPlayersThisSession = new Set(); // To track players already sent a prohibit vote

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

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

        #loadInterface() {
            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" });
            humanKickSection.appendChild(this.#humanKickPlayerListContainer);
            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" });
            botKickSection.appendChild(this.#botKickPlayerListContainer);
            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));
            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));
            autoProhibitDrawingRow.appendChild(autoProhibitDrawingButton);
            automationSection.appendChild(autoProhibitDrawingRow);

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

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

        #setupObservers() {
            const playerListElement = document.getElementById("playerlist");
            if (playerListElement) {
                const observer = new MutationObserver(() => {
                    this.#updatePlayerLists();
                    // Explicitly call the continuous checks if toggles are active
                    if (this.#isAutoKickActive) {
                        this.#performAutoKick();
                    }
                    if (this.#isAutoProhibitDrawingActive) {
                        this.#performAutoProhibitDrawing();
                    }
                });
                // 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(); // Initial update
            }
        }

        #updatePlayerLists() {
            this.#humanKickPlayerListContainer.innerHTML = '';
            this.#botKickPlayerListContainer.innerHTML = '';
            this.#cachedPlayers = [];

            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) {
                const noPlayersMessage = domMake.Tree("span", {}, ["No hay otros jugadores."]);
                this.#humanKickPlayerListContainer.appendChild(noPlayersMessage.cloneNode(true));
                this.#botKickPlayerListContainer.appendChild(noPlayersMessage.cloneNode(true));
                return;
            }

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

//                if (playerId === myPlayerId) {
//                    return;
//                }

                this.#cachedPlayers.push({ id: parseInt(playerId), name: playerName, isDrawing: isDrawing });
            });

            this.#renderHumanKickButtons();
            this.#renderBotKickButtons();
        }

        #renderHumanKickButtons() {
            this.#humanKickPlayerListContainer.innerHTML = '';
            if (this.#cachedPlayers.length === 0) {
                this.#humanKickPlayerListContainer.appendChild(domMake.TextNode("No hay jugadores."));
                return;
            }

            this.#cachedPlayers.forEach(player => {
                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));
                this.#humanKickPlayerListContainer.appendChild(playerButton);
            });
        }

        #renderBotKickButtons() {
            this.#botKickPlayerListContainer.innerHTML = '';
            if (this.#cachedPlayers.length === 0) {
                this.#botKickPlayerListContainer.appendChild(domMake.TextNode("No hay jugadores."));
                return;
            }

            this.#cachedPlayers.forEach(player => {
                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));
                this.#botKickPlayerListContainer.appendChild(playerButton);
            });
        }

        #sendHumanVoteKick(targetPlayerId, targetPlayerName) {
            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() {
            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()}.`);
            }

            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;
        }

        #sendBotKick(targetPlayerId, targetPlayerName) {
            const bot = this.#getBot();
            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) {
            this.#isAutoKickActive = !this.#isAutoKickActive;
            button.classList.toggle("active", this.#isAutoKickActive);
            button.innerHTML = this.#isAutoKickActive
                ? '<i class="fas fa-user-minus"></i> Auto Expulsar Activo'
                : '<i class="fas fa-user-minus"></i> Auto Expulsar';

            if (this.#isAutoKickActive) {
                const bot = this.#getBot();
                if (!bot) {
                    this.notify("error", "Necesitas un bot activo y conectado para usar 'Auto Expulsar'.");
                    this.#isAutoKickActive = false;
                    button.classList.remove("active");
                    button.innerHTML = '<i class="fas fa-user-minus"></i> Auto Expulsar';
                    return;
                }
                this.notify("success", "Automatización 'Auto Expulsar' activada. Los jugadores en la sala serán expulsados periódicamente.");
                this.#kickedPlayersThisSession.clear(); // Clear for a new session
                this.#performAutoKick(); // Perform initial sweep
                this.#autoKickInterval = setInterval(() => {
                    this.#performAutoKick();
                }, 5000); // Check every 5 seconds
            } else {
                clearInterval(this.#autoKickInterval);
                this.#autoKickInterval = null;
                this.notify("info", "Automatización 'Auto Expulsar' desactivada.");
            }
        }

        #performAutoKick() {
            if (!this.#isAutoKickActive) return;

            const bot = this.#getBot();
            if (!bot || !bot.getReadyState()) {
                this.notify("warning", "Bot desconectado, deteniendo 'Auto Expulsar'.");
                const button = document.querySelector('.auto-action-toggle:has(.fa-user-minus)');
                if (button) {
                    this.#toggleAutoKick(button); // Deactivate the toggle
                }
                return;
            }

            // Players currently in the room that haven't been targeted for kick in this session
            const playersToKick = this.#cachedPlayers.filter(player => {
                return !this.#kickedPlayersThisSession.has(player.id);
            });

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

            playersToKick.forEach(player => {
                const data = _io.emits.sendvotekick(player.id);
                bot.send(data);
                this.#kickedPlayersThisSession.add(player.id); // Mark as targeted
                this.notify("info", `Bot "${bot.name}" envió voto para expulsar a ${player.name} (ID: ${player.id}).`);
            });
        }

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

            if (this.#isAutoProhibitDrawingActive) {
                const bot = this.#getBot();
                if (!bot) {
                    this.notify("error", "Necesitas un bot activo y conectado para usar 'Auto Prohibir Dibujo'.");
                    this.#isAutoProhibitDrawingActive = false;
                    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(); // Clear for a new session
                this.#performAutoProhibitDrawing(); // Perform initial sweep
                this.#autoProhibitDrawingInterval = setInterval(() => {
                    this.#performAutoProhibitDrawing();
                }, 5000); // Check every 5 seconds for new players drawing
            } else {
                clearInterval(this.#autoProhibitDrawingInterval);
                this.#autoProhibitDrawingInterval = null;
                this.notify("info", "Automatización 'Auto Prohibir Dibujo' desactivada.");
            }
        }

        #performAutoProhibitDrawing() {
            if (!this.#isAutoProhibitDrawingActive) return;

            const bot = this.#getBot();
            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 => {
                // Only consider players who are currently drawing AND have not been prohibited yet in this session
                return player.isDrawing && !this.#prohibitedPlayersThisSession.has(player.id);
            });

            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); // Mark as prohibited
                this.notify("info", `Bot "${bot.name}" envió voto para prohibir dibujar a ${player.name} (ID: ${player.id}).`);
            });
        }
    }
})("QBit");
// --- END NEW MODULE: PlayerKickTools ---

// --- START NEW MODULE: PlayerSocials (COMBINED MODULE) ---
(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");

// --- START UPDATED MODULE: AutodrawV2 (ACTUALIZADO con Modo Formas) ---
(function AutodrawV2() {
    const QBit = globalThis[arguments[0]];

    QBit.Styles.addRules([
        `#${QBit.identifier} .autodraw-controls .cheat-row {
            display: flex;
            width: 100%;
            margin-bottom: 5px;
        }`,
        `#${QBit.identifier} .autodraw-controls .cheat-row > * {
            flex: 1;
            margin: 0 2px;
            text-align: center;
        }`,
        `#${QBit.identifier} .autodraw-controls input[type="number"],
         #${QBit.identifier} .autodraw-controls input[type="file"] {
            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 .btn {
            width: 100%;
            padding: 5px;
            box-sizing: border-box;
            background-color: var(--secondary); /* Grey button for controls */
        }`,
        `#${QBit.identifier} .autodraw-controls .btn.active {
            background-color: var(--success); /* Active state green */
        }`,
        `#${QBit.identifier} .autodraw-controls .btn i {
            margin-right: 5px;
        }`,
        `#${QBit.identifier} .autodraw-controls .effect-toggle.active {
            background-color: var(--info); /* Blue for active effect */
            color: white;
        }`,
        `#${QBit.identifier} .autodraw-controls .effect-toggle {
            background-color: var(--secondary);
            color: var(--dark);
        }`,
        `#${QBit.identifier} .autodraw-controls label {
            font-size: 0.85em;
            margin-bottom: 3px;
            display: block;
        }`
    ]);

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

        #previewCanvas; // This internal canvas is now crucial for image processing
        #originalCanvas; // The game's canvas (not used for image loading, but for dimensions)
        #imageData = null; // Raw pixel data from the loaded image
        #executionLine = []; // Array of drawing commands (lines/dots)
        #drawingActive = false; // Control drawing loop

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

        // UI Element References for settings
        #imageSizeInput;
        #brushSizeInput;
        #pixelSizeInput;
        #offsetXInput;
        #offsetYInput;
        #drawingSpeedInput;
        #modeButtons = {}; // To manage active state of mode buttons
        #imageFileInput; // Reference to the file input

        // Default Settings
        #defaultSettings = {
            imageSize: 4,
            brushSize: 32,
            pixelSize: 1,
            offsetX: 10,
            offsetY: 0,
            drawingSpeed: 10
        };

        constructor() {
            super("Autodraw V2", '<i class="fas fa-pen"></i>');
            this.#onStartup();
        }

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

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

            // Image Loader
            const imageLoadRow = domMake.Row();
            this.#imageFileInput = domMake.Tree("input", { type: "file", id: "autodraw-image-input", title: "Cargar Imagen (PNG/JPG)" });
            this.#imageFileInput.addEventListener("change", (e) => this.#readImage(e.target));
            imageLoadRow.appendChild(this.#imageFileInput);
            container.appendChild(imageLoadRow);

            // Drawing Settings - Row 1 (Image Size, Brush Size, Pixel Size)
            const settingsRow1 = domMake.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", {}, ["Tamaño Pincel:"]), this.#brushSizeInput]),
                domMake.Tree("div", {}, [domMake.Tree("label", {}, ["Espacio Px:"]), this.#pixelSizeInput])
            );
            container.appendChild(settingsRow1);

            // Drawing Settings - Row 2 (Offset X, Offset Y, Drawing Speed)
            const settingsRow2 = domMake.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)" });

            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])
            );
            container.appendChild(settingsRow2);

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

            // Drawing Modes
            const modesRow = domMake.Row();
            modesRow.style.marginTop = "10px";
            modesRow.style.flexWrap = "wrap";
            modesRow.style.gap = "5px";

            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"); // Nuevo botón para el modo formas
            container.appendChild(modesRow);


            this.htmlElements.section.appendChild(container); // AÑADIDO: Asegura que el contenedor principal esté añadido
        }

        #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.#setDrawingMode('rainMode'); // Activate rainMode by default
        }

        #setupCanvas() {
            this.#previewCanvas = document.createElement('canvas');
            this.#originalCanvas = document.getElementById('canvas'); // The game's drawing 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.notify("warning", "No se seleccionó ninguna imagen.");
                this.#imageData = null; // Clear any previous image data
                return;
            }

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

                    ctx.drawImage(img, 0, 0, this.#previewCanvas.width, this.#previewCanvas.height);

                    this.#imageData = ctx.getImageData(0, 0, this.#previewCanvas.width, this.#previewCanvas.height);
                    this.notify("success", "Imagen lista para dibujar.");

                    if (this.#currentDrawingMode) {
                        this.#prepareDrawingCommands();
                    }

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

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

            this.#executionLine = [];

            const { width, height } = this.#imageData;
            const size = parseFloat(this.#imageSizeInput.value);
            const modifier = parseFloat(this.#pixelSizeInput.value);
            const thickness = parseFloat(this.#brushSizeInput.value);
            const offsetX = parseFloat(this.#offsetXInput.value);
            const offsetY = parseFloat(this.#offsetYInput.value);

            const toGameCoords = (x_pixel, y_pixel, originalImgWidth, originalImgHeight) => {
                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];
            };

            const getPixelColor = (x, y) => {
                const originalPxX = Math.floor(x * (this.#imageData.width / this.#previewCanvas.width));
                const originalPxY = Math.floor(y * (this.#imageData.height / this.#previewCanvas.height));

                if (originalPxX < 0 || originalPxX >= this.#imageData.width || originalPxY < 0 || originalPxY >= this.#imageData.height) return null;
                const index = (originalPxY * this.#imageData.width + originalPxX) * 4;
                const a = this.#imageData.data[index + 3];
                if (a < 20) return null;
                const r = this.#imageData.data[index + 0];
                const g = this.#imageData.data[index + 1];
                const b = this.#imageData.data[index + 2];
                return `rgb(${r},${g},${b})`;
            };


            if (this.#currentDrawingMode === 'rainMode') {
                for (let x = 0; x < width; x += modifier) {
                    let columnPixels = [];
                    for (let y = 0; y < height; y += modifier) {
                        const color = 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 pixels = col.pixels;
                    if (pixels.length === 0) continue;

                    pixels.sort((a, b) => a.y - b.y);

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

                    for (let i = 1; i < pixels.length; i++) {
                        let currentPixel = pixels[i];
                        if (currentPixel.y !== prevPixel.y + modifier || currentPixel.color !== prevPixel.color) {
                            this.#executionLine.push({
                                pos1: toGameCoords(startPixel.x, startPixel.y, width, height),
                                pos2: toGameCoords(prevPixel.x, prevPixel.y, width, height),
                                color: startPixel.color,
                                thickness
                            });
                            startPixel = currentPixel;
                        }
                        prevPixel = currentPixel;
                    }
                    this.#executionLine.push({
                        pos1: toGameCoords(startPixel.x, startPixel.y, width, height),
                        pos2: toGameCoords(prevPixel.x, prevPixel.y, width, height),
                        color: startPixel.color,
                        thickness
                    });
                }

            } else if (this.#currentDrawingMode === 'waveDraw') {
                const waveAmplitude = 15;
                const waveFrequency = 0.05;

                for (let y = 0; y < height; y += modifier) {
                    let startPixel = null;
                    let lastColor = null;

                    for (let x = 0; x < width; x += modifier) {
                        let currentX = x;
                        let currentY = y + waveAmplitude * Math.sin(x * waveFrequency);

                        const actualColor = getPixelColor(currentX, currentY);

                        if (actualColor) {
                            if (!startPixel) {
                                startPixel = { x: currentX, y: currentY, color: actualColor };
                                lastColor = actualColor;
                            } else if (actualColor !== lastColor) {
                                this.#executionLine.push({
                                    pos1: toGameCoords(startPixel.x, startPixel.y, width, height),
                                    pos2: toGameCoords(currentX, currentY, width, height),
                                    color: lastColor,
                                    thickness
                                });
                                startPixel = { x: currentX, y: currentY, color: actualColor };
                                lastColor = actualColor;
                            }
                        } else if (startPixel) {
                            this.#executionLine.push({
                                pos1: toGameCoords(startPixel.x, startPixel.y, width, height),
                                pos2: toGameCoords(currentX, currentY, width, height),
                                color: lastColor,
                                thickness
                            });
                            startPixel = null;
                            lastColor = null;
                        }
                    }
                    if (startPixel) {
                        this.#executionLine.push({
                            pos1: toGameCoords(startPixel.x, startPixel.y, width, height),
                            pos2: toGameCoords(width, y + waveAmplitude * Math.sin(width * waveFrequency), width, height),
                            color: lastColor,
                            thickness
                        });
                    }
                }

            } else if (this.#currentDrawingMode === 'spiralDraw') {
                const centerX = width / 2;
                const centerY = height / 2;
                const maxRadius = Math.min(width, height) / 2;
                const density = 0.5;

                for (let r = 0; r < maxRadius; r += modifier * density) {
                    const numPoints = Math.floor(2 * Math.PI * r / modifier);
                    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;
                        const spiralX = centerX + r * Math.cos(angle);
                        const spiralY = centerY + r * Math.sin(angle);

                        const color = getPixelColor(spiralX, spiralY);

                        if (color) {
                            if (startPoint === null) {
                                startPoint = { x: spiralX, y: spiralY, color: color };
                                lastColor = color;
                            } else if (color !== lastColor) {
                                this.#executionLine.push({
                                    pos1: toGameCoords(startPoint.x, startPoint.y, width, height),
                                    pos2: toGameCoords(prevX, prevY, width, height),
                                    color: lastColor,
                                    thickness
                                });
                                startPoint = { x: spiralX, y: spiralY, color: color };
                                lastColor = color;
                            }
                            prevX = spiralX;
                            prevY = spiralY;
                        } else if (startPoint !== null) {
                            this.#executionLine.push({
                                pos1: toGameCoords(startPoint.x, startPoint.y, width, height),
                                pos2: toGameCoords(prevX, prevY, width, height),
                                color: lastColor,
                                thickness
                            });
                            startPoint = null;
                        }
                    }
                    if (startPoint) {
                         this.#executionLine.push({
                            pos1: toGameCoords(startPoint.x, startPoint.y, width, height),
                            pos2: toGameCoords(prevX, prevY, width, height),
                            color: lastColor,
                            thickness
                        });
                    }
                }

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

                allPixels.forEach(p => {
                    this.#executionLine.push({
                        pos1: toGameCoords(p.x, p.y, width, height),
                        pos2: toGameCoords(p.x + modifier / 2, p.y + modifier / 2, width, height),
                        color: p.color,
                        thickness
                    });
                });
            } else if (this.#currentDrawingMode === '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.");
                    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...");
                const { drawingCommands: shapeCommands } = await shapeDetectorInstance.analyzeImageDataForShapes(
                    this.#imageData,
                    this.#imageData.width,
                    this.#imageData.height,
                    false
                );

                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], // Ya vienen en coords 0-100 del juego
                        pos2: [cmd.x2, cmd.y2],
                        color: this.htmlElements.colorInput?.value || "#000000",
                        thickness: this.#brushSizeInput.value
                    });
                });
                this.notify("success", `Modo Formas: Se prepararon ${this.#executionLine.length} líneas desde formas detectadas.`);
            }


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

        #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() {
            const commandsPrepared = await this.#prepareDrawingCommands();
            if (!commandsPrepared) {
                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.notify("info", `Iniciando dibujo con ${activeBots.length} bots...`);

            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()) {
                    botInterface.bot.emit(
                        "line",
                        -1,
                        line.pos1[0], line.pos1[1],
                        line.pos2[0], line.pos2[1],
                        true,
                        line.thickness,
                        line.color,
                        false
                    );
                    currentLineIndex++;
                } else {
                    this.notify("warning", `Bot ${botInterface ? botInterface.getName() : 'desconocido'} no está listo. Saltando...`);
                    currentLineIndex++;
                }

                await new Promise(resolve => setTimeout(resolve, delayMs));
            }

            if (!this.#drawingActive) {
                this.notify("info", `Dibujo detenido manualmente. Completado ${currentLineIndex} de ${this.#executionLine.length} líneas.`);
            } else {
                this.notify("success", "Dibujo completado!");
            }
            this.#drawingActive = false;
        }

        #stopDrawing() {
            this.#drawingActive = false;
        }

        #setDrawingMode(mode) {
            this.#currentDrawingMode = mode;
            for (const key in this.#modeButtons) {
                if (this.#modeButtons.hasOwnProperty(key)) {
                    this.#modeButtons[key].classList.toggle("active", key === mode);
                }
            }
            this.notify("info", `Modo de dibujo cambiado a: '${mode}'`);

            if (this.#imageData && this.#imageData.data && this.#imageData.data.length > 0) {
                this.#prepareDrawingCommands();
            }
        }
    }
})("QBit");
// --- END UPDATED MODULE: AutodrawV2 ---


// START INTELLIGENT

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

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

        #currentBrushSize = 5;
        #currentSketchColor = "#888888";
        #SKETCH_DATABASE = {}; // Will be loaded dynamically

        constructor() {
            super("Artista Inteligente", '<i class="fas fa-brain"></i>');
            this.#onStartup();
        }

        async #onStartup() {
            this.#loadInterface();
            await this.#loadSketchesFromGithub();
        }

        #loadInterface() {
            this.#row1(); // Generación de Bocetos Asistida
            this.#row2(); // Limpiar Lienzo
            this.#row3(); // Configuración de Color y Tamaño del Boceto
            // #row4 (sketch list) will be loaded after sketches are fetched
        }

        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.#row4(); // Now load the sketch list after data is available
            } catch (error) {
                this.notify("error", `Error al cargar la base de datos de bocetos: ${error.message}`);
                console.error("Error loading SKETCH_DATABASE:", error);
                // Optionally, load a fallback or disable sketch generation features
            }
        }

        #row1() {
            const row = domMake.Row();
            {
                const sketchInput = 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(sketchInput.value.toUpperCase()); // Convertir a mayúsculas
                });

                row.appendAll(sketchInput, generateSketchButton);
            }
            this.htmlElements.section.appendChild(row);
        }

        #row2() {
            const row = domMake.Row();
            {
                const clearCanvasButton = domMake.Button("Limpiar Lienzo");
                clearCanvasButton.title = "Limpia el lienzo con una línea blanca muy grande.";

                clearCanvasButton.addEventListener("click", () => {
                    const botManager = this.findGlobal("BotClientManager")?.siblings[0];
                    if (botManager && botManager.children.length > 0) {
                        const activeBot = botManager.children[0].bot; // Usar el primer bot activo
                        if (activeBot && activeBot.getReadyState()) {
                            // Dibuja dos líneas blancas que cubren todo el canvas con un grosor muy grande
                            activeBot.emit("line", -1, 0, 0, 100, 100, true, 900, "#FFFFFF", false); // Línea diagonal 1
                            activeBot.emit("line", -1, 100, 0, 0, 100, true, 900, "#FFFFFF", false); // Línea diagonal 2
                            this.notify("success", "El lienzo ha sido limpiado.");
                        } else {
                            this.notify("warning", "El bot seleccionado no está conectado.");
                        }
                    } else {
                        this.notify("warning", "Se necesita al menos 1 bot activo para limpiar el lienzo.");
                    }
                });
                row.appendChild(clearCanvasButton);
            }
            this.htmlElements.section.appendChild(row);
        }

        #row3() {
            const row = 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}`);
                });

                row.appendAll(sketchColorLabel, sketchColorInput, brushSizeLabel, brushSizeInput);
            }
            this.htmlElements.section.appendChild(row);
        }

        #row4() {
            const row = domMake.Row(); // Este row contendrá la etiqueta y el contenedor con scroll
            const sketchListContainer = domMake.IconList(); // IconList is a flex container
            sketchListContainer.classList.add('nowrap'); // Class to force no-wrap and add horizontal scroll

            // Create buttons for each word in the database
            Object.keys(this.#SKETCH_DATABASE).forEach(word => {
                const sketchButton = domMake.Button(word);
                sketchButton.title = `Generar boceto para: ${word}`;
                sketchButton.style.flex = '0 0 auto'; // Prevents buttons from stretching and taking up all available width
                sketchButton.style.margin = '2px';
                sketchButton.style.padding = '2px 5px';
                sketchButton.style.fontSize = '0.7em';

                sketchButton.addEventListener("click", () => {
                    this.simulateAISketch(word);
                });
                sketchListContainer.appendChild(sketchButton);
            });

            // Add the label and scroll container to the main row of this section
            row.appendAll(domMake.Tree("label", {}, ["Bocetos Rápidos (" + Object.keys(this.#SKETCH_DATABASE).length + "):"]), sketchListContainer);
            this.htmlElements.section.appendChild(row);
        }

        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 botManager = this.findGlobal("BotClientManager")?.siblings[0];
            if (!botManager || botManager.children.length === 0) {
                this.notify("warning", "Se necesita al menos 1 bot activo para generar bocetos.");
                return;
            }

            const activeBot = botManager.children[0].bot; // Use the first active bot
            if (!activeBot || !activeBot.getReadyState()) {
                this.notify("warning", "El bot seleccionado no está conectado.");
                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);
                        activeBot.emit("line", -1, x1_circ, y1_circ, x2_circ, y2_circ, true, this.#currentBrushSize, this.#currentSketchColor, false);
                    }
                } else {
                    activeBot.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!`);
        }
    }
})("QBit");

// END INTELLIGENT

// Drawing Assistant Module
// This module provides drawing tools like grid, symmetry, and pixel-perfect drawing on an overlay canvas

(function DrawingAssistantModule() {
    const QBit = globalThis[arguments[0]]; // Corrected access to QBit

    // Styles for the overlay canvas and UI elements
    QBit.Styles.addRules([
        `#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 { background-color: var(--info); color: white; }`,
        `#drawing-assistant-symmetry-toggle.active { background-color: var(--info); color: white; }`,
        `#drawing-assistant-pixel-perfect-toggle.active { background-color: var(--info); color: white; }`,
        `#drawing-assistant-controls input[type="number"],
         #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);
         }`,
        `#drawing-assistant-controls ._row > * { margin: 0 2px; }`
    ]);

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

        #overlayCanvas;
        #overlayCtx;
        #gameCanvas; // Reference to the actual game canvas

        #isSymmetryActive = false;
        #isPixelPerfectActive = false;
        #isGridVisible = false;

        #drawingColor = "#000000"; // Default drawing color
        #drawingThickness = 5;    // Default drawing thickness

        // Event listeners (need to manage their lifecycle)
        #mouseMoveListener = null;
        #mouseDownListener = null;
        #mouseUpListener = null;

        constructor() {
            super("Asistente de Dibujo", '<i class="fas fa-drafting-compass"></i>');
            this.#onStartup();
        }

        #onStartup() {
            this.#findGameCanvas();
            this.#setupOverlayCanvas();
            this.#loadInterface();
            this.#hookGameDrawingEvents();
            this.notify("info", "Asistente de Dibujo cargado. Asegúrate de tener un bot activo para usar las herramientas de dibujo.");
        }

        #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 {
                // Fallback dimensions, but core functions won't work well without game canvas
                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();

            // Handle window resize to keep overlay aligned
            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 game canvas dimensions change (e.g. initial load), update actual canvas resolution
            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);
        }

        #loadInterface() {
            const container = domMake.Tree("div", { id: "drawing-assistant-controls" });

            // Drawing parameters (Color, Thickness)
            const paramsRow = domMake.Row();
            paramsRow.style.gap = "5px";

            const colorInput = domMake.Tree("input", { type: "color", value: this.#drawingColor, title: "Color de Dibujo" });
            colorInput.addEventListener("change", (e) => this.#drawingColor = e.target.value);
            paramsRow.appendAll(domMake.Tree("label", {}, ["Color:"]), colorInput);

            const thicknessInput = domMake.Tree("input", { type: "number", min: "1", max: "100", value: this.#drawingThickness, title: "Grosor de Dibujo" });
            thicknessInput.addEventListener("change", (e) => this.#drawingThickness = parseInt(e.target.value));
            paramsRow.appendAll(domMake.Tree("label", {}, ["Grosor:"]), thicknessInput);
            container.appendChild(paramsRow);

            // Geometric Shapes
            const shapesRow = domMake.Row();
            shapesRow.style.gap = "5px";

            const drawLineButton = domMake.Button('<i class="fas fa-grip-lines"></i> Línea');
            drawLineButton.addEventListener("click", () => this.#enableShapeDrawing('line'));
            shapesRow.appendChild(drawLineButton);

            const drawRectButton = domMake.Button('<i class="fas fa-vector-square"></i> Rect.');
            drawRectButton.addEventListener("click", () => this.#enableShapeDrawing('rect'));
            shapesRow.appendChild(drawRectButton);

            const drawCircleButton = domMake.Button('<i class="fas fa-circle"></i> Círculo');
            drawCircleButton.addEventListener("click", () => this.#enableShapeDrawing('circle'));
            shapesRow.appendChild(drawCircleButton);
            container.appendChild(shapesRow);

            // Toggles (Grid, Symmetry, Pixel Perfect)
            const togglesRow = domMake.Row();
            togglesRow.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));
            togglesRow.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));
            togglesRow.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));
            togglesRow.appendChild(togglePixelPerfectButton);
            container.appendChild(togglesRow);

            // Collaborative Drawing Trigger
            const collabRow = 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());
            collabRow.appendChild(startCollabDrawButton);
            container.appendChild(collabRow);


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

        /**
         * Generic helper to get the active bot.
         * @returns {object|null} The active bot instance, or null.
         */
        #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;
            }
            // Access the first (and likely only) instance of BotClientManager
            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);
            }
            // Fallback to the first available bot if no specific bot is selected or found
            if (!activeBotClientInterface && botClientInterfaces.length > 0) {
                activeBotClientInterface = botClientInterfaces[0]; // Corrected to get the first instance
                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;
        }

        #hookGameDrawingEvents() {
            if (!this.#gameCanvas) return;

            // Remove existing listeners to prevent duplicates
            this.#gameCanvas.removeEventListener('mousedown', this.#mouseDownListener);
            this.#gameCanvas.removeEventListener('mousemove', this.#mouseMoveListener);
            this.#gameCanvas.removeEventListener('mouseup', this.#mouseUpListener);
            this.#gameCanvas.removeEventListener('mouseout', this.#mouseUpListener);

            let isDrawing = false;
            let lastX = 0, lastY = 0;

            const sendLineCommand = (startX, startY, endX, endY) => {
                const bot = this.#getBot();
                if (!bot) return;

                // Scale coordinates from canvas pixels to game's 0-100 range
                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;

                // Apply Pixel Perfect snapping if active
                if (this.#isPixelPerfectActive) {
                    const snapSize = 2; // Snap to 2% grid, adjust as needed
                    gameX1 = Math.round(gameX1 / snapSize) * snapSize;
                    gameY1 = Math.round(gameY1 / snapSize) * snapSize;
                    gameX2 = Math.round(gameX2 / snapSize) * snapSize;
                    gameY2 = Math.round(gameY2 / snapSize) * snapSize;
                }

                // Ensure coordinates are within 0-100 bounds
                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 symmetry is active, send mirrored commands
                if (this.#isSymmetryActive) {
                    const midX = 50; // Center of the canvas (game's 0-100 scale)
                    const mirroredGameX1 = midX + (midX - gameX1);
                    const mirroredGameX2 = midX + (midX - gameX2);
                    bot.emit("line", -1, mirroredGameX1, gameY1, mirroredGameX2, gameY2, true, this.#drawingThickness, this.#drawingColor, false);
                }
            };

            this.#mouseDownListener = (e) => {
                isDrawing = true;
                const rect = this.#gameCanvas.getBoundingClientRect();
                lastX = e.clientX - rect.left;
                lastY = e.clientY - rect.top;
                // Start a new path on overlay if drawing for visualization
                this.#overlayCtx.beginPath();
                this.#overlayCtx.moveTo(lastX, lastY);
            };

            this.#mouseMoveListener = (e) => {
                if (!isDrawing) return;
                const rect = this.#gameCanvas.getBoundingClientRect();
                const currentX = e.clientX - rect.left;
                const currentY = e.clientY - rect.top;

                sendLineCommand(lastX, lastY, currentX, currentY);

                // Draw on overlay for visual feedback
                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(); // Start new path to prevent connecting old points
                this.#overlayCtx.moveTo(currentX, currentY);


                lastX = currentX;
                lastY = currentY;
            };

            this.#mouseUpListener = () => {
                isDrawing = false;
                this.#clearOverlay(); // Clear temporary drawing from overlay
                if (this.#isGridVisible) this.#drawGrid(); // Redraw static guides
                if (this.#isSymmetryActive) this.#drawSymmetryLines(); // Redraw static guides
            };

            this.#gameCanvas.addEventListener('mousedown', this.#mouseDownListener);
            this.#gameCanvas.addEventListener('mousemove', this.#mouseMoveListener);
            this.#gameCanvas.addEventListener('mouseup', this.#mouseUpListener);
            this.#gameCanvas.addEventListener('mouseout', this.#mouseUpListener); // Stop drawing if mouse leaves canvas
        }

        // --- Geometric Shape Drawing ---
        #enableShapeDrawing(shapeType) {
            this.notify("info", `Modo '${shapeType}' activado. Haz clic en el lienzo para dibujar.`);
            const bot = this.#getBot();
            if (!bot) return;

            let startCoords = null;
            let currentShapeOverlayListener = null; // Renamed to avoid confusion with `currentShapeOverlay` variable

            const drawShapePreview = (x1, y1, x2, y2) => {
                this.#clearOverlay(); // Clear previous preview
                if (this.#isGridVisible) this.#drawGrid(); // Redraw static guides
                if (this.#isSymmetryActive) this.#drawSymmetryLines();

                const ctx = this.#overlayCtx;
                ctx.strokeStyle = this.#drawingColor;
                ctx.lineWidth = 1; // Thinner for preview
                ctx.setLineDash([5, 5]); // Dashed line for preview
                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') {
                    // Calculate radius, ensuring it's not negative
                    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([]); // Reset line dash after drawing preview
            };

            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.");

                    // Start live preview on mousemove
                    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 {
                    // Second click: draw the shape
                    const bot = this.#getBot();
                    if (!bot) { // Check if bot is still available
                        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') {
                        // Draw 4 lines for rectangle
                        bot.emit("line", -1, x1, y1, x2, y1, true, thickness, color, false); // Top
                        bot.emit("line", -1, x2, y1, x2, y2, true, thickness, color, false); // Right
                        bot.emit("line", -1, x2, y2, x1, y2, true, thickness, color, false); // Bottom
                        bot.emit("line", -1, x1, y2, x1, y1, true, thickness, color, false); // Left
                    } 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); // Radius in game coordinates (0-100)

                        const segments = 48; // 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 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);
                        }
                    }

                    // Clean up and disable shape drawing
                    this.#gameCanvas.removeEventListener('click', onClick);
                    this.#gameCanvas.removeEventListener('mousemove', currentShapeOverlayListener);
                    this.#clearOverlay(); // Clear final preview
                    this.notify("success", `${shapeType} dibujado.`);
                    startCoords = null; // Reset for next shape
                }
            };
            this.#gameCanvas.addEventListener('click', onClick);
        }

        // --- Grid Overlay ---
        #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.");
            }
            // Redraw symmetry lines if active
            if (this.#isSymmetryActive) this.#drawSymmetryLines();
        }

        #drawGrid() {
            if (!this.#gameCanvas) return;
            const ctx = this.#overlayCtx;
            const rect = this.#gameCanvas.getBoundingClientRect();
            const cellSize = 50; // px, adjust for desired grid density

            ctx.strokeStyle = "rgba(100, 100, 100, 0.5)";
            ctx.lineWidth = 1;
            ctx.setLineDash([2, 2]); // Dashed grid lines

            // Vertical lines
            for (let x = 0; x <= rect.width; x += cellSize) {
                ctx.beginPath();
                ctx.moveTo(x, 0);
                ctx.lineTo(x, rect.height);
                ctx.stroke();
            }

            // Horizontal lines
            for (let y = 0; y <= rect.height; y += cellSize) {
                ctx.beginPath();
                ctx.moveTo(0, y);
                ctx.lineTo(rect.width, y);
                ctx.stroke();
            }
            ctx.setLineDash([]); // Reset line dash
        }

        // --- Symmetry Mode ---
        #toggleSymmetry(button) {
            this.#isSymmetryActive = !this.#isSymmetryActive;
            button.classList.toggle("active", this.#isSymmetryActive);
            this.#clearOverlay();
            if (this.#isGridVisible) this.#drawGrid(); // Redraw grid if active
            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();

            // Center vertical line
            ctx.strokeStyle = "rgba(255, 0, 0, 0.7)"; // Red for symmetry line
            ctx.lineWidth = 2;
            ctx.setLineDash([5, 5]); // Dashed line
            ctx.beginPath();
            ctx.moveTo(rect.width / 2, 0);
            ctx.lineTo(rect.width / 2, rect.height);
            ctx.stroke();
            ctx.setLineDash([]); // Reset line dash
        }

        // --- Pixel Perfect / Snap to Grid Drawing ---
        #togglePixelPerfect(button) {
            this.#isPixelPerfectActive = !this.#isPixelPerfectActive;
            button.classList.toggle("active", this.#isPixelPerfectActive);
            this.notify("info", `Modo 'Píxel Perfect' ${this.#isPixelPerfectActive ? 'Activado' : 'Desactivado'}.`);
        }

        // --- Collaborative Drawing Trigger ---
        #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') {
                // Assuming AutodrawV2.startDrawing handles bot distribution internally
                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");

// --- START NEW MODULE: PaletteMaster (CORREGIDO: Event Handlers para StrokeMaster) ---
(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");

        // MoreColorPalettes properties
        #customColors = [];
        #colorButtonsContainer;
        #colorInput;
        #colorPaletteObserver;
        #gameTriangleElement = null;
        #proxyGameButton = null;

        // StrokeMaster properties
        #isPressureActive = false;
        #isTextureActive = false;
        #lastMousePos = { x: 0, y: 0 };
        #lastTimestamp = 0;
        #lastDrawThickness = 5;

        // MODIFICADO: Declarar los handlers como propiedades de la clase, enlazadas una única vez
        #boundMouseDownHandler;
        #boundMouseMoveHandler;
        #boundMouseUpHandler; // This will also handle mouseout

        constructor() {
            super("Maestro de Paletas", '<i class="fas fa-brush"></i>');
            // MODIFICADO: Enlazar los handlers una única vez en el constructor
            this.#boundMouseDownHandler = this.#handleMouseDown.bind(this);
            this.#boundMouseMoveHandler = this.#handleMouseMove.bind(this);
            this.#boundMouseUpHandler = this.#handleMouseUp.bind(this);

            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" }, [""]));

            // 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));
     //s       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> Degradado Diamante');
            diamondGradientButton.addEventListener("click", () => this.#simulateGradientFill("diamond"));
            gradientGroup.appendChild(domMake.Tree("div", {}, [diamondGradientButton]));

            const radialGradientButton = domMake.Button('<i class="fas fa-bullseye"></i> Degradado Radial');
            radialGradientButton.addEventListener("click", () => this.#simulateGradientFill("radial"));
            gradientGroup.appendChild(domMake.Tree("div", {}, [radialGradientButton]));

            const linearGradientButton = domMake.Button('<i class="fas fa-grip-lines"></i> Degradado Lineal');
            linearGradientButton.addEventListener("click", () => this.#simulateGradientFill("linear"));
            gradientGroup.appendChild(domMake.Tree("div", {}, [linearGradientButton]));

            const verticalGradientButton = domMake.Button('<i class="fas fa-arrows-alt-v"></i> Degradado Vertical');
            verticalGradientButton.addEventListener("click", () => this.#simulateGradientFill("vertical"));
            gradientGroup.appendChild(domMake.Tree("div", {}, [verticalGradientButton]));

            const conicalGradientButton = domMake.Button('<i class="fas fa-circle-notch"></i> Degradado Cónico');
            conicalGradientButton.addEventListener("click", () => this.#simulateGradientFill("conical"));
            gradientGroup.appendChild(domMake.Tree("div", {}, [conicalGradientButton]));

            const waveGradientButton = domMake.Button('<i class="fas fa-water"></i> Degradado Ondulado');
            waveGradientButton.addEventListener("click", () => this.#simulateGradientFill("wave"));
            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', (event) => this.#handleCustomColorClick(newButton));
            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(clickedButton) {
            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.bind(this));
            });
        }

        #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()}.`);
            }
   //         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;
        }

        #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); // mouseout also stops drawing

            // 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);
        }

        // MODIFICADO: Handlers ahora son métodos de la clase
        #handleMouseDown(e) {
            this.isDrawingLocal = true;
            const rect = e.target.getBoundingClientRect(); // Use e.target for robustness
            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'}.`);
        }

        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": { // <-- Añadido bloque
                        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;
                    } // <-- Cierre de bloque
                    case "radial": { // <-- Añadido bloque
                        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);
                            // Cambiado ligeramente para que simule mejor un punto con una línea muy pequeña
                            bot.emit("line", -1, x, y, x + 0.1, y + 0.1, true, thickness, currentColor, false);
                        }
                        break;
                    } // <-- Cierre de bloque
                    case "linear": { // <-- Añadido bloque
                        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;
                    } // <-- Cierre de bloque
                    case "vertical": { // <-- Añadido bloque
                        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;
                    } // <-- Cierre de bloque
                    case "conical": { // <-- Añadido bloque
                        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;
                    } // <-- Cierre de bloque
                    case "wave": { // <-- Añadido bloque
                        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;
                    } // <-- Cierre de bloque
                }
                await new Promise(resolve => setTimeout(resolve, delayMs));
            }
            this.notify("success", `Degradado tipo '${type}' dibujado.`);
        }
    }
})("QBit");
// --- END NEW MODULE: PaletteMaster ---

// --- START NEW MODULE: SwarmCommander (COMBINED MODULE: TacticalBotSwarm + BugGeneratorModule) ---

(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 NEW MODULE: SwarmCommander ---
// START EXTRACTOR


// --- START
(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 NEW MODULE: ImageAnalyzer ---

(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");

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

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

        #cmdIdInput;
        #param1Input;
        #param2Input;
        #param3Input;

        constructor() {
            super("Client Cmd", '<i class="fas fa-code"></i>');
            this.#onStartup();
        }

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

        #loadInterface() {
            const row1 = domMake.Row();
            this.#cmdIdInput = domMake.Tree("input", { type: "number", placeholder: "Command ID (e.g., 10)" });
            row1.appendChild(this.#cmdIdInput);
            this.htmlElements.section.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);
            this.htmlElements.section.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);
            this.htmlElements.section.appendChild(row3);
        }

        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.");
            }
        }
    }
})("QBit");


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

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

        #playerMetricsContainer;
        #snapshotContainer;
        #snapshots = [];
        #maxSnapshots = 3;

        constructor() {
            super("Telemetría Avanzada", '<i class="fas fa-chart-line"></i>');
            this.#onStartup();
        }

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

        #loadInterface() {
            this.#row1(); // Panel de Control de Jugadores
            this.#row2(); // Historial Visual de Rondas
            this.#row3(); // Temas Dinámicos del HUD
        }

        #row1() {
            const row = domMake.Row();
            this.#playerMetricsContainer = domMake.Tree("div", { class: "player-metrics-list" });
            row.appendChild(domMake.Tree("label", {}, ["Métricas de Jugadores:"]));
            row.appendChild(this.#playerMetricsContainer);
            this.htmlElements.section.appendChild(row);
            this.updatePlayerMetrics(); // Initial update
        }

        #row2() {
            const row = domMake.Row();
            const captureSnapshotButton = domMake.Button("Capturar Lienzo");
            captureSnapshotButton.title = "Guarda una imagen del lienzo actual.";
            captureSnapshotButton.addEventListener("click", () => this.captureCanvasSnapshot());

            this.#snapshotContainer = domMake.Tree("div", { class: "snapshot-previews icon-list" });
            row.appendAll(captureSnapshotButton, this.#snapshotContainer);
            this.htmlElements.section.appendChild(row);
        }

        #row3() {
            const row = domMake.Row();
            const hudColorLabel = domMake.Tree("label", {}, ["Color del HUD:"]);
            const hudColorInput = domMake.Tree("input", { type: "color", value: "#007bff" }); // Default Bootstrap primary

            hudColorInput.addEventListener("change", (e) => {
                const newColor = e.target.value;
                document.documentElement.style.setProperty('--primary', newColor);
                document.documentElement.style.setProperty('--success', newColor); // Apply to success as well for consistency
                this.notify("info", `Color del HUD cambiado a: ${newColor}`);
            });
            row.appendAll(hudColorLabel, hudColorInput);
            this.htmlElements.section.appendChild(row);
        }

        #listenToGameEvents() {
            // Update player metrics whenever player list changes
            const playerListElement = document.getElementById("playerlist");
            if (playerListElement) {
                const observer = new MutationObserver(() => this.updatePlayerMetrics());
                observer.observe(playerListElement, { childList: true, subtree: true });
            }

            // Listen for chat messages for conceptual "heatmap"
            if (globalThis._io && globalThis._io.events) {
                // This is a placeholder as direct binding to _io.events.bc_chatmessage might not always work without a bot.
                // A more robust solution would be to observe the #chatbox_messages div.
                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';
                                }
                            });
                        });
                    });
                    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) {
            // This is a conceptual update. In a real scenario, this would update
            // a dedicated "activity heatmap" visual.
            this.notify("debug", ``);
            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)'; // Flash green
                    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(); // Keep only the last N snapshots
                }
                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) {
            // Create a temporary overlay to display the full snapshot
            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()); // Close on click
            document.body.appendChild(overlay);
        }
    }
})("QBit");


(function GameLogModule() {
const QBit = globalThis[arguments[0]]; // Corrected access to QBit
class GameLog extends QBit {
    static dummy1 = QBit.register(this);
    static dummy2 = QBit.bind(this, "CubeEngine");

    #gameLog = []; // Stores log entries
    #logDisplayElement;
    #isLoggingActive = true; // Default to active logging

    constructor() {
        super("Registro del Juego", '<i class="fas fa-clipboard-list"></i>');
        this.#onStartup();
    }

    #onStartup() {
        this.#loadInterface();
        this.#setupLogHooks();
        this.notify("info", "Registro de Juego iniciado.");
    }

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

        // Toggle Logging Button
        const toggleRow = domMake.Row();
        const toggleButton = domMake.Button("Desactivar Registro");
        toggleButton.addEventListener("click", () => this.#toggleLogging(toggleButton));
        toggleRow.appendChild(toggleButton);
        container.appendChild(toggleRow);

        // Log Display Area
        const displayRow = domMake.Row();
        this.#logDisplayElement = domMake.Tree("div", {
            style: `
                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;
            `
        }, ["Registro de eventos vacío."]);
        displayRow.appendChild(this.#logDisplayElement);
        container.appendChild(displayRow);

        // Control Buttons
        const controlRow = domMake.Row();
        const clearButton = domMake.Button("Limpiar Log");
        clearButton.addEventListener("click", () => this.#clearLog());
        controlRow.appendChild(clearButton);

        const exportTxtButton = domMake.Button("Exportar TXT");
        exportTxtButton.addEventListener("click", () => this.#exportLog('txt'));
        controlRow.appendChild(exportTxtButton);

        const exportJsonButton = domMake.Button("Exportar JSON");
        exportJsonButton.addEventListener("click", () => this.#exportLog('json'));
        controlRow.appendChild(exportJsonButton);
        container.appendChild(controlRow);

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

    #setupLogHooks() {
        // Hook into incoming WebSocket events (using _io.events where possible)
        if (globalThis._io && globalThis._io.events) {
            // Example of hooking:
            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" // To see player list changes
            ];

            eventsToLog.forEach(eventName => {
                const originalEventCallback = globalThis._io.events[eventName];
                globalThis._io.events[eventName] = (...args) => {
                    this.#logEvent(eventName, args);
                    if (originalEventCallback) {
                        originalEventCallback.apply(this, args);
                    }
                };
            });
        }

        // Also observe the actual chatbox for messages to ensure all chat is captured,
        // as some might not pass through _io.events in a way we can easily intercept.
        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)) }); // Deep copy 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() {
        // Display only the last N messages to avoid performance issues
        const maxDisplayEntries = 50;
        const entriesToDisplay = this.#gameLog.slice(-maxDisplayEntries);

        this.#logDisplayElement.innerHTML = ''; // Clear previous content
        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) // Show full details on hover
            });
            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.`; // Too much data to display directly
            } 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);
        });
        // Scroll to bottom
        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 { // Default to TXT
            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 LOG

// --- 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; // Nuevo: Canvas para el avatar
        #avatarCtx;    // Nuevo: Contexto 2D del canvas
        #playerNameDisplay;
        #playerStatsDisplay;
        #prevButton;
        #nextButton;
        #downloadProfileButton;
        #openPlayerProfileButton; // Nuevo botón para abrir perfil en una nueva pestaña
        #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();
            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);

            const profileDisplayArea = domMake.Tree("div", { class: "profile-display-area" });

            // Canvas para el avatar
            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, // Usamos el canvas aquí
                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());

            // Nuevo botón para Abrir Perfil de Jugador
            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);

            // Contenedores para detalles del perfil y loader (dentro del módulo)
            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;" }); // Usar pre para texto preformateado
            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');
            // Nota: #profileDetailsContent se controla directamente en #displayProfileDetails
            // const detailsDisplay = this.htmlElements.section.querySelector('.profile-details-display');

            if (mainContent) {
                mainContent.style.display = enabled ? 'flex' : 'none';
            }

            buttonRows.forEach(row => {
                row.style.display = enabled ? 'flex' : 'none';
            });

            // Los botones deben estar habilitados/deshabilitados directamente
            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;

            // También controlar los botones de navegación explícitamente si son parte del setInterfaceEnabled
            if (this.#prevButton) this.#prevButton.disabled = !enabled;
            if (this.#nextButton) this.#nextButton.disabled = !enabled;
        }

        async #fetchScoreboardData() {
            this.notify("info", "Obteniendo datos del marcador...");
            this.#loadingIndicator.textContent = "Cargando datos del marcador (Página 1)...";
            this.#setInterfaceEnabled(false);

            try {
                let allPlayers = [];
                for (let page = 1; page <= 5; page++) {
                    this.notify("log", `Cargando datos del marcador (Página ${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) {
                            this.notify("info", `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');

                    // Seleccionar todas las filas con IDs r1 a r100, asumiendo un patrón
                    const rows = doc.querySelectorAll('table.table tbody tr[id^="r"]');

                    if (rows.length === 0) {
                        this.notify("info", `No más jugadores encontrados en la página ${page}.`);
                        break;
                    }

                    rows.forEach(row => {
                        const rank = row.querySelector('th[scope="row"]')?.textContent.trim();
                        const playerId = row.dataset.avatarid;
                        const playerName = row.querySelector('td:nth-child(2) a')?.textContent.trim();
                        const avatarUrl = row.querySelector('td:nth-child(2) 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();

                        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}`
                            });
                        }
                    });
                }

                if (allPlayers.length === 0) {
                    this.notify("warning", "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) {
                this.notify("error", `Error al cargar datos del marcador: ${error.message}`);
                this.#loadingIndicator.textContent = `Error: ${error.message}`;
            }
        }

        async #updateProfileDisplay() { // Ahora asíncrona para cargar la imagen
            if (this.#playersData.length === 0) {
                this.#avatarCtx.clearRect(0, 0, this.#avatarCanvas.width, this.#avatarCanvas.height); // Limpiar canvas
                this.#playerNameDisplay.textContent = "No hay datos de jugadores.";
                this.#playerStatsDisplay.textContent = "";
                this.#setInterfaceEnabled(false);
                this.#profileDetailsContent.style.display = 'none'; // Asegurarse de que el panel de detalles esté oculto
                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}`;

            // Cargar imagen en el canvas
            this.#avatarCtx.clearRect(0, 0, this.#avatarCanvas.width, this.#avatarCanvas.height); // Limpiar antes de dibujar
            const img = new Image();
            img.crossOrigin = "Anonymous"; // Necesario para evitar "tainted canvases"
            img.onload = () => {
                // Dibujar la imagen en el canvas, ajustando para que encaje y sea circular si es posible vía CSS.
                // El CSS ya maneja la forma circular y el object-fit.
                // Aquí solo nos aseguramos de dibujar la imagen completa en el canvas del tamaño deseado.
                this.#avatarCtx.drawImage(img, 0, 0, this.#avatarCanvas.width, this.#avatarCanvas.height);
            };
            img.onerror = () => {
                this.notify("warning", `No se pudo cargar el avatar para ${player.name}.`);
                // Opcional: dibujar un avatar por defecto o un placeholder en el canvas
                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'; // Asegurarse de que el panel de detalles esté oculto al cambiar de perfil
        }

        #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();
            this.notify("info", `Mostrando perfil: ${this.#playersData[this.#currentIndex].name}`);
        }

        #selectRandomProfile() {
            if (this.#playersData.length === 0) {
                this.notify("warning", "No hay jugadores cargados para seleccionar al azar.");
                return;
            }
            const randomIndex = Math.floor(Math.random() * this.#playersData.length);
            this.#currentIndex = randomIndex;
            this.#updateProfileDisplay();
            this.notify("info", `Jugador al azar seleccionado: ${this.#playersData[this.#currentIndex].name}`);
        }

        #downloadProfileData() {
            if (this.#playersData.length === 0 || this.#currentIndex === -1) {
                this.notify("warning", "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);
            this.notify("success", `Datos de ${player.name} descargados.`);
        }

        // Nuevo: Método para abrir la página de perfil del jugador
        #openPlayerProfilePage() {
            if (this.#playersData.length === 0 || this.#currentIndex === -1) {
                this.notify("warning", "No hay perfil seleccionado para abrir.");
                return;
            }
            const player = this.#playersData[this.#currentIndex];
            window.open(player.profileUrl, '_blank');
            this.notify("info", `Abriendo perfil de ${player.name} en una nueva pestaña.`);
        }

        async #displayProfileDetails() {
            if (this.#playersData.length === 0 || this.#currentIndex === -1) {
                this.notify("warning", "No hay perfil seleccionado para mostrar detalles.");
                this.#profileDetailsContent.style.display = 'none'; // Asegurarse de que esté oculto
                return;
            }

            const player = this.#playersData[this.#currentIndex];
            const profileUrl = player.profileUrl;

            this.#profileDetailsContent.textContent = '';
            this.#profileDetailsContent.style.display = 'none'; // Ocultar mientras se carga
            this.#modalLoader.style.display = 'block';

            this.notify("info", `Cargando detalles completos para ${player.name}...`);

            if (!this.#playerProfileExtractorModule || !this.#playerProfileExtractorModule.siblings || this.#playerProfileExtractorModule.siblings.length === 0) {
                this.notify("error", "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') {
                this.notify("error", "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;
                this.notify("success", `Detalles completos de ${player.name} cargados.`);

            } catch (error) {
                this.notify("error", `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'; // Mostrar el contenido después de la carga
            }
        }

        async #analyzeCurrentAvatar() {
            if (this.#playersData.length === 0 || this.#currentIndex === -1) {
                this.notify("warning", "No hay avatar seleccionado para analizar.");
                return;
            }

            const player = this.#playersData[this.#currentIndex];
            const avatarUrl = player.avatarUrl; // Usamos la URL original del avatar

            if (!this.#imageAnalyzerModule || !this.#imageAnalyzerModule.siblings || this.#imageAnalyzerModule.siblings.length === 0) {
                this.notify("error", "El módulo 'Analizador de Imágenes' no está activo. No se puede analizar el avatar.");
                return;
            }

            const analyzerInstance = this.#imageAnalyzerModule.siblings[0];
            // Asegurarse de que ImageAnalyzer tenga un método público para esto
            // Si performAnalysisFromExternalUrl no existe, necesitaríamos modificar ImageAnalyzer también.
            if (typeof analyzerInstance.performAnalysisFromExternalUrl === 'function') {
                this.notify("info", `Enviando avatar de ${player.name} al Analizador de Imágenes...`);
                // Llamamos al método público del ImageAnalyzer y le pasamos la URL del avatar
                analyzerInstance.performAnalysisFromExternalUrl(avatarUrl);
            } else {
                this.notify("error", "El módulo 'Analizador de Imágenes' no tiene el método de análisis requerido (performAnalysisFromExternalUrl).");
            }
        }
    }
})("QBit");

// --- END RANDOM PROFILE

// EXPANSION PACK - - - - - - - - - - -

// --- START UI

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

    // Store original jQuery functions
    let _originalJQueryFnModal;
    let _originalJQueryFnPopover;
    let _originalJQueryFnDropdown; // For Bootstrap dropdowns
    let _originalJQueryFnCollapse;

    QBit.Styles.addRules([
        `#${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;
        }`,
        // ESTILOS CLAVE PARA LOS BOTONES "CUADRADOS" Y ESPACIADOS EN UNA COLUMNA:
        `#${QBit.identifier} .ui-persistence-toggle-button {
            background-color: var(--secondary);
            color: var(--dark);
            width: 100%; /* MODIFICADO: Ocupa todo el ancho disponible */
            min-width: unset; /* Reinicia el min-width anterior */
            padding: 10px 15px; /* Mantiene el padding para hacerlos grandes */
            box-sizing: border-box;
            font-size: 1rem;
            display: flex;
            flex-direction: column; /* APILA: Icono arriba, texto abajo */
            align-items: center; /* Centra el contenido horizontalmente dentro del botón */
            justify-content: center; /* Centra el contenido verticalmente dentro del botón */
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            cursor: pointer;
            text-align: center; /* Asegura que el texto se centre */
            flex: none; /* MODIFICADO: Deshabilita el comportamiento flex para ocupar la mitad del ancho */
            min-height: 70px; /* Asegura una altura mínima consistente */
        }`,
        `#${QBit.identifier} .ui-persistence-toggle-button i {
            margin-right: 0; /* Asegura que no haya margen lateral si están apilados */
            margin-bottom: 5px; /* Espacio entre el icono y el texto */
            font-size: 1.5em; /* Icono más grande */
        }`,
        `#${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; /* MODIFICADO: Apila los botones verticalmente uno debajo del otro */
            flex-wrap: nowrap; /* MODIFICADO: Evita que los botones se envuelvan a la siguiente columna */
            gap: 10px; /* Espacio entre los botones (ahora solo verticalmente) */
            margin-top: 5px;
            justify-content: flex-start; /* MODIFICADO: Alinea los elementos al inicio (arriba) */
            align-items: stretch; /* MODIFICADO: Hace que los elementos secundarios se estiren para ocupar el 100% del ancho del contenedor */
        }`,
    ]);

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

        _persistentElements = new Map();

        _chatattopNewMessageElement;
        _drawControlsPopuplistElement;
        _friendsTabFriendlistElement;
        _friendsContainerElement;

        _cubeEngineMenuObserver; // Not used as Global Toggle is removed

        constructor() {
            super("Persistencia de UI", '<i class="fas fa-thumbtack"></i>');
            this._onStartup();
        }

        _onStartup() {
            this._loadInterface();
            this._captureOriginalFunctions();
            this._patchJQueryFunctions();
            this._findSpecificElements();
            this._setupObservers();
        }

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

            const section = domMake.Tree("div", { class: "ui-persistence-section" });
            section.appendChild(domMake.Tree("div", { class: "ui-persistence-section-title" }, ["Control de Visibilidad de UI"]));

            const elementList = domMake.Tree("div", { class: "ui-persistence-element-list" });

            // iconClass: sólo pon el nombre después de "fa-" de fontawesome, puedes cambiarlos fácilmente
            const addToggleButton = (id, labelHtml, iconClass) => { // labelHtml ahora puede contener HTML como <br>
                // Usamos domMake.Button para crear el botón base, pero lo vaciamos para añadir nuestro contenido
                const button = domMake.Button('');
                button.classList.add("ui-persistence-toggle-button"); // Aquí se añade la clase CSS
                button.setAttribute("data-persistence-id", id);
                button.setAttribute("data-original-label", labelHtml); // Guardar el HTML original

                // CREA EL ICONO
                const icon = domMake.Tree("i");
                icon.className = `fas ${iconClass}`;

                // CREA EL SPAN DEL LABEL
                const labelSpan = domMake.Tree("span");
                labelSpan.innerHTML = labelHtml; // MODIFICADO: Usar innerHTML para interpretar <br>

                // ORDENA: ICONO + LABEL dentro del botón
                button.appendChild(icon);
                button.appendChild(labelSpan);

                // Set initial state based on the map
                if (this._persistentElements.has(id) && this._persistentElements.get(id) === true) {
                    button.classList.add("active");
                    labelSpan.innerHTML = labelHtml.replace('Mantener', 'Liberar'); // MODIFICADO
                }
                button.addEventListener("click", (e) => this._toggleElementPersistence(id, button, labelHtml)); // Pasar labelHtml original
                elementList.appendChild(button);
                return button;
            };

            // Mantenemos los <BR> para asegurar el espaciado vertical dentro del botón.
            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');

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

        _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) {
                    // Only intercept 'hide' if persistence for 'all-modals' is explicitly active
                    if (action === 'hide' && self._isPersistent(this, 'all-modals')) {
                        self.notify('info', `[UI Persistencia] Bloqueando intento de ocultar modal: #${this.attr('id') || this.attr('class')}.`);
                        // Force visibility if it's currently visible (to prevent it from closing)
                        if (this.hasClass('show')) {
                            self._forceVisibility(this);
                            const backdrop = jQuery('.modal-backdrop');
                            if (backdrop.length) {
                                backdrop.off('click.dismiss.bs.modal'); // Prevent backdrop from dismissing
                                self._forceVisibility(backdrop);
                            }
                        }
                        return this; // Prevent original hide call
                    }
                    // For all other actions (including 'show') or if persistence is not active,
                    // let the original method execute normally.
                    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) { // Recibe 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);
        }

        // Apply or revert persistence rules for a specific element type
        _applyPersistenceRulesForElement(id, isActive) {
            const targetElements = this._getElementsForPersistenceId(id);

            targetElements.forEach(el => {
                if (isActive) {
                    // For Modals, Popovers, Dropdowns, etc. only force visibility if they are currently active/shown
                    // This avoids opening all hidden elements.
                    if (id === 'all-modals' && jQuery(el).hasClass('modal')) {
                        // Only force 'show' on modals. The hide logic is handled by patch.
                        // We don't want to show ALL modals just because the toggle is active.
                        // The button's purpose is to keep the *currently active* modal visible.
                        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') {
                        // For these elements, we always force visibility when their toggle is active
                        this._forceVisibility(jQuery(el));
                    } else if (id === 'friends-tabfriendlist' && jQuery(el).is(this._friendsTabFriendlistElement)) {
                        // For friends list, force its tab and its parent collapsible to show
                        this._forceVisibility(jQuery(el));
                        jQuery(this._friendsContainerElement).collapse('show');
                    }

                    // Specific behavior for elements that need their 'open' attribute managed
                    if (el.tagName === 'DETAILS') {
                        jQuery(el).attr('open', true);
                    }
                } else {
                    // Revert to normal state when persistence is deactivated
                    this._revertVisibility(jQuery(el));
                    if (el.tagName === 'DETAILS') {
                        jQuery(el).attr('open', false);
                    }
                    // No explicit hide calls here for Bootstrap elements; let their native behavior resume
                }
            });
        }

        // Helper to get jQuery elements based on the persistence ID
        _getElementsForPersistenceId(id) {
            switch (id) {
                case 'all-modals':
                    // Returns currently visible modals AND backdrops, so we can control them.
                    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')]; // These might not always have '.show'
                case 'friends-tabfriendlist':
                    return this._friendsTabFriendlistElement ? [this._friendsTabFriendlistElement] : [];
                default:
                    return [];
            }
        }

        _isPersistent(element, categoryId) {
            // Check if this specific category is toggled and its value is strictly true
            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'); // For Bootstrap
                });
            }
        }

        _revertVisibility($element) {
             if ($element && $element.length > 0) {
                 $element.each((idx, el) => {
                     jQuery(el).css({ 'display': '', 'visibility': '', 'opacity': '' }); // Revert to default/inherited
                     jQuery(el).removeClass('show').addClass('hide'); // For Bootstrap
                 });
             }
        }
    }
})("QBit");

// END UI

// START INSERT

(function DrawariaImageInserterModule() {
    const QBit = globalThis['QBit'] || CubeEngine;

    // Add some necessary styles for the new inputs and buttons
    QBit.Styles.addRules([
        `#image-inserter-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;
        }`,
        `#image-inserter-container .drawing-settings > div {
            display: flex;
            flex-direction: column;
        }`,
        `#image-inserter-container .drawing-settings label {
            font-size: 0.8em;
            margin-bottom: 2px;
            color: var(--CE-color);
        }`,
        `#image-inserter-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);
        }`,
        `#image-inserter-container .action-buttons {
            display: flex;
            gap: 5px;
            margin-top: 10px;
        }`,
        `#image-inserter-container .action-buttons button {
            flex: 1;
        }`,
        `#image-inserter-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); }
        }`
    ]);

    // 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 // Also compare alpha
        );
    }

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

        #fileInput;
        #statusLabel;
        #insertButton;
        #stopDrawingButton;
        #currentDrawingIndex = 0;
        #drawingActive = false;

        #processingCanvas;
        #processingCtx;
        #imageData = null; // Stores pixel data after image load

        #brushSizeInput;
        #drawingSpeedInput;
        #offsetXInput;
        #offsetYInput;
        #pixelDensityInput;
        #colorToleranceInput; // New input for color similarity tolerance

        #drawingCommands = [];

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

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

        #setupProcessingCanvas() {
            this.#processingCanvas = document.createElement('canvas');
            this.#processingCtx = this.#processingCanvas.getContext('2d', { willReadFrequently: true });
        }

        #loadInterface() {
            const container = domMake.Tree("div", { id: "image-inserter-container", style: "padding: 8px;" });

            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.";
            container.appendAll(uploadLabel, this.#fileInput);

            // Drawing Settings
            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: "15", 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);

            container.appendChild(settingsGroup);

            this.#statusLabel = domMake.Tree("div", { style: "margin: 8px 0; min-height: 20px; color: var(--info);" }, ["Sin imagen cargada."]);
            container.appendChild(this.#statusLabel);

            // Action Buttons (Insert and Stop)
            const actionButtonsRow = 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.";
            actionButtonsRow.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.";
            actionButtonsRow.appendChild(this.#stopDrawingButton);
            container.appendChild(actionButtonsRow);

            this.htmlElements.section.appendChild(container);

            // Event Listeners
            this.#fileInput.addEventListener("change", (ev) => this.#handleFileInput(ev));
            this.#insertButton.addEventListener("click", () => this.#startDrawing());
            this.#stopDrawingButton.addEventListener("click", () => this.#stopDrawing());
        }

        async #handleFileInput(ev) {
            const file = this.#fileInput.files[0];
            if (!file) {
                this.#statusLabel.textContent = "No se seleccionó ningún archivo.";
                this.#insertButton.disabled = true;
                return;
            }

            this.#statusLabel.innerHTML = 'Cargando imagen... <span class="loading-spinner"></span>';
            this.#insertButton.disabled = true;
            this.#stopDrawingButton.disabled = true;
            this.#drawingActive = false; // Ensure any previous drawing is stopped

            try {
                const base64 = await this.#fileToBase64(file);
                const img = new Image();
                img.crossOrigin = "Anonymous"; // Essential for getImageData later
                img.onload = async () => {
                    // Resize image to a reasonable size for processing (e.g., max 150x150)
                    const maxDim = 150; // Increased resolution for better quality
                    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);

                    // Generate commands immediately after loading,
                    // so the user sees the command count before clicking "Draw"
                    await this.#generateDrawingCommands();

                    this.#statusLabel.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.#statusLabel.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]; // Fallback to first bot
            }

            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;
        }

        // MODIFIED: Generates drawing commands by detecting horizontal lines of similar colors
        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 pixel is not mostly transparent
                        if (currentLineColor === null) {
                            // Start a new line segment
                            currentLineColor = currentColor;
                            lineStartX = x;
                        } else if (!areColorsSimilar(currentLineColor, currentColor, colorTolerance)) {
                            // Color change, end current line and start new one
                            const gameX1 = lineStartX * scaleX + offsetX;
                            const gameY1 = y * scaleY + offsetY;
                            const gameX2 = (x - pixelDensity) * scaleX + offsetX; // End at previous pixel's X
                            const gameY2 = y * scaleY + offsetY;

                            this.#drawingCommands.push({
                                x1: gameX1,
                                y1: gameY1,
                                x2: gameX2 + (brushSize * scaleX * 0.5), // Adjust end X to account for brush size
                                y2: gameY2,
                                color: rgbaArrayToString(currentLineColor),
                                thickness: brushSize
                            });

                            currentLineColor = currentColor;
                            lineStartX = x;
                        }
                        // If same color, continue current line
                    } else { // Transparent pixel
                        if (currentLineColor !== null) {
                            // End current line segment due to transparency
                            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;
                        }
                    }
                }
                // End any line segment that was still active at the end of the row
                if (currentLineColor !== null && lineStartX !== -1) {
                    const gameX1 = lineStartX * scaleX + offsetX;
                    const gameY1 = y * scaleY + offsetY;
                    const gameX2 = (width - pixelDensity) * scaleX + offsetX; // End at image width
                    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; // Disable file input during drawing

            this.notify("info", "Iniciando dibujo de imagen...");
            this.#statusLabel.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;
                }

                // Ensure coordinates are within 0-100 bounds for the game canvas
                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.#statusLabel.textContent = `Dibujando... ${this.#currentDrawingIndex}/${this.#drawingCommands.length}`;

                // Yield control for a short period to prevent UI freezes and allow server processing
                await new Promise(resolve => setTimeout(resolve, delayMs));
            }

            if (this.#drawingActive) { // If loop finished naturally
                this.notify("success", "Dibujo de imagen completado!");
                this.#statusLabel.textContent = `Dibujo completado (${this.#drawingCommands.length} líneas).`;
            } else { // If stopped manually
                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.#statusLabel.textContent = `Dibujo detenido. ${this.#currentDrawingIndex}/${this.#drawingCommands.length} líneas.`;
        }
    }
})();

// END INSERT

// START BAD GIRL

// --- START NEW MODULE: Bot Bad Girl ---
(function BotBadGirlModule() {
    const QBit = globalThis[arguments[0]];

    // Estilos para el nuevo módulo
    QBit.Styles.addRules([
        `#${QBit.identifier} .bad-girl-section {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 10px;
            background-color: var(--CE-bg_color);
        }`,
        `#${QBit.identifier} .bad-girl-section-title {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--danger); /* Color rojo para el título */
            text-align: center;
        }`,
        `#${QBit.identifier} .bad-girl-toggle-button.active {
            background-color: var(--danger); /* Botón activo en rojo */
            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);
        }`,
         `#${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 BotBadGirl extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        #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"
        ];

        // UI Elements
        #toggleButton;
        #textarea;
        #intervalInput;

        constructor() {
            super("Bot Bad Girl", '<i class="fas fa-angry"></i>');
            this.#onStartup();
        }

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

        #loadInterface() {
            const container = domMake.Tree("div", { class: "bad-girl-section" });

            container.appendChild(domMake.Tree("div", { class: "bad-girl-section-title" }, ["Modo 'Chica Mala' (Spam)"]));

            // --- Botón de Activación ---
            this.#toggleButton = domMake.Button('<i class="fas fa-play-circle"></i> Iniciar Spam');
            this.#toggleButton.classList.add("bad-girl-toggle-button");
            this.#toggleButton.addEventListener("click", () => this.#toggleSpam());
            container.appendChild(this.#toggleButton);

            // --- Lista de Mensajes Personalizados ---
            container.appendChild(domMake.Tree("label", { style: "margin-top: 10px; display: block;" }, ["Lista de mensajes (uno por línea):"]));
            this.#textarea = domMake.Tree("textarea", {
                class: "bad-girl-textarea",
                placeholder: "Escribe aquí tus mensajes, uno por línea."
            });
            this.#textarea.value = this.#messageList.join("\n");
            container.appendChild(this.#textarea);

             // --- Controles (Intervalo y Guardar) ---
            const controlsDiv = domMake.Tree("div", { class: "bad-girl-controls" });

            const intervalLabel = domMake.Tree("label", { for: "bad-girl-interval" }, ["Intervalo (ms):"]);
            this.#intervalInput = domMake.Tree("input", {
                type: "number",
                id: "bad-girl-interval",
                value: this.#intervalTime,
                min: "100",
                step: "50",
                style: "width: 80px;"
            });
            this.#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.`);
                    // Si el spam está activo, se reinicia para aplicar el nuevo intervalo
                    if (this.#spamInterval) {
                        this.#toggleSpam(); // Detiene
                        this.#toggleSpam(); // Inicia con el nuevo valor
                    }
                }
            });

            const saveButton = domMake.Button("Guardar Lista");
            saveButton.addEventListener("click", () => this.#updateMessageList());

            controlsDiv.appendAll(intervalLabel, this.#intervalInput, saveButton);
            container.appendChild(controlsDiv);

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

        #getBot() {
            const botManagerClass = this.findGlobal("BotClientManager");
            if (!botManagerClass || !botManagerClass.siblings || botManagerClass.siblings.length === 0) {
                this.notify("warning", "El 'BotClientManager' no está activo. Crea un bot primero.");
                return null;
            }

            const botManagerInstance = botManagerClass.siblings[0];
            const selectedBotInput = document.querySelector('input[name="botClient"]:checked');
            let activeBotInterface = null;

            if (selectedBotInput) {
                activeBotInterface = botManagerInstance.children.find(bci => bci.htmlElements.input === selectedBotInput);
            }

            if (!activeBotInterface && botManagerInstance.children.length > 0) {
                activeBotInterface = botManagerInstance.children[0]; // Fallback al primer bot
            }

            if (!activeBotInterface) {
                this.notify("warning", "No hay ningún bot disponible.");
                return null;
            }

            if (!activeBotInterface.bot || !activeBotInterface.bot.getReadyState()) {
                this.notify("warning", `El bot "${activeBotInterface.getName()}" no está conectado.`);
                return null;
            }

            return activeBotInterface.bot;
        }

        #updateMessageList() {
            const newMessages = this.#textarea.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.");
            }
        }

        #toggleSpam() {
            if (this.#spamInterval) {
                // Detener el spam
                clearInterval(this.#spamInterval);
                this.#spamInterval = null;
                this.#toggleButton.classList.remove("active");
                this.#toggleButton.innerHTML = '<i class="fas fa-play-circle"></i> Iniciar Spam';
                this.notify("info", "Spam de 'Chica Mala' detenido.");
            } else {
                // Iniciar el spam
                const bot = this.#getBot();
                if (!bot) {
                    this.notify("error", "No se puede iniciar el spam: no hay un bot válido y conectado.");
                    return;
                }

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

                this.#toggleButton.classList.add("active");
                this.#toggleButton.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(); // Verificar en cada ciclo si el bot sigue conectado
                    if (!currentBot) {
                        this.notify("error", "El bot se ha desconectado. Deteniendo el spam.");
                        this.#toggleSpam(); // Detener el intervalo si el bot ya no es válido
                        return;
                    }

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

                }, this.#intervalTime);
            }
        }
    }
})("QBit");

// END Bad Girl

// Start smart

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

    // Helper function to create a labeled toggle switch manually
    function createToggle(labelText, callback) {
        const row = domMake.Row(); // Use a row for consistency
        row.style.alignItems = 'center'; // Vertically align items
        row.style.justifyContent = 'space-between'; // Put label and toggle on ends

        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 });
        const indicatorLabel = domMake.Tree("label", {
             for: checkboxId,
             class: "icon", // Reusing icon class for visual style, adjust if needed
             style: `
                 width: 24px; /* Smaller toggle */
                 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: var(--secondary); /* Default off color */
             `
         });

        // Initial state (off) - Use a placeholder icon, will change on check
         indicatorLabel.innerHTML = '<i class="fas fa-microchip" style="font-size: 1.2em;"></i>'; // Empty square for "off" state

         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>'; // Green check for "on" state
                 indicatorLabel.style.backgroundColor = 'var(--info)'; // Active color
              } else {
                 indicatorLabel.innerHTML = '<i class="fas fa-square" style="font-size: 1.2em;"></i>'; // Empty square for "off" state
                 indicatorLabel.style.backgroundColor = 'var(--secondary)'; // Off color
              }
             if (callback && typeof callback === 'function') {
                 callback(checked);
             }
         });

        row.appendAll(labelSpan, domMake.Tree("div", {style: "flex-shrink: 0;"}, [checkbox, indicatorLabel])); // Put toggle elements in a div for flex alignment
        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 > button {
            flex-grow: 1;
        }`,
        `#${QBit.identifier} .sentinel-control-group label {
             flex-shrink: 0; /* Prevent label from shrinking */
             margin-right: 5px;
        }`,
        `#${QBit.identifier} .sentinel-follow-button.active {
             background-color: var(--warning); /* Indicate following */
             color: white;
        }`
    ]);

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

        // Convert ALL private fields (#) to protected fields (_)
        _bots = []; // Array to hold active BotClient instances identified
        _botManagerInstance = null; // Reference to the BotClientManager instance
        _gameCanvas = null; // Reference to the game's drawing canvas

        _followTarget = { id: null, interval: null }; // Player ID to follow and interval ID
        _naturalMovementInterval = null; // Interval ID for natural movement
        _activeToggles = { // State of toggle buttons
            naturalMovement: false,
            reactiveChat: false,
            smartGestures: false // RE-ADDED: Smart Gestures toggle
        };
        _chatCooldown = new Map(); // Bot ID -> last chat timestamp
        _gestureCooldown = new Map(); // Bot ID -> last gesture timestamp (NEW)

        _ui = { // References to UI elements
            playerDropdown: null,
            followButton: null,
            personalitySelect: null,
            naturalMovementToggleCheckbox: null, // Added reference
        };

        // Personality Chat & Gesture Data
        _personalities = {
            Amigable: {
                // Spanish phrases
                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 phrases
                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}"],

                // Gesture IDs (mapped from image 0-19)
                gestures: {
                    greeting: 5,     // Sparkles ✨
                    acknowledgement: 11, // Thumbs Up 👍
                    question: 10,    // Index Finger Up ☝️
                    laughter: 7,     // Fireworks 🎆
                    general: 17,     // Open Hands ✋ (uncertainty/stop)
                    congrats: 19,    // Clapping Hands 👏
                    playerJoin: 2,   // Fire 🔥 (excitement) / or 5 for sparkles
                    playerLeave: 3,  // Rocket 🚀 (leaving quickly) / or 17 for bye
                    drawing: 4,      // Heart ❤️ (loving the drawing)
                    goodjob_drawing: 0 // Trophy 🏆 (great drawing)
                }
            },
            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,     // Crown 👑
                    acknowledgement: 12, // Target 🎯
                    question: 10,    // Index Finger Up ☝️
                    laughter: 6,     // Explosion 💥
                    general: 16,     // Bomb 💣
                    congrats: 0,     // Trophy 🏆
                    playerJoin: 13,  // Index Finger Right 👉 (pointing to them)
                    playerLeave: 3,  // Rocket 🚀
                    drawing: 12,     // Target 🎯 (focus on drawing)
                    goodjob_drawing: 0 // Trophy 🏆
                }
            },
            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,    // Thumbs Up 👍
                    acknowledgement: 11, // Thumbs Up 👍
                    question: 10,    // Index Finger Up ☝️
                    laughter: 5,     // Sparkles ✨
                    general: 17,     // Open Hands ✋
                    congrats: 11,    // Thumbs Up 👍
                    playerJoin: 11,  // Thumbs Up 👍
                    playerLeave: 11, // Thumbs Up 👍
                    drawing: 8,      // Coffee ☕ (chilling while drawing)
                    goodjob_drawing: 11 // Thumbs Up 👍
                }
            }
        };

        // --- 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);

        constructor() {
            super("Bot Sentinel: Personalidad AI", '<i class="fas fa-cog"></i>');
            this._loadInterface();
            this._initializeBotListeners();
            this._gameCanvas = document.getElementById('canvas');
        }

        _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 found. Attempting to track bots...");

            setTimeout(() => {
                 this._botManagerInstance.children.forEach(botInterface => {
                      if (botInterface instanceof QBit.findGlobal("BotClientInterface") && botInterface.bot) {
                           this._addBot(botInterface.bot);
                      } else {
                           this.notify("warning", "Found a child in BotClientManager that doesn't seem to be a BotClientInterface.");
                      }
                 });

                 if (this._bots.length === 0) {
                      this.notify("info", "No initial bots found. Waiting for bots to be added.");
                 } else {
                      this.notify("info", `Found ${this._bots.length} initial bots.`);
                      this._updatePlayerDropdown();
                 }

            }, 1000);


            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", "Player list observer attached for dropdown and spawnedavatar updates.");
             } else {
                  this.notify("warning", "Player list element not found. Player dropdown might not update correctly.");
             }

             this.notify("info", "Bot Sentinel initialization complete.");
        }

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

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

            const naturalMovementToggleRow = createToggle("Movimiento Natural", this._toggleNaturalMovement);
            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);

            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);
            interactionSection.appendChild(reactiveChatToggle);

            // RE-ADDED: Smart Gestures toggle
            const smartGesturesToggle = createToggle("Gestos Inteligentes", (checked) => this._activeToggles.smartGestures = checked);
            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);
            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", `Attempted to add a bot instance (${botInstance?.name || 'Unknown'}) that is not managed by 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);
                 }
            });

            if (botInstance.room?.players?.length > 0) {
                 // this._updatePlayerDropdown(); // Called by observer now
            }
        }

        _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.");
                      this._toggleNaturalMovement(false);
                      this._activeToggles.reactiveChat = false;
                      this._activeToggles.smartGestures = false; // Toggle needs to be set off
                      this._toggleSmartFollow();
                 }
            }
        }

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

            if (this._botManagerInstance) {
                 this._botManagerInstance.children.forEach(botInterface => {
                      if (botInterface instanceof QBit.findGlobal("BotClientInterface") && botInterface.bot) {
                           this._addBot(botInterface.bot);
                      }
                 });
                 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 connectedBots = this._bots.filter(bot => bot.getReadyState());
            const players = connectedBots.length > 0 ? connectedBots[0].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();
                 }
            }
             if (this._activeToggles.naturalMovement) {
                  this._toggleNaturalMovement(true);
             }
        }

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

             if (isActive && this._followTarget.id !== null) {
                 this.notify("info", "Movimiento Natural activado. Deteniendo seguimiento.");
                 this._toggleSmartFollow();
             }

            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); // Every 3-5 seconds
                this._executeNaturalMovement(); // Initial move
            } 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 canvasRect = this._gameCanvas.getBoundingClientRect();
            // Game coordinates are 0-100. Center is 50,50.
            const centerX = 50;
            const centerY = 50;

            connectedBots.forEach(bot => {
                 if (bot.attributes?.spawned === false) {
                      this.notify("debug", `Bot ${bot.name} not spawned for natural movement. Attempting spawn.`);
                      bot.emit("spawnavatar");
                      return;
                 }

                // Aim towards the center, but with a random deviation for "natural" feel
                const driftAmount = 15; // Max random deviation from target
                let targetX = centerX + (Math.random() - 0.5) * driftAmount;
                let targetY = centerY + (Math.random() - 0.5) * driftAmount;

                // Clamp positions within 0-100 bounds
                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); // Check and adjust position frequently (every 0.5 sec)
                this._followLogic(); // Immediate first check
            }
        }

        _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;
             }

            // Find the target player's *spawned avatar DOM element*
            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();

            // Calculate target player's center position in 0-100 game coordinates
            // Use avatarRect.left/top for its current visual position
            // Add half of avatarRect.width/height to get its center
            // Subtract canvasRect.left/top to get position relative to canvas
            // Then convert to 0-100 scale.
            let targetXGameCoords = ((avatarRect.left + (avatarRect.width / 2) - canvasRect.left) / canvasRect.width) * 100;
            let targetYGameCoords = ((avatarRect.top + (avatarRect.height / 2) - canvasRect.top) / canvasRect.height) * 100;

            // Ensure coordinates are within game bounds
            targetXGameCoords = Math.max(0, Math.min(100, targetXGameCoords));
            targetYGameCoords = Math.max(0, Math.min(100, targetYGameCoords));

            // Move each connected bot to a slightly different position near the target player
            connectedBots.forEach((bot, index) => {
                 if (bot.attributes?.spawned === false) {
                      this.notify("debug", `Bot ${bot.name} not spawned for follow. Attempting spawn.`);
                      bot.emit("spawnavatar");
                      return;
                 }

                // Calculate offset based on index to spread bots out
                const offsetDistance = 10 + index * 5; // Spread bots further based on index (adjust as needed)
                const offsetAngle = (index * 1.5 + Math.random()) * Math.PI * 2 / connectedBots.length; // Distribute angles

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

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

                // Clamp positions within 0-100 bounds (leave some margin)
                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", `Skipping smooth move for bot ${bot?.name || 'Unknown'}: Not ready or spawned.`);
                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); // Cooldown de 7 segundos
            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); // 0.5 to 1 second delay
             }
         }

         _canBotGesture(bot) {
             const now = Date.now();
             const lastGesture = this._gestureCooldown.get(bot.id) || 0;
             // Cooldown for gestures to prevent spam, and check if smartGestures is active
             const canGesture = this._activeToggles.smartGestures && (now - lastGesture > 500); // REDUCED: 0.5-second cooldown for liveliness
             if (canGesture) {
                 this._gestureCooldown.set(bot.id, now);
             }
             return canGesture;
         }

         _sendBotGesture(bot, gestureId) {
             // Gesture ID can be 0-19 based on the provided image/DOM
             if (gestureId === undefined || gestureId === null) {
                 this.notify("debug", `Skipping gesture: Invalid ID for 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} used gesture ${gestureId}.`);
                     }
                 }, 100 + Math.random() * 200); // 0.1 to 0.3 second delay for quick reactions
             }
         }

        // Helper to determine message language
        _getMessageLanguage(message) {
            const lowerCaseMsg = message.toLowerCase();
            const spanishKeywords = ['hola', 'como', 'estas', 'tu', 'si', 'que', 'jaja', 'claro', 'asi'];
            const englishKeywords = ['hi', 'hello', 'how', 'you', 'yes', 'no', 'what', 'lol', 'omg', 'but', 'well'];

            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'; // Default or mixed
        }

        _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;

            // Greetings
            if (/\b(hola|buenas|hey|hi|hello)\b/.test(lowerCaseMsg)) {
                responseType = 'greeting';
                responseMessage = lang === 'english' ? personality.english_greetings : personality.spanish_greetings;
            }
            // Acknowledgements/Agreement (si, yes, ok, claro)
            else if (/\b(si|yes|ok|claro|yeah)\b/.test(lowerCaseMsg)) {
                responseType = 'acknowledgement';
                responseMessage = lang === 'english' ? personality.english_acknowledgements : personality.spanish_acknowledgements;
            }
            // Questions (como estas, y tu, how are you, what)
            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;
            }
            // Laughter (xd, lol, haha, omg)
            else if (/\b(xd|lol|jaja|haha|omg)\b/.test(lowerCaseMsg)) {
                responseType = 'laughter';
                responseMessage = lang === 'english' ? personality.english_laughter : personality.spanish_laughter;
            }
            // General filler/disagreement (que, but, well)
            else if (/\b(que|but|well|pero|bueno)\b/.test(lowerCaseMsg)) {
                responseType = 'general';
                responseMessage = lang === 'english' ? personality.english_general : personality.spanish_general;
            }
            // Drawing compliments (lindo, hermoso, dibujas bien, good job drawing)
            else if (/\b(lindo|hermoso|dibujas\sbien|buen\sdibujo|buen\strabajo|good\sjob|nice\sdraw)\b/.test(lowerCaseMsg)) {
                responseType = 'goodjob_drawing'; // Custom type for drawing compliments
                responseMessage = lang === 'english' ? [`Thanks, {player}!`, `Glad you liked it, {player}!`] : [`Gracias, {player}!`, `Me alegro que te guste, {player}!`];
                 // If personality doesn't have specific goodjob_drawing chat, just emote
            }


            if (responseType && responseMessage) {
                const selectedResponse = responseMessage[Math.floor(Math.random() * responseMessage.length)];
                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;

            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); // Default to Spanish for now

            this._bots.forEach((bot, index) => {
                if (bot.getReadyState()) {
                     if (this._activeToggles.reactiveChat) {
                         if (index === 0 && Math.random() < 0.7 && this._canBotChat(bot)) {
                             this._sendBotChat(bot, response);
                         }
                     }
                    // RE-ADDED: Gestures for correct guess
                    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); // Default to Spanish

             this._bots.forEach((bot, index) => {
                 if (bot.getReadyState()) {
                      if (this._activeToggles.reactiveChat) {
                           if (index === 0 && Math.random() < 0.6 && this._canBotChat(bot)) {
                               this._sendBotChat(bot, response);
                           }
                      }
                      // RE-ADDED: Gestures for player join
                      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); // Default to Spanish

             this._bots.forEach((bot, index) => {
                 if (bot.getReadyState()) {
                      if (this._activeToggles.reactiveChat) {
                           if (index === 0 && Math.random() < 0.5 && this._canBotChat(bot)) {
                              this._sendBotChat(bot, response);
                          }
                      }
                      // RE-ADDED: Gestures for player leave
                      if (this._activeToggles.smartGestures && personality.gestures.playerLeave) {
                           this._sendBotGesture(bot, personality.gestures.playerLeave);
                      }
                 }
             });
         }

         _handleTurnEnd(botInstance, data) {
             if (!this._activeToggles.reactiveChat) 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 one of our bots is drawing, do nothing
             if (this._bots.some(b => b.id === drawingPlayerId)) {
                 return;
             }

             const personality = this._personalities[this._ui.personalitySelect.value];
             // Send a "Good luck" or "Start drawing" gesture
             if (this._activeToggles.smartGestures && personality.gestures.drawing) {
                 this._bots.forEach(bot => { // All bots can gesture
                     this._sendBotGesture(bot, personality.gestures.drawing);
                 });
             }
             // Optionally, chat "¡Buena suerte, {player}!"
             if (this._activeToggles.reactiveChat && Math.random() < 0.4 && this._canBotChat(botInstance)) {
                 const chatMessage = `¡Buena suerte, ${drawingPlayerName}!`;
                 this._sendBotChat(botInstance, chatMessage);
             }
         }

         _handleWordSelected(botInstance, data) {
             // This event means the drawing player has picked their word.
             // It's a good moment for another subtle "good luck" or "ready" gesture.

             // We only want to react if *our bot is NOT* the drawing player.
             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];
             // Send a "ready" gesture, maybe a thumbs up
             if (this._activeToggles.smartGestures && personality.gestures.acknowledgement) { // Reusing acknowledgment gesture for "ready"
                 this._bots.forEach(bot => {
                     this._sendBotGesture(bot, personality.gestures.acknowledgement);
                 });
             }
         }
    }
})("QBit");

// END smart

})();