Greasy Fork is available in English.

Cube Engine Uptaded New Options

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

Versión del día 13/07/2025. Echa un vistazo a la versión más reciente.

Tendrás que instalar una extensión para tu navegador como Tampermonkey, Greasemonkey o Violentmonkey si quieres utilizar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Violentmonkey para instalar este script.

Necesitarás instalar una extensión como Tampermonkey o Userscripts para instalar este script.

Necesitará instalar una extensión como Tampermonkey para instalar este script.

Necesitarás instalar una extensión para administrar scripts de usuario si quieres instalar este script.

(Ya tengo un administrador de scripts de usuario, déjame instalarlo)

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión como Stylus para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

Necesitará instalar una extensión del gestor de estilos de usuario para instalar este estilo.

(Ya tengo un administrador de estilos de usuario, déjame instalarlo)

// ==UserScript==
// @name         Cube Engine Uptaded New Options
// @version      9.0.4.1
// @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: Gradient Fills y Robustez) ---
(function PaletteMasterModule() {
    const QBit = globalThis[arguments[0]];

    const LOCAL_STORAGE_KEY = 'cubicEngineCustomColors';
    const DEFAULT_CUSTOM_COLORS = [
        { name: "Teal", hex: "#008080" }, { name: "Lime", hex: "#AAFF00" },
        { name: "Cyan", hex: "#00FFFF" }, { name: "Magenta", hex: "#FF00FF" },
        { name: "Olive", hex: "#808000" }, { name: "Maroon", hex: "#800000" }
    ];

    QBit.Styles.addRules([
        // General section styling
        `#${QBit.identifier} .palette-master-section {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 10px;
            background-color: var(--CE-bg_color);
        }`,
        `#${QBit.identifier} .palette-master-section-title {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--dark-blue-title);
            text-align: center;
        }`,
        // MoreColorPalettes specific
        `#${QBit.identifier} .custom-color-button {
            box-shadow: 0 0 2px rgba(0,0,0,0.3);
            cursor: pointer;
            border: 1px solid transparent;
        }`,
        `#${QBit.identifier} .custom-color-button.custom-active-color {
            box-shadow: 0 0 5px 2px var(--info);
            border: 1px solid var(--info);
        }`,
        // StrokeMaster specific
        `#${QBit.identifier} .stroke-master-toggle-button.active {
            background-color: var(--info);
            color: white;
        }`,
        `#${QBit.identifier} .stroke-master-control-group {
            display: flex;
            flex-wrap: wrap;
            gap: 5px;
            margin-top: 5px;
            padding-top: 5px;
            border-top: 1px solid rgba(0,0,0,0.1);
        }`,
        `#${QBit.identifier} .stroke-master-control-group > div {
            flex: 1 1 48%; /* For responsiveness */
            display: flex;
            flex-direction: column;
            align-items: flex-start;
        }`,
        `#${QBit.identifier} .stroke-master-control-group input[type="number"],
         #${QBit.identifier} .stroke-master-control-group input[type="range"] {
            width: 100%;
        }`
    ]);

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

        // Convert ALL private fields (#) to protected fields (_) for robustness
        _customColors = [];
        _colorButtonsContainer;
        _colorInput;
        _colorPaletteObserver;
        _gameTriangleElement = null;
        _proxyGameButton = null;

        _isPressureActive = false;
        _isTextureActive = false;
        _lastMousePos = { x: 0, y: 0 };
        _lastTimestamp = 0;
        _lastDrawThickness = 5;

        // Bound Handlers: Declare and bind methods that will be used as callbacks
        _handleMouseDown = this._handleMouseDown.bind(this);
        _handleMouseMove = this._handleMouseMove.bind(this);
        _handleMouseUp = this._handleMouseUp.bind(this);
        _addNewCustomColor = this._addNewCustomColor.bind(this);
        _clearAllCustomColors = this._clearAllCustomColors.bind(this);
        _handleCustomColorClick = this._handleCustomColorClick.bind(this);
        _handleGameColorClick = this._handleGameColorClick.bind(this);
        _togglePressureControl = this._togglePressureControl.bind(this);
        _toggleTextureBrush = this._toggleTextureBrush.bind(this);
        _simulateGradientFill = this._simulateGradientFill.bind(this); // This method needs to be async

        constructor() {
            super("Maestro de Paletas", '<i class="fas fa-brush"></i>');
            this._onStartup();
        }

        _onStartup() {
            this._loadInterface();
            this._loadCustomColors();
            this._setupColorPaletteObserver();
            this._hookDrawingEvents(); // Attach event listeners
        }

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

            // --- Section: Gestión de Paletas ---
            const paletteSection = domMake.Tree("div", { class: "palette-master-section" });
            paletteSection.appendChild(domMake.Tree("div", { style: "width: 100%; text-align: center; font-weight: bold; margin-bottom: 5px;" },  ["Gestión de Paletas"]));

            const addColorRow = domMake.Row();
            const addColorLabel = domMake.Tree("label", {}, ["Añadir Color:"]);
            this._colorInput = domMake.Tree("input", { type: "color", value: "#FF0000" });
            const addColorButton = domMake.Button("Añadir");
            addColorButton.addEventListener("click", () => this._addNewCustomColor(this._colorInput.value));
            addColorRow.appendAll(addColorLabel, this._colorInput, addColorButton);
            paletteSection.appendChild(addColorRow);

            const clearColorsRow = domMake.Row();
            const clearAllColorsButton = domMake.Button("Limpiar Todos");
            clearAllColorsButton.addEventListener("click", this._clearAllCustomColors);
            clearColorsRow.appendChild(clearAllColorsButton);
            paletteSection.appendChild(clearColorsRow);

            const customColorsDisplayRow = domMake.Row();
            this._colorButtonsContainer = domMake.IconList();
            customColorsDisplayRow.appendChild(this._colorButtonsContainer);
            paletteSection.appendChild(customColorsDisplayRow);
            container.appendChild(paletteSection);

            // --- Section: Herramientas de Trazo ---
            const strokeSection = domMake.Tree("div", { class: "palette-master-section" });
            strokeSection.appendChild(domMake.Tree("div", { class: "palette-master-section-title" }, ["Herramientas de Trazo"])); // Added proper title

            // Pressure Control
            const pressureRow = domMake.Row();
            const pressureButton = domMake.Button("Control de Presión");
            pressureButton.classList.add("stroke-master-toggle-button");
            pressureButton.addEventListener("click", () => this._togglePressureControl(pressureButton));
            pressureRow.appendChild(pressureButton);
            strokeSection.appendChild(pressureRow);

            // Texture Brush
            const textureRow = domMake.Row();
            const textureButton = domMake.Button("Pincel Texturizado");
            textureButton.classList.add("stroke-master-toggle-button");
            textureButton.addEventListener("click", () => this._toggleTextureBrush(textureButton));
            textureRow.appendChild(textureButton);
            strokeSection.appendChild(textureRow);


            // Gradient Fills (conceptual buttons)
            const gradientGroup = domMake.Tree("div", { class: "stroke-master-control-group" });
            gradientGroup.appendChild(domMake.Tree("label", { style: "width: 100%; text-align: center; font-weight: bold; margin-bottom: 5px;" }, ["Rellenos Degradados"]));

            const diamondGradientButton = domMake.Button('<i class="fas fa-gem"></i>Efecto de Degradado<br> Diamante');
            diamondGradientButton.addEventListener("click", () => this._simulateGradientFill("diamond")); // Use bound _simulateGradientFill
            gradientGroup.appendChild(domMake.Tree("div", {}, [diamondGradientButton]));

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

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

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

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

            const waveGradientButton = domMake.Button('<i class="fas fa-water"></i>Efecto de Degradado<br> Ondulado');
            waveGradientButton.addEventListener("click", () => this._simulateGradientFill("wave")); // Use bound _simulateGradientFill
            gradientGroup.appendChild(domMake.Tree("div", {}, [waveGradientButton]));


            strokeSection.appendChild(gradientGroup);
            container.appendChild(strokeSection);

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

        // --- MoreColorPalettes Methods ---
        _loadCustomColors() {
            try {
                const storedColors = localStorage.getItem(LOCAL_STORAGE_KEY);
                this._customColors = storedColors ? JSON.parse(storedColors) : [...DEFAULT_CUSTOM_COLORS];
            } catch (e) {
                this.notify("error", `Error al cargar colores: ${e.message}. Usando colores por defecto.`);
                this._customColors = [...DEFAULT_CUSTOM_COLORS];
            }
            this._renderCustomColorButtons();
        }

        _saveCustomColors() {
            try {
                localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(this._customColors));
                this.notify("success", "Colores personalizados guardados.");
            } catch (e) {
                this.notify("error", `Error al guardar colores: ${e.message}`);
            }
        }

        _renderCustomColorButtons() {
            this._colorButtonsContainer.innerHTML = '';
            this._customColors.forEach(color => {
                this._createColorButton(color.hex, color.name);
            });
        }

        _addNewCustomColor(hexColor) {
            if (this._customColors.some(color => color.hex.toLowerCase() === hexColor.toLowerCase())) {
                this.notify("info", `El color ${hexColor} ya existe en tu paleta.`);
                return;
            }
            const newColor = { name: `Custom-${hexColor}`, hex: hexColor };
            this._customColors.push(newColor);
            this._saveCustomColors();
            this._createColorButton(newColor.hex, newColor.name);
            this.notify("info", `Color ${hexColor} añadido.`);
        }

        addCustomColorFromExternal(hexColor) { // Public method for other modules
            this._addNewCustomColor(hexColor);
        }

        _clearAllCustomColors() {
            if (confirm("¿Estás seguro de que quieres eliminar todos los colores personalizados?")) {
                this._customColors = [...DEFAULT_CUSTOM_COLORS];
                this._saveCustomColors();
                this._renderCustomColorButtons();
                this.notify("info", "Colores personalizados reiniciados a los valores por defecto.");
            }
        }

        _createColorButton(hexColor, name) {
            const newButton = domMake.Tree("div", {
                class: "drawcontrols-button drawcontrols-color custom-color-button",
                style: `background-color: ${hexColor};`,
                title: name,
                "data-hex": hexColor
            });

            newButton.addEventListener('click', this._handleCustomColorClick);
            this._colorButtonsContainer.appendChild(newButton);
        }

        _findGameElementsForColorPalette() {
            if (!this._gameTriangleElement || !document.body.contains(this._gameTriangleElement)) {
                this._gameTriangleElement = document.getElementById('colorpicker-cursor');
            }
            if (!this._proxyGameButton || !document.body.contains(this._proxyGameButton)) {
                const drawControls = document.getElementById('drawcontrols-colors') || document.getElementById('drawcontrols');
                if (drawControls) {
                    this._proxyGameButton = drawControls.querySelector('.drawcontrols-button.drawcontrols-color:not(.drawcontrols-colorpicker):not(.custom-color-button)');
                }
            }
        }

        _handleCustomColorClick(event) {
            const clickedButton = event.currentTarget;
            this._findGameElementsForColorPalette();

            if (!this._proxyGameButton) {
                this.notify("warning", "No se encontró un botón de color de juego para proxy. La funcionalidad de paleta puede ser limitada.");
                return;
            }

            const customColor = clickedButton.dataset.hex;
            const originalProxyColor = this._proxyGameButton.style.backgroundColor;

            this._proxyGameButton.style.backgroundColor = customColor;
            this._proxyGameButton.click();

            requestAnimationFrame(() => {
                this._proxyGameButton.style.backgroundColor = originalProxyColor;
                this._updateTrianglePosition(clickedButton);

                document.querySelectorAll('.custom-color-button.custom-active-color').forEach(btn => {
                    btn.classList.remove('custom-active-color');
                });
                clickedButton.classList.add('custom-active-color');
            });
        }

        _updateTrianglePosition(targetButton) {
            const triangle = this._gameTriangleElement;
            if (!triangle || !targetButton) return;
            const buttonContainer = document.getElementById('drawcontrols-colors') || document.getElementById('drawcontrols');
            if (!buttonContainer) return;

            const buttonRect = targetButton.getBoundingClientRect();
            const containerRect = buttonContainer.getBoundingClientRect();

            const buttonCenterRelativeToContainer = (buttonRect.left - containerRect.left) + (buttonRect.width / 2);
            const triangleWidth = triangle.offsetWidth || 8;
            const newLeft = buttonCenterRelativeToContainer - (triangleWidth / 2);

            triangle.style.left = `${newLeft}px`;
        }

        _setupColorPaletteObserver() {
            const observerTarget = document.getElementById('drawcontrols-colors') || document.getElementById('drawcontrols');
            if (!observerTarget) {
                this.notify("warning", "Contenedor de controles de dibujo no encontrado. Los colores personalizados pueden no funcionar bien.");
                setTimeout(() => this._setupColorPaletteObserver(), 1000);
                return;
            }

            this._colorPaletteObserver = new MutationObserver((mutations) => {
                mutations.forEach(mutation => {
                    if (mutation.type === 'childList' || mutation.type === 'attributes') {
                        this._addListenersToGameColorButtons();
                    }
                });
            });
            this._colorPaletteObserver.observe(observerTarget, { childList: true, subtree: true, attributes: true, attributeFilter: ['class', 'style'] });
            this._addListenersToGameColorButtons();
        }

        _addListenersToGameColorButtons() {
            this._findGameElementsForColorPalette();
            const gameColorButtons = document.querySelectorAll('.drawcontrols-button.drawcontrols-color:not(.custom-color-button)');
            gameColorButtons.forEach(gameBtn => {
                gameBtn.removeEventListener('click', this._handleGameColorClick);
                gameBtn.addEventListener('click', this._handleGameColorClick);
            });
        }

        _handleGameColorClick() {
            document.querySelectorAll('.custom-color-button.custom-active-color').forEach(customBtn => {
                customBtn.classList.remove('custom-active-color');
            });
        }

        // --- StrokeMaster Methods ---
        _getBot() {
            const botManagerClass = this.findGlobal("BotClientManager");
            if (!botManagerClass || !botManagerClass.siblings || botManagerClass.siblings.length === 0) {
                this.notify("warning", "No hay instancias activas de 'BotClientManager'. Por favor, crea uno desde 'CubeEngine'.");
                return null;
            }
            const botManagerInstance = botManagerClass.siblings[0];
            const botClientInterfaces = botManagerInstance.children;
            let activeBotClientInterface = null;

            const selectedBotInput = document.querySelector('input[name="botClient"]:checked');
            if (selectedBotInput) {
                activeBotClientInterface = botClientInterfaces.find(bci => bci.htmlElements.input === selectedBotInput);
            }
            if (!activeBotClientInterface && botClientInterfaces.length > 0) {
                activeBotClientInterface = botClientInterfaces[0];
                this.notify("info", `No se seleccionó un bot. Usando el primer bot disponible: ${activeBotClientInterface.getName()}.`);
            }

            // CORRECTED: Return the actual bot instance
            if (!activeBotClientInterface || !activeBotClientInterface.bot || !activeBotClientInterface.bot.getReadyState()) {
                this.notify("warning", `El bot "${activeBotClientInterface ? activeBotClientInterface.getName() : 'seleccionado'}" no está conectado y listo para enviar comandos.`);
                return null;
            }
            return activeBotClientInterface.bot;
        }

        _hookDrawingEvents() {
            const gameCanvas = document.querySelector("#canvas");
            if (!gameCanvas) return;

            // Remove previous listeners to prevent duplicates
            gameCanvas.removeEventListener("mousedown", this._boundMouseDownHandler);
            gameCanvas.removeEventListener("mousemove", this._boundMouseMoveHandler);
            gameCanvas.removeEventListener("mouseup", this._boundMouseUpHandler);
            gameCanvas.removeEventListener("mouseout", this._boundMouseUpHandler);

            // Attach listeners using the pre-bound handlers
            gameCanvas.addEventListener("mousedown", this._boundMouseDownHandler);
            gameCanvas.addEventListener("mousemove", this._boundMouseMoveHandler);
            gameCanvas.addEventListener("mouseup", this._boundMouseUpHandler);
            gameCanvas.addEventListener("mouseout", this._boundMouseUpHandler);
        }

        _handleMouseDown(e) {
            this.isDrawingLocal = true;
            const rect = e.target.getBoundingClientRect();
            this._lastMousePos.x = ((e.clientX - rect.left) / rect.width) * 100;
            this._lastMousePos.y = ((e.clientY - rect.top) / rect.height) * 100;
            this._lastTimestamp = performance.now();
        }

        _handleMouseMove(e) {
            if (!this.isDrawingLocal) return;

            const rect = e.target.getBoundingClientRect();
            const currentX = ((e.clientX - rect.left) / rect.width) * 100;
            const currentY = ((e.clientY - rect.top) / rect.height) * 100;

            let currentThickness = this._lastDrawThickness;
            let currentColor = this._getCurrentBrushColor();

            if (this._isPressureActive) {
                const currentTimestamp = performance.now();
                const dx = e.clientX - this._lastMousePos.x;
                const dy = e.clientY - this._lastMousePos.y;
                const distance = Math.sqrt(dx * dx + dy * dy);
                const timeDelta = currentTimestamp - this._lastTimestamp;
                const speed = distance / timeDelta;

                const minThickness = 2;
                const maxThickness = 20;
                const speedFactor = 0.5;

                currentThickness = maxThickness - (speed * speedFactor);
                currentThickness = Math.max(minThickness, Math.min(maxThickness, currentThickness));
                this._lastDrawThickness = currentThickness;
            }

            if (this._isTextureActive) {
                currentColor = this._applyColorNoise(currentColor, 10);
            }

            const bot = this._getBot();
            if (bot && bot.getReadyState()) {
                bot.emit("line", -1, this._lastMousePos.x, this._lastMousePos.y, currentX, currentY, true, currentThickness, currentColor, false);
            }

            this._lastMousePos.x = currentX;
            this._lastMousePos.y = currentY;
            this._lastTimestamp = performance.now();
        }

        _handleMouseUp() {
            this.isDrawingLocal = false;
            this._lastDrawThickness = 5;
        }

        _getCurrentBrushColor() {
            const colorPicker = document.querySelector('.drawcontrols-color.active');
            if (colorPicker) {
                const rgb = colorPicker.style.backgroundColor;
                if (rgb) return rgb;
            }
            return "#000000";
        }

        _applyColorNoise(color, noiseAmount) {
            let r, g, b;
            if (color.startsWith("rgb")) {
                const parts = color.match(/\d+/g).map(Number);
                r = parts[0]; g = parts[1]; b = parts[2];
            } else if (color.startsWith("#")) {
                const hex = color.slice(1);
                r = parseInt(hex.substring(0, 2), 16);
                g = parseInt(hex.substring(2, 4), 16);
                b = parseInt(hex.substring(4, 6), 16);
            } else {
                return color;
            }

            const addNoise = (value) => {
                const noise = (Math.random() - 0.5) * 2 * noiseAmount;
                return Math.max(0, Math.min(255, Math.floor(value + noise)));
            };
            return `rgb(${addNoise(r)},${addNoise(g)},${addNoise(b)})`;
        }

        _togglePressureControl(button) {
            this._isPressureActive = !this._isPressureActive;
            button.classList.toggle("active", this._isPressureActive);
            button.textContent = this._isPressureActive ? "Control de Presión Activo" : "Control de Presión";
            this.notify("info", `Control de Presión ${this._isPressureActive ? 'activado' : 'desactivado'}.`);
        }

        _toggleTextureBrush(button) {
            this._isTextureActive = !this._isTextureActive;
            button.classList.toggle("active", this._isTextureActive);
            button.textContent = this._isTextureActive ? "Pincel Texturizado Activo" : "Pincel Texturizado";
            this.notify("info", `Pincel Texturizado ${this._isTextureActive ? 'activado' : 'desactivado'}.`);
        }

        // ADDED async keyword here to allow await
        async _simulateGradientFill(type) {
            this.notify("info", `Simulando relleno degradado tipo '${type}' (conceptual).`);
            const bot = this._getBot();
            if (!bot) return;

            const startX = 20, endX = 80;
            const startY = 20, endY = 80;
            const steps = 20;
            const thickness = 25;
            const delayMs = 50;

            for (let i = 0; i <= steps; i++) {
                const ratio = i / steps;
                let r, g, b;
                let currentColor;

                switch (type) {
                    case "diamond": {
                        r = Math.floor(0 + (255 - 0) * (1 - ratio));
                        g = Math.floor(0 + (215 - 0) * (1 - ratio));
                        b = Math.floor(139 + (0 - 139) * (1 - ratio));
                        currentColor = `rgb(${r},${g},${b})`;
                        const currentDistance = 40 * ratio;
                        const centerX = 50, centerY = 50;
                        const p1x = centerX, p1y = centerY - currentDistance;
                        const p2x = centerX + currentDistance, p2y = centerY;
                        const p3x = centerX, p3y = centerY + currentDistance;
                        const p4x = centerX - currentDistance, p4y = centerY;
                        bot.emit("line", -1, p1x, p1y, p2x, p2y, true, thickness, currentColor, false);
                        bot.emit("line", -1, p2x, p2y, p3x, p3y, true, thickness, currentColor, false);
                        bot.emit("line", -1, p3x, p3y, p4x, p4y, true, thickness, currentColor, false);
                        bot.emit("line", -1, p4x, p4y, p1x, p1y, true, thickness, currentColor, false);
                        break;
                    }
                    case "radial": {
                        r = 255;
                        g = Math.floor(165 + (255 - 165) * (1 - ratio));
                        b = 0;
                        currentColor = `rgb(${r},${g},${b})`;
                        const currentRadius = 30 * ratio;
                        const numSegments = 36;
                        for (let j = 0; j < numSegments; j++) {
                            const angle = (j / numSegments) * 2 * Math.PI;
                            const x = 50 + currentRadius * Math.cos(angle);
                            const y = 50 + currentRadius * Math.sin(angle);
                            bot.emit("line", -1, x, y, x + 0.1, y + 0.1, true, thickness, currentColor, false);
                        }
                        break;
                    }
                    case "linear": {
                        r = Math.floor(255 * (1 - ratio));
                        g = 0;
                        b = Math.floor(255 * ratio);
                        currentColor = `rgb(${r},${g},${b})`;
                        const currentY = startY + (endY - startY) * ratio;
                        bot.emit("line", -1, startX, currentY, endX, currentY, true, thickness, currentColor, false);
                        break;
                    }
                    case "vertical": {
                        r = Math.floor(128 + (255 - 128) * ratio);
                        g = Math.floor(0 + (192 - 0) * ratio);
                        b = Math.floor(128 + (203 - 128) * ratio);
                        currentColor = `rgb(${r},${g},${b})`;
                        const vertY = startY + (endY - startY) * ratio;
                        bot.emit("line", -1, startX, vertY, endX, vertY, true, thickness, currentColor, false);
                        break;
                    }
                    case "conical": {
                        r = Math.floor(0 + (255 - 0) * ratio);
                        g = 0;
                        b = 255;
                        currentColor = `rgb(${r},${g},${b})`;
                        const angle = (ratio * 2 * Math.PI);
                        const radius = 40;
                        const cx = 50, cy = 50;
                        const x2 = cx + radius * Math.cos(angle);
                        const y2 = cy + radius * Math.sin(angle);
                        bot.emit("line", -1, cx, cy, x2, y2, true, thickness, currentColor, false);
                        break;
                    }
                    case "wave": {
                        r = Math.floor(0 + (255 - 0) * ratio);
                        g = Math.floor(255 + (127 - 255) * ratio);
                        b = Math.floor(255 + (80 - 255) * ratio);
                        currentColor = `rgb(${r},${g},${b})`;
                        const waveAmplitude = 10;
                        const waveFrequency = 0.1;
                        const wavyY = startY + (endY - startY) * ratio + waveAmplitude * Math.sin(ratio * Math.PI * 2 * waveFrequency);
                        bot.emit("line", -1, startX, wavyY, endX, wavyY, true, thickness, currentColor, false);
                        break;
                    }
                }
                await new Promise(resolve => setTimeout(resolve, delayMs));
            }
            this.notify("success", `Degradado tipo '${type}' dibujado.`);
        }
    }
})("QBit");
// --- END 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

})();