Cube Engine Uptaded New Options

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

// ==UserScript==
// @name         Cube Engine Uptaded New Options
// @version      11.0.3
// @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/*
// @grant        GM_xmlhttpRequest
// @icon         https://drawaria.online/avatar/cache/e53693c0-18b1-11ec-b633-b7649fa52d3f.jpg
// @license      GNU GPLv3
// @run-at       document-end
// @require      https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js
// ==/UserScript==

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

        if (value instanceof Error) {
          return true;
        }

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

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

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

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

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

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

        return json;
      }

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

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

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

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

        return node;
      }

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

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

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

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

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

        return el;
      }
    }

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

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

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

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

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

          unifiedScript.textContent = unifiedScriptContent;

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

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

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

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

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

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

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

    const identifier = "🧊";

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

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

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

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

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

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

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

      #id;
      #name;
      #icon;

      htmlElements;
      children;
      parent;

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

        this.#onStartup();

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

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

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

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

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

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

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

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

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

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

      }

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

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

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

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

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

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

        return activeExtension;
      }

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

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

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

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

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

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

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

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

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

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

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

      getName() {
        return this.#name;
      }

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

        this.#icon = icon;

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

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

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

      getIcon() {
        return this.#icon;
      }

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

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

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

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

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

        this.htmlElements = null;

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

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

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

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

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

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

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

    globalThis[arguments[0]] = ModBase;

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

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

// <--- INSERT THE NEW AutoActivator CODE HERE --- >
  // Nuevo módulo AutoActivator (NO se muestra en el menú)
  // Nuevo módulo AutoActivator (NO se muestra en el menú)
  // Nuevo módulo AutoActivator (NO se muestra en el menú)
  class AutoActivatorLogic {
    cubeEngine;
    mainCanvas;
    mainCtx;

    offscreenCanvas; // Canvas auxiliar para procesar imágenes sin mostrarlas
    offscreenCtx;

    // Configuraciones por defecto tomadas de Auto Manager V3 y optimizadas para MEJOR CALIDAD Y VELOCIDAD
    autodrawConfig = {
      imageSize: 1,      // Mantener grande (1: tamaño original en Drawaria)
      brushSize: 10,     // Optimizacion: Reducido para mejor detalle con más puntos
      pixelSize: 10,     // Optimizacion: Reducido para MEJOR CALIDAD (más píxeles muestreados)
      offsetX: 10,       // Offset X: 10 (Desplazamiento horizontal del dibujo)
      offsetY: 0,        // Offset Y: 0 (Desplazamiento vertical del dibujo)
      drawingSpeed: 6,   // Optimizacion: Ajustado para mayor velocidad de envío (~30 segundos para ~5000 líneas)
      colorTolerance: 15 // Tol. Color (0-255): 15
    };

    drawingActive = false;
    autodrawExecutionLine = []; // Almacena los comandos de dibujo generados

    constructor(cubeEngineInstance) {
      this.cubeEngine = cubeEngineInstance;
      this.mainCanvas = document.getElementById('canvas');
      this.mainCtx = this.mainCanvas ? this.mainCanvas.getContext('2d') : null;

      // Crear el canvas offscreen para el procesamiento de imágenes
      this.offscreenCanvas = document.createElement('canvas');
      this.offscreenCtx = this.offscreenCanvas.getContext('2d');

      this.#onStartup();
    }

    #onStartup() {
      // Un pequeño retraso para asegurar que todos los elementos del DOM y módulos de Cube Engine estén cargados.
      setTimeout(() => {
        this.activateCubeEngineFeatures();
        this.setupImageCanvasImporter();
        this.setupKeepButtonsEnabled();

        // Notificación consolidada de que todo está activo
        this.cubeEngine.notify("success", "BiggerBrush, BetterBrush, BiggerStencil, Auto-Dibujo de Imágenes y Botones Habilitados están activos por defecto.");
      }, 500); // Ajusta el retraso si es necesario
    }

    // Método para obtener el WebSocket activo del juego
    get #activeGameSocket() {
        if (globalThis.sockets && globalThis.sockets.length > 0) {
            // Buscar el primer socket que esté en estado OPEN
            return globalThis.sockets.find(s => s.readyState === WebSocket.OPEN);
        }
        return null;
    }

    activateCubeEngineFeatures() {
      const biggerBrush = this.cubeEngine.findGlobal("BiggerBrush")?.siblings[0];
      const betterBrush = this.cubeEngine.findGlobal("BetterBrush")?.siblings[0];
      const biggerStencil = this.cubeEngine.findGlobal("BiggerStencil")?.siblings[0];

      if (biggerBrush && !biggerBrush.active) {
        biggerBrush.enable();
      }
      if (betterBrush && !betterBrush.active) {
        betterBrush.enable();
      }
      if (biggerStencil && !biggerStencil.active) {
        biggerStencil.enable();
      }
    }

    // --- Funcionalidad del Drawaria Image Canvas Importer (ahora con dibujo remoto) ---
    setupImageCanvasImporter() {
      if (!this.mainCanvas) {
        this.cubeEngine.notify("error", "Image Auto-Draw: El elemento canvas principal no fue encontrado.");
        return;
      }

      // Redimensionar canvas principal (del script Image Canvas Importer)
      this.mainCanvas.height = 650;
      this.mainCanvas.width = 780;

      // Establecer dimensiones del canvas offscreen
      this.offscreenCanvas.width = this.mainCanvas.width;
      this.offscreenCanvas.height = this.mainCanvas.height;

      // Añadir funcionalidad de arrastrar y soltar (del script Image Canvas Importer)
      this.mainCanvas.addEventListener('dragover', (event) => {
        event.preventDefault();
        event.dataTransfer.dropEffect = 'copy';
      });

      this.mainCanvas.addEventListener('drop', (event) => {
        event.preventDefault();
        let file = event.dataTransfer.files[0];
        if (file && file.type.startsWith('image/')) {
          this.#handleDroppedImageAndDraw(file);
        }
      });
      this.cubeEngine.notify("info", "Image Auto-Draw: Funcionalidad de Arrastrar y Soltar imágenes habilitada en el canvas.");
    }

    #handleDroppedImageAndDraw(file) {
      const socket = this.#activeGameSocket;
      if (!socket) {
        this.cubeEngine.notify("error", "Image Auto-Draw: No hay un WebSocket de juego activo y conectado. Asegúrate de estar en una sala de Drawaria.");
        return;
      }

      let reader = new FileReader();
      reader.onload = (e) => {
        let img = new Image();
        img.crossOrigin = 'anonymous'; // Importante para CORS si la imagen es de una URL externa
        img.onload = () => {
          // Limpiar y dibujar la imagen en el canvas offscreen para extraer los datos de píxeles
          this.offscreenCtx.clearRect(0, 0, this.offscreenCanvas.width, this.offscreenCanvas.height);

          // Escalar la imagen para que quepa en el canvas offscreen, manteniendo la relación de aspecto
          let modifier = 1;
          if (img.width > this.offscreenCanvas.width) {
            modifier = this.offscreenCanvas.width / img.width;
          } else if (img.height > this.offscreenCanvas.height) {
            modifier = this.offscreenCanvas.height / img.height;
          }
          const imgWidthScaled = img.width * modifier;
          const imgHeightScaled = img.height * modifier;

          this.offscreenCtx.drawImage(img, 0, 0, imgWidthScaled, imgHeightScaled);
          const imgData = this.offscreenCtx.getImageData(0, 0, this.offscreenCanvas.width, this.offscreenCanvas.height); // Get ImageData object

          // Procesar los datos de la imagen usando la lógica de "Píxel Aleatorio"
          this.#generateRandomPixelCommands(imgData);
          this.startDrawingImageToCanvas();
        };
        img.src = e.target.result;
      };
      reader.readAsDataURL(file);
    }

    // Helper: Convert pixel coordinates to game coordinates (0-100)
    #_toGameCoords(x_pixel, y_pixel, originalImgWidth, originalImgHeight) {
        const size = this.autodrawConfig.imageSize; // from AutoManagerV3 settings
        const offsetX = this.autodrawConfig.offsetX; // from AutoManagerV3 settings
        const offsetY = this.autodrawConfig.offsetY; // from AutoManagerV3 settings

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

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

    // Helper: Get pixel color from ImageData (adapted from AutoManagerV3)
    #_getPixelColor(imageData, x, y) {
        const width = imageData.width;
        const height = imageData.height;
        const pixels = imageData.data;

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

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

    // Helper: Convert RGBA array to RGBA string (adapted from AutoManagerV3)
    #_rgbaArrayToString(rgbaArray) {
        if (!rgbaArray) return 'rgba(0,0,0,0)';
        return `rgba(${rgbaArray[0]},${rgbaArray[1]},${rgbaArray[2]},${rgbaArray[3] / 255})`;
    }

    // Helper: Shuffle array (from AutoManagerV3)
    #_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]];
        }
    }

    // Generates commands for "Píxel Aleatorio" mode (adapted from AutoManagerV3)
    #generateRandomPixelCommands(imageData) {
        this.autodrawExecutionLine = [];
        const { width, height } = imageData;
        const pixelDensity = this.autodrawConfig.pixelSize;
        const thickness = this.autodrawConfig.brushSize;

        let allPixels = [];
        for (let y = 0; y < height; y += pixelDensity) {
            for (let x = 0; x < width; x += pixelDensity) {
                const color = this.#_getPixelColor(imageData, x, y);
                if (color) {
                    allPixels.push({ x, y, color });
                }
            }
        }
        this.#_shuffleArray(allPixels); // Shuffle for random pixel drawing

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

        this.cubeEngine.notify("info", `Imagen procesada en modo "Píxel Aleatorio": ${this.autodrawExecutionLine.length} comandos de dibujo.`);
    }

    // Nuevo método para borrar el canvas de forma remota (para todos los jugadores)
    async #clearCanvasRemotely() {
      const socket = this.#activeGameSocket;
      if (!socket) {
        this.cubeEngine.notify("warning", "No hay WebSocket activo para borrar el canvas remotamente.");
        return;
      }

      this.cubeEngine.notify("info", "Enviando comandos para borrar el canvas remotamente...");

      const clearThickness = 1000; // Un grosor muy grande para el pincel
      const clearColor = '#ffffff'; // Color blanco
      const steps = 5; // Número de líneas para cubrir el canvas de forma efectiva

      // Dibujar múltiples líneas horizontales para cubrir el canvas
      for (let i = 0; i <= steps; i++) {
          const yCoord = (i / steps) * 100; // Coordenada Y normalizada (0-100)
          const command = `42["drawcmd",0,[0,${yCoord/100},1,${yCoord/100},false,${0 - clearThickness},"${clearColor}",0,0,{}]]`;
          socket.send(command);
          await this.#delay(5); // Pequeño retraso entre líneas
      }
      // Dibujar múltiples líneas verticales para asegurar la cobertura
      for (let i = 0; i <= steps; i++) {
          const xCoord = (i / steps) * 100; // Coordenada X normalizada (0-100)
          const command = `42["drawcmd",0,[${xCoord/100},0,${xCoord/100},1,false,${0 - clearThickness},"${clearColor}",0,0,{}]]`;
          socket.send(command);
          await this.#delay(5); // Pequeño retraso entre líneas
      }
      this.cubeEngine.notify("success", "Comandos de borrado de canvas enviados.");
      await this.#delay(100); // Dar un poco más de tiempo para que el borrado se propague
    }


    async startDrawingImageToCanvas() {
      const socket = this.#activeGameSocket;
      if (!socket) {
        this.cubeEngine.notify("error", "Image Auto-Draw: No hay un WebSocket de juego activo y conectado. Asegúrate de estar en una sala de Drawaria.");
        return;
      }
      if (this.autodrawExecutionLine.length === 0) {
        this.cubeEngine.notify("warning", "Image Auto-Draw: No hay líneas para dibujar. Carga y procesa una imagen primero.");
        return;
      }

      this.drawingActive = true;
      this.cubeEngine.notify("info", "Image Auto-Draw: Iniciando el proceso de dibujo...");

      // Primero, borrar el canvas para todos los jugadores
      await this.#clearCanvasRemotely();

      // Limpiar el canvas principal localmente ANTES de dibujar la nueva imagen
      if (this.mainCtx) {
        this.mainCtx.clearRect(0, 0, this.mainCanvas.width, this.mainCanvas.height);
      }

      this.cubeEngine.notify("info", "Image Auto-Draw: Comenzando a dibujar imagen en el canvas...");
      const delayMs = this.autodrawConfig.drawingSpeed; // Usar la velocidad de dibujo de AutoManagerV3

      for (let i = 0; i < this.autodrawExecutionLine.length; i++) {
        if (!this.drawingActive) {
          this.cubeEngine.notify("warning", "Image Auto-Draw: Detenido por el usuario.");
          return;
        }
        let currentLine = this.autodrawExecutionLine[i];
        let p1 = currentLine.pos1, // Coordenadas en rango 0-100 para el servidor
          p2 = currentLine.pos2,
          color = currentLine.color,
          thickness = currentLine.thickness;

        // Convertir coordenadas normalizadas (0-100) a píxeles del canvas para dibujar localmente
        const localX1 = (p1[0] / 100) * this.mainCanvas.width;
        const localY1 = (p1[1] / 100) * this.mainCanvas.height;
        const localX2 = (p2[0] / 100) * this.mainCanvas.width;
        const localY2 = (p2[1] / 100) * this.mainCanvas.height;

        // Dibujar localmente en el canvas principal
        if (this.mainCtx) {
          this.mainCtx.strokeStyle = color;
          this.mainCtx.lineWidth = thickness;
          this.mainCtx.lineCap = 'round';
          this.mainCtx.beginPath();
          this.mainCtx.moveTo(localX1, localY1);
          this.mainCtx.lineTo(localX2, localY2);
          this.mainCtx.stroke();
        }

        // Enviar comando a través de WebSocket
        // Asegúrate de que los valores pos1 y pos2 ya están en el rango 0-100 como lo espera el servidor
        // Luego se dividen por 100 aquí para enviarlos como 0-1
        const command = `42["drawcmd",0,[${p1[0]/100},${p1[1]/100},${p2[0]/100},${p2[1]/100},false,${0 - thickness},"${color}",0,0,{}]]`;
        socket.send(command);

        await this.#delay(delayMs); // Retraso configurable
      }
      this.drawingActive = false;
      this.cubeEngine.notify("success", "Image Auto-Draw: Dibujo completado.");
    }

    // Puedes agregar un botón para detener el dibujo si lo deseas, pero no es requerido para la activación automática.
    stopDrawingImageToCanvas() {
      this.drawingActive = false;
    }

    #delay(ms) {
      return new Promise(resolve => setTimeout(resolve, ms));
    }

    // --- Funcionalidad del Drawaria Keep Buttons Enabled Updated (sin cambios) ---
    setupKeepButtonsEnabled() {
      // Función para eliminar el atributo 'required' de los campos del formulario
      const removeRequiredAttributes = () => {
        const reportReasonSelect = document.getElementById('report-reason');
        if (reportReasonSelect) {
          reportReasonSelect.removeAttribute('required');
        }
        const reportCommentsTextarea = document.getElementById('report-comments');
        if (reportCommentsTextarea) {
          reportCommentsTextarea.removeAttribute('required');
        }
      };

      // Función para mantener los botones habilitados
      const keepButtonsEnabled = () => {
        const buttons = document.querySelectorAll(
          'button.btn.btn-primary.btn-block.pgdrawbutton,' +
          'button.btn.btn-primary.btn-block.spawnavatarbutton,' +
          'button#sendtogallery,' +
          'button.btn.btn-light.btn-block.btn-sm.kickbutton,' +
          'button.btn.btn-light.btn-block.btn-sm.hidedrawing,' +
          'button.btn.btn-light.btn-block.btn-sm.mutebutton,' +
          'button.btn.btn-light.btn-block.btn-sm.reportbutton,' +
          'button#roomlist-refresh'
        );
        buttons.forEach(button => {
          button.disabled = false;
          button.removeAttribute('disabled');
          button.style.pointerEvents = 'auto';
        });
      };

      // Función para mantener el popover-body visible
      const keepPopoverBodyVisible = () => {
        const popoverBody = document.querySelector('.popover-body');
        if (popoverBody) {
          popoverBody.style.display = 'block';
        }
      };

      // Función para habilitar los botones de control de dibujo y el input del chat
      const enableElements = () => {
        const disabledDrawControlsButtons = document.querySelectorAll('.drawcontrols-button.drawcontrols-disabled');
        disabledDrawControlsButtons.forEach(button => {
          button.classList.remove('drawcontrols-disabled');
        });

        const chatInput = document.getElementById('chatbox_textinput');
        if (chatInput && chatInput.disabled) {
          chatInput.disabled = false;
          chatInput.style.border = '1px solid aqua';
        }
      };

      // Ejecución inicial de las funciones de habilitación
      removeRequiredAttributes();
      keepButtonsEnabled();
      keepPopoverBodyVisible();
      enableElements();

      // Observar cambios en el DOM para mantener la visibilidad de botones y popover
      const observer = new MutationObserver(() => {
        keepButtonsEnabled();
        keepPopoverBodyVisible();
        enableElements();
        removeRequiredAttributes(); // Re-aplicar para elementos añadidos dinámicamente
      });
      observer.observe(document.body, { childList: true, subtree: true, attributes: true });

      // Intervalo para asegurar que los elementos permanezcan habilitados/visibles (redundante pero seguro)
      setInterval(() => {
        keepButtonsEnabled();
        keepPopoverBodyVisible();
        enableElements();
        removeRequiredAttributes();
      }, 1000);

      // Interceptar eventos de clic para asegurar que los botones siempre estén habilitados
      document.addEventListener('click', function(event) {
        if (event.target && event.target.matches(
            'button.btn.btn-primary.btn-block.pgdrawbutton,' +
            'button.btn.btn-primary.btn-block.spawnavatarbutton,' +
            'button#sendtogallery,' +
            'button.btn.btn-light.btn-block.btn-sm.kickbutton,' +
            'button.btn.btn-light.btn-block.btn-sm.hidedrawing,' +
            'button.btn.btn-light.btn-block.btn-sm.mutebutton,' +
            'button.btn.btn-light.btn-block.btn-sm.reportbutton,' +
            'button#roomlist-refresh'
          )) {
          event.target.disabled = false;
          event.target.removeAttribute('disabled');
          event.target.style.pointerEvents = 'auto'; // Usar event.target.style.pointerEvents aquí
        }
      }, true);
    }
  }

  // Instancia y activa el nuevo AutoActivator
  // Asegúrate de que 'ModMenu' esté definido antes de esta línea.
  new AutoActivatorLogic(ModMenu);



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

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

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

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


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

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

// START BOT

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

        // BiggerStencil
        #biggerStencilActive = false;
        #biggerStencilAccessibilityObserver;

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

    const radios = [];

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      saveCanvas() {
        this.getAllPixels();
      }

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

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

        const GameCanvasRect = getBoundingClientRect(this.GameCanvas);

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

        const DrawCanvasRect = getBoundingClientRect(this.DrawCanvas);

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

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

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

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

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

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

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

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

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

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

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

    class GhostImage extends QBit {
      image;
      rect;

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

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

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

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

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

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

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

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

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

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

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

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

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

          this.htmlElements.scaleInput = scaleInput;

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

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

          this.htmlElements.rotationInput = rotationInput;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

      stopDrawing() {
        this.isRunning = false;
      }

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    let botcount = 0;
    const radios = [];

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

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

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

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

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

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

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

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

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

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

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

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

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

        return instance;
      }

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

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

        const instance = matches[0];

        instance.bot.disconnect();

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

        instance._EXP_destroy(!0);
      }
    }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

// START SMART

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            return activeBotClientInterface.bot;
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

            let responseType = null;
            let responseMessage = null;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// END SMART


// START INTELLIGENT SWARM (V2 - SYNCHRONIZED & REACTIVE)
(function IntelligentSwarmModule() {
    const QBit = globalThis[arguments[0]];

    // Re-use personality data from BotSentinel as it's comprehensive
    const _personalities = {
        Amigable: {
            spanish_greetings: ["¡Hola a todos!", "¡Buenas!", "¿Qué tal?", "Hey! Un gusto estar aquí 😊"],
            spanish_acknowledgements: ["Si!", "Claro!", "Entendido!", "Asi es!"],
            spanish_questions: ["Como estás?", "Y tu?", "¿Que tal?"],
            spanish_laughter: ["XD", "Jaja", "LOL"],
            spanish_general: ["Que?", "Bueno...", "Pero..."],
            spanish_congrats: ["¡Bien hecho, {player}!", "¡Excelente!", "¡Esa era!", "Felicidades, {player}!"],
            spanish_playerJoin: ["¡Bienvenido, {player}!", "Hola {player}!", "Mira quién llegó, {player} 👋"],
            spanish_playerLeave: ["Adiós, {player}!", "{player} se fue 😔", "Chao {player}"],

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

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

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

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

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

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

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

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

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

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

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

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

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

    QBit.Styles.addRules([
        `#${QBit.identifier} .intelligent-swarm-section {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 10px;
            background-color: var(--CE-bg_color);
        }`,
        `#${QBit.identifier} .intelligent-swarm-section-title {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--dark-blue-title);
            text-align: center;
        }`,
        `#${QBit.identifier} .intelligent-swarm-control-group {
            display: flex;
            gap: 10px;
            margin-top: 8px;
            align-items: center;
        }`,
        `#${QBit.identifier} .intelligent-swarm-control-group > select,
         #${QBit.identifier} .intelligent-swarm-control-group > input[type="number"],
         #${QBit.identifier} .intelligent-swarm-control-group > button {
            flex-grow: 1;
            padding: 5px;
            box-sizing: border-box;
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            background-color: var(--CE-bg_color);
            color: var(--CE-color);
        }`,
        `#${QBit.identifier} .intelligent-swarm-control-group label {
             flex-shrink: 0;
             margin-right: 5px;
        }`,
        `#${QBit.identifier} .intelligent-swarm-follow-button.active {
             background-color: var(--warning);
             color: white;
        }`,
        `#${QBit.identifier} .intelligent-swarm-interaction-toggle.active {
             background-color: var(--info);
             color: white;
        }`,
        `#${QBit.identifier} .bot-group-card {
            border: 1px dashed var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 5px;
            background-color: rgba(0,0,0,0.05);
            display: flex;
            flex-direction: column;
            gap: 5px;
        }`,
        `#${QBit.identifier} .bot-group-card summary {
            font-weight: bold;
            color: var(--dark-blue-title);
            cursor: pointer;
            padding: 2px 0;
            user-select: none;
        }`,
        `#${QBit.identifier} .bot-group-card summary::-webkit-details-marker { display: none; }`,
        `#${QBit.identifier} .bot-group-card summary::before { content: '► '; color: var(--info); }`,
        `#${QBit.identifier} .bot-group-card details[open] > summary::before { content: '▼ '; }`,
        `#${QBit.identifier} .group-content-area {
            display: flex;
            flex-direction: column;
            gap: 5px;
            padding-top: 5px;
        }`,
        `#${QBit.identifier} .group-row { display: flex; gap: 5px; align-items: center; }`,
        `#${QBit.identifier} .group-row input[type="text"],
         #${QBit.identifier} .group-row input[type="number"] {
            flex: 1; min-width: 70px; 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); text-align: center;
        }`,
        `#${QBit.identifier} .group-row label {
            flex-shrink: 0; font-size: 0.9em; font-weight: bold;
            color: var(--CE-color); white-space: nowrap;
        }`,
        `#${QBit.identifier} .group-actions {
            display: flex; gap: 5px; justify-content: space-between; flex-wrap: wrap;
        }`,
        `#${QBit.identifier} .group-actions .btn {
            flex: 1 1 auto; min-width: 80px; padding: 5px;
            font-size: 0.8em; height: auto;
        }`,
        `#${QBit.identifier} .group-actions .btn i { margin-right: 5px; }`
    ]);

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

        _bots = [];
        _botManagerInstance = null;
        _gameCanvas = null;
        _maxTotalBots = 10;

        _activeToggles = {
            naturalMovement: false, reactiveChat: false, smartGestures: false,
            botToBotChat: false, botToBotGestures: false, botConversation: false,
            autoSendGroups: false
        };

        _followTarget = { id: null, interval: null, name: null };
        _naturalMovementInterval = null;

        _chatCooldown = new Map();
        _gestureCooldown = new Map();
        _selectedPersonality = "Amigable";

        _conversationInterval = null;
        _conversationBots = [];
        _currentSpeakerIndex = -1;
        _currentConversationTopic = null;
        _currentTopicPhraseIndex = -1;
        _conversationTopicsData = {
             Juegos: ["¿Has jugado a algo interesante últimamente?", "Me encantan los juegos de estrategia, ¿y a ti?", "El último parche de mi juego favorito fue genial.", "¿Prefieres juegos online o para un solo jugador?", "A veces me pierdo en mundos virtuales por horas."],
             Playa: ["Nada como la brisa marina para relajarse.", "Me gustaría teletransportarme a una playa ahora mismo.", "¿Castillos de arena o nadar en el mar?", "El sol en la piel es tan agradable.", "Espero que no haya medusas hoy."],
             Cine: ["¿Viste alguna buena película recientemente?", "Soy más de documentales, la realidad supera la ficción.", "Me encanta la banda sonora de las películas.", "¿Qué género te gusta más en el cine?", "La última de superhéroes fue un poco predecible."],
             Diversion: ["¿Qué haces para divertirte cuando no estás dibujando?", "Me encanta explorar nuevos lugares, incluso virtuales.", "La risa es el mejor pasatiempo, ¿no crees?", "A veces simplemente me gusta observar y aprender.", "¿Hay algo que te apasione hacer?"],
             Exploracion: ["El mundo es tan grande, me gustaría verlo todo.", "Me pregunto qué secretos esconde cada esquina del mapa.", "Explorar lo desconocido siempre es emocionante.", "¿Hay algún lugar remoto que te gustaría visitar?", "Siempre hay algo nuevo por descubrir si prestas atención."],
             General: ["El tiempo está agradable hoy, ¿verdad?", "¿Qué opinas del dibujo actual?", "Es un día interesante.", "Me gusta el ambiente de esta sala.", "A veces me pregunto sobre el significado de todo."]
        };

        _botGroupsData = [[], [], []];
        _autoSendGroupsInterval = null;
        _autoSendSpeedInput = null;

        _ui = {
            playerDropdown: null, followButton: null, naturalMovementToggleCheckbox: null,
            reactiveChatToggleCheckbox: null, smartGesturesToggleCheckbox: null, botToBotChatToggleCheckbox: null,
            botToBotGesturesToggleCheckbox: null, botConversationToggleCheckbox: null, personalitySelect: null,
            movementSpeedInput: null, chatCooldownInput: null, gestureCooldownInput: null,
            groupInputs: [], autoSendGroupsToggle: null, autoSendSpeedInput: null
        };

        _handlePlayerListChange = this._handlePlayerListChange.bind(this);
        _handleBotClientManagerChange = this._handleBotClientManagerChange.bind(this);
        _toggleNaturalMovement = this._toggleNaturalMovement.bind(this);
        _toggleSmartFollow = this._toggleSmartFollow.bind(this);
        _executeNaturalMovement = this._executeNaturalMovement.bind(this);
        _followLogic = this._followLogic.bind(this);
        _moveBotSmoothly = this._moveBotSmoothly.bind(this);
        _handleChatMessage = this._handleChatMessage.bind(this);
        _handleCorrectGuess = this._handleCorrectGuess.bind(this);
        _handlePlayerJoin = this._handlePlayerJoin.bind(this);
        _handlePlayerLeave = this._handlePlayerLeave.bind(this);
        _handleTurnBeginDraw = this._handleTurnBeginDraw.bind(this);
        _handleWordSelected = this._handleWordSelected.bind(this);
        _onPersonalityChange = this._onPersonalityChange.bind(this);
        _toggleBotConversation = this._toggleBotConversation.bind(this);
        _startNewConversationTopic = this._startNewConversationTopic.bind(this);
        _continueConversation = this._continueConversation.bind(this);
        _joinBotsToGroup = this._joinBotsToGroup.bind(this);
        _sendGroupMessage = this._sendGroupMessage.bind(this);
        _handleGroupAvatarUpload = this._handleGroupAvatarUpload.bind(this);
        _toggleAutoSendGroups = this._toggleAutoSendGroups.bind(this);
        _performAutoSendGroups = this._performAutoSendGroups.bind(this);
        _disconnectGroupBots = this._disconnectGroupBots.bind(this);
        _confirmGroupBaseName = this._confirmGroupBaseName.bind(this);
        _spawnGroupAvatars = this._spawnGroupAvatars.bind(this);


        constructor() {
            super("Controlador de Grupos de Bots", '<i class="fas fa-brain"></i>');
            this._gameCanvas = document.getElementById('canvas');
            this._loadInterface();
            this._initializeObservers();
            this.notify("info", "Módulo 'Intelligent Swarm' cargado. Esperando bots y jugadores...");
        }

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

            // --- Section: General Behavior Toggles ---
            const behaviorSection = domMake.Tree("div", { class: "intelligent-swarm-section" });
            behaviorSection.appendChild(domMake.Tree("div", { class: "intelligent-swarm-section-title" }, ["Comportamiento General"]));

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

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

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

            const botToBotChatToggle = createToggle("Chat entre Bots", (checked) => this._activeToggles.botToBotChat = checked);
            this._ui.botToBotChatToggleCheckbox = botToBotChatToggle.querySelector('input[type="checkbox"]');
            behaviorSection.appendChild(botToBotChatToggle);

            const botToBotGesturesToggle = createToggle("Gestos entre Bots", (checked) => this._activeToggles.botToBotGestures = checked);
            this._ui.botToBotGesturesToggleCheckbox = botToBotGesturesToggle.querySelector('input[type="checkbox"]');
            behaviorSection.appendChild(botToBotGesturesToggle);
            container.appendChild(behaviorSection);

            // --- Section: Movement Controls ---
            const movementSection = domMake.Tree("div", { class: "intelligent-swarm-section" });
            movementSection.appendChild(domMake.Tree("div", { class: "intelligent-swarm-section-title" }, ["Control de Movimiento"]));

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

            const followGroup = domMake.Tree("div", { class: "intelligent-swarm-control-group" });
            this._ui.playerDropdown = domMake.Tree("select", {style: "flex-grow: 1;"});
            followGroup.appendChild(this._ui.playerDropdown);
            this._ui.followButton = domMake.Button("Seguir Jugador");
            this._ui.followButton.classList.add("intelligent-swarm-follow-button");
            this._ui.followButton.addEventListener("click", this._toggleSmartFollow);
            followGroup.appendChild(this._ui.followButton);
            movementSection.appendChild(followGroup);

            const movementSpeedGroup = domMake.Tree("div", { class: "intelligent-swarm-control-group" });
            movementSpeedGroup.appendChild(domMake.Tree("label", {}, ["Vel. Movimiento (ms):"]));
            this._ui.movementSpeedInput = domMake.Tree("input", { type: "number", value: "3000", min: "500", max: "10000", step: "100" });
            movementSpeedGroup.appendChild(this._ui.movementSpeedInput);
            movementSpeedGroup.appendChild(domMake.Tree("label", {}, ["Movimiento Suave (ms):"]));
            movementSpeedGroup.appendChild(domMake.Tree("input", { type: "number", value: "300", min: "50", max: "1000", step: "50" }));
            this._ui.movementSpeedInput.addEventListener("change", () => {
                if (this._activeToggles.naturalMovement) {
                    this._toggleNaturalMovement(false);
                    this._toggleNaturalMovement(true);
                }
                if (this._followTarget.interval) {
                    clearInterval(this._followTarget.interval);
                    this._followTarget.interval = setInterval(this._followLogic, (parseInt(this._ui.movementSpeedInput.value) || 3000) / 3);
                }
            });
            movementSection.appendChild(movementSpeedGroup);
            container.appendChild(movementSection);

            // --- Section: Bot Conversation Controls ---
            const botConversationSection = domMake.Tree("div", { class: "intelligent-swarm-section" });
            botConversationSection.appendChild(domMake.Tree("div", { class: "intelligent-swarm-section-title" }, ["Conversaciones entre Bots"]));
            const botConversationToggle = createToggle("Empezar a hablar y conversar", this._toggleBotConversation);
            this._ui.botConversationToggleCheckbox = botConversationToggle.querySelector('input[type="checkbox"]');
            botConversationSection.appendChild(botConversationToggle);
            container.appendChild(botConversationSection);

            // --- Section: Gestión de Grupos de Bots ---
            const groupManagementSection = domMake.Tree("div", { class: "intelligent-swarm-section" });
            groupManagementSection.appendChild(domMake.Tree("div", { class: "intelligent-swarm-section-title" }, ["Gestión de Grupos de Bots"]));

            for (let i = 0; i < 3; i++) {
                const groupId = i;
                const avatarFileId = `group${i}AvatarFile`;

                const groupDetails = domMake.Tree("details", { class: "bot-group-card", open: (i === 0) });
                const groupSummary = domMake.Tree("summary", {}, [`Grupo ${i + 1}:`]);
                groupDetails.appendChild(groupSummary);
                const groupInnerContent = domMake.Tree("div", { class: "group-content-area" });
                groupDetails.appendChild(groupInnerContent);

                const baseNameRow = domMake.Row({ class: "group-row" });
                const initialRandomName = this._generateRandomBotName();
                const baseNameInput = domMake.Tree("input", { type: "text", placeholder: `Nombre del grupo`, value: initialRandomName });
                const confirmNameButton = domMake.Button('<i class="fas fa-check"></i>', { title: "Confirmar nombre base para bots" });
                confirmNameButton.addEventListener("click", () => this._confirmGroupBaseName(groupId, baseNameInput.value));
                baseNameRow.appendAll(baseNameInput, confirmNameButton);
                groupInnerContent.appendChild(baseNameRow);

                groupInnerContent.appendChild(domMake.Tree("label", {}, [`Nombres de Bots:`]));
                const botNameInputs = [];
                for (let j = 0; j < 3; j++) {
                    const individualBotNameInput = domMake.Tree("input", { type: "text", placeholder: `Bot ${j + 1} (Grupo ${i + 1})` });
                    botNameInputs.push(individualBotNameInput);
                    groupInnerContent.appendChild(domMake.Row({ class: "group-row" }, [domMake.Tree("label", {}, `Bot ${j + 1}:`), individualBotNameInput]));
                }
                this._ui.groupInputs[i] = { baseNameInput: baseNameInput, nameInputs: botNameInputs };

                const messageInput = domMake.Tree("input", { type: "text", placeholder: `Mensaje Grupo ${i + 1}` });
                const sendButton = domMake.Button('<i class="fas fa-paper-plane"></i> Enviar', { title: "Enviar mensaje del grupo" });
                sendButton.addEventListener("click", () => this._sendGroupMessage(groupId));
                groupInnerContent.appendAll(domMake.Row({ class: "group-row" }, [messageInput, sendButton]));
                this._ui.groupInputs[i].messageInput = messageInput;

                const actionsRow = domMake.Row({ class: "group-actions" });
                const joinBotsButton = domMake.Button('<i class="fas fa-user-plus"></i> Unir Bots (3)', { title: "Unir 3 bots a este grupo" });
                joinBotsButton.addEventListener("click", () => this._joinBotsToGroup(groupId));
                actionsRow.appendChild(joinBotsButton);

                const avatarButtonLabel = domMake.Tree("label", { class: "btn", title: "Subir avatar para bots del grupo", for: avatarFileId });
                avatarButtonLabel.appendAll(domMake.Tree("i", { class: "fas fa-upload" }), domMake.TextNode(" Avatar"));
                const avatarFileInput = domMake.Tree("input", { type: "file", id: avatarFileId, hidden: true, accept: "image/png, image/jpeg" });
                avatarFileInput.addEventListener("change", (e) => this._handleGroupAvatarUpload(groupId, e.target.files[0]));
                groupInnerContent.appendChild(avatarFileInput);
                this._ui.groupInputs[i].avatarFileInput = avatarFileInput;
                actionsRow.appendChild(avatarButtonLabel);

                const spawnGroupAvatarsButton = domMake.Button('<i class="fas fa-user-circle"></i> Spawn (3)', { title: "Spawnear los avatares de los 3 bots de este grupo en el canvas" });
                spawnGroupAvatarsButton.addEventListener("click", () => this._spawnGroupAvatars(groupId));
                actionsRow.appendChild(spawnGroupAvatarsButton);

                const disconnectGroupButton = domMake.Button('<i class="fas fa-user-times"></i> Desconectar (3)', { title: "Desconectar todos los bots de este grupo" });
                disconnectGroupButton.addEventListener("click", () => this._disconnectGroupBots(groupId));
                actionsRow.appendChild(disconnectGroupButton);
                groupInnerContent.appendChild(actionsRow);

                groupManagementSection.appendChild(groupDetails);
            }

            const globalGroupActionsRow = domMake.Row({ class: "intelligent-swarm-control-group", style: "margin-top: 10px;" });
            const sendAllGroupsButton = domMake.Button('<i class="fas fa-paper-plane"></i> Enviar Todo');
            sendAllGroupsButton.title = "Enviar el mensaje configurado de cada grupo.";
            sendAllGroupsButton.addEventListener("click", () => { for (let i = 0; i < 3; i++) this._sendGroupMessage(i); });
            // globalGroupActionsRow.appendChild(sendAllGroupsButton);

            this._ui.autoSendGroupsToggle = createToggle("Auto Enviar Grupos", this._toggleAutoSendGroups);
            // globalGroupActionsRow.appendChild(this._ui.autoSendGroupsToggle);

            // globalGroupActionsRow.appendChild(domMake.Tree("label", {}, ["Vel. Auto (ms):"]));
            this._ui.autoSendSpeedInput = domMake.Tree("input", { type: "number", value: "5000", min: "1000", max: "60000", step: "500" });
            this._ui.autoSendSpeedInput.addEventListener("change", () => {
                if (this._activeToggles.autoSendGroups) {
                    this._toggleAutoSendGroups(false);
                    this._toggleAutoSendGroups(true);
                }
            });
            // globalGroupActionsRow.appendChild(this._ui.autoSendSpeedInput);

            groupManagementSection.appendChild(globalGroupActionsRow);
            container.appendChild(groupManagementSection);

            // --- Section: Cooldowns and Delays ---
            const cooldownsSection = domMake.Tree("div", { class: "intelligent-swarm-section" });
            // cooldownsSection.appendChild(domMake.Tree("div", { class: "intelligent-swarm-section-title" }, ["Configuración de Ritmo"]));

            const chatCooldownGroup = domMake.Tree("div", { class: "intelligent-swarm-control-group" });
            chatCooldownGroup.appendChild(domMake.Tree("label", {}, ["Cooldown Chat (ms):"]));
            this._ui.chatCooldownInput = domMake.Tree("input", { type: "number", value: "2000", min: "1000", max: "30000", step: "500" });
            chatCooldownGroup.appendChild(this._ui.chatCooldownInput);
            cooldownsSection.appendChild(chatCooldownGroup);

            const gestureCooldownGroup = domMake.Tree("div", { class: "intelligent-swarm-control-group" });
            gestureCooldownGroup.appendChild(domMake.Tree("label", {}, ["Cooldown Gesto (ms):"]));
            this._ui.gestureCooldownInput = domMake.Tree("input", { type: "number", value: "500", min: "100", max: "5000", step: "100" });
            gestureCooldownGroup.appendChild(this._ui.gestureCooldownInput);
            cooldownsSection.appendChild(gestureCooldownGroup);

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

        _generateRandomBotName() {
            const adjectives = ["Carlos", "Olivia", "Pablo", "Sofia", "Daniel", "Valentina", "Javier", "Camila"];
            const nouns = [""];
            const randomAdj = adjectives[Math.floor(Math.random() * adjectives.length)];
            const randomNoun = nouns[Math.floor(Math.random() * nouns.length)];
            return `${randomAdj} ${randomNoun}`;
        }

        _initializeObservers() {
            const botManagerClass = this.findGlobal("BotClientManager");
            if (!botManagerClass || !botManagerClass.siblings || !botManagerClass.siblings.length === 0) {
                setTimeout(() => this._initializeObservers(), 500);
                return;
            }
            this._botManagerInstance = botManagerClass.siblings[0];

            let botManagerObserverTimer;
            const botManagerObserver = new MutationObserver(() => {
                clearTimeout(botManagerObserverTimer);
                botManagerObserverTimer = setTimeout(this._handleBotClientManagerChange, 100);
            });
            botManagerObserver.observe(this._botManagerInstance.htmlElements.children, { childList: true, subtree: false });
            this._handleBotClientManagerChange();

            const playerListElement = document.getElementById("playerlist");
            if (playerListElement) {
                let playerListObserverTimer;
                const playerListObserver = new MutationObserver(() => {
                    clearTimeout(playerListObserverTimer);
                    playerListObserverTimer = setTimeout(this._handlePlayerListChange, 100);
                });
                playerListObserver.observe(playerListElement, { childList: true, subtree: true, attributes: true, attributeFilter: ['data-playerid', 'data-loggedin', 'style'] });
                this._handlePlayerListChange();
            } else {
                this.notify("warning", "Elemento de lista de jugadores no encontrado. Player dropdown might not update.");
            }
        }

        _handleBotClientManagerChange() {
            const prevManagedBotsSet = new Set(this._bots);
            const currentBotsInManager = this._botManagerInstance.children
                .map(bci => bci.bot)
                .filter(bot => bot && bot.getReadyState());

            this._bots = currentBotsInManager;
            const currentManagedBotsSet = new Set(this._bots);

            this._bots.forEach(bot => {
                if (!prevManagedBotsSet.has(bot)) {
                    this._attachBotListeners(bot);
                    this.notify("log", `Intelligent Swarm ahora gestiona a: ${bot.name} (recién conectado).`);
                }
            });

            this._botGroupsData.forEach((group, groupIdx) => {
                this._botGroupsData[groupIdx] = group.filter(bot => currentManagedBotsSet.has(bot));
            });

            this.notify("info", `Bots activos gestionados: ${this._bots.length}.`);
            this._updatePlayerDropdown();

            if (this._activeToggles.botConversation) {
                this._toggleBotConversation(false);
                this._toggleBotConversation(true);
            }
            if (this._activeToggles.autoSendGroups) {
                this._toggleAutoSendGroups(false);
                this._toggleAutoSendGroups(true);
            }
        }

        _attachBotListeners(botInstance) {
            const isListenerAttached = botInstance.customObservers.some(obs => obs.callback.name === "_handleChatMessage");
            if (isListenerAttached) {
                this.notify("debug", `Listeners ya adjuntos a ${botInstance.name}.`);
                return;
            }

            this.notify("log", `Adjuntando listeners para: ${botInstance.name}`);
            const listeners = [
                { event: "bc_chatmessage", callback: (data) => this._handleChatMessage(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: "uc_turn_begindraw", callback: (data) => this._handleTurnBeginDraw(botInstance, data) },
                { event: "uc_turn_selectword", callback: (data) => this._handleWordSelected(botInstance, data) },
            ];

            listeners.forEach(listener => botInstance.customObservers.push(listener));
        }


        _removeBot(botInstance) {
            this._bots = this._bots.filter(b => b !== botInstance);
            this.notify("log", `Intelligent Swarm ya no gestiona a: ${botInstance.name}`);
            if (this._followTarget.id === botInstance.id) {
                this.notify("info", `El bot seguido (${botInstance.name}) ha sido removido. Deteniendo seguimiento.`);
                this._toggleSmartFollow();
            }
        }

        _handlePlayerListChange() {
            this._updatePlayerDropdown();
            if (this._activeToggles.naturalMovement && !this._naturalMovementInterval && this._bots.length > 0) {
                this._toggleNaturalMovement(true);
            } else if (this._followTarget.id && !this._followTarget.interval && this._bots.length > 0) {
                this._followLogic();
            }
        }

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

            const anyConnectedBot = this._bots.find(bot => bot.getReadyState());
            const currentRoomPlayers = anyConnectedBot?.room?.players || [];
            const myBotIds = new Set(this._bots.map(b => b.id));
            const humanPlayers = currentRoomPlayers.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 (humanos)"]));
                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 (${this._followTarget.name}) abandonó o no válido. Deteniendo seguimiento.`);
                    this._toggleSmartFollow();
                }
                return;
            }

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

            if (humanPlayers.some(p => String(p.id) === currentSelection)) {
                this._ui.playerDropdown.value = currentSelection;
            } else {
                this._ui.playerDropdown.selectedIndex = 0;
            }
        }

        _getBots(requireConnected = true, skipNotification = false) {
            const connectedBots = this._bots.filter(bot => bot.getReadyState());
            if (connectedBots.length === 0 && !skipNotification) {
                this.notify("warning", "No hay bots conectados y listos para realizar la acción.");
            }
            return connectedBots;
        }

        _toggleNaturalMovement(isActive) {
            this._activeToggles.naturalMovement = isActive;
            const toggleCheckbox = this._ui.naturalMovementToggleCheckbox;
            if (toggleCheckbox) toggleCheckbox.checked = isActive;

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

            const connectedBots = this._getBots(true);
            const canRun = isActive && connectedBots.length > 0 && this._followTarget.id === null;

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

        _executeNaturalMovement() {
            if (this._followTarget.id || !this._activeToggles.naturalMovement || !this._gameCanvas) return;
            const connectedBots = this._getBots(true);
            if (connectedBots.length === 0) {
                this.notify("warning", "No hay bots conectados para Movimiento Natural. Deteniendo.");
                this._toggleNaturalMovement(false);
                return;
            }
            const centerX = 50, centerY = 50, driftAmount = 15;

            connectedBots.forEach(bot => {
                 if (bot.attributes?.spawned === false) {
                      this.notify("debug", `Bot ${bot.name} no generado para movimiento natural. Intentando generar.`);
                      bot.emit("spawnavatar");
                      bot.attributes.spawned = true;
                      return;
                 }
                let targetX = centerX + (Math.random() - 0.5) * driftAmount * 2;
                let targetY = centerY + (Math.random() - 0.5) * driftAmount * 2;
                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 connectedBots = this._getBots(true);
            if (connectedBots.length === 0) {
                 this.notify("warning", "Necesitas al menos un bot conectado para seguir a un jugador.");
                 return;
            }
            const anyConnectedBot = connectedBots[0];
            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, name: null };
                this._ui.followButton.textContent = "Seguir Jugador";
                this._ui.followButton.classList.remove("active");
                this.notify("info", `Dejando de seguir a ${targetName}.`);
                if (this._activeToggles.naturalMovement) this._toggleNaturalMovement(true);
            } else {
                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, name: targetName, interval: null };
                this._ui.followButton.textContent = `Siguiendo: ${targetName}`;
                this._ui.followButton.classList.add("active");
                this.notify("info", `Iniciando seguimiento a ${targetName}.`);
                const moveSpeed = parseInt(this._ui.movementSpeedInput.value) || 3000;
                this._followTarget.interval = setInterval(this._followLogic, moveSpeed / 3);
                this._followLogic();
            }
        }

        _followLogic() {
            if (this._followTarget.id === null || !this._gameCanvas) return;
            const connectedBots = this._getBots(true);
            if (connectedBots.length === 0) {
                this.notify("warning", "No hay bots conectados para seguir. Deteniendo seguimiento.");
                this._toggleSmartFollow();
                return;
            }
            const targetPlayerElement = document.querySelector(`.spawnedavatar[data-playerid="${this._followTarget.id}"]`);
            if (!targetPlayerElement) {
                this.notify("warning", `Avatar del jugador seguido (${this._followTarget.name}) no encontrado. Deteniendo seguimiento.`);
                this._toggleSmartFollow();
                return;
            }
            const canvasRect = this._gameCanvas.getBoundingClientRect();
            const avatarRect = targetPlayerElement.getBoundingClientRect();
            let targetXGameCoords = ((avatarRect.left + (avatarRect.width / 2) - canvasRect.left) / canvasRect.width) * 100;
            let targetYGameCoords = ((avatarRect.top + (avatarRect.height / 2) - canvasRect.top) / canvasRect.height) * 100;
            targetXGameCoords = Math.max(0, Math.min(100, targetXGameCoords));
            targetYGameCoords = Math.max(0, Math.min(100, targetYGameCoords));

            connectedBots.forEach((bot, index) => {
                 if (bot.attributes?.spawned === false) {
                      this.notify("debug", `Bot ${bot.name} no generado para seguir. Intentando generar.`);
                      bot.emit("spawnavatar");
                      bot.attributes.spawned = true;
                      return;
                 }
                const offsetDistance = 10 + index * 5;
                const offsetAngle = (index * 1.5 + Math.random()) * Math.PI * 2 / connectedBots.length;
                const offsetX = offsetDistance * Math.cos(offsetAngle);
                const offsetY = offsetDistance * Math.sin(offsetAngle);
                let moveX = targetXGameCoords + offsetX;
                let moveY = targetYGameCoords + offsetY;
                moveX = Math.max(5, Math.min(95, moveX));
                moveY = Math.max(5, Math.min(95, moveY));
                this._moveBotSmoothly(bot, moveX, moveY);
            });
        }

        _moveBotSmoothly(bot, targetX, targetY) {
            if (!bot || !bot.getReadyState() || typeof bot.emit !== 'function' || bot.attributes?.spawned === false) {
                this.notify("debug", `Saltando movimiento suave para bot ${bot?.name || 'Desconocido'}: No está listo o no ha sido generado.`);
                return;
            }
             bot._lastCommandedX = bot._lastCommandedX ?? 50;
             bot._lastCommandedY = bot._lastCommandedY ?? 50;
             const currentX = bot._lastCommandedX;
             const currentY = bot._lastCommandedY;
             const steps = 10;
             const stepDelay = (parseInt(this._ui.movementSpeedInput.value) || 3000) / (steps * 3);
             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);
             }
        }

        _onPersonalityChange() {
            this._selectedPersonality = this._ui.personalitySelect.value;
            this.notify("info", `Personalidad de los bots cambiada a: ${this._selectedPersonality}.`);
        }

        _canChat(bot) {
            const now = Date.now();
            const lastChat = this._chatCooldown.get(bot.id) || 0;
            const chatCooldownMs = parseInt(this._ui.chatCooldownInput.value) || 2000;
            const canChat = (now - lastChat > chatCooldownMs);
            if (canChat) this._chatCooldown.set(bot.id, now);
            return canChat;
        }

         _sendChat(bot, message) {
             if (bot && bot.getReadyState() && this._canChat(bot)) {
                 setTimeout(() => {
                      if (bot.getReadyState()) {
                          bot.emit("chatmsg", message);
                          this.notify("log", `${bot.name} (Pers: ${this._selectedPersonality}): "${message}"`);
                      }
                 }, 500 + Math.random() * 500);
             }
         }

         _canGesture(bot) {
             const now = Date.now();
             const lastGesture = this._gestureCooldown.get(bot.id) || 0;
             const gestureCooldownMs = parseInt(this._ui.gestureCooldownInput.value) || 500;
             const canGesture = (now - lastGesture > gestureCooldownMs);
             if (canGesture) this._gestureCooldown.set(bot.id, now);
             return canGesture;
         }

         _sendGesture(bot, gestureId) {
             if (gestureId === undefined || gestureId === null) {
                 this.notify("debug", `Saltando gesto: ID inválido para bot ${bot.name}.`);
                 return;
             }
             if (bot && bot.getReadyState() && this._canGesture(bot)) {
                 setTimeout(() => {
                     if (bot.getReadyState()) {
                          bot.emit("sendgesture", gestureId);
                          this.notify("log", `${bot.name} usó el gesto ${gestureId}.`);
                     }
                 }, 100 + Math.random() * 200);
             }
         }

        _getMessageLanguage(message) {
            const lowerCaseMsg = message.toLowerCase();
            const spanishKeywords = ['hola', 'buenas', 'que', 'qué', 'tal', 'como', 'cómo', 'estás', 'estas', 'estoy', 'bien', 'mal', 'sí', 'si', 'no', 'por favor', 'gracias', 'adiós', 'chao', 'jaja', 'xd', 'dibujo', 'jugador', 'adivinar', 'palabra'];
            const englishKeywords = ['hi', 'hello', 'hey', 'what', 'how', 'are', 'you', 'i', 'am', 'fine', 'good', 'bad', 'yes', 'no', 'please', 'thank', 'thanks', 'bye', 'goodbye', 'lol', 'haha', 'draw', 'player', 'guess', 'word'];
            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';
        }

        _handleChatMessage(receivingBot, msg) {
            const isSelfMessage = receivingBot.id === msg.id;
            const isFromOurBot = this._bots.some(b => b.id === msg.id);
            const isHumanMessage = !isFromOurBot && msg.id !== 0;

            if (isSelfMessage) return;

            let allowChat = (isHumanMessage && this._activeToggles.reactiveChat) || (isFromOurBot && this._activeToggles.botToBotChat);
            let allowGestures = (isHumanMessage && this._activeToggles.smartGestures) || (isFromOurBot && this._activeToggles.botToBotGestures);
            if (!allowChat && !allowGestures) return;

            const personality = _personalities[this._selectedPersonality];
            const lowerCaseMsg = msg.message.toLowerCase().trim();
            const lang = this._getMessageLanguage(msg.message);
            let responseType = null, responseMessages = null;

            if (/\b(hola|buenas|hey|hi|hello)\b/.test(lowerCaseMsg)) responseType = 'greeting';
            else if (/\b(si|yes|ok|claro|yeah)\b/.test(lowerCaseMsg)) responseType = 'acknowledgement';
            else if (/\b(como\sestas|y\stu|how\sare\syou|what's\sup|what)\b/.test(lowerCaseMsg)) responseType = 'question';
            else if (/\b(xd|lol|jaja|haha|omg)\b/.test(lowerCaseMsg)) responseType = 'laughter';
            else if (/\b(que|but|well|pero|bueno)\b/.test(lowerCaseMsg)) responseType = 'general';
            else if (/\b(lindo|hermoso|dibujas\sbien|buen\sdibujo|buen\strabajo|good\sjob|nice\sdraw)\b/.test(lowerCaseMsg)) responseType = 'goodjob_drawing';

            if (responseType) {
                responseMessages = personality[`${lang}_${responseType}`] || personality[`${lang}_general`] || personality.spanish_general;
                if (allowChat && responseMessages && Math.random() < 0.7) {
                    const selectedResponse = responseMessages[Math.floor(Math.random() * responseMessages.length)];
                    this._sendChat(receivingBot, selectedResponse.replace("{player}", msg.name));
                }
                if (allowGestures && personality.gestures[responseType] !== undefined) {
                    this._sendGesture(receivingBot, 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 = _personalities[this._selectedPersonality];
            const response = personality.spanish_congrats[Math.floor(Math.random() * personality.spanish_congrats.length)].replace("{player}", player.name);
            this._getBots(true).forEach(bot => {
                if (this._activeToggles.reactiveChat && Math.random() < 0.7) this._sendChat(bot, response);
                if (this._activeToggles.smartGestures && personality.gestures.congrats !== undefined) this._sendGesture(bot, personality.gestures.congrats);
            });
        }

        _handlePlayerJoin(player) {
            if (this._bots.some(b => b.id === player.id) || player.id === 0) return;
            const personality = _personalities[this._selectedPersonality];
            const response = personality.spanish_playerJoin[Math.floor(Math.random() * personality.spanish_playerJoin.length)].replace("{player}", player.name);
            this._getBots(true).forEach(bot => {
                if (this._activeToggles.reactiveChat && Math.random() < 0.6) this._sendChat(bot, response);
                if (this._activeToggles.smartGestures && personality.gestures.playerJoin !== undefined) this._sendGesture(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 = _personalities[this._selectedPersonality];
            const response = personality.spanish_playerLeave[Math.floor(Math.random() * personality.spanish_playerLeave.length)].replace("{player}", player.name);
            this._getBots(true).forEach(bot => {
                if (this._activeToggles.reactiveChat && Math.random() < 0.5) this._sendChat(bot, response);
                if (this._activeToggles.smartGestures && personality.gestures.playerLeave !== undefined) this._sendGesture(bot, personality.gestures.playerLeave);
            });
        }

        _handleTurnBeginDraw(botInstance, data) {
            const drawingPlayerId = data[0];
            const drawingPlayerName = botInstance.room?.players?.find(p => p.id === drawingPlayerId)?.name || 'alguien';
            const isOurBotDrawing = this._bots.some(b => b.id === drawingPlayerId);
            this._getBots(true).forEach(bot => {
                if (bot.id === drawingPlayerId) return;
                let allowChat = (isOurBotDrawing && this._activeToggles.botToBotChat) || (!isOurBotDrawing && this._activeToggles.reactiveChat);
                let allowGestures = (isOurBotDrawing && this._activeToggles.botToBotGestures) || (!isOurBotDrawing && this._activeToggles.smartGestures);
                if (allowGestures) {
                    const personality = _personalities[this._selectedPersonality];
                    if (personality.gestures.drawing !== undefined) this._sendGesture(bot, personality.gestures.drawing);
                }
                if (allowChat && Math.random() < 0.4) this._sendChat(bot, `¡Buena suerte, ${drawingPlayerName}!`);
            });
        }

        _handleWordSelected(botInstance, data) {
            const drawingPlayerElement = document.querySelector('.playerlist-row[data-turn="true"]');
            const drawingPlayerId = drawingPlayerElement ? parseInt(drawingPlayerElement.dataset.playerid) : null;
            const isOurBotDrawing = this._bots.some(b => b.id === drawingPlayerId);
            this._getBots(true).forEach(bot => {
                if (bot.id === drawingPlayerId) return;
                let allowGestures = (isOurBotDrawing && this._activeToggles.botToBotGestures) || (!isOurBotDrawing && this._activeToggles.smartGestures);
                if (allowGestures) {
                    const personality = _personalities[this._selectedPersonality];
                    if (personality.gestures.acknowledgement !== undefined) this._sendGesture(bot, personality.gestures.acknowledgement);
                }
            });
        }

        _toggleBotConversation(isActive) {
            this._activeToggles.botConversation = isActive;
            const toggleCheckbox = this._ui.botConversationToggleCheckbox;
            if (toggleCheckbox) toggleCheckbox.checked = isActive;

            if (isActive) {
                this._conversationBots = this._getBots(true);
                if (this._conversationBots.length < 2) {
                    this.notify("warning", "Se necesitan al menos 2 bots conectados para iniciar una conversación.");
                    this._activeToggles.botConversation = false;
                    if (toggleCheckbox) toggleCheckbox.checked = false;
                    return;
                }
                for (let i = this._conversationBots.length - 1; i > 0; i--) {
                    const j = Math.floor(Math.random() * (i + 1));
                    [this._conversationBots[i], this._conversationBots[j]] = [this._conversationBots[j], this._conversationBots[i]];
                }
                this._currentSpeakerIndex = 0;
                this._startNewConversationTopic();
                const chatCooldownMs = parseInt(this._ui.chatCooldownInput.value) || 2000;
                this.notify("info", `Conversaciones entre bots iniciadas. Turno cada ~${chatCooldownMs}ms.`);
                this._conversationInterval = setInterval(this._continueConversation, chatCooldownMs);
            } else {
                clearInterval(this._conversationInterval);
                this._conversationInterval = null;
                this._conversationBots = [];
                this._currentSpeakerIndex = -1;
                this._currentConversationTopic = null;
                this._currentTopicPhraseIndex = -1;
                this.notify("info", "Conversaciones entre bots detenidas.");
            }
        }

        _startNewConversationTopic() {
            const topicKeys = Object.keys(this._conversationTopicsData).filter(key => key !== 'General');
            let selectedTopic = 'General';
            for (const topic of topicKeys) { if (Math.random() < 0.20) { selectedTopic = topic; break; } }
            if (selectedTopic === 'General' && Math.random() < 0.5) selectedTopic = topicKeys[Math.floor(Math.random() * topicKeys.length)];
            this._currentConversationTopic = selectedTopic;
            this._currentTopicPhraseIndex = 0;
            this.notify("log", `Nueva conversación iniciada. Tema: "${this._currentConversationTopic}".`);
        }

        _continueConversation() {
            if (!this._activeToggles.botConversation || this._conversationBots.length < 2) {
                this.notify("warning", "No hay suficientes bots para continuar la conversación. Deteniendo.");
                this._toggleBotConversation(false);
                return;
            }
            const speakerBot = this._conversationBots[this._currentSpeakerIndex];
            const topicPhrases = this._conversationTopicsData[this._currentConversationTopic];
            if (!speakerBot || !speakerBot.getReadyState()) {
                this.notify("debug", `Bot ${speakerBot ? speakerBot.name : 'desconocido'} no está listo. Saltando turno.`);
                this._currentSpeakerIndex = (this._currentSpeakerIndex + 1) % this._conversationBots.length;
                return;
            }
            if (this._currentTopicPhraseIndex < topicPhrases.length) {
                const phrase = topicPhrases[this._currentTopicPhraseIndex];
                this._sendChat(speakerBot, phrase);
                this._currentTopicPhraseIndex++;
            } else {
                this._startNewConversationTopic();
                const phrase = this._conversationTopicsData[this._currentConversationTopic][0];
                this._sendChat(speakerBot, phrase);
                this._currentTopicPhraseIndex = 1;
            }
            this._currentSpeakerIndex = (this._currentSpeakerIndex + 1) % this._conversationBots.length;
        }

        _confirmGroupBaseName(groupId, baseName) {
            const botNameInputs = this._ui.groupInputs[groupId].nameInputs;
            const effectiveBaseName = baseName.trim() || `Grupo ${groupId + 1}`;
            botNameInputs.forEach((input, index) => input.value = `${effectiveBaseName}`);
            this.notify("info", `Nombres de bots en Grupo ${groupId + 1} actualizados.`);
        }

        async _joinBotsToGroup(groupId) {
            const botsToCreate = 3;
            const currentTotalBots = this._bots.length;
            if (currentTotalBots + botsToCreate > this._maxTotalBots) {
                this.notify("warning", `Límite de bots (${this._maxTotalBots}) alcanzado. No se pueden crear más bots.`);
                return;
            }
            this.notify("info", `Uniendo 3 bots al grupo ${groupId + 1}...`);
            const joinedBots = [];
            const botNameInputs = this._ui.groupInputs[groupId].nameInputs;
            for (let i = 0; i < botsToCreate; i++) {
                const botName = botNameInputs[i].value.trim() || `Grupo ${groupId + 1} Bot ${i + 1}`;
                const botInterface = this._botManagerInstance.createBotClientInterface();
                const botInstance = botInterface.bot;
                if (!botInstance) {
                    this.notify("error", `Fallo al crear el bot ${botName}.`);
                    continue;
                }
                botInterface.setClientName(botName);
                const avatarFileInput = this._ui.groupInputs[groupId].avatarFileInput;
                if (avatarFileInput && avatarFileInput.dataset.avatarUidParts) {
                    const avatarUidParts = JSON.parse(avatarFileInput.dataset.avatarUidParts);
                    botInstance.avatar = avatarUidParts;
                    botInterface.setClientIcon(avatarUidParts);
                }
                const roomUrlInput = document.querySelector("#invurl");
                const currentRoomId = roomUrlInput ? roomUrlInput.value : window.location.pathname.replace('/room/', '');
                botInstance.enterRoom(currentRoomId);
                joinedBots.push(botInstance);
                await new Promise(resolve => setTimeout(resolve, 500));
            }
            this._botGroupsData[groupId] = joinedBots;
            this.notify("success", `Grupo ${groupId + 1}: ${joinedBots.length} bots unidos y en sala.`);
            this._handleBotClientManagerChange();
        }

        _sendGroupMessage(groupId) {
            const groupBots = this._botGroupsData[groupId];
            if (!groupBots || groupBots.length === 0) {
                this.notify("warning", `Grupo ${groupId + 1} no tiene bots para enviar mensajes.`);
                return;
            }
            const message = this._ui.groupInputs[groupId].messageInput.value.trim();
            if (!message) {
                this.notify("warning", `Mensaje del Grupo ${groupId + 1} está vacío.`);
                return;
            }
            groupBots.forEach(bot => {
                if (bot.getReadyState()) this._sendChat(bot, message);
                else this.notify("warning", `Bot ${bot.name} (Grupo ${groupId + 1}) no está conectado para enviar mensaje.`);
            });
        }

        async _handleGroupAvatarUpload(groupId, file) {
            if (!file) {
                this.notify("warning", `No se seleccionó ningún archivo para el Grupo ${groupId + 1}.`);
                return;
            }
            this.notify("info", `Subiendo avatar para Grupo ${groupId + 1}...`);
            try {
                const reader = new FileReader();
                reader.readAsDataURL(file);
                reader.onload = async () => {
                    const base64Image = reader.result;
                    const response = await fetch("https://drawaria.online/uploadavatarimage", {
                        method: "POST", body: "imagedata=" + encodeURIComponent(base64Image) + "&fromeditor=true",
                        headers: { "Content-Type": "application/x-www-form-urlencoded; charset=UTF-8" },
                    });
                    if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
                    const body = await response.text();
                    const avatarUidParts = body.split(".");
                    if (avatarUidParts.length < 2) throw new Error("Respuesta inválida del servidor al subir avatar.");
                    this._ui.groupInputs[groupId].avatarFileInput.dataset.avatarUidParts = JSON.stringify(avatarUidParts);
                    const groupBots = this._botGroupsData[groupId];
                    if (groupBots && groupBots.length > 0) {
                        for (const bot of groupBots) {
                            if (bot.getReadyState()) {
                                bot.avatar = avatarUidParts;
                                const botClientInterface = this._botManagerInstance.children.find(bci => bci.bot === bot);
                                if (botClientInterface) botClientInterface.setClientIcon(avatarUidParts);
                                this.notify("info", `Avatar de ${bot.name} actualizado en la UI.`);
                            }
                        }
                    }
                    this.notify("success", `Avatar para Grupo ${groupId + 1} subido y asignado.`);
                    this.notify("info", "Para que los bots existentes usen el nuevo avatar, deben ser reconectados (desconectados y unidos de nuevo).");
                };
                reader.onerror = (error) => { throw new Error(`Error al leer el archivo: ${error.message}`); };
            } catch (error) {
                this.notify("error", `Fallo al asignar avatar al Grupo ${groupId + 1}: ${error.message}`);
                console.error("Avatar upload error:", error);
            }
        }

        _spawnGroupAvatars(groupId) {
            const groupBots = this._botGroupsData[groupId];
            if (!groupBots || groupBots.length === 0) {
                this.notify("warning", `Grupo ${groupId + 1} no tiene bots para spawnear.`);
                return;
            }
            this.notify("info", `Spawning avatares para el Grupo ${groupId + 1}...`);
            groupBots.forEach(bot => {
                if (bot.getReadyState() && !bot.attributes.spawned) {
                    bot.emit("spawnavatar");
                    bot.attributes.spawned = true;
                    this.notify("log", `Avatar de ${bot.name} spawneado.`);
                } else if (bot.attributes.spawned) {
                     this.notify("log", `Avatar de ${bot.name} ya está spawneado.`);
                } else {
                    this.notify("warning", `Bot ${bot.name} (Grupo ${groupId + 1}) no está conectado para spawnear su avatar.`);
                }
            });
        }

        _disconnectGroupBots(groupId) {
            const groupBots = this._botGroupsData[groupId];
            if (!groupBots || groupBots.length === 0) {
                this.notify("warning", `Grupo ${groupId + 1} no tiene bots conectados para desconectar.`);
                return;
            }
            this.notify("info", `Desconectando bots del Grupo ${groupId + 1}...`);
            groupBots.forEach(bot => { if (bot.getReadyState()) bot.disconnect(); });
            this.notify("success", `Bots del Grupo ${groupId + 1} desconectados.`);
            setTimeout(() => this._handleBotClientManagerChange(), 50);
        }

        _toggleAutoSendGroups(isActive) {
            this._activeToggles.autoSendGroups = isActive;
            const toggleCheckbox = this._ui.autoSendGroupsToggle.querySelector('input[type="checkbox"]');
            if (toggleCheckbox) toggleCheckbox.checked = isActive;

            if (isActive) {
                const autoSendSpeedMs = parseInt(this._ui.autoSendSpeedInput.value) || 5000;
                if (autoSendSpeedMs < 1000) {
                     this.notify("warning", "Velocidad de auto-envío demasiado baja. Mínimo 1000ms.");
                     this._activeToggles.autoSendGroups = false;
                     if (toggleCheckbox) toggleCheckbox.checked = false;
                     return;
                }
                this.notify("info", `Auto-envío de grupos activado (cada ${autoSendSpeedMs}ms).`);
                this._autoSendGroupsInterval = setInterval(this._performAutoSendGroups, autoSendSpeedMs);
                this._performAutoSendGroups();
            } else {
                clearInterval(this._autoSendGroupsInterval);
                this._autoSendGroupsInterval = null;
                this.notify("info", "Auto-envío de grupos desactivado.");
            }
        }

        _performAutoSendGroups() {
            if (!this._activeToggles.autoSendGroups) return;
            for (let i = 0; i < 3; i++) {
                const groupBots = this._botGroupsData[i];
                if (groupBots && groupBots.length > 0) {
                    const message = this._ui.groupInputs[i].messageInput.value.trim();
                    if (message) {
                        const randomBot = groupBots[Math.floor(Math.random() * groupBots.length)];
                        if (randomBot.getReadyState()) this._sendChat(randomBot, message);
                    }
                }
            }
        }
    }
})("QBit");
// --- END IntelligentSwarm


// --- START Swarm

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

            container.appendChild(disruptionSection);

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

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

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

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

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

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

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

            return activeBotClientInterface.bot;
        }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// END SWARM

// START KICK

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

        // Custom Chat
        #messageInput;

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                const data = await response.json();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// START CLIENT

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

        #previewCanvas;
        #previewCtx; // Context for #previewCanvas
        #originalCanvas; // Reference to the game's main canvas
        #originalCtx; // Context for #originalCanvas

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

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

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

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

        #pantherTextInput;
        #pantherTextSizeInput;
        #pantherTextColorInput;

        #colorToleranceInput;

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

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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

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

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

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

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

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

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

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


            this.htmlElements.section.appendChild(container);

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

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

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

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

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

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

            this.#setDrawingMode('rainMode');
        }

        #setupCanvas() {
            this.#previewCanvas = document.createElement('canvas');
            this.#previewCtx = this.#previewCanvas.getContext('2d');

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

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

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

            const FR = new FileReader();
            FR.addEventListener('load', (evt) => {
                const img = new Image();
                img.addEventListener('load', async () => {
                    this.notify("info", "Imagen cargada. Procesando pixeles...");
                    const ctx = this.#previewCtx; // Use preview canvas for image processing
                    ctx.clearRect(0, 0, this.#previewCanvas.width, this.#previewCanvas.height);

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

                        for (let i = 1; i < pixelsInColumn.length; i++) {
                            let currentPixel = pixelsInColumn[i];
                            // Check for discontinuity or significant color change
                            if (currentPixel.y !== prevPixel.y + pixelDensity || !this.#areColorsSimilar(startPixel.color, currentPixel.color, colorTolerance)) {
                                this.#executionLine.push({
                                    pos1: this.#toGameCoords(startPixel.x, startPixel.y, width, height),
                                    pos2: this.#toGameCoords(prevPixel.x, prevPixel.y, width, height),
                                    color: this.#rgbaArrayToString(startPixel.color),
                                    thickness
                                });
                                startPixel = currentPixel;
                            }
                            prevPixel = currentPixel;
                        }
                        // Add the last segment of the column
                        this.#executionLine.push({
                            pos1: this.#toGameCoords(startPixel.x, startPixel.y, width, height),
                            pos2: this.#toGameCoords(prevPixel.x, prevPixel.y, width, height),
                            color: this.#rgbaArrayToString(startPixel.color),
                            thickness
                        });
                    }
                    break;
                }
                case 'waveDraw': { // Added block scope
                    const waveAmplitude = 15; // Max vertical displacement
                    const waveFrequency = 0.05; // How many waves across the image

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

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

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

                            if (actualColor) {
                                if (!startPoint) {
                                    startPoint = { x: currentXForWave, y: currentYForWave, color: actualColor };
                                    lastColor = actualColor;
                                } else if (!this.#areColorsSimilar(actualColor, lastColor, colorTolerance)) {
                                    this.#executionLine.push({
                                        pos1: this.#toGameCoords(startPoint.x, startPoint.y, width, height),
                                        pos2: this.#toGameCoords(currentXForWave - pixelDensity, currentYForWave, width, height), // End before color change
                                        color: this.#rgbaArrayToString(lastColor),
                                        thickness
                                    });
                                    startPoint = { x: currentXForWave, y: currentYForWave, color: actualColor };
                                    lastColor = actualColor;
                                }
                            } else if (startPoint) {
                                // Transparency encountered, end current segment
                                this.#executionLine.push({
                                    pos1: this.#toGameCoords(startPoint.x, startPoint.y, width, height),
                                    pos2: this.#toGameCoords(currentXForWave - pixelDensity, currentYForWave, width, height),
                                    color: this.#rgbaArrayToString(lastColor),
                                    thickness
                                });
                                startPoint = null;
                                lastColor = null;
                            }
                        }
                        // End any remaining segment at the end of the row
                        if (startPoint) {
                            this.#executionLine.push({
                                pos1: this.#toGameCoords(startPoint.x, startPoint.y, width, height),
                                pos2: this.#toGameCoords(width - pixelDensity, y + waveAmplitude * Math.sin((width - pixelDensity) * waveFrequency), width, height),
                                color: this.#rgbaArrayToString(lastColor),
                                thickness
                            });
                        }
                    }
                    break;
                }
                case 'spiralDraw': { // Added block scope
                    const centerX = width / 2;
                    const centerY = height / 2;
                    const maxRadius = Math.min(width, height) / 2;
                    const density = 0.5; // Controls how tight the spiral is

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

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

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

                            const color = this.#getPixelColor(spiralX, spiralY);

                            if (color) {
                                if (startPoint === null) {
                                    startPoint = { x: spiralX, y: spiralY, color: color };
                                    lastColor = color;
                                } else if (!this.#areColorsSimilar(color, lastColor, colorTolerance)) {
                                    this.#executionLine.push({
                                        pos1: this.#toGameCoords(startPoint.x, startPoint.y, width, height),
                                        pos2: this.#toGameCoords(prevX, prevY, width, height),
                                        color: this.#rgbaArrayToString(lastColor),
                                        thickness
                                    });
                                    startPoint = { x: spiralX, y: spiralY, color: color };
                                    lastColor = color;
                                }
                                prevX = spiralX;
                                prevY = spiralY;
                            } else if (startPoint !== null) {
                                this.#executionLine.push({
                                    pos1: this.#toGameCoords(startPoint.x, startPoint.y, width, height),
                                    pos2: this.#toGameCoords(prevX, prevY, width, height),
                                    color: this.#rgbaArrayToString(lastColor),
                                    thickness
                                });
                                startPoint = null;
                            }
                        }
                        if (startPoint) { // Add the last segment of the current spiral turn
                             this.#executionLine.push({
                                pos1: this.#toGameCoords(startPoint.x, startPoint.y, width, height),
                                pos2: this.#toGameCoords(prevX, prevY, width, height),
                                color: this.#rgbaArrayToString(lastColor),
                                thickness
                            });
                        }
                    }
                    this.#spiralAngle += 0.1; // Increment for a "rotating" spiral effect on subsequent runs
                    break;
                }
                case 'randomPixelDraw': { // Added block scope
                    let allPixels = [];
                    for (let y = 0; y < height; y += pixelDensity) {
                        for (let x = 0; x < width; x += pixelDensity) {
                            const color = this.#getPixelColor(x, y);
                            if (color) {
                                allPixels.push({ x, y, color });
                            }
                        }
                    }
                    this.#shuffleArray(allPixels);

                    allPixels.forEach(p => {
                        this.#executionLine.push({
                            pos1: this.#toGameCoords(p.x, p.y, width, height),
                            pos2: this.#toGameCoords(p.x + pixelDensity / 2, p.y + pixelDensity / 2, width, height), // Draw a small dot
                            color: this.#rgbaArrayToString(p.color),
                            thickness
                        });
                    });
                    break;
                }
                case 'shapeDrawMode': { // Added block scope
                    const shapeDetectorClass = this.findGlobal("ShapeDetector");
                    if (!shapeDetectorClass || !shapeDetectorClass.siblings || shapeDetectorClass.siblings.length === 0) {
                        this.notify("error", "El módulo 'Detector de Formas' no está activo. Asegúrate de que está cargado y enlazado.");
                        return false;
                    }
                    const shapeDetectorInstance = shapeDetectorClass.siblings[0];

                    if (!shapeDetectorInstance || typeof shapeDetectorInstance.analyzeImageDataForShapes !== 'function') {
                        this.notify("error", "El módulo 'Detector de Formas' no está listo o le falta el método 'analyzeImageDataForShapes'.");
                        return false;
                    }

                    this.notify("info", "Analizando imagen para formas con Detector de Formas...");
                    // Assuming analyzeImageDataForShapes returns commands in game coords (0-100) or similar
                    const { drawingCommands: shapeCommands } = await shapeDetectorInstance.analyzeImageDataForShapes(
                        this.#imageData,
                        this.#imageData.width,
                        this.#imageData.height,
                        false // Pass false if you don't want it to draw directly
                    );

                    if (shapeCommands.length === 0) {
                        this.notify("warning", "No se detectaron formas significativas en la imagen para dibujar.");
                        return false;
                    }

                    shapeCommands.forEach(cmd => {
                        this.#executionLine.push({
                            pos1: [cmd.x1, cmd.y1],
                            pos2: [cmd.x2, cmd.y2],
                            color: cmd.color || this.#pantherTextColorInput.value, // Use detected color or default
                            thickness: thickness
                        });
                    });
                    this.notify("success", `Modo Formas: Se prepararon ${this.#executionLine.length} líneas desde formas detectadas.`);
                    break;
                }
                default:
                    this.notify("warning", `Modo de dibujo desconocido: '${this.#currentDrawingMode}'. Usando un modo de línea por defecto.`);
                    // Fallback to simple horizontal line scan if mode is unknown
                    await this.#generateHorizontalLineCommands();
                    break;
            }

            this.notify("info", `Comandos de dibujo preparados para el modo '${this.#currentDrawingMode}': ${this.#executionLine.length} líneas.`);
            return true;
        }

        // Generic horizontal line scanning mode (can be used as default or specific mode)
        async #generateHorizontalLineCommands() {
            if (!this.#imageData) {
                this.notify("warning", "No hay datos de imagen para generar comandos (horizontal).");
                return;
            }

            const pixels = this.#imageData.data;
            const width = this.#imageData.width;
            const height = this.#imageData.height;

            const brushSize = parseInt(this.#brushSizeInput.value) || 2;
            const offsetX = parseFloat(this.#offsetXInput.value) || 0;
            const offsetY = parseFloat(this.#offsetYInput.value) || 0;
            const pixelDensity = parseInt(this.#pixelSizeInput.value) || 1;
            const colorTolerance = parseInt(this.#colorToleranceInput.value) || 15;

            const imageRenderSize = 100 / parseInt(this.#imageSizeInput.value);
            const scaleX = imageRenderSize / width;
            const scaleY = imageRenderSize / height;

            for (let y = 0; y < height; y += pixelDensity) {
                let currentLineColor = null;
                let lineStartX = -1;

                for (let x = 0; x < width; x += pixelDensity) {
                    const index = (y * width + x) * 4;
                    const r = pixels[index];
                    const g = pixels[index + 1];
                    const b = pixels[index + 2];
                    const a = pixels[index + 3];

                    const currentColor = [r, g, b, a];

                    if (a > 20) {
                        if (currentLineColor === null) {
                            currentLineColor = currentColor;
                            lineStartX = x;
                        } else if (!this.#areColorsSimilar(currentLineColor, currentColor, colorTolerance)) {
                            const gameX1 = lineStartX * scaleX + offsetX;
                            const gameY1 = y * scaleY + offsetY;
                            const gameX2 = (x - pixelDensity) * scaleX + offsetX;

                            this.#executionLine.push({
                                x1: gameX1,
                                y1: gameY1,
                                x2: gameX2 + (brushSize * scaleX * 0.5),
                                y2: gameY1,
                                color: this.#rgbaArrayToString(currentLineColor),
                                thickness: brushSize
                            });

                            currentLineColor = currentColor;
                            lineStartX = x;
                        }
                    } else {
                        if (currentLineColor !== null) {
                            const gameX1 = lineStartX * scaleX + offsetX;
                            const gameY1 = y * scaleY + offsetY;
                            const gameX2 = (x - pixelDensity) * scaleX + offsetX;
                            this.#executionLine.push({
                                x1: gameX1,
                                y1: gameY1,
                                x2: gameX2 + (brushSize * scaleX * 0.5),
                                y2: gameY1,
                                color: this.#rgbaArrayToString(currentLineColor),
                                thickness: brushSize
                            });
                            currentLineColor = null;
                            lineStartX = -1;
                        }
                    }
                }
                if (currentLineColor !== null && lineStartX !== -1) {
                    const gameX1 = lineStartX * scaleX + offsetX;
                    const gameY1 = y * scaleY + offsetY;
                    const gameX2 = (width - pixelDensity) * scaleX + offsetX;
                    this.#executionLine.push({
                        x1: gameX1,
                        y1: gameY1,
                        x2: gameX2 + (brushSize * scaleX * 0.5),
                        y2: gameY1,
                        color: this.#rgbaArrayToString(currentLineColor),
                        thickness: brushSize
                    });
                }
            }
        }


        #shuffleArray(array) {
            for (let i = array.length - 1; i > 0; i--) {
                const j = Math.floor(Math.random() * (i + 1));
                [array[i], array[j]] = [array[j], array[i]];
            }
        }

        async #startDrawing() { // This method now initiates the drawing task, could be image or text
            if (this.#imageData && this.#executionLine.length === 0) { // If image is loaded but commands not generated
                 await this.#prepareDrawingCommands(); // Ensure commands are generated for the current image
            }

            if (this.#executionLine.length === 0) {
                this.notify("warning", "No hay comandos de dibujo preparados. Carga una imagen o texto primero.");
                return;
            }

            const gameSocket = this.#getGameSocket();
            if (!gameSocket) {
                this.notify("warning", "No se encontró una conexión WebSocket activa con el juego. Asegúrate de estar en una sala de Drawaria.");
                return;
            }

            this.#drawingActive = true;
            this.#currentDrawingIndex = 0;
            this.notify("info", `Iniciando tarea de dibujo...`);
            this.#updateStatus(`Dibujando... 0/${this.#executionLine.length} líneas.`);

            const delayMs = parseInt(this.#drawingSpeedInput.value);
            let currentLineIndex = 0;

            // Get the current game canvas context for local rendering
            const localCtx = this.#originalCtx;
            const canvasWidth = this.#originalCanvas.width;
            const canvasHeight = this.#originalCanvas.height;

            while (this.#drawingActive && currentLineIndex < this.#executionLine.length) {
                const line = this.#executionLine[currentLineIndex];

                if (!gameSocket || gameSocket.readyState !== WebSocket.OPEN) {
                    this.notify("warning", "Conexión WebSocket perdida. Deteniendo dibujo.");
                    this.#stopDrawing();
                    break;
                }

                let drawColor = line.color;
                if (this.#rainbowMode) {
                    drawColor = `hsl(${this.#currentHue}, 100%, 50%)`;
                }

                // Convert game coordinates (0-100) to local pixel coordinates
                const localX1 = (line.pos1[0] / 100) * canvasWidth;
                const localY1 = (line.pos1[1] / 100) * canvasHeight;
                const localX2 = (line.pos2[0] / 100) * canvasWidth;
                const localY2 = (line.pos2[1] / 100) * canvasHeight;

                // Local rendering on the game canvas
                if (localCtx) {
                    localCtx.strokeStyle = drawColor;
                    localCtx.lineWidth = line.thickness;
                    localCtx.lineCap = 'round';
                    localCtx.beginPath();
                    localCtx.moveTo(localX1, localY1);
                    localCtx.lineTo(localX2, localY2);
                    localCtx.stroke();
                }

                // Construct and send the draw command directly via the game's WebSocket
                const command = `42["drawcmd",0,[${line.pos1[0]/100},${line.pos1[1]/100},${line.pos2[0]/100},${line.pos2[1]/100},false,${0 - line.thickness},"${drawColor}",0,0,{}]]`;
                gameSocket.send(command);

                currentLineIndex++;
                this.#updateStatus(`Dibujando... ${currentLineIndex}/${this.#executionLine.length} líneas.`);
                await new Promise(resolve => setTimeout(resolve, delayMs));
            }

            if (!this.#drawingActive) {
                this.notify("info", `Tarea de dibujo detenida manualmente. Completado ${currentLineIndex} de ${this.#executionLine.length} líneas.`);
            } else {
                this.notify("success", "Tarea de dibujo completada!");
            }
            this.#drawingActive = false;
            this.#updateStatus("Estado: Listo.");
        }

        #stopDrawing() {
            this.#drawingActive = false;
            this.notify("info", "Tarea de dibujo detenida.");
            this.#updateStatus("Estado: Detenido.");
        }

        #setDrawingMode(mode) {
            // Remove 'active' class from all mode buttons
            for (const key in this.#modeButtons) {
                if (this.#modeButtons.hasOwnProperty(key)) {
                    this.#modeButtons[key].classList.remove("active");
                }
            }
            // Add 'active' class to the clicked button
            if (this.#modeButtons[mode]) {
                this.#modeButtons[mode].classList.add("active");
            }

            this.#currentDrawingMode = mode;
            this.notify("info", `Modo de dibujo de imagen cambiado a: '${mode}'`);

            if (this.#imageData && this.#imageData.data && this.#imageData.data.length > 0) {
                this.#prepareDrawingCommands(); // Regenerate commands for the new mode
            }
        }

        async #startTextDrawing() {
            const text = this.#pantherTextInput.value.toUpperCase();
            if (!text) {
                this.notify("warning", "Introduce texto para dibujar.");
                return;
            }

            const gameSocket = this.#getGameSocket();
            if (!gameSocket) {
                this.notify("warning", "No se encontró una conexión WebSocket activa con el juego. Asegúrate de estar en una sala de Drawaria.");
                return;
            }

            this.notify("info", `Iniciando dibujo de texto: "${text}"`);
            this.#updateStatus(`Dibujando texto...`);

            this.#drawingActive = true;
            this.#executionLine = []; // Clear current image commands

            const charWidth = 5;
            const charHeight = 7;
            const pixelSize = parseInt(this.#pixelSizeInput.value) || 1;
            const textSize = parseInt(this.#pantherTextSizeInput.value) || 4;
            const textColor = this.#pantherTextColorInput.value || "#000000";
            const thickness = parseInt(this.#brushSizeInput.value) || 5;

            const scaledPixelSize = pixelSize * (textSize / 2);
            let currentXOffset = parseFloat(this.#offsetXInput.value) || 0;
            const startYOffset = parseFloat(this.#offsetYInput.value) || 0;
            const delayMs = parseInt(this.#drawingSpeedInput.value) || 10;

            // Get the current game canvas context for local rendering
            const localCtx = this.#originalCtx;
            const canvasWidth = this.#originalCanvas.width;
            const canvasHeight = this.#originalCanvas.height;

            while (this.#drawingActive) { // Loop until drawing is stopped or text is fully drawn
                let charDrawn = false;
                if (this.#executionLine.length < text.length * charWidth * charHeight) { // Heuristic check if text is still being processed
                    const charIndex = Math.floor(this.#executionLine.length / (charWidth * charHeight));
                    const char = text[charIndex];

                    if (char) {
                        if (!FONT_MAP[char]) {
                            this.notify("warning", `Carácter '${char}' no soportado, saltando.`);
                            currentXOffset += (charWidth + 1) * scaledPixelSize;
                            this.#executionLine.push({}); // Dummy to advance index
                        } else {
                            const bitmap = FONT_MAP[char];
                            const currentBitmapPixelIndex = this.#executionLine.length % (charWidth * charHeight);
                            const y = Math.floor(currentBitmapPixelIndex / charWidth);
                            const x = currentBitmapPixelIndex % charWidth;

                            if (y < charHeight && bitmap[y][x] === 1) {
                                const gameX = currentXOffset + x * scaledPixelSize;
                                const gameY = startYOffset + y * scaledPixelSize;

                                if (!gameSocket || gameSocket.readyState !== WebSocket.OPEN) {
                                    this.notify("warning", "Conexión WebSocket perdida. Deteniendo dibujo de texto.");
                                    this.#stopDrawing();
                                    break;
                                }

                                let drawColor = textColor;
                                if (this.#rainbowMode) {
                                    drawColor = `hsl(${this.#currentHue}, 100%, 50%)`;
                                }

                                // Convert game coordinates (0-100) to local pixel coordinates
                                const localX = (gameX / 100) * canvasWidth;
                                const localY = (gameY / 100) * canvasHeight;

                                // Local rendering on the game canvas
                                if (localCtx) {
                                    localCtx.strokeStyle = drawColor;
                                    localCtx.lineWidth = thickness;
                                    localCtx.lineCap = 'round';
                                    localCtx.beginPath();
                                    localCtx.moveTo(localX, localY);
                                    localCtx.lineTo(localX, localY); // Draw a dot
                                    localCtx.stroke();
                                }

                                // Send draw command directly via game's WebSocket
                                const command = `42["drawcmd",0,[${gameX/100},${gameY/100},${gameX/100},${gameY/100},false,${0 - thickness},"${drawColor}",0,0,{}]]`;
                                gameSocket.send(command);

                                this.#executionLine.push({}); // Dummy entry to count commands
                                charDrawn = true;
                            } else if (y >= charHeight) { // Finished current character's bitmap
                                currentXOffset += (charWidth + 1) * scaledPixelSize;
                                this.#executionLine.length = (charIndex + 1) * (charWidth * charHeight); // Advance execution line
                            }
                        }
                    } else { // No more characters in text
                        break;
                    }
                } else { // All characters drawn
                    break;
                }

                if (charDrawn) {
                    this.#updateStatus(`Dibujando texto... Línea: ${this.#executionLine.length}`);
                }
                await new Promise(resolve => setTimeout(resolve, delayMs));
            }

            if (!this.#drawingActive) {
                this.notify("info", `Dibujo de texto detenido manualmente.`);
            } else {
                this.notify("success", "Dibujo de texto completado!");
            }
            this.#drawingActive = false;
            this.#updateStatus("Estado: Listo.");
        }


        #initGradientStrip() {
            const strip = this.htmlElements.section.querySelector('#gradientStrip');
            if (!strip) return;
            const ctx = strip.getContext('2d');
            strip.width = strip.offsetWidth;
            strip.height = strip.offsetHeight;

            for (let x = 0; x < strip.width; x++) {
                const hue = (x / strip.width) * 360;
                ctx.fillStyle = `hsl(${hue}, 100%, 50%)`;
                ctx.fillRect(x, 0, 1, strip.height);
            }
        }

        #handleGradientStripClick(e) {
            const strip = e.target;
            const rect = strip.getBoundingClientRect();
            const x = e.clientX - rect.left;
            this.#currentHue = (x / rect.width) * 360;
            this.#rainbowMode = false;
            this.htmlElements.section.querySelector('#rainbowModeToggle').checked = false;
            this.notify("info", `Color fijo seleccionado (Hue: ${Math.round(this.#currentHue)}).`);
        }

        #toggleRainbowMode(checked) {
            this.#rainbowMode = checked;
            if (this.#rainbowMode) {
                this.notify("info", "Modo Arcoíris activado.");
            } else {
                this.notify("info", "Modo Arcoíris desactivado.");
            }
        }

        #updateRainbowColor(timestamp) {
            if (!this.#rainbowMode) {
                requestAnimationFrame(this.#updateRainbowColor.bind(this));
                return;
            }
            if (timestamp - this.#lastRainbowUpdate < this.#rainbowSpeed) {
                requestAnimationFrame(this.#updateRainbowColor.bind(this));
                return;
            }
            this.#lastRainbowUpdate = timestamp;
            const step = this.#rainbowDirection === 'forward' ? 1 : -1;
            this.#currentHue = (this.#currentHue + step) % 360;
            if (this.#currentHue < 0) this.#currentHue += 360;
            requestAnimationFrame(this.#updateRainbowColor.bind(this));
        }

        #updateStatus(message) {
            this.#statusMessageElement.innerHTML = `Estado: ${message}`;
        }

        // --- MiniCanvas Drawing and Ruler Functionality (Adapted from original Manager V3) ---

        #setupMiniCanvasDrawing() {
            this.#miniCtx.lineCap = 'round';
            this.#miniCtx.lineJoin = 'round';

            this.#miniCanvas.addEventListener('mousedown', (e) => this.#handleMiniCanvasMouseDown(e));
            this.#miniCanvas.addEventListener('mousemove', (e) => this.#handleMiniCanvasMouseMove(e));
            this.#miniCanvas.addEventListener('mouseup', () => this.#handleMiniCanvasMouseUp());
            this.#miniCanvas.addEventListener('mouseout', () => this.#handleMiniCanvasMouseUp()); // Stop drawing if mouse leaves
        }

        #getMiniCanvasPos(e) {
            const rect = this.#miniCanvas.getBoundingClientRect();
            return {
                x: e.clientX - rect.left,
                y: e.clientY - rect.top
            };
        }

        #handleMiniCanvasMouseDown(e) {
            this.#isMiniDrawing = true;
            const pos = this.#getMiniCanvasPos(e);
            let x = pos.x;
            let y = pos.y;

            if (this.#rulerMode) {
                const effectiveAngle = this.#getRulerEffectiveAngle(x, y);
                const angleRad = (effectiveAngle * Math.PI) / 180;
                const dx = pos.x - this.#rulerX;
                const dy = pos.y - this.#rulerY;
                const proj = dx * Math.cos(angleRad) + dy * Math.sin(angleRad);
                x = this.#rulerX + proj * Math.cos(angleRad);
                y = this.#rulerY + proj * Math.sin(angleRad);
            }
            this.#miniLastX = x;
            this.#miniLastY = y;
            // For first point, draw a dot (line from itself to itself)
            this.#drawOnMiniCanvas(pos.x, pos.y, this.#miniLastX, this.#miniLastY, this.#brushSizeInput.value);
        }

        #handleMiniCanvasMouseMove(e) {
            if (!this.#isMiniDrawing) return;
            const pos = this.#getMiniCanvasPos(e);
            let x = pos.x;
            let y = pos.y;

            if (this.#rulerMode) {
                const effectiveAngle = this.#getRulerEffectiveAngle(x, y, this.#miniLastX, this.#miniLastY);
                const angleRad = (effectiveAngle * Math.PI) / 180;
                const dx = pos.x - this.#rulerX;
                const dy = pos.y - this.#rulerY;
                const proj = dx * Math.cos(angleRad) + dy * Math.sin(angleRad);
                x = this.#rulerX + proj * Math.cos(angleRad);
                y = this.#rulerY + proj * Math.sin(angleRad);
            }
            this.#drawOnMiniCanvas(x, y, this.#miniLastX, this.#miniLastY, this.#brushSizeInput.value);
            this.#miniLastX = x;
            this.#miniLastY = y;
        }

        #handleMiniCanvasMouseUp() {
            this.#isMiniDrawing = false;
        }

        #drawOnMiniCanvas(x, y, lastX, lastY, thickness) {
            const gameSocket = this.#getGameSocket();
            if (!gameSocket) {
                this.notify("warning", "No se encontró una conexión WebSocket activa con el juego para dibujar.");
                return;
            }

            const miniCanvasWidth = this.#miniCanvas.width;
            const miniCanvasHeight = this.#miniCanvas.height;

            // Convert miniCanvas coordinates (0-miniCanvas.width/height) to game coordinates (0-100)
            const gameX = (x / miniCanvasWidth) * 100;
            const gameY = (y / miniCanvasHeight) * 100;
            const gameLastX = (lastX / miniCanvasWidth) * 100;
            const gameLastY = (lastY / miniCanvasHeight) * 100;

            let drawColor = `hsl(${this.#currentHue}, 100%, 50%)`;

            // Local rendering on the game canvas (main game canvas)
            if (this.#originalCtx) {
                const localX = (gameX / 100) * this.#originalCanvas.width;
                const localY = (gameY / 100) * this.#originalCanvas.height;
                const localLastX = (gameLastX / 100) * this.#originalCanvas.width;
                const localLastY = (gameLastY / 100) * this.#originalCanvas.height;

                this.#originalCtx.strokeStyle = drawColor;
                this.#originalCtx.lineWidth = thickness;
                this.#originalCtx.lineCap = 'round';
                this.#originalCtx.beginPath();
                this.#originalCtx.moveTo(localLastX, localLastY);
                this.#originalCtx.lineTo(localX, localY);
                this.#originalCtx.stroke();
            }

            // Send draw command directly via game's WebSocket
            const command = `42["drawcmd",0,[${gameLastX/100},${gameLastY/100},${gameX/100},${gameY/100},false,${0 - thickness},"${drawColor}",0,0,{}]]`;
            gameSocket.send(command);

            // Also draw on the miniCanvas itself for visual feedback
            this.#miniCtx.beginPath();
            this.#miniCtx.moveTo(lastX, lastY);
            this.#miniCtx.lineTo(x, y);
            this.#miniCtx.strokeStyle = drawColor;
            this.#miniCtx.lineWidth = thickness / 2; // Scale down thickness for miniCanvas
            this.#miniCtx.stroke();
        }

        // --- Ruler Logic ---
        #setupRuler() {
            this.#rulerLineElement = domMake.Tree('div', { class: 'ruler-line' });
            this.#miniCanvas.parentNode.insertBefore(this.#rulerLineElement, this.#miniCanvas.nextSibling);

            // Event listeners for ruler controls
            this.htmlElements.section.querySelector('#rulerModeToggle').addEventListener('change', (e) => {
                this.#rulerMode = e.target.checked;
                this.#updateRulerDisplay();
            });
            this.htmlElements.section.querySelector('#rulerHorizontalToggle').addEventListener('change', (e) => {
                this.#rulerHorizontal = e.target.checked;
                if (this.#rulerHorizontal) {
                    this.htmlElements.section.querySelector('#rulerVerticalToggle').checked = false;
                    this.htmlElements.section.querySelector('#rulerV2Toggle').checked = false;
                    this.#rulerVertical = false;
                    this.#rulerV2 = false;
                }
                this.#updateRulerDisplay();
            });
            this.htmlElements.section.querySelector('#rulerVerticalToggle').addEventListener('change', (e) => {
                this.#rulerVertical = e.target.checked;
                if (this.#rulerVertical) {
                    this.htmlElements.section.querySelector('#rulerHorizontalToggle').checked = false;
                    this.htmlElements.section.querySelector('#rulerV2Toggle').checked = false;
                    this.#rulerHorizontal = false;
                    this.#rulerV2 = false;
                }
                this.#updateRulerDisplay();
            });
            this.htmlElements.section.querySelector('#rulerV2Toggle').addEventListener('change', (e) => {
                this.#rulerV2 = e.target.checked;
                if (this.#rulerV2) {
                    this.htmlElements.section.querySelector('#rulerHorizontalToggle').checked = false;
                    this.htmlElements.section.querySelector('#rulerVerticalToggle').checked = false;
                    this.#rulerHorizontal = false;
                    this.#rulerVertical = false;
                }
                this.#updateRulerDisplay();
            });

            this.#rulerAngleInput.addEventListener('input', (e) => {
                this.#rulerAngle = parseInt(e.target.value);
                this.#updateRulerDisplay();
            });
            this.#rulerXInput.addEventListener('input', (e) => {
                this.#rulerX = parseInt(e.target.value);
                this.#updateRulerDisplay();
            });
            this.#rulerYInput.addEventListener('input', (e) => {
                this.#rulerY = parseInt(e.target.value);
                this.#updateRulerDisplay();
            });

            this.#updateRulerDisplay(); // Initial display update
        }

        #updateRulerDisplay() {
            if (!this.#rulerLineElement) return;

            this.#rulerLineElement.style.left = `${this.#rulerX}px`;
            this.#rulerLineElement.style.top = `${this.#rulerY}px`;

            let effectiveAngle = this.#rulerAngle;
            if (this.#rulerHorizontal) {
                effectiveAngle = 0;
            } else if (this.#rulerVertical) {
                effectiveAngle = 90;
            }
            // For Ruler V2, angle is calculated dynamically in getRulerEffectiveAngle
            // Here, we just ensure it's not overridden by stored angle if V2 is active.
            if (this.#rulerV2) {
                // If V2 is active, the static angle input is ignored for display
                // The actual snap logic happens during mousemove. We just hide the line for simplicity.
                this.#rulerLineElement.style.display = 'none';
                return;
            }

            this.#rulerLineElement.style.transform = `rotate(${effectiveAngle}deg)`;
            this.#rulerLineElement.style.display = this.#rulerMode ? 'block' : 'none';
        }

        #getRulerEffectiveAngle(currentX, currentY, lastX = 0, lastY = 0) {
            if (this.#rulerHorizontal) return 0;
            if (this.#rulerVertical) return 90;
            if (this.#rulerV2) {
                // Snaps to nearest 0, 45, 90, 135, 180, 225, 270, 315 degree angle
                const dx = currentX - lastX;
                const dy = currentY - lastY;
                const angle = Math.atan2(dy, dx) * (180 / Math.PI); // Angle in degrees
                const snappedAngle = Math.round(angle / 45) * 45;
                return snappedAngle < 0 ? snappedAngle + 360 : snappedAngle; // Ensure positive angle
            }
            return this.#rulerAngle;
        }

        // Helper to get the currently active game WebSocket
        #getGameSocket() {
            if (globalThis.sockets && globalThis.sockets.length > 0) {
                // Find the first WebSocket that is in OPEN state
                return globalThis.sockets.find(s => s.readyState === WebSocket.OPEN);
            }
            return null;
        }
    }
})();

// START INTELLIGENT ARTIST + DRAWING ASSISTANT (FUSIONADO)
// START INTELLIGENT ARTIST + DRAWING ASSISTANT (FUSIONADO)
(function ArtisanStudioModule() {
    const QBit = globalThis[arguments[0]];

    // --- Consolidated Styles ---
    QBit.Styles.addRules([
        // Styles for Intelligent Artist sections
        `#${QBit.identifier} .intelligent-artist-section {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 10px;
            background-color: var(--CE-bg_color);
        }`,
        `#${QBit.identifier} .intelligent-artist-section-title {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--dark-blue-title);
            text-align: center;
        }`,
        `#${QBit.identifier} .intelligent-artist-section input[type="text"],
         #${QBit.identifier} .intelligent-artist-section input[type="number"],
         #${QBit.identifier} .intelligent-artist-section input[type="color"],
         #${QBit.identifier} .drawing-assistant-controls input[type="number"],
         #${QBit.identifier} .drawing-assistant-controls input[type="color"] {
            width: 100%; padding: 5px; box-sizing: border-box;
            border: 1px solid var(--CE-color); border-radius: .25rem;
            background-color: var(--CE-bg_color); color: var(--CE-color);
         }`,
        `#${QBit.identifier} .intelligent-artist-section ._row > *,
         #${QBit.identifier} .drawing-assistant-controls ._row > * {
            margin: 0 2px;
        }`,
        // UPDATED: Styles for the horizontal scrolling sketch list
        `#${QBit.identifier} .intelligent-artist-sketch-list {
            display: flex;
            flex-wrap: nowrap; /* Forces horizontal layout */
            overflow-x: auto;  /* Adds the scrollbar when content overflows */
            gap: 5px;
            padding: 5px; /* Add padding inside the box */
            margin-top: 5px; /* Space from label above */
            border: 1px solid var(--CE-color); /* The "cuadrito" border */
            border-radius: .25rem;
            background-color: var(--CE-bg_color); /* Background for the box */
            line-height: normal; /* Ensure text within buttons is normal */
        }`,
        `#${QBit.identifier} .intelligent-artist-sketch-list .btn {
            flex: 0 0 auto; /* Prevents stretching and ensures they stay put */
            margin: 0; /* Remove default margin from btn class */
            padding: 2px 5px; /* Compact padding */
            font-size: 0.7em;
            white-space: nowrap; /* Prevents button text from wrapping */
        }`,

        // Styles for Drawing Assistant sections
        `#drawing-assistant-overlay {
            position: absolute;
            top: 0;
            left: 0;
            z-index: 1000; /* Above game canvas, below main UI elements */
            pointer-events: none; /* Crucial to allow clicks to pass through to game canvas */
        }`,
        `#drawing-assistant-grid-toggle.active,
         #drawing-assistant-symmetry-toggle.active,
         #drawing-assistant-pixel-perfect-toggle.active {
             background-color: var(--info);
             color: white;
         }`,
        // Module toggle button specific styles
        `#${QBit.identifier} .module-toggle-button {
            background-color: var(--secondary);
            color: var(--dark);
            width: 100%;
            padding: 8px 15px;
            box-sizing: border-box;
            font-size: 0.95em;
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 8px;
            margin-bottom: 10px;
        }`,
        `#${QBit.identifier} .module-toggle-button.active {
            background-color: var(--success);
            color: white;
        }`,
        // Transparent brush specific styles
        `#${QBit.identifier} .transparent-brush-toggle-button {
            background: #492; /* Specific color for this button */
            color: #fff;
            width: 100%;
            padding: 7px 0;
            border: none;
            border-radius: 7px;
            font-weight: bold;
            cursor: pointer;
            margin-bottom: 8px;
        }`,
        `#${QBit.identifier} .transparent-brush-toggle-button.active {
            background: #2a5; /* Active color */
        }`,
        `#${QBit.identifier} .transparent-brush-opacity-slider {
            width: 70px;
            vertical-align: middle;
            -webkit-appearance: none; /* Remove default slider style */
            height: 6px; /* Adjust height */
            background: #ddd; /* Track color */
            outline: none;
            border-radius: 3px;
        }`,
        `#${QBit.identifier} .transparent-brush-opacity-slider::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 16px; /* Thumb size */
            height: 16px;
            border-radius: 50%;
            background: #666; /* Thumb color */
            cursor: grab;
        }`,
        `#${QBit.identifier} .transparent-brush-opacity-slider::-moz-range-thumb {
            width: 16px;
            height: 16px;
            border-radius: 50%;
            background: #666;
            cursor: grab;
        }`,
        `#${QBit.identifier} .transparent-brush-percentage {
            display: inline-block;
            min-width: 35px;
            text-align: right;
        }`
    ]);

    // --- General WebSocket Helper (centralized for all modules, botless) ---
    // Defined WITHIN the IIFE to be accessible to ArtisanStudio's methods
    function getGameSocket() {
        if (globalThis.sockets && globalThis.sockets.length > 0) {
            // Find the first WebSocket that is in OPEN state
            return globalThis.sockets.find(s => s.readyState === WebSocket.OPEN);
        }
        return null;
    }


    class ArtisanStudio extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        // --- Module Active State ---
        _isActive = false;

        // --- Intelligent Artist Properties ---
        _currentBrushSize = 5;
        _currentSketchColor = "#888888";
        _SKETCH_DATABASE = {}; // Will be loaded dynamically
        _sketchTextInput;
        _sketchListContainer; // Reference to the container for quick sketch buttons
        _sketchListCountLabel; // Reference to the label that displays the sketch count
        _ui = {}; // Centralized object for UI element references

        // --- Drawing Assistant Properties ---
        _overlayCanvas;
        _overlayCtx;
        _gameCanvas; // Reference to the actual game canvas
        _gameCtx; // Context of the actual game canvas

        _isSymmetryActive = false;
        _isPixelPerfectActive = false;
        _isGridVisible = false;

        _drawingColor = "#000000"; // Default drawing color for assistant tools
        _drawingThickness = 5;    // Default drawing thickness for assistant tools

        // State for drawing assistant
        _isDrawingLocal = false;
        _lastX = 0;
        _lastY = 0;

        // --- Transparent Brush Properties (NEW) ---
        _isTransparentBrushActive = false;
        _customBrushAlpha = 0.3; // Initial value

        // Original bound handlers for drawing assistant (keep these)
        _boundMouseDownHandler = this._handleMouseDown.bind(this);
        _boundMouseMoveHandler = this._handleMouseMove.bind(this);
        _boundMouseUpHandler = this._handleMouseUp.bind(this);

        // Handlers for shape drawing mode (to be managed for detachment)
        _onClickShapeHandler = null;
        _onMoveShapeHandler = null;


        constructor() {
            super("Artisan Studio", '<i class="fas fa-palette"></i>'); // New name and icon

            // FIX: Define _setModuleActive and _setUIEnabled directly on 'this'
            // in the constructor using arrow function syntax. This makes them
            // properties of the instance and guarantees their 'this' context and availability.
            this._setModuleActive = (active) => {
                this._isActive = active;
                if (this._isActive) {
                    this._ui.moduleToggleButton.innerHTML = '<i class="fas fa-power-off"></i> Desactivar Artisan Studio';
                    this._ui.moduleToggleButton.classList.add('active');
                    //this.notify("info", "Módulo 'Artisan Studio' ACTIVADO.");
                    this._setUIEnabled(true);
                    this._hookGameDrawingEvents(); // Attach listeners for drawing assistant
                } else {
                    this._ui.moduleToggleButton.innerHTML = '<i class="fas fa-power-off"></i> Activar Artisan Studio';
                    this._ui.moduleToggleButton.classList.remove('active');
                    //this.notify("info", "Módulo 'Artisan Studio' DESACTIVADO.");
                    this._setUIEnabled(false);
                    this._unhookGameDrawingEvents(); // Detach listeners
                }
            };

            this._setUIEnabled = (enabled) => {
                const interactiveElements = [
                    this._sketchTextInput,
                    this._ui.generateSketchButton,
                    this._ui.clearCanvasButton,
                    this._ui.sketchColorInput,
                    this._ui.brushSizeInput,
                    this._ui.assistantColorInput,
                    this._ui.assistantThicknessInput,
                    this._ui.drawLineButton,
                    this._ui.drawRectButton,
                    this._ui.drawCircleButton,
                    this._ui.toggleGridButton,
                    this._ui.toggleSymmetryButton,
                    this._ui.togglePixelPerfectButton,
                    this._ui.startCollabDrawButton,
                    this._ui.transparentBrushToggleButton,
                    this._ui.transparentBrushSlider
                ];

                interactiveElements.forEach(el => {
                    if (el) {
                        el.disabled = !enabled;
                    }
                });

                if (this._ui.transparentBrushPercent) {
                    this._ui.transparentBrushPercent.style.color = enabled ? 'var(--CE-color)' : 'var(--dark-gray)';
                }

                if (this._sketchListContainer) {
                    Array.from(this._sketchListContainer.children).forEach(button => {
                        if (button.tagName === 'BUTTON') {
                            button.disabled = !enabled;
                        }
                    });
                }

                if (!enabled) {
                    this._isDrawingLocal = false;
                    this._clearOverlay();
                    if (this._gameCanvas) {
                        if (this._onClickShapeHandler) {
                            this._gameCanvas.removeEventListener('click', this._onClickShapeHandler);
                            this._onClickShapeHandler = null;
                        }
                        if (this._onMoveShapeHandler) {
                            this._gameCanvas.removeEventListener('mousemove', this._onMoveShapeHandler);
                            this._onMoveShapeHandler = null;
                        }
                    }
                }
            };

            this._onStartup();
        }

        async _onStartup() {
            this._findGameCanvas();
            this._setupOverlayCanvas();
            this._loadInterface();
            await this._loadSketchesFromGithub();

            // FIX: Call the already-bound method directly. No setTimeout needed now that it's an instance property.
            this._setModuleActive(false);
            //this.notify("info", "Módulo 'Artisan Studio' cargado. Explora las herramientas de dibujo asistido y generación de bocetos.");
        }

        _loadInterface() {
            const container = domMake.Tree("div");

            // --- NEW: Toggle for activating/deactivating the module ---
            const moduleToggleRow = domMake.Row();
            this._ui.moduleToggleButton = domMake.Button('<i class="fas fa-power-off"></i> Activar Artisan Studio');
            this._ui.moduleToggleButton.classList.add('module-toggle-button');
            this._ui.moduleToggleButton.addEventListener('click', () => this._toggleModuleActive());
            moduleToggleRow.appendChild(this._ui.moduleToggleButton);
            container.appendChild(moduleToggleRow);


            // --- Section: Bocetos Asistidos y Limpieza (Intelligent Artist) ---
            const intelligentArtistSection = domMake.Tree("div", { class: "intelligent-artist-section" });
            intelligentArtistSection.appendChild(domMake.Tree("div", { class: "intelligent-artist-section-title" }, ["Bocetos Asistidos"]));

            // Row 1: Generate Sketch
            const generateSketchRow = domMake.Row();
            this._sketchTextInput = domMake.Tree("input", { type: "text", placeholder: "Concepto de boceto (ej. 'árbol')" });
            this._ui.generateSketchButton = domMake.Button("Generar Boceto");
            this._ui.generateSketchButton.title = "Dibuja un boceto predefinido para la palabra ingresada.";
            this._ui.generateSketchButton.addEventListener("click", () => {
                this._simulateAISketch(this._sketchTextInput.value.toUpperCase());
            });
            generateSketchRow.appendAll(this._sketchTextInput, this._ui.generateSketchButton);
            intelligentArtistSection.appendChild(generateSketchRow);

            // Row 2: Clear Canvas
            const clearCanvasRow = domMake.Row();
            this._ui.clearCanvasButton = domMake.Button("Limpiar Lienzo");
            this._ui.clearCanvasButton.title = "Limpia el lienzo con una línea blanca muy grande.";
            this._ui.clearCanvasButton.addEventListener("click", () => {
                const gameSocket = getGameSocket(); // Use global helper function
                if (gameSocket) {
                    // Clear locally first for instant feedback
                    if (this._gameCtx && this._gameCanvas) {
                        this._gameCtx.clearRect(0, 0, this._gameCanvas.width, this._gameCanvas.height);
                    }
                    // Send clear commands to server
                    gameSocket.send(`42["drawcmd",0,[0.5,0.5,0.5,0.5,true,-2000,"#FFFFFF",0,0,{}]]`); // Clear with a huge white square
                    this.notify("success", "El lienzo ha sido limpiado.");
                } else {
                    this.notify("warning", "No hay conexión WebSocket activa para limpiar el lienzo.");
                }
            });
            clearCanvasRow.appendChild(this._ui.clearCanvasButton);
            intelligentArtistSection.appendChild(clearCanvasRow);

            // Row 3: Sketch Color and Size
            const sketchConfigRow = domMake.Row();
            const sketchColorLabel = domMake.Tree("label", {}, ["Color Boceto:"]);
            this._ui.sketchColorInput = domMake.Tree("input", { type: "color", value: this._currentSketchColor });
            this._ui.sketchColorInput.title = "Define el color del boceto.";
            this._ui.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:"]);
            this._ui.brushSizeInput = domMake.Tree("input", { type: "number", min: 1, max: 50, value: this._currentBrushSize });
            this._ui.brushSizeInput.title = "Define el tamaño del pincel para el boceto.";
            this._ui.brushSizeInput.addEventListener("change", (e) => {
                this._currentBrushSize = parseInt(e.target.value);
                //is.notify("info", `Tamaño del pincel para boceto cambiado a: ${this._currentBrushSize}`);
            });
            sketchConfigRow.appendAll(sketchColorLabel, this._ui.sketchColorInput, brushSizeLabel, this._ui.brushSizeInput);
            intelligentArtistSection.appendChild(sketchConfigRow);

            // Row 4: Sketch List - Adjusted structure for horizontal scrolling box
            this._sketchListCountLabel = domMake.Tree("label", { id: "artisan-studio-sketch-count-label" }, ["Bocetos Rápidos (0):"]);
            // Append label directly, not in a domMake.Row() with the container to ensure stacking
            intelligentArtistSection.appendChild(this._sketchListCountLabel);

            this._sketchListContainer = domMake.IconList({ class: 'intelligent-artist-sketch-list' }); // Uses the new CSS for the box and slider
            // Append the container directly
            intelligentArtistSection.appendChild(this._sketchListContainer);


            container.appendChild(intelligentArtistSection);

            // --- Section: Herramientas de Dibujo Asistido (Drawing Assistant) ---
            const drawingAssistantSection = domMake.Tree("div", { class: "intelligent-artist-section" });
            drawingAssistantSection.appendChild(domMake.Tree("div", { class: "intelligent-artist-section-title" }, ["Herramientas Asistidas"]));

            // Drawing parameters (Color, Thickness)
            const assistantParamsRow = domMake.Row();
            assistantParamsRow.style.gap = "5px";

            this._ui.assistantColorInput = domMake.Tree("input", { type: "color", value: this._drawingColor, title: "Color de Dibujo" });
            this._ui.assistantColorInput.addEventListener("change", (e) => this._drawingColor = e.target.value);
            assistantParamsRow.appendAll(domMake.Tree("label", {}, ["Color:"]), this._ui.assistantColorInput);

            this._ui.assistantThicknessInput = domMake.Tree("input", { type: "number", min: "1", max: "100", value: this._drawingThickness, title: "Grosor de Dibujo" });
            this._ui.assistantThicknessInput.addEventListener("change", (e) => this._drawingThickness = parseInt(e.target.value));
            assistantParamsRow.appendAll(domMake.Tree("label", {}, ["Grosor:"]), this._ui.assistantThicknessInput);
            drawingAssistantSection.appendChild(assistantParamsRow);

            // Geometric Shapes
            const assistantShapesRow = domMake.Row();
            assistantShapesRow.style.gap = "5px";

            this._ui.drawLineButton = domMake.Button('<i class="fas fa-grip-lines"></i> Línea');
            this._ui.drawLineButton.addEventListener("click", () => this._enableShapeDrawing('line'));
            assistantShapesRow.appendChild(this._ui.drawLineButton);

            this._ui.drawRectButton = domMake.Button('<i class="fas fa-vector-square"></i> Rect.');
            this._ui.drawRectButton.addEventListener("click", () => this._enableShapeDrawing('rect'));
            assistantShapesRow.appendChild(this._ui.drawRectButton);

            this._ui.drawCircleButton = domMake.Button('<i class="fas fa-circle"></i> Círculo');
            this._ui.drawCircleButton.addEventListener("click", () => this._enableShapeDrawing('circle'));
            assistantShapesRow.appendChild(this._ui.drawCircleButton);
            drawingAssistantSection.appendChild(assistantShapesRow);

            // Toggles (Grid, Symmetry, Pixel Perfect)
            const assistantTogglesRow = domMake.Row();
            assistantTogglesRow.style.gap = "5px";

            this._ui.toggleGridButton = domMake.Button('<i class="fas fa-th"></i> Cuadrícula');
            this._ui.toggleGridButton.id = "drawing-assistant-grid-toggle";
            this._ui.toggleGridButton.addEventListener("click", () => this._toggleGrid(this._ui.toggleGridButton));
            assistantTogglesRow.appendChild(this._ui.toggleGridButton);

            this._ui.toggleSymmetryButton = domMake.Button('<i class="fas fa-arrows-alt-h"></i> Simetría');
            this._ui.toggleSymmetryButton.id = "drawing-assistant-symmetry-toggle";
            this._ui.toggleSymmetryButton.addEventListener("click", () => this._toggleSymmetry(this._ui.toggleSymmetryButton));
            assistantTogglesRow.appendChild(this._ui.toggleSymmetryButton);

            this._ui.togglePixelPerfectButton = domMake.Button('<i class="fas fa-compress-arrows-alt"></i> Píxel Perfect');
            this._ui.togglePixelPerfectButton.id = "drawing-assistant-pixel-perfect-toggle";
            this._ui.togglePixelPerfectButton.addEventListener("click", () => this._togglePixelPerfect(this._ui.togglePixelPerfectButton));
            assistantTogglesRow.appendChild(this._ui.togglePixelPerfectButton);
            drawingAssistantSection.appendChild(assistantTogglesRow);

            // Collaborative Drawing Trigger
            const assistantCollabRow = domMake.Row();
            this._ui.startCollabDrawButton = domMake.Button('<i class="fas fa-users-cog"></i> Iniciar Dibujo Colaborativo');
            this._ui.startCollabDrawButton.title = "Activa el módulo Autodraw V2 para que los bots colaboren en el dibujo (requiere imagen cargada en Autodraw V2).";
            this._ui.startCollabDrawButton.addEventListener("click", () => this._triggerAutodrawV2Collab());
            assistantCollabRow.appendChild(this._ui.startCollabDrawButton);
            drawingAssistantSection.appendChild(assistantCollabRow);

            container.appendChild(drawingAssistantSection);

            // --- NEW: Transparent Brush Section ---
            const transparentBrushSection = domMake.Tree("div", { class: "intelligent-artist-section" });
            transparentBrushSection.appendChild(domMake.Tree("div", { class: "intelligent-artist-section-title" }, ["Pincel Transparente"]));

            this._ui.transparentBrushToggleButton = domMake.Button('Pincel: OFF');
            this._ui.transparentBrushToggleButton.classList.add('transparent-brush-toggle-button');
            this._ui.transparentBrushToggleButton.addEventListener('click', this._toggleTransparentBrush.bind(this));
            transparentBrushSection.appendChild(this._ui.transparentBrushToggleButton);

            const opacityControlRow = domMake.Tree('div', { style: 'display:flex; align-items:center; justify-content:center; gap:8px; font-size:0.9em; color:var(--CE-color);' });
            const opacityLabel = domMake.Tree('label', {}, ['Opacidad:']);
            this._ui.transparentBrushSlider = domMake.Tree('input', {
                type: 'range', min: '0.05', max: '1', step: '0.01', value: '0.3',
                class: 'transparent-brush-opacity-slider'
            });
            this._ui.transparentBrushSlider.addEventListener('input', this._updateTransparentBrushAlpha.bind(this));
            this._ui.transparentBrushPercent = domMake.Tree('span', { class: 'transparent-brush-percentage' }, ['30%']);

            opacityControlRow.appendAll(opacityLabel, this._ui.transparentBrushSlider, this._ui.transparentBrushPercent);
            transparentBrushSection.appendChild(opacityControlRow);

            container.appendChild(transparentBrushSection);
            // --- END NEW: Transparent Brush Section ---

            this.htmlElements.section.appendChild(container);

            // Set initial percentage display for transparent brush slider
            this._ui.transparentBrushPercent.textContent = Math.round(parseFloat(this._ui.transparentBrushSlider.value) * 100) + "%";
        }

        // --- Module Activation/Deactivation Logic ---
        _toggleModuleActive = () => { // Defined as arrow function
            this._isActive = !this._isActive;
            if (this._isActive) {
                this._ui.moduleToggleButton.innerHTML = '<i class="fas fa-power-off"></i> Desactivar Artisan Studio';
                this._ui.moduleToggleButton.classList.add('active');
                //is.notify("info", "Módulo 'Artisan Studio' ACTIVADO.");
                this._setUIEnabled(true);
                this._hookGameDrawingEvents(); // Attach listeners for drawing assistant
            } else {
                this._ui.moduleToggleButton.innerHTML = '<i class="fas fa-power-off"></i> Activar Artisan Studio';
                this._ui.moduleToggleButton.classList.remove('active');
                //is.notify("info", "Módulo 'Artisan Studio' DESACTIVADO.");
                this._setUIEnabled(false);
                this._unhookGameDrawingEvents(); // Detach listeners
            }
        };

        _setUIEnabled = (enabled) => { // Defined as arrow function
            // Collect all interactive elements that need to be enabled/disabled
            const interactiveElements = [
                this._sketchTextInput,
                this._ui.generateSketchButton,
                this._ui.clearCanvasButton,
                this._ui.sketchColorInput,
                this._ui.brushSizeInput,
                this._ui.assistantColorInput,
                this._ui.assistantThicknessInput,
                this._ui.drawLineButton,
                this._ui.drawRectButton,
                this._ui.drawCircleButton,
                this._ui.toggleGridButton,
                this._ui.toggleSymmetryButton,
                this._ui.togglePixelPerfectButton,
                this._ui.startCollabDrawButton,
                // Transparent brush controls
                this._ui.transparentBrushToggleButton,
                this._ui.transparentBrushSlider
            ];

            interactiveElements.forEach(el => {
                if (el) {
                    el.disabled = !enabled;
                }
            });

            // Handle the percentage span text color to indicate disabled state
            if (this._ui.transparentBrushPercent) {
                this._ui.transparentBrushPercent.style.color = enabled ? 'var(--CE-color)' : 'var(--dark-gray)';
            }


            // Also handle the dynamic sketch list buttons
            if (this._sketchListContainer) {
                Array.from(this._sketchListContainer.children).forEach(button => {
                    if (button.tagName === 'BUTTON') { // Ensure it's a button
                        button.disabled = !enabled;
                    }
                });
            }

            // When disabling, ensure any active drawing modes (shape drawing, local freehand) are stopped
            if (!enabled) {
                this._isDrawingLocal = false; // Stop local freehand drawing
                this._clearOverlay(); // Clear any overlay artifacts

                // Cancel any pending shape drawing operations by removing its temporary listeners
                if (this._gameCanvas) {
                    if (this._onClickShapeHandler) {
                        this._gameCanvas.removeEventListener('click', this._onClickShapeHandler);
                        this._onClickShapeHandler = null; // Clear reference
                    }
                    if (this._onMoveShapeHandler) {
                        this._gameCanvas.removeEventListener('mousemove', this._onMoveShapeHandler);
                        this._onMoveShapeHandler = null; // Clear reference
                    }
                }
            }
        };


        // Helper to send draw commands (new, for both local and remote drawing)
        #sendDrawCommand(x1_norm, y1_norm, x2_norm, y2_norm, thickness, color) {
            if (!this._isActive) {
               //his.notify("warning", "Módulo 'Artisan Studio' inactivo. No se enviará comando de dibujo.");
                return;
            }

            const gameSocket = getGameSocket(); // Use global helper function
            if (!gameSocket) {
                this.notify("warning", "No hay conexión WebSocket activa. No se pueden enviar comandos de dibujo.");
                return;
            }

            // --- APPLY TRANSPARENCY (NEW) ---
            let finalColor = color;
            if (this._isTransparentBrushActive) {
                finalColor = this._convertToRgba(color, this._customBrushAlpha);
            }
            // --- END APPLY TRANSPARENCY ---

            // Send command to server
            const command = `42["drawcmd",0,[${x1_norm},${y1_norm},${x2_norm},${y2_norm},false,${0 - thickness},"${finalColor}",0,0,{}]]`;
            gameSocket.send(command);

            // Local rendering on the game canvas
            if (this._gameCtx && this._gameCanvas) {
                const localX1 = x1_norm * this._gameCanvas.width;
                const localY1 = y1_norm * this._gameCanvas.height;
                const localX2 = x2_norm * this._gameCanvas.width;
                const localY2 = y2_norm * this._gameCanvas.height;

                this._gameCtx.strokeStyle = finalColor; // Use finalColor
                this._gameCtx.lineWidth = thickness;
                this._gameCtx.lineCap = 'round';
                this._gameCtx.lineJoin = 'round';
                this._gameCtx.beginPath();
                this._gameCtx.moveTo(localX1, localY1);
                this._gameCtx.lineTo(localX2, localY2);
                this._gameCtx.stroke();
            }
        }

        // --- Helper to convert color string to RGBA with specific alpha (NEW) ---
        _convertToRgba(colorString, alpha) {
            // If already rgba and we're just changing alpha
            if (colorString.startsWith('rgba')) {
                const parts = colorString.match(/rgba\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d*\.?\d+))?\)/);
                if (parts) {
                    return `rgba(${parts[1]},${parts[2]},${parts[3]},${alpha})`;
                }
            }
            // If rgb
            else if (colorString.startsWith('rgb')) {
                const parts = colorString.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
                if (parts) {
                    return `rgba(${parts[1]},${parts[2]},${parts[3]},${alpha})`;
                }
            }
            // If hex
            else if (colorString.startsWith('#')) {
                let hex = colorString.slice(1);
                if (hex.length === 3) {
                    hex = hex.split('').map(char => char + char).join('');
                }
                const r = parseInt(hex.substring(0, 2), 16);
                const g = parseInt(hex.substring(2, 4), 16);
                const b = parseInt(hex.substring(4, 6), 16);
                return `rgba(${r},${g},${b},${alpha})`;
            }
            // Fallback for unexpected formats (e.g., named colors, invalid hex/rgb)
            console.warn("ArtisanStudio: Unexpected color format for transparency conversion:", colorString);
            return `rgba(0,0,0,${alpha})`; // Default to black with specified alpha
        }


        // --- Intelligent Artist Methods (now part of ArtisanStudio) ---
        async _loadSketchesFromGithub() {
            const githubUrl = "https://raw.githubusercontent.com/NuevoMundoOficial/SKETCH_DATABASE/main/sketches.json";
           //his.notify("info", "Cargando base de datos de bocetos desde GitHub...");
            try {
                const response = await fetch(githubUrl);
                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }
                this._SKETCH_DATABASE = await response.json();
                //this.notify("success", "Base de datos de bocetos cargada exitosamente.");
                this._populateSketchList(); // Now populate the list
            } catch (error) {
             // this.notify("error", `Error al cargar la base de datos de bocetos: ${error.message}`);
                console.error("Error loading SKETCH_DATABASE:", error);
            }
        }

        _populateSketchList() {
            if (!this._sketchListContainer || !this._sketchListCountLabel) return;

            this._sketchListContainer.innerHTML = ''; // Clear previous content
            const words = Object.keys(this._SKETCH_DATABASE);

            // Update the label with the count using the stored reference
            this._sketchListCountLabel.textContent = `Bocetos Rápidos (${words.length}):`;

            words.forEach(word => {
                const sketchButton = domMake.Button(word);
                sketchButton.title = `Generar boceto para: ${word}`;
                sketchButton.style.flex = '0 0 auto';
                sketchButton.style.margin = '0'; // Use 0 margin as gap handles spacing
                sketchButton.style.padding = '2px 5px';
                sketchButton.style.fontSize = '0.7em';

                sketchButton.addEventListener("click", () => {
                    this._simulateAISketch(word);
                });
                this._sketchListContainer.appendChild(sketchButton);
            });

            // Ensure buttons are disabled/enabled based on module's active state
            this._setUIEnabled(this._isActive);
        }

        _simulateAISketch(concept) {
            if (!this._isActive) {
            //  this.notify("warning", "Módulo 'Artisan Studio' inactivo. Actívalo para generar bocetos.");
                return;
            }

            const gameSocket = getGameSocket(); // Use global helper function
            if (!gameSocket) {
            //  this.notify("warning", "No hay conexión WebSocket activa para dibujar el boceto.");
                return;
            }

            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).`);
          //this.notify("info", "Dibujando el boceto conceptual...");

            sketchData.forEach(line => {
                const x1_norm = line.x1 / 100;
                const y1_norm = line.y1 / 100;
                const x2_norm = line.x2 / 100;
                const y2_norm = line.y2 / 100;

                if (line.type === "circle") {
                    // Simulate a circle with multiple small lines
                    const centerX_norm = line.x1 / 100;
                    const centerY_norm = line.y1 / 100; // Corrected from centerY_game
                    const radius_norm = line.radius / 100;
                    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 cx1_norm = centerX_norm + radius_norm * Math.cos(angle1);
                        const cy1_norm = centerY_norm + radius_norm * Math.sin(angle1);
                        const cx2_norm = centerX_norm + radius_norm * Math.cos(angle2);
                        const cy2_norm = centerY_norm + radius_norm * Math.sin(angle2);

                        this.#sendDrawCommand(cx1_norm, cy1_norm, cx2_norm, cy2_norm, this._currentBrushSize, this._currentSketchColor);
                    }
                } else {
                    this.#sendDrawCommand(x1_norm, y1_norm, x2_norm, y2_norm, this._currentBrushSize, this._currentSketchColor);
                }
            });
          //this.notify("success", `Boceto de "${concept}" dibujado. ¡Ahora puedes calcarlo o mejorarlo!`);
        }

        // --- Drawing Assistant Methods (now part of ArtisanStudio) ---
        _findGameCanvas() {
            this._gameCanvas = document.getElementById('canvas');
            if (this._gameCanvas) {
                this._gameCtx = this._gameCanvas.getContext('2d');
            } else {
                this.notify("error", "Canvas del juego no encontrado. Algunas funciones de dibujo no estarán disponibles.");
            }
        }

        _setupOverlayCanvas() {
            this._overlayCanvas = domMake.Tree('canvas', { id: 'drawing-assistant-overlay' });
            if (this._gameCanvas) {
                this._overlayCanvas.width = this._gameCanvas.width;
                this._overlayCanvas.height = this._gameCanvas.height;
                this._overlayCanvas.style.width = this._gameCanvas.style.width; // Match CSS size
                this._overlayCanvas.style.height = this._gameCanvas.style.height;
                this._gameCanvas.parentNode.insertBefore(this._overlayCanvas, this._gameCanvas.nextSibling); // Place right after game canvas
            } else {
                this._overlayCanvas.width = 1000;
                this._overlayCanvas.height = 1000;
                this._overlayCanvas.style.width = '700px';
                this._overlayCanvas.style.height = '700px';
                document.body.appendChild(this._overlayCanvas);
            }
            this._overlayCtx = this._overlayCanvas.getContext('2d');
            this._updateOverlaySizeAndPosition();

            window.addEventListener('resize', this._updateOverlaySizeAndPosition.bind(this));
        }

        _updateOverlaySizeAndPosition() {
            if (!this._gameCanvas || !this._overlayCanvas) return;

            const gameCanvasRect = this._gameCanvas.getBoundingClientRect();

            this._overlayCanvas.style.top = `${gameCanvasRect.top}px`;
            this._overlayCanvas.style.left = `${gameCanvasRect.left}px`;
            this._overlayCanvas.style.width = `${gameCanvasRect.width}px`;
            this._overlayCanvas.style.height = `${gameCanvasRect.height}px`;

            if (this._overlayCanvas.width !== this._gameCanvas.width) {
                 this._overlayCanvas.width = this._gameCanvas.width;
            }
            if (this._overlayCanvas.height !== this._gameCanvas.height) {
                 this._overlayCanvas.height = this._gameCanvas.height;
            }

            this._clearOverlay();
            if (this._isGridVisible) {
                this._drawGrid();
            }
            if (this._isSymmetryActive) {
                this._drawSymmetryLines();
            }
        }

        _clearOverlay() {
            this._overlayCtx.clearRect(0, 0, this._overlayCtx.canvas.width, this._overlayCtx.canvas.height);
        }

        _hookGameDrawingEvents() {
            if (!this._gameCanvas) return;
            this._gameCanvas.addEventListener('mousedown', this._boundMouseDownHandler);
            this._gameCanvas.addEventListener('mousemove', this._boundMouseMoveHandler);
            this._gameCanvas.addEventListener('mouseup', this._boundMouseUpHandler);
            this._gameCanvas.addEventListener('mouseout', this._boundMouseUpHandler);
        }

        _unhookGameDrawingEvents() {
            if (!this._gameCanvas) return;
            this._gameCanvas.removeEventListener('mousedown', this._boundMouseDownHandler);
            this._gameCanvas.removeEventListener('mousemove', this._boundMouseMoveHandler);
            this._gameCanvas.removeEventListener('mouseup', this._boundMouseUpHandler);
            this._gameCanvas.removeEventListener('mouseout', this._boundMouseUpHandler);

            // Also ensure any specific shape drawing listeners are removed if active
            if (this._onClickShapeHandler) {
                this._gameCanvas.removeEventListener('click', this._onClickShapeHandler);
                this._onClickShapeHandler = null;
            }
            if (this._onMoveShapeHandler) {
                this._gameCanvas.removeEventListener('mousemove', this._onMoveShapeHandler);
                this._onMoveShapeHandler = null;
            }
            this._clearOverlay(); // Clear any visual overlays from drawing assistant
        }

        _handleMouseDown(e) {
            if (!this._isActive) return;
            this._isDrawingLocal = true;
            const rect = this._gameCanvas.getBoundingClientRect();
            this._lastX = e.clientX - rect.left;
            this._lastY = e.clientY - rect.top;
            this._overlayCtx.beginPath();
            this._overlayCtx.moveTo(this._lastX, this._lastY);
        }

        _handleMouseMove(e) {
            if (!this._isActive || !this._isDrawingLocal) return;
            const rect = this._gameCanvas.getBoundingClientRect();
            const currentX = e.clientX - rect.left;
            const currentY = e.clientY - rect.top;

            this._sendLineCommandFromPixels(this._lastX, this._lastY, currentX, currentY);

            this._overlayCtx.lineTo(currentX, currentY);
            this._overlayCtx.strokeStyle = this._drawingColor;
            this._overlayCtx.lineWidth = this._drawingThickness;
            this._overlayCtx.lineCap = 'round';
            this._overlayCtx.lineJoin = 'round';
            this._overlayCtx.stroke();
            this._overlayCtx.beginPath();
            this._overlayCtx.moveTo(currentX, currentY);

            this._lastX = currentX;
            this._lastY = currentY;
        }

        _handleMouseUp() {
            if (!this._isActive) return;
            this._isDrawingLocal = false;
            this._clearOverlay();
            if (this._isGridVisible) this._drawGrid();
            if (this._isSymmetryActive) this._drawSymmetryLines();
        }

        // Helper to convert pixel coords to game-normalized coords (0-1) and send
        _sendLineCommandFromPixels(startX_px, startY_px, endX_px, endY_px) {
            if (!this._isActive) return;

            const rect = this._gameCanvas.getBoundingClientRect();
            const scaleFactorX = 100 / rect.width;
            const scaleFactorY = 100 / rect.height;

            let gameX1_norm = (startX_px * scaleFactorX) / 100;
            let gameY1_norm = (startY_px * scaleFactorY) / 100;
            let gameX2_norm = (endX_px * scaleFactorX) / 100;
            let gameY2_norm = (endY_px * scaleFactorY) / 100;

            if (this._isPixelPerfectActive) {
                const snapResolution = 2 / 100; // 2 units in game coords, converted to 0-1 range
                gameX1_norm = Math.round(gameX1_norm / snapResolution) * snapResolution;
                gameY1_norm = Math.round(gameY1_norm / snapResolution) * snapResolution;
                gameX2_norm = Math.round(gameX2_norm / snapResolution) * snapResolution;
                gameY2_norm = Math.round(gameY2_norm / snapResolution) * snapResolution;
            }

            // Ensure coordinates are within 0-1 range for server
            gameX1_norm = Math.max(0, Math.min(1, gameX1_norm));
            gameY1_norm = Math.max(0, Math.min(1, gameY1_norm));
            gameX2_norm = Math.max(0, Math.min(1, gameX2_norm));
            gameY2_norm = Math.max(0, Math.min(1, gameY2_norm));

            this.#sendDrawCommand(gameX1_norm, gameY1_norm, gameX2_norm, gameY2_norm, this._drawingThickness, this._drawingColor);

            if (this._isSymmetryActive) {
                const midX_norm = 0.5; // Midpoint of 0-1 range
                const mirroredGameX1_norm = midX_norm + (midX_norm - gameX1_norm);
                const mirroredGameX2_norm = midX_norm + (midX_norm - gameX2_norm);
                this.#sendDrawCommand(mirroredGameX1_norm, gameY1_norm, mirroredGameX2_norm, gameY2_norm, this._drawingThickness, this._drawingColor);
            }
        }

        _enableShapeDrawing(shapeType) {
            if (!this._isActive) {
                this.notify("warning", "Módulo 'Artisan Studio' inactivo. Actívalo para usar herramientas de forma.");
                return;
            }

            this.notify("info", `Modo '${shapeType}' activado. Haz clic en el lienzo para definir la forma.`);
            const gameSocket = getGameSocket(); // Use global helper function
            if (!gameSocket) {
                this.notify("warning", "No hay conexión WebSocket activa para dibujar la forma.");
                return;
            }

            let startCoords = null; // Pixel coordinates on the visible game canvas

            // Remove previous temporary shape drawing listeners if any are active
            if (this._onClickShapeHandler) {
                this._gameCanvas.removeEventListener('click', this._onClickShapeHandler);
                this._onClickShapeHandler = null;
            }
            if (this._onMoveShapeHandler) {
                this._gameCanvas.removeEventListener('mousemove', this._onMoveShapeHandler);
                this._onMoveShapeHandler = null;
            }


            const drawShapePreview = (x1_px, y1_px, x2_px, y2_px) => {
                this._clearOverlay();
                if (this._isGridVisible) this._drawGrid();
                if (this._isSymmetryActive) this._drawSymmetryLines();

                const ctx = this._overlayCtx;
                ctx.strokeStyle = this._drawingColor;
                ctx.lineWidth = 1;
                ctx.setLineDash([5, 5]);
                ctx.beginPath();

                const width_px = x2_px - x1_px;
                const height_px = y2_px - y1_px;

                if (shapeType === 'line') {
                    ctx.moveTo(x1_px, y1_px);
                    ctx.lineTo(x2_px, y2_px);
                } else if (shapeType === 'rect') {
                    ctx.rect(x1_px, y1_px, width_px, height_px);
                } else if (shapeType === 'circle') {
                    const dx_px = x2_px - x1_px;
                    const dy_px = y2_px - y1_px;
                    const radius_px = Math.sqrt(dx_px * dx_px + dy_px * dy_px);
                    ctx.arc(x1_px, y1_px, radius_px, 0, 2 * Math.PI);
                }
                ctx.stroke();
                ctx.setLineDash([]);
            };

            this._onClickShapeHandler = (e) => { // Store as class property
                if (!this._isActive) return;

                const rect = this._gameCanvas.getBoundingClientRect();
                const currentX_px = e.clientX - rect.left;
                const currentY_px = e.clientY - rect.top;

                if (!startCoords) {
                    startCoords = { x: currentX_px, y: currentY_px };
                    this.notify("info", "Haz clic de nuevo para definir el final de la forma.");

                    this._onMoveShapeHandler = (moveEvent) => { // Store as class property
                        if (!this._isActive) return;
                        const moveRect = this._gameCanvas.getBoundingClientRect();
                        const moveX_px = moveEvent.clientX - moveRect.left;
                        const moveY_px = moveEvent.clientY - moveRect.top;
                        drawShapePreview(startCoords.x, startCoords.y, moveX_px, moveY_px);
                    };
                    this._gameCanvas.addEventListener('mousemove', this._onMoveShapeHandler);

                } else {
                    const gameSocket = getGameSocket(); // Use global helper function
                    if (!gameSocket) {
                        this.notify("warning", "No hay conexión WebSocket activa, no se puede dibujar la forma.");
                        this._gameCanvas.removeEventListener('click', this._onClickShapeHandler);
                        this._gameCanvas.removeEventListener('mousemove', this._onMoveShapeHandler);
                        this._onClickShapeHandler = null; this._onMoveShapeHandler = null; // Clear references
                        this._clearOverlay();
                        return;
                    }

                    const rect = this._gameCanvas.getBoundingClientRect();
                    const scaleFactorX = 100 / rect.width;
                    const scaleFactorY = 100 / rect.height;

                    const x1_game = startCoords.x * scaleFactorX;
                    const y1_game = startCoords.y * scaleFactorY;
                    const x2_game = currentX_px * scaleFactorX;
                    const y2_game = currentY_px * scaleFactorY;

                    const thickness = this._drawingThickness;
                    const color = this._drawingColor;

                    // Convert to 0-1 normalized range for sending
                    const x1_norm = x1_game / 100;
                    const y1_norm = y1_game / 100;
                    const x2_norm = x2_game / 100;
                    const y2_norm = y2_game / 100;


                    if (shapeType === 'line') {
                        this.#sendDrawCommand(x1_norm, y1_norm, x2_norm, y2_norm, thickness, color);
                    } else if (shapeType === 'rect') {
                        this.#sendDrawCommand(x1_norm, y1_norm, x2_norm, y1_norm, thickness, color);
                        this.#sendDrawCommand(x2_norm, y1_norm, x2_norm, y2_norm, thickness, color);
                        this.#sendDrawCommand(x2_norm, y2_norm, x1_norm, y2_norm, thickness, color);
                        this.#sendDrawCommand(x1_norm, y2_norm, x1_norm, y1_norm, thickness, color);
                    } else if (shapeType === 'circle') {
                        const centerX_game = x1_game;
                        const centerY_game = y1_game;
                        const dx_game = x2_game - x1_game;
                        const dy_game = y2_game - y1_game;
                        const radius_game = Math.sqrt(dx_game * dx_game + dy_game * dy_game);

                        const segments = 48;
                        for (let i = 0; i < segments; i++) {
                            const angle1 = (i / segments) * Math.PI * 2;
                            const angle2 = ((i + 1) / segments) * Math.PI * 2;

                            const cx1_game = centerX_game + radius_game * Math.cos(angle1);
                            const cy1_game = centerY_game + radius_game * Math.sin(angle1);
                            const cx2_game = centerX_game + radius_game * Math.cos(angle2);
                            const cy2_game = centerY_game + radius_game * Math.sin(angle2);

                            this.#sendDrawCommand(cx1_game / 100, cy1_game / 100, cx2_game / 100, cy2_game / 100, thickness, color);
                        }
                    }

                    this._gameCanvas.removeEventListener('click', this._onClickShapeHandler);
                    this._gameCanvas.removeEventListener('mousemove', this._onMoveShapeHandler);
                    this._onClickShapeHandler = null; this._onMoveShapeHandler = null; // Clear references
                    this._clearOverlay();
                    this.notify("success", `${shapeType} dibujado.`);
                    startCoords = null;
                }
            };
            this._gameCanvas.addEventListener('click', this._onClickShapeHandler); // Attach here
        }

        _toggleGrid(button) {
            if (!this._isActive) {
                this.notify("warning", "Módulo 'Artisan Studio' inactivo. Actívalo para usar la cuadrícula.");
                return;
            }
            this._isGridVisible = !this._isGridVisible;
            button.classList.toggle("active", this._isGridVisible);
            this._clearOverlay();
            if (this._isGridVisible) {
                this._drawGrid();
                this.notify("info", "Cuadrícula visible.");
            } else {
                this.notify("info", "Cuadrícula oculta.");
            }
            if (this._isSymmetryActive) this._drawSymmetryLines();
        }

        _drawGrid() {
            if (!this._gameCanvas) return;
            const ctx = this._overlayCtx;
            const rect = this._gameCanvas.getBoundingClientRect();
            const cellSize = 50;

            ctx.strokeStyle = "rgba(100, 100, 100, 0.5)";
            ctx.lineWidth = 1;
            ctx.setLineDash([2, 2]);

            for (let x = 0; x <= rect.width; x += cellSize) {
                ctx.beginPath();
                ctx.moveTo(x, 0);
                ctx.lineTo(x, rect.height);
                ctx.stroke();
            }

            for (let y = 0; y <= rect.height; y += cellSize) {
                ctx.beginPath();
                ctx.moveTo(0, y);
                ctx.lineTo(rect.width, y);
                ctx.stroke();
            }
            ctx.setLineDash([]);
        }

        _toggleSymmetry(button) {
            if (!this._isActive) {
                this.notify("warning", "Módulo 'Artisan Studio' inactivo. Actívalo para usar la simetría.");
                return;
            }
            this._isSymmetryActive = !this._isSymmetryActive;
            button.classList.toggle("active", this._isSymmetryActive);
            this._clearOverlay();
            if (this._isGridVisible) this._drawGrid();
            if (this._isSymmetryActive) {
                this._drawSymmetryLines();
                this.notify("info", "Modo Simetría Activo.");
            } else {
                this.notify("info", "Modo Simetría Inactivo.");
            }
        }

        _drawSymmetryLines() {
            if (!this._gameCanvas) return;
            const ctx = this._overlayCtx;
            const rect = this._gameCanvas.getBoundingClientRect();

            ctx.strokeStyle = "rgba(255, 0, 0, 0.7)";
            ctx.lineWidth = 2;
            ctx.setLineDash([5, 5]);
            ctx.beginPath();
            ctx.moveTo(rect.width / 2, 0);
            ctx.lineTo(rect.width / 2, rect.height);
            ctx.stroke();
            ctx.setLineDash([]);
        }

        _togglePixelPerfect(button) {
            if (!this._isActive) {
         //     this.notify("warning", "Módulo 'Artisan Studio' inactivo. Actívalo para usar Píxel Perfect.");
                return;
            }
            this._isPixelPerfectActive = !this._isPixelPerfectActive;
            button.classList.toggle("active", this._isPixelPerfectActive);
      //    this.notify("info", `Modo 'Píxel Perfect' ${this._isPixelPerfectActive ? 'Activado' : 'Desactivado'}.`);
        }

        _triggerAutodrawV2Collab() {
            if (!this._isActive) {
     //         this.notify("warning", "Módulo 'Artisan Studio' inactivo. Actívalo para iniciar dibujo colaborativo.");
                return;
            }

            const autodrawV2Class = this.findGlobal("AutodrawV2");
            if (!autodrawV2Class || !autodrawV2Class.siblings || autodrawV2Class.siblings.length === 0) {
        //       this.notify("warning", "El módulo 'Autodraw V2' no está activo o no se encontró. No se puede iniciar el dibujo colaborativo.");
                 return;
            }
            const autodrawV2Instance = autodrawV2Class.siblings[0];

            if (autodrawV2Instance && typeof autodrawV2Instance.startDrawing === 'function') {
                autodrawV2Instance.startDrawing();
      //        this.notify("info", "Iniciando dibujo colaborativo a través del módulo Autodraw V2.");
            } else {
      //        this.notify("warning", "La instancia del módulo 'Autodraw V2' no está lista. Asegúrate de que Autodraw V2 se inicializó correctamente.");
            }
        }

        // --- Transparent Brush Methods (NEW) ---
        _toggleTransparentBrush() {
            if (!this._isActive) {
         //     this.notify("warning", "Módulo 'Artisan Studio' inactivo. Actívalo para usar el Pincel Transparente.");
                // Ensure the button state doesn't change visually if module is inactive
                this._ui.transparentBrushToggleButton.classList.remove('active');
                this._ui.transparentBrushToggleButton.textContent = 'Pincel: OFF';
                this._ui.transparentBrushToggleButton.style.background = '#492';
                this._isTransparentBrushActive = false; // Force state to false
                return;
            }

            this._isTransparentBrushActive = !this._isTransparentBrushActive;
            this._ui.transparentBrushToggleButton.textContent = this._isTransparentBrushActive ? 'Pincel: ON' : 'Pincel: OFF';
            this._ui.transparentBrushToggleButton.style.background = this._isTransparentBrushActive ? '#2a5' : '#492';
   //       this.notify("info", `Pincel Transparente: ${this._isTransparentBrushActive ? 'ON' : 'OFF'}`);
        }

        _updateTransparentBrushAlpha() {
            if (!this._isActive) return;

            this._customBrushAlpha = parseFloat(this._ui.transparentBrushSlider.value);
            this._ui.transparentBrushPercent.textContent = Math.round(this._customBrushAlpha * 100) + "%";
        //  this.notify("debug", `Opacidad del Pincel Transparente ajustada a: ${this._customBrushAlpha}`);
        }
    }
})("QBit");
// START Game Ultra Log

(function GameUltraLogModule() {
    const QBit = globalThis[arguments[0]];

    // Combined Styles from both modules
    QBit.Styles.addRules([
        // Styles for CanvasImageInserter (Dibujo Ultra Rapido)
        `#game-ultra-log-container .drawing-settings {
            display: grid;
            grid-template-columns: 1fr 1fr;
            gap: 5px;
            margin-top: 10px;
            padding: 5px;
            border: 1px dashed var(--CE-color);
            border-radius: .25rem;
        }`,
        `#game-ultra-log-container .drawing-settings > div {
            display: flex;
            flex-direction: column;
        }`,
        `#game-ultra-log-container .drawing-settings label {
            font-size: 0.8em;
            margin-bottom: 2px;
            color: var(--CE-color);
        }`,
        `#game-ultra-log-container .drawing-settings input[type="number"] {
            width: 100%;
            padding: 5px;
            box-sizing: border-box;
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            background-color: var(--CE-bg_color);
            color: var(--CE-color);
        }`,
        `#game-ultra-log-container .action-buttons {
            display: flex;
            gap: 5px;
            margin-top: 10px;
        }`,
        `#game-ultra-log-container .action-buttons button {
            flex: 1;
        }`,
        `#game-ultra-log-container .loading-spinner {
            border: 4px solid rgba(0, 0, 0, 0.1);
            border-left-color: var(--info);
            border-radius: 50%;
            width: 20px;
            height: 20px;
            animation: spin 1s linear infinite;
            display: inline-block;
            vertical-align: middle;
            margin-left: 5px;
        }`,
        `@keyframes spin {
            0% { transform: rotate(0deg); }
            100% { transform: rotate(360deg); }
        }`,

        // Styles for GameLog (Registro del Juego)
        `#game-ultra-log-container .log-section {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 10px;
            background-color: var(--CE-bg_color);
        }`,
        `#game-ultra-log-container .log-section-title {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--dark-blue-title);
            text-align: center;
        }`,
        `#game-ultra-log-container .log-display-area {
            max-height: 250px;
            overflow-y: auto;
            border: 1px solid var(--CE-color);
            padding: 5px;
            font-size: 0.75em;
            background-color: var(--CE-bg_color);
            color: var(--CE-color);
            margin-top: 5px;
            margin-bottom: 5px;
        }`,
        `#game-ultra-log-container .log-control-buttons {
            display: flex;
            gap: 5px;
            margin-top: 5px;
        }`,
        `#game-ultra-log-container .log-control-buttons button {
            flex: 1;
            padding: 5px;
        }`,
        `#game-ultra-log-container .log-control-buttons button.active {
            background-color: var(--info);
            color: white;
        }`
    ]);

    // Helper to convert RGBA array to RGBA string for CSS/Canvas
    function _rgbaArrayToString(rgbaArray) {
        return `rgba(${rgbaArray[0]},${rgbaArray[1]},${rgbaArray[2]},${rgbaArray[3] / 255})`;
    }

    // Helper to check if two RGBA colors are "similar enough"
    function _areColorsSimilar(color1, color2, threshold = 15) {
        if (!color1 || !color2) return false;
        return (
            Math.abs(color1[0] - color2[0]) <= threshold &&
            Math.abs(color1[1] - color2[1]) <= threshold &&
            Math.abs(color1[2] - color2[2]) <= threshold &&
            Math.abs(color1[3] - color2[3]) <= threshold
        );
    }

    class GameUltraLog extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        // --- Properties for Dibujo Ultra Rápido (CanvasImageInserter) ---
        _fileInput;
        _drawingStatusLabel;
        _insertButton;
        _stopDrawingButton;
        _currentDrawingIndex = 0;
        _drawingActive = false;

        _processingCanvas;
        _processingCtx;
        _imageData = null;
        _gameCanvas; // Reference to the game's main canvas
        _gameCtx;    // Context of the game's main canvas

        _brushSizeInput;
        _drawingSpeedInput;
        _offsetXInput;
        _offsetYInput;
        _pixelDensityInput;
        _colorToleranceInput;

        _drawingCommands = [];

        // --- Properties for Registro del Juego (GameLog) ---
        _gameLog = [];
        _logDisplayElement;
        _isLoggingActive = true;

        constructor() {
            super("Game Ultra Log", '<i class="fas fa-fire"></i>'); // Keep original log icon
            this._onStartup();
        }

        _onStartup() {
            this._loadInterface();
            this._setupProcessingCanvas(); // For image drawing
            this._findGameCanvas(); // Locate game canvas and its context
            this._setupLogHooks(); // For game logging
            this.notify("info", "Módulo 'Game Ultra Log' cargado.");
        }

        _setupProcessingCanvas() {
            this._processingCanvas = document.createElement('canvas');
            this._processingCtx = this._processingCanvas.getContext('2d', { willReadFrequently: true });
        }

        _findGameCanvas() {
            this._gameCanvas = document.getElementById('canvas');
            if (this._gameCanvas) {
                this._gameCtx = this._gameCanvas.getContext('2d');
            } else {
                this.notify("warning", "Canvas del juego no encontrado. Las funciones de dibujo en el lienzo del juego no estarán disponibles.");
            }
        }

        _loadInterface() {
            const container = domMake.Tree("div", { id: "game-ultra-log-container" });

            // --- Section: Dibujo Ultra Rápido ---
            const drawingSection = domMake.Tree("div", { class: "log-section" });
            drawingSection.appendChild(domMake.Tree("div", { class: "log-section-title" }, ["Dibujo Ultra Rápido"]));

            const fileInputId = "image-inserter-fileinput-" + (Math.random() * 1e8 | 0);
            this._fileInput = domMake.Tree("input", { type: "file", accept: "image/*", id: fileInputId, hidden: true });
            const uploadLabel = domMake.Tree("label", { for: fileInputId, class: "btn btn-outline-secondary" }, [
                domMake.Tree("i", { class: "fas fa-upload" }), " Seleccionar Imagen"
            ]);
            uploadLabel.title = "Selecciona una imagen PNG/JPG para procesar y dibujar en el canvas.";
            drawingSection.appendAll(uploadLabel, this._fileInput);

            const settingsGroup = domMake.Tree("div", { class: "drawing-settings" });

            const brushSizeDiv = domMake.Tree("div");
            brushSizeDiv.appendAll(domMake.Tree("label", {}, ["Tamaño Pincel (px):"]),
                this._brushSizeInput = domMake.Tree("input", { type: "number", value: "2", min: "1", max: "100", title: "Grosor de línea para dibujar cada píxel o segmento." })
            );
            settingsGroup.appendChild(brushSizeDiv);

            const pixelDensityDiv = domMake.Tree("div");
            pixelDensityDiv.appendAll(domMake.Tree("label", {}, ["Densidad Píxel (salto):"]),
                this._pixelDensityInput = domMake.Tree("input", { type: "number", value: "1", min: "1", max: "10", title: "Cada cuántos píxeles se tomará una muestra (mayor = más rápido, menos detalle)." })
            );
            settingsGroup.appendChild(pixelDensityDiv);

            const offsetXDiv = domMake.Tree("div");
            offsetXDiv.appendAll(domMake.Tree("label", {}, ["Offset X (%):"]),
                this._offsetXInput = domMake.Tree("input", { type: "number", value: "0", min: "-100", max: "100", title: "Desplazamiento horizontal del dibujo en el canvas (0-100%)." })
            );
            settingsGroup.appendChild(offsetXDiv);

            const offsetYDiv = domMake.Tree("div");
            offsetYDiv.appendAll(domMake.Tree("label", {}, ["Offset Y (%):"]),
                this._offsetYInput = domMake.Tree("input", { type: "number", value: "0", min: "-100", max: "100", title: "Desplazamiento vertical del dibujo en el canvas (0-100%)." })
            );
            settingsGroup.appendChild(offsetYDiv);

            const drawingSpeedDiv = domMake.Tree("div");
            drawingSpeedDiv.appendAll(domMake.Tree("label", {}, ["Vel. Dibujo (ms/línea):"]),
                this._drawingSpeedInput = domMake.Tree("input", { type: "number", value: "5", min: "1", max: "500", title: "Retraso en milisegundos entre cada comando de dibujo enviado." })
            );
            settingsGroup.appendChild(drawingSpeedDiv);

            const colorToleranceDiv = domMake.Tree("div");
            colorToleranceDiv.appendAll(domMake.Tree("label", {}, ["Tolerancia Color (0-255):"]),
                this._colorToleranceInput = domMake.Tree("input", { type: "number", value: "0", min: "0", max: "255", title: "Define cuán similares deben ser dos píxeles para agruparse en una misma línea. Menor valor = más detalle, más líneas." })
            );
            settingsGroup.appendChild(colorToleranceDiv);
            drawingSection.appendChild(settingsGroup);

            this._drawingStatusLabel = domMake.Tree("div", { style: "margin: 8px 0; min-height: 20px; color: var(--info);" }, ["Sin imagen cargada."]);
            drawingSection.appendChild(this._drawingStatusLabel);

            const drawingActionButtonsRow = domMake.Row({ class: "action-buttons" });
            this._insertButton = domMake.Button('<i class="fas fa-play"></i> Iniciar Dibujo');
            this._insertButton.disabled = true;
            this._insertButton.title = "Dibuja la imagen seleccionada en el canvas del juego.";
            drawingActionButtonsRow.appendChild(this._insertButton);

            this._stopDrawingButton = domMake.Button('<i class="fas fa-stop"></i> Detener Dibujo');
            this._stopDrawingButton.disabled = true;
            this._stopDrawingButton.title = "Detiene el proceso de dibujo actual.";
            drawingActionButtonsRow.appendChild(this._stopDrawingButton);
            drawingSection.appendChild(drawingActionButtonsRow);

            container.appendChild(drawingSection);

            // --- Section: Registro del Juego ---
            const logSection = domMake.Tree("div", { class: "log-section" });
            logSection.appendChild(domMake.Tree("div", { class: "log-section-title" }, ["Registro del Juego"]));

            const toggleLogButton = domMake.Button("Desactivar Registro");
            toggleLogButton.addEventListener("click", () => this._toggleLogging(toggleLogButton));
            logSection.appendChild(toggleLogButton);

            this._logDisplayElement = domMake.Tree("div", { class: "log-display-area" }, ["Registro de eventos vacío."]);
            logSection.appendChild(this._logDisplayElement);

            const logControlButtonsRow = domMake.Row({ class: "log-control-buttons" });
            const clearButton = domMake.Button("Limpiar Log");
            clearButton.addEventListener("click", () => this._clearLog());
            logControlButtonsRow.appendChild(clearButton);

            const exportTxtButton = domMake.Button("Exportar TXT");
            exportTxtButton.addEventListener("click", () => this._exportLog('txt'));
            logControlButtonsRow.appendChild(exportTxtButton);

            const exportJsonButton = domMake.Button("Exportar JSON");
            exportJsonButton.addEventListener("click", () => this._exportLog('json'));
            logControlButtonsRow.appendChild(exportJsonButton);
            logSection.appendChild(logControlButtonsRow);

            container.appendChild(logSection);
            this.htmlElements.section.appendChild(container);

            // Event Listeners for drawing functionality
            this._fileInput.addEventListener("change", (ev) => this._handleFileInput(ev));
            this._insertButton.addEventListener("click", () => this._startDrawing());
            this._stopDrawingButton.addEventListener("click", () => this._stopDrawing());
        }

        // Helper to get the currently active game WebSocket (botless)
        _getGameSocket() {
            if (globalThis.sockets && globalThis.sockets.length > 0) {
                // Find the first WebSocket that is in OPEN state
                return globalThis.sockets.find(s => s.readyState === WebSocket.OPEN);
            }
            return null;
        }

        // Helper to send draw commands (new, for both local and remote drawing)
        _sendDrawCommand(x1_game, y1_game, x2_game, y2_game, thickness, color) {
            const gameSocket = this._getGameSocket();
            if (!gameSocket) {
                this.notify("warning", "No hay conexión WebSocket activa. No se pueden enviar comandos de dibujo.");
                return;
            }

            // Send command to server (coordinates normalized to 0-1)
            const command = `42["drawcmd",0,[${x1_game/100},${y1_game/100},${x2_game/100},${y2_game/100},false,${0 - thickness},"${color}",0,0,{}]]`;
            gameSocket.send(command);

            // Local rendering on the game canvas
            if (this._gameCtx && this._gameCanvas) {
                const localX1 = (x1_game / 100) * this._gameCanvas.width;
                const localY1 = (y1_game / 100) * this._gameCanvas.height;
                const localX2 = (x2_game / 100) * this._gameCanvas.width;
                const localY2 = (y2_game / 100) * this._gameCanvas.height;

                this._gameCtx.strokeStyle = color;
                this._gameCtx.lineWidth = thickness;
                this._gameCtx.lineCap = 'round';
                this._gameCtx.lineJoin = 'round';
                this._gameCtx.beginPath();
                this._gameCtx.moveTo(localX1, localY1);
                this._gameCtx.lineTo(localX2, localY2);
                this._gameCtx.stroke();
            }
        }

        async _handleFileInput(ev) {
            const file = this._fileInput.files[0];
            if (!file) {
                this._drawingStatusLabel.textContent = "No se seleccionó ningún archivo.";
                this._insertButton.disabled = true;
                return;
            }

            this._drawingStatusLabel.innerHTML = 'Cargando imagen... <span class="loading-spinner"></span>';
            this._insertButton.disabled = true;
            this._stopDrawingButton.disabled = true;
            this._drawingActive = false;

            try {
                const base64 = await this._fileToBase64(file);
                const img = new Image();
                img.crossOrigin = "Anonymous";
                img.onload = async () => {
                    const maxDim = 150;
                    let width = img.width;
                    let height = img.height;

                    if (width > maxDim || height > maxDim) {
                        if (width / maxDim > height / maxDim) {
                            height = Math.round(height * (maxDim / width));
                            width = maxDim;
                        } else {
                            width = Math.round(width * (maxDim / height));
                            height = maxDim;
                        }
                    }

                    this._processingCanvas.width = width;
                    this._processingCanvas.height = height;
                    this._processingCtx.clearRect(0, 0, width, height);
                    this._processingCtx.drawImage(img, 0, 0, width, height);
                    this._imageData = this._processingCtx.getImageData(0, 0, width, height);

                    await this._generateDrawingCommands();

                    this._drawingStatusLabel.textContent = `Imagen '${file.name}' cargada y lista (${this._drawingCommands.length} comandos).`;
                    this._insertButton.disabled = false;
                    this.notify("success", "Imagen cargada y comandos generados.");
                };
                img.onerror = (err) => {
                    throw new Error("Fallo al cargar la imagen: " + err.type);
                };
                img.src = base64;

            } catch (e) {
                this._drawingStatusLabel.textContent = `Error: ${e.message}`;
                this.notify("error", `Fallo al procesar imagen: ${e.message}`);
            }
        }

        _fileToBase64(file) {
            return new Promise((resolve, reject) => {
                const reader = new FileReader();
                reader.onload = (ev) => resolve(ev.target.result);
                reader.onerror = (err) => reject(new Error("Error leyendo archivo: " + err.message));
                reader.readAsDataURL(file);
            });
        }

        async _generateDrawingCommands() {
            if (!this._imageData) {
                this.notify("warning", "No hay datos de imagen para generar comandos.");
                return;
            }

            this._drawingCommands = [];
            const pixels = this._imageData.data;
            const width = this._imageData.width;
            const height = this._imageData.height;

            const brushSize = parseInt(this._brushSizeInput.value) || 2;
            const offsetX = parseFloat(this._offsetXInput.value) || 0;
            const offsetY = parseFloat(this._offsetYInput.value) || 0;
            const pixelDensity = parseInt(this._pixelDensityInput.value) || 1;
            const colorTolerance = parseInt(this._colorToleranceInput.value) || 15;

            const scaleX = 100 / width;
            const scaleY = 100 / height;

            for (let y = 0; y < height; y += pixelDensity) {
                let currentLineColor = null;
                let lineStartX = -1;

                for (let x = 0; x < width; x += pixelDensity) {
                    const index = (y * width + x) * 4;
                    const r = pixels[index];
                    const g = pixels[index + 1];
                    const b = pixels[index + 2];
                    const a = pixels[index + 3];

                    const currentColor = [r, g, b, a];

                    if (a > 20) {
                        if (currentLineColor === null) {
                            currentLineColor = currentColor;
                            lineStartX = x;
                        } else if (!_areColorsSimilar(currentLineColor, currentColor, colorTolerance)) {
                            const gameX1 = lineStartX * scaleX + offsetX;
                            const gameY1 = y * scaleY + offsetY;
                            const gameX2 = (x - pixelDensity) * scaleX + offsetX;
                            const gameY2 = y * scaleY + offsetY;

                            this._drawingCommands.push({
                                x1: gameX1,
                                y1: gameY1,
                                x2: gameX2 + (brushSize * scaleX * 0.5),
                                y2: gameY2,
                                color: _rgbaArrayToString(currentLineColor),
                                thickness: brushSize
                            });

                            currentLineColor = currentColor;
                            lineStartX = x;
                        }
                    } else {
                        if (currentLineColor !== null) {
                            const gameX1 = lineStartX * scaleX + offsetX;
                            const gameY1 = y * scaleY + offsetY;
                            const gameX2 = (x - pixelDensity) * scaleX + offsetX;
                            const gameY2 = y * scaleY + offsetY;

                            this._drawingCommands.push({
                                x1: gameX1,
                                y1: gameY1,
                                x2: gameX2 + (brushSize * scaleX * 0.5),
                                y2: gameY2,
                                color: _rgbaArrayToString(currentLineColor),
                                thickness: brushSize
                            });
                            currentLineColor = null;
                            lineStartX = -1;
                        }
                    }
                }
                if (currentLineColor !== null && lineStartX !== -1) {
                    const gameX1 = lineStartX * scaleX + offsetX;
                    const gameY1 = y * scaleY + offsetY;
                    const gameX2 = (width - pixelDensity) * scaleX + offsetX;
                    const gameY2 = y * scaleY + offsetY;

                    this._drawingCommands.push({
                        x1: gameX1,
                        y1: gameY1,
                        x2: gameX2 + (brushSize * scaleX * 0.5),
                        y2: gameY2,
                        color: _rgbaArrayToString(currentLineColor),
                        thickness: brushSize
                    });
                }
            }
            this.notify("info", `Comandos de dibujo generados: ${this._drawingCommands.length} líneas.`);
        }

        async _startDrawing() {
            if (this._drawingCommands.length === 0) {
                this.notify("warning", "No hay comandos de dibujo. Carga una imagen y genera los comandos primero.");
                return;
            }

            const gameSocket = this._getGameSocket();
            if (!gameSocket) {
                this.notify("error", "No se encontró una conexión WebSocket activa con el juego. Asegúrate de estar en una sala de Drawaria.");
                return;
            }

            this._drawingActive = true;
            this._currentDrawingIndex = 0;
            this._insertButton.disabled = true;
            this._stopDrawingButton.disabled = false;
            this._fileInput.disabled = true;

            this.notify("info", "Iniciando dibujo de imagen...");
            this._drawingStatusLabel.innerHTML = `Dibujando... ${this._currentDrawingIndex}/${this._drawingCommands.length}`;

            const delayMs = parseInt(this._drawingSpeedInput.value) || 5;

            while (this._drawingActive && this._currentDrawingIndex < this._drawingCommands.length) {
                const cmd = this._drawingCommands[this._currentDrawingIndex];

                if (!gameSocket || gameSocket.readyState !== WebSocket.OPEN) {
                    this.notify("warning", "Conexión WebSocket perdida. Deteniendo dibujo.");
                    this._stopDrawing();
                    break;
                }

                const clippedX1 = Math.max(0, Math.min(100, cmd.x1));
                const clippedY1 = Math.max(0, Math.min(100, cmd.y1));
                const clippedX2 = Math.max(0, Math.min(100, cmd.x2));
                const clippedY2 = Math.max(0, Math.min(100, cmd.y2));

                // Send and render locally
                this._sendDrawCommand(clippedX1, clippedY1, clippedX2, clippedY2, cmd.thickness, cmd.color);

                this._currentDrawingIndex++;
                this._drawingStatusLabel.textContent = `Dibujando... ${this._currentDrawingIndex}/${this._drawingCommands.length}`;

                await new Promise(resolve => setTimeout(resolve, delayMs));
            }

            if (this._drawingActive) {
                this.notify("success", "Dibujo de imagen completado!");
                this._drawingStatusLabel.textContent = `Dibujo completado (${this._drawingCommands.length} líneas).`;
            } else {
                this.notify("info", `Dibujo detenido manualmente. ${this._currentDrawingIndex} de ${this._drawingCommands.length} líneas dibujadas.`);
            }

            this._insertButton.disabled = false;
            this._stopDrawingButton.disabled = true;
            this._fileInput.disabled = false;
        }

        _stopDrawing() {
            this._drawingActive = false;
            this._insertButton.disabled = false;
            this._stopDrawingButton.disabled = true;
            this._fileInput.disabled = false;
            this.notify("info", "Dibujo detenido.");
            this._drawingStatusLabel.textContent = `Dibujo detenido. ${this._currentDrawingIndex}/${this._drawingCommands.length} líneas.`;
        }

        // --- Methods for Registro del Juego (GameLog) ---

        _setupLogHooks() {
            if (globalThis._io && globalThis._io.events) {
                const eventsToLog = [
                    "bc_chatmessage", "uc_turn_begindraw", "uc_turn_selectword",
                    "bc_round_results", "bc_turn_results", "bc_votekick",
                    "bc_clientnotify", "bc_announcement", "bc_extannouncement",
                    "mc_roomplayerschange"
                ];

                eventsToLog.forEach(eventName => {
                    const originalEventCallback = globalThis._io.events[eventName];
                    globalThis._io.events[eventName] = (...args) => {
                        this._logEvent(eventName, args);
                        if (originalEventCallback) {
                            originalEventCallback.apply(this, args);
                        }
                    };
                });
            }

            const chatboxMessages = document.getElementById("chatbox_messages");
            if (chatboxMessages) {
                const chatObserver = new MutationObserver((mutations) => {
                    if (!this._isLoggingActive) return;
                    mutations.forEach(mutation => {
                        mutation.addedNodes.forEach(node => {
                            if (node.nodeType === 1 && node.classList.contains('chatmessage')) {
                                this._logChatMessage(node);
                            }
                        });
                    });
                });
                chatObserver.observe(chatboxMessages, { childList: true });
            }
        }

        _logEvent(type, data) {
            if (!this._isLoggingActive) return;
            const timestamp = new Date().toISOString();
            this._gameLog.push({ timestamp, type, data: JSON.parse(JSON.stringify(data)) });
            this._updateLogDisplay();
        }

        _logChatMessage(messageNode) {
            if (!this._isLoggingActive) return;
            const timestamp = messageNode.dataset.ts ? new Date(parseInt(messageNode.dataset.ts)).toISOString() : new Date().toISOString();
            let entry = { timestamp, type: "chatmsg" };

            if (messageNode.classList.contains('systemchatmessage') || messageNode.classList.contains('systemchatmessage5')) {
                entry.subtype = "system";
                entry.content = messageNode.textContent.trim();
            } else {
                entry.subtype = "player";
                entry.playerName = messageNode.querySelector('.playerchatmessage-name')?.textContent?.trim() || 'Unknown';
                entry.playerId = messageNode.querySelector('.playerchatmessage-name')?.parentElement?.dataset?.playerid || 'N/A';
                entry.content = messageNode.querySelector('.playerchatmessage-text')?.textContent?.trim() || '';
                entry.isSelf = messageNode.classList.contains('playerchatmessage-selfname');
            }
            this._gameLog.push(entry);
            this._updateLogDisplay();
        }

        _updateLogDisplay() {
            const maxDisplayEntries = 50;
            const entriesToDisplay = this._gameLog.slice(-maxDisplayEntries);

            this._logDisplayElement.innerHTML = '';
            entriesToDisplay.forEach(entry => {
                const logLine = domMake.Tree("div", {
                    style: `
                        white-space: nowrap;
                        overflow: hidden;
                        text-overflow: ellipsis;
                        color: ${entry.type.includes('error') ? 'var(--danger)' : entry.type.includes('warning') ? 'var(--warning)' : entry.type.includes('system') ? 'var(--info)' : 'var(--CE-color)'};
                    `,
                    title: JSON.stringify(entry)
                });
                let displayTxt = `[${new Date(entry.timestamp).toLocaleTimeString()}] `;
                if (entry.type === "chatmsg") {
                    if (entry.subtype === "system") {
                        displayTxt += `[SISTEMA] ${entry.content}`;
                    } else {
                        displayTxt += `[CHAT] ${entry.playerName} (${entry.playerId}): ${entry.content}`;
                    }
                } else if (entry.type === "uc_turn_begindraw" && entry.data && entry.data) {
                    displayTxt += `[TURNO] Comienza dibujo. Palabra: ${entry.data[1] || 'Desconocida'}`;
                } else if (entry.type === "uc_turn_selectword" && entry.data && entry.data) {
                    displayTxt += `[TURNO] Seleccionar palabra: [${entry.data[2]?.join(', ') || 'N/A'}]`;
                } else if (entry.type === "bc_round_results") {
                    displayTxt += `[RONDA] Resultados de ronda.`;
                } else if (entry.type === "bc_turn_results") {
                    displayTxt += `[TURNO] Resultados de turno.`;
                } else {
                    displayTxt += `[${entry.type}] ${JSON.stringify(entry.data).substring(0, 50)}...`;
                }
                logLine.textContent = displayTxt;
                this._logDisplayElement.appendChild(logLine);
            });
            this._logDisplayElement.scrollTop = this._logDisplayElement.scrollHeight;
        }

        _toggleLogging(button) {
            this._isLoggingActive = !this._isLoggingActive;
            button.classList.toggle("active", !this._isLoggingActive);
            button.textContent = this._isLoggingActive ? "Desactivar Registro" : "Activar Registro";
            this.notify("info", `Registro de Juego: ${this._isLoggingActive ? 'Activo' : 'Inactivo'}`);
        }

        _clearLog() {
            if (confirm("¿Estás seguro de que quieres limpiar todo el registro del juego?")) {
                this._gameLog = [];
                this._updateLogDisplay();
                this.notify("info", "Registro de Juego limpiado.");
            }
        }

        _exportLog(format) {
            if (this._gameLog.length === 0) {
                this.notify("warning", "No hay datos en el registro para exportar.");
                return;
            }

            let dataString;
            let mimeType;
            let filename = `drawaria_game_log_${new Date().toISOString().slice(0, 10)}`;

            if (format === 'json') {
                dataString = JSON.stringify(this._gameLog, null, 2);
                mimeType = 'application/json';
                filename += '.json';
            } else {
                dataString = this._gameLog.map(entry => {
                    const time = new Date(entry.timestamp).toLocaleTimeString();
                    if (entry.type === "chatmsg") {
                        if (entry.subtype === "system") {
                            return `[${time}] [SISTEMA] ${entry.content}`;
                        } else {
                            return `[${time}] [CHAT] ${entry.playerName} (${entry.playerId}): ${entry.content}`;
                        }
                    } else if (entry.type === "uc_turn_begindraw" && entry.data && entry.data) {
                        return `[${time}] [TURNO_INICIO] Palabra: ${entry.data[1] || 'Desconocida'}`;
                    } else if (entry.type === "uc_turn_selectword" && entry.data && entry.data) {
                        return `[${time}] [TURNO_PALABRA] Opciones: [${entry.data[2]?.join(', ') || 'N/A'}]`;
                    } else if (entry.type === "bc_round_results") {
                        return `[${time}] [RONDA_FIN] Resultados: ${JSON.stringify(entry.data[0].map(p => ({name: p[1], score: p[2]})))}`;
                    }
                    return `[${time}] [${entry.type}] ${JSON.stringify(entry.data)}`;
                }).join('\n');
                mimeType = 'text/plain';
                filename += '.txt';
            }

            const blob = new Blob([dataString], { type: mimeType });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = filename;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            this.notify("success", `Log exportado como ${filename}.`);
        }
    }
})("QBit");

// --- 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");

        _customColors = [];
        _colorButtonsContainer;
        _colorInput;
        _colorPaletteObserver;
        _gameTriangleElement = null;
        _proxyGameButton = null;
        _gameCanvas = null; // Reference to the main game canvas
        _gameCtx = null;    // Context of the main game canvas

        _isPressureActive = false;
        _isTextureActive = false;
        _lastMousePos = { x: 0, y: 0 }; // Stores game coordinates (0-100)
        _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);

        constructor() {
            super("Maestro de Paletas", '<i class="fas fa-brush"></i>');
            this._onStartup();
        }

        _onStartup() {
            this._findGameCanvas(); // Locate game canvas and its context
            this._loadInterface();
            this._loadCustomColors();
            this._setupColorPaletteObserver();
            this._hookDrawingEvents(); // Attach event listeners to game canvas
        }

        _findGameCanvas() {
            this._gameCanvas = document.getElementById('canvas');
            if (this._gameCanvas) {
                this._gameCtx = this._gameCanvas.getContext('2d');
            } else {
                this.notify("warning", "Canvas del juego no encontrado. Las herramientas de trazo podrían no funcionar.");
            }
        }

        _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"]));

            // 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); // Re-added to UI

            // 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); // Re-added to UI


            // 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"));
            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"));
            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"));
            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"));
            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"));
            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"));
            gradientGroup.appendChild(domMake.Tree("div", {}, [waveGradientButton]));


            strokeSection.appendChild(gradientGroup);
            container.appendChild(strokeSection);

            this.htmlElements.section.appendChild(container);
        }

        // Helper to get the currently active game WebSocket (botless)
        #getGameSocket() {
            if (globalThis.sockets && globalThis.sockets.length > 0) {
                // Find the first WebSocket that is in OPEN state
                return globalThis.sockets.find(s => s.readyState === WebSocket.OPEN);
            }
            return null;
        }

        // Helper to send draw commands (new, for both local and remote drawing)
        // x1_game, y1_game, x2_game, y2_game are in 0-100 game coordinates
        _sendDrawCommand(x1_game, y1_game, x2_game, y2_game, thickness, color) {
            const gameSocket = this.#getGameSocket();
            if (!gameSocket) {
                this.notify("warning", "No hay conexión WebSocket activa. No se pueden enviar comandos de dibujo.");
                return;
            }

            // Send command to server (coordinates normalized to 0-1)
            const command = `42["drawcmd",0,[${x1_game/100},${y1_game/100},${x2_game/100},${y2_game/100},false,${0 - thickness},"${color}",0,0,{}]]`;
            gameSocket.send(command);

            // Local rendering on the game canvas
            if (this._gameCtx && this._gameCanvas) {
                const localX1 = (x1_game / 100) * this._gameCanvas.width;
                const localY1 = (y1_game / 100) * this._gameCanvas.height;
                const localX2 = (x2_game / 100) * this._gameCanvas.width;
                const localY2 = (y2_game / 100) * this._gameCanvas.height;

                this._gameCtx.strokeStyle = color;
                this._gameCtx.lineWidth = thickness;
                this._gameCtx.lineCap = 'round';
                this._gameCtx.lineJoin = 'round';
                this._gameCtx.beginPath();
                this._gameCtx.moveTo(localX1, localY1);
                this._gameCtx.lineTo(localX2, localY2);
                this._gameCtx.stroke();
            }
        }


        // --- 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.`);
        }

        // Public method to add color from other modules (e.g., Image Analyzer)
        addCustomColorFromExternal(hexColor) {
            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 ---

        _hookDrawingEvents() {
            if (!this._gameCanvas) return;

            // Remove previous listeners to prevent duplicates
            this._gameCanvas.removeEventListener("mousedown", this._boundMouseDownHandler);
            this._gameCanvas.removeEventListener("mousemove", this._boundMouseMoveHandler);
            this._gameCanvas.removeEventListener("mouseup", this._boundMouseUpHandler);
            this._gameCanvas.removeEventListener("mouseout", this._boundMouseUpHandler);

            // Attach listeners using the pre-bound handlers
            this._gameCanvas.addEventListener("mousedown", this._boundMouseDownHandler);
            this._gameCanvas.addEventListener("mousemove", this._boundMouseMoveHandler);
            this._gameCanvas.addEventListener("mouseup", this._boundMouseUpHandler);
            this._gameCanvas.addEventListener("mouseout", this._boundMouseUpHandler);
        }

        _handleMouseDown(e) {
            this.isDrawingLocal = true;
            const rect = e.target.getBoundingClientRect();
            // Store coordinates as game coordinates (0-100)
            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_game = ((e.clientX - rect.left) / rect.width) * 100;
            const currentY_game = ((e.clientY - rect.top) / rect.height) * 100;

            let currentThickness = this._lastDrawThickness;
            let currentColor = this._getCurrentBrushColor();

            if (this._isPressureActive) {
                const currentTimestamp = performance.now();
                // Calculate speed using original pixel coordinates for accuracy
                const originalRect = this._gameCanvas.getBoundingClientRect(); // Use _gameCanvas for accurate dimensions
                const dx_px = e.clientX - ((this._lastMousePos.x / 100) * originalRect.width + originalRect.left);
                const dy_px = e.clientY - ((this._lastMousePos.y / 100) * originalRect.height + originalRect.top);
                const distance = Math.sqrt(dx_px * dx_px + dy_px * dy_px);
                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);
            }

            this._sendDrawCommand(this._lastMousePos.x, this._lastMousePos.y, currentX_game, currentY_game, currentThickness, currentColor);

            this._lastMousePos.x = currentX_game;
            this._lastMousePos.y = currentY_game;
            this._lastTimestamp = performance.now();
        }

        _handleMouseUp() {
            this.isDrawingLocal = false;
            this._lastDrawThickness = 5; // Reset thickness
        }

        _getCurrentBrushColor() {
            const colorPicker = document.querySelector('.drawcontrols-color.active');
            if (colorPicker) {
                const rgb = colorPicker.style.backgroundColor;
                if (rgb) return rgb;
            }
            return "#000000";
        }

        _applyColorNoise(color, noiseAmount) {
            let r, g, b;
            if (color.startsWith("rgb")) {
                const parts = color.match(/\d+/g).map(Number);
                r = parts[0]; g = parts[1]; b = parts[2];
            } else if (color.startsWith("#")) {
                const hex = color.slice(1);
                r = parseInt(hex.substring(0, 2), 16);
                g = parseInt(hex.substring(2, 4), 16);
                b = parseInt(hex.substring(4, 6), 16);
            } else {
                return color;
            }

            const addNoise = (value) => {
                const noise = (Math.random() - 0.5) * 2 * noiseAmount;
                return Math.max(0, Math.min(255, Math.floor(value + noise)));
            };
            return `rgb(${addNoise(r)},${addNoise(g)},${addNoise(b)})`;
        }

        _togglePressureControl(button) {
            this._isPressureActive = !this._isPressureActive;
            button.classList.toggle("active", this._isPressureActive);
            button.textContent = this._isPressureActive ? "Control de Presión Activo" : "Control de Presión";
            this.notify("info", `Control de Presión ${this._isPressureActive ? 'activado' : 'desactivado'}.`);
        }

        _toggleTextureBrush(button) {
            this._isTextureActive = !this._isTextureActive;
            button.classList.toggle("active", this._isTextureActive);
            button.textContent = this._isTextureActive ? "Pincel Texturizado Activo" : "Pincel Texturizado";
            this.notify("info", `Pincel Texturizado ${this._isTextureActive ? 'activado' : 'desactivado'}.`);
        }

        async _simulateGradientFill(type) {
            this.notify("info", `Simulando relleno degradado tipo '${type}'.`);

            const startX = 20, endX = 80; // Game coordinates (0-100)
            const startY = 20, endY = 80; // Game coordinates (0-100)
            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; // Game units
                        const centerX = 50, centerY = 50; // Game units
                        const p1x = centerX, p1y = centerY - currentDistance;
                        const p2x = centerX + currentDistance, p2y = centerY;
                        const p3x = centerX, p3y = centerY + currentDistance;
                        const p4x = centerX - currentDistance, p4y = centerY;
                        this._sendDrawCommand(p1x, p1y, p2x, p2y, thickness, currentColor);
                        this._sendDrawCommand(p2x, p2y, p3x, p3y, thickness, currentColor);
                        this._sendDrawCommand(p3x, p3y, p4x, p4y, thickness, currentColor);
                        this._sendDrawCommand(p4x, p4y, p1x, p1y, thickness, currentColor);
                        break;
                    }
                    case "radial": {
                        r = 255;
                        g = Math.floor(165 + (255 - 165) * (1 - ratio));
                        b = 0;
                        currentColor = `rgb(${r},${g},${b})`;
                        const currentRadius = 30 * ratio; // Game units
                        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);
                            this._sendDrawCommand(x, y, x + 0.1, y + 0.1, thickness, currentColor); // Draw small dots
                        }
                        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;
                        this._sendDrawCommand(startX, currentY, endX, currentY, thickness, currentColor);
                        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;
                        this._sendDrawCommand(startX, vertY, endX, vertY, thickness, currentColor);
                        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; // Game units
                        const cx = 50, cy = 50; // Game units
                        const x2 = cx + radius * Math.cos(angle);
                        const y2 = cy + radius * Math.sin(angle);
                        this._sendDrawCommand(cx, cy, x2, y2, thickness, currentColor);
                        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; // Game units
                        const waveFrequency = 0.1;
                        const wavyY = startY + (endY - startY) * ratio + waveAmplitude * Math.sin(ratio * Math.PI * 2 * waveFrequency);
                        this._sendDrawCommand(startX, wavyY, endX, wavyY, thickness, currentColor);
                        break;
                    }
                }
                await new Promise(resolve => setTimeout(resolve, delayMs));
            }
            this.notify("success", `Degradado tipo '${type}' dibujado.`);
        }
    }
})("QBit");

// START EXTRACTOR

(function PlayerProfileExtractorModule() {
    const QBit = globalThis[arguments[0]];

    // Define token names for better readability. These are from Drawaria's common.js.
    const TOKEN_NAMES = {
        0: "Thumbs Up",
        1: "Heart",
        2: "Paint Brush",
        3: "Cocktail",
        4: "Peace Sign",
        5: "Feather",
        6: "Trophy",
        7: "Mug",
        8: "Gift"
    };

    class PlayerProfileExtractor extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        #profileUrlInput;
        #extractedDataDisplay;
        #downloadButton;
        #lastExtractedData = null; // Store data for download

        constructor() {
            super("Extractor de Perfil (Pegar URL de perfil)", '<i class="fas fa-id-card"></i>');
            this.#onStartup();
        }

        #onStartup() {
            this.#loadInterface();
            this.notify("info", "Módulo Extractor de Perfil cargado.");
        }

        #loadInterface() {
            const container = domMake.Tree("div");

            // URL Input Row
            const urlRow = domMake.Row();
            // MODIFICACIÓN: Valor por defecto en lugar de placeholder
            this.#profileUrlInput = domMake.Tree("input", {
                type: "text",
                value: "https://drawaria.online/profile/?uid=86e33830-86ea-11ec-8553-bff27824cf71",
                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 rofileExtractor


// START IMAGETOOLS

(function ImageToolsModule() {
    const QBit = globalThis["QBit"];

    // Función simple para convertir RGB a Hex
    function rgbToHex(r, g, b) {
        return "#" + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1).toUpperCase();
    }

    class ImageTools extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        constructor() {
            super("Gallery Image Tools", '<i class="fas fa-id-badge"></i>');
            this.imageUrlInput = null;

            // Redimensionador
            this.scaleInput = null;
            this.widthInput = null;
            this.heightInput = null;
            this.resizeProcessButton = null;
            this.resizePreviewContainer = null;

            // Selector de Color
            this.pickedColorBox = null;
            this.pickedColorHex = null;
            this.pickedColorRgb = null;
            this.copyColorButton = null;

            // Voltear/Rotar
            this.flipRotateSection = null;
            this.flipHButton = null;
            this.flipVButton = null;
            this.rotateCWButton = null;
            this.rotateCCWButton = null;

            // Escala de Grises (Mejorado)
            this.grayscaleSection = null;
            this.grayscaleTypeSelect = null;
            this.applyGrayscaleButton = null;
            this.resetGrayscaleButton = null;

            // Brillo/Contraste
            this.brightnessContrastSection = null;
            this.brightnessInput = null;
            this.contrastInput = null;
            this.applyBrightnessContrastButton = null;
            this.resetBrightnessContrastButton = null;

            // Invertir Colores
            this.invertColorsSection = null;
            this.invertColorsButton = null;

            // Desenfoque
            this.blurSection = null;
            this.blurRadiusInput = null;
            this.applyBlurButton = null;
            this.resetBlurButton = null;

            // Nitidez (Mejorado)
            this.sharpenSection = null;
            this.sharpenAmountInput = null;
            this.applySharpenButton = null;
            this.resetSharpenButton = null;

            // Saturación
            this.saturationSection = null;
            this.saturationInput = null;
            this.applySaturationButton = null;
            this.resetSaturationButton = null;

            // Sepia (Mejorado)
            this.sepiaSection = null;
            this.sepiaAmountInput = null;
            this.applySepiaButton = null;
            this.resetSepiaButton = null;

            // Filtro de Color (NUEVO)
            this.colorFilterSection = null;
            this.colorFilterRInput = null;
            this.colorFilterGInput = null;
            this.colorFilterBInput = null;
            this.colorFilterMixInput = null;
            this.colorFilterColorBox = null;
            this.applyColorFilterButton = null;
            this.resetColorFilterButton = null;

            // Umbral (NUEVO)
            this.thresholdSection = null;
            this.thresholdInput = null;
            this.applyThresholdButton = null;
            this.resetThresholdButton = null;

            // Detección de Bordes (NUEVO)
            this.edgeDetectionSection = null;
            this.edgeDetectionButton = null;


            this.interactiveCanvas = null;
            this.loadingIndicator = null;

            this.currentImageBase64 = null; // Almacenará la imagen en Base64 una vez cargada
            this.originalImageBase64OnLoad = null; // Almacena la imagen original tal como se cargó
            this.originalWidth = 0;
            this.originalHeight = 0;
            this.isColorPickingMode = false;

            this._onStartup(); // LLAMADA CORREGIDA
        }

        _onStartup() { // RENOMBRADO de #onStartup
            this._loadInterface(); // LLAMADA CORREGIDA
        }

        _loadInterface() { // RENOMBRADO de #loadInterface
            const section = this.htmlElements.section;

            // --- Controles de Carga de Imagen por URL ---
            const loadControlsSection = domMake.Tree("div", { style: "margin-bottom:15px;" });
            loadControlsSection.appendChild(domMake.Tree("h5", { style: "text-align:center; margin-bottom:10px;" }, "Cargar Imagen por URL"));

            const urlRow = domMake.Row();
            this.imageUrlInput = domMake.Tree("input", {
                type: "text",
                value: "https://galleryhost2-cf.drawaria.online/images/1838s.jpg", // Texto predefinido
                style: "min-width:170px;max-width:260px;",
                id: "image-tools-url-input"
            });
            urlRow.appendChild(this.imageUrlInput);
            loadControlsSection.appendChild(urlRow);

            const loadImageButton = domMake.Button("Cargar Imagen");
            loadImageButton.addEventListener('click', () => this._loadImageFromURL(this.imageUrlInput.value));
            loadControlsSection.appendChild(domMake.Tree("div", { style: "text-align:center; margin-top: 10px;" }, loadImageButton));

            section.appendChild(loadControlsSection);
            section.appendChild(domMake.Tree("hr"));

            // --- Controles de Pestañas/Modos ---
            const modeTabs = domMake.Tree("div", { class: "icon-list", style: "justify-content:center; margin-bottom:10px;" });

            // Pestaña Redimensionador
            const resizerTabInputId = generate.uuidv4();
            const resizerTabInput = domMake.Tree("input", { type: "radio", name: "imageToolMode", id: resizerTabInputId, hidden: true, checked: true });
            const resizerTabLabel = domMake.Tree("label", { for: resizerTabInputId, class: "icon", title: "Redimensionar Imagen" }, domMake.Tree("i", { class: "fas fa-arrows-alt-h" }));
            resizerTabInput.addEventListener("change", () => this._toggleToolMode('resizer'));

            // Pestaña Selector de Color
            const colorPickerTabInputId = generate.uuidv4();
            const colorPickerTabInput = domMake.Tree("input", { type: "radio", name: "imageToolMode", id: colorPickerTabInputId, hidden: true });
            const colorPickerTabLabel = domMake.Tree("label", { for: colorPickerTabInputId, class: "icon", title: "Selector de Color" }, domMake.Tree("i", { class: "fas fa-palette" }));
            colorPickerTabInput.addEventListener("change", () => this._toggleToolMode('colorPicker'));

            // Pestaña Voltear/Rotar
            const flipRotateTabInputId = generate.uuidv4();
            const flipRotateTabInput = domMake.Tree("input", { type: "radio", name: "imageToolMode", id: flipRotateTabInputId, hidden: true });
            const flipRotateTabLabel = domMake.Tree("label", { for: flipRotateTabInputId, class: "icon", title: "Voltear/Rotar Imagen" }, domMake.Tree("i", { class: "fas fa-sync-alt" }));
            flipRotateTabInput.addEventListener("change", () => this._toggleToolMode('flipRotate'));

            // Pestaña Escala de Grises (MEJORADO)
            const grayscaleTabInputId = generate.uuidv4();
            const grayscaleTabInput = domMake.Tree("input", { type: "radio", name: "imageToolMode", id: grayscaleTabInputId, hidden: true });
            const grayscaleTabLabel = domMake.Tree("label", { for: grayscaleTabInputId, class: "icon", title: "Escala de Grises" }, domMake.Tree("i", { class: "fas fa-tint-slash" }));
            grayscaleTabInput.addEventListener("change", () => this._toggleToolMode('grayscale'));

            // Pestaña Brillo/Contraste
            const brightnessContrastTabInputId = generate.uuidv4();
            const brightnessContrastTabInput = domMake.Tree("input", { type: "radio", name: "imageToolMode", id: brightnessContrastTabInputId, hidden: true });
            const brightnessContrastTabLabel = domMake.Tree("label", { for: brightnessContrastTabInputId, class: "icon", title: "Brillo/Contraste" }, domMake.Tree("i", { class: "fas fa-sun" }));
            brightnessContrastTabInput.addEventListener("change", () => this._toggleToolMode('brightnessContrast'));

            // Pestaña Invertir Colores
            const invertColorsTabInputId = generate.uuidv4();
            const invertColorsTabInput = domMake.Tree("input", { type: "radio", name: "imageToolMode", id: invertColorsTabInputId, hidden: true });
            const invertColorsTabLabel = domMake.Tree("label", { for: invertColorsTabInputId, class: "icon", title: "Invertir Colores" }, domMake.Tree("i", { class: "fas fa-exchange-alt" }));
            invertColorsTabInput.addEventListener("change", () => this._toggleToolMode('invertColors'));

            // Pestaña Desenfoque
            const blurTabInputId = generate.uuidv4();
            const blurTabInput = domMake.Tree("input", { type: "radio", name: "imageToolMode", id: blurTabInputId, hidden: true });
            const blurTabLabel = domMake.Tree("label", { for: blurTabInputId, class: "icon", title: "Desenfoque" }, domMake.Tree("i", { class: "fas fa-water" }));
            blurTabInput.addEventListener("change", () => this._toggleToolMode('blur'));

            // Pestaña Nitidez (MEJORADO)
            const sharpenTabInputId = generate.uuidv4();
            const sharpenTabInput = domMake.Tree("input", { type: "radio", name: "imageToolMode", id: sharpenTabInputId, hidden: true });
            const sharpenTabLabel = domMake.Tree("label", { for: sharpenTabInputId, class: "icon", title: "Nitidez" }, domMake.Tree("i", { class: "fas fa-plus-circle" }));
            sharpenTabInput.addEventListener("change", () => this._toggleToolMode('sharpen'));

            // Pestaña Saturación
            const saturationTabInputId = generate.uuidv4();
            const saturationTabInput = domMake.Tree("input", { type: "radio", name: "imageToolMode", id: saturationTabInputId, hidden: true });
            const saturationTabLabel = domMake.Tree("label", { for: saturationTabInputId, class: "icon", title: "Saturación" }, domMake.Tree("i", { class: "fas fa-fill-drip" }));
            saturationTabInput.addEventListener("change", () => this._toggleToolMode('saturation'));

            // Pestaña Sepia (MEJORADO)
            const sepiaTabInputId = generate.uuidv4();
            const sepiaTabInput = domMake.Tree("input", { type: "radio", name: "imageToolMode", id: sepiaTabInputId, hidden: true });
            const sepiaTabLabel = domMake.Tree("label", { for: sepiaTabInputId, class: "icon", title: "Sepia" }, domMake.Tree("i", { class: "fas fa-tint" }));
            sepiaTabInput.addEventListener("change", () => this._toggleToolMode('sepia'));

            // Pestaña Filtro de Color (NUEVO)
            const colorFilterTabInputId = generate.uuidv4();
            const colorFilterTabInput = domMake.Tree("input", { type: "radio", name: "imageToolMode", id: colorFilterTabInputId, hidden: true });
            const colorFilterTabLabel = domMake.Tree("label", { for: colorFilterTabInputId, class: "icon", title: "Filtro de Color" }, domMake.Tree("i", { class: "fas fa-fill" }));
            colorFilterTabInput.addEventListener("change", () => this._toggleToolMode('colorFilter'));

            // Pestaña Umbral (NUEVO)
            const thresholdTabInputId = generate.uuidv4();
            const thresholdTabInput = domMake.Tree("input", { type: "radio", name: "imageToolMode", id: thresholdTabInputId, hidden: true });
            const thresholdTabLabel = domMake.Tree("label", { for: thresholdTabInputId, class: "icon", title: "Umbral" }, domMake.Tree("i", { class: "fas fa-adjust" }));
            thresholdTabInput.addEventListener("change", () => this._toggleToolMode('threshold'));

            // Pestaña Detección de Bordes (NUEVO)
            const edgeDetectionTabInputId = generate.uuidv4();
            const edgeDetectionTabInput = domMake.Tree("input", { type: "radio", name: "imageToolMode", id: edgeDetectionTabInputId, hidden: true });
            const edgeDetectionTabLabel = domMake.Tree("label", { for: edgeDetectionTabInputId, class: "icon", title: "Detección de Bordes" }, domMake.Tree("i", { class: "fas fa-ruler-combined" }));
            edgeDetectionTabInput.addEventListener("change", () => this._toggleToolMode('edgeDetection'));


            modeTabs.appendAll(
                resizerTabInput, resizerTabLabel,
                colorPickerTabInput, colorPickerTabLabel,
                flipRotateTabInput, flipRotateTabLabel,
                grayscaleTabInput, grayscaleTabLabel,
                brightnessContrastTabInput, brightnessContrastTabLabel,
                invertColorsTabInput, invertColorsTabLabel,
                blurTabInput, blurTabLabel,
                sharpenTabInput, sharpenTabLabel,
                saturationTabInput, saturationTabLabel,
                sepiaTabInput, sepiaTabLabel,
                colorFilterTabInput, colorFilterTabLabel,
                thresholdTabInput, thresholdTabLabel,
                edgeDetectionTabInput, edgeDetectionTabLabel
            );
            section.appendChild(modeTabs);

            // --- Contenedores de cada herramienta ---
            this.resizerSection = domMake.Tree("section", { id: "image-resizer-section" });
            this.colorPickerSection = domMake.Tree("section", { id: "image-color-picker-section", hidden: true });
            this.flipRotateSection = domMake.Tree("section", { id: "image-flip-rotate-section", hidden: true });
            this.grayscaleSection = domMake.Tree("section", { id: "image-grayscale-section", hidden: true });
            this.brightnessContrastSection = domMake.Tree("section", { id: "image-brightness-contrast-section", hidden: true });
            this.invertColorsSection = domMake.Tree("section", { id: "image-invert-colors-section", hidden: true });
            this.blurSection = domMake.Tree("section", { id: "image-blur-section", hidden: true });
            this.sharpenSection = domMake.Tree("section", { id: "image-sharpen-section", hidden: true });
            this.saturationSection = domMake.Tree("section", { id: "image-saturation-section", hidden: true });
            this.sepiaSection = domMake.Tree("section", { id: "image-sepia-section", hidden: true });
            this.colorFilterSection = domMake.Tree("section", { id: "image-color-filter-section", hidden: true });
            this.thresholdSection = domMake.Tree("section", { id: "image-threshold-section", hidden: true });
            this.edgeDetectionSection = domMake.Tree("section", { id: "image-edge-detection-section", hidden: true });


            section.appendAll(
                this.resizerSection,
                this.colorPickerSection,
                this.flipRotateSection,
                this.grayscaleSection,
                this.brightnessContrastSection,
                this.invertColorsSection,
                this.blurSection,
                this.sharpenSection,
                this.saturationSection,
                this.sepiaSection,
                this.colorFilterSection,
                this.thresholdSection,
                this.edgeDetectionSection
            );


            // --- Área compartida de previsualización de imagen ---
            this.interactiveCanvas = domMake.Tree("canvas", {
                style: "max-width:100%; height:auto; display:block; margin: 15px auto 10px auto; border: 1px dashed var(--CE-color); cursor:crosshair;",
            });
            this.interactiveCanvas.width = 1;
            this.interactiveCanvas.height = 1;
            this.interactiveCanvas.addEventListener('click', (e) => this._pickColor(e));
            this.interactiveCanvas.addEventListener('mousemove', (e) => this._previewColor(e));
            this.interactiveCanvas.addEventListener('mouseout', () => this._clearPreviewColor());

            section.appendChild(domMake.Tree("h5", { style: "text-align:center; margin:15px 0 5px 0;" }, "Imagen Cargada:"));
            section.appendChild(this.interactiveCanvas);


            // --- Indicador de Carga Global ---
            this.loadingIndicator = domMake.Tree("div", {
                style: "font-size:0.9em;color:#888;margin-top:5px;display:none; text-align:center;",
            }, "Procesando...");
            section.appendChild(this.loadingIndicator);

            // Instrucciones generales
            section.appendChild(
                domMake.Tree("div", {
                    style: "font-size:0.8em;opacity:.7;margin:10px 0;line-height:1.4",
                }, [
                    domMake.Tree("strong", {}, "Funcionamiento:"),
                    " Esta herramienta carga y manipula imágenes localmente en tu navegador. Pega un link directo de imagen de galería de Drawaria. Se usará GM_xmlhttpRequest para superar CORS."
                ])
            );

            // Cargar contenido de las herramientas
            this._loadResizerUI(); // LLAMADA CORREGIDA
            this._loadColorPickerUI(); // LLAMADA CORREGIDA
            this._loadFlipRotateUI(); // LLAMADA CORREGIDA
            this._loadGrayscaleUI(); // LLAMADA CORREGIDA
            this._loadBrightnessContrastUI(); // LLAMADA CORREGIDA
            this._loadInvertColorsUI(); // LLAMADA CORREGIDA
            this._loadBlurUI(); // LLAMADA CORREGIDA
            this._loadSharpenUI(); // LLAMADA CORREGIDA
            this._loadSaturationUI(); // LLAMADA CORREGIDA
            this._loadSepiaUI(); // LLAMADA CORREGIDA
            this._loadColorFilterUI(); // LLAMADA CORREGIDA
            this._loadThresholdUI(); // LLAMADA CORREGIDA
            this._loadEdgeDetectionUI(); // LLAMADA CORREGIDA

            this._updateToolButtonsState(); // Actualizar estado inicial de botones
        }

        _loadResizerUI() { // RENOMBRADO
            const section = this.resizerSection;
            section.appendChild(domMake.Tree("h5", { style: "text-align:center; margin-bottom:10px;" }, "Herramienta de Redimensionar"));

            const controlsRow = domMake.Row();
            controlsRow.style.flexFlow = "wrap";
            controlsRow.style.gap = "5px";

            this.scaleInput = domMake.Tree("input", {
                type: "number", min: 1, max: 500, value: 100, step: 5,
                title: "Escala (%)", style: "width: calc(33% - 5px);", id: "resizer-scale-input"
            });
            this.widthInput = domMake.Tree("input", {
                type: "number", min: 1, placeholder: "Ancho (px)", title: "Ancho Fijo (px)",
                style: "width: calc(33% - 5px);", id: "resizer-width-input"
            });
            this.heightInput = domMake.Tree("input", {
                type: "number", min: 1, placeholder: "Alto (px)", title: "Alto Fijo (px)",
                style: "width: calc(33% - 5px);", id: "resizer-height-input"
            });

            controlsRow.appendAll(
                domMake.Tree("span", { style: "width:100%; text-align:center; font-size:0.9em; opacity:0.8;" }, "Ajustar Tamaño:"),
                this.scaleInput,
                domMake.Tree("span", { style: "font-size:0.8em; opacity:0.7; display:flex; align-items:center;" }, "%"),
                this.widthInput,
                this.heightInput
            );

            [this.scaleInput, this.widthInput, this.heightInput].forEach(inputElement => {
                inputElement.addEventListener("input", () => {
                    if (inputElement.value !== "") {
                        if (inputElement !== this.scaleInput) this.scaleInput.value = "";
                        if (inputElement !== this.widthInput) this.widthInput.value = "";
                        if (inputElement !== this.heightInput) this.heightInput.value = "";
                    }
                });
            });
            section.appendChild(controlsRow);

            this.resizeProcessButton = domMake.Button("Aplicar Redimensionar");
            this.resizeProcessButton.disabled = true;
            this.resizeProcessButton.addEventListener("click", () => this.processResize());
            section.appendChild(domMake.Tree("div", { style: "text-align:center; margin-top: 10px;" }, this.resizeProcessButton));

            this.resizePreviewContainer = domMake.Tree("div", {
                style: "margin-top:15px; border-top: 1px solid var(--CE-color); padding-top:10px; text-align: center;"
            });
            section.appendChild(this.resizePreviewContainer);
        }

        _loadColorPickerUI() { // RENOMBRADO
            const section = this.colorPickerSection;
            section.appendChild(domMake.Tree("h5", { style: "text-align:center; margin-bottom:10px;" }, "Herramienta de Selector de Color"));

            const colorDisplayRow = domMake.Row();
            colorDisplayRow.style.alignItems = "center";
            colorDisplayRow.style.gap = "10px";

            this.pickedColorBox = domMake.Tree("div", {
                style: "width:40px; height:40px; border:1px solid var(--CE-color); border-radius:4px; background-color:#CCC; flex-shrink:0;",
            });
            this.pickedColorHex = domMake.Tree("span", { style: "font-weight:bold; width: 100%;" }, "HEX: -");
            this.pickedColorRgb = domMake.Tree("span", { style: "font-size:0.9em; opacity:0.8; width: 100%;" }, "RGB: -");
            this.copyColorButton = domMake.Button('<i class="fas fa-copy"></i> Copiar');
            this.copyColorButton.disabled = true;
            this.copyColorButton.addEventListener("click", () => this._copyPickedColor());

            const textContainer = domMake.Tree("div", { style: "display:flex; flex-direction:column; flex-grow:1;" });
            textContainer.appendAll(this.pickedColorHex, this.pickedColorRgb);

            colorDisplayRow.appendAll(this.pickedColorBox, textContainer, this.copyColorButton);
            section.appendChild(colorDisplayRow);

            section.appendChild(domMake.Tree("p", { style: "text-align:center; font-size:0.85em; opacity:0.7; margin-top:10px;" }, "Haz click en la imagen de arriba para seleccionar un color."));
        }

        // UI para Voltear/Rotar
        _loadFlipRotateUI() { // RENOMBRADO
            const section = this.flipRotateSection;
            section.appendChild(domMake.Tree("h5", { style: "text-align:center; margin-bottom:10px;" }, "Herramienta Voltear/Rotar"));

            const controlsRow = domMake.Row();
            controlsRow.style.flexFlow = "wrap";
            controlsRow.style.justifyContent = "center";
            controlsRow.style.gap = "10px";

            this.flipHButton = domMake.Button('<i class="fas fa-arrows-alt-h"></i> Voltear Horizontal');
            this.flipVButton = domMake.Button('<i class="fas fa-arrows-alt-v"></i> Voltear Vertical');
            this.rotateCWButton = domMake.Button('<i class="fas fa-redo"></i> Rotar 90° CW');
            this.rotateCCWButton = domMake.Button('<i class="fas fa-undo"></i> Rotar 90° CCW');

            this.flipHButton.addEventListener('click', () => this.processFlipRotate('horizontal'));
            this.flipVButton.addEventListener('click', () => this.processFlipRotate('vertical'));
            this.rotateCWButton.addEventListener('click', () => this.processFlipRotate('rotateCW'));
            this.rotateCCWButton.addEventListener('click', () => this.processFlipRotate('rotateCCW'));

            controlsRow.appendAll(this.flipHButton, this.flipVButton, this.rotateCWButton, this.rotateCCWButton);
            section.appendChild(controlsRow);
        }

        // UI para Escala de Grises (MEJORADO)
        _loadGrayscaleUI() { // RENOMBRADO
            const section = this.grayscaleSection;
            section.appendChild(domMake.Tree("h5", { style: "text-align:center; margin-bottom:10px;" }, "Herramienta Escala de Grises"));

            const controlsRow = domMake.Row();
            controlsRow.style.flexFlow = "wrap";
            controlsRow.style.justifyContent = "center";
            controlsRow.style.gap = "10px";

            const typeLabel = domMake.Tree("label", { style: "width:100%; text-align:center;" }, "Tipo de Grises: ");
            this.grayscaleTypeSelect = domMake.Tree("select", { style: "width:auto;" });
            this.grayscaleTypeSelect.append(
                domMake.Tree("option", { value: "luminance" }, "Luminancia (Perceptual)"),
                domMake.Tree("option", { value: "average" }, "Promedio Simple")
            );

            controlsRow.appendAll(
                typeLabel, this.grayscaleTypeSelect
            );
            section.appendChild(controlsRow);

            this.applyGrayscaleButton = domMake.Button('Aplicar Escala de Grises');
            this.applyGrayscaleButton.addEventListener('click', () => this.processGrayscale());

            this.resetGrayscaleButton = domMake.Button('Reset');
            this.resetGrayscaleButton.addEventListener('click', () => {
                this.grayscaleTypeSelect.value = "luminance"; // Reset to default
                this.processGrayscale(true); // Re-apply with reset values
            });

            const buttonsRow = domMake.Row();
            buttonsRow.style.justifyContent = "center";
            buttonsRow.style.gap = "10px";
            buttonsRow.style.marginTop = "15px";
            buttonsRow.appendAll(this.applyGrayscaleButton, this.resetGrayscaleButton);
            section.appendChild(buttonsRow);
        }

        // UI para Brillo/Contraste
        _loadBrightnessContrastUI() { // RENOMBRADO
            const section = this.brightnessContrastSection;
            section.appendChild(domMake.Tree("h5", { style: "text-align:center; margin-bottom:10px;" }, "Herramienta Brillo/Contraste"));

            const controlsRow = domMake.Row();
            controlsRow.style.flexFlow = "wrap";
            controlsRow.style.gap = "10px";
            controlsRow.style.justifyContent = "center";


            // Brillo
            const brightnessLabel = domMake.Tree("label", { style: "width:100%; text-align:center;" }, "Brillo: ");
            this.brightnessInput = domMake.Tree("input", {
                type: "range", min: -100, max: 100, value: 0, step: 1,
                style: "width:80%;", title: "Ajuste de Brillo"
            });
            const brightnessValueSpan = domMake.Tree("span", { style: "width:10%; text-align:right;" }, "0");
            this.brightnessInput.addEventListener('input', () => brightnessValueSpan.textContent = this.brightnessInput.value);

            // Contraste
            const contrastLabel = domMake.Tree("label", { style: "width:100%; text-align:center; margin-top:10px;" }, "Contraste: ");
            this.contrastInput = domMake.Tree("input", {
                type: "range", min: -100, max: 100, value: 0, step: 1,
                style: "width:80%;", title: "Ajuste de Contraste"
            });
            const contrastValueSpan = domMake.Tree("span", { style: "width:10%; text-align:right;" }, "0");
            this.contrastInput.addEventListener('input', () => contrastValueSpan.textContent = this.contrastInput.value);


            controlsRow.appendAll(
                brightnessLabel, this.brightnessInput, brightnessValueSpan,
                contrastLabel, this.contrastInput, contrastValueSpan
            );
            section.appendChild(controlsRow);

            const buttonsRow = domMake.Row();
            buttonsRow.style.justifyContent = "center";
            buttonsRow.style.gap = "10px";
            buttonsRow.style.marginTop = "15px";

            this.applyBrightnessContrastButton = domMake.Button('Aplicar');
            this.applyBrightnessContrastButton.addEventListener('click', () => this.processBrightnessContrast());

            this.resetBrightnessContrastButton = domMake.Button('Reset');
            this.resetBrightnessContrastButton.addEventListener('click', () => {
                this.brightnessInput.value = 0;
                this.contrastInput.value = 0;
                brightnessValueSpan.textContent = "0";
                contrastValueSpan.textContent = "0";
                this.processBrightnessContrast(true); // Re-apply with reset values
            });

            buttonsRow.appendAll(this.applyBrightnessContrastButton, this.resetBrightnessContrastButton);
            section.appendChild(buttonsRow);
        }

        _loadInvertColorsUI() { // RENOMBRADO
            const section = this.invertColorsSection;
            section.appendChild(domMake.Tree("h5", { style: "text-align:center; margin-bottom:10px;" }, "Herramienta Invertir Colores"));

            const controlsRow = domMake.Row();
            controlsRow.style.flexFlow = "wrap";
            controlsRow.style.justifyContent = "center";
            controlsRow.style.gap = "10px";

            this.invertColorsButton = domMake.Button('<i class="fas fa-adjust"></i> Invertir Colores');
            this.invertColorsButton.addEventListener('click', () => this.processInvertColors());

            controlsRow.appendAll(this.invertColorsButton);
            section.appendChild(controlsRow);
        }

        _loadBlurUI() { // RENOMBRADO
            const section = this.blurSection;
            section.appendChild(domMake.Tree("h5", { style: "text-align:center; margin-bottom:10px;" }, "Herramienta Desenfoque"));

            const controlsRow = domMake.Row();
            controlsRow.style.flexFlow = "wrap";
            controlsRow.style.gap = "10px";
            controlsRow.style.justifyContent = "center";

            const blurLabel = domMake.Tree("label", { style: "width:100%; text-align:center;" }, "Radio de Desenfoque (píxeles): ");
            this.blurRadiusInput = domMake.Tree("input", {
                type: "range", min: 0, max: 10, value: 0, step: 1, // Radio pequeño para eficiencia (0-10px)
                style: "width:80%;", title: "Radio de Desenfoque"
            });
            const blurValueSpan = domMake.Tree("span", { style: "width:10%; text-align:right;" }, "0");
            this.blurRadiusInput.addEventListener('input', () => blurValueSpan.textContent = this.blurRadiusInput.value);

            controlsRow.appendAll(
                blurLabel, this.blurRadiusInput, blurValueSpan
            );
            section.appendChild(controlsRow);

            this.applyBlurButton = domMake.Button('Aplicar Desenfoque');
            this.applyBlurButton.addEventListener('click', () => this.processBlur());

            this.resetBlurButton = domMake.Button('Reset Desenfoque');
            this.resetBlurButton.addEventListener('click', () => {
                this.blurRadiusInput.value = 0;
                blurValueSpan.textContent = "0";
                this.processBlur(true); // Re-apply with reset value
            });

            const buttonsRow = domMake.Row();
            buttonsRow.style.justifyContent = "center";
            buttonsRow.style.gap = "10px";
            buttonsRow.style.marginTop = "15px";
            buttonsRow.appendAll(this.applyBlurButton, this.resetBlurButton);
            section.appendChild(buttonsRow);
        }

        _loadSharpenUI() { // RENOMBRADO
            const section = this.sharpenSection;
            section.appendChild(domMake.Tree("h5", { style: "text-align:center; margin-bottom:10px;" }, "Herramienta Nitidez"));

            const controlsRow = domMake.Row();
            controlsRow.style.flexFlow = "wrap";
            controlsRow.style.gap = "10px";
            controlsRow.style.justifyContent = "center";

            const sharpenLabel = domMake.Tree("label", { style: "width:100%; text-align:center;" }, "Cantidad de Nitidez: ");
            this.sharpenAmountInput = domMake.Tree("input", {
                type: "range", min: 0, max: 10, value: 0, step: 1,
                style: "width:80%;", title: "Cantidad de Nitidez"
            });
            const sharpenValueSpan = domMake.Tree("span", { style: "width:10%; text-align:right;" }, "0");
            this.sharpenAmountInput.addEventListener('input', () => sharpenValueSpan.textContent = this.sharpenAmountInput.value);

            controlsRow.appendAll(
                sharpenLabel, this.sharpenAmountInput, sharpenValueSpan
            );
            section.appendChild(controlsRow);

            this.applySharpenButton = domMake.Button('Aplicar Nitidez');
            this.applySharpenButton.addEventListener('click', () => this.processSharpen());

            this.resetSharpenButton = domMake.Button('Reset Nitidez');
            this.resetSharpenButton.addEventListener('click', () => {
                this.sharpenAmountInput.value = 0;
                sharpenValueSpan.textContent = "0";
                this.processSharpen(true); // Re-apply with reset value
            });

            const buttonsRow = domMake.Row();
            buttonsRow.style.justifyContent = "center";
            buttonsRow.style.gap = "10px";
            buttonsRow.style.marginTop = "15px";
            buttonsRow.appendAll(this.applySharpenButton, this.resetSharpenButton);
            section.appendChild(buttonsRow);
        }

        _loadSaturationUI() { // RENOMBRADO
            const section = this.saturationSection;
            section.appendChild(domMake.Tree("h5", { style: "text-align:center; margin-bottom:10px;" }, "Herramienta Saturación"));

            const controlsRow = domMake.Row();
            controlsRow.style.flexFlow = "wrap";
            controlsRow.style.gap = "10px";
            controlsRow.style.justifyContent = "center";

            const saturationLabel = domMake.Tree("label", { style: "width:100%; text-align:center;" }, "Nivel de Saturación: ");
            this.saturationInput = domMake.Tree("input", {
                type: "range", min: 0, max: 200, value: 100, step: 1, // 0% (blanco y negro) a 200% (doble saturación)
                style: "width:80%;", title: "Ajuste de Saturación"
            });
            const saturationValueSpan = domMake.Tree("span", { style: "width:10%; text-align:right;" }, "100%");
            this.saturationInput.addEventListener('input', () => saturationValueSpan.textContent = this.saturationInput.value + "%");

            controlsRow.appendAll(
                saturationLabel, this.saturationInput, saturationValueSpan
            );
            section.appendChild(controlsRow);

            this.applySaturationButton = domMake.Button('Aplicar Saturación');
            this.applySaturationButton.addEventListener('click', () => this.processSaturation());

            this.resetSaturationButton = domMake.Button('Reset Saturación');
            this.resetSaturationButton.addEventListener('click', () => {
                this.saturationInput.value = 100;
                saturationValueSpan.textContent = "100%";
                this.processSaturation(true); // Re-apply with reset value
            });

            const buttonsRow = domMake.Row();
            buttonsRow.style.justifyContent = "center";
            buttonsRow.style.gap = "10px";
            buttonsRow.style.marginTop = "15px";
            buttonsRow.appendAll(this.applySaturationButton, this.resetSaturationButton);
            section.appendChild(buttonsRow);
        }

        _loadSepiaUI() { // RENOMBRADO
            const section = this.sepiaSection;
            section.appendChild(domMake.Tree("h5", { style: "text-align:center; margin-bottom:10px;" }, "Herramienta Sepia"));

            const controlsRow = domMake.Row();
            controlsRow.style.flexFlow = "wrap";
            controlsRow.style.gap = "10px";
            controlsRow.style.justifyContent = "center";

            const sepiaLabel = domMake.Tree("label", { style: "width:100%; text-align:center;" }, "Intensidad Sepia (%): ");
            this.sepiaAmountInput = domMake.Tree("input", {
                type: "range", min: 0, max: 100, value: 100, step: 1,
                style: "width:80%;", title: "Intensidad Sepia"
            });
            const sepiaValueSpan = domMake.Tree("span", { style: "width:10%; text-align:right;" }, "100%");
            this.sepiaAmountInput.addEventListener('input', () => sepiaValueSpan.textContent = this.sepiaAmountInput.value + "%");

            controlsRow.appendAll(
                sepiaLabel, this.sepiaAmountInput, sepiaValueSpan
            );
            section.appendChild(controlsRow);

            this.applySepiaButton = domMake.Button('Aplicar Sepia');
            this.applySepiaButton.addEventListener('click', () => this.processSepia());

            this.resetSepiaButton = domMake.Button('Reset Sepia');
            this.resetSepiaButton.addEventListener('click', () => {
                this.sepiaAmountInput.value = 100;
                sepiaValueSpan.textContent = "100%";
                this.processSepia(true); // Re-apply with reset value
            });

            const buttonsRow = domMake.Row();
            buttonsRow.style.justifyContent = "center";
            buttonsRow.style.gap = "10px";
            buttonsRow.style.marginTop = "15px";
            buttonsRow.appendAll(this.applySepiaButton, this.resetSepiaButton);
            section.appendChild(buttonsRow);
        }

        _loadColorFilterUI() { // RENOMBRADO
            const section = this.colorFilterSection;
            section.appendChild(domMake.Tree("h5", { style: "text-align:center; margin-bottom:10px;" }, "Herramienta Filtro de Color"));

            const controlsRow = domMake.Row();
            controlsRow.style.flexFlow = "wrap";
            controlsRow.style.gap = "10px";
            controlsRow.style.justifyContent = "center";

            // R
            const rLabel = domMake.Tree("label", { style: "width:100%; text-align:center;" }, "Rojo: ");
            this.colorFilterRInput = domMake.Tree("input", { type: "range", min: 0, max: 255, value: 0, step: 1, style: "width:80%;" });
            const rValueSpan = domMake.Tree("span", { style: "width:10%; text-align:right;" }, "0");
            this.colorFilterRInput.addEventListener('input', () => { rValueSpan.textContent = this.colorFilterRInput.value; this._updateColorFilterPreview(); });

            // G
            const gLabel = domMake.Tree("label", { style: "width:100%; text-align:center; margin-top:10px;" }, "Verde: ");
            this.colorFilterGInput = domMake.Tree("input", { type: "range", min: 0, max: 255, value: 0, step: 1, style: "width:80%;" });
            const gValueSpan = domMake.Tree("span", { style: "width:10%; text-align:right;" }, "0");
            this.colorFilterGInput.addEventListener('input', () => { gValueSpan.textContent = this.colorFilterGInput.value; this._updateColorFilterPreview(); });

            // B
            const bLabel = domMake.Tree("label", { style: "width:100%; text-align:center; margin-top:10px;" }, "Azul: ");
            this.colorFilterBInput = domMake.Tree("input", { type: "range", min: 0, max: 255, value: 0, step: 1, style: "width:80%;" });
            const bValueSpan = domMake.Tree("span", { style: "width:10%; text-align:right;" }, "0");
            this.colorFilterBInput.addEventListener('input', () => { bValueSpan.textContent = this.colorFilterBInput.value; this._updateColorFilterPreview(); });

            // Intensidad de Mezcla
            const mixLabel = domMake.Tree("label", { style: "width:100%; text-align:center; margin-top:10px;" }, "Intensidad Mezcla (%): ");
            this.colorFilterMixInput = domMake.Tree("input", { type: "range", min: 0, max: 100, value: 50, step: 1, style: "width:80%;" });
            const mixValueSpan = domMake.Tree("span", { style: "width:10%; text-align:right;" }, "50%");
            this.colorFilterMixInput.addEventListener('input', () => mixValueSpan.textContent = this.colorFilterMixInput.value + "%");

            this.colorFilterColorBox = domMake.Tree("div", {
                style: "width:40px; height:40px; border:1px solid var(--CE-color); border-radius:4px; background-color:#CCC; flex-shrink:0; margin: 10px auto;"
            });

            controlsRow.appendAll(
                rLabel, this.colorFilterRInput, rValueSpan,
                gLabel, this.colorFilterGInput, gValueSpan,
                bLabel, this.colorFilterBInput, bValueSpan,
                mixLabel, this.colorFilterMixInput, mixValueSpan,
                this.colorFilterColorBox
            );
            section.appendChild(controlsRow);

            this.applyColorFilterButton = domMake.Button('Aplicar Filtro');
            this.applyColorFilterButton.addEventListener('click', () => this.processColorFilter());

            this.resetColorFilterButton = domMake.Button('Reset Filtro');
            this.resetColorFilterButton.addEventListener('click', () => {
                this.colorFilterRInput.value = 0; rValueSpan.textContent = "0";
                this.colorFilterGInput.value = 0; gValueSpan.textContent = "0";
                this.colorFilterBInput.value = 0; bValueSpan.textContent = "0";
                this.colorFilterMixInput.value = 50; mixValueSpan.textContent = "50%";
                this._updateColorFilterPreview();
                this.processColorFilter(true); // Re-apply with reset values
            });

            const buttonsRow = domMake.Row();
            buttonsRow.style.justifyContent = "center";
            buttonsRow.style.gap = "10px";
            buttonsRow.style.marginTop = "15px";
            buttonsRow.appendAll(this.applyColorFilterButton, this.resetColorFilterButton);
            section.appendChild(buttonsRow);
            this._updateColorFilterPreview(); // Set initial color preview
        }

        _loadThresholdUI() { // RENOMBRADO
            const section = this.thresholdSection;
            section.appendChild(domMake.Tree("h5", { style: "text-align:center; margin-bottom:10px;" }, "Herramienta Umbral (Binarizar)"));

            const controlsRow = domMake.Row();
            controlsRow.style.flexFlow = "wrap";
            controlsRow.style.gap = "10px";
            controlsRow.style.justifyContent = "center";

            const thresholdLabel = domMake.Tree("label", { style: "width:100%; text-align:center;" }, "Valor del Umbral (0-255): ");
            this.thresholdInput = domMake.Tree("input", {
                type: "range", min: 0, max: 255, value: 128, step: 1,
                style: "width:80%;", title: "Valor del Umbral"
            });
            const thresholdValueSpan = domMake.Tree("span", { style: "width:10%; text-align:right;" }, "128");
            this.thresholdInput.addEventListener('input', () => thresholdValueSpan.textContent = this.thresholdInput.value);

            controlsRow.appendAll(
                thresholdLabel, this.thresholdInput, thresholdValueSpan
            );
            section.appendChild(controlsRow);

            this.applyThresholdButton = domMake.Button('Aplicar Umbral');
            this.applyThresholdButton.addEventListener('click', () => this.processThreshold());

            this.resetThresholdButton = domMake.Button('Reset Umbral');
            this.resetThresholdButton.addEventListener('click', () => {
                this.thresholdInput.value = 128;
                thresholdValueSpan.textContent = "128";
                this.processThreshold(true); // Re-apply with reset value
            });

            const buttonsRow = domMake.Row();
            buttonsRow.style.justifyContent = "center";
            buttonsRow.style.gap = "10px";
            buttonsRow.style.marginTop = "15px";
            buttonsRow.appendAll(this.applyThresholdButton, this.resetThresholdButton);
            section.appendChild(buttonsRow);
        }

        _loadEdgeDetectionUI() { // RENOMBRADO
            const section = this.edgeDetectionSection;
            section.appendChild(domMake.Tree("h5", { style: "text-align:center; margin-bottom:10px;" }, "Herramienta Detección de Bordes"));

            const controlsRow = domMake.Row();
            controlsRow.style.flexFlow = "wrap";
            controlsRow.style.justifyContent = "center";
            controlsRow.style.gap = "10px";

            this.edgeDetectionButton = domMake.Button('<i class="fas fa-ruler-combined"></i> Aplicar Detección de Bordes');
            this.edgeDetectionButton.addEventListener('click', () => this.processEdgeDetection());

            controlsRow.appendAll(this.edgeDetectionButton);
            section.appendChild(controlsRow);
        }


        _toggleToolMode(mode) {
            // Ocultar todas las secciones de herramientas
            this.resizerSection.hidden = true;
            this.colorPickerSection.hidden = true;
            this.flipRotateSection.hidden = true;
            this.grayscaleSection.hidden = true;
            this.brightnessContrastSection.hidden = true;
            this.invertColorsSection.hidden = true;
            this.blurSection.hidden = true;
            this.sharpenSection.hidden = true;
            this.saturationSection.hidden = true;
            this.sepiaSection.hidden = true;
            this.colorFilterSection.hidden = true;
            this.thresholdSection.hidden = true;
            this.edgeDetectionSection.hidden = true;

            // Mostrar la sección correspondiente al modo
            if (mode === 'resizer') {
                this.resizerSection.hidden = false;
                this.isColorPickingMode = false;
            } else if (mode === 'colorPicker') {
                this.colorPickerSection.hidden = false;
                this.isColorPickingMode = true;
                this._clearPickedColor(); // Limpiar UI del color picker al cambiar a este modo
            } else if (mode === 'flipRotate') {
                this.flipRotateSection.hidden = false;
                this.isColorPickingMode = false;
            } else if (mode === 'grayscale') {
                this.grayscaleSection.hidden = false;
                this.isColorPickingMode = false;
                // Resetear select a default
                if (this.grayscaleTypeSelect) this.grayscaleTypeSelect.value = "luminance";
            } else if (mode === 'brightnessContrast') {
                this.brightnessContrastSection.hidden = false;
                this.isColorPickingMode = false;
                // Resetear sliders a 0 al entrar en el modo
                if (this.brightnessInput) this.brightnessInput.value = 0;
                if (this.contrastInput) this.contrastInput.value = 0;
                const brightnessValueSpan = this.brightnessInput?.nextElementSibling;
                if(brightnessValueSpan) brightnessValueSpan.textContent = "0";
                const contrastValueSpan = this.contrastInput?.nextElementSibling;
                if(contrastValueSpan) contrastValueSpan.textContent = "0";
            } else if (mode === 'invertColors') {
                this.invertColorsSection.hidden = false;
                this.isColorPickingMode = false;
            } else if (mode === 'blur') {
                this.blurSection.hidden = false;
                this.isColorPickingMode = false;
                // Resetear slider a 0 al entrar
                if (this.blurRadiusInput) this.blurRadiusInput.value = 0;
                const blurValueSpan = this.blurRadiusInput?.nextElementSibling;
                if(blurValueSpan) blurValueSpan.textContent = "0";
            } else if (mode === 'sharpen') {
                this.sharpenSection.hidden = false;
                this.isColorPickingMode = false;
                // Resetear slider a 0 al entrar
                if (this.sharpenAmountInput) this.sharpenAmountInput.value = 0;
                const sharpenValueSpan = this.sharpenAmountInput?.nextElementSibling;
                if(sharpenValueSpan) sharpenValueSpan.textContent = "0";
            } else if (mode === 'saturation') {
                this.saturationSection.hidden = false;
                this.isColorPickingMode = false;
                // Resetear slider a 100% al entrar
                if (this.saturationInput) this.saturationInput.value = 100;
                const saturationValueSpan = this.saturationInput?.nextElementSibling;
                if(saturationValueSpan) saturationValueSpan.textContent = "100%";
            } else if (mode === 'sepia') {
                this.sepiaSection.hidden = false;
                this.isColorPickingMode = false;
                // Resetear slider a 100% al entrar
                if (this.sepiaAmountInput) this.sepiaAmountInput.value = 100;
                const sepiaValueSpan = this.sepiaAmountInput?.nextElementSibling;
                if(sepiaValueSpan) sepiaValueSpan.textContent = "100%";
            } else if (mode === 'colorFilter') {
                this.colorFilterSection.hidden = false;
                this.isColorPickingMode = false;
                // Resetear sliders y preview
                if (this.colorFilterRInput) this.colorFilterRInput.value = 0;
                if (this.colorFilterGInput) this.colorFilterGInput.value = 0;
                if (this.colorFilterBInput) this.colorFilterBInput.value = 0;
                if (this.colorFilterMixInput) this.colorFilterMixInput.value = 50;
                const rSpan = this.colorFilterRInput?.nextElementSibling; if(rSpan) rSpan.textContent = "0";
                const gSpan = this.colorFilterGInput?.nextElementSibling; if(gSpan) gSpan.textContent = "0";
                const bSpan = this.colorFilterBInput?.nextElementSibling; if(bSpan) bSpan.textContent = "0";
                const mixSpan = this.colorFilterMixInput?.nextElementSibling; if(mixSpan) mixSpan.textContent = "50%";
                this._updateColorFilterPreview();
            } else if (mode === 'threshold') {
                this.thresholdSection.hidden = false;
                this.isColorPickingMode = false;
                // Resetear slider a 128
                if (this.thresholdInput) this.thresholdInput.value = 128;
                const thresholdValueSpan = this.thresholdInput?.nextElementSibling;
                if(thresholdValueSpan) thresholdValueSpan.textContent = "128";
            } else if (mode === 'edgeDetection') {
                this.edgeDetectionSection.hidden = false;
                this.isColorPickingMode = false;
            }

            this._updateToolButtonsState(); // Actualizar estado de botones
        }

        _updateToolButtonsState() {
            const hasLoadedImage = this.currentImageBase64 !== null;

            // Botones del redimensionador
            if (this.resizeProcessButton) {
                this.resizeProcessButton.disabled = !hasLoadedImage;
            }
            // Botones de voltear/rotar
            if (this.flipHButton) this.flipHButton.disabled = !hasLoadedImage;
            if (this.flipVButton) this.flipVButton.disabled = !hasLoadedImage;
            if (this.rotateCWButton) this.rotateCWButton.disabled = !hasLoadedImage;
            if (this.rotateCCWButton) this.rotateCCWButton.disabled = !hasLoadedImage;
            // Botones de escala de grises
            if (this.applyGrayscaleButton) this.applyGrayscaleButton.disabled = !hasLoadedImage;
            if (this.resetGrayscaleButton) this.resetGrayscaleButton.disabled = !hasLoadedImage;
            // Botones de brillo/contraste
            if (this.applyBrightnessContrastButton) this.applyBrightnessContrastButton.disabled = !hasLoadedImage;
            if (this.resetBrightnessContrastButton) this.resetBrightnessContrastButton.disabled = !hasLoadedImage;
            // Botón de invertir colores
            if (this.invertColorsButton) this.invertColorsButton.disabled = !hasLoadedImage;
            // Botones de desenfoque
            if (this.applyBlurButton) this.applyBlurButton.disabled = !hasLoadedImage;
            if (this.resetBlurButton) this.resetBlurButton.disabled = !hasLoadedImage;
            // Botones de nitidez
            if (this.applySharpenButton) this.applySharpenButton.disabled = !hasLoadedImage;
            if (this.resetSharpenButton) this.resetSharpenButton.disabled = !hasLoadedImage;
            // Botones de saturación
            if (this.applySaturationButton) this.applySaturationButton.disabled = !hasLoadedImage;
            if (this.resetSaturationButton) this.resetSaturationButton.disabled = !hasLoadedImage;
            // Botones de sepia
            if (this.applySepiaButton) this.applySepiaButton.disabled = !hasLoadedImage;
            if (this.resetSepiaButton) this.resetSepiaButton.disabled = !hasLoadedImage;
            // Botones de filtro de color
            if (this.applyColorFilterButton) this.applyColorFilterButton.disabled = !hasLoadedImage;
            if (this.resetColorFilterButton) this.resetColorFilterButton.disabled = !hasLoadedImage;
            // Botones de umbral
            if (this.applyThresholdButton) this.applyThresholdButton.disabled = !hasLoadedImage;
            if (this.resetThresholdButton) this.resetThresholdButton.disabled = !hasLoadedImage;
            // Botón de detección de bordes
            if (this.edgeDetectionButton) this.edgeDetectionButton.disabled = !hasLoadedImage;

            // Los botones de copia de color se habilitan al seleccionar un color, no solo al cargar la imagen.
        }

        async _loadImageFromURL(imageUrl) {
            if (!imageUrl) {
                this.notify("warning", "Por favor, pega un link de imagen.");
                return;
            }

            this.loadingIndicator.style.display = 'block';
            this.notify("info", "Cargando imagen...");
            this._updateToolButtonsState(); // Desactivar botones de procesamiento
            this.currentImageBase64 = null;
            this.originalImageBase64OnLoad = null;
            if (this.resizePreviewContainer) {
                this.resizePreviewContainer.innerHTML = "";
            }


            const img = new Image();
            img.crossOrigin = "anonymous";

            const handleImageLoaded = () => {
                this.originalWidth = img.naturalWidth;
                this.originalHeight = img.naturalHeight;

                this.interactiveCanvas.width = img.naturalWidth;
                this.interactiveCanvas.height = img.naturalHeight;
                const ctx = this.interactiveCanvas.getContext('2d');
                ctx.clearRect(0, 0, this.interactiveCanvas.width, this.interactiveCanvas.height);
                ctx.drawImage(img, 0, 0);

                try {
                    this.currentImageBase64 = this.interactiveCanvas.toDataURL('image/png');
                    this.originalImageBase64OnLoad = this.currentImageBase64;
                    this.notify("success", "Imagen cargada y lista.");
                } catch (e) {
                    console.error("Error al obtener Base64 desde el canvas (Tainted Canvas inesperado):", e);
                    this.notify("error", "Error interno al procesar la imagen. La imagen está visible pero no es accesible para las herramientas (Tainted Canvas).");
                    this.currentImageBase64 = null;
                    this.originalImageBase64OnLoad = null;
                    this.interactiveCanvas.getContext('2d').clearRect(0,0,this.interactiveCanvas.width,this.interactiveCanvas.height);
                }

                this.loadingIndicator.style.display = 'none';
                this._updateToolButtonsState();
            };

            const handleImageError = (msg, errorObj = null) => {
                this.loadingIndicator.style.display = 'none';
                this.notify("error", msg);
                if (errorObj) console.error("Image loading error:", errorObj);
                this.currentImageBase64 = null;
                this.originalImageBase64OnLoad = null;
                this._updateToolButtonsState();
                this.interactiveCanvas.getContext('2d').clearRect(0,0,this.interactiveCanvas.width,this.interactiveCanvas.height);
            };

            if (typeof GM_xmlhttpRequest !== 'undefined') {
                GM_xmlhttpRequest({
                    method: "GET",
                    url: imageUrl,
                    responseType: "blob",
                    onload: (res) => {
                        if (res.status === 200) {
                            const reader = new FileReader();
                            reader.onloadend = (e) => {
                                img.onload = handleImageLoaded;
                                img.onerror = (imgErr) => handleImageError("No se pudo cargar la imagen desde el blob (archivo corrupto o formato no soportado).", imgErr);
                                img.src = e.target.result;
                            };
                            reader.onerror = (readerErr) => handleImageError("Error al leer los datos de la imagen (blob).", readerErr);
                            reader.readAsDataURL(res.response);
                        } else {
                            handleImageError(`Fallo al cargar URL via GM_xmlhttpRequest: Status ${res.status} - ${res.statusText || 'Error desconocido'}.`);
                        }
                    },
                    onerror: (err) => {
                        handleImageError(`Error de red con GM_xmlhttpRequest. No se pudo cargar la imagen: ${err.statusText || JSON.stringify(err)}`);
                    }
                });
            } else {
                handleImageError("GM_xmlhttpRequest no está disponible. Asegúrate que `@grant GM_xmlhttpRequest` está en el encabezado de tu userscript. La carga de imágenes de galería puede fallar.");
            }
        }

        // --- Lógica de Procesamiento de Imagen (base para todas las transformaciones) ---
        async _processImageTransform(transformFunction, notificationMsg, isReset = false) {
            if (!this.originalImageBase64OnLoad) {
                this.notify("warning", "Primero carga una imagen.");
                return;
            }

            this.loadingIndicator.style.display = 'block';
            this.notify("info", notificationMsg);
            this._updateToolButtonsState();

            const img = new Image();
            img.src = isReset ? this.originalImageBase64OnLoad : this.currentImageBase64;
            img.onload = () => {
                const canvas = document.createElement('canvas');
                const ctx = canvas.getContext('2d');
                canvas.width = this.originalWidth;
                canvas.height = this.originalHeight;

                ctx.drawImage(img, 0, 0);

                transformFunction(ctx, canvas.width, canvas.height, img); // Pasar 'img' para acceso a sus dimensiones originales en rotación

                this.currentImageBase64 = canvas.toDataURL('image/png');
                this.interactiveCanvas.width = this.originalWidth; // Resetear el interactiveCanvas a las dimensiones originales
                this.interactiveCanvas.height = this.originalHeight; // antes de dibujar
                this.interactiveCanvas.getContext('2d').drawImage(canvas, 0, 0);

                this.notify("success", notificationMsg + " completado.");
                this.loadingIndicator.style.display = 'none';
                this._updateToolButtonsState();
            };
            img.onerror = (e) => {
                console.error(`Error al cargar imagen para ${notificationMsg}:`, e);
                this.notify("error", `Error al aplicar ${notificationMsg}. Recarga la imagen.`);
                this.loadingIndicator.style.display = 'none';
                this._updateToolButtonsState();
            };
        }

        // --- Lógica del Redimensionador ---
        processResize() {
            if (!this.currentImageBase64) {
                this.notify("warning", "Primero carga una imagen para usar el redimensionador.");
                return;
            }

            this.loadingIndicator.style.display = 'block';
            this.resizeProcessButton.disabled = true;
            if (this.resizePreviewContainer) {
                this.resizePreviewContainer.innerHTML = "";
            }
            this.notify("info", "Procesando redimensionamiento...");

            const img = new Image();
            img.src = this.currentImageBase64;
            img.onload = () => {
                const canvas = document.createElement('canvas');
                const ctx = canvas.getContext('2d');

                let targetWidth = this.originalWidth;
                let targetHeight = this.originalHeight;

                const scale = parseFloat(this.scaleInput.value);
                const fixedWidth = parseInt(this.widthInput.value, 10);
                const fixedHeight = parseInt(this.heightInput.value, 10);

                if (!isNaN(scale) && scale > 0) {
                    targetWidth = this.originalWidth * (scale / 100);
                    targetHeight = this.originalHeight * (scale / 100);
                } else if (!isNaN(fixedWidth) && fixedWidth > 0) {
                    targetWidth = fixedWidth;
                    targetHeight = this.originalHeight * (fixedWidth / this.originalWidth);
                } else if (!isNaN(fixedHeight) && fixedHeight > 0) {
                    targetHeight = fixedHeight;
                    targetWidth = this.originalWidth * (fixedHeight / this.originalHeight);
                }

                targetWidth = Math.max(1, Math.round(targetWidth));
                targetHeight = Math.max(1, Math.round(targetHeight));

                canvas.width = targetWidth;
                canvas.height = targetHeight;

                ctx.drawImage(img, 0, 0, targetWidth, targetHeight);

                const previewImg = domMake.Tree("img", {
                    src: canvas.toDataURL('image/png'),
                    alt: "Imagen Redimensionada",
                    style: "max-width:100%; height:auto; display:block; margin: 0 auto; border: 1px dashed var(--CE-color);",
                });
                if (this.resizePreviewContainer) {
                    this.resizePreviewContainer.appendChild(previewImg);
                }

                const downloadLink = domMake.Tree("a", {
                    href: canvas.toDataURL('image/png'),
                    download: `resized_image_${targetWidth}x${targetHeight}.png`,
                    class: "btn btn-outline-secondary",
                    style: "margin-top: 10px;",
                }, "Descargar Imagen Redimensionada");
                if (this.resizePreviewContainer) {
                    this.resizePreviewContainer.appendChild(downloadLink);
                }

                this.notify("success", `Imagen redimensionada a ${targetWidth}x${targetHeight}.`);
                this.loadingIndicator.style.display = 'none';
                this._updateToolButtonsState();
            };
            img.onerror = (e) => {
                console.error("Error al cargar la imagen para redimensionar (executeResizeProcess):", e);
                this.notify("error", "Error interno al redimensionar la imagen. Intenta cargarla de nuevo.");
                this.loadingIndicator.style.display = 'none';
                this._updateToolButtonsState();
            };
        }

        // --- Lógica Voltear/Rotar ---
        async processFlipRotate(transformType) {
            this._processImageTransform((ctx, width, height, imgRef) => { // imgRef es la imagen original
                let newWidth = this.originalWidth; // Estos son los anchos/altos del canvas actual (antes de rotar)
                let newHeight = this.originalHeight;

                ctx.setTransform(1, 0, 0, 1, 0, 0); // Reset transform
                ctx.clearRect(0, 0, ctx.canvas.width, ctx.canvas.height); // Limpiar con las viejas dimensiones


                if (transformType === 'rotateCW' || transformType === 'rotateCCW') {
                    // Para rotaciones, el ancho y alto del canvas cambian
                    newWidth = this.originalHeight; // El nuevo ancho es el alto original
                    newHeight = this.originalWidth; // El nuevo alto es el ancho original
                    ctx.canvas.width = newWidth;
                    ctx.canvas.height = newHeight;
                    ctx.translate(ctx.canvas.width / 2, ctx.canvas.height / 2); // Trasladar al centro del nuevo canvas
                    ctx.rotate(transformType === 'rotateCW' ? Math.PI / 2 : -Math.PI / 2); // Rotar
                    ctx.drawImage(imgRef, -this.originalWidth / 2, -this.originalHeight / 2); // Dibujar desde el centro de la imagen original
                } else if (transformType === 'horizontal') {
                    ctx.canvas.width = newWidth;
                    ctx.canvas.height = newHeight;
                    ctx.translate(ctx.canvas.width, 0);
                    ctx.scale(-1, 1);
                    ctx.drawImage(imgRef, 0, 0);
                } else if (transformType === 'vertical') {
                    ctx.canvas.width = newWidth;
                    ctx.canvas.height = newHeight;
                    ctx.translate(0, ctx.canvas.height);
                    ctx.scale(1, -1);
                    ctx.drawImage(imgRef, 0, 0);
                } else {
                    ctx.canvas.width = newWidth;
                    ctx.canvas.height = newHeight;
                    ctx.drawImage(imgRef, 0, 0);
                }
                // Actualizar las dimensiones originales para el próximo procesamiento
                this.originalWidth = newWidth;
                this.originalHeight = newHeight;

            }, `Aplicando transformación de volteo/rotación.`, false); // No es un reset de filtro, es una transformación en sí misma
        }


        // --- Lógica Escala de Grises (MEJORADO) ---
        async processGrayscale(isReset = false) {
            this._processImageTransform((ctx, width, height) => {
                if (isReset) return;

                const imageData = ctx.getImageData(0, 0, width, height);
                const data = imageData.data;
                const type = this.grayscaleTypeSelect.value;

                for (let i = 0; i < data.length; i += 4) {
                    const r = data[i];
                    const g = data[i + 1];
                    const b = data[i + 2];
                    let avg;

                    if (type === 'luminance') {
                        avg = (r * 0.299 + g * 0.587 + b * 0.114);
                    } else { // 'average'
                        avg = (r + g + b) / 3;
                    }
                    data[i] = avg;
                    data[i + 1] = avg;
                    data[i + 2] = avg;
                }
                ctx.putImageData(imageData, 0, 0);
            }, `Aplicando escala de grises (${this.grayscaleTypeSelect?.value || 'luminancia'}).`, isReset);
        }

        // --- Lógica de Brillo/Contraste ---
        async processBrightnessContrast(isReset = false) {
            this._processImageTransform((ctx, width, height) => {
                if (isReset) return;

                const brightness = parseFloat(this.brightnessInput.value);
                const contrast = parseFloat(this.contrastInput.value);

                const imageData = ctx.getImageData(0, 0, width, height);
                const data = imageData.data;

                const brightnessFactor = brightness / 100;
                const contrastFactor = (contrast + 100) / 100;
                const intercept = 128 * (1 - contrastFactor);

                for (let i = 0; i < data.length; i += 4) {
                    data[i] = data[i] + (brightnessFactor * 255);
                    data[i + 1] = data[i + 1] + (brightnessFactor * 255);
                    data[i + 2] = data[i + 2] + (brightnessFactor * 255);

                    data[i] = (data[i] * contrastFactor) + intercept;
                    data[i + 1] = (data[i + 1] * contrastFactor) + intercept;
                    data[i + 2] = (data[i + 2] * contrastFactor) + intercept;

                    data[i] = Math.max(0, Math.min(255, data[i]));
                    data[i + 1] = Math.max(0, Math.min(255, data[i + 1]));
                    data[i + 2] = Math.max(0, Math.min(255, data[i + 2]));
                }
                ctx.putImageData(imageData, 0, 0);
            }, `Aplicando brillo (${this.brightnessInput?.value}) y contraste (${this.contrastInput?.value}).`, isReset);
        }

        // --- Lógica Invertir Colores ---
        async processInvertColors() {
             this._processImageTransform((ctx, width, height) => {
                const imageData = ctx.getImageData(0, 0, width, height);
                const data = imageData.data;

                for (let i = 0; i < data.length; i += 4) {
                    data[i] = 255 - data[i];
                    data[i + 1] = 255 - data[i + 1];
                    data[i + 2] = 255 - data[i + 2];
                }
                ctx.putImageData(imageData, 0, 0);
            }, "Invirtiendo colores.");
        }

        // --- Lógica de Desenfoque (Box Blur) ---
        async processBlur(isReset = false) {
            this._processImageTransform((ctx, width, height) => {
                const radius = isReset ? 0 : parseInt(this.blurRadiusInput.value, 10);
                if (radius === 0) return;

                const imageData = ctx.getImageData(0, 0, width, height);
                const pixels = imageData.data;
                const blurredPixels = new Uint8ClampedArray(pixels.length);

                for (let y = 0; y < height; y++) {
                    for (let x = 0; x < width; x++) {
                        let rSum = 0, gSum = 0, bSum = 0, count = 0;
                        for (let dy = -radius; dy <= radius; dy++) {
                            for (let dx = -radius; dx <= radius; dx++) {
                                const nx = x + dx;
                                const ny = y + dy;
                                if (nx >= 0 && nx < width && ny >= 0 && ny < height) {
                                    const index = (ny * width + nx) * 4;
                                    rSum += pixels[index]; gSum += pixels[index + 1]; bSum += pixels[index + 2];
                                    count++;
                                }
                            }
                        }
                        const outputIndex = (y * width + x) * 4;
                        blurredPixels[outputIndex] = rSum / count;
                        blurredPixels[outputIndex + 1] = gSum / count;
                        blurredPixels[outputIndex + 2] = bSum / count;
                        blurredPixels[outputIndex + 3] = pixels[outputIndex + 3];
                    }
                }
                imageData.data.set(blurredPixels);
                ctx.putImageData(imageData, 0, 0);
            }, `Aplicando desenfoque (Radio: ${this.blurRadiusInput?.value || 0}).`, isReset);
        }

        // --- Lógica de Nitidez (Máscara de Enfoque Simple, MEJORADO) ---
        async processSharpen(isReset = false) {
            this._processImageTransform((ctx, width, height) => {
                const amount = isReset ? 0 : parseInt(this.sharpenAmountInput.value, 10);
                if (amount === 0) return;

                const imageData = ctx.getImageData(0, 0, width, height);
                const pixels = imageData.data;
                const sharpenedPixels = new Uint8ClampedArray(pixels.length);

                const kernelFactor = amount;
                const kernel = [
                    0, -kernelFactor, 0,
                    -kernelFactor, 1 + 4 * kernelFactor, -kernelFactor,
                    0, -kernelFactor, 0
                ];

                const kernelSize = Math.sqrt(kernel.length);
                const halfKernel = Math.floor(kernelSize / 2);

                for (let y = 0; y < height; y++) {
                    for (let x = 0; x < width; x++) {
                        let rSum = 0, gSum = 0, bSum = 0;

                        for (let ky = 0; ky < kernelSize; ky++) {
                            for (let kx = 0; kx < kernelSize; kx++) {
                                const px = x + kx - halfKernel;
                                const py = y + ky - halfKernel;

                                if (px >= 0 && px < width && py >= 0 && py < height) {
                                    const index = (py * width + px) * 4;
                                    const kernelValue = kernel[ky * kernelSize + kx];

                                    rSum += pixels[index] * kernelValue;
                                    gSum += pixels[index + 1] * kernelValue;
                                    bSum += pixels[index + 2] * kernelValue;
                                }
                            }
                        }

                        const outputIndex = (y * width + x) * 4;
                        sharpenedPixels[outputIndex] = Math.max(0, Math.min(255, rSum));
                        sharpenedPixels[outputIndex + 1] = Math.max(0, Math.min(255, gSum));
                        sharpenedPixels[outputIndex + 2] = Math.max(0, Math.min(255, bSum));
                        sharpenedPixels[outputIndex + 3] = pixels[outputIndex + 3];
                    }
                }
                imageData.data.set(sharpenedPixels);
                ctx.putImageData(imageData, 0, 0);
            }, `Aplicando nitidez (Cantidad: ${this.sharpenAmountInput?.value || 0}).`, isReset);
        }

        // --- Lógica de Saturación ---
        async processSaturation(isReset = false) {
            this._processImageTransform((ctx, width, height) => {
                const saturation = isReset ? 100 : parseFloat(this.saturationInput.value);
                if (saturation === 100 && !isReset) return;

                const imageData = ctx.getImageData(0, 0, width, height);
                const data = imageData.data;

                const R = 0.299;
                const G = 0.587;
                const B = 0.114;
                const S = saturation / 100;

                for (let i = 0; i < data.length; i += 4) {
                    const r = data[i];
                    const g = data[i + 1];
                    const b = data[i + 2];

                    const gray = R * r + G * g + B * b;

                    data[i] = Math.max(0, Math.min(255, gray + (r - gray) * S));
                    data[i + 1] = Math.max(0, Math.min(255, gray + (g - gray) * S));
                    data[i + 2] = Math.max(0, Math.min(255, gray + (b - gray) * S));
                }
                ctx.putImageData(imageData, 0, 0);
            }, `Aplicando saturación (${this.saturationInput?.value || 100}%).`, isReset);
        }

        // --- Lógica de Sepia (MEJORADO) ---
        async processSepia(isReset = false) {
            this._processImageTransform((ctx, width, height) => {
                const sepiaAmount = isReset ? 100 : parseFloat(this.sepiaAmountInput.value);
                if (sepiaAmount === 0 && !isReset) return;

                const imageData = ctx.getImageData(0, 0, width, height);
                const data = imageData.data;

                const amountFactor = sepiaAmount / 100;

                for (let i = 0; i < data.length; i += 4) {
                    const r = data[i];
                    const g = data[i + 1];
                    const b = data[i + 2];

                    const gray = (r * 0.299 + g * 0.587 + b * 0.114);

                    let sr = Math.min(255, (gray * 0.393) + (gray * 0.769) + (gray * 0.189));
                    let sg = Math.min(255, (gray * 0.349) + (gray * 0.686) + (gray * 0.168));
                    let sb = Math.min(255, (gray * 0.272) + (gray * 0.534) + (gray * 0.131));

                    // Interpolar entre el original y el sepia
                    data[i] = r + (sr - r) * amountFactor;
                    data[i + 1] = g + (sg - g) * amountFactor;
                    data[i + 2] = b + (sb - b) * amountFactor;

                    data[i] = Math.max(0, Math.min(255, data[i]));
                    data[i + 1] = Math.max(0, Math.min(255, data[i + 1]));
                    data[i + 2] = Math.max(0, Math.min(255, data[i + 2]));
                }
                ctx.putImageData(imageData, 0, 0);
            }, `Aplicando efecto sepia (${this.sepiaAmountInput?.value || 100}%).`, isReset);
        }

        // NUEVO: Lógica de Filtro de Color
        async processColorFilter(isReset = false) {
            this._processImageTransform((ctx, width, height) => {
                const targetR = isReset ? 0 : parseInt(this.colorFilterRInput.value, 10);
                const targetG = isReset ? 0 : parseInt(this.colorFilterGInput.value, 10);
                const targetB = isReset ? 0 : parseInt(this.colorFilterBInput.value, 10);
                const mixAmount = isReset ? 50 : parseInt(this.colorFilterMixInput.value, 10); // 0-100

                const imageData = ctx.getImageData(0, 0, width, height);
                const data = imageData.data;

                const mixFactor = mixAmount / 100;

                for (let i = 0; i < data.length; i += 4) {
                    const r = data[i];
                    const g = data[i + 1];
                    const b = data[i + 2];

                    data[i] = r + (targetR - r) * mixFactor;
                    data[i + 1] = g + (targetG - g) * mixFactor;
                    data[i + 2] = b + (targetB - b) * mixFactor;

                    data[i] = Math.max(0, Math.min(255, data[i]));
                    data[i + 1] = Math.max(0, Math.min(255, data[i + 1]));
                    data[i + 2] = Math.max(0, Math.min(255, data[i + 2]));
                }
                ctx.putImageData(imageData, 0, 0);
            }, `Aplicando filtro de color (R:${this.colorFilterRInput?.value}, G:${this.colorFilterGInput?.value}, B:${this.colorFilterBInput?.value}, Mezcla:${this.colorFilterMixInput?.value}%).`, isReset);
        }

        // Helper para previsualizar color en UI de Filtro de Color
        _updateColorFilterPreview() {
            if (!this.colorFilterColorBox || !this.colorFilterRInput) return;

            const r = parseInt(this.colorFilterRInput.value, 10);
            const g = parseInt(this.colorFilterGInput.value, 10);
            const b = parseInt(this.colorFilterBInput.value, 10);
            const hex = rgbToHex(r, g, b);
            this.colorFilterColorBox.style.backgroundColor = hex;
        }

        // NUEVO: Lógica de Umbral (Binarización)
        async processThreshold(isReset = false) {
            this._processImageTransform((ctx, width, height) => {
                const thresholdValue = isReset ? 128 : parseInt(this.thresholdInput.value, 10);

                const imageData = ctx.getImageData(0, 0, width, height);
                const data = imageData.data;

                for (let i = 0; i < data.length; i += 4) {
                    const r = data[i];
                    const g = data[i + 1];
                    const b = data[i + 2];
                    const avg = (r * 0.299 + g * 0.587 + b * 0.114);

                    const color = avg >= thresholdValue ? 255 : 0;
                    data[i] = color;
                    data[i + 1] = color;
                    data[i + 2] = color;
                }
                ctx.putImageData(imageData, 0, 0);
            }, `Aplicando umbral (Valor: ${this.thresholdInput?.value || 128}).`, isReset);
        }

        // NUEVO: Lógica de Detección de Bordes (Simple Prewitt/Sobel like)
        async processEdgeDetection() {
            this._processImageTransform((ctx, width, height) => {
                const imageData = ctx.getImageData(0, 0, width, height);
                const pixels = imageData.data;
                const outputPixels = new Uint8ClampedArray(pixels.length);

                const grayPixels = new Uint8ClampedArray(pixels.length / 4);
                for (let i = 0; i < pixels.length; i += 4) {
                    grayPixels[i / 4] = (pixels[i] * 0.299 + pixels[i + 1] * 0.587 + pixels[i + 2] * 0.114);
                }

                const sobelX = [
                    -1, 0, 1,
                    -2, 0, 2,
                    -1, 0, 1
                ];
                const sobelY = [
                    -1, -2, -1,
                    0, 0, 0,
                    1, 2, 1
                ];

                for (let y = 1; y < height - 1; y++) {
                    for (let x = 1; x < width - 1; x++) {
                        let Gx = 0;
                        let Gy = 0;

                        for (let ky = 0; ky < 3; ky++) {
                            for (let kx = 0; kx < 3; kx++) {
                                const px = x + kx - 1;
                                const py = y + ky - 1;
                                const index = (py * width + px);
                                const kernelIndex = ky * 3 + kx;

                                Gx += grayPixels[index] * sobelX[kernelIndex];
                                Gy += grayPixels[index] * sobelY[kernelIndex];
                            }
                        }

                        const magnitude = Math.sqrt(Gx * Gx + Gy * Gy);
                        const edgeValue = 255 - Math.min(255, Math.max(0, magnitude));

                        const outputIndex = (y * width + x) * 4;
                        outputPixels[outputIndex] = edgeValue;
                        outputPixels[outputIndex + 1] = edgeValue;
                        outputPixels[outputIndex + 2] = edgeValue;
                        outputPixels[outputIndex + 3] = pixels[outputIndex + 3];
                    }
                }

                for (let i = 0; i < pixels.length; i += 4) {
                    const x = (i / 4) % width;
                    const y = Math.floor((i / 4) / width);
                    if (x === 0 || x === width - 1 || y === 0 || y === height - 1) {
                        outputPixels[i] = 0;
                        outputPixels[i + 1] = 0;
                        outputPixels[i + 2] = 0;
                        outputPixels[i + 3] = pixels[i + 3];
                    }
                }

                imageData.data.set(outputPixels);
                ctx.putImageData(imageData, 0, 0);
            }, "Aplicando detección de bordes.");
        }


        // --- Lógica del Selector de Color (sin cambios) ---
        _pickColor(event) {
            if (!this.currentImageBase64) {
                this.notify("warning", "Primero carga una imagen para usar el selector de color.");
                return;
            }
            if (!this.isColorPickingMode) return;

            const canvas = this.interactiveCanvas;
            const ctx = canvas.getContext('2d');

            const rect = canvas.getBoundingClientRect();
            const x = event.clientX - rect.left;
            const y = event.clientY - rect.top;

            const scaleX = canvas.width / rect.width;
            const scaleY = canvas.height / rect.height;
            const pixelX = Math.floor(x * scaleX);
            const pixelY = Math.floor(y * scaleY);

            if (pixelX < 0 || pixelX >= canvas.width || pixelY < 0 || pixelY >= canvas.height) {
                this._clearPickedColor();
                return;
            }

            const pixel = ctx.getImageData(pixelX, pixelY, 1, 1).data;
            const r = pixel[0];
            const g = pixel[1];
            const b = pixel[2];
            const a = pixel[3];

            if (a === 0) {
                this.notify("info", "Píxel transparente. No hay color que seleccionar.");
                this._clearPickedColor();
                return;
            }

            const hex = rgbToHex(r, g, b);
            if (this.pickedColorBox) this.pickedColorBox.style.backgroundColor = hex;
            if (this.pickedColorHex) this.pickedColorHex.textContent = `HEX: ${hex}`;
            if (this.pickedColorRgb) this.pickedColorRgb.textContent = `RGB: ${r}, ${g}, ${b}`;
            if (this.copyColorButton) this.copyColorButton.disabled = false;

            this.notify("success", `Color seleccionado: ${hex}`);
        }

        _previewColor(event) {
            if (!this.currentImageBase64 || !this.isColorPickingMode) {
                this._clearPreviewColor();
                return;
            }

            const canvas = this.interactiveCanvas;
            const ctx = canvas.getContext('2d');

            const rect = canvas.getBoundingClientRect();
            const x = event.clientX - rect.left;
            const y = event.clientY - rect.top;

            const scaleX = canvas.width / rect.width;
            const scaleY = canvas.height / rect.height;
            const pixelX = Math.floor(x * scaleX);
            const pixelY = Math.floor(y * scaleY);

            if (pixelX < 0 || pixelX >= canvas.width || pixelY < 0 || pixelY >= canvas.height) {
                this._clearPreviewColor();
                return;
            }

            const pixel = ctx.getImageData(pixelX, pixelY, 1, 1).data;
            const r = pixel[0];
            const g = pixel[1];
            const b = pixel[2];
            const a = pixel[3];

            if (a === 0) {
                this._clearPreviewColor();
                return;
            }

            const hex = rgbToHex(r, g, b);
            if (this.pickedColorBox) this.pickedColorBox.style.backgroundColor = hex;
            if (this.pickedColorHex) this.pickedColorHex.textContent = `HEX: ${hex}`;
            if (this.pickedColorRgb) this.pickedColorRgb.textContent = `RGB: ${r}, ${g}, ${b}`;
            if (this.copyColorButton) this.copyColorButton.disabled = false;
        }

        _clearPreviewColor() {
            if (!this.isColorPickingMode || !this.currentImageBase64) {
                this._clearPickedColor();
            }
        }

        _clearPickedColor() {
            if (this.pickedColorBox) this.pickedColorBox.style.backgroundColor = '#CCC';
            if (this.pickedColorHex) this.pickedColorHex.textContent = 'HEX: -';
            if (this.pickedColorRgb) this.pickedColorRgb.textContent = 'RGB: -';
            if (this.copyColorButton) this.copyColorButton.disabled = true;
        }

        _copyPickedColor() {
            const hex = this.pickedColorHex.textContent.replace('HEX: ', '');
            if (hex && hex !== '-') {
                navigator.clipboard.writeText(hex)
                    .then(() => this.notify("success", `Color ${hex} copiado al portapapeles!`))
                    .catch(err => {
                        this.notify("error", "Error al copiar el color.");
                        console.error("Copy color error:", err);
                    });
            } else {
                this.notify("warning", "No hay color para copiar.");
            }
        }
    }
})();

// END IMAGETOOLS


// --- START ANALIZER
(function ImageAnalyzerModule() {
    const QBit = globalThis[arguments[0]];

    QBit.Styles.addRules([
        `#image-analyzer-container {
            display: flex;
            flex-direction: column;
            gap: 10px;
            padding: 5px;
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            background-color: var(--CE-bg_color);
        }`,
        `#image-analyzer-container input[type="text"],
         #image-analyzer-container button {
            width: 100%;
            padding: 5px;
            box-sizing: border-box;
         }`,
        `#image-preview-canvas {
            border: 1px dashed var(--CE-color);
            max-width: 100%;
            height: auto;
            display: block;
            margin: 5px auto;
        }`,
        `#dominant-colors-display {
            display: flex;
            flex-wrap: wrap;
            gap: 5px;
            margin-top: 5px;
        }`,
        `#dominant-colors-display .color-swatch {
            width: 30px;
            height: 30px;
            border: 1px solid #ccc;
            border-radius: 3px;
            cursor: pointer;
            box-shadow: 0 0 3px rgba(0,0,0,0.2);
        }`,
        `#analysis-results {
            background-color: var(--CE-bg_color);
            border: 1px solid var(--CE-color);
            padding: 8px;
            margin-top: 10px;
            font-size: 0.8em;
            max-height: 150px;
            overflow-y: auto;
            white-space: pre-wrap;
            font-family: monospace;
        }`,
        `#image-analyzer-container .action-buttons {
            display: flex;
            gap: 5px;
            margin-top: 5px;
        }`,
        `#image-analyzer-container .action-buttons button {
            flex: 1;
        }`
    ]);

    class ImageAnalyzer extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        #imageUrlInput;
        #loadAndAnalyzeButton;
        #imagePreviewCanvas;
        #imagePreviewCtx;
        #dominantColorsDisplay;
        #analysisResultsDisplay;
        #copyResultsButton;
        #downloadResultsButton;

        constructor() {
            super("Analizador de Imágenes", '<i class="fas fa-camera-retro"></i>');
            this.#onStartup();
        }

        #onStartup() {
            this.#loadInterface();
            this.notify("info", "Módulo Analizador de Imágenes cargado.");
            this.#loadAndAnalyzeButton.disabled = false;
            this.#loadAndAnalyzeButton.textContent = 'Cargar y Analizar';
            this.#copyResultsButton.disabled = true;
            this.#downloadResultsButton.disabled = true;
        }

        #loadInterface() {
            const container = domMake.Tree("div", { id: "image-analyzer-container" });

            this.#imageUrlInput = domMake.Tree("input", {
                type: "text",
                placeholder: "Pegar URL de imagen (ej. avatar)",
                value: "https://drawaria.online/avatar/cache/1a5f4450-7153-11ef-acaf-250da20bac69.jpg"
            });
            container.appendChild(this.#imageUrlInput);

            this.#loadAndAnalyzeButton = domMake.Button('<i class="fas fa-chart-pie"></i> Cargar y Analizar');
            this.#loadAndAnalyzeButton.addEventListener("click", () => this.#loadImageAndAnalyze());
            container.appendChild(this.#loadAndAnalyzeButton);

            this.#imagePreviewCanvas = domMake.Tree("canvas", { id: "image-preview-canvas" });
            container.appendChild(this.#imagePreviewCanvas);
            this.#imagePreviewCtx = this.#imagePreviewCanvas.getContext('2d');

            container.appendChild(domMake.Tree("div", {}, ["Colores Dominantes (clic para añadir a Paletas):"]));
            this.#dominantColorsDisplay = domMake.Tree("div", { id: "dominant-colors-display" });
            container.appendChild(this.#dominantColorsDisplay);

            this.#analysisResultsDisplay = domMake.Tree("pre", { id: "analysis-results" }, ["Resultados del análisis aparecerán aquí."]);
            container.appendChild(this.#analysisResultsDisplay);

            // Action Buttons Row (Copy/Download)
            const actionButtonsRow = domMake.Row({ class: "action-buttons" });
            this.#copyResultsButton = domMake.Button('<i class="fas fa-copy"></i> Copiar');
            this.#copyResultsButton.addEventListener("click", () => this.#copyResultsToClipboard());
            actionButtonsRow.appendChild(this.#copyResultsButton);

            this.#downloadResultsButton = domMake.Button('<i class="fas fa-download"></i> Descargar');
            this.#downloadResultsButton.addEventListener("click", () => this.#downloadResults());
            actionButtonsRow.appendChild(this.#downloadResultsButton);
            container.appendChild(actionButtonsRow);


            this.htmlElements.section.appendChild(container);
        }

        async #loadImageAndAnalyze() {
            const imageUrl = this.#imageUrlInput.value.trim();
            if (!imageUrl) {
                this.notify("warning", "Por favor, introduce una URL de imagen.");
                return;
            }

            this.notify("info", "Cargando imagen para análisis...");
            this.#analysisResultsDisplay.textContent = "Cargando...";
            this.#dominantColorsDisplay.innerHTML = '';
            this.#imagePreviewCtx.clearRect(0, 0, this.#imagePreviewCanvas.width, this.#imagePreviewCanvas.height);
            this.#copyResultsButton.disabled = true;
            this.#downloadResultsButton.disabled = true;

            const img = new window.Image();
            img.crossOrigin = "Anonymous"; // Crucial for CORS
            img.src = imageUrl;

            img.onload = async () => {
                try {
                    const maxWidth = 300;
                    const maxHeight = 300;
                    let width = img.width;
                    let height = img.height;

                    if (width > maxWidth || height > maxHeight) {
                        if (width / maxWidth > height / maxHeight) {
                            height = height * (maxWidth / width);
                            width = maxWidth;
                        } else {
                            width = width * (maxHeight / height);
                            height = maxHeight;
                        }
                    }

                    this.#imagePreviewCanvas.width = width;
                    this.#imagePreviewCanvas.height = height;
                    this.#imagePreviewCtx.drawImage(img, 0, 0, width, height);

                    const imageData = this.#imagePreviewCtx.getImageData(0, 0, width, height);
                    const pixels = imageData.data;

                    const results = {};

                    const colorMap = new Map();
                    const sampleStep = Math.max(1, Math.floor(pixels.length / 4 / 5000));

                    for (let i = 0; i < pixels.length; i += 4 * sampleStep) {
                        const r = pixels[i];
                        const g = pixels[i + 1];
                        const b = pixels[i + 2];
                        const a = pixels[i + 3];

                        if (a > 20) {
                            const colorKey = `${r},${g},${b}`;
                            colorMap.set(colorKey, (colorMap.get(colorKey) || 0) + 1);
                        }
                    }

                    const sortedColors = Array.from(colorMap.entries())
                        .sort((a, b) => b[1] - a[1])
                        .slice(0, 5);

                    results.dominantColors = sortedColors.map(([rgbStr, count]) => {
                        const [r, g, b] = rgbStr.split(',').map(Number);
                        return { r, g, b, hex: `#${r.toString(16).padStart(2, '0')}${g.toString(16).padStart(2, '0')}${b.toString(16).padStart(2, '0')}` };
                    });

                    this.#dominantColorsDisplay.innerHTML = '';
                    results.dominantColors.forEach(color => {
                        const swatch = domMake.Tree("div", {
                            class: "color-swatch",
                            style: `background-color: ${color.hex};`,
                            title: `Clic para añadir ${color.hex} a Paletas de Color.`
                        });
                        swatch.addEventListener('click', () => this.#addDominantColorToPalette(color.hex));
                        this.#dominantColorsDisplay.appendChild(swatch);
                    });

                    let totalBrightness = 0;
                    let nonTransparentPixelCount = 0;
                    for (let i = 0; i < pixels.length; i += 4) {
                        const r = pixels[i];
                        const g = pixels[i + 1];
                        const b = pixels[i + 2];
                        const a = pixels[i + 3];
                        if (a > 0) {
                            totalBrightness += (0.299 * r + 0.587 * g + 0.114 * b);
                            nonTransparentPixelCount++;
                        }
                    }
                    results.averageBrightness = nonTransparentPixelCount > 0 ? (totalBrightness / nonTransparentPixelCount).toFixed(2) : 'N/A';
                    results.imageDimensions = `${width}x${height}`;
                    results.totalPixels = width * height;
                    results.nonTransparentPixels = nonTransparentPixelCount;

                    let resultsText = "--- Resultados del Análisis de Imagen ---\n";
                    resultsText += `Dimensiones: ${results.imageDimensions} píxeles\n`;
                    resultsText += `Píxeles no transparentes: ${results.nonTransparentPixels}\n`;
                    resultsText += `Brillo promedio: ${results.averageBrightness}\n`;
                    resultsText += `Colores Dominantes (HEX): ${results.dominantColors.map(c => c.hex).join(', ')}\n`;
                    resultsText += "\n¡Análisis completado!";
                    this.#analysisResultsDisplay.textContent = resultsText;

                    this.notify("success", "Análisis de imagen completado.");
                    this.#copyResultsButton.disabled = false;
                    this.#downloadResultsButton.disabled = false;

                } catch (e) {
                    if (e.name === "SecurityError" || (e.message && e.message.includes("tainted"))) {
                        this.notify("error", "Error de CORS: No se pudo acceder a los píxeles de la imagen. La imagen debe estar en el mismo origen o permitir CORS.");
                        this.#analysisResultsDisplay.textContent = "Error: No se pudo leer la imagen debido a restricciones de seguridad (CORS).";
                    } else {
                        this.notify("error", `Error al analizar la imagen: ${e.message}`);
                        this.#analysisResultsDisplay.textContent = `Error: ${e.message}`;
                        console.error("Image analysis error:", e);
                    }
                }
            };

            img.onerror = () => {
                this.notify("error", "Fallo al cargar la imagen. ¿URL correcta o problema de red?");
                this.#analysisResultsDisplay.textContent = "Error: Fallo al cargar la imagen.";
            };
        }

        // --- Nuevas funciones de Utilidad ---
        async #copyResultsToClipboard() {
            const resultsText = this.#analysisResultsDisplay.textContent;
            if (!resultsText.trim()) {
                this.notify("warning", "No hay resultados para copiar.");
                return;
            }
            try {
                await navigator.clipboard.writeText(resultsText);
                this.notify("success", "Resultados copiados al portapapeles.");
            } catch (err) {
                this.notify("error", `Error al copiar: ${err.message}`);
                console.error("Copy to clipboard failed:", err);
            }
        }

        #downloadResults() {
            const resultsText = this.#analysisResultsDisplay.textContent;
            if (!resultsText.trim()) {
                this.notify("warning", "No hay resultados para descargar.");
                return;
            }

            const blob = new Blob([resultsText], { type: 'text/plain;charset=utf-8' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `drawaria_image_analysis_${Date.now()}.txt`;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            this.notify("success", "Resultados descargados como TXT.");
        }

        #addDominantColorToPalette(hexColor) {
            const moreColorPalettesModule = this.findGlobal("MoreColorPalettes");
            if (moreColorPalettesModule && moreColorPalettesModule.siblings && moreColorPalettesModule.siblings.length > 0) {
                const paletteInstance = moreColorPalettesModule.siblings[0]; // Assuming first instance
                if (paletteInstance && typeof paletteInstance.addCustomColorFromExternal === 'function') {
                    paletteInstance.addCustomColorFromExternal(hexColor);
                    this.notify("info", `Color ${hexColor} enviado a 'Paletas de Color'.`);
                } else {
                    this.notify("warning", "El módulo 'Paletas de Color' no está listo o le falta la función para añadir colores.");
                }
            } else {
                this.notify("warning", "El módulo 'Paletas de Color' no se encontró o no está activo.");
            }
        }
    }
})("QBit");
// --- END Analyzer

// --- START RANDOM PROFILE
(function RandomProfileSelectorModule() {
    const QBit = globalThis[arguments[0]];

    QBit.Styles.addRules([
        `#${QBit.identifier} .random-profile-section {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 10px;
            background-color: var(--CE-bg_color);
        }`,
        `#${QBit.identifier} .random-profile-section-title {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--dark-blue-title);
            text-align: center;
        }`,
        `#${QBit.identifier} .profile-display-area {
            display: flex;
            align-items: center;
            justify-content: center;
            gap: 10px;
            padding: 10px;
            border: 1px dashed var(--CE-color);
            border-radius: .25rem;
            margin-bottom: 10px;
            flex-wrap: wrap;
        }`,
        // Estilos para el canvas de avatar
        `#${QBit.identifier} #avatar-canvas {
            width: 150px;
            height: 150px;
            border-radius: 50%;
            border: 2px solid var(--info);
            object-fit: cover; /* Aunque es canvas, mantiene la intención visual */
            flex-shrink: 0;
            background-color: #f0f0f0; /* Fondo para cuando no hay imagen */
        }`,
        `#${QBit.identifier} .profile-display-info {
            flex-grow: 1;
            text-align: center;
            min-width: 150px;
            margin-top: 5px;
        }`,
        `#${QBit.identifier} .profile-display-name {
            font-weight: bold;
            font-size: 1.2em;
            color: var(--dark-blue-title);
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
        }`,
        `#${QBit.identifier} .profile-display-stats {
            font-size: 0.9em;
            color: var(--CE-color);
        }`,
        /* Geometry Dash style navigation buttons */
        `#${QBit.identifier} .gd-nav-button {
            border-radius: 8px;
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: 1.8em;
            background-color: var(--secondary);
            color: var(--dark);
            cursor: pointer;
            border: 2px solid var(--CE-color);
            box-shadow: inset 0 2px 4px rgba(255,255,255,0.2), 0 2px 5px rgba(0,0,0,0.3);
            transition: transform 0.2s ease-in-out, background-color 0.2s ease, box-shadow 0.2s ease;
            flex-shrink: 0;
        }`,
        `#${QBit.identifier} .gd-nav-button:hover {
            background-color: var(--info);
            color: white;
            transform: translateY(-2px) scale(1.05);
            box-shadow: inset 0 2px 4px rgba(255,255,255,0.3), 0 5px 10px rgba(0,0,0,0.4);
        }`,
        `#${QBit.identifier} .gd-nav-button:active {
            transform: translateY(0) scale(1);
            box-shadow: inset 0 1px 2px rgba(0,0,0,0.3);
        }`,
        `#${QBit.identifier} .button-row .gd-nav-button {
            flex: none;
            margin: 0 5px;
        }`,
        `#${QBit.identifier} .button-row {
            display: flex;
            flex-wrap: wrap;
            gap: 5px;
            margin-bottom: 10px;
            justify-content: center;
        }`,
        `#${QBit.identifier} .button-row .btn {
            flex: 1 1 48%;
            min-width: 120px;
        }`,
        `#${QBit.identifier} .profile-details-display {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 10px;
            margin-top: 10px;
            background-color: var(--CE-bg_color);
            color: var(--CE-color);
            font-family: monospace;
            font-size: 0.9em;
            white-space: pre-wrap;
            max-height: 400px;
            overflow-y: auto;
            display: none; /* Controlled by #displayProfileDetails */
        }`,
        `#${QBit.identifier} .profile-details-loader {
            text-align: center;
            padding: 20px;
            color: var(--info);
        }`
    ]);

    class RandomProfileSelector extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        #playersData = [];
        #currentIndex = -1;
        #currentPage = 1;
        #totalPages = 1;

        // UI elements
        #avatarCanvas;
        #avatarCtx;
        #playerNameDisplay;
        #playerStatsDisplay;
        #prevButton;
        #nextButton;
        #downloadProfileButton;
        #openPlayerProfileButton;
        #openProfileDetailsButton;
        #randomPlayerButton;
        #loadingIndicator;
        #profileDetailsContent;
        #modalLoader;
        #analyzeAvatarButton;

        // Dependencies
        #playerProfileExtractorModule = null;
        #imageAnalyzerModule = null;

        constructor() {
            super("Selector de Perfil Aleatorio", '<i class="fas fa-user-circle"></i>');
            this.#onStartup();
        }

        async #onStartup() {
            this.#loadInterface();
            await this.#fetchScoreboardData();
            // Ensure modules are found after CubeEngine might have loaded them
            this.#playerProfileExtractorModule = this.findGlobal("PlayerProfileExtractor");
            this.#imageAnalyzerModule = this.findGlobal("ImageAnalyzer");
        }

        #loadInterface() {
            const container = domMake.Tree("div", { class: "random-profile-section" });

            container.appendChild(domMake.Tree("div", { class: "random-profile-section-title" }, ["Explorador de Perfiles"]));

            this.#loadingIndicator = domMake.Tree("div", { class: "profile-details-loader" }, ["Cargando datos del marcador..."]);
            container.appendChild(this.#loadingIndicator); // Append loading indicator directly

            const profileDisplayArea = domMake.Tree("div", { class: "profile-display-area" });

            this.#avatarCanvas = domMake.Tree("canvas", { id: "avatar-canvas", width: "150", height: "150" });
            this.#avatarCtx = this.#avatarCanvas.getContext('2d');

            this.#playerNameDisplay = domMake.Tree("div", { class: "profile-display-name" }, ["Nombre del Jugador"]);
            this.#playerStatsDisplay = domMake.Tree("div", { class: "profile-display-stats" }, ["Puntuación: N/A | Victorias: N/A"]);

            this.#prevButton = domMake.Button('<i class="fas fa-chevron-left"></i>', { class: "gd-nav-button" });
            this.#prevButton.addEventListener("click", () => this.#navigateProfile(-1));

            this.#nextButton = domMake.Button('<i class="fas fa-chevron-right"></i>', { class: "gd-nav-button" });
            this.#nextButton.addEventListener("click", () => this.#navigateProfile(1));

            profileDisplayArea.appendAll(this.#prevButton, domMake.Tree("div", { style: "display: flex; flex-direction: column; align-items: center;" }, [
                this.#avatarCanvas,
                this.#playerNameDisplay,
                this.#playerStatsDisplay
            ]), this.#nextButton);
            container.appendChild(profileDisplayArea);

            const actionButtonsRow1 = domMake.Row({ class: "button-row" });
            this.#downloadProfileButton = domMake.Button('<i class="fas fa-file-download"></i> Descargar Perfil (JSON)');
            this.#downloadProfileButton.addEventListener("click", () => this.#downloadProfileData());

            this.#openPlayerProfileButton = domMake.Button('<i class="fas fa-user"></i> Abrir Perfil de Jugador');
            this.#openPlayerProfileButton.addEventListener("click", () => this.#openPlayerProfilePage());
            actionButtonsRow1.appendAll(this.#downloadProfileButton, this.#openPlayerProfileButton);
            container.appendChild(actionButtonsRow1);

            const actionButtonsRow2 = domMake.Row({ class: "button-row" });
            this.#openProfileDetailsButton = domMake.Button('<i class="fas fa-info-circle"></i> Mostrar Detalles Completos');
            this.#openProfileDetailsButton.addEventListener("click", () => this.#displayProfileDetails());
            this.#randomPlayerButton = domMake.Button('<i class="fas fa-random"></i> Jugador al Azar');
            this.#randomPlayerButton.addEventListener("click", () => this.#selectRandomProfile());
            actionButtonsRow2.appendAll(this.#openProfileDetailsButton, this.#randomPlayerButton);
            container.appendChild(actionButtonsRow2);

            this.#modalLoader = domMake.Tree("div", { class: "profile-details-loader" }, ["Cargando datos del perfil extendidos..."]);
            this.#profileDetailsContent = domMake.Tree("pre", { class: "profile-details-display", style: "margin: 0;" });
            container.appendAll(this.#modalLoader, this.#profileDetailsContent);

            this.htmlElements.section.appendChild(container);

            this.#setInterfaceEnabled(false);
        }

        #setInterfaceEnabled(enabled) {
            this.#loadingIndicator.style.display = enabled ? 'none' : 'block';

            const mainContent = this.htmlElements.section.querySelector('.profile-display-area');
            const buttonRows = this.htmlElements.section.querySelectorAll('.button-row');

            if (mainContent) {
                mainContent.style.display = enabled ? 'flex' : 'none';
            }

            buttonRows.forEach(row => {
                row.style.display = enabled ? 'flex' : 'none';
            });

            if (this.#downloadProfileButton) this.#downloadProfileButton.disabled = !enabled;
            if (this.#openPlayerProfileButton) this.#openPlayerProfileButton.disabled = !enabled;
            if (this.#openProfileDetailsButton) this.#openProfileDetailsButton.disabled = !enabled;
            if (this.#randomPlayerButton) this.#randomPlayerButton.disabled = !enabled;
            if (this.#analyzeAvatarButton) this.#analyzeAvatarButton.disabled = !enabled;

            if (this.#prevButton) this.#prevButton.disabled = !enabled;
            if (this.#nextButton) this.#nextButton.disabled = !enabled;
        }

        async #fetchScoreboardData() {
            console.log("RandomProfileSelector: Obteniendo datos del marcador...");
            this.#setInterfaceEnabled(false);

            try {
                let allPlayers = [];
                // Fetch up to 5 pages of the scoreboard
                for (let page = 1; page <= 5; page++) {
                    this.#loadingIndicator.textContent = `Cargando datos del marcador (Página ${page})...`;
                    const url = `https://drawaria.online/scoreboards/?page=${page}`;
                    const response = await fetch(url);
                    if (!response.ok) {
                        if (response.status === 404) {
                            console.log(`RandomProfileSelector: Página de marcador ${page} no encontrada, deteniendo la carga.`);
                            break;
                        }
                        throw new Error(`HTTP error! status: ${response.status}`);
                    }
                    const html = await response.text();
                    const parser = new DOMParser();
                    const doc = parser.parseFromString(html, 'text/html');

                    // Select all rows with IDs r1 to r100, assuming a pattern
                    const rows = doc.querySelectorAll('table.table tbody tr[id^="r"]');

                    if (rows.length === 0) {
                        console.log(`RandomProfileSelector: No más jugadores encontrados en la página ${page}.`);
                        break; // Stop if a page returns no rows
                    }

                    rows.forEach(row => {
                        const rank = row.querySelector('th[scope="row"]')?.textContent.trim();
                        const playerId = row.dataset.avatarid; // Get data-avatarid from the row
                        const playerLink = row.querySelector('td:nth-child(2) a');
                        const playerName = playerLink?.textContent.trim();
                        const avatarUrl = playerLink?.querySelector('img')?.src;

                        const score = row.querySelector('td:nth-child(3)')?.textContent.trim();
                        const stars = row.querySelector('td:nth-child(4)')?.textContent.trim();
                        const wins = row.querySelector('td:nth-child(5)')?.textContent.trim();
                        const matches = row.querySelector('td:nth-child(6)')?.textContent.trim();
                        const guesses = row.querySelector('td:nth-child(7)')?.textContent.trim();
                        const avgGuessTime = row.querySelector('td:nth-child(8)')?.textContent.trim();

                        // Only add if essential data is present
                        if (playerId && playerName && avatarUrl) {
                            allPlayers.push({
                                rank: rank,
                                uid: playerId,
                                name: playerName,
                                avatarUrl: avatarUrl,
                                score: score,
                                stars: stars,
                                wins: wins,
                                matches: matches,
                                guesses: guesses,
                                avgGuessTime: avgGuessTime,
                                profileUrl: `https://drawaria.online/profile/?uid=${playerId}`
                            });
                        } else {
                            console.warn(`RandomProfileSelector: Faltan datos esenciales para la fila del jugador: ID=${playerId}, Nombre=${playerName}, Avatar=${avatarUrl}`);
                        }
                    });
                }

                if (allPlayers.length === 0) {
                    console.warn("RandomProfileSelector: No se encontraron jugadores en el marcador.");
                    this.#loadingIndicator.textContent = "No se encontraron jugadores.";
                    return;
                }

                this.#playersData = allPlayers;
                this.#setInterfaceEnabled(true);
                this.#currentIndex = 0;
                this.#updateProfileDisplay();

            } catch (error) {
                console.error(`RandomProfileSelector: Error al cargar datos del marcador: ${error.message}`);
                this.#loadingIndicator.textContent = `Error: ${error.message}`;
            }
        }

        async #updateProfileDisplay() {
            if (this.#playersData.length === 0) {
                this.#avatarCtx.clearRect(0, 0, this.#avatarCanvas.width, this.#avatarCanvas.height);
                this.#playerNameDisplay.textContent = "No hay datos de jugadores.";
                this.#playerStatsDisplay.textContent = "";
                this.#setInterfaceEnabled(false);
                this.#profileDetailsContent.style.display = 'none';
                return;
            }

            const player = this.#playersData[this.#currentIndex];
            this.#playerNameDisplay.textContent = player.name;
            this.#playerStatsDisplay.textContent = `Puntuación: ${player.score} | Victorias: ${player.wins} | Estrellas: ${player.stars}`;

            this.#avatarCtx.clearRect(0, 0, this.#avatarCanvas.width, this.#avatarCanvas.height);
            const img = new Image();
            img.crossOrigin = "Anonymous";
            img.onload = () => {
                this.#avatarCtx.drawImage(img, 0, 0, this.#avatarCanvas.width, this.#avatarCanvas.height);
            };
            img.onerror = () => {
                console.warn(`RandomProfileSelector: No se pudo cargar el avatar para ${player.name}.`);
                this.#avatarCtx.fillStyle = '#ccc';
                this.#avatarCtx.fillRect(0, 0, this.#avatarCanvas.width, this.#avatarCanvas.height);
                this.#avatarCtx.font = '10px Arial';
                this.#avatarCtx.fillStyle = '#666';
                this.#avatarCtx.textAlign = 'center';
                this.#avatarCtx.fillText('No Avatar', this.#avatarCanvas.width / 2, this.#avatarCanvas.height / 2);
            };
            img.src = player.avatarUrl;

            this.#prevButton.disabled = this.#currentIndex === 0;
            this.#nextButton.disabled = this.#currentIndex === this.#playersData.length - 1;

            this.#profileDetailsContent.textContent = '';
            this.#profileDetailsContent.style.display = 'none';
        }

        #navigateProfile(direction) {
            this.#currentIndex += direction;
            if (this.#currentIndex < 0) {
                this.#currentIndex = 0;
            } else if (this.#currentIndex >= this.#playersData.length) {
                this.#currentIndex = this.#playersData.length - 1;
            }
            this.#updateProfileDisplay();
            console.log(`RandomProfileSelector: Mostrando perfil: ${this.#playersData[this.#currentIndex].name}`);
        }

        #selectRandomProfile() {
            if (this.#playersData.length === 0) {
                console.warn("RandomProfileSelector: No hay jugadores cargados para seleccionar al azar.");
                return;
            }
            const randomIndex = Math.floor(Math.random() * this.#playersData.length);
            this.#currentIndex = randomIndex;
            this.#updateProfileDisplay();
            console.log(`RandomProfileSelector: Jugador al azar seleccionado: ${this.#playersData[this.#currentIndex].name}`);
        }

        #downloadProfileData() {
            if (this.#playersData.length === 0 || this.#currentIndex === -1) {
                console.warn("RandomProfileSelector: No hay perfil seleccionado para descargar.");
                return;
            }
            const player = this.#playersData[this.#currentIndex];
            const dataStr = JSON.stringify(player, null, 2);
            const blob = new Blob([dataStr], { type: 'application/json' });
            const url = URL.createObjectURL(blob);
            const filename = `drawaria_player_${player.name.replace(/[^a-zA-Z0-9]/g, '_')}_${player.uid.substring(0, 8)}.json`;

            const a = document.createElement('a');
            a.href = url;
            a.download = filename;
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
            console.log(`RandomProfileSelector: Datos de ${player.name} descargados.`);
        }

        #openPlayerProfilePage() {
            if (this.#playersData.length === 0 || this.#currentIndex === -1) {
                console.warn("RandomProfileSelector: No hay perfil seleccionado para abrir.");
                return;
            }
            const player = this.#playersData[this.#currentIndex];
            window.open(player.profileUrl, '_blank');
            console.log(`RandomProfileSelector: Abriendo perfil de ${player.name} en una nueva pestaña.`);
        }

        async #displayProfileDetails() {
            if (this.#playersData.length === 0 || this.#currentIndex === -1) {
                console.warn("RandomProfileSelector: No hay perfil seleccionado para mostrar detalles.");
                this.#profileDetailsContent.style.display = 'none';
                return;
            }

            const player = this.#playersData[this.#currentIndex];
            const profileUrl = player.profileUrl;

            this.#profileDetailsContent.textContent = '';
            this.#profileDetailsContent.style.display = 'none';
            this.#modalLoader.style.display = 'block';

            console.log(`RandomProfileSelector: Cargando detalles completos para ${player.name}...`);

            if (!this.#playerProfileExtractorModule || !this.#playerProfileExtractorModule.siblings || this.#playerProfileExtractorModule.siblings.length === 0) {
                console.error("RandomProfileSelector: El módulo 'Extractor de Perfil' no está activo. No se pueden cargar detalles extendidos.");
                this.#profileDetailsContent.textContent = "Error: El módulo Extractor de Perfil no está cargado. Asegúrate de que está habilitado.";
                this.#modalLoader.style.display = 'none';
                this.#profileDetailsContent.style.display = 'block';
                return;
            }

            const extractorInstance = this.#playerProfileExtractorModule.siblings[0];

            if (typeof extractorInstance._parseMainProfileHTML !== 'function') {
                console.error("RandomProfileSelector: El módulo Extractor de Perfil no tiene el método de parseo necesario. Puede que esté desactualizado o modificado incorrectamente.");
                this.#profileDetailsContent.textContent = "Error: El módulo Extractor de Perfil no está completamente funcional.";
                this.#modalLoader.style.display = 'none';
                this.#profileDetailsContent.style.display = 'block';
                return;
            }

            try {
                const response = await fetch(profileUrl);
                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }
                const htmlContent = await response.text();
                const parser = new DOMParser();
                const extractedData = extractorInstance._parseMainProfileHTML(htmlContent, parser);

                const combinedData = {
                    ...player,
                    ...extractedData,
                };

                let displayTxt = `--- Detalles Completos del Perfil de ${combinedData.name} ---\n`;
                displayTxt += `UID: ${combinedData.uid || 'N/A'}\n`;
                displayTxt += `Nombre: ${combinedData.name || 'N/A'}\n`;
                displayTxt += `Avatar URL: ${combinedData.avatarUrl || 'N/A'}\n`;
                displayTxt += `Rank: ${combinedData.rank || 'N/A'}\n`;
                displayTxt += `Experiencia: ${combinedData.experience || 'N/A'}\n`;

                displayTxt += `--- Estadísticas de Juego ---\n`;
                displayTxt += `Puntuación Total: ${combinedData.score || 'N/A'}\n`;
                displayTxt += `Estrellas: ${combinedData.stars || 'N/A'}\n`;
                displayTxt += `Victorias: ${combinedData.wins || 'N/A'}\n`;
                displayTxt += `Partidas Jugadas: ${combinedData.matches || 'N/A'}\n`;
                displayTxt += `Adivinanzas Correctas: ${combinedData.guesses || 'N/A'}\n`;
                displayTxt += `Tiempo Promedio de Adivinanza: ${combinedData.avgGuessTime || 'N/A'}\n`;

                if (combinedData.pictionaryStats && Object.keys(combinedData.pictionaryStats).length > 0) {
                    displayTxt += `\n--- Estadísticas Detalladas (Pictionary) ---\n`;
                    for (const key in combinedData.pictionaryStats) {
                        const displayKey = key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase());
                        displayTxt += `  ${displayKey}: ${combinedData.pictionaryStats[key]}\n`;
                    }
                }

                if (combinedData.accusedTokens && Object.keys(combinedData.accusedTokens).length > 0) {
                    displayTxt += `\n--- Menciones de Homenaje (Tokens) ---\n`;
                    for (const tokenName in combinedData.accusedTokens) {
                        displayTxt += `  ${tokenName}: ${combinedData.accusedTokens[tokenName]}\n`;
                    }
                }

                this.#profileDetailsContent.textContent = displayTxt;
                console.log(`RandomProfileSelector: Detalles completos de ${player.name} cargados.`);

            } catch (error) {
                console.error(`RandomProfileSelector: Error al cargar detalles de ${player.name}: ${error.message}`);
                this.#profileDetailsContent.textContent = `Error al cargar detalles: ${error.message}.`;
            } finally {
                this.#modalLoader.style.display = 'none';
                this.#profileDetailsContent.style.display = 'block';
            }
        }

        async #analyzeCurrentAvatar() {
            if (this.#playersData.length === 0 || this.#currentIndex === -1) {
                console.warn("RandomProfileSelector: No hay avatar seleccionado para analizar.");
                return;
            }

            const player = this.#playersData[this.#currentIndex];
            const avatarUrl = player.avatarUrl;

            if (!this.#imageAnalyzerModule || !this.#imageAnalyzerModule.siblings || this.#imageAnalyzerModule.siblings.length === 0) {
                console.error("RandomProfileSelector: El módulo 'Analizador de Imágenes' no está activo. No se puede analizar el avatar.");
                return;
            }

            const analyzerInstance = this.#imageAnalyzerModule.siblings[0];
            if (typeof analyzerInstance.performAnalysisFromExternalUrl === 'function') {
                console.log(`RandomProfileSelector: Enviando avatar de ${player.name} al Analizador de Imágenes...`);
                analyzerInstance.performAnalysisFromExternalUrl(avatarUrl);
            } else {
                console.error("RandomProfileSelector: El módulo 'Analizador de Imágenes' no tiene el método de análisis requerido (performAnalysisFromExternalUrl).");
            }
        }
    }
})("QBit");

// --- END RANDOM PROFILE

// START NAVIGATOR

(function RoomNavigatorModule() {
    const QBit = globalThis[arguments[0]]; // Corrected access to QBit
// Helper to fetch JSON (re-declaring if not globally accessible or just for clarity)
function fetchJson(url) {
    return new Promise((resolve, reject) => {
        const xhr = new XMLHttpRequest();
        xhr.open('GET', url);
        xhr.onload = () => {
            if (xhr.status >= 200 && xhr.status < 300) {
                try {
                    let parsedData = JSON.parse(xhr.responseText);
                    // Handle potential double-encoded JSON
                    if (typeof parsedData === 'string') {
                        parsedData = JSON.parse(parsedData);
                    }
                    resolve(parsedData);
                } catch (e) {
                    reject(new Error('Fallo al analizar la respuesta JSON: ' + e.message + ` (Raw: ${xhr.responseText.substring(0, 100)}...)`));
                }
            } else {
                reject(new Error(`La respuesta de la red no fue correcta, estado: ${xhr.status} ${xhr.statusText}`));
            }
        };
        xhr.onerror = () => reject(new Error('Fallo la solicitud de red. Revisa tu conexión o el servidor.'));
        xhr.send();
    });
}

class RoomNavigator extends QBit {
    static dummy1 = QBit.register(this);
    static dummy2 = QBit.bind(this, "CubeEngine");

    #roomListContainer;
    #refreshButton;
    #loadingIndicator;
    #filterNameInput;
    #filterMinPlayersInput;
    #filterMaxPlayersInput;
    #roomDataCache = []; // Store fetched raw room data

    constructor() {
        super("Navegador de Salas", '<i class="fas fa-search-location"></i>');
        this.#onStartup();
    }

    #onStartup() {
        this.#loadInterface();
        this.#fetchAndApplyFilters(); // Initial fetch and display
    }

    #loadInterface() {
        const container = domMake.Tree("div");

        // Refresh and Loading Row
        const headerRow = domMake.Row();
        headerRow.style.marginBottom = "10px";

        this.#refreshButton = domMake.Button('<i class="fas fa-sync-alt"></i> Actualizar Salas');
        this.#refreshButton.title = "Actualiza la lista de salas disponibles.";
        this.#refreshButton.addEventListener("click", () => this.#fetchAndApplyFilters());
        headerRow.appendChild(this.#refreshButton);

        this.#loadingIndicator = domMake.Tree("span", { style: "margin-left: 10px; color: var(--info); display: none;" }, ['Cargando...']);
        headerRow.appendChild(this.#loadingIndicator);
        container.appendChild(headerRow);

        // Filters Row
        const filtersRow = domMake.Row();
        filtersRow.style.flexWrap = "wrap";
        filtersRow.style.gap = "5px";

        this.#filterNameInput = domMake.Tree("input", { type: "text", placeholder: "Filtrar por nombre", style: "flex: 1 1 120px;" });
        this.#filterNameInput.addEventListener("input", () => this.#applyFiltersAndDisplayRooms());
        filtersRow.appendChild(this.#filterNameInput);

        this.#filterMinPlayersInput = domMake.Tree("input", { type: "number", min: "0", placeholder: "Min Jugadores", style: "width: 80px;" });
        this.#filterMinPlayersInput.addEventListener("input", () => this.#applyFiltersAndDisplayRooms());
        filtersRow.appendChild(this.#filterMinPlayersInput);

        this.#filterMaxPlayersInput = domMake.Tree("input", { type: "number", min: "0", placeholder: "Max Jugadores", style: "width: 80px;" });
        this.#filterMaxPlayersInput.addEventListener("input", () => this.#applyFiltersAndDisplayRooms());
        filtersRow.appendChild(this.#filterMaxPlayersInput);

        container.appendChild(filtersRow);

        // Room List Display Area
        this.#roomListContainer = domMake.Tree("div", {
            class: "room-list-display",
            style: `
                display: grid;
                grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
                gap: 10px;
                padding: 5px;
                max-height: 400px; /* Limits height and adds scrollbar */
                overflow-y: auto;
                border: 1px solid var(--CE-color);
                border-radius: .25rem;
                margin-top: 10px;
            `
        });
        container.appendChild(this.#roomListContainer);

        this.htmlElements.section.appendChild(container);
    }

    async #fetchAndApplyFilters() {
        this.#roomListContainer.innerHTML = '';
        this.#loadingIndicator.style.display = 'inline';
        this.#refreshButton.disabled = true;

        try {
            const roomData = await fetchJson('https://drawaria.online/getroomlist');
            if (!Array.isArray(roomData)) {
                 throw new Error('La respuesta no es un array de salas válido.');
            }
            this.#roomDataCache = roomData; // Cache the raw data
            this.notify("info", `Se encontraron ${roomData.length} salas.`);
            this.#applyFiltersAndDisplayRooms();
        } catch (error) {
            this.notify("error", `Error al cargar la lista de salas: ${error.message}`);
            this.#roomListContainer.appendChild(domMake.TextNode("Error al cargar las salas. Inténtalo de nuevo."));
            console.error("Fetch room list error:", error);
        } finally {
            this.#loadingIndicator.style.display = 'none';
            this.#refreshButton.disabled = false;
        }
    }

    #applyFiltersAndDisplayRooms() {
        let filteredRooms = [...this.#roomDataCache]; // Start with cached data

        const nameFilter = this.#filterNameInput.value.toLowerCase();
        const minPlayers = parseInt(this.#filterMinPlayersInput.value);
        const maxPlayers = parseInt(this.#filterMaxPlayersInput.value);

        if (nameFilter) {
            filteredRooms = filteredRooms.filter(room => {
                const roomName = room[3] ? room[3].toLowerCase() : ''; // Access roomName by index 3
                return roomName.includes(nameFilter);
            });
        }

        if (!isNaN(minPlayers) && minPlayers >= 0) {
            filteredRooms = filteredRooms.filter(room => room[1] >= minPlayers); // Access currentPlayers by index 1
        }

        if (!isNaN(maxPlayers) && maxPlayers >= 0) {
            filteredRooms = filteredRooms.filter(room => room[1] <= maxPlayers); // Access currentPlayers by index 1
        }

        this.#displayRooms(filteredRooms);
    }


    #displayRooms(rooms) {
        this.#roomListContainer.innerHTML = '';
        if (rooms.length === 0) {
            this.#roomListContainer.appendChild(domMake.TextNode("No hay salas que coincidan con los filtros."));
            return;
        }

        rooms.forEach(roomArray => {
            // Structure observed from the provided JSON example:
            // [0] roomId: string (e.g., "f0e1c44b-fc49-4160-91c9-b297fb662692" or "f49bf487-e96a-45a9-9616-c4b71662ac50.3")
            // [1] currentPlayers: number
            // [2] maxPlayers: number
            // [3] roomName: string (e.g., "жду друзей", "sos :)", or "")
            // [4] gameModeType: number (2 for public, 3 for private/friend/custom)
            // [5] unknown_number: number (e.g., 273052, 4176 - seems like a server-side counter or ID)
            // [6] flags_array: Array (e.g., [null,true,null,null,null,true] or [null,true])
            //      - flags_array[0]: boolean (possibly password protected if true)
            //      - flags_array[1]: boolean (this flag seems always true in sample, not a public/private indicator)
            // [7] roundsPlayed: number
            // [8] serverId: number or null (e.g., 5 or null if main server, part of room ID for specific servers)

            const roomId = roomArray[0];
            const currentPlayers = roomArray[1];
            const maxPlayers = roomArray[2];
            const roomName = roomArray[3];
            const gameModeType = roomArray[4];
            const flags = roomArray[6]; // Flags are at index 6
            const roundsPlayed = roomArray[7];
            const serverId = roomArray[8];

            let roomModeLabel = 'Desconocido';
            if (gameModeType === 2) {
                roomModeLabel = 'Público';
            } else if (gameModeType === 3) {
                roomModeLabel = 'Amigos/Privado';
            }

            // Access flags_array[0] for password protected status
            const isPasswordProtected = flags && flags.length > 0 && flags[0] === true;

            const isFull = currentPlayers >= maxPlayers;
            const statusColor = isFull ? 'var(--danger)' : 'var(--success)';
            const joinableStatus = isFull ? 'LLENA' : 'DISPONIBLE';

            const roomCard = domMake.Tree("div", {
                class: "room-card",
                style: `
                    border: 1px solid var(--CE-color);
                    border-radius: .25rem;
                    padding: 8px;
                    background-color: var(--CE-bg_color);
                    display: flex;
                    flex-direction: column;
                    justify-content: space-between;
                    gap: 5px;
                    font-size: 0.85em;
                    box-shadow: 0 2px 4px rgba(0,0,0,0.1);
                `
            });

            const nodesToAppend = [
                domMake.Tree("div", { style: "font-weight: bold; color: var(--dark-blue-title); overflow: hidden; text-overflow: ellipsis; white-space: nowrap;", title: roomName }, [`Sala: ${roomName || '(Sin Nombre)'}`]),
                domMake.Tree("div", {}, [`Jugadores: ${currentPlayers}/${maxPlayers}`]),
                domMake.Tree("div", {}, [`Rondas jugadas: ${roundsPlayed || 'N/A'}`]),
                domMake.Tree("div", {}, [`Modo: ${roomModeLabel}`]),
                isPasswordProtected ? domMake.Tree("div", {style: "color: var(--warning);"}, [`Contraseña: Sí`]) : null,
                domMake.Tree("div", { style: `color: ${statusColor}; font-weight: bold;` }, [`Estado: ${joinableStatus}`])
            ].filter(node => node instanceof Node);

            roomCard.appendAll(...nodesToAppend);

            const joinButton = domMake.Button("Unirse");
            joinButton.classList.add("btn-primary");
            joinButton.style.width = "100%";
            joinButton.style.marginTop = "5px";
            // joinButton.disabled = isFull; // REMOVED: Allow joining even if full
            joinButton.addEventListener("click", () => this.#joinRoom(roomId, serverId));
            roomCard.appendChild(joinButton);

            this.#roomListContainer.appendChild(roomCard);
        });
    }

    #joinRoom(roomId, serverId) {
        let fullRoomIdentifier = roomId;
        // Construct full room ID, e.g., "uuid.serverId"
        if (serverId !== null && serverId !== undefined && !String(roomId).includes(`.${serverId}`)) {
            fullRoomIdentifier = `${roomId}.${serverId}`;
        }
        const roomUrl = `https://drawaria.online/room/${fullRoomIdentifier}`;
        window.location.assign(roomUrl); // Navigate to the room
    }
}

})("QBit");

// END NAVIGATOR

// START CMD

(function UserInterfaceCMDSysyem() { // Renamed the module
    const QBit = globalThis[arguments[0]];

    // Store original jQuery functions (moved here to be accessible within the module scope)
    let _originalJQueryFnModal;
    let _originalJQueryFnPopover;
    let _originalJQueryFnDropdown; // For Bootstrap dropdowns
    let _originalJQueryFnCollapse;

    // Combined Styles for both functionalities
    QBit.Styles.addRules([
        // Styles for Client Command Sender part
        `#${QBit.identifier} .cmd-center-section {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 10px;
            background-color: var(--CE-bg_color);
            margin-top: 10px; /* Add some space from the previous section */
        }`,
        `#${QBit.identifier} .cmd-center-title {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--dark-blue-title);
            text-align: center;
        }`,
        `#${QBit.identifier} .cmd-center-links {
            display: flex;
            flex-direction: column;
            gap: 5px;
        }`,
        `#${QBit.identifier} .cmd-center-links .btn {
            width: 100%;
            padding: 8px 15px;
            box-sizing: border-box;
            font-size: 0.95em;
            display: flex;
            align-items: center;
            justify-content: center;
        }`,
        `#${QBit.identifier} .cmd-center-links .btn i {
            margin-right: 8px;
        }`,
        `#${QBit.identifier} .client-cmd-controls input[type="number"],
         #${QBit.identifier} .client-cmd-controls input[type="text"] {
            width: 100%;
            padding: 5px;
            box-sizing: border-box;
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            background-color: var(--CE-bg_color);
            color: var(--CE-color);
        }`,
        `#${QBit.identifier} .client-cmd-controls .btn {
            width: 100%;
            padding: 5px;
        }`,

        // Styles for UI Persistence part
        `#${QBit.identifier} .ui-persistence-section {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 10px;
            background-color: var(--CE-bg_color);
        }`,
        `#${QBit.identifier} .ui-persistence-section-title {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--dark-blue-title);
            text-align: center;
        }`,
        `#${QBit.identifier} .ui-persistence-toggle-button {
            background-color: var(--secondary);
            color: var(--dark);
            width: 100%;
            min-width: unset;
            padding: 10px 15px;
            box-sizing: border-box;
            font-size: 1rem;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            cursor: pointer;
            text-align: center;
            flex: none;
            min-height: 70px;
        }`,
        `#${QBit.identifier} .ui-persistence-toggle-button i {
            margin-right: 0;
            margin-bottom: 5px;
            font-size: 1.5em;
        }`,
        `#${QBit.identifier} .ui-persistence-toggle-button.active {
            background-color: var(--info);
            color: white;
        }`,
        `#${QBit.identifier} .ui-persistence-toggle-button.active i {
            color: #fff;
        }`,
        `#${QBit.identifier} .ui-persistence-element-list {
            display: flex;
            flex-direction: column;
            flex-wrap: nowrap;
            gap: 10px;
            margin-top: 5px;
            justify-content: flex-start;
            align-items: stretch;
        }`,
    ]);

    class UserInterfaceCMDSysyem extends QBit { // Renamed class
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        // Properties from ClientCommandSender
        _cmdIdInput;
        _param1Input;
        _param2Input;
        _param3Input;

        // Properties from UIPersistenceModule
        _persistentElements = new Map();
        _chatattopNewMessageElement;
        _drawControlsPopuplistElement;
        _friendsTabFriendlistElement;
        _friendsContainerElement;
        // _cubeEngineMenuObserver; // Not used as Global Toggle is removed

        constructor() {
            super("User Interface CMD System", '<i class="fas fa-desktop"></i>'); // New name and icon
            this._onStartup();
        }

        _onStartup() {
            // UI Persistence: Capture original jQuery functions and find elements early
            this._captureOriginalFunctions();
            this._findSpecificElements(); // Ensure elements are found before patching/observing
            this._patchJQueryFunctions();

            // Load the combined UI
            this._loadInterface();

            // UI Persistence: Setup observers after UI is loaded
            this._setupObservers();
        }

        _loadInterface() {
            const container = domMake.Tree("div");

            // --- Section: Client Command Sender Controls ---
            const clientCmdSection = domMake.Tree("div", { class: "cmd-center-section" }); // Reuse section style
            clientCmdSection.appendChild(domMake.Tree("div", { class: "cmd-center-title" }, ["Enviar Client Cmd"]));
            clientCmdSection.classList.add("client-cmd-controls"); // Add specific class for inputs

            const row1 = domMake.Row();
            this._cmdIdInput = domMake.Tree("input", { type: "number", placeholder: "Command ID (e.g., 10)" });
            row1.appendChild(this._cmdIdInput);
            clientCmdSection.appendChild(row1);

            const row2 = domMake.Row();
            this._param1Input = domMake.Tree("input", { type: "text", placeholder: "Param 1 (e.g., true)" });
            this._param2Input = domMake.Tree("input", { type: "text", placeholder: "Param 2 (e.g., 1)" });
            row2.appendAll(this._param1Input, this._param2Input);
            clientCmdSection.appendChild(row2);

            const row3 = domMake.Row();
            this._param3Input = domMake.Tree("input", { type: "text", placeholder: "Param 3 (e.g., 'text')" });
            const sendButton = domMake.Button("Send CMD");
            sendButton.addEventListener("click", () => this.sendCommand());
            row3.appendAll(this._param3Input, sendButton);
            clientCmdSection.appendChild(row3);

            container.appendChild(clientCmdSection);

            // --- Section: CMD Center (Resources) ---
            const cmdCenterSection = domMake.Tree("div", { class: "cmd-center-section" });
            cmdCenterSection.appendChild(domMake.Tree("div", { class: "cmd-center-title" }, ["CMD Center (Recursos)"]));
            cmdCenterSection.appendChild(domMake.Tree("div", { style: "font-size: 0.85em; margin-bottom: 10px; text-align: center;" }, ["Haz clic para abrir recursos de comandos y propiedades del juego en una nueva pestaña."]));

            const linksContainer = domMake.Tree("div", { class: "cmd-center-links" });

            const listCmdButton = domMake.Button('<i class="fas fa-list"></i> List Command');
            listCmdButton.title = "Abre una lista de comandos comunes de Drawaria.";
            listCmdButton.addEventListener("click", () => this._openExternalLink('https://raw.githubusercontent.com/NuevoMundoOficial/DrawariaWordList/main/list%20command.txt', 'Comandos de Drawaria'));
            linksContainer.appendChild(listCmdButton);

            const dataCenterButton = domMake.Button('<i class="fas fa-database"></i> Data Center (Propiedades del Juego)');
            dataCenterButton.title = "Abre un listado de propiedades y datos internos del juego.";
            dataCenterButton.addEventListener("click", () => this._openExternalLink('https://raw.githubusercontent.com/NuevoMundoOficial/DrawariaWordList/main/data%20center.txt', 'Propiedades de Juego Drawaria'));
            linksContainer.appendChild(dataCenterButton);

            const linksCenterButton = domMake.Button('<i class="fas fa-link"></i> Links Center (Enlaces del Juego)');
            linksCenterButton.title = "Abre una lista de enlaces útiles relacionados con el juego.";
            linksCenterButton.addEventListener("click", () => this._openExternalLink('https://raw.githubusercontent.com/NuevoMundoOficial/DrawariaWordList/main/links%20center.txt', 'Enlaces Útiles Drawaria'));
            linksContainer.appendChild(linksCenterButton);

            cmdCenterSection.appendChild(linksContainer);
            container.appendChild(cmdCenterSection);

            // --- Section: UI Persistence Controls ---
            const uiPersistenceSection = domMake.Tree("div", { class: "ui-persistence-section" });
            uiPersistenceSection.appendChild(domMake.Tree("div", { class: "ui-persistence-section-title" }, ["Control de Visibilidad de UI"]));

            const elementList = domMake.Tree("div", { class: "ui-persistence-element-list" });

            const addToggleButton = (id, labelHtml, iconClass) => {
                const button = domMake.Button('');
                button.classList.add("ui-persistence-toggle-button");
                button.setAttribute("data-persistence-id", id);
                button.setAttribute("data-original-label", labelHtml);

                const icon = domMake.Tree("i");
                icon.className = `fas ${iconClass}`;
                const labelSpan = domMake.Tree("span");
                labelSpan.innerHTML = labelHtml;

                button.appendChild(icon);
                button.appendChild(labelSpan);

                if (this._persistentElements.has(id) && this._persistentElements.get(id) === true) {
                    button.classList.add("active");
                    labelSpan.innerHTML = labelHtml.replace('Mantener', 'Liberar');
                }
                button.addEventListener("click", (e) => this._toggleElementPersistence(id, button, labelHtml));
                elementList.appendChild(button);
                return button;
            };

            addToggleButton('all-popovers', 'Mantener Todos los<br>Popovers', 'fa-comment-dots');
            addToggleButton('all-dropdowns', 'Mantener Todos los<br>Dropdowns', 'fa-caret-square-down');
            addToggleButton('draw-controls-popup', 'Mantener Panel de<br>Pincel', 'fa-paint-brush');
            addToggleButton('chat-new-message-notification', 'Mantener Notif. Chat<br>Nueva', 'fa-bell');
            addToggleButton('top-messages', 'Mantener Mensajes<br>Superiores', 'fa-comment-alt');
            addToggleButton('friends-tabfriendlist', 'Mantener Lista de<br>Amigos', 'fa-user-friends');

            uiPersistenceSection.appendChild(elementList);
            container.appendChild(uiPersistenceSection);

            this.htmlElements.section.appendChild(container);
        }

        // Methods from ClientCommandSender
        parseParam(paramStr) {
            if (paramStr.toLowerCase() === 'true') return true;
            if (paramStr.toLowerCase() === 'false') return false;
            if (!isNaN(paramStr) && paramStr.trim() !== '') return Number(paramStr);
            if (paramStr.startsWith('[') && paramStr.endsWith(']')) {
                try {
                    return JSON.parse(paramStr); // For arrays
                } catch (e) {
                    return paramStr;
                }
            }
            if (paramStr.startsWith('{') && paramStr.endsWith('}')) {
                try {
                    return JSON.parse(paramStr); // For objects
                } catch (e) {
                    return paramStr;
                }
            }
            return paramStr; // Default to string
        }

        sendCommand() {
            const cmdId = parseInt(this._cmdIdInput.value);
            if (isNaN(cmdId)) {
                this.notify("warning", "Command ID must be a number.");
                return;
            }

            const params = [];
            const param1 = this._param1Input.value.trim();
            const param2 = this._param2Input.value.trim();
            const param3 = this._param3Input.value.trim();

            if (param1) params.push(this.parseParam(param1));
            if (param2) params.push(this.parseParam(param2));
            if (param3) params.push(this.parseParam(param3));

            if (globalThis.sockets && globalThis.sockets.length > 0) {
                const payload = ["clientcmd", cmdId, params];
                const dataToSend = `${42}${JSON.stringify(payload)}`;

                globalThis.sockets[0].send(dataToSend);
                this.notify("info", `Custom clientcmd ${cmdId} sent with params: ${JSON.stringify(params)}.`);
            } else {
                this.notify("warning", "No active WebSocket connection found.");
            }
        }

        _openExternalLink(url, title) {
            try {
                const newWindow = window.open(url, '_blank');
                if (newWindow) {
                    this.notify("success", `Abriendo ${title} en una nueva pestaña.`);
                } else {
                    this.notify("warning", "No se pudo abrir la nueva pestaña. Asegúrate de que los bloqueadores de pop-ups estén desactivados.");
                }
            } catch (error) {
                this.notify("error", `Error al intentar abrir el enlace para ${title}: ${error.message}`);
                console.error(`Error opening external link for ${title}:`, error);
            }
        }

        // Methods from UIPersistenceModule
        _captureOriginalFunctions() {
            if (typeof jQuery !== 'undefined' && jQuery.fn) {
                _originalJQueryFnModal = jQuery.fn.modal;
                _originalJQueryFnPopover = jQuery.fn.popover;
                _originalJQueryFnDropdown = jQuery.fn.dropdown;
                _originalJQueryFnCollapse = jQuery.fn.collapse;
            } else {
                this.notify("error", "jQuery o sus plugins Bootstrap no están disponibles. La persistencia de UI puede no funcionar.");
            }
        }

        _patchJQueryFunctions() {
            const self = this;

            if (_originalJQueryFnModal) {
                jQuery.fn.modal = function(action, ...args) {
                    if (action === 'hide' && self._isPersistent(this, 'all-modals')) {
                        self.notify('info', `[UI Persistencia] Bloqueando intento de ocultar modal: #${this.attr('id') || this.attr('class')}.`);
                        if (this.hasClass('show')) {
                            self._forceVisibility(this);
                            const backdrop = jQuery('.modal-backdrop');
                            if (backdrop.length) {
                                backdrop.off('click.dismiss.bs.modal');
                                self._forceVisibility(backdrop);
                            }
                        }
                        return this;
                    }
                    return _originalJQueryFnModal.apply(this, [action, ...args]);
                };
            }

            if (_originalJQueryFnPopover) {
                jQuery.fn.popover = function(action, ...args) {
                    if (typeof action === 'string' && (action === 'hide' || action === 'destroy') && self._isPersistent(this, 'all-popovers')) {
                        self.notify('info', `[UI Persistencia] Bloqueando intento de ocultar popover: ${this.attr('aria-describedby') || this.attr('title')}.`);
                        self._forceVisibility(jQuery(this.attr('aria-describedby') ? `#${this.attr('aria-describedby')}` : this));
                        return this;
                    }
                    return _originalJQueryFnPopover.apply(this, [action, ...args]);
                };
            }

            if (_originalJQueryFnDropdown) {
                jQuery.fn.dropdown = function(action, ...args) {
                    if (typeof action === 'string' && (action === 'hide' || (action === 'toggle' && this.hasClass('show'))) && self._isPersistent(this, 'all-dropdowns')) {
                        self.notify('info', `[UI Persistencia] Bloqueando intento de ocultar dropdown: ${this.attr('id') || this.attr('class')}.`);
                        const menuId = this.attr('aria-labelledby') || this.next('.dropdown-menu').attr('id');
                        self._forceVisibility(jQuery(`#${menuId}, .${menuId}`));
                        return this;
                    }
                    return _originalJQueryFnDropdown.apply(this, [action, ...args]);
                };
            }

            if (_originalJQueryFnCollapse) {
                jQuery.fn.collapse = function(action, ...args) {
                    if (this.is(self._friendsContainerElement) && self._isPersistent(this, 'friends-tabfriendlist')) {
                        if (action === 'hide') {
                            self.notify('info', '[UI Persistencia] Bloqueando intento de ocultar el panel de amigos.');
                            self._forceVisibility(this);
                            return this;
                        }
                    }
                    return _originalJQueryFnCollapse.apply(this, [action, ...args]);
                };
            }
        }

        _findSpecificElements() {
            this._chatattopNewMessageElement = document.getElementById('chatattop-newmessage');
            this._drawControlsPopuplistElement = document.querySelector('.drawcontrols-popuplist');
            this._friendsTabFriendlistElement = document.getElementById('friends-tabfriendlist');
            this._friendsContainerElement = document.getElementById('friends-container');
        }

        _setupObservers() {
            const self = this;

            if (this._drawControlsPopuplistElement) {
                jQuery(this._drawControlsPopuplistElement).off('focusout.persistence').on('focusout.persistence', function(e) {
                    if (self._isPersistent(this, 'draw-controls-popup')) {
                        e.stopImmediatePropagation();
                        self.notify('info', '[UI Persistencia] Previniendo focusout en panel de pincel.');
                        self._forceVisibility(jQuery(this));
                    }
                });
            }

            if (this._chatattopNewMessageElement) {
                const chatNotificationObserver = new MutationObserver((mutations) => {
                    if (self._isPersistent(self._chatattopNewMessageElement, 'chat-new-message-notification')) {
                        for (const mutation of mutations) {
                            if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
                                if (jQuery(self._chatattopNewMessageElement).is(':hidden')) {
                                    self.notify('info', '[UI Persistencia] Forzando visibilidad de notificación de chat.');
                                    self._forceVisibility(jQuery(self._chatattopNewMessageElement));
                                }
                            }
                        }
                    }
                });
                chatNotificationObserver.observe(this._chatattopNewMessageElement, { attributes: true, attributeFilter: ['style'] });
            }

            if (this._friendsTabFriendlistElement && this._friendsContainerElement) {
                const friendsVisibilityObserver = new MutationObserver((mutations) => {
                    if (self._isPersistent(self._friendsTabFriendlistElement, 'friends-tabfriendlist')) {
                        for (const mutation of mutations) {
                            if (mutation.type === 'attributes' && (mutation.attributeName === 'style' || mutation.attributeName === 'class')) {
                                if (jQuery(self._friendsTabFriendlistElement).is(':hidden')) {
                                    self.notify('info', '[UI Persistencia] Forzando visibilidad de la lista de amigos.');
                                    self._forceVisibility(jQuery(self._friendsTabFriendlistElement));
                                }
                                if (jQuery(self._friendsContainerElement).is(':hidden')) {
                                    self.notify('info', '[UI Persistencia] Forzando apertura del contenedor de amigos.');
                                    jQuery(self._friendsContainerElement).collapse('show');
                                }
                            }
                        }
                    }
                });
                friendsVisibilityObserver.observe(this._friendsTabFriendlistElement, { attributes: true, attributeFilter: ['style', 'class'] });
                friendsVisibilityObserver.observe(this._friendsContainerElement, { attributes: true, attributeFilter: ['style', 'class'] });

                const friendsWgElement = jQuery('#friends-wg');
                if (friendsWgElement.length) {
                    friendsWgElement.off('focusout.friendsHide').on('focusout.friendsHide', function(e) {
                         if (self._isPersistent(self._friendsTabFriendlistElement, 'friends-tabfriendlist')) {
                             e.stopImmediatePropagation();
                             self.notify('debug', '[UI Persistencia] Bloqueando focusout de #friends-wg para mantener la lista abierta.');
                             jQuery(self._friendsContainerElement).collapse('show');
                         }
                    });
                }
            }
        }

        _toggleElementPersistence(id, buttonElement, originalLabelHtml) {
            const isCurrentlyPersistent = this._persistentElements.has(id);
            const newActiveState = !isCurrentlyPersistent;

            if (newActiveState) {
                this._persistentElements.set(id, true);
                this.notify("info", `[UI Persistencia] Activada para: ${originalLabelHtml.replace('<br>', ' ')}.`);
            } else {
                this._persistentElements.delete(id);
                this.notify("info", `[UI Persistencia] Desactivada para: ${originalLabelHtml.replace('<br>', ' ')}.`);
            }

            buttonElement.classList.toggle("active", newActiveState);

            const labelSpan = buttonElement.querySelector("span");
            if (labelSpan) {
                labelSpan.innerHTML = newActiveState
                    ? originalLabelHtml.replace('Mantener', 'Liberar')
                    : originalLabelHtml.replace('Liberar', 'Mantener');
            }

            this._applyPersistenceRulesForElement(id, newActiveState);
        }

        _applyPersistenceRulesForElement(id, isActive) {
            const targetElements = this._getElementsForPersistenceId(id);

            targetElements.forEach(el => {
                if (isActive) {
                    if (id === 'all-modals' && jQuery(el).hasClass('modal')) {
                        if (jQuery(el).hasClass('show')) {
                            this._forceVisibility(jQuery(el));
                        }
                    } else if (id === 'all-popovers' || id === 'all-dropdowns' || id === 'draw-controls-popup' || id === 'chat-new-message-notification' || id === 'top-messages') {
                        this._forceVisibility(jQuery(el));
                    } else if (id === 'friends-tabfriendlist' && jQuery(el).is(this._friendsTabFriendlistElement)) {
                        this._forceVisibility(jQuery(el));
                        jQuery(this._friendsContainerElement).collapse('show');
                    }

                    if (el.tagName === 'DETAILS') {
                        jQuery(el).attr('open', true);
                    }
                } else {
                    this._revertVisibility(jQuery(el));
                    if (el.tagName === 'DETAILS') {
                        jQuery(el).attr('open', false);
                    }
                }
            });
        }

        _getElementsForPersistenceId(id) {
            switch (id) {
                case 'all-modals':
                    return [...document.querySelectorAll('.modal.show, .modal-backdrop.show')];
                case 'all-popovers':
                    return [...document.querySelectorAll('.popover.show')];
                case 'all-dropdowns':
                    return [...document.querySelectorAll('.dropdown-menu.show')];
                case 'draw-controls-popup':
                    return this._drawControlsPopuplistElement ? [this._drawControlsPopuplistElement] : [];
                case 'chat-new-message-notification':
                    return this._chatattopNewMessageElement ? [this._chatattopNewMessageElement] : [];
                case 'top-messages':
                    return [...document.querySelectorAll('.topbox')];
                case 'friends-tabfriendlist':
                    return this._friendsTabFriendlistElement ? [this._friendsTabFriendlistElement] : [];
                default:
                    return [];
            }
        }

        _isPersistent(element, categoryId) {
            return this._persistentElements.has(categoryId) && this._persistentElements.get(categoryId) === true;
        }

        _forceVisibility($element) {
            if ($element && $element.length > 0) {
                $element.each((idx, el) => {
                    jQuery(el).css({ 'display': 'block', 'visibility': 'visible', 'opacity': '1' });
                    jQuery(el).removeClass('hide').addClass('show');
                });
            }
        }

        _revertVisibility($element) {
             if ($element && $element.length > 0) {
                 $element.each((idx, el) => {
                     jQuery(el).css({ 'display': '', 'visibility': '', 'opacity': '' });
                     jQuery(el).removeClass('show').addClass('hide');
                 });
             }
        }
    }
})("QBit");

// END CMD

// START Telemetry

(function AdvancedTranslateTelemetryModule() {
    const QBit = globalThis[arguments[0]];

    // Helper for making XHR GET requests and parsing JSON (from original AutoTranslate Chat)
    function _xhrGetJson(url, callback) {
        const req = new XMLHttpRequest();
        req.onload = (e) => {
            const response = req.response;
            if (!callback) return;
            try {
                callback(JSON.parse(response));
            } catch (error) {
                console.error("AdvancedTranslateTelemetry: Error parsing JSON response", error);
            }
        };
        req.onerror = (e) => {
            console.error("AdvancedTranslateTelemetry: XHR error", e);
        };
        req.open("GET", url);
        req.send();
    }

    // Language data (from original Translator Menu Full)
    const _translationLanguages = {
        "en": "English", "es": "Spanish", "fr": "French", "de": "German", "it": "Italian", "pt": "Portuguese",
        "ru": "Russian", "zh-CN": "Chinese (Simplified)", "ja": "Japanese", "ko": "Korean", "ar": "Arabic",
        "hi": "Hindi", "bn": "Bengali", "tr": "Turkish", "pl": "Polish", "nl": "Dutch", "sv": "Swedish",
        "da": "Danish", "no": "Norwegian", "fi": "Finnish", "el": "Greek", "he": "Hebrew", "id": "Indonesian",
        "ms": "Malay", "th": "Thai", "vi": "Vietnamese", "uk": "Ukrainian", "cs": "Czech", "hu": "Hungarian",
        "ro": "Romanian", "af": "Afrikaans", "sq": "Albanian", "am": "Amharic", "hy": "Armenian", "az": "Azerbaijani",
        "eu": "Basque", "be": "Belarusian", "bs": "Bosnian", "bg": "Bulgarian", "ca": "Catalan", "ceb": "Cebuano",
        "ny": "Chichewa", "co": "Corsican", "hr": "Croatian", "cz": "Czech (Legacy)", "eo": "Esperanto", "et": "Estonian",
        "tl": "Filipino", "fy": "Frisian", "gl": "Galician", "ka": "Georgian", "gu": "Gujarati", "ht": "Haitian Creole",
        "ha": "Hausa", "haw": "Hawaiian", "iw": "Hebrew (Legacy)", "hmn": "Hmong", "is": "Icelandic", "ig": "Igbo",
        "ga": "Irish", "jw": "Javanese", "kn": "Kannada", "kk": "Kazakh", "km": "Khmer", "ku": "Kurdish (Kurmanji)",
        "ky": "Kyrgyz", "lo": "Lao", "la": "Latin", "lv": "Latvian", "lt": "Lithuanian", "lb": "Luxembourgish",
        "mk": "Macedonian", "mg": "Malagasy", "ml": "Malayalam", "mt": "Maltese", "mi": "Maori", "mr": "Marathi",
        "mn": "Mongolian", "my": "Myanmar (Burmese)", "ne": "Nepali", "ps": "Pashto", "fa": "Persian", "pa": "Punjabi",
        "sm": "Samoan", "gd": "Scots Gaelic", "sr": "Serbian", "st": "Sesotho", "sn": "Shona", "sd": "Sindhi",
        "si": "Sinhala", "sk": "Slovak", "sl": "Slovenian", "so": "Somali", "su": "Sundanese", "sw": "Swahili",
        "tg": "Tajik", "ta": "Tamil", "te": "Telugu", "uz": "Uzbek", "xh": "Xhosa", "yi": "Yiddish", "yo": "Yoruba",
        "zu": "Zulu"
    };

    QBit.Styles.addRules([
        `#${QBit.identifier} .telemetry-section {
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            padding: 5px;
            margin-bottom: 10px;
            background-color: var(--CE-bg_color);
            max-width: 350px; /* NEW: Limit the maximum width of each section */
            margin-left: auto; /* Center the sections within their parent */
            margin-right: auto;
        }`,
        `#${QBit.identifier} .telemetry-section-title {
            font-weight: bold;
            margin-bottom: 5px;
            color: var(--dark-blue-title);
            text-align: center;
        }`,
        // AutoTranslate Chat specific styles
        `#${QBit.identifier} .auto-translate-status {
            font-size: 0.9em;
            color: var(--success);
            text-align: center;
            margin-bottom: 10px;
        }`,
        // Translator Menu specific styles
        `#${QBit.identifier} .dtr-textarea {
            width: 100%;
            padding: 8px;
            box-sizing: border-box;
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            resize: vertical;
            min-height: 60px;
            background-color: var(--CE-bg_color);
            color: var(--CE-color);
            font-size: 0.95em;
            line-height: 1.4;
        }`,
        `#${QBit.identifier} .dtr-custom-dropdown {
            position: relative;
            width: 100%; /* Occupy full width of its parent section */
            font-size: 0.95em;
            margin-bottom: 10px;
        }`,
        `#${QBit.identifier} .dtr-selected-language-display {
            width: calc(100% - 18px);
            padding: 8px 10px;
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            background-color: var(--CE-bg_color);
            color: var(--CE-color);
            cursor: pointer;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }`,
        `#${QBit.identifier} .dtr-dropdown-panel {
            position: absolute;
            top: 100%;
            left: 0;
            width: 100%; /* Occupy full width of dtr-custom-dropdown */
            /* max-width is now handled by the parent .telemetry-section */
            background-color: var(--CE-bg_color);
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
            z-index: 1000;
            max-height: 250px;
            overflow-y: auto;
            display: none;
            flex-direction: column;
            padding: 5px;
        }`,
        `#${QBit.identifier} .dtr-language-search-input {
            width: calc(100% - 16px);
            padding: 8px;
            box-sizing: border-box;
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            margin-bottom: 5px;
            background-color: var(--light-gray-bg);
            color: var(--dark-text);
            font-size: 0.9em;
        }`,
        `#${QBit.identifier} .dtr-lang-item {
            padding: 8px 10px;
            cursor: pointer;
            white-space: nowrap;
            overflow: hidden;
            text-overflow: ellipsis;
            border-radius: .20rem;
        }`,
        `#${QBit.identifier} .dtr-lang-item:hover {
            background-color: var(--light);
        }`,
        `#${QBit.identifier} .dtr-button {
            padding: 10px 15px;
            border: none;
            border-radius: .25rem;
            color: white;
            cursor: pointer;
            transition: background-color 0.2s ease;
            font-size: 1em;
            font-weight: 600;
            margin-bottom: 5px;
        }`,
        `#${QBit.identifier} .dtr-translate-button { background-color: var(--info); }`,
        `#${QBit.identifier} .dtr-translate-button:hover { background-color: var(--dark-info); }`,
        `#${QBit.identifier} .dtr-send-button { background-color: var(--primary); }`,
        `#${QBit.identifier} .dtr-send-button:hover { background-color: var(--dark-primary); }`,

        // Advanced Telemetry specific styles
        `#${QBit.identifier} .player-metrics-list {
            max-height: 150px;
            overflow-y: auto;
            border: 1px solid var(--CE-color);
            padding: 5px;
            font-size: 0.85em;
            background-color: var(--CE-bg_color);
            color: var(--CE-color);
        }`,
        `#${QBit.identifier} .snapshot-previews {
            display: flex;
            flex-wrap: wrap;
            gap: 5px;
            margin-top: 5px;
            border: 1px dashed var(--CE-color);
            padding: 5px;
            min-height: 60px;
            align-items: center;
        }`,
        `#${QBit.identifier} .snapshot-previews img {
            width: 50px;
            height: 50px;
            border: 1px solid #ccc;
            cursor: pointer;
            object-fit: cover;
        }`,
        `#${QBit.identifier} .hud-controls {
            display: flex;
            gap: 10px;
            align-items: center;
            margin-top: 5px;
        }`,
        `#${QBit.identifier} .hud-controls label {
            flex-shrink: 0;
        }`,
        `#${QBit.identifier} .hud-controls input[type="color"] {
            flex-grow: 1;
            border: 1px solid var(--CE-color);
            border-radius: .25rem;
            background-color: var(--CE-bg_color);
        }`
    ]);

    class AdvancedTranslateTelemetry extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        // --- AutoTranslate Chat Properties ---
        _chatObserver = null;

        // --- Translator Menu Properties ---
        _currentSelectedLanguageCode = "es";
        _translatorUi = { // Renamed to avoid conflict with QBit's _ui
            inputText: null,
            selectedLanguageDisplay: null,
            dropdownPanel: null,
            languageSearchInput: null,
            languageList: null,
            translateButton: null,
            outputText: null,
            sendButton: null,
        };
        _languageItems = {};

        // --- Advanced Telemetry Properties ---
        _playerMetricsContainer;
        _snapshotContainer;
        _snapshots = [];
        _maxSnapshots = 3;

        constructor() {
            super("Traductor y Telemetría", '<i class="fas fa-chart-line"></i>'); // Combined icon
            this._onStartup();
        }

        _onStartup() {
            this._loadInterface();
            this._setupAutoTranslateHooks(); // For automatic chat translation
            this._attachMenuEventListeners(); // For the manual translation menu
            this._listenToGameEvents(); // For telemetry data
            this.updatePlayerMetrics(); // Initial update for player metrics
            this.updateSnapshotPreviews(); // Initial update for snapshots
            this.notify("info", "Módulo 'Traductor y Telemetría' cargado.");
        }

        _loadInterface() {
            const container = domMake.Tree("div");

            // --- Section: AutoTranslate Chat ---
            const autoTranslateSection = domMake.Tree("div", { class: "telemetry-section" });
            autoTranslateSection.appendChild(domMake.Tree("div", { class: "telemetry-section-title" }, ["Traducción Automática de Chat"]));
            autoTranslateSection.appendChild(domMake.Tree("div", { class: "auto-translate-status" }, ["Estado: Activo y escuchando mensajes."]));
            container.appendChild(autoTranslateSection);

            // --- Section: Translator Menu Full ---
            const translatorMenuSection = domMake.Tree("div", { class: "telemetry-section" });
            translatorMenuSection.appendChild(domMake.Tree("div", { class: "telemetry-section-title" }, ["Menú de Traducción Completo"]));

            this._translatorUi.inputText = domMake.Tree('textarea', {
                placeholder: "Ingresa texto para traducir...", rows: 3, id: "dtrInputText",
                class: "dtr-textarea"
            });
            this._translatorUi.inputText.addEventListener('keydown', (e) => e.stopPropagation());
            translatorMenuSection.appendChild(this._translatorUi.inputText);

            const dtrCustomLanguageDropdown = domMake.Tree('div', { class: 'dtr-custom-dropdown' });
            this._translatorUi.selectedLanguageDisplay = domMake.Tree('div', { class: 'dtr-selected-language-display' }, [
                domMake.Tree('span', {}, [_translationLanguages[this._currentSelectedLanguageCode]]),
                domMake.Tree('span', {style: 'font-size: 0.8em; margin-left: 5px;'}, ['▼'])
            ]);
            this._translatorUi.dropdownPanel = domMake.Tree('div', { class: 'dtr-dropdown-panel' });
            this._translatorUi.languageSearchInput = domMake.Tree('input', {
                type: 'text', placeholder: 'Buscar idiomas...', id: 'dtrLanguageSearchInput',
                class: 'dtr-language-search-input'
            });
            this._translatorUi.languageSearchInput.addEventListener('keydown', (e) => e.stopPropagation());
            this._translatorUi.languageList = domMake.Tree('div', { id: 'dtrLanguageList', style: 'display: flex; flex-direction: column; gap: 2px;' });

            this._populateLanguageList();

            this._translatorUi.dropdownPanel.appendAll(this._translatorUi.languageSearchInput, this._translatorUi.languageList);
            dtrCustomLanguageDropdown.appendAll(this._translatorUi.selectedLanguageDisplay, this._translatorUi.dropdownPanel);
            translatorMenuSection.appendChild(dtrCustomLanguageDropdown);

            this._translatorUi.translateButton = domMake.Tree('button', { class: "dtr-button dtr-translate-button" }, ["Traducir"]);
            translatorMenuSection.appendChild(this._translatorUi.translateButton);

            this._translatorUi.outputText = domMake.Tree('textarea', {
                placeholder: "La traducción aparecerá aquí...", rows: 3, readonly: true, id: "dtrOutputText",
                class: "dtr-textarea"
            });
            translatorMenuSection.appendChild(this._translatorUi.outputText);

            this._translatorUi.sendButton = domMake.Tree('button', { class: "dtr-button dtr-send-button" }, ["Enviar Traducción"]);
            translatorMenuSection.appendChild(this._translatorUi.sendButton);

            container.appendChild(translatorMenuSection);

            // --- Section: Player Metrics (from AdvancedTelemetry) ---
            const playerMetricsSection = domMake.Tree("div", { class: "telemetry-section" });
            playerMetricsSection.appendChild(domMake.Tree("div", { class: "telemetry-section-title" }, ["Métricas de Jugadores"]));
            this._playerMetricsContainer = domMake.Tree("div", { class: "player-metrics-list" });
            playerMetricsSection.appendChild(this._playerMetricsContainer);
            container.appendChild(playerMetricsSection);

            // --- Section: Visual History (from AdvancedTelemetry) ---
            const visualHistorySection = domMake.Tree("div", { class: "telemetry-section" });
            visualHistorySection.appendChild(domMake.Tree("div", { class: "telemetry-section-title" }, ["Historial Visual de Lienzo"]));
            const captureSnapshotButton = domMake.Button("Capturar Lienzo");
            captureSnapshotButton.title = "Guarda una imagen del lienzo actual.";
            captureSnapshotButton.addEventListener("click", () => this.captureCanvasSnapshot());
            visualHistorySection.appendChild(captureSnapshotButton);
            this._snapshotContainer = domMake.Tree("div", { class: "snapshot-previews icon-list" });
            visualHistorySection.appendChild(this._snapshotContainer);
            container.appendChild(visualHistorySection);

            // --- Section: Dynamic HUD Themes (from AdvancedTelemetry) ---
            const hudThemesSection = domMake.Tree("div", { class: "telemetry-section" });
            hudThemesSection.appendChild(domMake.Tree("div", { class: "telemetry-section-title" }, ["Temas Dinámicos del HUD"]));
            const hudColorControls = domMake.Tree("div", { class: "hud-controls" });
            const hudColorLabel = domMake.Tree("label", {}, ["Color del HUD:"]);
            const hudColorInput = domMake.Tree("input", { type: "color", value: "#007bff" });
            hudColorInput.addEventListener("change", (e) => {
                const newColor = e.target.value;
                document.documentElement.style.setProperty('--primary', newColor);
                document.documentElement.style.setProperty('--success', newColor);
                this.notify("info", `Color del HUD cambiado a: ${newColor}`);
            });
            hudColorControls.appendAll(hudColorLabel, hudColorInput);
            hudThemesSection.appendChild(hudColorControls);
            container.appendChild(hudThemesSection);

            this.htmlElements.section.appendChild(container);
        }

        // --- AutoTranslate Chat Methods ---
        _setupAutoTranslateHooks() {
            const chatboxMessages = document.querySelector("#chatbox_messages");
            if (chatboxMessages) {
                this._chatObserver = new MutationObserver((mutations) => {
                    mutations.forEach((record) => {
                        record.addedNodes.forEach((node) => {
                            if (node.nodeType === 1 && !node.classList.contains("systemchatmessage5")) {
                                this._processChatMessageNode(node);
                            }
                        });
                    });
                });
                this._chatObserver.observe(chatboxMessages, { childList: true, subtree: false });
                this._addSystemMessage("Traducción automática de chat: activada (con info. de idioma).");
            } else {
                this.notify("warning", "Contenedor de mensajes de chat (#chatbox_messages) no encontrado. La traducción automática podría no funcionar.");
            }
        }

        _processChatMessageNode(node) {
            const messageElement = node.querySelector(".playerchatmessage-text");
            if (messageElement) {
                const textToTranslate = messageElement.textContent;
                this._translateText(textToTranslate, "auto", "en", (translation, detectedLang) => {
                    this._applyTranslationAsTooltip(translation, detectedLang, messageElement);
                });
            }

            const nameElement = node.querySelector(".playerchatmessage-name");
            if (nameElement) {
                const nameToTranslate = nameElement.textContent;
                this._translateText(nameToTranslate, "auto", "en", (translation, detectedLang) => {
                    this._applyTranslationAsTooltip(translation, detectedLang, nameElement);
                });
            }

            const selfnameElement = node.querySelector(".playerchatmessage-selfname");
            if (selfnameElement) {
                const nameToTranslate = selfnameElement.textContent;
                this._translateText(nameToTranslate, "auto", "en", (translation, detectedLang) => {
                    this._applyTranslationAsTooltip(translation, detectedLang, selfnameElement);
                });
            }
        }

        _applyTranslationAsTooltip(translatedText, detectedLangCode, targetNode) {
            const langName = new Intl.DisplayNames(['en'], { type: 'language' }).of(detectedLangCode);
            let tooltipText = translatedText;

            if (detectedLangCode !== 'en' && detectedLangCode !== 'auto' && langName && langName !== detectedLangCode) {
                tooltipText += ` (${langName})`;
            }
            targetNode.title = tooltipText;
        }

        _addSystemMessage(message) {
            const loggingContainer = document.getElementById("chatbox_messages");
            if (loggingContainer) {
                const chatmessage = domMake.Tree(
                    "div",
                    { class: `chatmessage systemchatmessage5`, "data-ts": Date.now(), style: `color: var(--info);` },
                    [message]
                );
                loggingContainer.appendChild(chatmessage);
                loggingContainer.scrollTop = loggingContainer.scrollHeight;
            }
        }

        // --- Shared Translation Function ---
        _translateText(textToTranslate, fromLang = "auto", toLang = "en", callback) {
            const url =
                "https://translate.googleapis.com/translate_a/single?client=gtx&sl=" +
                fromLang +
                "&tl=" +
                toLang +
                "&dt=t&q=" +
                encodeURI(textToTranslate);

            _xhrGetJson(url, (data) => {
                if (data && data[0] && data[0][0] && data[0][0][0]) {
                    const translatedText = data[0][0][0];
                    const detectedSourceLanguage = data[2] || 'unknown';
                    callback(translatedText, detectedSourceLanguage);
                } else {
                    console.warn("AdvancedTranslateTelemetry: Could not get translation for:", textToTranslate, data);
                    callback(textToTranslate, 'unknown');
                }
            });
        }

        // --- Translator Menu Methods ---
        _populateLanguageList() {
            this._translatorUi.languageList.innerHTML = '';
            for (const langCode in _translationLanguages) {
                const langName = _translationLanguages[langCode];
                const langItem = domMake.Tree('div', {
                    class: 'dtr-lang-item',
                    'data-lang-code': langCode,
                }, [langName]);

                langItem.addEventListener('click', (e) => {
                    e.stopPropagation();
                    this._currentSelectedLanguageCode = langCode;
                    this._translatorUi.selectedLanguageDisplay.querySelector('span:first-child').textContent = langName;
                    this._translatorUi.dropdownPanel.style.display = 'none';
                    this.notify("info", `Idioma de traducción manual configurado a: ${langName} (${langCode}).`);
                });
                this._translatorUi.languageList.appendChild(langItem);
                this._languageItems[langCode] = langItem;
            }
        }

        _attachMenuEventListeners() {
            this._translatorUi.selectedLanguageDisplay.addEventListener('click', (e) => {
                e.stopPropagation();
                this._translatorUi.dropdownPanel.style.display = this._translatorUi.dropdownPanel.style.display === 'none' ? 'flex' : 'none';
                if (this._translatorUi.dropdownPanel.style.display === 'flex') {
                    this._translatorUi.languageSearchInput.focus();
                    this._translatorUi.languageSearchInput.value = '';
                    this._translatorUi.languageSearchInput.dispatchEvent(new Event('input'));
                }
            });

            document.addEventListener('click', (e) => {
                const dropdownContainer = this._translatorUi.selectedLanguageDisplay.parentNode;
                if (dropdownContainer && !dropdownContainer.contains(e.target) && this._translatorUi.dropdownPanel.style.display === 'flex') {
                    this._translatorUi.dropdownPanel.style.display = 'none';
                }
            });

            this._translatorUi.languageSearchInput.addEventListener('input', () => {
                const searchTerm = this._translatorUi.languageSearchInput.value.toLowerCase();
                for (const langCode in _translationLanguages) {
                    const langName = _translationLanguages[langCode].toLowerCase();
                    if (langName.startsWith(searchTerm)) {
                        this._languageItems[langCode].style.display = 'block';
                    } else {
                        this._languageItems[langCode].style.display = 'none';
                    }
                }
            });

            this._translatorUi.translateButton.addEventListener("click", () => {
                const textToTranslate = this._translatorUi.inputText.value.trim();
                const toLang = this._currentSelectedLanguageCode;
                if (textToTranslate) {
                    this.notify("info", `Traduciendo texto a ${_translationLanguages[toLang]} (${toLang})...`);
                    this._translateText(textToTranslate, "auto", toLang, (translatedText) => {
                        this._translatorUi.outputText.value = translatedText;
                        this.notify("success", "Traducción completa.");
                    });
                } else {
                    this._translatorUi.outputText.value = "Por favor, introduce texto para traducir.";
                    this.notify("warning", "No hay texto para traducir.");
                }
            });

            this._translatorUi.sendButton.addEventListener("click", () => {
                const translatedText = this._translatorUi.outputText.value;
                if (translatedText) {
                    const chatInput = document.getElementById('chatbox_textinput');
                    if (chatInput) {
                        chatInput.value = translatedText;
                        const event = new KeyboardEvent('keydown', {
                            key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true
                        });
                        chatInput.dispatchEvent(event);
                        this.notify("info", "Traducción enviada al chat.");

                        const originalText = this._translatorUi.sendButton.textContent;
                        this._translatorUi.sendButton.textContent = "¡Enviado!";
                        setTimeout(() => {
                            this._translatorUi.sendButton.textContent = originalText;
                            this._translatorUi.inputText.value = "";
                            this._translatorUi.outputText.value = "";
                        }, 1500);

                    } else {
                        this.notify("error", "No se encontró el elemento de entrada de chat (#chatbox_textinput).");
                        const originalText = this._translatorUi.sendButton.textContent;
                        this._translatorUi.sendButton.textContent = "¡Chat no encontrado!";
                        setTimeout(() => { this._translatorUi.sendButton.textContent = originalText; }, 1500);
                    }
                } else {
                    this.notify("warning", "No hay nada que enviar.");
                }
            });
        }

        // --- Advanced Telemetry Methods ---
        _listenToGameEvents() {
            const playerListElement = document.getElementById("playerlist");
            if (playerListElement) {
                const observer = new MutationObserver(() => this.updatePlayerMetrics());
                observer.observe(playerListElement, { childList: true, subtree: true });
            }

            const chatboxMessages = document.getElementById("chatbox_messages");
            if (chatboxMessages) {
                const chatObserver = new MutationObserver((mutations) => {
                    mutations.forEach(mutation => {
                        mutation.addedNodes.forEach(node => {
                            if (node.classList && node.classList.contains('chatmessage') && !node.classList.contains('systemchatmessage5')) {
                                const playerNameElement = node.querySelector('.playerchatmessage-name a');
                                const playerName = playerNameElement ? playerNameElement.textContent : 'Unknown';
                                // this.updatePlayerActivity(playerName); // Commented out as it's a visual flash
                            }
                        });
                    });
                });
                chatObserver.observe(chatboxMessages, { childList: true });
            }
        }

        updatePlayerMetrics() {
            this._playerMetricsContainer.innerHTML = '';
            const playerRows = document.querySelectorAll("#playerlist .playerlist-row");
            if (playerRows.length === 0) {
                this._playerMetricsContainer.appendChild(domMake.TextNode("No hay jugadores en la sala."));
                return;
            }

            playerRows.forEach(playerRow => {
                const playerId = playerRow.dataset.playerid;
                const playerName = playerRow.querySelector(".playerlist-name a")?.textContent || `Player ${playerId}`;
                const score = playerRow.querySelector(".playerlist-rank")?.textContent || 'N/A';
                const turnScore = playerRow.querySelector(".playerlist-turnscore")?.textContent || 'N/A';

                const metricItem = domMake.Tree("div", { style: "margin: 2px 0; font-size: 0.8rem;" }, [
                    domMake.Tree("strong", {}, [`${playerName} (ID: ${playerId}): `]),
                    domMake.TextNode(`Puntuación: ${score}, Turno: ${turnScore}`)
                ]);
                this._playerMetricsContainer.appendChild(metricItem);
            });
        }

        // _updatePlayerActivity(playerName) { // Kept for reference but not directly used in UI
        //     this.notify("debug", `Player activity detected: ${playerName}`);
        //     const playerElements = document.querySelectorAll(`#playerlist .playerlist-row .playerlist-name a`);
        //     playerElements.forEach(el => {
        //         if (el.textContent === playerName) {
        //             el.closest('.playerlist-row').style.backgroundColor = 'rgba(0, 255, 0, 0.1)';
        //             setTimeout(() => {
        //                 el.closest('.playerlist-row').style.backgroundColor = '';
        //             }, 500);
        //         }
        //     });
        // }

        captureCanvasSnapshot() {
            const gameCanvas = document.body.querySelector("canvas#canvas");
            if (!gameCanvas) {
                this.notify("error", "Lienzo de juego no encontrado para capturar.");
                return;
            }

            try {
                const base64Image = gameCanvas.toDataURL("image/png");
                const timestamp = new Date().toLocaleString();

                this._snapshots.push({ data: base64Image, timestamp: timestamp });
                if (this._snapshots.length > this._maxSnapshots) {
                    this._snapshots.shift();
                }
                this.updateSnapshotPreviews();
                this.notify("success", `Instantánea del lienzo capturada: ${timestamp}`);
            } catch (e) {
                this.notify("error", `Error al capturar el lienzo: ${e.message}`);
                console.error("Canvas snapshot error:", e);
            }
        }

        updateSnapshotPreviews() {
            this._snapshotContainer.innerHTML = '';
            if (this._snapshots.length === 0) {
                this._snapshotContainer.appendChild(domMake.TextNode("No hay instantáneas guardadas."));
                return;
            }

            this._snapshots.forEach((snapshot, index) => {
                const img = domMake.Tree("img", {
                    src: snapshot.data,
                    style: "width: 50px; height: 50px; border: 1px solid #ccc; margin: 2px; cursor: pointer;",
                    title: `Instantánea ${index + 1}: ${snapshot.timestamp}`
                });
                img.addEventListener("click", () => this.displaySnapshot(snapshot.data));
                this._snapshotContainer.appendChild(img);
            });
        }

        displaySnapshot(imageData) {
            const overlay = domMake.Tree("div", {
                style: `
                    position: fixed; top: 0; left: 0; width: 100%; height: 100%;
                    background: rgba(0,0,0,0.8); z-index: 10000;
                    display: flex; justify-content: center; align-items: center;
                `
            });
            const img = domMake.Tree("img", {
                src: imageData,
                style: `max-width: 90%; max-height: 90%; border: 2px solid white;`
            });
            overlay.appendChild(img);
            overlay.addEventListener("click", () => overlay.remove());
            document.body.appendChild(overlay);
        }
    }
})("QBit");

// END AdvancedTranslateTelemetry


// --- START CHAT ---
(function AdvancedChatEnhancementsModule() {
    const QBit = globalThis.QBit;

    // --- Character maps for Fancy Text ---
    const FANCY_TEXT_MAPS = {
        'script': { 'A': '𝒜', 'B': 'ℬ', 'C': '𝒞', 'D': '𝒟', 'E': 'ℰ', 'F': 'ℱ', 'G': '𝒢', 'H': 'ℋ', 'I': 'ℐ', 'J': '𝒥', 'K': '𝒦', 'L': 'ℒ', 'M': 'ℳ', 'N': '𝒩', 'O': '𝒪', 'P': '𝒫', 'Q': '𝒬', 'R': 'ℛ', 'S': '𝒮', 'T': '𝒯', 'U': '𝒰', 'V': '𝒱', 'W': '𝒲', 'X': '𝒳', 'Y': '𝒴', 'Z': '𝒵', 'a': '𝒶', 'b': '𝒷', 'c': '𝒸', 'd': '𝒹', 'e': 'ℯ', 'f': '𝒻', 'g': '𝑔', 'h': '𝒽', 'i': '𝒾', 'j': '𝒿', 'k': '𝓀', 'l': '𝓁', 'm': '𝓂', 'n': '𝓃', 'o': 'ℴ', 'p': '𝓅', 'q': '𝓆', 'r': '𝓇', 's': '𝓈', 't': '𝓉', 'u': '𝓊', 'v': '𝓋', 'w': '𝓌', 'x': '𝓍', 'y': '𝓎', 'z': '𝓏', '0': '𝟢', '1': '𝟣', '2': '𝟤', '3': '𝟥', '4': '𝟦', '5': '𝟧', '6': '𝟨', '7': '𝟩', '8': '𝟪', '9': '𝟫' },
        'fraktur': { 'A': '𝔄', 'B': '𝔅', 'C': 'ℭ', 'D': '𝔇', 'E': '𝔈', 'F': '𝔉', 'G': '𝔊', 'H': 'ℌ', 'I': 'ℑ', 'J': '𝔍', 'K': '𝔎', 'L': '𝔏', 'M': '𝔐', 'N': '𝔑', 'O': '𝔒', 'P': '𝔓', 'Q': '𝔔', 'R': 'ℜ', 'S': '𝔖', 'T': '𝔗', 'U': '𝔘', 'V': '𝔙', 'W': '𝔚', 'X': '𝔛', 'Y': '𝔜', 'Z': 'ℨ', 'a': '𝔞', 'b': '𝔟', 'c': '𝔠', 'd': '𝔡', 'e': '𝔢', 'f': '𝔣', 'g': '𝔤', 'h': '𝔥', 'i': '𝔦', 'j': '𝔧', 'k': '𝔨', 'l': '𝔩', 'm': '𝔪', 'n': '𝔫', 'o': '𝔬', 'p': '𝔭', 'q': '𝔮', 'r': '𝔯', 's': '𝔰', 't': '𝔱', 'u': '𝔲', 'v': '𝔳', 'w': '𝔴', 'x': '𝔵', 'y': '𝔶', 'z': '𝔷', '0': '𝟘', '1': '𝟙', '2': '𝟚', '3': '𝟛', '4': '𝟜', '5': '𝟝', '6': '𝟞', '7': '𝟩', '8': '𝟪', '9': '𝟡' },
        'monospace': { 'A': '𝙰', 'B': '𝙱', 'C': '𝙲', 'D': '𝙳', 'E': '𝙴', 'F': '𝙵', 'G': '𝙶', 'H': '𝙷', 'I': '𝙸', 'J': '𝙹', 'K': '𝙺', 'L': '𝙻', 'M': '𝙼', 'N': '𝙽', 'O': '𝙾', 'P': '𝙿', 'Q': '𝚀', 'R': '𝚁', 'S': '𝚂', 'T': '𝚃', 'U': '𝚄', 'V': '𝚅', 'W': '𝚆', 'X': '𝚇', 'Y': '𝚈', 'Z': '𝚉', 'a': '𝚊', 'b': '𝚋', 'c': '𝚌', 'd': '𝚍', 'e': '𝚎', 'f': '𝚏', 'g': '𝚐', 'h': '𝚑', 'i': '𝚒', 'j': '𝚓', 'k': '𝚔', 'l': '𝚕', 'm': '𝚖', 'n': '𝚗', 'o': '𝚘', 'p': '𝚙', 'q': '𝚚', 'r': '𝚛', 's': '𝚜', 't': '𝚝', 'u': '𝚞', 'v': '𝚟', 'w': '𝚠', 'x': '𝚡', 'y': '𝚢', 'z': '𝚣', '0': '𝟶', '1': '𝟷', '2': '𝟸', '3': '𝟹', '4': '𝟺', '5': '𝟻', '6': '𝟼', '7': '𝟽', '8': '𝟾', '9': '𝟿' },
        'bold': { 'A': '𝗔', 'B': '𝗕', 'C': '𝗖', 'D': '𝗗', 'E': '𝗘', 'F': '𝗙', 'G': '𝗚', 'H': '𝗛', 'I': '𝗜', 'J': '𝗝', 'K': '𝗞', 'L': '𝗟', 'M': '𝗠', 'N': '𝗡', 'O': '𝗢', 'P': '𝗣', 'Q': '𝗤', 'R': '𝗥', 'S': '𝗦', 'T': '𝗧', 'U': '𝗨', 'V': '𝗩', 'W': '𝗪', 'X': '𝗫', 'Y': '𝗬', 'Z': '𝗭', 'a': '𝗮', 'b': '𝗯', 'c': '𝗰', 'd': '𝗱', 'e': '𝗲', 'f': '𝗳', 'g': '𝗴', 'h': '𝗵', 'i': '𝗶', 'j': '𝗷', 'k': '𝗸', 'l': '𝗹', 'm': '𝗺', 'n': '𝗻', 'o': '𝗼', 'p': '𝗽', 'q': '𝗾', 'r': '𝗿', 's': '𝘀', 't': '𝘁', 'u': '𝘂', 'v': '𝘃', 'w': '𝘄', 'x': '𝘅', 'y': '𝘆', 'z': '𝘇', '0': '𝟬', '1': '𝟭', '2': '𝟮', '3': '𝟯', '4': '𝟰', '5': '𝟱', '6': '𝟲', '7': '𝟳', '8': '𝟴', '9': '𝟵' },
        'italic': { 'A': '𝘈', 'B': '𝘉', 'C': '𝘊', 'D': '𝘋', 'E': '𝘌', 'F': '𝘍', 'G': '𝘎', 'H': '𝘏', 'I': '𝘐', 'J': '𝘑', 'K': '𝘒', 'L': '𝘓', 'M': '𝘔', 'N': '𝘕', 'O': '𝘖', 'P': '𝘗', 'Q': '𝘘', 'R': '𝘙', 'S': '𝘚', 'T': '𝘛', 'U': '𝘜', 'V': '𝘝', 'W': '𝘞', 'X': '𝘟', 'Y': '𝘠', 'Z': '𝘡', 'a': '𝘢', 'b': '𝘣', 'c': '𝘤', 'd': '𝘥', 'e': '𝘦', 'f': '𝘧', 'g': '𝘨', 'h': '𝘩', 'i': '𝘪', 'j': '𝘫', 'k': '𝘬', 'l': '𝘭', 'm': '𝘮', 'n': '𝘯', 'o': '𝘰', 'p': '𝘱', 'q': '𝘲', 'r': '𝘳', 's': '𝘴', 't': '𝘵', 'u': '𝘶', 'v': '𝘷', 'w': '𝘸', 'x': '𝘹', 'y': '𝘺', 'z': '𝘻' }
    };

    // --- ASCIImoji Database ---
    const ASCII_MOJIS = {
        'Saludos y Caras': ['(•◡•)/', '(* ^ ω ^)', '(´ ∀ ` *)', '(─‿‿─)', '(⌒‿⌒)', '(*¯︶¯*)', '(o^▽^o)', '٩(◕‿◕。)۶', '\(٥⁀▽⁀ )/', '(*°▽°*)', '╰(▔∀▔)╯', 'ヽ(>∀<☆)ノ', '(✧ω✧)', '(´。• ᵕ •。`)', '( ´ ▽ ` )', '( ̄▽ ̄)', '( ´ ω ` )', '(*´▽`*)', '٩(。•́‿•̀。)۶', '(*¯ ³¯*)', '( ´ ▽ ` )ノ', '( ´ ▽ ` )b', '(^▽^)', '( ̄ω ̄)', '(*•ω•*)', 'σ(≧ε≦σ)'],
        'Shrugs y Duda': ['¯\\_(ツ)_/¯', '┐( ´ д ` )┌', 'ヽ(ー_ー)ノ', '┐( ̄ヘ ̄)┌', '┐( ´ , ` )┌', 'ʅ(°_°)ʃ', '┐( ̄~ ̄)┌', 'ლ(ಠ_ಠლ)', '┐(˘_˘)┌', 'ლ(¯ロ¯"ლ)', '┐( ´・_・`)┌', '┐(‘~` )┌'],
        'Amor y Afecto': ['(。♥‿♥。)', '(´ ε ` )♡', '(´• ω •`) ♡', '(ღ˘⌣˘ღ)', '♥( ´ ▽ ` )ノ', '(♡ >ω< ♡)', '(´,,•ω•,,)♡', '(´ ω `♡)', '(´。• ᵕ •。`) ♡', '( ´・ω・)ノ(._.`)', '(づ ̄ ³ ̄)づ', '( T_T)\(^-^ )', '(づ ◕‿◕ )づ', '(づ。◕‿‿◕。)づ', '(づ  ̄ ³ ̄)づ', '(*^3^)/~♡', '(ノ´ з `)ノ', '(´ε` )', '(´,,•ω•,,)'],
        'Tristeza y Llanto': ['( ´•︵•` )', '(。•́︿•̀。)', '(T_T)', '( ; ω ; )', '(个_个)', '(ಥ_ಥ)', '(╥_╥)', '(o; T ω T)o', '。・゚゚*(>д<)*゚゚・。', '( ; _ ; )', '(ノ_<。)', '(´-ω-`)', '( T ... T )', '( ; ´ - ` A``)', '(´ ` )'],
        'Enojo y Frustración': ['(#`Д´)', '( ` ω ´ )', 'ヽ( `д´*)ノ', '( `ε´ )', '(#`д´)', '(・`ω´・)', '( ` ´ )', '( ` A ´ )ノ', '(#`д´)ノ', '(#`皿´)', '(凸`д´)凸', '(╯`□´)╯', '(╯`д´)╯', '(╯°□°)╯︵ ┻━┻', '┬─┬ノ( `Д´ノ)', '(-`д´-)', '(눈_눈)', '( ̄^ ̄)', '( ` , ´ )', '( ̄  ̄)'],
        'Animales': ['( ̄(エ) ̄)', '( ´(oo)` )', '(=`ω´=)', 'ଲ(ⓛ ω ⓛ)ଲ', '(^=◕ᴥᴥ^)', 'ଲ(ⓛ ω ⓛ)ଲ', '(V) (;,;) (V)', '(V) (・ω・) (V)', '<コ:彡', '~>`)~~~', '~<`)))彡', 'くコ:彡', '/(・ × ・)\', '(´・(oo)・`)', '(´(oo)`)', '(^._.^)ノ', '( : ౦ ‸ ౦ : )', '(U・x・U)', '三|*´ω`)ノ', '(´・(oo)・`)', 'ଲ(ⓛ ω ⓛ)ଲ'],
        'Especiales y Otros': ['(⁄ ⁄•⁄ω⁄•⁄ ⁄)', '( O . O )', '(°°)~', '( ☉_☉)', '(⊙_⊙)', '(⊙_⊙)', '( ̄д ̄)', '(ノ°▽°)ノ', '(☞゚∀゚)☞', '☜(゚∀゚☜)', '┗( T_T )┛', '(☞゚ヮ゚)☞', '♪~ ᕕ(ᐛ)ᕗ', 'ᕕ( ᐛ )ᕗ', 'ヾ(⌐■_■)ノ♪', '\(゚ー゚\)', '(/゚ー゚)/', '(~ ̄▽ ̄)~', '(~o ̄3 ̄)~', '( ̄ω ̄)', '~( ´ ` )~', '( ´ー`)', '( ´_ゝ`)', '( ̄。 ̄)', '( ̄~ ̄)', '(* ̄m ̄)', '( ´_ゝ`)', '( ̄~ ̄)', '( ̄。 ̄)', '( ̄(エ) ̄)ノ']
    };

    // --- Glitch/Weird/Distorted Text Strings ---
    const GLITCH_TEXT_STRINGS = {
        'weird': '𒈙𒈙𒈙𒈙𒈙𒈙𒈙𒈙𒈙𒈙𒈙𒈙𒈙𒈙𒈙',
        'corrupted': '░▒▓██▓▒░░▒▓██▓▒░░▒▓██▓▒░░▒▓██▓▒░░▒▓██▓▒░░▒▓██▓▒░░▒▓██▓▒░░▒▓██▓▒░░▒▓██▓▒░░▒▓█',
        'glitch': '𒐫𒐫𒐫𒐫𒐫𒐫𒐫𒐫𒐫𒐫𒐫𒐫',
        'distorted': 'ஔஔஔஔஔஔஔஔஔஔஔஔஔஔஔஔஔஔஔஔஔஔஔஔஔஔஔஔஔஔ',
        'wow': '꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅꧅',
        'nyan': '┈┈╭━━━━━━╮☆\n┈┈┃╳╳╳▕╲▂▂╱▏\n┈┈┃╳╳╳▕    ▏▍▕▍▏   ┊☆\n╰━┫╳╳╳▕▏    ╰┻╯    ┊\n┈┈┃╳╳╳╳╲▂▂╱\n☆┊╰┳┳━━┳┳╯┊┊☆',
        'pika': '  ▼ ̄>―< ̄▼            \n   /  ๑⚈ ․̫ ⚈๑)⚡⚡\n  (   |つ   づ)',
        'tiger': '┈┈▕▔╲┈┈┈╱▔▏┈┈\n┈┈▕┃╲▔▔▔╲┃▏┈┈\n┈┈╱┊┳👁╮╭👁┳▏┈┈\n┈╱┊╲┊▔┊┊▔┊▏┈┈\n╱┊┊▕┊╱◥◤╲┊▏┈┈\n┊┊┊┊╲╲╱╲╱╱┈┈┈\n┊┊┊┊┊▔▔▔▔▏┈┈┈',
        'cool cat': '┈┈┈┈┈┈^  ^ \n┈┈┈┈┈┈(•ㅅ•)☆\n    _ノ ヽ ノ\  __\n /    `/ ⌒Y⌒ Y    ヽ\n(     (三ヽ人     /        |',
        'hi box': '⠀⠀⠀⠀/)_/)☆ Hello~\n⠀⠀/(๑^᎑^๑)っ \\n/| ̄∪ ̄  ̄ |\/\n⠀|_____|/'
    };

    class AdvancedChatEnhancements extends QBit {
        static dummy1 = QBit.register(this);
        static dummy2 = QBit.bind(this, "CubeEngine");

        #originalChatInput = null;
        #textarea = null;
        #chatInputReplaced = false;
        #isModuleActive = false; // Nuevo: Estado del toggle del módulo

        #selectedFancyStyle = ''; // Nuevo: Estilo Fancy actualmente seleccionado para conversión en tiempo real

        #spamIntervalId = null; // Para el spammer

        // ASCII Art Pack properties
        #PACKS = [
            { num: 1, url: 'https://raw.githubusercontent.com/NuevoMundoOficial/DrawariaASCIIPacks/main/AsciiPack1.json' },
            { num: 2, url: 'https://raw.githubusercontent.com/NuevoMundoOficial/DrawariaASCIIPacks/main/AsciiPack2.json' },
            { num: 3, url: 'https://raw.githubusercontent.com/NuevoMundoOficial/DrawariaASCIIPacks/main/AsciiPack3.json' },
            { num: 4, url: 'https://raw.githubusercontent.com/NuevoMundoOficial/DrawariaASCIIPacks/main/AsciiPack4.json' },
            { num: 5, url: 'https://raw.githubusercontent.com/NuevoMundoOficial/DrawariaASCIIPacks/main/AsciiPack5.json' },
            { num: 6, url: 'https://raw.githubusercontent.com/NuevoMundoOficial/DrawariaASCIIPacks/main/AsciiPack6.json' },
            { num: 7, url: 'https://raw.githubusercontent.com/NuevoMundoOficial/DrawariaASCIIPacks/main/AsciiPack7.json' },
            { num: 8, url: 'https://raw.githubusercontent.com/NuevoMundoOficial/DrawariaASCIIPacks/main/AsciiPack8.json' }
        ];
        #packCache = {};
        #PAGE_SIZE = 10;

        #currentPackNum = 1;
        #currentUrl = this.#PACKS[0].url;
        #currentAscii = [];
        #filteredAscii = [];
        #currentPage = 0;

        // UI element references for ASCII Art
        #asciiListContainer = null;
        #asciiSearchInput = null;
        #asciiPageInfo = null;
        #asciiBtnPrev = null;
        #asciiBtnNext = null;

        constructor() {
            super("Chat Avanzado", '<i class="fas fa-comments-dollar"></i>'); // Icono comments-dollar
            this.#onStartup();
        }

        #onStartup() {
            // Un retraso es crucial para asegurar que la UI de Drawaria esté completamente renderizada.
            setTimeout(() => {
                this.#originalChatInput = document.querySelector('#chatbox_textinput');
                if (!this.#originalChatInput) {
                    // console.warn("El input del chat de Drawaria no fue encontrado. Las herramientas de chat no funcionarán.");
                    return;
                }
                this.#loadInterface();
                this.#setupHotkeys();
            }, 1000); // 1 segundo de retraso
        }

        #loadInterface() {
            const container = domMake.Tree("div");

            // --- Toggle para activar/desactivar el módulo ---
            const moduleToggleRow = domMake.Row();
            const toggleButton = domMake.Button('<i class="fas fa-power-off"></i> Activar Chat Avanzado');
            toggleButton.classList.add('module-toggle-button');
            toggleButton.addEventListener('click', () => this.#toggleModuleActive(toggleButton));
            moduleToggleRow.appendChild(toggleButton);
            container.appendChild(moduleToggleRow);

            // --- Fila 1: Acciones Básicas (Rellenar, Limpiar, Guardar, Cargar) ---
            const basicActionsRow = domMake.Row();
            basicActionsRow.style.gap = '5px';
            this.fillButton = domMake.Button("Rellenar");
            this.fillButton.title = "Rellena el chat con líneas en blanco (útil para limpiar visualmente)";
            this.fillButton.onclick = () => this.#fillTextareaWithBlankLines();
            this.clearButton = domMake.Button("Limpiar");
            this.clearButton.title = "Limpia todo el texto del área de chat";
            this.clearButton.onclick = () => this.#clearTextarea();
            this.saveButton = domMake.Button("Guardar");
            this.saveButton.title = "Guarda el texto actual como borrador";
            this.saveButton.onclick = () => this.#saveDraft();
            this.loadButton = domMake.Button("Cargar");
            this.loadButton.title = "Carga el último borrador guardado";
            this.loadButton.onclick = () => this.#loadDraft();
            basicActionsRow.appendAll(this.fillButton, this.clearButton, this.saveButton, this.loadButton);
            container.appendChild(basicActionsRow);

            // --- Fila 2: Formato de Texto (Negrita, Cursiva, Tachado) ---
            const formattingRow = domMake.Row();
            formattingRow.style.gap = '5px';
            this.boldButton = domMake.Button("<strong>B</strong>");
            this.boldButton.title = "Poner texto seleccionado en negrita (**texto**)";
            this.boldButton.onclick = () => this.#wrapSelection('**', '**');
            this.italicButton = domMake.Button("<em>I</em>");
            this.italicButton.title = "Poner texto seleccionado en cursiva (*texto*)";
            this.italicButton.onclick = () => this.#wrapSelection('*', '*');
            this.strikeButton = domMake.Button("<del>S</del>");
            this.strikeButton.title = "Poner texto seleccionado en tachado (~~texto~~)";
            this.strikeButton.onclick = () => this.#wrapSelection('~~', '~~');
            formattingRow.appendAll(this.boldButton, this.italicButton, this.strikeButton);
            container.appendChild(formattingRow);

            // --- Fila 3: Herramientas Creativas (Fancy, Emojis) ---
            const creativeRow = domMake.Row();
            creativeRow.style.gap = '5px';
            this.fancySelect = domMake.Tree("select", { title: "Convertir texto a un estilo Fancy" });
            this.fancySelect.appendAll(
                domMake.Tree("option", { value: "" }, ["-- Estilo Fancy --"]),
                domMake.Tree("option", { value: "script" }, ["Script"]),
                domMake.Tree("option", { value: "fraktur" }, ["Fraktur"]),
                domMake.Tree("option", { value: "monospace" }, ["Monospace"]),
                domMake.Tree("option", { value: "bold" }, ["Bold"]),
                domMake.Tree("option", { value: "italic" }, ["Italic"])
            );
            this.fancySelect.onchange = (e) => this.#setFancyStyle(e.target.value); // Cambiado a setFancyStyle
            this.emojiButton = domMake.Button("😊 Emojis");
            this.emojiButton.title = "Abrir selector de Emojis y ASCIImojis";
            this.emojiButton.onclick = () => this.#openEmojiPicker();
            creativeRow.appendAll(this.fancySelect, this.emojiButton);
            container.appendChild(creativeRow);

            // --- Contadores de Palabras y Caracteres ---
            const countersRow = domMake.Row();
            countersRow.style.marginTop = '10px';
            countersRow.style.fontSize = '0.9em';
            this.wordCountDisplay = domMake.Tree("span");
            this.charCountDisplay = domMake.Tree("span");
            countersRow.appendAll(this.wordCountDisplay, this.charCountDisplay);
            container.appendChild(countersRow);

            // --- Sección "Más Opciones" (detalles expandibles) ---
            const moreOptionsDetails = domMake.Tree("details");
            moreOptionsDetails.style.marginTop = '15px';
            const moreOptionsSummary = domMake.Tree("summary", { style: 'font-weight: bold; cursor: pointer;' }, ["Más Opciones"]);
            moreOptionsDetails.appendChild(moreOptionsSummary);

            const glitchButtonsRow = domMake.Row();
            glitchButtonsRow.style.gap = '5px';
            glitchButtonsRow.style.marginTop = '10px';
            glitchButtonsRow.style.flexWrap = 'wrap'; // Ensure wrapping
            glitchButtonsRow.style.justifyContent = 'flex-start'; // Align buttons to the start

            const addGlitchButton = (key, text) => {
                const btn = domMake.Button(text, {
                    style: `
                        padding: 4px 8px; /* Smaller padding */
                        font-size: 0.85em; /* Smaller font size */
                        line-height: 1.2; /* Adjust line height for compactness */
                        min-width: auto; /* Allow width to shrink */
                        background: #f0f0f0;
                        color: #333;
                        border: 1px solid #ccc;
                        border-radius: 5px;
                        cursor: pointer;
                        transition: background 0.1s ease;
                    `
                });
                btn.title = `Insertar texto '${key}'`;
                btn.onclick = () => this.#insertPredefinedText(GLITCH_TEXT_STRINGS[key]);
                btn.onmouseenter = () => btn.style.background = "#e0e0e0";
                btn.onmouseleave = () => btn.style.background = "#f0f0f0";
                glitchButtonsRow.appendChild(btn);
                this[`${key}Button`] = btn; // Store reference
            };

            addGlitchButton('weird', 'Weird');
            addGlitchButton('corrupted', 'Corrupted');
            addGlitchButton('glitch', 'Glitch');
            addGlitchButton('distorted', 'Distorted');
            addGlitchButton('wow', 'Wow');
            addGlitchButton('nyan', 'Nyan Cat');
            addGlitchButton('pika', 'Pikachu');
            addGlitchButton('tiger', 'Tiger');
            addGlitchButton('cool cat', 'Cool Cat');
            addGlitchButton('hi box', 'Hi Box'); // New 'Hi Box' button

            moreOptionsDetails.appendChild(glitchButtonsRow);
            container.appendChild(moreOptionsDetails);

            // --- Sección "Spam de Mensajes" (detalles expandibles) ---
            const spamDetails = domMake.Tree("details");
            spamDetails.style.marginTop = '15px';
            const spamSummary = domMake.Tree("summary", { style: 'font-weight: bold; cursor: pointer;' }, ["Spam de Mensajes"]);
            spamDetails.appendChild(spamSummary);

            const spamControlsRow = domMake.Row();
            spamControlsRow.style.gap = '5px';
            spamControlsRow.style.marginTop = '10px';

            this.spamMessageSelect = domMake.Tree("select", { title: "Selecciona un mensaje para spamear" });
            this.spamMessageSelect.appendChild(domMake.Tree("option", { value: "random" }, ["-- Random --"])); // Opción Random
            // Include all GLITCH_TEXT_STRINGS in spam options
            Object.keys(GLITCH_TEXT_STRINGS).forEach(key => {
                this.spamMessageSelect.appendChild(domMake.Tree("option", { value: key }, [key.charAt(0).toUpperCase() + key.slice(1)]));
            });

            this.spamButton = domMake.Button("Spamear");
            this.spamButton.title = "Activar/Desactivar el envío repetido del mensaje seleccionado (700ms)";
            this.spamButton.onclick = () => this.#toggleSpam();

            spamControlsRow.appendAll(this.spamMessageSelect, this.spamButton);
            spamDetails.appendChild(spamControlsRow);
            container.appendChild(spamDetails);

            // --- NEW: ASCII Art Packs (con buscador y paginación) ---
            const asciiArtDetails = domMake.Tree("details", { id: "ascii-art-module-details" }); // Added ID for easier targeting
            asciiArtDetails.style.marginTop = '15px';
            const asciiArtSummary = domMake.Tree("summary", { style: 'font-weight: bold; cursor: pointer;' }, ["ASCII Art Packs (con buscador y paginación)"]);
            asciiArtDetails.appendChild(asciiArtSummary);
            asciiArtDetails.appendChild(this.#createAsciiArtPanelContent());
            container.appendChild(asciiArtDetails);


            this.htmlElements.section.appendChild(container);
            this.#updateCounters(); // Llamada inicial

            this.#setUIEnabled(false); // Deshabilitar la UI por defecto
        }

        #createAsciiArtPanelContent() {
            const panelContent = domMake.Tree("div", {
                style: `
                    padding: 8px;
                    background: #f2f2f2; /* Lighter background */
                    border-radius: 5px;
                    margin-top: 10px;
                `
            });

            // Botones de packs
            const btnsBar = domMake.Tree("div", {
                style: `
                    margin: 10px 0 6px 0;
                    display: flex;
                    justify-content: center;
                    gap: 7px;
                    flex-wrap: wrap;
                `
            });

            this.#PACKS.forEach(pack => {
                const btn = domMake.Button(String(pack.num), {
                    class: 'ascii-pack-button', // Added class for robust selection
                    title: "Pack " + pack.num,
                    style: `
                        font-weight: bold;
                        width: 34px;
                        height: 34px;
                        color: #000; /* Text color for pack buttons */
                        background: #FFFFFF; /* Changed to white */
                        border: 1px solid #999; /* Adjusted border for white background */
                        border-radius: 7px;
                        cursor: pointer;
                        transition: background .16s;
                    `
                });
                btn.onmouseenter = () => btn.style.background = "#F0F0F0"; // Lighter hover
                btn.onmouseleave = () => btn.style.background = "#FFFFFF"; // Restore white background
                btn.onclick = () => {
                    this.#showPack(pack.num, pack.url);
                };
                btnsBar.appendChild(btn);
            });
            panelContent.appendChild(btnsBar);

            // Buscador
            const searchDiv = domMake.Tree("div", {
                style: `
                    margin-bottom: 9px;
                    text-align: center;
                `
            });
            this.#asciiSearchInput = domMake.Tree("input", {
                type: 'text',
                placeholder: 'Buscar en este pack...',
                style: `
                    width: 88%;
                    padding: 6px;
                    font-size: 1em;
                    background: #FFFFFF; /* Explicitly white for search input */
                    color: #000; /* Black text for search input */
                    border: 1px solid #999; /* Adjusted border */
                    border-radius: 6px;
                `
            });
            this.#asciiSearchInput.addEventListener('input', () => {
                this.#filterAsciiList(this.#asciiSearchInput.value);
            });
            searchDiv.appendChild(this.#asciiSearchInput);
            panelContent.appendChild(searchDiv);

            // Área de ASCII
            this.#asciiListContainer = domMake.Tree("div", {
                id: "ascii-art-list",
                style: `
                    overflow-y: auto;
                    max-height: 250px;
                    padding: 4px;
                    border-top: 1px solid #999; /* Changed to lighter border */
                    background: #E0E0E0; /* Changed to a slightly darker light background */
                    border-radius: 8px;
                `
            });
            panelContent.appendChild(this.#asciiListContainer);

            // Paginador
            const pager = domMake.Tree("div", {
                style: `
                    display: flex;
                    justify-content: center;
                    align-items: center;
                    margin-top: 10px;
                    gap: 10px;
                    padding: 5px;
                `
            });

            this.#asciiBtnPrev = domMake.Button('⬅', {
                style: `
                    font-size: 1.2em;
                    background: #FFFFFF; /* Changed to white */
                    color: #000; /* Black text */
                    border: 1px solid #999; /* Adjusted border */
                    border-radius: 6px;
                    cursor: pointer;
                `
            });
            this.#asciiBtnPrev.onmouseenter = () => this.#asciiBtnPrev.style.background = "#F0F0F0";
            this.#asciiBtnPrev.onmouseleave = () => this.#asciiBtnPrev.style.background = "#FFFFFF";
            this.#asciiBtnPrev.onclick = () => {
                if (this.#currentPage > 0) {
                    this.#currentPage--;
                    this.#renderAsciiList(this.#getCurrentPageAsciis());
                    this.#updateAsciiPagination();
                }
            };

            this.#asciiPageInfo = domMake.Tree('span', {
                style: 'font-size: 1em; color: #000;' /* Black text for page info */
            });

            this.#asciiBtnNext = domMake.Button('➡', {
                style: `
                    font-size: 1.2em;
                    background: #FFFFFF; /* Changed to white */
                    color: #000; /* Black text */
                    border: 1px solid #999; /* Adjusted border */
                    border-radius: 6px;
                    cursor: pointer;
                `
            });
            this.#asciiBtnNext.onmouseenter = () => this.#asciiBtnNext.style.background = "#F0F0F0";
            this.#asciiBtnNext.onmouseleave = () => this.#asciiBtnNext.style.background = "#FFFFFF";
            this.#asciiBtnNext.onclick = () => {
                if ((this.#currentPage + 1) * this.#PAGE_SIZE < this.#filteredAscii.length) {
                    this.#currentPage++;
                    this.#renderAsciiList(this.#getCurrentPageAsciis());
                    this.#updateAsciiPagination();
                }
            };

            pager.appendAll(this.#asciiBtnPrev, this.#asciiPageInfo, this.#asciiBtnNext);
            panelContent.appendChild(pager);

            // Load default pack on module startup
            this.#showPack(1, this.#PACKS[0].url);

            return panelContent;
        }

        #toggleModuleActive(button) {
            this.#isModuleActive = !this.#isModuleActive;
            if (this.#isModuleActive) {
                button.innerHTML = '<i class="fas fa-power-off"></i> Desactivar Chat Avanzado';
                button.classList.add('active');
                this.#replaceChatInput();
                this.#setUIEnabled(true);
            } else {
                button.innerHTML = '<i class="fas fa-power-off"></i> Activar Chat Avanzado';
                button.classList.remove('active');
                this.#restoreOriginalChatInput();
                this.#setUIEnabled(false);
                this.#stopSpam(); // Asegurarse de detener el spam al desactivar el módulo
            }
        }

        #setUIEnabled(enabled) {
            const buttons = [
                this.fillButton, this.clearButton, this.saveButton, this.loadButton,
                this.boldButton, this.italicButton, this.strikeButton,
                this.emojiButton, this.spamButton, this.spamMessageSelect,
                this.fancySelect, this.weirdButton, this.corruptedButton,
                this.glitchButton, this.distortedButton, this.wowButton,
                this.nyanButton, this.pikaButton, this.tigerButton,
                this['cool catButton'], this['hi boxButton'], // Added 'hi box' button to this list
                this.#asciiSearchInput, this.#asciiBtnPrev, this.#asciiBtnNext
            ];
            buttons.forEach(btn => {
                if (btn) {
                    btn.disabled = !enabled;
                }
            });

            const asciiPackButtons = this.htmlElements.section.querySelectorAll('.ascii-pack-button');
            asciiPackButtons.forEach(btn => {
                if (btn) {
                    btn.disabled = !enabled;
                }
            });
        }

        #replaceChatInput() {
            if (this.#chatInputReplaced) return; // Ya reemplazado

            if (this.#originalChatInput) {
                const textarea = domMake.Tree('textarea', {
                    id: this.#originalChatInput.id,
                    className: this.#originalChatInput.className,
                    placeholder: this.#originalChatInput.placeholder,
                    maxLength: this.#originalChatInput.maxLength
                });
                textarea.style.cssText = this.#originalChatInput.style.cssText;
                textarea.style.height = 'auto'; // Permitir que crezca
                textarea.style.resize = 'vertical';

                this.#originalChatInput.parentNode.replaceChild(textarea, this.#originalChatInput);
                this.#textarea = textarea;
                this.#chatInputReplaced = true;
                this.#textarea.addEventListener('input', () => this.#handleTextInput());

                // Asegurar que Enter envía el mensaje (comportamiento por defecto de Drawaria)
                this.#textarea.addEventListener('keydown', (event) => {
                    if (event.key === 'Enter' && !event.shiftKey) {
                        event.preventDefault(); // Prevenir nueva línea
                        const sendButton = document.querySelector('#chatattop-sendbutton');
                        if (sendButton) {
                            sendButton.click();
                        }
                    }
                });
            }
        }

        #restoreOriginalChatInput() {
            if (!this.#chatInputReplaced) return; // No hay textarea que restaurar

            if (this.#textarea && this.#originalChatInput && this.#textarea.parentNode) {
                this.#textarea.parentNode.replaceChild(this.#originalChatInput, this.#textarea);
                this.#textarea = null;
                this.#chatInputReplaced = false;
            }
        }

        #setupHotkeys() {
            document.addEventListener('keydown', (event) => {
                // Solo si el módulo está activo y el textarea está en uso
                if (!this.#isModuleActive || !this.#textarea || document.activeElement !== this.#textarea) return;

                if (event.ctrlKey && event.shiftKey) {
                    const keyMap = {
                        'B': () => this.#wrapSelection('**', '**'),
                        'I': () => this.#wrapSelection('*', '*'),
                        'S': () => this.#wrapSelection('~~', '~~'),
                        'C': () => this.#clearTextarea(),
                        'F': () => this.#fillTextareaWithBlankLines(),
                        'E': () => this.#openEmojiPicker(),
                    };
                    if (keyMap[event.key.toUpperCase()]) {
                        event.preventDefault();
                        keyMap[event.key.toUpperCase()]();
                    }
                }
            });
        }

        #updateCounters() {
            if (!this.#textarea || !this.wordCountDisplay) return;
            const text = this.#textarea.value;
            const wordCount = text.trim() ? text.trim().split(/\s+/).length : 0;
            const charCount = text.length;
            this.wordCountDisplay.textContent = `Palabras: ${wordCount}`;
            this.charCountDisplay.textContent = ` | Caracteres: ${charCount}`;
        }

        #fillTextareaWithBlankLines() {
            if (!this.#textarea) return;
            this.#textarea.value = "\n".repeat(199) + "\u00AD";
            // Simular evento de input para actualizar contadores y triggers
            this.#textarea.dispatchEvent(new Event('input', { bubbles: true }));
        }

        #clearTextarea() {
            if (!this.#textarea) return;
            this.#textarea.value = '';
            this.#textarea.dispatchEvent(new Event('input', { bubbles: true }));
        }

        #saveDraft() {
            if (!this.#textarea) return;
            localStorage.setItem('chatDraft', this.#textarea.value);
        }

        #loadDraft() {
            if (!this.#textarea) return;
            const draft = localStorage.getItem('chatDraft');
            if (draft) {
                this.#textarea.value = draft;
            } else {
                // console.warn("No hay borrador guardado.");
            }
            this.#textarea.dispatchEvent(new Event('input', { bubbles: true }));
        }

        #getSelection() {
            if (!this.#textarea) return null;
            return {
                start: this.#textarea.selectionStart,
                end: this.#textarea.selectionEnd,
                text: this.#textarea.value.substring(this.#textarea.selectionStart, this.#textarea.selectionEnd)
            };
        }

        #replaceSelection(newText, selectionStart, selectionEnd) {
            if (!this.#textarea) return;
            const currentText = this.#textarea.value;
            this.#textarea.value = currentText.substring(0, selectionStart) + newText + currentText.substring(selectionEnd);
            this.#textarea.selectionStart = selectionStart;
            this.#textarea.selectionEnd = selectionStart + newText.length;
            this.#textarea.focus();
            this.#textarea.dispatchEvent(new Event('input', { bubbles: true })); // Trigger input event
        }

        #wrapSelection(prefix, suffix) {
            const sel = this.#getSelection();
            if (sel && sel.text) {
                this.#replaceSelection(prefix + sel.text + suffix, sel.start, sel.end);
            }
        }

        #setFancyStyle(style) {
            this.#selectedFancyStyle = style;
            if (style) {
                // console.log(`Estilo Fancy '${style}' activado. El texto se convertirá al escribir.`);
                this.#handleTextInput(); // Aplicar el estilo al texto actual si lo hay
            } else {
                // console.log("Estilo Fancy desactivado.");
                // Para restaurar el texto original al desactivar el estilo fancy,
                // necesitaríamos guardar el texto original antes de aplicar el fancy.
                // Por ahora, solo desactiva la conversión en tiempo real.
            }
        }

        #handleTextInput() {
            this.#updateCounters();

            if (!this.#selectedFancyStyle || !this.#textarea) return;

            const currentText = this.#textarea.value;
            const currentCursorPos = this.#textarea.selectionStart;

            const map = FANCY_TEXT_MAPS[this.#selectedFancyStyle];
            if (!map) return;

            let convertedText = '';
            let newCursorPos = currentCursorPos;

            for (let i = 0; i < currentText.length; i++) {
                const originalChar = currentText[i];
                const convertedChar = map[originalChar] || originalChar;

                convertedText += convertedChar;

                if (i < currentCursorPos && convertedChar.length !== originalChar.length) {
                    newCursorPos += (convertedChar.length - originalChar.length);
                }
            }

            if (this.#textarea.value === convertedText) {
                return;
            }

            const originalDispatch = Object.getOwnPropertyDescriptor(HTMLTextAreaElement.prototype, 'value').set;
            originalDispatch.call(this.#textarea, convertedText);

            this.#textarea.selectionStart = newCursorPos;
            this.#textarea.selectionEnd = newCursorPos;
        }

        #insertPredefinedText(text) {
            if (!this.#textarea) return;
            const sendButton = document.querySelector('#chatattop-sendbutton');
            if (!sendButton) {
                // console.error("No se encontró el botón de enviar.");
                return;
            }

            // Remove "@Facemojikeyboard" from the text
            let processedTxt = text.replace(/@Facemojikeyboard/g, '').trim();

            // Add a blank line before the ASCII art if it's not empty after processing
            this.#textarea.value = (processedTxt ? '\n' : '') + processedTxt;

            this.#textarea.dispatchEvent(new Event('input', { bubbles: true })); // Trigger input event
            sendButton.click(); // Automatically send the message
        }

        #openEmojiPicker() {
            // Eliminar picker anterior si existe
            const existingPicker = document.getElementById('advanced-emoji-picker');
            if (existingPicker) {
                existingPicker.remove();
                return;
            }

            const picker = domMake.Tree('div', {
                id: 'advanced-emoji-picker',
                style: `
                    position: absolute;
                    bottom: 50px;
                    left: 10px;
                    width: 300px;
                    max-height: 400px;
                    background: #f9f9f9;
                    border: 1px solid #ccc;
                    border-radius: 8px;
                    box-shadow: 0 4px 12px rgba(0,0,0,0.15);
                    z-index: 1001;
                    display: flex;
                    flex-direction: column;
                `
            });

            const header = domMake.Tree('div', { style: 'padding: 8px; border-bottom: 1px solid #ddd; display: flex; align-items: center; gap: 8px;' });
            const searchInput = domMake.Tree('input', { type: 'text', placeholder: 'Buscar emoji...', style: 'width: 100%; padding: 5px; border: 1px solid #ccc; border-radius: 4px;' });
            const closeButton = domMake.Button('×', { style: 'border: none; background: none; font-size: 20px; cursor: pointer;' });
            closeButton.onclick = () => picker.remove();
            header.appendAll(searchInput, closeButton);

            const content = domMake.Tree('div', { style: 'overflow-y: auto; padding: 8px; flex-grow: 1;' });

            Object.entries(ASCII_MOJIS).forEach(([category, emojis]) => {
                const categoryContainer = domMake.Tree('div', { 'data-category': category.toLowerCase() });
                categoryContainer.appendChild(domMake.Tree('h4', { style: 'margin: 10px 0 5px; font-size: 14px; color: #333;' }, [category])); // Changed h4 color
                const emojiGrid = domMake.Tree('div', { style: 'display: flex; flex-wrap: wrap; gap: 5px;' });
                emojis.forEach(emoji => {
                    const btn = domMake.Button(emoji, { class: 'emoji-btn', 'data-emoji': emoji.toLowerCase(), style: 'background: #fff; border: 1px solid #ddd; border-radius: 4px; padding: 5px; cursor: pointer; font-size: 16px;' });
                    btn.onclick = () => {
                        this.#insertEmoji(emoji); // This sends immediately
                        picker.remove();
                    };
                    emojiGrid.appendChild(btn);
                });
                categoryContainer.appendChild(emojiGrid);
                content.appendChild(categoryContainer);
            });

            searchInput.oninput = () => {
                const term = searchInput.value.toLowerCase();
                content.querySelectorAll('.emoji-btn').forEach(btn => {
                    const match = btn.dataset.emoji.includes(term);
                    btn.style.display = match ? '' : 'none';
                });
            };

            picker.appendAll(header, content);
            document.body.appendChild(picker);
        }

        #insertEmoji(emoji) {
            if (!this.#textarea) return;
            const sendButton = document.querySelector('#chatattop-sendbutton');
            if (!sendButton) {
                // console.error("No se encontró el botón de enviar.");
                return;
            }

            this.#textarea.value = emoji; // Set the value directly
            this.#textarea.dispatchEvent(new Event('input', { bubbles: true })); // Trigger input event
            sendButton.click(); // Automatically send the message
        }

        #toggleSpam() {
            if (this.#spamIntervalId) {
                this.#stopSpam();
            } else {
                this.#startSpam();
            }
        }

        #startSpam() {
            const selectedOption = this.spamMessageSelect.value;
            let messageToSend;

            if (selectedOption === "random") {
                const glitchTexts = Object.values(GLITCH_TEXT_STRINGS);
                if (glitchTexts.length === 0) {
                    // console.warn("No hay textos glitch para spamear al azar.");
                    return;
                }
                messageToSend = glitchTexts[Math.floor(Math.random() * glitchTexts.length)];
            } else {
                messageToSend = GLITCH_TEXT_STRINGS[selectedOption];
            }

            if (!messageToSend) {
                // console.warn("Selecciona un mensaje válido o un tipo de texto glitch.");
                return;
            }

            const sendButton = document.querySelector('#chatattop-sendbutton');
            if (!sendButton || !this.#originalChatInput) {
                // console.error("No se encontró el botón de enviar o el input del chat.");
                return;
            }

            // Deshabilitar la selección de mensajes y el botón de spam
            this.spamMessageSelect.disabled = true;
            this.spamButton.textContent = "Detener Spam";
            this.spamButton.classList.add('active'); // O un estilo de 'activo'

            // console.log(`Spameando con texto ${selectedOption === "random" ? "aleatorio" : `'${selectedOption}'`} cada 700ms.`);

            this.#spamIntervalId = setInterval(() => {
                // If the option is "random", select a new message each tick
                if (selectedOption === "random") {
                    const glitchTexts = Object.values(GLITCH_TEXT_STRINGS);
                    if (glitchTexts.length === 0) { // If the list becomes empty for some reason
                        // console.warn("No hay textos glitch para spamear. Deteniendo spam.");
                        this.#stopSpam();
                        return;
                    }
                    messageToSend = glitchTexts[Math.floor(Math.random() * glitchTexts.length)];
                }

                // Apply the same processing as #sendAsciiToChat and #insertPredefinedText
                let processedMessage = messageToSend.replace(/@Facemojikeyboard/g, '').trim();
                processedMessage = (processedMessage ? '\n' : '') + processedMessage;

                if (this.#originalChatInput && sendButton) {
                    // If the enhanced textarea is active, use it
                    if (this.#textarea && this.#chatInputReplaced) {
                        this.#textarea.value = processedMessage;
                    } else { // If not, use the original input
                        this.#originalChatInput.value = processedMessage;
                    }
                    sendButton.click();
                } else {
                    // console.warn("El input o botón de chat no está disponible. Deteniendo spam.");
                    this.#stopSpam();
                }
            }, 700);
        }

        #stopSpam() {
            if (this.#spamIntervalId) {
                clearInterval(this.#spamIntervalId);
                this.#spamIntervalId = null;
                // console.log("Spam detenido.");

                // Enable message selection and spam button
                this.spamMessageSelect.disabled = false;
                this.spamButton.textContent = "Spamear";
                this.spamButton.classList.remove('active');
            }
        }

        // --- ASCII Art Pack Methods ---

        #getCurrentPageAsciis() {
            const start = this.#currentPage * this.#PAGE_SIZE;
            return this.#filteredAscii.slice(start, start + this.#PAGE_SIZE);
        }

        #updateAsciiPagination() {
            let total = Math.ceil(this.#filteredAscii.length / this.#PAGE_SIZE) || 1;
            this.#asciiPageInfo.textContent = ` ${this.#currentPage + 1} / ${total} `;
            this.#asciiBtnPrev.disabled = this.#currentPage === 0;
            this.#asciiBtnNext.disabled = (this.#currentPage + 1) * this.#PAGE_SIZE >= this.#filteredAscii.length;
        }

        #showPack(packNum, url) {
            this.#currentPackNum = packNum;
            this.#currentUrl = url;
            this.#currentAscii = [];
            this.#filteredAscii = [];
            this.#currentPage = 0;

            if (this.#asciiSearchInput) this.#asciiSearchInput.value = "";
            if (this.#asciiListContainer) {
                this.#asciiListContainer.innerHTML = `<div style="color:#333;margin:20px auto;text-align:center;font-size:17px">Cargando pack #${packNum}...</div>`; // Changed loading text color
            }

            if (this.#packCache[url]) {
                this.#currentAscii = this.#packCache[url].slice();
                this.#filterAsciiList("");
                return;
            }
            fetch(url)
                .then(r => r.json())
                .then(data => {
                    this.#currentAscii = data.slice();
                    this.#packCache[url] = data.slice();
                    this.#filterAsciiList("");
                })
                .catch(e => {
                    if (this.#asciiListContainer) {
                        this.#asciiListContainer.innerHTML = `<div style='color:#ff7878'>Error cargando pack</div>`;
                    }
                });
        }

        #filterAsciiList(query) {
            if (!this.#currentAscii) return;
            const q = (query || "").toLowerCase();
            this.#filteredAscii = !q ? this.#currentAscii.slice() : this.#currentAscii.filter(txt =>
                txt.toLowerCase().includes(q));
            this.#currentPage = 0;
            this.#renderAsciiList(this.#getCurrentPageAsciis());
            this.#updateAsciiPagination();
        }

        #renderAsciiList(asciiList) {
            if (!this.#asciiListContainer) return;
            this.#asciiListContainer.innerHTML = "";
            if (!asciiList.length) {
                this.#asciiListContainer.innerHTML = '<div style="color:#555;text-align:center;padding:26px">Sin resultados en esta página.</div>'; // Changed no results text color
                return;
            }
            asciiList.forEach(ascii => {
                const asciiDiv = domMake.Tree('div', {
                    className: "ascii-art-entry",
                    style: `
                        background: #FFFFFF; /* Changed to white */
                        margin: 9px 0;
                        padding: 10px 10px 10px 45px;
                        border-radius: 8px;
                        box-shadow: rgba(0, 0, 0, 0.2) 0px 1px 6px; /* Adjusted box shadow for lighter background */
                        white-space: pre;
                        position: relative;
                        font-family: monospace;
                        font-size: 1.05em;
                        cursor: pointer;
                        transition: background 0.12s;
                    `,
                    title: "Haz clic para enviar al chat"
                });

                // Botón copiar arriba a la izquierda
                const copyBtn = domMake.Button("📋", {
                    title: "Copiar",
                    style: `
                        position: absolute;
                        left: 10px;
                        top: 9px;
                        background: #F0F0F0; /* Lighter background for copy button */
                        color: #000; /* Black text for copy button */
                        border: 1px solid #999;
                        border-radius: 5px;
                        padding: 2px 7px;
                        font-size: 1em;
                        cursor: pointer;
                        transition: background 0.14s;
                    `
                });
                copyBtn.onmouseenter = () => copyBtn.style.background = "#E0E0E0"; // Lighter hover
                copyBtn.onmouseleave = () => copyBtn.style.background = "#F0F0F0"; // Restore lighter background
                copyBtn.onclick = (e) => {
                    e.stopPropagation();
                    navigator.clipboard.writeText(ascii).then(
                        () => {
                            copyBtn.innerHTML = "✅";
                            setTimeout(() => copyBtn.innerHTML = "📋", 1000);
                        }
                    );
                };

                // Enviar al chat al hacer click en el div
                asciiDiv.onclick = () => this.#sendAsciiToChat(ascii);

                // Hover highlight
                asciiDiv.onmouseenter = () => asciiDiv.style.background = "#F8F8F8"; // Adjusted hover effect
                asciiDiv.onmouseleave = () => asciiDiv.style.background = "#FFFFFF"; // Restore white background

                asciiDiv.appendChild(copyBtn);

                // Texto ASCII
                const pre = domMake.Tree('pre', {
                    style: `
                        margin: 0;
                        user-select: text;
                        color: #000000; /* Changed to black for better visibility */
                    `
                }, [ascii]);
                asciiDiv.appendChild(pre);

                this.#asciiListContainer.appendChild(asciiDiv);
            });
        }

        #sendAsciiToChat(txt) {
            if (!this.#textarea) return; // Ensure enhanced textarea is active

            const sendButton = document.querySelector('#chatattop-sendbutton');
            if (!sendButton) {
                // console.error("No se encontró el botón de enviar.");
                return;
            }

            // Remove "@Facemojikeyboard" from the text
            let processedTxt = txt.replace(/@Facemojikeyboard/g, '').trim();

            // Add a blank line before the ASCII art if it's not empty after processing
            this.#textarea.value = (processedTxt ? '\n' : '') + processedTxt;

            this.#textarea.dispatchEvent(new Event('input', { bubbles: true })); // Trigger input event
            sendButton.click(); // Automatically send the message
        }
    }
})("QBit");
// Drawaria tools module

(function () {
  // Re-declare QBit if not already in scope from the main userscript.
  // This assumes the main Cube Engine script has already defined QBit globally.
  const QBit = window.QBit;

  // --- Shared utility function for all modules ---
  // This will try to find the primary game WebSocket connection.
  // It iterates through globalThis.sockets (provided by Cube Engine)
  // to find the WebSocket connected to drawaria.online for game commands.
  function getGameSocket() {
    if (globalThis.sockets && globalThis.sockets.length > 0) {
      const gameSocket = globalThis.sockets.find(s =>
        s.url.includes("drawaria.online/socket.io") && s.readyState === WebSocket.OPEN
      );
      if (gameSocket) {
        return gameSocket;
      }
    }
    return null;
  }

  // --- Start CSS Rules for all modules ---
  // These styles are applied once for all three modules to ensure consistency and avoid duplication.
  QBit.Styles.addRules([
    // General section styling for all modules
    `#${QBit.identifier} .module-section {
        display: flex;
        flex-direction: column;
        gap: 10px;
        padding: 5px;
        border: 1px solid var(--CE-color);
        border-radius: .25rem;
        background-color: var(--CE-bg_color);
        margin-bottom: 10px; /* Space between modules */
    }`,
    `#${QBit.identifier} .module-section-title {
        font-weight: bold;
        margin-bottom: 5px;
        color: var(--dark-blue-title);
        text-align: center;
    }`,

    // Shared input/button styles
    `#${QBit.identifier} .module-form-group label {
        display: block;
        margin-bottom: 5px;
        font-size: 0.8em;
        color: var(--CE-color);
    }`,
    `#${QBit.identifier} .module-form-control {
        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} .module-btn-group {
        display: flex;
        gap: 5px;
    }`,
    `#${QBit.identifier} .module-btn-group .btn {
        flex: 1;
    }`,
    `#${QBit.identifier} .module-slider-container {
        margin-bottom: 10px;
    }`,
    `#${QBit.identifier} .module-slider-value {
        display: flex;
        justify-content: space-between;
        margin-bottom: 5px;
        font-size: 0.7em;
        color: var(--CE-color);
    }`,
    `#${QBit.identifier} .module-slider-container input[type="range"] {
        width: 100%;
        height: 6px;
        -webkit-appearance: none;
        border-radius: 3px;
        background: var(--light-gray-bg);
        margin: 5px 0;
    }`,
    `#${QBit.identifier} .module-slider-container input[type="range"]::-webkit-slider-thumb {
        -webkit-appearance: none;
        width: 16px;
        height: 16px;
        background: var(--info);
        border-radius: 50%;
        cursor: pointer;
    }`,
    `#${QBit.identifier} .module-status-indicator {
        display: inline-block;
        width: 10px;
        height: 10px;
        border-radius: 50%;
        margin-right: 5px;
        vertical-align: middle;
    }`,
    `#${QBit.identifier} .module-status-connected {
        background-color: var(--success);
    }`,
    `#${QBit.identifier} .module-status-disconnected {
        background-color: var(--danger);
    }`,


    // Styles for Art & Effects Tool
    `#${QBit.identifier} .artfx-section-header {
        display: flex;
        align-items: center;
        justify-content: space-between;
        margin-bottom: 8px;
    }`,
    `#${QBit.identifier} .artfx-section-header h4 {
        margin: 0;
        color: var(--info);
        font-size: 1em;
    }`,
    `#${QBit.identifier} .artfx-input-row {
        display: flex;
        flex-wrap: wrap;
        gap: 8px;
        margin-bottom: 8px;
        align-items: center;
    }`,
    `#${QBit.identifier} .artfx-input-row label {
        flex-basis: 80px;
        flex-shrink: 0;
        font-size: 0.9em;
        color: var(--CE-color);
    }`,
    `#${QBit.identifier} .artfx-input-row input[type="text"],
     #${QBit.identifier} .artfx-input-row input[type="number"] {
        flex-grow: 1;
        padding: 6px;
        background-color: var(--light-gray-bg);
        border: 1px solid var(--CE-color);
        color: var(--CE-color);
        border-radius: 4px;
        box-sizing: border-box;
        min-width: 0;
    }`,
    `#${QBit.identifier} .artfx-button-grid {
        display: grid;
        grid-template-columns: repeat(auto-fit, minmax(130px, 1fr));
        gap: 8px;
        margin-top: 10px;
    }`,
    `#${QBit.identifier} .artfx-button {
        padding: 8px 10px;
        background-color: var(--primary);
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
        text-align: center;
        font-size: 0.85em;
        transition: background-color 0.2s;
        display: flex;
        align-items: center;
        justify-content: center;
        gap: 5px;
    }`,
    `#${QBit.identifier} .artfx-button:hover {
        background-color: var(--dark-primary);
    }`,
    `#${QBit.identifier} .artfx-button.danger {
        background-color: var(--danger);
    }`,
    `#${QBit.identifier} .artfx-button.danger:hover {
        background-color: var(--dark-danger);
    }`,
    `#${QBit.identifier} .artfx-button.special {
        background-color: var(--info);
    }`,
    `#${QBit.identifier} .artfx-button.special:hover {
        background-color: var(--dark-info);
    }`,
    `#${QBit.identifier} .artfx-clear-button {
        padding: 8px 10px;
        background-color: var(--warning);
        color: white;
        border: none;
        border-radius: 4px;
        cursor: pointer;
        text-align: center;
        font-size: 0.85em;
        transition: background-color 0.2s;
        width: 100%;
        margin-top: 8px;
    }`,
    `#${QBit.identifier} .artfx-clear-button:hover {
        background-color: var(--dark-warning);
    }`
  ]);
  // --- End CSS Rules for all modules ---


  class DrawariaTools extends QBit {
    static dummy1 = QBit.register(this);
    static dummy2 = QBit.bind(this, "CubeEngine");

    constructor() {
      super("Drawaria Tools", '<i class="fas fa-tools"></i>'); // Main module name and icon
      this._onStartup();
    }

    _onStartup() {
      // Instantiate and load each sub-module
      this.loadExtension(FlagAutodrawTool);
      this.loadExtension(ArtEffectsTool);

      // --- NEW: Cargar los 5 nuevos módulos aquí ---
      this.loadExtension(PatternDrawerTool);
      this.loadExtension(ExpansiveShapesToolNuevo); // Nombre de la clase del nuevo módulo Expansive Shapes
      this.loadExtension(GenerativeAnimatorTool);
      this.loadExtension(LuminousFlowGeneratorTool);
      this.loadExtension(ElementalAnimationsTool);
      this.loadExtension(DynamicVisualEffectsTool);
      this.loadExtension(DraggableActionMenuTool);
      this.loadExtension(PermanentRoomBotTool);
      this.loadExtension(GeometryDashCubeOnlineDrawer);
      this.loadExtension(MinecraftDrawer);
      this.loadExtension(WordHelperTool);

// WordHelperTool
      // --- FIN NEW ---

      this.notify("info", "Módulo principal 'Drawaria Tools' cargado. Puedes expandir los sub-módulos para ver las herramientas.");
    }
  }

// --- Sub-Module 1: FlagAutodrawTool ---
// Implements the functionality to draw country flags on the Drawaria canvas automatically.
// It loads flag images, processes them into drawing commands, and sends them via the game socket.
class FlagAutodrawTool extends QBit {
    // Note: No static dummy2 = QBit.bind(this, "CubeEngine"); here.
    // It's loaded via DrawariaTools.loadExtension, making it a child.

    // Private properties for internal state and UI elements
    _canvas = null;
    _ctx = null; // Main game canvas context
    _previewCanvas = null; // Offscreen canvas for image processing
    _previewCtx = null;
    _imgDataGlobal = null; // Raw pixel data of the loaded image
    _executionLine = []; // Stores the series of drawing commands
    _drawingActive = false; // Flag to control drawing process
    _socketStatus = 'disconnected'; // Current WebSocket connection status

    _ui = {}; // Object to hold references to UI elements for easier access

    // Configurable settings for flag autodraw, with default values
    _autodrawImageSize = 4;
    _autodrawBrushSize = 13;
    _autodrawPixelSize = 2;
    _autodrawOffsetX = 0; // Offset in game units (0-100%)
    _autodrawOffsetY = 0;

    // Predefined list of flags with their image URLs.
    // This list has been expanded to include flags for all UN member states and observer states.
    _flags = {
      Afghanistan: 'https://flagcdn.com/w320/af.png',
      Albania: 'https://flagcdn.com/w320/al.png',
      Algeria: 'https://flagcdn.com/w320/dz.png',
      Andorra: 'https://flagcdn.com/w320/ad.png',
      Angola: 'https://flagcdn.com/w320/ao.png',
      'Antigua and Barbuda': 'https://flagcdn.com/w320/ag.png',
      Argentina: 'https://flagcdn.com/w320/ar.png',
      Armenia: 'https://flagcdn.com/w320/am.png',
      Australia: 'https://flagcdn.com/w320/au.png',
      Austria: 'https://flagcdn.com/w320/at.png',
      Azerbaijan: 'https://flagcdn.com/w320/az.png',
      Bahamas: 'https://flagcdn.com/w320/bs.png',
      Bahrain: 'https://flagcdn.com/w320/bh.png',
      Bangladesh: 'https://flagcdn.com/w320/bd.png',
      Barbados: 'https://flagcdn.com/w320/bb.png',
      Belarus: 'https://flagcdn.com/w320/by.png',
      Belgium: 'https://flagcdn.com/w320/be.png',
      Belize: 'https://flagcdn.com/w320/bz.png',
      Benin: 'https://flagcdn.com/w320/bj.png',
      Bhutan: 'https://flagcdn.com/w320/bt.png',
      Bolivia: 'https://flagcdn.com/w320/bo.png',
      'Bosnia and Herzegovina': 'https://flagcdn.com/w320/ba.png',
      Botswana: 'https://flagcdn.com/w320/bw.png',
      Brazil: 'https://flagcdn.com/w320/br.png',
      Brunei: 'https://flagcdn.com/w320/bn.png',
      Bulgaria: 'https://flagcdn.com/w320/bg.png',
      'Burkina Faso': 'https://flagcdn.com/w320/bf.png',
      Burundi: 'https://flagcdn.com/w320/bi.png',
      Cambodia: 'https://flagcdn.com/w320/kh.png',
      Cameroon: 'https://flagcdn.com/w320/cm.png',
      Canada: 'https://flagcdn.com/w320/ca.png',
      'Cape Verde': 'https://flagcdn.com/w320/cv.png',
      'Central African Republic': 'https://flagcdn.com/w320/cf.png',
      Chad: 'https://flagcdn.com/w320/td.png',
      Chile: 'https://flagcdn.com/w320/cl.png',
      China: 'https://flagcdn.com/w320/cn.png',
      Colombia: 'https://flagcdn.com/w320/co.png',
      Comoros: 'https://flagcdn.com/w320/km.png',
      'Democratic Republic of the Congo': 'https://flagcdn.com/w320/cd.png',
      'Republic of the Congo': 'https://flagcdn.com/w320/cg.png',
      'Costa Rica': 'https://flagcdn.com/w320/cr.png',
      Croatia: 'https://flagcdn.com/w320/hr.png',
      Cuba: 'https://flagcdn.com/w320/cu.png',
      Cyprus: 'https://flagcdn.com/w320/cy.png',
      'Czech Republic': 'https://flagcdn.com/w320/cz.png',
      Denmark: 'https://flagcdn.com/w320/dk.png',
      Djibouti: 'https://flagcdn.com/w320/dj.png',
      Dominica: 'https://flagcdn.com/w320/dm.png',
      'Dominican Republic': 'https://flagcdn.com/w320/do.png',
      Ecuador: 'https://flagcdn.com/w320/ec.png',
      Egypt: 'https://flagcdn.com/w320/eg.png',
      'El Salvador': 'https://flagcdn.com/w320/sv.png',
      'Equatorial Guinea': 'https://flagcdn.com/w320/gq.png',
      Eritrea: 'https://flagcdn.com/w320/er.png',
      Estonia: 'https://flagcdn.com/w320/ee.png',
      Eswatini: 'https://flagcdn.com/w320/sz.png',
      Ethiopia: 'https://flagcdn.com/w320/et.png',
      Fiji: 'https://flagcdn.com/w320/fj.png',
      Finland: 'https://flagcdn.com/w320/fi.png',
      France: 'https://flagcdn.com/w320/fr.png',
      Gabon: 'https://flagcdn.com/w320/ga.png',
      Gambia: 'https://flagcdn.com/w320/gm.png',
      Georgia: 'https://flagcdn.com/w320/ge.png',
      Germany: 'https://flagcdn.com/w320/de.png',
      Ghana: 'https://flagcdn.com/w320/gh.png',
      Greece: 'https://flagcdn.com/w320/gr.png',
      Grenada: 'https://flagcdn.com/w320/gd.png',
      Guatemala: 'https://flagcdn.com/w320/gt.png',
      Guinea: 'https://flagcdn.com/w320/gn.png',
      'Guinea-Bissau': 'https://flagcdn.com/w320/gw.png',
      Guyana: 'https://flagcdn.com/w320/gy.png',
      Haiti: 'https://flagcdn.com/w320/ht.png',
      Honduras: 'https://flagcdn.com/w320/hn.png',
      Hungary: 'https://flagcdn.com/w320/hu.png',
      Iceland: 'https://flagcdn.com/w320/is.png',
      India: 'https://flagcdn.com/w320/in.png',
      Indonesia: 'https://flagcdn.com/w320/id.png',
      Iran: 'https://flagcdn.com/w320/ir.png',
      Iraq: 'https://flagcdn.com/w320/iq.png',
      Ireland: 'https://flagcdn.com/w320/ie.png',
      Israel: 'https://flagcdn.com/w320/il.png',
      Italy: 'https://flagcdn.com/w320/it.png',
      'Ivory Coast': 'https://flagcdn.com/w320/ci.png', // Officially Côte d'Ivoire
      Jamaica: 'https://flagcdn.com/w320/jm.png',
      Japan: 'https://flagcdn.com/w320/jp.png',
      Jordan: 'https://flagcdn.com/w320/jo.png',
      Kazakhstan: 'https://flagcdn.com/w320/kz.png',
      Kenya: 'https://flagcdn.com/w320/ke.png',
      Kiribati: 'https://flagcdn.com/w320/ki.png',
      Kuwait: 'https://flagcdn.com/w320/kw.png',
      Kyrgyzstan: 'https://flagcdn.com/w320/kg.png',
      Laos: 'https://flagcdn.com/w320/la.png',
      Latvia: 'https://flagcdn.com/w320/lv.png',
      Lebanon: 'https://flagcdn.com/w320/lb.png',
      Lesotho: 'https://flagcdn.com/w320/ls.png',
      Liberia: 'https://flagcdn.com/w320/lr.png',
      Libya: 'https://flagcdn.com/w320/ly.png',
      Liechtenstein: 'https://flagcdn.com/w320/li.png',
      Lithuania: 'https://flagcdn.com/w320/lt.png',
      Luxembourg: 'https://flagcdn.com/w320/lu.png',
      Madagascar: 'https://flagcdn.com/w320/mg.png',
      Malawi: 'https://flagcdn.com/w320/mw.png',
      Malaysia: 'https://flagcdn.com/w320/my.png',
      Maldives: 'https://flagcdn.com/w320/mv.png',
      Mali: 'https://flagcdn.com/w320/ml.png',
      Malta: 'https://flagcdn.com/w320/mt.png',
      'Marshall Islands': 'https://flagcdn.com/w320/mh.png',
      Mauritania: 'https://flagcdn.com/w320/mr.png',
      Mauritius: 'https://flagcdn.com/w320/mu.png',
      Mexico: 'https://flagcdn.com/w320/mx.png',
      Micronesia: 'https://flagcdn.com/w320/fm.png',
      Moldova: 'https://flagcdn.com/w320/md.png',
      Monaco: 'https://flagcdn.com/w320/mc.png',
      Mongolia: 'https://flagcdn.com/w320/mn.png',
      Montenegro: 'https://flagcdn.com/w320/me.png',
      Morocco: 'https://flagcdn.com/w320/ma.png',
      Mozambique: 'https://flagcdn.com/w320/mz.png',
      Myanmar: 'https://flagcdn.com/w320/mm.png',
      Namibia: 'https://flagcdn.com/w320/na.png',
      Nauru: 'https://flagcdn.com/w320/nr.png',
      Nepal: 'https://flagcdn.com/w320/np.png',
      Netherlands: 'https://flagcdn.com/w320/nl.png',
      'New Zealand': 'https://flagcdn.com/w320/nz.png',
      Nicaragua: 'https://flagcdn.com/w320/ni.png',
      Niger: 'https://flagcdn.com/w320/ne.png',
      Nigeria: 'https://flagcdn.com/w320/ng.png',
      'North Korea': 'https://flagcdn.com/w320/kp.png',
      'North Macedonia': 'https://flagcdn.com/w320/mk.png',
      Norway: 'https://flagcdn.com/w320/no.png',
      Oman: 'https://flagcdn.com/w320/om.png',
      Pakistan: 'https://flagcdn.com/w320/pk.png',
      Palau: 'https://flagcdn.com/w320/pw.png',
      Palestine: 'https://flagcdn.com/w320/ps.png',
      Panama: 'https://flagcdn.com/w320/pa.png',
      'Papua New Guinea': 'https://flagcdn.com/w320/pg.png',
      Paraguay: 'https://flagcdn.com/w320/py.png',
      Peru: 'https://flagcdn.com/w320/pe.png',
      Philippines: 'https://flagcdn.com/w320/ph.png',
      Poland: 'https://flagcdn.com/w320/pl.png',
      Portugal: 'https://flagcdn.com/w320/pt.png',
      Qatar: 'https://flagcdn.com/w320/qa.png',
      Romania: 'https://flagcdn.com/w320/ro.png',
      Russia: 'https://flagcdn.com/w320/ru.png',
      Rwanda: 'https://flagcdn.com/w320/rw.png',
      'Saint Kitts and Nevis': 'https://flagcdn.com/w320/kn.png',
      'Saint Lucia': 'https://flagcdn.com/w320/lc.png',
      'Saint Vincent and the Grenadines': 'https://flagcdn.com/w320/vc.png',
      Samoa: 'https://flagcdn.com/w320/ws.png',
      'San Marino': 'https://flagcdn.com/w320/sm.png',
      'São Tomé and Príncipe': 'https://flagcdn.com/w320/st.png',
      'Saudi Arabia': 'https://flagcdn.com/w320/sa.png',
      Senegal: 'https://flagcdn.com/w320/sn.png',
      Serbia: 'https://flagcdn.com/w320/rs.png',
      Seychelles: 'https://flagcdn.com/w320/sc.png',
      'Sierra Leone': 'https://flagcdn.com/w320/sl.png',
      Singapore: 'https://flagcdn.com/w320/sg.png',
      Slovakia: 'https://flagcdn.com/w320/sk.png',
      Slovenia: 'https://flagcdn.com/w320/si.png',
      'Solomon Islands': 'https://flagcdn.com/w320/sb.png',
      Somalia: 'https://flagcdn.com/w320/so.png',
      'South Africa': 'https://flagcdn.com/w320/za.png',
      'South Korea': 'https://flagcdn.com/w320/kr.png',
      'South Sudan': 'https://flagcdn.com/w320/ss.png',
      Spain: 'https://flagcdn.com/w320/es.png',
      'Sri Lanka': 'https://flagcdn.com/w320/lk.png',
      Sudan: 'https://flagcdn.com/w320/sd.png',
      Suriname: 'https://flagcdn.com/w320/sr.png',
      Sweden: 'https://flagcdn.com/w320/se.png',
      Switzerland: 'https://flagcdn.com/w320/ch.png',
      Syria: 'https://flagcdn.com/w320/sy.png',
      Tajikistan: 'https://flagcdn.com/w320/tj.png',
      Tanzania: 'https://flagcdn.com/w320/tz.png',
      Thailand: 'https://flagcdn.com/w320/th.png',
      'Timor-Leste': 'https://flagcdn.com/w320/tl.png',
      Togo: 'https://flagcdn.com/w320/tg.png',
      Tonga: 'https://flagcdn.com/w320/to.png',
      'Trinidad and Tobago': 'https://flagcdn.com/w320/tt.png',
      Tunisia: 'https://flagcdn.com/w320/tn.png',
      Turkey: 'https://flagcdn.com/w320/tr.png',
      Turkmenistan: 'https://flagcdn.com/w320/tm.png',
      Tuvalu: 'https://flagcdn.com/w320/tv.png',
      Uganda: 'https://flagcdn.com/w320/ug.png',
      Ukraine: 'https://flagcdn.com/w320/ua.png',
      'United Arab Emirates': 'https://flagcdn.com/w320/ae.png',
      'United Kingdom': 'https://flagcdn.com/w320/gb.png',
      'United States': 'https://flagcdn.com/w320/us.png',
      Uruguay: 'https://flagcdn.com/w320/uy.png',
      Uzbekistan: 'https://flagcdn.com/w320/uz.png',
      Vanuatu: 'https://flagcdn.com/w320/vu.png',
      'Vatican City': 'https://flagcdn.com/w320/va.png',
      Venezuela: 'https://flagcdn.com/w320/ve.png',
      Vietnam: 'https://flagcdn.com/w320/vn.png',
      Yemen: 'https://flagcdn.com/w320/ye.png',
      Zambia: 'https://flagcdn.com/w320/zm.png',
      Zimbabwe: 'https://flagcdn.com/w320/zw.png',
    };

    constructor() {
        super("Flag Autodraw Tool", '<i class="fas fa-flag"></i>'); // Module name and icon
        this._onStartup(); // Initialize the module
    }

    _onStartup() {
        this._canvas = document.getElementById('canvas');
        if (this._canvas) {
            this._ctx = this._canvas.getContext('2d');
        } else {
            // If main canvas isn't found, log an error and stop initialization.
            this.notify("error", "Canvas del juego no encontrado para Flag Autodraw.");
            return;
        }

        // Initialize offscreen canvas for image processing
        this._previewCanvas = document.createElement('canvas');
        this._previewCtx = this._previewCanvas.getContext('2d');

        this._loadInterface(); // Build the module's UI
        this._setupEventListeners(); // Attach all event listeners
        // Update initial connection status
        this._updateConnectionStatus(getGameSocket() && getGameSocket().readyState === WebSocket.OPEN ? 'connected' : 'disconnected');
        this.notify("info", "Módulo 'Flag Autodraw Tool' cargado.");
    }

    // Builds the module's user interface within its designated section.
    _loadInterface() {
        const container = domMake.Tree("div", { id: `${this.identifier}-container`, class: "module-section" });
        this.htmlElements.section.appendChild(container); // Append to the Cube Engine module's section

        // Connection Status display
        container.appendChild(domMake.Tree("div", { class: "module-section-title" }, ["Estado de Conexión"]));
        const connectionStatusDiv = domMake.Tree("div", {}, [
            domMake.Tree("span", { id: `${this.identifier}-connectionStatus`, class: `module-status-indicator module-status-${this._socketStatus}` }),
            domMake.Tree("span", { id: `${this.identifier}-statusText` }, [this._socketStatus.charAt(0).toUpperCase() + this._socketStatus.slice(1)])
        ]);
        container.appendChild(connectionStatusDiv);

        // Flag selection dropdown and search input
        container.appendChild(domMake.Tree("div", { class: "module-section-title" }, ["Dibujo de Bandera"]));

        // Search input for flags
        this._ui.flagSearchInput = domMake.Tree("input", {
            id: `${this.identifier}-flagSearch`,
            type: "text",
            class: "module-form-control",
            placeholder: "Buscar bandera por nombre..."
        });
        container.appendChild(this._ui.flagSearchInput);

        // Flag selection dropdown
        const flagSelectOptions = Object.keys(this._flags).map(flag => domMake.Tree("option", { value: flag }, [flag]));
        this._ui.flagSelect = domMake.Tree("select", { id: `${this.identifier}-flagSelect`, class: "module-form-control" }, [domMake.Tree("option", { value: "" }, ["Seleccionar una Bandera"]), ...flagSelectOptions]);
        container.appendChild(domMake.Tree("div", { class: "module-form-group" }, [domMake.Tree("label", { for: `${this.identifier}-flagSelect` }, ["Seleccionar Bandera:"]), this._ui.flagSelect]));

        // Autodraw settings sliders
        this._createSlider(container, "autodraw_imagesize", "Tamaño de Imagen", "1", "10", "4", "Grande", "Pequeña");
        this._GME_ENGINE_IMAGESIZE_SLIDER = this._ui.autodrawImagesizeInput; // Expose for other modules
        this._createSlider(container, "autodraw_brushsize", "Tamaño del Pincel", "2", "20", "13", "Fino", "Grueso");
        this._createSlider(container, "autodraw_pixelsize", "Distancia del Píxel", "2", "20", "2", "Cerca", "Lejos");
        this._createSlider(container, "autodraw_offset_x", "Desplazamiento Horizontal (%)", "0", "100", "0", "0", "100");
        this._createSlider(container, "autodraw_offset_y", "Desplazamiento Vertical (%)", "0", "100", "0", "100");

        // Action buttons
        this._ui.startAutodrawButton = domMake.Button('<i class="fas fa-play-circle"></i> Iniciar Autodraw');
        this._ui.stopAutodrawButton = domMake.Button('<i class="fas fa-stop-circle"></i> Detener Autodraw');
        this._ui.clearCanvasButton = domMake.Button('<i class="fas fa-eraser"></i> Limpiar Todo');

        container.appendChild(domMake.Tree("div", { class: "module-btn-group" }, [this._ui.startAutodrawButton, this._ui.stopAutodrawButton]));
        container.appendChild(domMake.Tree("div", { class: "module-btn-group" }, [this._ui.clearCanvasButton]));
    }

    // Helper to create a slider input with labels and value display
    _createSlider(parent, idSuffix, labelText, min, max, value, minLabel, maxLabel) {
        const sliderId = `${this.identifier}-${idSuffix}`;
        const valueId = `${sliderId}Value`;

        const sliderContainer = domMake.Tree("div", { class: "module-slider-container" }, [
            domMake.Tree("label", { for: sliderId }, [labelText]),
            domMake.Tree("div", { class: "module-slider-value" }, [
                domMake.Tree("span", {}, [minLabel]),
                domMake.Tree("span", { id: valueId }, [value]),
                domMake.Tree("span", {}, [maxLabel])
            ]),
            domMake.Tree("input", { type: "range", id: sliderId, min: min, max: max, value: value })
        ]);
        parent.appendChild(sliderContainer);
        // Store references to the slider input and its value display span
        this._ui[idSuffix.replace(/_([a-z])/g, (g) => g[1].toUpperCase()) + "Input"] = sliderContainer.querySelector('input');
        this._ui[idSuffix.replace(/_([a-z])/g, (g) => g[1].toUpperCase()) + "Value"] = sliderContainer.querySelector(`#${valueId}`);
    }

    // Sets up all event listeners for UI interactions.
    _setupEventListeners() {
        // Event listener for flag selection change
        this._ui.flagSelect.addEventListener('change', (e) => {
            const selectedFlagName = e.target.value;
            if (selectedFlagName && this._flags[selectedFlagName]) {
                this._loadImageForAutodraw(this._flags[selectedFlagName]);
            } else if (!selectedFlagName) {
                this._imgDataGlobal = null; // Clear image data if no flag is selected
                this.notify("debug", "No flag selected, image data cleared.");
            }
        });

        // Add event listener for flag search input to filter the dropdown
        this._ui.flagSearchInput.addEventListener('keyup', (e) => {
            const searchTerm = e.target.value.toLowerCase();
            const flagSelect = this._ui.flagSelect;
            const options = flagSelect.getElementsByTagName('option');

            // Iterate through all options (skip the first one which is the placeholder)
            for (let i = 1; i < options.length; i++) {
                const optionText = options[i].textContent || options[i].innerText;
                if (optionText.toLowerCase().includes(searchTerm)) {
                    options[i].style.display = ''; // Show option if it matches
                } else {
                    options[i].style.display = 'none'; // Hide option if it doesn't match
                }
            }
        });


        // Event listener for "Start Autodraw" button
        this._ui.startAutodrawButton.addEventListener('click', () => {
            const socket = getGameSocket(); // Get the active game WebSocket
            if (!socket) {
                this.notify("warning", "No hay conexión al juego. Asegúrate de estar en una sala de Drawaria.");
                return;
            }
            if (!this._imgDataGlobal) {
                this.notify("warning", "Por favor, selecciona una bandera primero para el autodraw.");
                return;
            }

            // Update autodraw settings from UI sliders
            this._autodrawImageSize = parseInt(this._ui.autodrawImagesizeInput.value, 10);
            this._autodrawBrushSize = parseInt(this._ui.autodrawBrushsizeInput.value, 10);
            this._autodrawPixelSize = parseInt(this._ui.autodrawPixelsizeInput.value, 10);
            this._autodrawOffsetX = parseInt(this._ui.autodrawOffsetXInput.value, 10);
            this._autodrawOffsetY = parseInt(this._ui.autodrawOffsetYInput.value, 10);

            // Process the loaded image into drawing commands
            this._processImageForAutodraw(this._autodrawImageSize, this._autodrawPixelSize, this._autodrawBrushSize, { x: this._autodrawOffsetX, y: this._autodrawOffsetY });
            this._executeAutodraw(socket); // Start the drawing process
        });

        // Event listener for "Stop Autodraw" button
        this._ui.stopAutodrawButton.addEventListener('click', () => {
            this._drawingActive = false; // Stop the drawing loop
            this.notify("info", "Autodraw detenido por el usuario.");
        });

        // Event listener for "Clear All" button
        this._ui.clearCanvasButton.addEventListener('click', async () => {
            const socket = getGameSocket();
            if (!socket) {
                this.notify("warning", "No hay conexión al juego. No se puede limpiar el lienzo.");
                return;
            }

            this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); // Clear local canvas immediately

            const clearThickness = 1000; // Large thickness for effective clearing
            const clearColor = '#ffffff'; // White color for clearing
            const steps = 5; // Number of lines to draw for full coverage

            // Send multiple large white lines to clear the canvas remotely
            for (let i = 0; i <= steps; i++) {
                this._drawLineAndSendCommand(socket, 0, (i / steps) * this._canvas.height, this._canvas.width, (i / steps) * this._canvas.height, clearColor, clearThickness);
                await this._delay(5); // Small delay between commands
                this._drawLineAndSendCommand(socket, (i / steps) * this._canvas.width, 0, (i / steps) * this._canvas.width, this._canvas.height, clearColor, clearThickness);
                await this._delay(5);
            }
            this.notify("success", "El lienzo ha sido limpiado.");
        });

        // Update slider value displays dynamically as user drags them
        for (const key in this._ui) {
            if (key.endsWith("Input") && this._ui[key].type === "range") {
                const valueSpanKey = key.replace("Input", "Value");
                if (this._ui[valueSpanKey]) {
                    this._ui[key].addEventListener('input', () => {
                        this._ui[valueSpanKey].textContent = this._ui[key].value;
                    });
                }
            }
        }
    }

    // Updates the connection status display in the UI.
    _updateConnectionStatus(status) {
        this._socketStatus = status;
        const statusIndicator = document.getElementById(`${this.identifier}-connectionStatus`);
        const statusText = document.getElementById(`${this.identifier}-statusText`);

        if (statusIndicator && statusText) {
            statusIndicator.className = `module-status-indicator module-status-${status}`;
            statusText.textContent = status.charAt(0).toUpperCase() + status.slice(1);
        }
    }

    // Utility function for creating a delay.
    _delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    // Draws a line locally on the canvas and sends a drawing command to the server.
    _drawLineAndSendCommand(socket, x1, y1, x2, y2, color, thickness) {
        if (!this._canvas || !this._ctx) return;

        // Draw locally for immediate feedback
        this._ctx.strokeStyle = color;
        this._ctx.lineWidth = thickness;
        this._ctx.lineCap = 'round';
        this._ctx.beginPath();
        this._ctx.moveTo(x1, y1);
        this._ctx.lineTo(x2, y2);
        this._ctx.stroke();

        // Send command to the game server if socket is open
        if (socket && socket.readyState === WebSocket.OPEN) {
            // Normalize coordinates (0-1 range) for the server
            const normX1 = (x1 / this._canvas.width).toFixed(4);
            const normY1 = (y1 / this._canvas.height).toFixed(4);
            const normX2 = (x2 / this._canvas.width).toFixed(4);
            const normY2 = (y2 / this._canvas.height).toFixed(4);

            // Send draw command: `42["drawcmd",0,[x1,y1,x2,y2,is_active,thickness_negative,color,player_id,is_pixel,{}]]`
            socket.send(`42["drawcmd",0,[${normX1},${normY1},${normX2},${normY2},false,${0 - thickness},"${color}",0,0,{}]]`);
        } else {
            this.notify("warning", "Socket no conectado. Comando de dibujo no enviado al servidor.");
        }
    }

    // Recalculates pixel coordinates with given offset, normalizing them to 0-1 range.
    _recalc(value, offset) {
        const cw = this._canvas.width;
        const ch = this._canvas.height;
        return [
            Math.min(1, Math.max(0, (value[0] / cw + offset.x / 100))),
            Math.min(1, Math.max(0, (value[1] / ch + offset.y / 100)))
        ];
    }

    // Loads an image into the offscreen canvas and extracts its pixel data.
    _loadImageForAutodraw(url) {
        let img = new Image();
        img.crossOrigin = 'anonymous'; // Important for loading images from different origins (CORS)
        img.src = url;
        img.addEventListener('load', () => {
            if (!this._canvas) {
                this.notify("error", "Main canvas element not found for Autodraw.");
                return;
            }
            const cw = this._canvas.width;
            const ch = this._canvas.height;

            this._previewCanvas.width = cw;
            this._previewCanvas.height = ch;
            this._previewCtx.clearRect(0, 0, cw, ch); // Clear offscreen canvas

            // Scale and center the image in the offscreen canvas
            let scale = Math.min(cw / img.width, ch / img.height);
            let scaledWidth = img.width * scale;
            let scaledHeight = img.height * scale;
            let dx = (cw - scaledWidth) / 2;
            let dy = (ch - scaledHeight) / 2;

            this._previewCtx.drawImage(img, dx, dy, scaledWidth, scaledHeight);
            this._imgDataGlobal = this._previewCtx.getImageData(0, 0, cw, ch).data; // Store pixel data
            this.notify("info", "Imagen cargada y procesada para Autodraw.");
        });
        img.addEventListener('error', () => {
            this.notify("error", `Fallo al cargar imagen para Autodraw: ${url}`);
        });
    }

    // Processes the loaded image pixel data into a series of drawing commands (lines).
    _processImageForAutodraw(size, modifier = 1, thickness = 5, offset = { x: 0, y: 0 }, ignoreColors = []) {
        if (!this._imgDataGlobal || !this._canvas) {
            this.notify("warning", "No hay datos de imagen o lienzo disponible para procesar.");
            return;
        }
        this._executionLine = []; // Reset drawing commands
        const cw = this._canvas.width;
        const ch = this._canvas.height;
        const step = size * modifier; // Pixel sampling step

        // Iterate through the image row by row
        for (let y = 0; y < ch; y += step) {
            let startX = 0;
            let currentColor = null;

            // Iterate pixel by pixel horizontally to detect color changes and form lines
            for (let x = 0; x < cw; x += 1) {
                const currentPixelX = Math.floor(x / step) * step; // Ensure sampling is on the grid
                const currentPixelY = Math.floor(y / step) * step;

                const index = (Math.floor(currentPixelY) * cw + Math.floor(currentPixelX)) * 4;
                const a = this._imgDataGlobal[index + 3] || 0; // Alpha channel

                if (a > 20) { // If pixel is not mostly transparent
                    const r = this._imgDataGlobal[index + 0] || 0;
                    const g = this._imgDataGlobal[index + 1] || 0;
                    const b = this._imgDataGlobal[index + 2] || 0;
                    const color = `rgb(${r},${g},${b})`;

                    if (!ignoreColors.includes(color)) { // If color is not in ignore list
                        if (color !== currentColor) { // If color changes, end previous segment and start new one
                            if (currentColor !== null && x > startX) {
                                this._executionLine.push({
                                    pixelPos1: [startX, y],
                                    pixelPos2: [x, y], // End the line at the current x
                                    color: currentColor,
                                    thickness: thickness,
                                });
                            }
                            currentColor = color;
                            startX = x;
                        }
                    } else { // If color is ignored, break current segment
                        if (currentColor !== null && x > startX) {
                            this._executionLine.push({
                                pixelPos1: [startX, y],
                                pixelPos2: [x, y],
                                color: currentColor,
                                thickness: thickness,
                            });
                        }
                        currentColor = null;
                    }
                } else { // If pixel is transparent, break current segment
                    if (currentColor !== null && x > startX) {
                        this._executionLine.push({
                            pixelPos1: [startX, y],
                            pixelPos2: [x, y],
                            color: currentColor,
                            thickness: thickness,
                        });
                    }
                    currentColor = null;
                }
            }
            // Add the last segment of the current row if it exists
            if (currentColor !== null && cw > startX) {
                this._executionLine.push({
                    pixelPos1: [startX, y],
                    pixelPos2: [cw, y], // Extend to the end of the canvas width
                    color: currentColor,
                    thickness: thickness,
                });
            }
        }
        this.notify("debug", `Procesamiento de imagen completo, líneas: ${this._executionLine.length}`);
    }

    // Executes the generated drawing commands on the canvas.
    async _executeAutodraw(socket) {
        if (!socket || socket.readyState !== WebSocket.OPEN) {
            this.notify("warning", "Socket no está conectado. Autodraw detenido.");
            return;
        }
        if (this._executionLine.length === 0) {
            this.notify("warning", "No se generaron líneas para dibujar. Carga y procesa una imagen primero.");
            return;
        }

        this._drawingActive = true;
        for (let i = 0; i < this._executionLine.length; i++) {
            if (!this._drawingActive) {
                this.notify("info", "Autodraw detenido por el usuario.");
                break;
            }
            const line = this._executionLine[i];
            // Recalculate positions with offset for drawing and sending
            const p1 = this._recalc(line.pixelPos1, { x: this._autodrawOffsetX, y: this._autodrawOffsetY });
            const p2 = this._recalc(line.pixelPos2, { x: this._autodrawOffsetX, y: this._autodrawOffsetY });
            const color = line.color;
            const thickness = line.thickness;

            // Draw the line (locally and remotely)
            this._drawLineAndSendCommand(socket, line.pixelPos1[0], line.pixelPos1[1], line.pixelPos2[0], line.pixelPos2[1], color, thickness);
            await this._delay(1); // Small delay to prevent overloading the server/browser
        }
        this._drawingActive = false;
        this.notify("success", "Autodraw completo.");
    }
}


  // --- Sub-Module 4: ArtEffectsTool ---
  // Provides advanced image drawing and generative art effects.
  class ArtEffectsTool extends QBit {
    _canvas = null;
    _ctx = null;
    _previewCanvas = null;
    _previewCtx = null;
    _drawingActive = false;
    _imageData = null; // Renamed from sprite_data_pixel_art
    _executionLine = [];
    _globalFrameCount = 0; // Renamed from global_frame_count

    _ui = {}; // UI elements references

    _pixelFont = { // Pixel font definitions
      'A': ["0110", "1001", "1111", "1001", "1001"], 'B': ["1110", "1001", "1110", "1001", "1110"], 'C': ["0110", "1000", "1000", "1000", "0110"], 'D': ["1110", "1001", "1001", "1001", "1110"], 'E': ["1111", "1000", "1110", "1000", "1111"], 'G': ["0110", "1000", "1011", "1001", "0111"], 'H': ["1001", "1001", "1111", "1001", "1001"], 'I': ["111", "010", "010", "010", "111"], 'K': ["1001", "1010", "1100", "1010", "1001"], 'L': ["1000", "1000", "1000", "1000", "1111"], 'M': ["10001", "11011", "10101", "10001", "10001"], 'N': ["1001", "1101", "1011", "1001", "1001"], 'O': ["0110", "1001", "1001", "1001", "0110"], 'P': ["1110", "1001", "1110", "1000", "1000"], 'R': ["1110", "1001", "1110", "1010", "1001"], 'S': ["0111", "1000", "0110", "0001", "1110"], 'U': ["1001", "1001", "1001", "1001", "0110"], 'V': ["10001", "10001", "01010", "01010", "00100"], ' ': ["000", "000", "000", "000", "000"]
    };
    _charHeight = 5;

    constructor() {
      super("Art & Effects Tool", '<i class="fas fa-paint-brush"></i>');
      this._onStartup();
    }

    _onStartup() {
      this._canvas = document.getElementById('canvas');
      if (this._canvas) {
        this._ctx = this._canvas.getContext('2d');
      } else {
        this.notify("error", "Canvas del juego no encontrado para Art & Effects Tool.");
        return;
      }
      this._previewCanvas = document.createElement('canvas');
      this._previewCtx = this._previewCanvas.getContext('2d');

      this._loadInterface();
      this._setupEventListeners();
      this.notify("info", "Módulo 'Art & Effects Tool' cargado.");
    }

    _loadInterface() {
      const container = domMake.Tree("div", { id: `${this.identifier}-container`, class: "module-section" });
      this.htmlElements.section.appendChild(container);

      // Image Drawing Section
      container.appendChild(domMake.Tree("div", { class: "module-section-title" }, ["Dibujo de Imágenes"]));
      const imageDrawSection = domMake.Tree("div", {});
      this._createInputRow(imageDrawSection, "img-file-input", "Cargar Imagen:", "file", null, "image/*");
      this._createInputRow(imageDrawSection, "img-url-input", "O URL de Imagen:", "text", null, null, "Enter image URL");
      container.appendChild(imageDrawSection);

      const imageSettingsDetails = domMake.Tree("details", {});
      imageSettingsDetails.appendChild(domMake.Tree("summary", {}, ["Configuración de Dibujo"]));
      const imageSettingsDiv = domMake.Tree("div", {});
      this._createInputRow(imageSettingsDiv, "engine_imagesize", "Pixel Step:", "number", "5", "1", "50");
      this._createInputRow(imageSettingsDiv, "engine_brushsize", "Brush Size:", "number", "3", "1", "100");
      this._createInputRow(imageSettingsDiv, "engine_offset_x", "Offset X (%):", "number", "0", "-100", "100");
      this._createInputRow(imageSettingsDiv, "engine_offset_y", "Offset Y (%):", "number", "0", "-100", "100");
      this._createInputRow(imageSettingsDiv, "engine_draw_delay", "Draw Delay (ms):", "number", "10", "0", "1000");
      imageSettingsDetails.appendChild(imageSettingsDiv);
      container.appendChild(imageSettingsDetails);

      const imageDrawButtons = domMake.Tree("div", { class: "artfx-button-grid" });
      this._ui.imgDrawStartButton = domMake.Button('<i class="fas fa-play"></i> Dibujar Imagen', { class: "artfx-button" });
      this._ui.imgDrawStopButton = domMake.Button('<i class="fas fa-stop"></i> Detener', { class: "artfx-button danger" });
      imageDrawButtons.appendAll(this._ui.imgDrawStartButton, this._ui.imgDrawStopButton);
      container.appendChild(imageDrawButtons);

      // Generative Art Effects Section
      container.appendChild(domMake.Tree("div", { class: "module-section-title" }, ["Efectos de Arte Generativo"]));
      const generativeEffectsButtons = domMake.Tree("div", { class: "artfx-button-grid" });
      this._ui.pixelArtCharsButton = domMake.Button('<i class="fas fa-gamepad"></i> Pixel Art VS', { class: "artfx-button special" });
      this._ui.pulsatingStainedGlassButton = domMake.Button('<i class="fas fa-vector-square"></i> Vidrio Pulsante', { class: "artfx-button special" });
      this._ui.celestialBalletButton = domMake.Button('<i class="fas fa-atom"></i> Ballet Celeste', { class: "artfx-button special" });
      this._ui.recursiveStarNovaButton = domMake.Button('<i class="fas fa-star-and-crescent"></i> Nova Estelar', { class: "artfx-button special" });
      this._ui.fractalMandalaButton = domMake.Button('<i class="fas fa-infinity"></i> Mandala Fractal', { class: "artfx-button special" });
      this._ui.directionalHueBlastButton = domMake.Button('<i class="fas fa-compass"></i> Explosión de Tono', { class: "artfx-button special" });
      this._ui.colorFestivalButton = domMake.Button('<i class="fas fa-palette"></i> Festival de Color', { class: "artfx-button special" });
      this._ui.fireworksButton = domMake.Button('<i class="fas fa-firecracker"></i> Fuegos Artificiales', { class: "artfx-button special" });

      generativeEffectsButtons.appendAll(
        this._ui.pixelArtCharsButton,
        this._ui.pulsatingStainedGlassButton,
        this._ui.celestialBalletButton,
        this._ui.recursiveStarNovaButton,
        this._ui.fractalMandalaButton,
        this._ui.directionalHueBlastButton,
        this._ui.colorFestivalButton,
        this._ui.fireworksButton
      );
      container.appendChild(generativeEffectsButtons);

      this._ui.clearCanvasButton = domMake.Button('<i class="fas fa-eraser"></i> Limpiar Todo', { class: "artfx-clear-button" });
      container.appendChild(this._ui.clearCanvasButton);
    }

    _createInputRow(parent, id, label, type, value = null, accept = null, placeholder = null) {
      const row = domMake.Tree("div", { class: "artfx-input-row" });
      const inputElement = domMake.Tree("input", { type: type, id: `${this.identifier}-${id}`, placeholder: placeholder });
      if (value !== null) inputElement.value = value;
      if (accept !== null) inputElement.accept = accept;
      row.appendAll(domMake.Tree("label", { for: `${this.identifier}-${id}` }, [label]), inputElement);
      parent.appendChild(row);
      this._ui[id.replace(/-([a-z])/g, (g) => g[1].toUpperCase())] = inputElement; // Store reference
    }

    _setupEventListeners() {
      // Image Drawing Listeners
      this._ui.imgFileInput.addEventListener('change', (e) => {
        if (!e.target.files || !e.target.files[0]) return;
        const FR = new FileReader();
        FR.onload = e => this._loadImage(e.target.result);
        FR.readAsDataURL(e.target.files[0]);
        this._ui.imgUrlInput.value = '';
      });
      this._ui.imgUrlInput.addEventListener('change', (e) => {
        if (e.target.value.trim() !== "") this._loadImage(e.target.value.trim());
        this._ui.imgFileInput.value = '';
      });
      this._ui.imgDrawStartButton.addEventListener('click', () => {
        if (!getGameSocket()) { this.notify("warning", "No hay conexión al juego. Por favor, asegúrate de estar en una sala."); return; }
        const pS = this._ui.engineImagesize.value, bS = this._ui.engineBrushsize.value, oX = this._ui.engineOffsetX.value, oY = this._ui.engineOffsetY.value;
        const cmds = this._generateImageDrawingCommands(pS, bS, { x: oX, y: oY });
        if (cmds.length > 0) this._executeDrawingCommands(cmds);
        else this.notify("warning", "No se generaron comandos de dibujo desde la imagen. Asegúrate de que una imagen está cargada.");
      });
      this._ui.imgDrawStopButton.addEventListener('click', () => this._drawingActive = false);

      // Generative Art Effects Listeners
      this._ui.pixelArtCharsButton.addEventListener('click', () => this._pixelArtCharacters());
      this._ui.pulsatingStainedGlassButton.addEventListener('click', () => this._pulsatingStainedGlass());
      this._ui.celestialBalletButton.addEventListener('click', () => this._celestialBallet());
      this._ui.recursiveStarNovaButton.addEventListener('click', () => this._recursiveStarPolygonNova());
      this._ui.fractalMandalaButton.addEventListener('click', () => this._fractalBloomMandala());
      this._ui.directionalHueBlastButton.addEventListener('click', () => this._directionalHueBlast());
      this._ui.colorFestivalButton.addEventListener('click', () => this._colorFestival());
      this._ui.fireworksButton.addEventListener('click', () => this._lightSpeedFireworks());

      // Clear Canvas Button
      this._ui.clearCanvasButton.addEventListener('click', () => this._clearCanvas());
    }

    _delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }
    _getRandomColor(saturation = 100, lightness = 50) {
      const hue = Math.floor(Math.random() * 360);
      return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
    }

    _sendDrawCmd(start, end, color, thickness, isEraser = false, algo = 0) {
      if (!this._canvas || !this._ctx) {
        this.notify("error", "Canvas o contexto no disponible para dibujo.");
        return false;
      }
      const socket = getGameSocket();
      if (!socket || socket.readyState !== WebSocket.OPEN) {
        this.notify("warning", "WebSocket no conectado. Comando no enviado.");
        return false;
      }

      const actualThickness = isEraser ? thickness : thickness;
      this._ctx.strokeStyle = color;
      this._ctx.lineWidth = actualThickness;
      this._ctx.lineCap = 'round';
      this._ctx.beginPath();
      this._ctx.moveTo(start[0] * this._canvas.width, start[1] * this._canvas.height);
      this._ctx.lineTo(end[0] * this._canvas.width, end[1] * this._canvas.height);
      this._ctx.stroke();

      const gT = isEraser ? thickness : 0 - thickness;
      socket.send(`42["drawcmd",0,[${start[0].toFixed(4)},${start[1].toFixed(4)},${end[0].toFixed(4)},${end[1].toFixed(4)},${isEraser},${gT},"${color}",0,0,{"2":${algo},"3":0.5,"4":0.5}]]`);
      return true;
    }

    async _drawPixel(x, y, size, color, draw_delay) {
      const endX = x + size * 0.0001;
      const endY = y + size * 0.0001;
      const effectiveThickness = size * Math.min(this._canvas.width, this._canvas.height) * 0.9;

      if (!this._sendDrawCmd([x, y], [endX, endY], color, effectiveThickness)) {
        this._drawingActive = false;
        return false;
      }
      if (draw_delay > 0) await this._delay(draw_delay);
      return true;
    }

    _loadImage(url) {
      if (!this._canvas) { this.notify("error", "Main game canvas not found!"); return; }
      var img = new Image();
      img.onload = () => {
        this._previewCanvas.width = this._canvas.width;
        this._previewCanvas.height = this._canvas.height;

        var tempCtx = this._previewCanvas.getContext('2d');
        let sW = img.width, sH = img.height, sF = 1;
        if (img.width > this._canvas.width || img.height > this._canvas.height) sF = Math.min(this._canvas.width / img.width, this._canvas.height / img.height);
        sW = img.width * sF;
        sH = img.height * sF;
        const oX = (this._canvas.width - sW) / 2;
        const oY = (this._canvas.height - sH) / 2;

        tempCtx.clearRect(0, 0, this._canvas.width, this._canvas.height);
        tempCtx.drawImage(img, oX, oY, sW, sH);
        try {
          this._imageData = tempCtx.getImageData(0, 0, this._canvas.width, this._canvas.height).data;
          this.notify("info", 'Image loaded for processing.');
        } catch (e) {
          this.notify("error", "Error processing image. Check console for details.");
          this._imageData = null;
        }
      };
      img.onerror = () => { this.notify("error", "Failed image load. Check URL or CORS."); };
      img.crossOrigin = 'anonymous';
      img.src = url;
    }

    _generateImageDrawingCommands(pixelStep, brushThickness, offsetPercent, forceBW = false) {
      if (!this._imageData) { this.notify("warning", 'No image data to draw. Load an image first.'); return []; }
      this._executionLine = [];
      const step = Math.max(1, parseInt(pixelStep, 10));
      const thick = parseInt(brushThickness, 10);
      const oX_norm = parseInt(offsetPercent.x, 10) / 100;
      const oY_norm = parseInt(offsetPercent.y, 10) / 100;

      const cw = this._canvas.width;
      const ch = this._canvas.height;

      for (let y = 0; y < ch; y += step) {
        let lastStartNormalized = null;
        let lastColor = null;

        for (let x = 0; x < cw; x += step) {
          if (y >= ch || x >= cw) continue;

          let idx = (y * cw + x) * 4;
          if (idx + 3 >= this._imageData.length) continue;

          let r = this._imageData[idx];
          let g = this._imageData[idx + 1];
          let b = this._imageData[idx + 2];
          let a = this._imageData[idx + 3];

          let currentColor = `rgb(${r},${g},${b})`;
          if (forceBW) {
            const gs = (r + g + b) / 3;
            currentColor = gs < 128 ? 'rgb(0,0,0)' : 'rgb(255,255,255)';
          }

          const currentPixelNormalized = [(x / cw) + oX_norm, (y / ch) + oY_norm];

          if (a > 128) {
            if (lastStartNormalized === null) {
              lastStartNormalized = currentPixelNormalized;
              lastColor = currentColor;
            } else if (currentColor !== lastColor) {
              const endXNormalized = ((x - step) / cw) + oX_norm;
              this._executionLine.push({
                pos1: lastStartNormalized,
                pos2: [endXNormalized, currentPixelNormalized[1]],
                color: lastColor,
                thickness: thick
              });
              lastStartNormalized = currentPixelNormalized;
              lastColor = currentColor;
            }
          } else {
            if (lastStartNormalized !== null) {
              const endXNormalized = ((x - step) / cw) + oX_norm;
              this._executionLine.push({
                pos1: lastStartNormalized,
                pos2: [endXNormalized, currentPixelNormalized[1]],
                color: lastColor,
                thickness: thick
              });
              lastStartNormalized = null;
              lastColor = null;
            }
          }
        }
        if (lastStartNormalized !== null) {
          this._executionLine.push({
            pos1: lastStartNormalized,
            pos2: [((cw - 1) / cw) + oX_norm, lastStartNormalized[1]],
            color: lastColor,
            thickness: thick
          });
        }
      }
      this.notify("info", `Image drawing commands generated: ${this._executionLine.length} lines.`);
      return this._executionLine;
    }

    async _executeDrawingCommands(commands) {
      if (!getGameSocket() || getGameSocket().readyState !== WebSocket.OPEN) {
        this.notify("error", "Not connected to Drawaria. Please be in a room.");
        this._drawingActive = false;
        return;
      }
      this._drawingActive = true;
      this.notify("info", `Drawing ${commands.length} lines...`);
      const draw_delay = parseInt(this._ui.engineDrawDelay.value, 10) || 10;

      for (let i = 0; i < commands.length; i++) {
        if (!this._drawingActive) {
          this.notify("info", "Drawing stopped by user.");
          break;
        }
        let line = commands[i];
        if (!this._sendDrawCmd(line.pos1, line.pos2, line.color, line.thickness)) {
          this.notify("warning", "Drawing interrupted: WebSocket closed or error.");
          break;
        }
        if (draw_delay > 0) await this._delay(draw_delay);
      }
      this._drawingActive = false;
      this.notify("success", 'Finished drawing.');
    }

    async _clearCanvas() {
      if (!this._canvas || !this._ctx) {
        this.notify("error", "Canvas not found, cannot clear locally.");
        return;
      }
      if (!getGameSocket() || getGameSocket().readyState !== WebSocket.OPEN) {
        this.notify("error", "Not connected to Drawaria. Cannot send clear command to server.");
        return;
      }

      this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);

      this.notify("info", "Sending clear commands...");
      const clearThickness = 2000;
      const clearColor = '#FFFFFF';
      const steps = 5;

      for (let i = 0; i <= steps; i++) {
        if (!this._sendDrawCmd([0.01, (i / steps)], [0.99, (i / steps)], clearColor, clearThickness, true)) break;
        await this._delay(5);
        if (!this._sendDrawCmd([(i / steps), 0.01], [(i / steps), 0.99], clearColor, clearThickness, true)) break;
        await this._delay(5);
      }
      this.notify("success", "Clear commands sent.");
    }

    async _drawPixelText(text, startX, startY, charPixelSize, color, textPixelDelay, letterSpacingFactor = 0.8) {
      let currentX = startX;
      text = text.toUpperCase();

      for (const char of text) {
        if (!this._drawingActive) return;
        const charData = this._pixelFont[char];
        if (charData) {
          let charWidth = 0;
          for (let y = 0; y < this._charHeight; y++) {
            if (!this._drawingActive) return;
            const row = charData[y];
            charWidth = Math.max(charWidth, row.length);
            for (let x = 0; x < row.length; x++) {
              if (!this._drawingActive) return;
              if (row[x] === '1') {
                const dX = currentX + x * charPixelSize;
                const dY = startY + y * charPixelSize;
                if (!await this._drawPixel(dX, dY, charPixelSize, color, textPixelDelay)) return;
              }
            }
          }
          currentX += (charWidth + letterSpacingFactor) * charPixelSize;
        } else {
          currentX += (3 + letterSpacingFactor) * charPixelSize;
        }
      }
    }

    async _pixelArtCharacters() {
      if (!getGameSocket() || getGameSocket().readyState !== WebSocket.OPEN) { this.notify("error", "Not connected to Drawaria. Please be in a room."); return; }
      this._drawingActive = true; this.notify("info", "Starting Enhanced Pixel Art Characters...");

      const Q_TOP_LEFT = { xMin: 0.0, yMin: 0.0, xMax: 0.5, yMax: 0.5 };
      const Q_TOP_RIGHT = { xMin: 0.5, yMin: 0.0, xMax: 1.0, yMax: 0.5 };
      const Q_BOTTOM_LEFT = { xMin: 0.0, yMin: 0.5, xMax: 0.5, yMax: 1.0 };
      const Q_BOTTOM_RIGHT = { xMin: 0.5, yMin: 0.5, xMax: 1.0, yMax: 1.0 };

      const marioSprite = {
        name: "MARIO", nameColor: "#FF0000", width: 12,
        data: ["____RRRRR___", "___RRRRRRR__", "___NNNYNY___", "__NSSYSYYN__", "__NSSYSYYYNN", "__NYYYYYYYYN", "____BBBB____", "__RBBBRBBR__", "_RBBRRRBBRR_", "RBBBBBRBBBB_", "BBBBBBRBBBBB", "BBBB__BBBB__", "NNN____NNN__", "_NN____NN___"],
        colors: { R: "#E60000", N: "#7A3D03", Y: "#FBD000", S: "#FFCC99", B: "#0040FF" },
        quadrant: Q_TOP_LEFT, textOffsetY: -0.08
      };
      const pikachuSprite = {
        name: "PIKACHU", nameColor: "#FFA500", width: 13,
        data: ["____PPPPP____", "___PKKKPKK___", "__PKKPKPKKK__", "_PKKPKKPKPKK_", "_PKKPOKPKPOKK", "PPKPKKKPKPKPP", "PPKPK_KPKPKPP", "_PKPKKKPKPKP_", "__PKKKKKPKP__", "___PPPPPPP___", "____PP_PP____"],
        colors: { P: "#FFDE38", K: "#000000", O: "#FF4444", W: "#FFFFFF" },
        quadrant: Q_TOP_RIGHT, textOffsetY: -0.08
      };
      const linkSprite = {
        name: "LINK", nameColor: "#008000", width: 11,
        data: ["____GGG____", "___GGGGG___", "__LGGGGGL__", "_LGSYYSGLS_", "_GSSSSSGSG_", "__GSSSG GG_", "___GGGGG___", "___GNGNG___", "___GNGNG___", "__NNYNYNN__", "_BN___NB_", "B_______B"],
        colors: { G: "#00A000", L: "#90EE90", S: "#FFDBAC", Y: "#FFFF99", N: "#704830", B: "#503020" },
        quadrant: Q_BOTTOM_LEFT, textOffsetY: 0.13
      };
      const sonicSprite = {
        name: "SONIC", nameColor: "#0000FF", width: 13,
        data: ["___CCCCCCC___", "__CCCWCCCWC__", "_CCWCCCWCWCC_", "_CTWCWCWTWCC_", "CTTTWCWTTTWCW", "CTTT K TTTWCW", "CCTTTTTTWCC_", "_CCTTTTTCC__", "__EWWWEWWWE__", "__E_W_W_W_E__", "___E___E____"],
        colors: { C: "#0070FF", T: "#C0D8F0", W: "#FFFFFF", E: "#D00000", K: "#000000" },
        quadrant: Q_BOTTOM_RIGHT, textOffsetY: 0.13
      };

      const characters = [marioSprite, pikachuSprite, linkSprite, sonicSprite];
      const pixelDrawDelay = 3;
      const textPixelDelay = 2;
      const textCharPixelSize = 0.008;

      const lineThickness = 8;
      const vsTextSize = 0.02;
      const vsColor = "#000000";
      if (!this._sendDrawCmd([0, 0.5], [1, 0.5], marioSprite.colors.R, lineThickness)) { this._drawingActive = false; return; }
      if (!this._sendDrawCmd([0.5, 0], [0.5, 1], pikachuSprite.colors.O, lineThickness)) { this._drawingActive = false; return; }
      if (!this._sendDrawCmd([0.5, 0.5], [0.5, 1.0], sonicSprite.colors.C, lineThickness)) { this._drawingActive = false; return; }
      if (!this._sendDrawCmd([0.0, 0.5], [0.5, 0.5], linkSprite.colors.G, lineThickness)) { this._drawingActive = false; return; }

      await this._delay(100);
      if (this._drawingActive) await this._drawPixelText("VS", 0.5 - (vsTextSize * (this._pixelFont['V'][0].length + this._pixelFont['S'][0].length + 0.8)) / 2, 0.5 - (vsTextSize * this._charHeight) / 2, vsTextSize, vsColor, textPixelDelay);
      await this._delay(100);

      for (const char of characters) {
        if (!this._drawingActive) break;
        const charHeightPx = char.data.length;
        const charWidthPx = char.width;
        const quadW = char.quadrant.xMax - char.quadrant.xMin;
        const quadH = char.quadrant.yMax - char.quadrant.yMin;

        const scaleFactor = 0.65;
        const pixelSizeX = (quadW * scaleFactor) / charWidthPx;
        const pixelSizeY = (quadH * scaleFactor) / charHeightPx;
        const finalPixelSize = Math.min(pixelSizeX, pixelSizeY);

        const totalSpriteW = charWidthPx * finalPixelSize;
        const totalSpriteH = charHeightPx * finalPixelSize;
        const startX = char.quadrant.xMin + (quadW - totalSpriteW) / 2;
        const startY = char.quadrant.yMin + (quadH - totalSpriteH) / 2;

        const nameLenEst = char.name.length * (this._pixelFont['M'] ? this._pixelFont['M'][0].length : 3) * textCharPixelSize;
        const textStartX = char.quadrant.xMin + (quadW - nameLenEst) / 2;
        let textStartY;
        if (char.textOffsetY < 0) {
          textStartY = startY + char.textOffsetY - (this._charHeight * textCharPixelSize);
        } else {
          textStartY = startY + totalSpriteH + char.textOffsetY;
        }
        textStartY = Math.max(char.quadrant.yMin + 0.01, Math.min(char.quadrant.yMax - 0.01 - (this._charHeight * textCharPixelSize), textStartY));

        if (this._drawingActive) await this._drawPixelText(char.name, textStartX, textStartY, textCharPixelSize, char.nameColor, textPixelDelay);
        await this._delay(50);

        for (let y = 0; y < charHeightPx; y++) {
          if (!this._drawingActive) break;
          for (let x = 0; x < charWidthPx; x++) {
            if (!this._drawingActive) break;
            const colorChar = char.data[y][x];
            if (colorChar !== "_" && char.colors[colorChar]) {
              const dX = startX + x * finalPixelSize;
              const dY = startY + y * finalPixelSize;
              if (!await this._drawPixel(dX, dY, finalPixelSize, char.colors[colorChar], pixelDrawDelay)) { this._drawingActive = false; break; }
            }
          }
        }
        if (!this._drawingActive) break;
        await this._delay(200);
      }
      this._drawingActive = false; this.notify("success", "Enhanced Pixel Art Characters finished.");
    }

    async _directionalHueBlast() {
      if (!getGameSocket() || getGameSocket().readyState !== WebSocket.OPEN) { this.notify("error", "Not connected to Drawaria. Please be in a room."); return; }
      this._drawingActive = true; this.notify("info", "Starting Directional Hue Blast...");
      const d = [{ s: [0.5, 0], e: [0.5, 1] }, { s: [0.5, 1], e: [0.5, 0] }, { s: [0, 0.5], e: [1, 0.5] }, { s: [1, 0.5], e: [0, 0.5] }];
      for (let i = 0; i < 100 && this._drawingActive; i++) {
        for (let D of d) {
          if (!this._drawingActive) break;
          let t = i / 100, x = D.s[0] + (D.e[0] - D.s[0]) * t, y = D.s[1] + (D.e[1] - D.s[1]) * t;
          if (!this._sendDrawCmd([x, y], [x + 0.001, y + 0.001], `hsl(${i * 3.6},100%,50%)`, 10 + i * 0.5)) break;
        }
        if (!this._drawingActive) break;
        await this._delay(40);
      }
      this._drawingActive = false; this.notify("success", "Directional Hue Blast finished.");
    }

    async _colorFestival() {
      if (!getGameSocket() || getGameSocket().readyState !== WebSocket.OPEN) { this.notify("error", "Not connected to Drawaria. Please be in a room."); return; }
      this._drawingActive = true; this.notify("info", "Starting Color Festival...");
      const nS = 120, fD = 60, sD = 10;
      for (let i = 0; i < nS && this._drawingActive; i++) {
        let x = Math.random() * 0.8 + 0.1, y = Math.random() * 0.8 + 0.1, bS = Math.random() * 0.08 + 0.03, c = this._getRandomColor(90, 55), t = Math.floor(Math.random() * 10) + 4, type = Math.floor(Math.random() * 4), ok = true;
        if (type === 0) {
          for (let j = 0; j < bS * 100 && ok && this._drawingActive; j += t / 2) {
            let lY = y - bS / 2 + (j / 100);
            if (lY > y + bS / 2) break;
            ok = this._sendDrawCmd([x - bS / 2, lY], [x + bS / 2, lY], c, t);
            if (sD > 0 && ok) await this._delay(sD);
          }
        } else if (type === 1) {
          const s = bS;
          ok = this._sendDrawCmd([x, y - s / 2], [x + s / 2, y + s / 2], c, t);
          if (ok && this._drawingActive && sD > 0) await this._delay(sD); if (!ok || !this._drawingActive) break;
          ok = this._sendDrawCmd([x + s / 2, y + s / 2], [x - s / 2, y + s / 2], c, t);
          if (ok && this._drawingActive && sD > 0) await this._delay(sD); if (!ok || !this._drawingActive) break;
          ok = this._sendDrawCmd([x - s / 2, y + s / 2], [x, y - s / 2], c, t);
        } else if (type === 2) {
          const s = bS * 0.7;
          for (let k = 0; k < 8 && ok && this._drawingActive; k++) {
            const a = (k / 8) * 2 * Math.PI;
            ok = this._sendDrawCmd([x, y], [x + s * Math.cos(a), y + s * Math.sin(a)], c, t);
            if (sD > 0 && ok) await this._delay(sD);
          }
        } else {
          let lX = x, lY = y;
          for (let k = 0; k <= 20 && ok && this._drawingActive; k++) {
            const a = (k / 20) * 2 * 2 * Math.PI, r = (k / 20) * bS, cX = x + r * Math.cos(a), cY = y + r * Math.sin(a);
            if (k > 0) ok = this._sendDrawCmd([lX, lY], [cX, cY], c, t);
            lX = cX; lY = cY;
            if (sD > 0 && ok) await this._delay(sD);
          }
        }
        if (!ok || !this._drawingActive) break;
        if (fD > 0) await this._delay(fD);
      }
      this._drawingActive = false; this.notify("success", "Color Festival finished.");
    }

    async _lightSpeedFireworks() {
      if (!getGameSocket() || getGameSocket().readyState !== WebSocket.OPEN) { this.notify("error", "Not connected to Drawaria. Please be in a room."); return; }
      this._drawingActive = true; this.notify("info", "Starting Light Speed Fireworks...");
      const nF = 8, fD = 600;
      for (let i = 0; i < nF && this._drawingActive; i++) {
        let sX = Math.random() * 0.6 + 0.2, sY = 0.95, pX = sX + (Math.random() - 0.5) * 0.3, pY = Math.random() * 0.4 + 0.05, lC = this._getRandomColor(100, 70), lT = 6, pC = 40 + Math.floor(Math.random() * 40), pT = 4 + Math.floor(Math.random() * 4), lS = 25, lSD = 4, ePD = 8, ok = true;
        for (let s = 0; s < lS && ok && this._drawingActive; s++) {
          let pr = s / lS, nP = (s + 1) / lS, cX = sX + (pX - sX) * pr, cY = sY + (pY - sY) * pr, nX = sX + (pX - sX) * nP, nY = sY + (pY - sY) * nP;
          ok = this._sendDrawCmd([cX, cY], [nX, nY], lC, lT);
          if (lSD > 0 && ok) await this._delay(lSD);
        }
        if (!ok || !this._drawingActive) break;
        const eH = Math.random() * 360;
        for (let j = 0; j < pC && ok && this._drawingActive; j++) {
          const a = Math.random() * 2 * Math.PI, d = Math.random() * 0.20 + 0.05, eX = pX + d * Math.cos(a), eY = pY + d * Math.sin(a);
          const pH = (eH + (Math.random() - 0.5) * 60 + 360) % 360;
          ok = this._sendDrawCmd([pX, pY], [eX, eY], `hsl(${pH},100%,60%)`, pT);
          if (ePD > 0 && ok) await this._delay(ePD);
        }
        if (!ok || !this._drawingActive) break;
        if (fD > 0) await this._delay(fD);
      }
      this._drawingActive = false; this.notify("success", "Light Speed Fireworks finished.");
    }

    async _fractalBloomMandala() {
      if (!getGameSocket() || getGameSocket().readyState !== WebSocket.OPEN) { this.notify("error", "Not connected to Drawaria. Please be in a room."); return; }
      this._drawingActive = true; this.notify("info", "Starting Fractal Bloom Mandala...");
      const cX = 0.5, cY = 0.5, mD = 4, iB = 6 + Math.floor(Math.random() * 3), iL = 0.15, lR = 0.65, aS = Math.PI / (3 + Math.random() * 2), del = 20, bH = Math.random() * 360;
      let gRot = this._globalFrameCount * 0.01;
      async function dB(cx, cy, A, L, D, cH, bT) {
        if (!this._drawingActive || D > mD || L < 0.005) return;
        const x2 = cx + L * Math.cos(A), y2 = cy + L * Math.sin(A), t = Math.max(1, bT * Math.pow(lR, D - 1) * 2), c = `hsl(${(cH + D * 20) % 360},${80 - D * 10}%,${60 - D * 8}%)`;
        if (!this._sendDrawCmd([cx, cy], [x2, y2], c, t)) { this._drawingActive = false; return; }
        if (del > 0) await this._delay(del);
        if (!this._drawingActive) return;
        await dB.call(this, x2, y2, A - aS, L * lR, D + 1, cH, bT);
        if (!this._drawingActive) return;
        await dB.call(this, x2, y2, A + aS, L * lR, D + 1, cH, bT);
        if (D < mD - 1 && Math.random() < 0.4) {
          if (!this._drawingActive) return;
          await dB.call(this, x2, y2, A, L * lR * 0.8, D + 1, cH, bT);
        }
      }
      for (let i = 0; i < iB && this._drawingActive; i++) {
        const A = (i / iB) * 2 * Math.PI + gRot;
        await dB.call(this, cX, cY, A, iL, 1, (bH + i * (360 / iB)) % 360, 10);
        if (del > 0 && this._drawingActive) await this._delay(del * 3);
      }
      this._globalFrameCount++; this._drawingActive = false; this.notify("success", "Fractal Bloom Mandala finished.");
    }

    async _pulsatingStainedGlass() {
      if (!getGameSocket() || getGameSocket().readyState !== WebSocket.OPEN) { this.notify("error", "Not connected to Drawaria. Please be in a room."); return; }
      this._drawingActive = true; this.notify("info", "Starting Pulsating Stained Glass...");
      const gX = 5 + Math.floor(Math.random() * 4), gY = 4 + Math.floor(Math.random() * 3), cW = 1 / gX, cH = 1 / gY, aS = 150, gD = 50, lT = 3, lC = "rgb(40,40,40)";
      let C = [];
      for (let r = 0; r < gY; r++) {
        for (let c = 0; c < gX; c++) {
          const cT = Math.random();
          let p = [];
          const x = c * cW, y = r * cH, w = cW, h = cH;
          if (cT < 0.33) { p = [[x, y], [x + w, y], [x + w, y + h], [x, y + h]]; } else if (cT < 0.66) { if (Math.random() < 0.5) { p = [[x, y], [x + w, y], [x + w, y + h], [x, y], [x, y + h], [x + w, y + h]]; } else { p = [[x, y], [x + w, y], [x, y + h], [x + w, y], [x + w, y + h], [x, y + h]]; } } else { const cx = x + w / 2, cy = y + h / 2; p = [[x, y], [x + w, y], [cx, cy], [x + w, y], [x + w, y + h], [cx, cy], [x + w, y + h], [x, y + h], [cx, cy], [x, y + h], [x, y], [cx, cy]]; }
          C.push({ bP: p, h: Math.random() * 360, lP: Math.random() * Math.PI * 2, lS: 0.05 + Math.random() * 0.1 });
        }
      }
      for (const cell of C) {
        if (!this._drawingActive) break;
        for (let i = 0; i < cell.bP.length; i += 3) {
          if (!this._drawingActive || i + 2 >= cell.bP.length) break;
          const p1 = cell.bP[i], p2 = cell.bP[i + 1], p3 = cell.bP[i + 2];
          if (!this._sendDrawCmd(p1, p2, lC, lT)) { this._drawingActive = false; break; }
          if (!this._sendDrawCmd(p2, p3, lC, lT)) { this._drawingActive = false; break; }
          if (!this._sendDrawCmd(p3, p1, lC, lT)) { this._drawingActive = false; break; }
          await this._delay(5);
        }
      }
      for (let f = 0; f < aS && this._drawingActive; f++) {
        for (const cell of C) {
          if (!this._drawingActive) break;
          const cL = 40 + 20 * Math.sin(cell.lP + f * cell.lS);
          const col = `hsl(${cell.h},80%,${cL}%)`;
          for (let i = 0; i < cell.bP.length; i += 3) {
            if (!this._drawingActive || i + 2 >= cell.bP.length) break;
            const p1 = cell.bP[i], p2 = cell.bP[i + 1], p3 = cell.bP[i + 2];
            const m12 = [(p1[0] + p2[0]) / 2, (p1[1] + p2[1]) / 2], m23 = [(p2[0] + p3[0]) / 2, (p2[1] + p3[1]) / 2];
            if (!this._sendDrawCmd(m12, p3, col, lT * 3 + 2)) { this._drawingActive = false; break; }
            if (!this._sendDrawCmd(m23, p1, col, lT * 3 + 2)) { this._drawingActive = false; break; }
          }
        }
        if (!this._drawingActive) break;
        await this._delay(gD);
      }
      this._drawingActive = false; this.notify("success", "Pulsating Stained Glass finished.");
    }

    async _celestialBallet() {
      if (!getGameSocket() || getGameSocket().readyState !== WebSocket.OPEN) { this.notify("error", "Not connected to Drawaria. Please be in a room."); return; }
      this._drawingActive = true; this.notify("info", "Starting Celestial Ballet...");
      const nD = 8 + Math.floor(Math.random() * 5), st = 150, th = 3, bD = 25, D = [];
      for (let i = 0; i < nD; i++) {
        D.push({ x: 0.5, y: 0.5, vx: (Math.random() - 0.5) * 0.02, vy: (Math.random() - 0.5) * 0.02, oC_X: 0.5 + (Math.random() - 0.5) * 0.4, oC_Y: 0.5 + (Math.random() - 0.5) * 0.4, oS: (Math.random() * 0.05 + 0.02) * (Math.random() < 0.5 ? 1 : -1), h: Math.random() * 360, lX: 0.5, lY: 0.5 });
      }
      for (let S = 0; S < st && this._drawingActive; S++) {
        for (const d of D) {
          if (!this._drawingActive) break;
          d.lX = d.x; d.lY = d.y;
          const aTO = Math.atan2(d.y - d.oC_Y, d.x - d.oC_X);
          d.vx += Math.cos(aTO + Math.PI / 2) * d.oS * 0.1; d.vy += Math.sin(aTO + Math.PI / 2) * d.oS * 0.1;
          d.vx += (0.5 - d.x) * 0.0005; d.vy += (0.5 - d.y) * 0.0005;
          d.vx *= 0.97; d.vy *= 0.97;
          d.x += d.vx; d.y += d.vy;
          if (d.x < 0.01 || d.x > 0.99) d.vx *= -0.8;
          if (d.y < 0.01 || d.y > 0.99) d.vy *= -0.8;
          d.x = Math.max(0.01, Math.min(0.99, d.x)); d.y = Math.max(0.01, Math.min(0.99, d.y));
          d.h = (d.h + 0.5) % 360;
          const c = `hsl(${d.h},100%,70%)`;
          if (!this._sendDrawCmd([d.lX, d.lY], [d.x, d.y], c, th)) { this._drawingActive = false; break; }
        }
        if (bD > 0 && this._drawingActive) await this._delay(bD);
      }
      this._drawingActive = false; this.notify("success", "Celestial Ballet finished.");
    }

    async _recursiveStarPolygonNova() {
      if (!getGameSocket() || getGameSocket().readyState !== WebSocket.OPEN) { this.notify("error", "Not connected to Drawaria. Please be in a room."); return; }
      this._drawingActive = true; this.notify("info", "Starting Recursive Star Polygon Nova...");
      const cX = 0.5, cY = 0.5, iR = 0.25, mD = 3 + Math.floor(Math.random() * 1), nP = 5 + Math.floor(Math.random() * 2) * 2, sF = 2 + Math.floor(Math.random() * 1), rSF = 0.4, sD = 15, bH = Math.random() * 360;
      let gRot = this._globalFrameCount * 0.01;
      async function dS(cx, cy, r, P, sk, D, cH, cT, pA) {
        if (!this._drawingActive || D > mD || r < 0.005) return;
        const sC = [];
        for (let i = 0; i < P; i++) { const a = (i / P) * 2 * Math.PI + pA + gRot; sC.push({ x: cx + r * Math.cos(a), y: cy + r * Math.sin(a) }); };
        const col = `hsl(${(cH + D * 30) % 360},95%,${65 - D * 10}%)`, th = Math.max(1, cT);
        for (let i = 0; i < P && this._drawingActive; i++) {
          const p1 = sC[i], p2 = sC[(i + sk) % P];
          if (!this._sendDrawCmd([p1.x, p1.y], [p2.x, p2.y], col, th)) { this._drawingActive = false; return; }
          if (sD > 0 && this._drawingActive) await this._delay(sD);
        }
        for (let i = 0; i < P && this._drawingActive; i++) {
          const nA = (i / P) * 2 * Math.PI + pA + Math.PI / P;
          await dS.call(this, sC[i].x, sC[i].y, r * rSF, P, sk, D + 1, cH, th * 0.7, nA);
        }
      }
      await dS.call(this, cX, cY, iR, nP, sF, 1, bH, 6, 0);
      this._globalFrameCount++; this._drawingActive = false; this.notify("success", "Recursive Star Polygon Nova finished.");
    }
  }

// --- Sub-Module 5: PatternDrawerTool ---
// Draws various patterns on Drawaria.online, rendering locally and globally.
class PatternDrawerTool extends QBit {
    _canvas = null;
    _ctx = null;
    _drawingActive = false;
    _socketStatus = 'disconnected';
    _executionLine = []; // Stores draw commands in game coordinates (0-100)

    _ui = {}; // UI elements references

    constructor() {
        super("Pattern Drawer", '<i class="fas fa-th-large"></i>'); // Unique name and icon
        this._onStartup();
    }

    _onStartup() {
        this._canvas = document.getElementById('canvas');
        if (this._canvas) {
            this._ctx = this._canvas.getContext('2d');
        } else {
            this.notify("error", "Canvas del juego no encontrado para Pattern Drawer.");
            return;
        }

        this._loadInterface();
        this._setupEventListeners();
        setInterval(() => { // Periodically check and update connection status
            this._updateConnectionStatus(getGameSocket() && getGameSocket().readyState === WebSocket.OPEN ? 'connected' : 'disconnected');
        }, 1000);
        this.notify("info", "Módulo 'Pattern Drawer' cargado.");
    }

    _loadInterface() {
        const container = domMake.Tree("div", { id: `${this.identifier}-container`, class: "module-section" });
        this.htmlElements.section.appendChild(container);

        // Connection Status
        container.appendChild(domMake.Tree("div", { class: "module-section-title" }, ["Estado de Conexión"]));
        const connectionStatusDiv = domMake.Tree("div", {}, [
            domMake.Tree("span", { id: `${this.identifier}-connectionStatus`, class: `module-status-indicator module-status-${this._socketStatus}` }),
            domMake.Tree("span", { id: `${this.identifier}-statusText` }, [this._socketStatus.charAt(0).toUpperCase() + this._socketStatus.slice(1)])
        ]);
        container.appendChild(connectionStatusDiv);

        // Pattern Selector
        container.appendChild(domMake.Tree("div", { class: "module-section-title" }, ["Seleccionar Patrón"]));
        this._ui.patternSelect = domMake.Tree("select", { id: `${this.identifier}-patternSelect`, class: "module-form-control" });
        const patterns = [
            "grid", "zigzag", "spiral", "waves", "circles", "diagonals",
            "star", "crosshatch", "triangles", "dots", "hexagons", "radiance",
            "checkerboard", "swirls", "lattice", "fractal", "arcs", "mosaic",
            "ripples", "vortex"
        ];
        patterns.forEach(p => {
            this._ui.patternSelect.appendChild(domMake.Tree("option", { value: p }, [p.charAt(0).toUpperCase() + p.slice(1)]));
        });
        container.appendChild(this._ui.patternSelect);

        // Drawing Controls Inputs
        container.appendChild(domMake.Tree("div", { class: "module-section-title" }, ["Configuración de Patrón"]));
        const controlsGroup = domMake.Tree("div", { class: "module-btn-group", style: "flex-wrap: wrap;" }); // Using btn-group for grid-like layout
        const addInput = (id, label, type, min, max, value, title) => {
            const input = domMake.Tree("input", { type, id: `${this.identifier}-${id}`, min, max, value, title, class: "module-form-control" });
            this._ui[id.replace(/-/g, '')] = input; // Store reference
            const wrapper = domMake.Tree("div", { style: "flex: 1 1 48%;" }); // Approx half width
            wrapper.appendAll(domMake.Tree("label", { for: `${this.identifier}-${id}` }, [label]), input);
            controlsGroup.appendChild(wrapper);
        };
        addInput("brushsize", "Grosor:", "number", "2", "20", "4", "Tamaño del Pincel");
        addInput("stepsize", "Paso Patrón:", "number", "5", "50", "10", "Tamaño del Paso del Patrón");
        addInput("offsetX", "Offset X:", "number", "-50", "150", "0", "Desplazamiento Horizontal");
        addInput("offsetY", "Offset Y:", "number", "-50", "150", "0", "Desplazamiento Vertical");
        container.appendChild(controlsGroup);

        // Action Buttons
        const actionButtonsDiv = domMake.Tree("div", { class: "module-btn-group" });
        this._ui.startButton = domMake.Button('<i class="fas fa-play-circle"></i> Iniciar');
        this._ui.stopButton = domMake.Button('<i class="fas fa-stop-circle"></i> Detener');
        this._ui.clearButton = domMake.Button('<i class="fas fa-eraser"></i> Limpiar Todo');
        actionButtonsDiv.appendAll(this._ui.startButton, this._ui.stopButton, this._ui.clearButton);
        container.appendChild(actionButtonsDiv);

        this._ui.statusLabel = domMake.Tree("div", { id: `${this.identifier}-status`, class: "worldgen-status" }, ["Estado: Listo."]);
        container.appendChild(this._ui.statusLabel);
    }

    _setupEventListeners() {
        this._ui.startButton.addEventListener('click', () => {
            const pattern = this._ui.patternSelect.value;
            const thickness = parseInt(this._ui.brushsize.value) || 4;
            const stepSize = parseInt(this._ui.stepsize.value) || 10;
            const offset = {
                x: parseInt(this._ui.offsetX.value) || 0,
                y: parseInt(this._ui.offsetY.value) || 0,
            };

            if (isNaN(thickness) || isNaN(stepSize) || isNaN(offset.x) || isNaN(offset.y)) {
                this.notify("warning", "Valores de entrada inválidos.");
                return;
            }

            const socket = getGameSocket();
            if (!socket || socket.readyState !== WebSocket.OPEN) {
                this.notify("error", "No conectado a Drawaria. Por favor, asegúrate de estar en una sala.");
                return;
            }

            this._ui.statusLabel.textContent = `Dibujando ${pattern}...`;
            this._drawPattern(pattern, thickness, stepSize, offset); // Populate executionLine
            this._executePatternDrawing(socket); // Start execution
        });

        this._ui.stopButton.addEventListener('click', () => {
            this._drawingActive = false;
            this._ui.statusLabel.textContent = 'Dibujo detenido por el usuario.';
        });

        this._ui.clearButton.addEventListener('click', async () => {
            const socket = getGameSocket();
            if (!socket || socket.readyState !== WebSocket.OPEN) {
                this.notify("error", "No conectado a Drawaria. Por favor, asegúrate de estar en una sala.");
                return;
            }

            this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height); // Clear local canvas immediately

            const clearThickness = 1000;
            const clearColor = '#FFFFFF';
            const steps = 5;

            for (let i = 0; i <= steps; i++) {
                this._sendAndRenderDrawCommand(socket, [0, (i / steps) * 100], [100, (i / steps) * 100], clearColor, clearThickness);
                await this._delay(20);
                this._sendAndRenderDrawCommand(socket, [(i / steps) * 100, 0], [(i / steps) * 100, 100], clearColor, clearThickness);
                await this._delay(20);
            }
            this._ui.statusLabel.textContent = 'Comandos de limpieza de lienzo enviados.';
        });
    }

    _updateConnectionStatus(status) {
        this._socketStatus = status;
        const statusIndicator = document.getElementById(`${this.identifier}-connectionStatus`);
        const statusText = document.getElementById(`${this.identifier}-statusText`);

        if (statusIndicator && statusText) {
            statusIndicator.className = `module-status-indicator module-status-${status}`;
            statusText.textContent = status.charAt(0).toUpperCase() + status.slice(1);
            // Enable/disable start button based on connection status
            if (this._ui.startButton) {
                this._ui.startButton.disabled = !(status === 'connected');
            }
        }
    }

    _delay(ms) {
        return new Promise(resolve => setTimeout(resolve, ms));
    }

    _getRainbowColor(step, totalSteps) {
        if (totalSteps <= 0) return 'hsl(0, 100%, 50%)';
        let hue = (step / totalSteps) * 360;
        return `hsl(${hue}, 100%, 50%)`;
    }

    _clamp(value, min, max) {
        return Math.max(min, Math.min(max, value));
    }

    _sendAndRenderDrawCommand(socket, start_game_coords, end_game_coords, color, thickness) {
        if (!socket || socket.readyState !== WebSocket.OPEN) {
            this.notify("warning", "WebSocket no conectado. No se puede enviar ni renderizar el comando de dibujo.");
            return false;
        }

        const x1_norm = (start_game_coords[0] / 100);
        const y1_norm = (start_game_coords[1] / 100);
        const x2_norm = (end_game_coords[0] / 100);
        const y2_norm = (end_game_coords[1] / 100);

        // Local rendering
        this._ctx.strokeStyle = color;
        this._ctx.lineWidth = thickness;
        this._ctx.lineCap = 'round';
        this._ctx.lineJoin = 'round';
        this._ctx.beginPath();
        this._ctx.moveTo(x1_norm * this._canvas.width, y1_norm * this._canvas.height);
        this._ctx.lineTo(x2_norm * this._canvas.width, y2_norm * this._canvas.height);
        this._ctx.stroke();

        const command = `42["drawcmd",0,[${x1_norm.toFixed(4)},${y1_norm.toFixed(4)},${x2_norm.toFixed(4)},${y2_norm.toFixed(4)},false,${0 - thickness},"${color}",0,0,{}]]`;
        socket.send(command);
        return true;
    }

    _drawPattern(pattern, thickness, stepSize, offset) {
        this._executionLine = [];
        const canvasWidth = 100, canvasHeight = 100; // Game coordinates 0-100
        let stepCount = 0;

        // Calculate total steps for rainbow coloring (simplified calculation for brevity)
        let totalSteps = 0;
        switch (pattern) {
            case 'grid': totalSteps = Math.ceil(canvasWidth / stepSize) * 2; break;
            case 'zigzag': totalSteps = Math.ceil(canvasWidth / stepSize); break;
            case 'spiral': totalSteps = Math.floor((50 / (stepSize / 2)) * (Math.PI * 2 / 0.3)); break;
            case 'waves': totalSteps = Math.ceil(canvasWidth / stepSize); break;
            case 'circles': totalSteps = Math.floor(50 / stepSize); break;
            case 'diagonals': totalSteps = Math.ceil((canvasWidth + canvasHeight) / stepSize); break;
            case 'star': totalSteps = 16; break;
            case 'crosshatch': totalSteps = Math.ceil((canvasWidth + canvasHeight) / stepSize) * 2; break;
            case 'triangles': totalSteps = Math.ceil(canvasWidth / stepSize) * 2; break;
            case 'dots': totalSteps = Math.ceil(canvasWidth / stepSize) * Math.ceil(canvasHeight / stepSize); break;
            case 'hexagons': totalSteps = Math.ceil(canvasWidth / (stepSize * 1.5)) * Math.ceil(canvasHeight / (stepSize * Math.sqrt(3))); break;
            case 'radiance': totalSteps = 24; break;
            case 'checkerboard': totalSteps = Math.ceil(canvasWidth / stepSize) * Math.ceil(canvasHeight / stepSize) * 2; break;
            case 'swirls': totalSteps = Math.floor((50 / (stepSize / 2)) * (Math.PI * 2 / 0.4)); break;
            case 'lattice': totalSteps = Math.ceil((canvasWidth + canvasHeight) / stepSize) * 2; break;
            case 'fractal': totalSteps = 31; break;
            case 'arcs': totalSteps = Math.ceil(canvasHeight / stepSize) * Math.floor(Math.PI / 0.2); break;
            case 'mosaic': totalSteps = Math.min(100, Math.floor((canvasWidth * canvasHeight) / (stepSize * stepSize))) * 2; break;
            case 'ripples': totalSteps = Math.floor(50 / stepSize) * Math.floor(Math.PI * 2 / 0.2); break;
            case 'vortex': totalSteps = Math.floor(Math.PI * 2 / 0.1); break;
            default: totalSteps = 1;
        }

        if (pattern === 'grid') {
            for (let x = 0; x <= canvasWidth; x += stepSize) {
                this._executionLine.push({
                    pos1: [this._clamp(x + offset.x, 0, canvasWidth), this._clamp(0 + offset.y, 0, canvasHeight)],
                    pos2: [this._clamp(x + offset.x, 0, canvasWidth), this._clamp(canvasHeight + offset.y, 0, canvasHeight)],
                    color: this._getRainbowColor(stepCount++, totalSteps),
                    thickness: thickness
                });
            }
            for (let y = 0; y <= canvasHeight; y += stepSize) {
                this._executionLine.push({
                    pos1: [this._clamp(0 + offset.x, 0, canvasWidth), this._clamp(y + offset.y, 0, canvasHeight)],
                    pos2: [this._clamp(canvasWidth + offset.x, 0, canvasWidth), this._clamp(y + offset.y, 0, canvasHeight)],
                    color: this._getRainbowColor(stepCount++, totalSteps),
                    thickness: thickness
                });
            }
        } else if (pattern === 'zigzag') {
            let x = 0, y = 0;
            while (x <= canvasWidth) {
                let nextX = Math.min(x + stepSize, canvasWidth);
                let nextY = (y === 0) ? canvasHeight : 0;
                this._executionLine.push({
                    pos1: [this._clamp(x + offset.x, 0, canvasWidth), this._clamp(y + offset.y, 0, canvasHeight)],
                    pos2: [this._clamp(nextX + offset.x, 0, canvasWidth), this._clamp(nextY + offset.y, 0, canvasHeight)],
                    color: this._getRainbowColor(stepCount++, totalSteps),
                    thickness: thickness
                });
                x = nextX;
                y = nextY;
                if (x === canvasWidth && y === 0) {
                    this._executionLine.push({
                        pos1: [this._clamp(canvasWidth + offset.x, 0, canvasWidth), this._clamp(0 + offset.y, 0, canvasHeight)],
                        pos2: [this._clamp(canvasWidth + offset.x, 0, canvasWidth), this._clamp(canvasHeight + offset.y, 0, canvasHeight)],
                        color: this._getRainbowColor(stepCount++, totalSteps),
                        thickness: thickness
                    });
                }
            }
        } else if (pattern === 'spiral') {
            let centerX = canvasWidth / 2 + offset.x;
            let centerY = canvasHeight / 2 + offset.y;
            let maxRadius = 50;
            let angleStep = 0.3;
            for (let r = 5; r <= maxRadius; r += stepSize / 2) {
                for (let a = 0; a < Math.PI * 2; a += angleStep) {
                    let x1 = centerX + r * Math.cos(a);
                    let y1 = centerY + r * Math.sin(a);
                    let x2 = centerX + r * Math.cos(a + angleStep);
                    let y2 = centerY + r * Math.sin(a + angleStep);
                    this._executionLine.push({
                        pos1: [this._clamp(x1, 0, canvasWidth), this._clamp(y1, 0, canvasHeight)],
                        pos2: [this._clamp(x2, 0, canvasWidth), this._clamp(y2, 0, canvasHeight)],
                        color: this._getRainbowColor(stepCount++, totalSteps),
                        thickness: thickness
                    });
                }
            }
        } else if (pattern === 'waves') {
            let amplitude = 15;
            let frequency = 0.05;
            for (let x = 0; x <= canvasWidth; x += stepSize) {
                let y1 = canvasHeight / 2 + amplitude * Math.sin(x * frequency) + offset.y;
                let y2 = canvasHeight / 2 + amplitude * Math.sin(Math.min(x + stepSize, canvasWidth) * frequency) + offset.y;
                this._executionLine.push({
                    pos1: [this._clamp(x + offset.x, 0, canvasWidth), this._clamp(y1, 0, canvasHeight)],
                    pos2: [this._clamp(Math.min(x + stepSize, canvasWidth) + offset.x, 0, canvasWidth), this._clamp(y2, 0, canvasHeight)],
                    color: this._getRainbowColor(stepCount++, totalSteps),
                    thickness: thickness
                });
            }
        } else if (pattern === 'circles') {
            let centerX = canvasWidth / 2 + offset.x;
            let centerY = canvasHeight / 2 + offset.y;
            let maxRadius = 50;
            for (let r = stepSize; r <= maxRadius; r += stepSize) {
                for (let a = 0; a < Math.PI * 2; a += 0.2) {
                    let x1 = centerX + r * Math.cos(a);
                    let y1 = centerY + r * Math.sin(a);
                    let x2 = centerX + r * Math.cos(a + 0.2);
                    let y2 = centerY + r * Math.sin(a + 0.2);
                    this._executionLine.push({
                        pos1: [this._clamp(x1, 0, canvasWidth), this._clamp(y1, 0, canvasHeight)],
                        pos2: [this._clamp(x2, 0, canvasWidth), this._clamp(y2, 0, canvasHeight)],
                        color: this._getRainbowColor(stepCount++, totalSteps),
                        thickness: thickness
                    });
                }
            }
        } else if (pattern === 'diagonals') {
            for (let i = -(canvasHeight); i <= canvasWidth; i += stepSize) {
                let x1 = i, y1 = 0;
                let x2 = i + canvasHeight, y2 = canvasHeight;

                let startX = this._clamp(x1 + offset.x, 0, canvasWidth);
                let startY = this._clamp(y1 + offset.y, 0, canvasHeight);
                let endX = this._clamp(x2 + offset.x, 0, canvasWidth);
                let endY = this._clamp(y2 + offset.y, 0, canvasHeight);

                if (x1 < 0 && startY > 0) startX = (startY - offset.y) + offset.x;
                if (x2 > canvasWidth && endY < canvasHeight) endX = (endY - offset.y) + offset.x;

                this._executionLine.push({
                    pos1: [startX, startY],
                    pos2: [endX, endY],
                    color: this._getRainbowColor(stepCount++, totalSteps),
                    thickness: thickness
                });
            }
        } else if (pattern === 'star') {
            let centerX = canvasWidth / 2 + offset.x;
            let centerY = canvasHeight / 2 + offset.y;
            let radius = 50;
            for (let a = 0; a < Math.PI * 2; a += Math.PI / 8) {
                let x = centerX + radius * Math.cos(a);
                let y = centerY + radius * Math.sin(a);
                this._executionLine.push({
                    pos1: [this._clamp(centerX, 0, canvasWidth), this._clamp(centerY, 0, canvasHeight)],
                    pos2: [this._clamp(x, 0, canvasWidth), this._clamp(y, 0, canvasHeight)],
                    color: this._getRainbowColor(stepCount++, totalSteps),
                    thickness: thickness
                });
            }
        } else if (pattern === 'crosshatch') {
            for (let i = -canvasHeight; i <= canvasWidth; i += stepSize) {
                let x1 = i, y1 = 0;
                let x2 = i + canvasHeight, y2 = canvasHeight;

                let startX = this._clamp(x1 + offset.x, 0, canvasWidth);
                let startY = this._clamp(y1 + offset.y, 0, canvasHeight);
                let endX = this._clamp(x2 + offset.x, 0, canvasWidth);
                let endY = this._clamp(y2 + offset.y, 0, canvasHeight);

                if (x1 < 0 && startY > 0) startX = (startY - offset.y) + offset.x;
                if (x2 > canvasWidth && endY < canvasHeight) endX = (endY - offset.y) + offset.x;

                this._executionLine.push({
                    pos1: [startX, startY],
                    pos2: [endX, endY],
                    color: this._getRainbowColor(stepCount++, totalSteps),
                    thickness: thickness
                });
            }
            for (let i = 0; i <= canvasWidth + canvasHeight; i += stepSize) {
                let x1 = canvasWidth, y1 = i;
                let x2 = 0, y2 = i - canvasWidth;

                let startX = this._clamp(x1 + offset.x, 0, canvasWidth);
                let startY = this._clamp(y1 + offset.y, 0, canvasHeight);
                let endX = this._clamp(x2 + offset.x, 0, canvasWidth);
                let endY = this._clamp(y2 + offset.y, 0, canvasHeight);

                if (y1 > canvasHeight && startX < canvasWidth) startX = canvasWidth - (startY - offset.y - canvasHeight);
                if (y2 < 0 && endX > 0) endX = (endY - offset.y);

                this._executionLine.push({
                    pos1: [startX, startY],
                    pos2: [endX, endY],
                    color: this._getRainbowColor(stepCount++, totalSteps),
                    thickness: thickness
                });
            }
        } else if (pattern === 'triangles') {
            for (let y = 0; y <= canvasHeight; y += stepSize * Math.sqrt(3)) {
                for (let x = 0; x <= canvasWidth; x += stepSize * 2) {
                    let p1_up = [x, y + stepSize * Math.sqrt(3)];
                    let p2_up = [x + stepSize, y];
                    let p3_up = [x + stepSize * 2, y + stepSize * Math.sqrt(3)];
                    this._executionLine.push({ pos1: [this._clamp(p1_up[0] + offset.x, 0, canvasWidth), this._clamp(p1_up[1] + offset.y, 0, canvasHeight)], pos2: [this._clamp(p2_up[0] + offset.x, 0, canvasWidth), this._clamp(p2_up[1] + offset.y, 0, canvasHeight)], color: this._getRainbowColor(stepCount++, totalSteps), thickness: thickness });
                    this._executionLine.push({ pos1: [this._clamp(p2_up[0] + offset.x, 0, canvasWidth), this._clamp(p2_up[1] + offset.y, 0, canvasHeight)], pos2: [this._clamp(p3_up[0] + offset.x, 0, canvasWidth), this._clamp(p3_up[1] + offset.y, 0, canvasHeight)], color: this._getRainbowColor(stepCount++, totalSteps), thickness: thickness });
                    this._executionLine.push({ pos1: [this._clamp(p3_up[0] + offset.x, 0, canvasWidth), this._clamp(p3_up[1] + offset.y, 0, canvasHeight)], pos2: [this._clamp(p1_up[0] + offset.x, 0, canvasWidth), this._clamp(p1_up[1] + offset.y, 0, canvasHeight)], color: this._getRainbowColor(stepCount++, totalSteps), thickness: thickness });

                    let p1_down = [x + stepSize, y + stepSize * Math.sqrt(3)];
                    let p2_down = [x + stepSize * 1.5, y + stepSize * Math.sqrt(3) * 1.5];
                    let p3_down = [x + stepSize * 0.5, y + stepSize * Math.sqrt(3) * 1.5];
                    this._executionLine.push({ pos1: [this._clamp(p1_down[0] + offset.x, 0, canvasWidth), this._clamp(p1_down[1] + offset.y, 0, canvasHeight)], pos2: [this._clamp(p2_down[0] + offset.x, 0, canvasWidth), this._clamp(p2_down[1] + offset.y, 0, canvasHeight)], color: this._getRainbowColor(stepCount++, totalSteps), thickness: thickness });
                    this._executionLine.push({ pos1: [this._clamp(p2_down[0] + offset.x, 0, canvasWidth), this._clamp(p2_down[1] + offset.y, 0, canvasHeight)], pos2: [this._clamp(p3_down[0] + offset.x, 0, canvasWidth), this._clamp(p3_down[1] + offset.y, 0, canvasHeight)], color: this._getRainbowColor(stepCount++, totalSteps), thickness: thickness });
                    this._executionLine.push({ pos1: [this._clamp(p3_down[0] + offset.x, 0, canvasWidth), this._clamp(p3_down[1] + offset.y, 0, canvasHeight)], pos2: [this._clamp(p1_down[0] + offset.x, 0, canvasWidth), this._clamp(p1_down[1] + offset.y, 0, canvasHeight)], color: this._getRainbowColor(stepCount++, totalSteps), thickness: thickness });
                }
            }
        } else if (pattern === 'dots') {
            for (let x = 0; x <= canvasWidth; x += stepSize) {
                for (let y = 0; y <= canvasHeight; y += stepSize) {
                    this._executionLine.push({
                        pos1: [this._clamp(x + offset.x, 0, canvasWidth), this._clamp(y + offset.y, 0, canvasHeight)],
                        pos2: [this._clamp(x + offset.x + 0.1, 0, canvasWidth), this._clamp(y + offset.y + 0.1, 0, canvasHeight)],
                        color: this._getRainbowColor(stepCount++, totalSteps),
                        thickness: thickness
                    });
                }
            }
        } else if (pattern === 'hexagons') {
            let hexSize = stepSize;
            let hexWidth = hexSize * 1.5;
            let hexHeight = hexSize * Math.sqrt(3);
            for (let y = 0; y < canvasHeight + hexHeight; y += hexHeight) {
                for (let x = 0; x < canvasWidth + hexWidth; x += hexWidth) {
                    let offsetX_row = (Math.floor(y / hexHeight) % 2 === 0) ? 0 : hexWidth / 2;
                    let points = [
                        [x + offsetX_row, y + hexSize / 2],
                        [x + offsetX_row + hexSize / 2, y],
                        [x + offsetX_row + hexSize * 1.5, y],
                        [x + offsetX_row + hexSize * 2, y + hexSize / 2],
                        [x + offsetX_row + hexSize * 1.5, y + hexSize * 1.5],
                        [x + offsetX_row + hexSize / 2, y + hexSize * 1.5]
                    ];
                    for (let i = 0; i < 6; i++) {
                        let p1 = points[i];
                        let p2 = points[(i + 1) % 6];
                        this._executionLine.push({
                            pos1: [this._clamp(p1[0] + offset.x, 0, canvasWidth), this._clamp(p1[1] + offset.y, 0, canvasHeight)],
                            pos2: [this._clamp(p2[0] + offset.x, 0, canvasWidth), this._clamp(p2[1] + offset.y, 0, canvasHeight)],
                            color: this._getRainbowColor(stepCount++, totalSteps),
                            thickness: thickness
                        });
                    }
                }
            }
        } else if (pattern === 'radiance') {
            let centerX = canvasWidth / 2 + offset.x;
            let centerY = canvasHeight / 2 + offset.y;
            let radius = Math.min(centerX, centerY, canvasWidth - centerX, canvasHeight - centerY);
            let numLines = 24;
            for (let i = 0; i < numLines; i++) {
                let angle = (i / numLines) * Math.PI * 2;
                let endX = centerX + radius * Math.cos(angle);
                let endY = centerY + radius * Math.sin(angle);
                this._executionLine.push({
                    pos1: [this._clamp(centerX, 0, canvasWidth), this._clamp(centerY, 0, canvasHeight)],
                    pos2: [this._clamp(endX, 0, canvasWidth), this._clamp(endY, 0, canvasHeight)],
                    color: this._getRainbowColor(stepCount++, totalSteps),
                    thickness: thickness
                });
            }
        } else if (pattern === 'checkerboard') {
            for (let y = 0; y < canvasHeight; y += stepSize) {
                for (let x = 0; x < canvasWidth; x += stepSize) {
                    if ((Math.floor(x / stepSize) + Math.floor(y / stepSize)) % 2 === 0) {
                        this._executionLine.push({ pos1: [this._clamp(x + offset.x, 0, canvasWidth), this._clamp(y + offset.y, 0, canvasHeight)], pos2: [this._clamp(x + stepSize + offset.x, 0, canvasWidth), this._clamp(y + offset.y, 0, canvasHeight)], color: this._getRainbowColor(stepCount++, totalSteps), thickness: thickness });
                        this._executionLine.push({ pos1: [this._clamp(x + stepSize + offset.x, 0, canvasWidth), this._clamp(y + offset.y, 0, canvasHeight)], pos2: [this._clamp(x + stepSize + offset.x, 0, canvasWidth), this._clamp(y + stepSize + offset.y, 0, canvasHeight)], color: this._getRainbowColor(stepCount++, totalSteps), thickness: thickness });
                        this._executionLine.push({ pos1: [this._clamp(x + stepSize + offset.x, 0, canvasWidth), this._clamp(y + stepSize + offset.y, 0, canvasHeight)], pos2: [this._clamp(x + offset.x, 0, canvasWidth), this._clamp(y + stepSize + offset.y, 0, canvasHeight)], color: this._getRainbowColor(stepCount++, totalSteps), thickness: thickness });
                        this._executionLine.push({ pos1: [this._clamp(x + offset.x, 0, canvasWidth), this._clamp(y + stepSize + offset.y, 0, canvasHeight)], pos2: [this._clamp(x + offset.x, 0, canvasWidth), this._clamp(y + offset.y, 0, canvasHeight)], color: this._getRainbowColor(stepCount++, totalSteps), thickness: thickness });
                    }
                }
            }
        } else if (pattern === 'swirls') {
            let centerX = canvasWidth / 2 + offset.x;
            let centerY = canvasHeight / 2 + offset.y;
            let maxRadius = 50;
            let angleStep = 0.4;
            for (let r = 5; r <= maxRadius; r += stepSize / 2) {
                for (let a = 0; a < Math.PI * 2; a += angleStep) {
                    let swirl = 5 * Math.sin(r * 0.1);
                    let x1 = centerX + (r + swirl) * Math.cos(a);
                    let y1 = centerY + (r + swirl) * Math.sin(a);
                    let x2 = centerX + (r + swirl) * Math.cos(a + angleStep);
                    let y2 = centerY + (r + swirl) * Math.sin(a + angleStep);
                    this._executionLine.push({
                        pos1: [this._clamp(x1, 0, canvasWidth), this._clamp(y1, 0, canvasHeight)],
                        pos2: [this._clamp(x2, 0, canvasWidth), this._clamp(y2, 0, canvasHeight)],
                        color: this._getRainbowColor(stepCount++, totalSteps),
                        thickness: thickness
                    });
                }
            }
        } else if (pattern === 'lattice') {
            for (let y = 0; y <= canvasHeight; y += stepSize) {
                this._executionLine.push({
                    pos1: [this._clamp(0 + offset.x, 0, canvasWidth), this._clamp(y + offset.y, 0, canvasHeight)],
                    pos2: [this._clamp(canvasWidth + offset.x, 0, canvasWidth), this._clamp(y + offset.y, 0, canvasHeight)],
                    color: this._getRainbowColor(stepCount++, totalSteps),
                    thickness: thickness
                });
            }
            for (let x = 0; x <= canvasWidth; x += stepSize) {
                this._executionLine.push({
                    pos1: [this._clamp(x + offset.x, 0, canvasWidth), this._clamp(0 + offset.y, 0, canvasHeight)],
                    pos2: [this._clamp(x + offset.x, 0, canvasWidth), this._clamp(canvasHeight + offset.y, 0, canvasHeight)],
                    color: this._getRainbowColor(stepCount++, totalSteps),
                    thickness: thickness
                });
            }
        } else if (pattern === 'fractal') {
            const drawBranch = (x, y, length, angle, depth) => {
                if (depth === 0) return;
                let x2 = x + length * Math.cos(angle);
                let y2 = y - length * Math.sin(angle);
                this._executionLine.push({
                    pos1: [this._clamp(x + offset.x, 0, canvasWidth), this._clamp(y + offset.y, 0, canvasHeight)],
                    pos2: [this._clamp(x2 + offset.x, 0, canvasWidth), this._clamp(y2 + offset.y, 0, canvasHeight)],
                    color: this._getRainbowColor(stepCount++, totalSteps),
                    thickness: thickness
                });
                drawBranch(x2, y2, length * 0.7, angle + Math.PI / 4, depth - 1);
                drawBranch(x2, y2, length * 0.7, angle - Math.PI / 4, depth - 1);
            };
            drawBranch(canvasWidth / 2, canvasHeight, stepSize * 2, Math.PI / 2, 4);
        } else if (pattern === 'arcs') {
            let numArcs = Math.floor(canvasHeight / stepSize);
            for (let i = 0; i < numArcs; i++) {
                let y = i * stepSize + offset.y;
                let centerX = canvasWidth / 2 + offset.x;
                let radius = canvasWidth / 2;
                for (let a = 0; a <= Math.PI; a += 0.1) {
                    let x1 = centerX + radius * Math.cos(a);
                    let y1 = y + radius * Math.sin(a);
                    let x2 = centerX + radius * Math.cos(a + 0.1);
                    let y2 = y + radius * Math.sin(a + 0.1);
                    this._executionLine.push({
                        pos1: [this._clamp(x1, 0, canvasWidth), this._clamp(y1, 0, canvasHeight)],
                        pos2: [this._clamp(x2, 0, canvasWidth), this._clamp(y2, 0, canvasHeight)],
                        color: this._getRainbowColor(stepCount++, totalSteps),
                        thickness: thickness
                    });
                }
            }
        } else if (pattern === 'mosaic') {
            let squareSize = stepSize;
            for (let y = 0; y < canvasHeight; y += squareSize) {
                for (let x = 0; x < canvasWidth; x += squareSize) {
                    let startX_inner = x + Math.random() * squareSize;
                    let startY_inner = y + Math.random() * squareSize;
                    let endX_inner = x + Math.random() * squareSize;
                    let endY_inner = y + Math.random() * squareSize;
                    this._executionLine.push({
                        pos1: [this._clamp(startX_inner + offset.x, 0, canvasWidth), this._clamp(startY_inner + offset.y, 0, canvasHeight)],
                        pos2: [this._clamp(endX_inner + offset.x, 0, canvasWidth), this._clamp(endY_inner + offset.y, 0, canvasHeight)],
                        color: this._getRainbowColor(stepCount++, totalSteps),
                        thickness: thickness
                    });
                }
            }
        } else if (pattern === 'ripples') {
            let centerX = canvasWidth / 2 + offset.x;
            let centerY = canvasHeight / 2 + offset.y;
            let maxRadius = 50;
            let rippleAmplitude = 5;
            for (let r = stepSize; r <= maxRadius; r += stepSize) {
                for (let a = 0; a < Math.PI * 2; a += 0.2) {
                    let currentRadius = r + rippleAmplitude * Math.sin(a * 5);
                    let x1 = centerX + currentRadius * Math.cos(a);
                    let y1 = centerY + currentRadius * Math.sin(a);
                    let x2 = centerX + currentRadius * Math.cos(a + 0.2);
                    let y2 = centerY + currentRadius * Math.sin(a + 0.2);
                    this._executionLine.push({
                        pos1: [this._clamp(x1, 0, canvasWidth), this._clamp(y1, 0, canvasHeight)],
                        pos2: [this._clamp(x2, 0, canvasWidth), this._clamp(y2, 0, canvasHeight)],
                        color: this._getRainbowColor(stepCount++, totalSteps),
                        thickness: thickness
                    });
                }
            }
        } else if (pattern === 'vortex') {
            let centerX = canvasWidth / 2 + offset.x;
            let centerY = canvasHeight / 2 + offset.y;
            let maxRadius = 50;
            let angleStep = 0.1;
            let spiralTightness = 0.05;
            for (let i = 0; i < maxRadius / (stepSize / 4); i++) {
                let r = maxRadius - (i * stepSize / 4);
                if (r < 0) r = 0;
                let currentAngle = i * angleStep + spiralTightness * i;
                let x1 = centerX + r * Math.cos(currentAngle);
                let y1 = centerY + r * Math.sin(currentAngle);
                let x2 = centerX + (r - stepSize / 4) * Math.cos(currentAngle + angleStep + spiralTightness);
                let y2 = centerY + (r - stepSize / 4) * Math.sin(currentAngle + angleStep + spiralTightness);
                this._executionLine.push({
                    pos1: [this._clamp(x1, 0, canvasWidth), this._clamp(y1, 0, canvasHeight)],
                    pos2: [this._clamp(x2, 0, canvasWidth), this._clamp(y2, 0, canvasHeight)],
                    color: this._getRainbowColor(stepCount++, totalSteps),
                    thickness: thickness
                });
            }
        }
    }

    async _executePatternDrawing(socket) {
        if (!socket || socket.readyState !== WebSocket.OPEN) {
            this.notify("error", "Error: Not connected to Drawaria room.");
            return;
        }

        this._drawingActive = true;
        for (let i = 0; i < this._executionLine.length; i++) {
            if (!this._drawingActive) {
                this.notify("info", 'Dibujo detenido.');
                return;
            }
            let currentLine = this._executionLine[i];
            let p1 = currentLine.pos1,
                p2 = currentLine.pos2,
                color = currentLine.color,
                thickness = currentLine.thickness;

            this._sendAndRenderDrawCommand(socket, p1, p2, color, thickness);
            await this._delay(50);
        }
        this._drawingActive = false;
        this.notify("success", 'Dibujo completado.');
    }
}

// --- Sub-Module 6: ExpansiveShapesToolNuevo (Botless & Local Render) ---
// Note: This is the NEW/Updated version. Keeping original class name to avoid confusion.
class ExpansiveShapesToolNuevo extends QBit {
    _canvas = null;
    _ctx = null;
    _drawingActive = false;
    _socketStatus = 'disconnected';

    _currentAnimationInterval = null;
    _shapeColor = '#000000';
    _animationRadiusOrSize = 0;

    _animationThickness = 5; // Thickness of lines for animations (NERFED: was 10)
    _animationIncrement = 0.01; // How much the shape grows per interval
    _animationIntervalMs = 120; // Time between animation steps
    _colorChangeIntervalMs = 100; // Interval for multicolor mode

    _multicolorInterval = null;
    _isMulticolorEnabled = false;

    _ui = {}; // Object to hold references to UI elements

    constructor() {
        super("Expansive Shapes Tool (Nuevo)", '<i class="fas fa-brush"></i>'); // Unique name and icon
        this._onStartup();
    }

    _onStartup() {
        this._canvas = document.getElementById('canvas');
        if (this._canvas) {
            this._ctx = this._canvas.getContext('2d');
        } else {
            this.notify("error", "Canvas del juego no encontrado para Expansive Shapes Tool Nuevo.");
            return;
        }

        this._loadInterface();
        this._setupEventListeners();
        setInterval(() => { // Periodically check and update connection status
            this._updateConnectionStatus(getGameSocket() && getGameSocket().readyState === WebSocket.OPEN ? 'connected' : 'disconnected');
        }, 1000);
        this.notify("info", "Módulo 'Expansive Shapes Tool Nuevo' cargado.");
    }

    _loadInterface() {
        const container = domMake.Tree("div", { id: `${this.identifier}-container`, class: "module-section" });
        this.htmlElements.section.appendChild(container);

        // Connection Status
        container.appendChild(domMake.Tree("div", { class: "module-section-title" }, ["Estado de Conexión"]));
        const connectionStatusDiv = domMake.Tree("div", {}, [
            domMake.Tree("span", { id: `${this.identifier}-connectionStatus`, class: `module-status-indicator module-status-${this._socketStatus}` }),
            domMake.Tree("span", { id: `${this.identifier}-statusText` }, [this._socketStatus.charAt(0).toUpperCase() + this._socketStatus.slice(1)])
        ]);
        container.appendChild(connectionStatusDiv);

        // Shapes Grid for starting animations
        container.appendChild(domMake.Tree("div", { class: "module-section-title" }, ["Formas"]));
        const shapesGrid = domMake.Tree("div", { class: "module-btn-group", style: "flex-wrap: wrap;" });
        const shapes = [
            { id: "startCircleButton", text: "Círculo", drawFn: this._drawCircle },
            { id: "startLinesButton", text: "Líneas", drawFn: this._drawLines },
            { id: "startTriangleButton", text: "Triángulo", drawFn: this._drawTriangle },
            { id: "startSquareButton", text: "Cuadrado", drawFn: this._drawSquare },
            { id: "startDiamondButton", text: "Rombo", drawFn: this._drawDiamond },
            { id: "startShieldButton", text: "Escudo", drawFn: this._drawShield },
            { id: "startSpiderEffectButton", text: "Efecto Araña", drawFn: this._drawSpiderEffect },
            { id: "start4LinesButton", text: "4 Líneas", drawFn: this._draw4Lines },
            { id: "start6LinesButton", text: "6 Líneas", drawFn: this._draw6Lines }
        ];
        shapes.forEach(shape => {
            const button = domMake.Button(shape.text);
            button.addEventListener('click', () => this._startAnimation(shape.drawFn.bind(this)));
            shapesGrid.appendChild(button);
            this._ui[shape.id] = button;
        });
        container.appendChild(shapesGrid);

        // Color Control section
        container.appendChild(domMake.Tree("div", { class: "module-section-title" }, ["Control de Color"]));
        this._ui.colorInput = domMake.Tree("input", { type: "color", id: `${this.identifier}-colorInput`, class: "module-form-control", value: this._shapeColor });
        container.appendChild(domMake.Tree("div", { class: "module-form-group" }, [domMake.Tree("label", { for: `${this.identifier}-colorInput` }, ["Color Sólido"]), this._ui.colorInput]));

        this._ui.startMulticolorButton = domMake.Button('Multicolor');
        this._ui.stopAllButton = domMake.Button('Detener Todo y Limpiar');

        container.appendChild(domMake.Tree("div", { class: "module-btn-group" }, [this._ui.startMulticolorButton, this._ui.stopAllButton]));
    }

    _setupEventListeners() {
        this._ui.colorInput.addEventListener('input', (e) => {
            this._shapeColor = e.target.value;
            this._stopMulticolorAnimation();
        });
        this._ui.startMulticolorButton.addEventListener('click', () => this._startMulticolorAnimation());
        this._ui.stopAllButton.addEventListener('click', () => {
            this._stopAllAnimations();
            this._clearCanvasLocallyAndRemotely();
        });
    }

    _updateConnectionStatus(status) {
        this._socketStatus = status;
        const statusIndicator = document.getElementById(`${this.identifier}-connectionStatus`);
        const statusText = document.getElementById(`${this.identifier}-statusText`);
        if (statusIndicator && statusText) {
            statusIndicator.className = `module-status-indicator module-status-${status}`;
            statusText.textContent = status.charAt(0).toUpperCase() + status.slice(1);
        }
        // Enable/disable shape buttons based on connection status
        const isConnected = status === 'connected';
        const shapeButtons = [
            this._ui.startCircleButton, this._ui.startLinesButton, this._ui.startTriangleButton,
            this._ui.startSquareButton, this._ui.startDiamondButton, this._ui.startShieldButton,
            this._ui.startSpiderEffectButton, this._ui.start4LinesButton, this._ui.start6LinesButton,
            this._ui.startMulticolorButton, this._ui.stopAllButton
        ];
        shapeButtons.forEach(btn => {
            if (btn) btn.disabled = !isConnected;
        });
    }

    _delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
    _getRandomColor() {
        const letters = '0123456789ABCDEF';
        let color = '#';
        for (let i = 0; i < 6; i++) {
            color += letters[Math.floor(Math.random() * 16)];
        }
        return color;
    }

    _drawLineAndSendCommand(startX_norm, startY_norm, endX_norm, endY_norm, thickness, color) {
        if (!this._canvas || !this._ctx) {
            this.notify("error", "Canvas or context not available for local drawing.");
            return;
        }

        const socket = getGameSocket();
        if (!socket || socket.readyState !== WebSocket.OPEN) {
            this._stopAllAnimations();
            this.notify("warning", "No hay conexión al juego. Animaciones detenidas.");
            return;
        }

        // Draw locally
        this._ctx.strokeStyle = color;
        this._ctx.lineWidth = thickness;
        this._ctx.lineCap = 'round';
        this._ctx.beginPath();
        this._ctx.moveTo(startX_norm * this._canvas.width, startY_norm * this._canvas.height);
        this._ctx.lineTo(endX_norm * this._canvas.width, endY_norm * this._canvas.height);
        this._ctx.stroke();

        // Send to server
        socket.send(`42["drawcmd",0,[${startX_norm.toFixed(4)},${startY_norm.toFixed(4)},${endX_norm.toFixed(4)},${endY_norm.toFixed(4)},false,${0 - thickness},"${color}",0,0,{}]]`);
    }

    _stopAllAnimations() {
        if (this._currentAnimationInterval) clearInterval(this._currentAnimationInterval);
        if (this._multicolorInterval) clearInterval(this._multicolorInterval);
        this._currentAnimationInterval = null;
        this._multicolorInterval = null;
        this._drawingActive = false;
        this._animationRadiusOrSize = 0;
        this._isMulticolorEnabled = false;
        if (this._ui.colorInput) this._ui.colorInput.value = '#000000';
        this._shapeColor = '#000000';
        this.notify("info", "Todas las animaciones detenidas.");
    }

    async _clearCanvasLocallyAndRemotely() {
        if (this._ctx) this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
        const socket = getGameSocket();
        if (!socket) { this.notify("warning", "No hay conexión. No se puede limpiar el lienzo."); return; }
        this.notify("info", "Enviando comandos de limpieza...");

        const clearThickness = 1000;
        const clearColor = '#FFFFFF';
        const steps = 5;

        for (let i = 0; i <= steps; i++) {
            this._drawLineAndSendCommand(0.01, i / 5, 0.99, i / 5, clearThickness, clearColor); await this._delay(5);
            this._drawLineAndSendCommand(i / 5, 0.01, i / 5, 0.99, clearThickness, clearColor); await this._delay(5);
        }
        this.notify("success", "Comandos de limpieza de lienzo enviados.");
    }

    _startAnimation(drawFunction) {
        this._stopAllAnimations();
        this._drawingActive = true;
        this._animationRadiusOrSize = 0;
        this.notify("info", "Iniciando animación...");
        this._currentAnimationInterval = setInterval(() => {
            if (!this._drawingActive) { this._stopAllAnimations(); return; }
            this._animationRadiusOrSize += this._animationIncrement;
            if (this._animationRadiusOrSize > 0.5) { this._stopAllAnimations(); return; }
            drawFunction(0.5, 0.5, this._animationRadiusOrSize, this._shapeColor);
        }, this._animationIntervalMs);
    }

    // Shape drawing functions
    _drawCircle(x, y, radius, color) {
        const angleIncrement = 10;
        for (let i = 0; i < 360; i += angleIncrement) {
            const angle1 = i * (Math.PI / 180);
            const angle2 = (i + angleIncrement) * (Math.PI / 180);
            const startX = x + radius * Math.cos(angle1);
            const startY = y + radius * Math.sin(angle1);
            const endX = x + radius * Math.cos(angle2);
            const endY = y + radius * Math.sin(angle2);
            this._drawLineAndSendCommand(startX, startY, endX, endY, this._animationThickness, color);
        }
    }

    _drawLines(x, y, radius, color) {
        const angleIncrement = 10;
        for (let i = 0; i < 360; i += angleIncrement) {
            const angle = i * (Math.PI / 180);
            const endX = x + radius * Math.cos(angle);
            const endY = y + radius * Math.sin(angle);
            this._drawLineAndSendCommand(x, y, endX, endY, this._animationThickness, color);
        }
    }

    _drawTriangle(x, y, size, color) {
        const points = [
            { x: x, y: y - size },
            { x: x - size, y: y + size },
            { x: x + size, y: y + size }
        ];
        this._drawLineAndSendCommand(points[0].x, points[0].y, points[1].x, points[1].y, this._animationThickness, color);
        this._drawLineAndSendCommand(points[1].x, points[1].y, points[2].x, points[2].y, this._animationThickness, color);
        this._drawLineAndSendCommand(points[2].x, points[2].y, points[0].x, points[0].y, this._animationThickness, color);
    }

    _drawSquare(x, y, size, color) {
        const halfSize = size / 2;
        const points = [
            { x: x - halfSize, y: y - halfSize },
            { x: x + halfSize, y: y - halfSize },
            { x: x + halfSize, y: y + halfSize },
            { x: x - halfSize, y: y + halfSize }
        ];
        this._drawLineAndSendCommand(points[0].x, points[0].y, points[1].x, points[1].y, this._animationThickness, color);
        this._drawLineAndSendCommand(points[1].x, points[1].y, points[2].x, points[2].y, this._animationThickness, color);
        this._drawLineAndSendCommand(points[2].x, points[2].y, points[3].x, points[3].y, this._animationThickness, color);
        this._drawLineAndSendCommand(points[3].x, points[3].y, points[0].x, points[0].y, this._animationThickness, color);
    }

    _drawDiamond(x, y, size, color) {
        const points = [
            { x: x, y: y - size },
            { x: x + size, y: y },
            { x: x, y: y + size },
            { x: x - size, y: y }
        ];
        this._drawLineAndSendCommand(points[0].x, points[0].y, points[1].x, points[1].y, this._animationThickness, color);
        this._drawLineAndSendCommand(points[1].x, points[1].y, points[2].x, points[2].y, this._animationThickness, color);
        this._drawLineAndSendCommand(points[2].x, points[2].y, points[3].x, points[3].y, this._animationThickness, color);
        this._drawLineAndSendCommand(points[3].x, points[3].y, points[0].x, points[0].y, this._animationThickness, color);
    }

    _drawShield(x, y, size, color) {
        const points = [
            { x: x, y: y - size * 0.8 },
            { x: x + size * 0.6, y: y - size * 0.8 },
            { x: x + size, y: y },
            { x: x, y: y + size * 1.2 },
            { x: x - size, y: y },
            { x: x - size * 0.6, y: y - size * 0.8 }
        ];
        this._drawLineAndSendCommand(points[0].x, points[0].y, points[1].x, points[1].y, this._animationThickness, color);
        this._drawLineAndSendCommand(points[1].x, points[1].y, points[2].x, points[2].y, this._animationThickness, color);
        this._drawLineAndSendCommand(points[2].x, points[2].y, points[3].x, points[3].y, this._animationThickness, color);
        this._drawLineAndSendCommand(points[3].x, points[3].y, points[4].x, points[4].y, this._animationThickness, color);
        this._drawLineAndSendCommand(points[4].x, points[4].y, points[5].x, points[5].y, this._animationThickness, color);
        this._drawLineAndSendCommand(points[5].x, points[5].y, points[0].x, points[0].y, this._animationThickness, color);
    }

    _drawSpiderEffect(x, y, size, color) {
        const outerRadius = size;
        const innerRadius = size * 0.8;
        const innerCircleOffsetX = x + size * 0.2;
        const innerCircleOffsetY = y - size * 0.2;

        for (let i = 0; i < 360; i += 30) {
            const angle1 = i * (Math.PI / 180);
            const angle2 = (i + 30) * (Math.PI / 180);
            const startX = x + outerRadius * Math.cos(angle1);
            const startY = y + outerRadius * Math.sin(angle1);
            const endX = x + outerRadius * Math.cos(angle2);
            const endY = y + outerRadius * Math.sin(angle2);
            this._drawLineAndSendCommand(startX, startY, endX, endY, this._animationThickness, color);
        }

        for (let i = 0; i < 360; i += 30) {
            const angle1 = i * (Math.PI / 180);
            const angle2 = (i + 30) * (Math.PI / 180);
            const startX = innerCircleOffsetX + innerRadius * Math.cos(angle1);
            const startY = innerCircleOffsetY + innerRadius * Math.sin(angle1);
            const endX = innerCircleOffsetX + innerRadius * Math.cos(angle2);
            const endY = innerCircleOffsetY + innerRadius * Math.sin(angle2);
            this._drawLineAndSendCommand(startX, startY, endX, endY, this._animationThickness, '#FFFFFF');
        }
    }

    _draw4Lines(x, y, size, color) {
        this._drawLineAndSendCommand(x, y, x + size, y, this._animationThickness, color);
        this._drawLineAndSendCommand(x, y, x - size, y, this._animationThickness, color);
        this._drawLineAndSendCommand(x, y, x, y + size, this._animationThickness, color);
        this._drawLineAndSendCommand(x, y, x, y - size, this._animationThickness, color);
    }

    _draw6Lines(x, y, size, color) {
        const angles = [0, Math.PI / 3, 2 * Math.PI / 3, Math.PI, 4 * Math.PI / 3, 5 * Math.PI / 3];
        for (const angle of angles) {
            const endX = x + size * Math.cos(angle);
            const endY = y + size * Math.sin(angle);
            this._drawLineAndSendCommand(x, y, endX, endY, this._animationThickness, color);
        }
    }

    _startMulticolorAnimation() {
        if (this._multicolorInterval) return;
        this._isMulticolorEnabled = true;
        this._multicolorInterval = setInterval(() => {
            this._shapeColor = this._getRandomColor();
            if (this._ui.colorInput) this._ui.colorInput.value = this._shapeColor;
        }, this._colorChangeIntervalMs);
        this.notify("info", "Modo multicolor activado.");
    }

    _stopMulticolorAnimation() {
        if (this._multicolorInterval) clearInterval(this._multicolorInterval);
        this._multicolorInterval = null;
        this._isMulticolorEnabled = false;
        this.notify("info", "Modo multicolor desactivado.");
    }
}

// --- Sub-Module 7: GenerativeAnimatorTool ---
// Allows users to create spectacular animations from their drawings.
// --- Sub-Module 7: GenerativeAnimatorTool ---
// Allows users to create spectacular animations from their drawings.
class GenerativeAnimatorTool extends QBit {
    _isAnimating = false;
    _drawingPixels = []; // Stores captured pixels from the canvas
    _canvas = null;
    _ctx = null;

    _ui = {}; // UI element references

    constructor() {
        super("Generative Animator", '<i class="fas fa-thumbs-up"></i>'); // Unique name and icon
        this._onStartup();
    }

    _onStartup() {
        this._canvas = document.getElementById('canvas');
        if (this._canvas) {
            this._ctx = this._canvas.getContext('2d');
        } else {
            this.notify("error", "Canvas del juego no encontrado para Generative Animator.");
            return;
        }

        this._loadInterface();
        this._setupEventListeners();
        setInterval(() => this._updateConnectionStatusUI(), 1000); // Check connection status regularly
        this._updateUIState('idle'); // Asegura que el estado inicial de la UI sea correcto.
        this.notify("info", "Módulo 'Generative Animator' cargado.");
    }

    _loadInterface() {
        const container = domMake.Tree("div", { id: `${this.identifier}-container`, class: "module-section" });
        this.htmlElements.section.appendChild(container);

        // Connection Status (similar to other modules)
        container.appendChild(domMake.Tree("div", { class: "module-section-title" }, ["Estado de Conexión"]));
        const connectionStatusDiv = domMake.Tree("div", {}, [
            domMake.Tree("span", { id: `${this.identifier}-connectionStatus`, class: `module-status-indicator module-status-disconnected` }),
            domMake.Tree("span", { id: `${this.identifier}-statusText` }, ["Disconnected"])
        ]);
        container.appendChild(connectionStatusDiv);

        // Capture Drawing Button
        container.appendChild(domMake.Tree("div", { class: "module-section-title" }, ["1. Capturar Dibujo"]));
        this._ui.captureBtn = domMake.Button('<i class="fas fa-camera"></i> Capturar Dibujo');
        this._ui.captureBtn.addEventListener('click', () => this._captureDrawing());
        container.appendChild(this._ui.captureBtn);

        // Animation Selector
        container.appendChild(domMake.Tree("div", { class: "module-section-title" }, ["2. Seleccionar Animación"]));
        this._ui.animationSelect = domMake.Tree("select", { id: `${this.identifier}-animationSelect`, class: "module-form-control" });
        const animationsList = [
            { value: "fireworks", label: "Fuegos Artificiales" },
            { value: "hueBlast", label: "Ráfaga de Tono" },
            { value: "colorFestival", label: "Festival de Color" },
            { value: "pixelArt", label: "Pixelizar Dibujo" },
            { value: "pixelTrail", label: "Rastros de Píxeles" },
        ];
        animationsList.forEach(anim => {
            this._ui.animationSelect.appendChild(domMake.Tree("option", { value: anim.value }, [anim.label]));
        });
        container.appendChild(this._ui.animationSelect);

        // Animate Button
        container.appendChild(domMake.Tree("div", { class: "module-btn-group" })); // For grouping buttons
        this._ui.animateBtn = domMake.Button('<i class="fas fa-play-circle"></i> 3. Animar');
        this._ui.animateBtn.disabled = true; // Disabled initially
        this._ui.animateBtn.addEventListener('click', () => this._runSelectedAnimation());
        container.lastChild.appendChild(this._ui.animateBtn);

        // Stop Animation Button
        this._ui.stopAnimationBtn = domMake.Button('<i class="fas fa-stop-circle"></i> Detener Animación');
        this._ui.stopAnimationBtn.disabled = true; // Disabled initially
        this._ui.stopAnimationBtn.addEventListener('click', () => this._isAnimating = false);
        container.lastChild.appendChild(this._ui.stopAnimationBtn);

        // Clear Canvas Button
        container.appendChild(domMake.Tree("div", { class: "module-section-title" }, ["Acciones Adicionales"]));
        this._ui.clearCanvasBtn = domMake.Button('<i class="fas fa-eraser"></i> Limpiar Lienzo');
        this._ui.clearCanvasBtn.addEventListener('click', () => this._clearCanvasForAll());
        container.appendChild(this._ui.clearCanvasBtn);
    }

    _setupEventListeners() {
        // Handled in _loadInterface
    }

    _updateConnectionStatusUI() {
        const statusIndicator = document.getElementById(`${this.identifier}-connectionStatus`);
        const statusText = document.getElementById(`${this.identifier}-statusText`);
        const isConnected = getGameSocket() !== null;

        if (statusIndicator && statusText) {
            statusIndicator.className = `module-status-indicator module-status-${isConnected ? 'connected' : 'disconnected'}`;
            statusText.textContent = isConnected ? "Conectado" : "Desconectado";
        }
        this._updateUIState(this._isAnimating ? 'animating' : 'idle');
    }

    _updateUIState(state) {
        // Asegúrate de que los elementos UI existan antes de intentar acceder a ellos
        if (!this._ui.captureBtn || !this._ui.animationSelect || !this._ui.animateBtn || !this._ui.stopAnimationBtn || !this._ui.clearCanvasBtn) {
            return;
        }

        const isConnected = getGameSocket() !== null;
        const isBusy = state === 'animating';

        this._ui.captureBtn.disabled = isBusy;
        this._ui.animationSelect.disabled = isBusy;
        // El botón de animar se deshabilita si está ocupado, no hay dibujo capturado, o no hay conexión
        this._ui.animateBtn.disabled = isBusy || this._drawingPixels.length === 0 || !isConnected;
        this._ui.stopAnimationBtn.disabled = !isBusy;
        this._ui.clearCanvasBtn.disabled = isBusy || !isConnected; // Deshabilita borrar durante la animación o si no hay conexión
    }

    async _clearCanvasForAll() {
        const currentSocket = getGameSocket();
        if (!currentSocket) {
            this.notify("warning", "No hay conexión WebSocket para limpiar el lienzo.");
            return;
        }
        if (this._ctx && this._canvas) {
            this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
        }
        this.notify("info", "Enviando comandos para borrar el lienzo...");
        // Send clear command using a large white line
        this._sendAndRenderDrawCmd([0.001, 0.5], [0.999, 0.5], "#FFFFFF", 2000);
        await new Promise(resolve => setTimeout(resolve, 200)); // Give time for clear to propagate
        this.notify("success", "Lienzo limpiado.");
    }

    _captureDrawing() {
        if (!this._canvas || !this._ctx) {
            this.notify("error", "Error: Canvas no encontrado o no inicializado. Asegúrate de estar en una sala de juego.");
            return;
        }

        const cw = this._canvas.width;
        const ch = this._canvas.height;
        const imageData = this._ctx.getImageData(0, 0, cw, ch);
        const data = imageData.data;
        this._drawingPixels = [];
        const sampleRate = 4; // Sample rate to reduce pixel count for performance

        for (let y = 0; y < ch; y += sampleRate) {
            for (let x = 0; x < cw; x += sampleRate) {
                const i = (y * cw + x) * 4;
                // Un píxel se considera "dibujado" si no es totalmente transparente (alpha > 100)
                // Y si no es un blanco casi puro (R, G, B < 250)
                if (data[i + 3] > 100 && (data[i] < 250 || data[i + 1] < 250 || data[i + 2] < 250)) {
                    this._drawingPixels.push({ x: x / cw, y: y / ch, color: `rgb(${data[i]},${data[i + 1]},${data[i + 2]})` });
                }
            }
        }

        if (this._drawingPixels.length === 0) {
            this.notify("warning", "¡Dibujo capturado! No se detectaron píxeles de dibujo (¿Quizás el dibujo es blanco o transparente?). El botón 'Animar' permanece deshabilitado.");
        } else {
            this.notify("success", `¡Dibujo capturado! ${this._drawingPixels.length} puntos listos para animar.`);
        }
        this._updateUIState('idle'); // Actualiza la UI para habilitar/deshabilitar el botón "Animar" según corresponda
    }

    async _runSelectedAnimation() {
        if (this._drawingPixels.length === 0) { this.notify("warning", "Primero, captura un dibujo."); return; }
        if (!getGameSocket()) { this.notify("error", "No estás conectado a una sala de Drawaria. Conéctate primero."); return; }
        if (this._isAnimating) return;

        const selectedEffect = this._ui.animationSelect.value;
        const effectFunction = this._animations[selectedEffect];

        if (effectFunction) {
            this._isAnimating = true;
            this._updateUIState('animating');
            await this._clearCanvasForAll();

            await effectFunction.call(this); // Call the animation function, binding 'this' to the module instance

            this._isAnimating = false;
            this._updateUIState('idle');
            this.notify("success", `Animación "${selectedEffect}" completada.`);
        } else {
            this.notify("error", `Animación "${selectedEffect}" no encontrada.`);
        }
    }

    // Helper: Convert RGB to HSL for hue-based animations
    _rgbToHsl(r, g, b) {
        r /= 255; g /= 255; b /= 255;
        const max = Math.max(r, g, b), min = Math.min(r, g, b);
        let h, s, l = (max + min) / 2;
        if (max === min) { h = s = 0; }
        else {
            const d = max - min;
            s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
            switch (max) {
                case r: h = (g - b) / d + (g < b ? 6 : 0); break;
                case g: h = (b - r) / d + 2; break;
                case b: h = (r - g) / d + 4; break;
            }
            h /= 6;
        }
        return [h, s, l];
    }

    // Unified function to send draw commands AND render locally
    _sendAndRenderDrawCmd(start_norm, end_norm, color, thickness) {
        const gameSocket = getGameSocket();
        if (!gameSocket) {
            this.notify("warning", "WebSocket no conectado. No se puede enviar ni renderizar el comando de dibujo.");
            this._isAnimating = false; // Stop animation if socket is lost
            return false;
        }

        const p1x_norm = Math.max(0, Math.min(1, start_norm[0]));
        const p1y_norm = Math.max(0, Math.min(1, start_norm[1]));
        const p2x_norm = Math.max(0, Math.min(1, end_norm[0]));
        const p2y_norm = Math.max(0, Math.min(1, end_norm[1]));
        const numThickness = parseFloat(thickness);

        // Local rendering on the game canvas
        if (this._ctx && this._canvas) {
            const p1x_px = p1x_norm * this._canvas.width;
            const p1y_px = p1y_norm * this._canvas.height;
            const p2x_px = p2x_norm * this._canvas.width;
            const p2y_px = p2y_norm * this._canvas.height;

            this._ctx.strokeStyle = color;
            this._ctx.lineWidth = numThickness;
            this._ctx.lineCap = 'round';
            this._ctx.lineJoin = 'round';
            this._ctx.beginPath();
            this._ctx.moveTo(p1x_px, p1y_px);
            this._ctx.lineTo(p2x_px, p2y_px);
            this._ctx.stroke();
        }

        // Send command to server
        const payload = `42["drawcmd",0,[${p1x_norm},${p1y_norm},${p2x_norm},${p2y_norm},false,${0 - numThickness},"${color}",0,0,{}]]`;
        gameSocket.send(payload);
        return true;
    }

    // --- Animation Implementations ---
    _animations = {
        fireworks: async function() {
            const particleCount = 5;
            for (const pixel of this._drawingPixels) {
                if (!this._isAnimating) break;
                const explosionCenterX = pixel.x;
                const explosionCenterY = pixel.y;
                for (let i = 0; i < particleCount; i++) {
                    const angle = Math.random() * 2 * Math.PI;
                    const distance = Math.random() * 0.015 + 0.005;
                    const endX = explosionCenterX + distance * Math.cos(angle);
                    const endY = explosionCenterY + distance * Math.sin(angle);
                    if (!this._sendAndRenderDrawCmd([explosionCenterX, explosionCenterY], [endX, endY], pixel.color, 2)) return;
                }
                await new Promise(resolve => setTimeout(resolve, 70));
            }
        },
        hueBlast: async function() {
            const steps = 100;
            for (let i = 0; i < steps && this._isAnimating; i++) {
                 const progress = i / steps;
                 const basePixel = this._drawingPixels[Math.floor(Math.random() * this._drawingPixels.length)];
                 const [r,g,b] = basePixel.color.match(/\d+/g).map(Number);
                 const hue = this._rgbToHsl(r,g,b)[0] * 360;
                 const newHue = (hue + progress * 180) % 360;
                 const color = `hsl(${newHue}, 100%, 60%)`;

                 const startX = 0.5;
                 const startY = 0.5;
                 const endX = startX + progress * 0.4 * Math.cos(Math.random() * 2 * Math.PI);
                 const endY = startY + progress * 0.4 * Math.sin(Math.random() * 2 * Math.PI);

                 if (!this._sendAndRenderDrawCmd([startX, startY], [endX, endY], color, 5 + i * 0.05)) return;
                 await new Promise(resolve => setTimeout(resolve, 40));
            }
        },
        colorFestival: async function() {
            const numShapes = 40;
            for (let i = 0; i < numShapes && this._isAnimating; i++) {
                 const pixel = this._drawingPixels[Math.floor(Math.random() * this._drawingPixels.length)];
                 const x = Math.random() * 0.9 + 0.05;
                 const y = Math.random() * 0.9 + 0.05;
                 const size = Math.random() * 0.04 + 0.02;
                 const thickness = Math.floor(Math.random() * 8) + 3;

                 const shapeType = Math.floor(Math.random() * 3);
                 if (shapeType === 0) { // Square
                     this._sendAndRenderDrawCmd([x-size, y-size], [x+size, y-size], pixel.color, thickness);
                     this._sendAndRenderDrawCmd([x+size, y-size], [x+size, y+size], pixel.color, thickness);
                     this._sendAndRenderDrawCmd([x+size, y+size], [x-size, y+size], pixel.color, thickness);
                     this._sendAndRenderDrawCmd([x-size, y+size], [x-size, y-size], pixel.color, thickness);
                 } else if (shapeType === 1) { // Star
                     for (let k = 0; k < 5; k++) {
                         const angle = (k / 5) * 2 * Math.PI;
                         this._sendAndRenderDrawCmd([x, y], [x + size * Math.cos(angle), y + size * Math.sin(angle)], pixel.color, thickness);
                     }
                 } else { // Spiral
                     let lastX = x, lastY = y;
                     for (let k = 0; k <= 20; k++) {
                         const angle = (k / 20) * 4 * Math.PI;
                         const radius = (k / 20) * size;
                         const currentX = x + radius * Math.cos(angle);
                         const currentY = y + radius * Math.sin(angle);
                         this._sendAndRenderDrawCmd([lastX, lastY], [currentX, currentY], pixel.color, thickness);
                         lastX = currentX; lastY = currentY;
                     }
                 }
                 await new Promise(resolve => setTimeout(resolve, 80));
            }
        },
        pixelArt: async function() {
            await this._clearCanvasForAll();
            const pixelSize_norm = 0.01;

            const totalSpriteW_norm = (this._canvas.width > this._canvas.height ? this._canvas.height : this._canvas.width) / this._canvas.width * 0.8;
            const totalSpriteH_norm = (this._canvas.width > this._canvas.height ? this._canvas.height : this._canvas.width) / this._canvas.height * 0.8;

            const startX_norm = 0.5 - (totalSpriteW_norm / 2);
            const startY_norm = 0.5 - (totalSpriteH_norm / 2);

            for(const pixel of this._drawingPixels) {
                if(!this._isAnimating) break;
                const drawX_norm = startX_norm + (pixel.x * totalSpriteW_norm);
                const drawY_norm = startY_norm + (pixel.y * totalSpriteH_norm);

                const thickness_px = pixelSize_norm * Math.min(this._canvas.width, this._canvas.height);

                if (!this._sendAndRenderDrawCmd([drawX_norm, drawY_norm], [drawX_norm + 0.0001, drawY_norm + 0.0001], pixel.color, thickness_px)) break;
                await new Promise(resolve => setTimeout(resolve, 5));
            }
        },
        pixelTrail: async function() {
            if (this._drawingPixels.length === 0) {
                this.notify("warning", "Rastros de Píxeles: No hay píxeles capturados para animar.");
                return;
            }

            const shuffledPixels = [...this._drawingPixels].sort(() => 0.5 - Math.random());
            const delayPerPixel = 70;
            const pixelThickness = 2;

            for (let i = 0; i < shuffledPixels.length && this._isAnimating; i++) {
                const pixel = shuffledPixels[i];
                if (!this._sendAndRenderDrawCmd([pixel.x, pixel.y], [pixel.x + 0.00001, pixel.y + 0.00001], pixel.color, pixelThickness)) {
                    this.notify("warning", "Rastros de Píxeles: Fallo al enviar comando de dibujo. Terminando animación.");
                    break;
                }
                await new Promise(resolve => setTimeout(resolve, delayPerPixel));
            }
        }
    };
}
// --- Sub-Module 7: LuminousFlowGeneratorTool ---
// Generates abstract, glowing, fluid art patterns on Drawaria canvas, rendering locally and globally.
class LuminousFlowGeneratorTool extends QBit {
    _isGenerating = false;
    _generationIntervalId = null;
    _currentX = 50; // Posición inicial X (centro del canvas en porcentaje)
    _currentY = 50; // Posición inicial Y (centro del canvas en porcentaje)
    _currentAngle = Math.random() * 2 * Math.PI; // Ángulo de dirección actual en radianes
    _currentHue = 0; // Tono HSL actual para el color
    _linesInCurrentStroke = 0;

    // Referencias al canvas principal del juego
    _gameCanvas = null;
    _gameCtx = null;

    // Elementos de la interfaz de usuario
    _ui = {
        numStrokesInput: null,
        linesPerStrokeInput: null,
        segmentLengthInput: null,
        angleRandomnessInput: null,
        minThicknessInput: null,
        maxThicknessInput: null,
        startHueInput: null,
        endHueInput: null,
        alphaInput: null,
        drawDelayInput: null,
        colorPreview: null,
        startButton: null,
        stopButton: null,
        statusLabel: null
    };

    // Valores por defecto (ajustables)
    _defaults = {
        numStrokes: 5,
        linesPerStroke: 100,
        segmentLength: 0.5, // % del canvas (Game coords 0-100)
        angleRandomness: 10, // grados
        minThickness: 5,
        maxThickness: 25,
        startHue: 0,   // Rojo
        endHue: 360,   // Rojo (ciclo completo)
        alpha: 0.8,    // Transparencia (AUMENTADO)
        drawDelay: 5   // ms por línea
    };

    constructor() {
        super("Luminous Flow Generator", '<i class="fas fa-lightbulb"></i>'); // Unique name and icon
        this._onStartup();
    }

    _onStartup() {
        this._findGameCanvas(); // Localizar el canvas principal y su contexto
        this._loadInterface();
        this._setInitialValues();
        this._updateColorPreview();
        this._setupEventListeners();
        setInterval(() => this._updateButtonsState(), 1000); // Check connection status regularly
        this.notify("info", "Módulo 'Luminous Flow Generator' cargado. ¡Prepara tu lienzo!");
    }

    _findGameCanvas() {
        this._gameCanvas = document.getElementById('canvas');
        if (this._gameCanvas) {
            this._gameCtx = this._gameCanvas.getContext('2d');
        } else {
            this.notify("warning", "Canvas del juego no encontrado. Las funciones de dibujo no estarán disponibles.");
        }
    }

    // Helper unificado para enviar comando de dibujo y renderizar localmente
    // x1_game, y1_game, x2_game, y2_game son en coordenadas de juego (0-100)
    _sendAndRenderDrawCommand(x1_game, y1_game, x2_game, y2_game, thickness, color) {
        // CORRECTED: Call getGameSocket as a global function, not a method of 'this'
        const gameSocket = getGameSocket();
        if (!gameSocket) {
            this.notify("warning", "No hay conexión WebSocket activa. No se pueden enviar comandos de dibujo.");
            this._stopGeneration(); // Stop if connection is lost
            return false;
        }

        // Convertir coordenadas de juego (0-100) a normalizadas (0-1) para el servidor
        const x1_norm = x1_game / 100;
        const y1_norm = y1_game / 100;
        const x2_norm = x2_game / 100;
        const y2_norm = y2_game / 100;

        // Enviar comando al servidor
        const payload = `42["drawcmd",0,[${x1_norm.toFixed(4)},${y1_norm.toFixed(4)},${x2_norm.toFixed(4)},${y2_norm.toFixed(4)},false,${0 - thickness},"${color}",0,0,{}]]`;
        gameSocket.send(payload);

        // Renderizado local en el canvas del juego
        if (this._gameCtx && this._gameCanvas) {
            const x1_px = x1_norm * this._gameCanvas.width;
            const y1_px = y1_norm * this._gameCanvas.height;
            const x2_px = x2_norm * this._gameCanvas.width;
            const y2_px = y2_norm * this._gameCanvas.height;

            this._gameCtx.strokeStyle = color;
            this._gameCtx.lineWidth = thickness;
            this._gameCtx.lineCap = 'round';
            this._gameCtx.lineJoin = 'round';
            this._gameCtx.beginPath();
            this._gameCtx.moveTo(x1_px, y1_px);
            this._gameCtx.lineTo(x2_px, y2_px);
            this._gameCtx.stroke();
        }
        return true;
    }


    _loadInterface() {
        const container = domMake.Tree("div", { class: "luminous-section" });
        this.htmlElements.section.appendChild(container);

        container.appendChild(domMake.Tree("div", { class: "luminous-section-title" }, ["Generador de Arte Luminoso"]));

        const controlsGroup = domMake.Tree("div", { class: "luminous-controls-group" });

        const addControlInput = (label, propName, type, min, max, step, placeholder = "") => {
            const wrapper = domMake.Tree("div");
            wrapper.appendChild(domMake.Tree("label", {}, [label]));
            const input = domMake.Tree("input", { type, min, max, step, placeholder, value: this._defaults[propName] });
            this._ui[propName + "Input"] = input; // Guardar referencia al elemento UI
            wrapper.appendChild(input);
            controlsGroup.appendChild(wrapper);
        };

        addControlInput("Número de Flujos:", "numStrokes", "number", 1, 50, 1);
        addControlInput("Líneas por Flujo:", "linesPerStroke", "number", 10, 500, 10);
        addControlInput("Longitud Segmento (%):", "segmentLength", "number", 0.1, 10, 0.1); // AUMENTADO max: 10
        addControlInput("Aleatoriedad Ángulo (º):", "angleRandomness", "number", 0, 90, 1);
        addControlInput("Grosor Mínimo:", "minThickness", "number", 1, 50, 1);
        addControlInput("Grosor Máximo:", "maxThickness", "number", 5, 100, 1);
        addControlInput("Tono Inicial (HSL):", "startHue", "number", 0, 359, 1);
        addControlInput("Tono Final (HSL):", "endHue", "number", 0, 359, 1);
        addControlInput("Opacidad (0-1):", "alpha", "number", 0.1, 1.0, 0.05);
        addControlInput("Retraso Dibujo (ms):", "drawDelay", "number", 1, 1000, 5);

        container.appendChild(controlsGroup);

        this._ui.colorPreview = domMake.Tree("div", { class: "luminous-color-preview" });
        container.appendChild(domMake.Tree("label", {}, ["Previsualización de Degradado:"]));
        container.appendChild(this._ui.colorPreview);

        const buttonRow = domMake.Row({ class: "luminous-button-row" });
        this._ui.startButton = domMake.Button('<i class="fas fa-play-circle"></i> Iniciar Generación');
        this._ui.startButton.addEventListener('click', () => this._startGeneration());
        this._ui.stopButton = domMake.Button('<i class="fas fa-stop-circle"></i> Detener Generación');
        this._ui.stopButton.disabled = true;
        this._ui.stopButton.addEventListener('click', () => this._stopGeneration());
        const clearCanvasButton = domMake.Button('<i class="fas fa-eraser"></i> Limpiar Lienzo');
        clearCanvasButton.addEventListener('click', () => this._clearCanvas());

        buttonRow.appendAll(this._ui.startButton, this._ui.stopButton, clearCanvasButton);
        container.appendChild(buttonRow);

        this._ui.statusLabel = domMake.Tree("div", { class: "luminous-status" }, ["Estado: Listo."]);
        container.appendChild(this._ui.statusLabel);
    }

    _setInitialValues() {
        for (const prop in this._defaults) {
            // Corrected: Use this._ui directly to set values, as elements are stored there
            const inputElement = this._ui[prop + "Input"];
            if (inputElement) {
                inputElement.value = this._defaults[prop];
            }
        }
    }

    _setupEventListeners() {
        // Event listeners para actualizar la previsualización del color
        const colorInputs = ['startHueInput', 'endHueInput', 'alphaInput'];
        colorInputs.forEach(prop => {
            const inputElement = this._ui[prop];
            if (inputElement) { // Ensure element exists before adding listener
                inputElement.addEventListener('input', () => this._updateColorPreview());
            }
        });
    }

    _updateColorPreview() {
        if (!this._ui.colorPreview || !this._ui.startHueInput || !this._ui.endHueInput || !this._ui.alphaInput) return;

        const startHue = parseInt(this._ui.startHueInput.value);
        const endHue = parseInt(this._ui.endHueInput.value);
        const alpha = parseFloat(this._ui.alphaInput.value);

        const gradient = `linear-gradient(to right, hsla(${startHue}, 100%, 50%, ${alpha}) 0%, hsla(${(startHue + (endHue - startHue) / 2) % 360}, 100%, 50%, ${alpha}) 50%, hsla(${endHue}, 100%, 50%, ${alpha}) 100%)`;
        this._ui.colorPreview.style.background = gradient;
    }

    _updateButtonsState() {
        // CORRECTED: Call getGameSocket as a global function
        const isConnected = getGameSocket() !== null;
        this._ui.startButton.disabled = !isConnected || this._isGenerating;
        this._ui.stopButton.disabled = !this._isGenerating;
        this._ui.statusLabel.textContent = isConnected ? (this._isGenerating ? "Estado: Generando arte..." : "Estado: Listo.") : "Estado: Desconectado del juego.";
    }

    async _clearCanvas() {
        const gameSocket = getGameSocket();
        if (!gameSocket) {
            this.notify("warning", "No hay conexión WebSocket activa para limpiar el lienzo.");
            return;
        }

        this.notify("info", "Enviando comandos para borrar el lienzo...");
        if (this._gameCtx && this._gameCanvas) {
            this._gameCtx.clearRect(0, 0, this._gameCanvas.width, this._gameCanvas.height);
        }

        const clearThickness = 1000;
        const clearColor = '#ffffff'; // White color to clear
        const steps = 5;

        for (let i = 0; i <= steps; i++) {
            const yCoord = (i / steps) * 100;
            this._sendAndRenderDrawCommand(0, yCoord, 100, yCoord, clearThickness, clearColor);
            await new Promise(resolve => setTimeout(resolve, 5));
        }
        for (let i = 0; i <= steps; i++) {
            const xCoord = (i / steps) * 100;
            this._sendAndRenderDrawCommand(xCoord, 0, xCoord, 100, clearThickness, clearColor);
            await new Promise(resolve => setTimeout(resolve, 5));
        }
        this.notify("success", "Lienzo limpiado.");
    }


    _startGeneration() {
        const gameSocket = getGameSocket();
        if (!gameSocket) {
            this.notify("error", "No se puede iniciar la generación sin una conexión de juego activa.");
            return;
        }

        this._isGenerating = true;
        this._updateButtonsState(); // Update button states immediately

        this._currentX = Math.random() * 100;
        this._currentY = Math.random() * 100;
        this._currentAngle = Math.random() * 2 * Math.PI;
        this._currentHue = parseInt(this._ui.startHueInput.value);
        this._linesInCurrentStroke = 0;

        this.notify("info", "Iniciando generación de arte luminoso...");

        this._clearCanvas().then(() => {
            this._generationIntervalId = setInterval(() => this._generateNextStep(), parseInt(this._ui.drawDelayInput.value));
        });
    }

    _stopGeneration() {
        this._isGenerating = false;
        if (this._generationIntervalId) {
            clearInterval(this._generationIntervalId);
            this._generationIntervalId = null;
        }
        this._updateButtonsState(); // Update button states immediately
        this.notify("info", "Generación de arte luminoso detenida.");
    }

    _generateNextStep() {
        // CORRECTED: Call getGameSocket as a global function
        if (!this._isGenerating || !getGameSocket()) {
            this._stopGeneration();
            return;
        }

        const numStrokes = parseInt(this._ui.numStrokesInput.value);
        const linesPerStroke = parseInt(this._ui.linesPerStrokeInput.value);
        const segmentLength = parseFloat(this._ui.segmentLengthInput.value);
        const angleRandomness = parseFloat(this._ui.angleRandomnessInput.value) * (Math.PI / 180);
        const minThickness = parseInt(this._ui.minThicknessInput.value);
        const maxThickness = parseInt(this._ui.maxThicknessInput.value);
        const startHue = parseInt(this._ui.startHueInput.value);
        const endHue = parseInt(this._ui.endHueInput.value);
        const alpha = parseFloat(this._ui.alphaInput.value);

        // Si se han dibujado suficientes líneas para el flujo actual o si es la primera línea
        if (this._linesInCurrentStroke >= linesPerStroke) {
            this._currentX = Math.random() * 100;
            this._currentY = Math.random() * 100;
            this._currentAngle = Math.random() * 2 * Math.PI;
            this._linesInCurrentStroke = 0; // Reiniciar contador de líneas para el nuevo flujo
        }

        this._currentAngle += (Math.random() - 0.5) * 2 * angleRandomness; // Aplicar aleatoriedad al ángulo
        const newX = this._currentX + segmentLength * Math.cos(this._currentAngle);
        const newY = this._currentY + segmentLength * Math.sin(this._currentAngle);

        // Clamp coordinates to stay within 0-100 range
        const clampedX1 = Math.max(0, Math.min(100, this._currentX));
        const clampedY1 = Math.max(0, Math.min(100, this._currentY));
        const clampedX2 = Math.max(0, Math.min(100, newX));
        const clampedY2 = Math.max(0, Math.min(100, newY));

        // Reflejar el ángulo si la línea golpea un borde
        if (newX <= 0 || newX >= 100) {
            this._currentAngle = Math.PI - this._currentAngle; // Reflejar horizontalmente
            this._currentX = clampedX2; // Ajustar posición para que no se salga
        }
        if (newY <= 0 || newY >= 100) {
            this._currentAngle = -this._currentAngle; // Reflejar verticalmente
            this._currentY = clampedY2; // Ajustar posición para que no se salga
        }

        // Interpolación de color basado en el progreso dentro del flujo
        let hueStep = 0;
        if (linesPerStroke > 0) { // Evitar división por cero
            hueStep = (endHue - startHue) / linesPerStroke;
        }
        this._currentHue = (this._currentHue + hueStep + 360) % 360;

        const color = `hsla(${this._currentHue}, 100%, 50%, ${alpha})`;
        const thickness = Math.random() * (maxThickness - minThickness) + minThickness;

        this._sendAndRenderDrawCommand(clampedX1, clampedY1, clampedX2, clampedY2, thickness, color);

        this._currentX = clampedX2;
        this._currentY = clampedY2;
        this._linesInCurrentStroke++;

        // Condición para detener la generación si se han dibujado todos los flujos
        if (this._linesInCurrentStroke >= linesPerStroke * numStrokes) {
            this._stopGeneration();
            this.notify("success", "Generación de arte completada.");
        }
    }
}

// --- Sub-Module 8: ElementalAnimationsTool ---
// Provides high-quality elemental animations (Ocean, Magma, Poison, PurpleGoo)
class ElementalAnimationsTool extends QBit {
    _drawingActive = false;
    _canvas = null;
    _ctx = null;

    _ui = {}; // UI elements references for settings
    _animationConfig = {}; // Stores current animation configuration

    constructor() {
        super("Elemental Animations", '<i class="fas fa-cloud"></i>'); // Unique name and icon
        this._onStartup();
    }

    _onStartup() {
        this._canvas = document.getElementById('canvas');
        if (this._canvas) {
            this._ctx = this._canvas.getContext('2d');
        } else {
            this.notify("error", "Canvas del juego no encontrado para Elemental Animations.");
            return;
        }

        this._loadInterface();
        this._setInitialValues(); // Call to set initial slider values
        this._setupEventListeners();
        // Periodically check and update connection status and button states
        setInterval(() => this._updateButtonsState(), 1000);
        this.notify("info", "Módulo 'Elemental Animations' cargado.");
    }

    _loadInterface() {
        const container = domMake.Tree("div", { class: "elemental-section" });
        this.htmlElements.section.appendChild(container);

        container.appendChild(domMake.Tree("h4", {}, ["Animaciones"]));
        const animationsGrid = domMake.Tree("div", { class: "elemental-button-grid" });
        const animationsList = [
            { id: "effect-ocean", label: "Ocean", icon: 'bxs-droplet', type: 'Ocean' },
            { id: "effect-magma", label: "Magma", icon: 'bxs-volcano', type: 'Magma' },
            { id: "effect-poison", label: "Poison", icon: 'bxs-skull', type: 'Poison' },
            { id: "effect-purplegoo", label: "Purple Goo", icon: 'bxs-vial', type: 'PurpleGoo' }
        ];

        animationsList.forEach(anim => {
            const button = domMake.Button(`<i class='bx ${anim.icon}'></i> ${anim.label}`, { class: `elemental-button ${anim.type.toLowerCase()}` });
            button.addEventListener('click', () => this._animateElementalWave(anim.type));
            animationsGrid.appendChild(button);
            this._ui[anim.id] = button; // Store UI reference
        });
        container.appendChild(animationsGrid);

        container.appendChild(domMake.Tree("h4", {}, ["Settings"]));
        const settingsDiv = domMake.Tree("div", {});

        const addSetting = (label, id, min, max, value, step, unit = '', isCheckbox = false) => {
            const item = domMake.Tree("div", { class: "elemental-setting-item" });
            let input;
            if (isCheckbox) {
                input = domMake.Tree("input", { type: "checkbox", id: `${this.identifier}-${id}`, checked: value });
                item.appendAll(domMake.Tree("label", { for: `${this.identifier}-${id}`, class: "checkbox-label" }, [label]), input);
            } else {
                input = domMake.Tree("input", { type: "range", id: `${this.identifier}-${id}`, min, max, value, step });
                const valueSpan = domMake.Tree("span", { id: `${this.identifier}-${id}Value`, class: "value-display" }, [`${value}${unit}`]);
                input.addEventListener('input', () => { // Corrected listener setup
                    valueSpan.textContent = `${input.value}${unit}`;
                    // Special handling for fill density display (divide by 1000)
                    if (id === "animationFillDensityInput") {
                        valueSpan.textContent = `${(parseFloat(input.value) / 1000).toFixed(3)}`;
                    }
                });
                item.appendAll(domMake.Tree("label", { for: `${this.identifier}-${id}` }, [label]), input, valueSpan);
            }
            settingsDiv.appendChild(item);
            this._ui[id] = input; // Store UI reference
        };

        addSetting("Speed (ms/frame):", "animationSpeedInput", "50", "500", "150", "10");
        addSetting("Wave Detail (Segments):", "animationSegmentsInput", "10", "50", "30", "1");
        addSetting("Particle Density (%):", "animationParticleChanceInput", "0", "100", "15", "1", "%");
        addSetting("Base Fill Density:", "animationFillDensityInput", "5", "100", "8", "1", ""); // Default fill density 0.008 (WAS 20, which was 0.02)
        addSetting("Randomize Frame Delay:", "animationRandomDelayToggle", null, null, false, null, null, true); // Checkbox

        settingsDiv.appendChild(domMake.Tree("p", { style: "font-size:0.85em;color:var(--warning);margin-top:10px;" }, ""));
        container.appendChild(settingsDiv);

        const stopButton = domMake.Button('<i class="fas fa-stop-circle"></i> Detener Animación', { class: "elemental-button danger" });
        stopButton.style.width = "100%";
        stopButton.addEventListener('click', () => this._stopDrawing());
        container.appendChild(stopButton);
    }

    _setInitialValues() { // Method to correctly set initial slider values on UI
        // This ensures the value displays match the initial range values
        this._ui.animationSpeedInput.value = 150;
        this._ui.animationSegmentsInput.value = 30;
        this._ui.animationParticleChanceInput.value = 15;
        this._ui.animationFillDensityInput.value = 8; // Default value from _defaults
        this._ui.animationRandomDelayToggle.checked = false;

        // Manually trigger input events to update value spans
        // This is crucial for numerical display on load
        this._ui.animationSpeedInput.dispatchEvent(new Event('input'));
        this._ui.animationSegmentsInput.dispatchEvent(new Event('input'));
        this._ui.animationParticleChanceInput.dispatchEvent(new Event('input'));
        this._ui.animationFillDensityInput.dispatchEvent(new Event('input'));
    }

    _setupEventListeners() {
        // Event listeners are set up in _loadInterface's addSetting during element creation.
        // No additional listeners are needed here as they are correctly linked.
    }

    _updateButtonsState() {
        const isConnected = getGameSocket() !== null;
        // Enable/disable all setting inputs
        const settingInputs = [
            this._ui.animationSpeedInput, this._ui.animationSegmentsInput,
            this._ui.animationParticleChanceInput, this._ui.animationFillDensityInput,
            this._ui.animationRandomDelayToggle
        ];
        settingInputs.forEach(input => {
            if (input) input.disabled = !isConnected || this._drawingActive;
        });

        // Enable/disable animation trigger buttons
        const animationButtons = [
            this._ui['effect-ocean'], this._ui['effect-magma'],
            this._ui['effect-poison'], this._ui['effect-purplegoo']
        ];
        animationButtons.forEach(button => {
            if (button) button.disabled = !isConnected || this._drawingActive;
        });

        // Enable/disable the global stop button
        const stopButton = document.querySelector(`#${this.identifier} .elemental-button.danger`);
        if (stopButton) {
            stopButton.disabled = !this._drawingActive;
        }
    }

    _delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }

    _getRandomInt(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min; }
    _getRandomFloat(min, max) { return Math.random() * (max - min) + min; }

    // Unified function to send draw commands AND render locally
    // start_norm and end_norm are in normalized 0-1 coordinates
    _sendAndRenderDrawCmd(start_norm, end_norm, color, thickness, isEraser = false, algo = 0) {
        const gameSocket = getGameSocket(); // Using the global getGameSocket from DrawariaTools
        if (!gameSocket) {
            this.notify("warning", "WebSocket no conectado. No se puede enviar ni renderizar el comando de dibujo.");
            this._stopDrawing(); // Stop drawing if socket is lost
            return false;
        }

        const p1x_norm = Math.max(0, Math.min(1, start_norm[0]));
        const p1y_norm = Math.max(0, Math.min(1, start_norm[1]));
        const p2x_norm = Math.max(0, Math.min(1, end_norm[0]));
        const p2y_norm = Math.max(0, Math.min(1, end_norm[1]));
        let numThickness = parseFloat(thickness);
        if (isNaN(numThickness)) {
            numThickness = 10;
        }

        // Local rendering on the game canvas
        if (this._ctx && this._canvas) {
            const p1x_px = p1x_norm * this._canvas.width;
            const p1y_px = p1y_norm * this._canvas.height;
            const p2x_px = p2x_norm * this._canvas.width;
            const p2y_px = p2y_norm * this._canvas.height;

            this._ctx.strokeStyle = color;
            this._ctx.lineWidth = numThickness;
            this._ctx.lineCap = 'round';
            this._ctx.lineJoin = 'round';
            if (isEraser) {
                this._ctx.globalCompositeOperation = 'destination-out'; // Apply eraser blend mode
            }
            this._ctx.beginPath();
            this._ctx.moveTo(p1x_px, p1y_px);
            this._ctx.lineTo(p2x_px, p2y_px);
            this._ctx.stroke();
            if (isEraser) {
                this._ctx.globalCompositeOperation = 'source-over'; // Reset blend mode
            }
        }

        const gT = isEraser ? numThickness : 0 - numThickness;
        gameSocket.send(`42["drawcmd",0,[${p1x_norm},${p1y_norm},${p2x_norm},${p2y_norm},${isEraser},${gT},"${color}",0,0,{"2":${algo},"3":0.5,"4":0.5}]]`);
        return true;
    }

    _stopDrawing() {
        this._drawingActive = false;
        this.notify("info", "Animación detenida.");
        this._updateButtonsState(); // Update UI after stopping
    }

    // Clear Canvas implementation
    async _clearCanvas() {
        const gameSocket = getGameSocket();
        if (!gameSocket) {
            this.notify("warning", "No hay conexión WebSocket activa para limpiar el lienzo.");
            return;
        }

        this.notify("info", "Enviando comandos para borrar el lienzo...");
        // Local clear before sending commands to avoid lingering old drawing
        if (this._ctx && this._canvas) {
            this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);
        }

        const clearThickness = 1000;
        const clearColor = '#ffffff'; // White color to clear
        const steps = 5;

        for (let i = 0; i <= steps; i++) {
            const yCoord = (i / steps) * 100;
            // Pass isEraser = true for clearing lines
            this._sendAndRenderDrawCmd([0, yCoord], [100, yCoord], clearColor, clearThickness, true);
            await new Promise(resolve => setTimeout(resolve, 5));
        }
        for (let i = 0; i <= steps; i++) {
            const xCoord = (i / steps) * 100;
            // Pass isEraser = true for clearing lines
            this._sendAndRenderDrawCmd([xCoord, 0], [xCoord, 100], clearColor, clearThickness, true);
            await new Promise(resolve => setTimeout(resolve, 5));
        }
        this.notify("success", "Lienzo limpiado.");
    }

    async _animateElementalWave(type) {
        if (!this._canvas || !this._ctx) {
            this.notify("error", "Canvas not ready. Please ensure you are in a Drawaria room.");
            return;
        }
        const gameSocket = getGameSocket();
        if (!gameSocket) {
            this.notify("error", "Not connected to Drawaria room. Please join a room first.");
            return;
        }
        if (this._drawingActive) {
            this.notify("warning", "Animation already in progress. Please wait or stop the current animation.");
            return;
        }

        this._drawingActive = true;
        this._updateButtonsState(); // Update UI to disable other buttons
        this.notify("info", `Starting ${type} Animation...`);

        let config;
        // Using 0-1 normalized coordinates for all calculations
        switch (type) {
            case 'Ocean':
                config = {
                    baseFillColor: '#00008B',
                    waveColors: ['#1E90FF', '#4682B4'],
                    outlineColor: '#AFEEEE',
                    particleColor: '#00FFFF',
                    particleType: 'blob',
                    particleChance: 0.15,
                    particleSizeMin: 0.01,
                    particleSizeMax: 0.03,
                    particleThickness: 8,
                    startHeight: 0.88, // Normalized Y
                    waveAmplitude: 0.06,
                    waveFrequency: 2.5,
                    waveSpeed: 0.02,
                    outlineOffset: 0.005,
                    outlineThicknessReduction: 3,
                    mainThickness: 30,
                    waveStyle: 'smooth',
                    frames: 100, // Total frames, not lines
                };
                break;
            case 'Magma':
                config = {
                    baseFillColor: '#660000',
                    waveColors: ['#FF2400', '#CC5500'],
                    outlineColor: '#282828',
                    particleColor: '#FFA500',
                    particleType: 'ember',
                    particleChance: 0.2,
                    particleSizeMin: 0.008,
                    particleSizeMax: 0.02,
                    particleThickness: 4,
                    startHeight: 0.86,
                    waveAmplitude: 0.04,
                    waveFrequency: 2,
                    waveSpeed: 0.01,
                    outlineOffset: 0.01,
                    outlineThicknessReduction: 1,
                    mainThickness: 28,
                    waveStyle: 'jagged',
                    frames: 100,
                };
                break;
            case 'Poison':
                config = {
                    baseFillColor: '#556B2F',
                    waveColors: ['#6B8E23', '#808000'],
                    outlineColor: '#ADFF2F',
                    particleColor: '#7FFF00',
                    particleType: 'blob',
                    particleChance: 0.25,
                    particleSizeMin: 0.015,
                    particleSizeMax: 0.035,
                    particleThickness: 10,
                    startHeight: 0.87,
                    waveAmplitude: 0.055,
                    waveFrequency: 1.8,
                    waveSpeed: 0.018,
                    outlineOffset: 0.006,
                    outlineThicknessReduction: 2,
                    mainThickness: 26,
                    waveStyle: 'gloopy',
                    frames: 100,
                };
                break;
            case 'PurpleGoo':
                config = {
                    baseFillColor: '#4B0082',
                    waveColors: ['#800080', '#9932CC'],
                    outlineColor: '#FF00FF',
                    particleColor: '#DA70D6',
                    particleType: 'blob',
                    particleChance: 0.2,
                    particleSizeMin: 0.01,
                    particleSizeMax: 0.03,
                    particleThickness: 9,
                    startHeight: 0.88,
                    waveAmplitude: 0.05,
                    waveFrequency: 2.2,
                    waveSpeed: 0.022,
                    outlineOffset: 0.007,
                    outlineThicknessReduction: 2,
                    mainThickness: 27,
                    waveStyle: 'jagged',
                    frames: 100,
                };
                break;
            default:
                this._stopDrawing();
                console.warn(`Unknown animation type: ${type}`);
                return;
        }

        // Apply User Settings from UI
        const userFrameDelay = parseInt(this._ui.animationSpeedInput.value); // ms per frame
        const userSegments = parseInt(this._ui.animationSegmentsInput.value); // number of wave segments
        const userParticleChance = parseFloat(this._ui.animationParticleChanceInput.value) / 100; // % chance to 0-1
        // Clamp fill density input to a reasonable range (0.005 to 0.1 for fillStep_norm)
        const userFillDensityInputVal = parseInt(this._ui.animationFillDensityInput.value);
        config.fillStep_norm = Math.max(0.005, Math.min(0.1, userFillDensityInputVal / 1000)); // Divide user input by 1000

        const addRandomDelay = this._ui.animationRandomDelayToggle.checked;

        // Clamp segments to a reasonable range for performance and visual quality
        config.segments = Math.max(10, Math.min(50, userSegments));
        config.particleChance = Math.max(0, Math.min(1, userParticleChance));


        // --- Clear the canvas before drawing new animation ---
        await this._clearCanvas();
        if (!this._drawingActive) { return; } // Check if clear stopped the drawing

        // --- Draw Solid Base Fill ---
        // Fill the entire canvas from bottom to top until startHeight
        const fillThicknessPerLine = 15; // Thicker lines for faster fill
        let fillY = 0.99; // Start near bottom
        const targetFillY = config.startHeight + config.waveAmplitude; // Fill up to below the wave crests

        while (fillY > targetFillY && this._drawingActive) {
            // Draw a full-width horizontal line
            // Use isEraser: false because we are coloring, not erasing
            if (!this._sendAndRenderDrawCmd([0, fillY], [1, fillY], config.baseFillColor, fillThicknessPerLine, false)) {
                this._stopDrawing();
                console.error("Failed to draw base fill segment.");
                break;
            }
            fillY -= config.fillStep_norm; // Move upwards by fill density
            await this._delay(10); // Small delay to avoid spamming
        }
        if (!this._drawingActive) { console.log("Animation stopped during base fill."); return; }
        await this._delay(50); // Pause after base fill is done

        // --- Animate Waves ---
        for (let frame = 0; frame < config.frames && this._drawingActive; frame++) {
            let wavePoints_norm = [];
            for (let i = 0; i <= config.segments; i++) { // Use configurable segments
                const x_norm = i / config.segments;
                let yOffset_norm = Math.sin((x_norm * config.waveFrequency * Math.PI) + (frame * config.waveSpeed)) * config.waveAmplitude;

                if (config.waveStyle === 'gloopy') {
                    yOffset_norm += (Math.sin((x_norm * config.waveFrequency * 1.7 * Math.PI) + (frame * config.waveSpeed * 1.3) + 0.5) * config.waveAmplitude * 0.4) * Math.sin(x_norm * Math.PI);
                } else if (config.waveStyle === 'jagged') {
                    yOffset_norm += (this._getRandomFloat(-0.5, 0.5)) * config.waveAmplitude * 0.3;
                }
                const y_norm = config.startHeight - yOffset_norm;
                wavePoints_norm.push([x_norm, y_norm]);
            }

            for (let i = 0; i < wavePoints_norm.length - 1 && this._drawingActive; i++) {
                const p1_norm = wavePoints_norm[i];
                const p2_norm = wavePoints_norm[i + 1];
                const waveColor = config.waveColors[i % config.waveColors.length];

                // Draw wave main body (not eraser mode)
                if (!this._sendAndRenderDrawCmd(p1_norm, p2_norm, waveColor, config.mainThickness, false)) {
                    this._stopDrawing(); break;
                }

                // Draw wave outline (not eraser mode)
                const p1_outline_norm = [p1_norm[0], p1_norm[1] - config.outlineOffset];
                const p2_outline_norm = [p2_norm[0], p2_norm[1] - config.outlineOffset];
                const outlineThickness = Math.max(1, config.mainThickness - config.outlineThicknessReduction);
                if (!this._sendAndRenderDrawCmd(p1_outline_norm, p2_outline_norm, config.outlineColor, outlineThickness, false)) {
                    this._stopDrawing(); break;
                }

                // Draw particles (not eraser mode)
                if (Math.random() < config.particleChance) {
                    const particleX_norm = this._getRandomFloat(Math.min(p1_norm[0], p2_norm[0]), Math.max(p1_norm[0], p2_norm[0]));
                    const particleY_norm = Math.min(p1_norm[1], p2_norm[1]) - this._getRandomFloat(0.01, 0.08);
                    const particleSize_norm = this._getRandomFloat(config.particleSizeMin, config.particleSizeMax);

                    const particleFinalThickness = Math.max(1, config.particleThickness * particleSize_norm / config.particleSizeMin);
                    if (!this._sendAndRenderDrawCmd([particleX_norm, particleY_norm], [particleX_norm + 0.001, particleY_norm + 0.001], config.particleColor, particleFinalThickness, false)) {
                        this._stopDrawing(); break;
                    }
                }
                if (!this._drawingActive) break;
            }
            if (!this._drawingActive) break;

            // Apply delay per frame
            let currentFrameDelay = userFrameDelay;
            if (addRandomDelay) {
                currentFrameDelay += this._getRandomInt(0, 50);
            }
            if (currentFrameDelay > 0) await this._delay(currentFrameDelay);
        }

        this._stopDrawing();
        console.log(`${type} Animation finished.`);
    }
}

// ==UserScript==
// @name         Drawaria Canvas Expander & Circular Toggle (Integrated)
// @namespace    http://tampermonkey.net/
// @version      2025-05-23.9 // Incremented version to bring back individual canvas toggles
// @description  Integrates canvas expansion and circular features into QBit's Dynamic Visual Effects module, with individual toggles and a single floating reset button.
// @author       YouTubeDrawaria
// @match        https://drawaria.online/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// @grant        none
// @license      MIT
// @downloadURL https://update.greasyfork.org/scripts/537028/Drawaria%20Canvas%20Expander%20Toggle.user.js
// @updateURL https://update.greasyfork.org/scripts/537028/Drawaria%20Canvas%20Expander%20Toggle.meta.js
// ==/UserScript==

// Define localStorage keys outside the class as global constants for consistency
const LS_KEY_EXPAND = 'drawaria_expand_canvas_v3';
const LS_KEY_CIRCULAR = 'drawaria_canvas_circular_v1';

// --- Sub-Module 9: DynamicVisualEffectsTool ---
// Provides various automated visual effects and bot management functionalities,
// now including integrated canvas controls via a single floating reset button.
class DynamicVisualEffectsTool extends QBit {
    _canvas = null;
    _ctx = null;
    _socketStatus = 'disconnected'; // Initialize here to prevent undefined

    // Toggle 1: JoinExit Bots
    _joinExitBotsInterval = null;
    _joinExitBotCount = 0;
    _joinExitBotInstances = []; // Store references to BotClientInterface instances

    // Toggle 2 & 3 & 5 & 6: Visuals
    _visualEffectInterval = null;
    _visualEffectFrame = 0; // For tracking animation progress
    _visualEffectType = null; // 'bw', 'rainbow', 'linear-loading', 'circular-loading'
    _visualEffectColor = "#000000"; // For current rainbow color or other color effects

    // Toggle 4: JoinExit Text Bots
    _joinExitTextBotsInterval = null;
    _textSpamBots = [];
    _textSpamLoopTimeout = null; // Timeout for the 3-minute cycle

    _ui = {}; // UI elements references (now includes the reset canvas button)

    // Canvas Expansion/Circular Properties
    _originalCanvasWidth = 0;
    _originalCanvasHeight = 0;
    _originalCanvasAspectRatio = 0;

    constructor() {
        super("Dynamic Visual Effects", '<i class="fas fa-smile"></i>'); // Unique name and icon
        this._onStartup();
    }

    _onStartup() {
        this._canvas = document.getElementById('canvas');
        if (!this._canvas) {
            console.warn("Canvas del juego no encontrado al inicio para Dynamic Visual Effects. Se intentará revalidar con el observador.");
        } else {
            this._ctx = this._canvas.getContext('2d');
        }

        this._loadInterface(); // This creates the module's main buttons

        // NEW: Create the single "Reset Canvas" button
        this._createResetCanvasButton();

        // Read initial states from localStorage
        const storedExpandedState = localStorage.getItem(LS_KEY_EXPAND);
        const initialStateExpanded = storedExpandedState === 'true';

        const storedCircularState = localStorage.getItem(LS_KEY_CIRCULAR);
        const initialStateCircular = storedCircularState === 'true';

        // Set button active states based on localStorage for the individual canvas toggles
        if (this._ui['expand-canvas']) {
            if (initialStateExpanded) {
                this._ui['expand-canvas'].classList.add('active');
            } else {
                this._ui['expand-canvas'].classList.remove('active');
            }
        }
        if (this._ui['circular-canvas']) {
            if (initialStateCircular) {
                this._ui['circular-canvas'].classList.add('active');
            } else {
                this._ui['circular-canvas'].classList.remove('active');
            }
        }

        // Initialize and start the MutationObserver to apply canvas state when elements are ready
        this._initializeCanvasStateObserver(initialStateExpanded, initialStateCircular);

        this._setupEventListeners(); // This will attach click handlers.
        setInterval(() => this._updateConnectionStatus(), 1000); // Periodically check connection
        this.notify("info", "Módulo 'Dynamic Visual Effects' cargado.");
    }

    _loadInterface() {
        const container = domMake.Tree("div", { id: `${this.identifier}-container`, class: "module-section" });
        this.htmlElements.section.appendChild(container);

        // Connection Status
        container.appendChild(domMake.Tree("div", { class: "module-section-title" }, ["Estado de Conexión"]));
        const connectionStatusDiv = domMake.Tree("div", {}, [
            domMake.Tree("span", { id: `${this.identifier}-connectionStatus`, class: `module-status-indicator module-status-${this._socketStatus}` }),
            domMake.Tree("span", { id: `${this.identifier}-statusText` }, [this._socketStatus.charAt(0).toUpperCase() + this._socketStatus.slice(1)])
        ]);
        container.appendChild(connectionStatusDiv);

        // Toggles Section
        container.appendChild(domMake.Tree("div", { class: "module-section-title" }, ["Efectos Automatizados"]));

        const createToggleButton = (id, labelText) => {
            const wrapper = domMake.Tree("div", { class: "module-form-group" });
            const button = domMake.Button(labelText);
            button.id = `${this.identifier}-${id}`;
            button.classList.add('module-toggle-button');
            button.addEventListener('click', () => this[`_toggle${this._capitalize(this._camelCase(id))}`](button));
            wrapper.appendChild(button);
            container.appendChild(wrapper);
            this._ui[id] = button; // Store reference
        };

        createToggleButton('join-exit-bots', 'Join/Exit Bots');
        createToggleButton('black-white-visuals', 'Black & White Visuals');
        createToggleButton('rainbow-visuals', 'Rainbow Visuals');
        createToggleButton('join-exit-text-bots', 'Join/Exit Text Bots');
        createToggleButton('linear-loading-effect', 'Linear Loading Effect');
        createToggleButton('circular-loading-effect', 'Circular Loading Effect');

        // NEW: Canvas Control Toggles (Brought back)
        container.appendChild(domMake.Tree("div", { class: "module-section-title" }, ["Control de Canvas"]));
        createToggleButton('expand-canvas', 'Hacer Canvas Cuadrado');
        createToggleButton('circular-canvas', 'Hacer Canvas Circular');
    }

    // NEW: Function to create and place the "Reset Canvas" button (FLOATING)
    _createResetCanvasButton() {
        // Inject CSS for the new button
        const style = document.createElement('style');
        style.type = 'text/css';
        style.innerHTML = `
            #drawaria-reset-canvas-button {
                position: fixed; /* Make it float */
                bottom: 20px;    /* 20px from bottom */
                right: 20px;     /* 20px from right */
                z-index: 99999;  /* Ensure it's on top of almost everything */
                background-color: #f1f9f5;
                border: 1px solid #b0b5b9;
                border-radius: 5px;
                padding: 5px 10px;
                cursor: pointer;
                font-size: 14px;
                display: none; /* Hidden by default, managed by JS */
                align-items: center;
                gap: 6px;
                box-shadow: 0 1px 3px rgba(0,0,0,0.1);
                transition: background-color 0.2s ease;
                white-space: nowrap;
                color: #e74c3c; /* Red color to signify reset/danger */
                font-weight: bold;
            }
            #drawaria-reset-canvas-button:hover {
                background-color: #e0e0e0;
            }
        `;
        document.head.appendChild(style);

        const resetButton = domMake.Button('<span style="font-size: 16px;">🔄</span> Restablecer Canvas');
        resetButton.id = 'drawaria-reset-canvas-button';
        resetButton.title = "Restablece el tamaño y la forma original del canvas.";

        // Append the button directly to the body to make it float
        document.body.appendChild(resetButton);

        // Store reference and attach event listener
        this._ui['reset-canvas'] = resetButton;
        resetButton.addEventListener('click', () => this._toggleResetCanvas(resetButton));

        console.log("Botón 'Restablecer Canvas' creado e inyectado.");
        this._updateResetButtonVisibility(); // Set initial visibility based on saved state
    }

    // NEW: Update visibility of the reset button
    _updateResetButtonVisibility() {
        const resetButton = this._ui['reset-canvas'];
        if (!resetButton) return;

        const isExpanded = localStorage.getItem(LS_KEY_EXPAND) === 'true';
        const isCircular = localStorage.getItem(LS_KEY_CIRCULAR) === 'true';

        if (isExpanded || isCircular) {
            resetButton.style.display = 'flex'; // Show the button
        } else {
            resetButton.style.display = 'none'; // Hide the button
        }
    }

    // NEW: Toggle handler for the single "Reset Canvas" button
    _toggleResetCanvas(button) {
        // Reset both states to false
        this._setCanvasExpanded(false, false); // This will handle clearing localStorage and updating visibility

        // Also update the states of the individual toggles in the UI
        if (this._ui['expand-canvas']) this._ui['expand-canvas'].classList.remove('active');
        if (this._ui['circular-canvas']) this._ui['circular-canvas'].classList.remove('active');

        this.notify("info", "Canvas restablecido a su estado original.");
    }

    // Helper for converting kebab-case to PascalCase for method names
    _camelCase(kebabCaseString) {
        return kebabCaseString.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
    }

    _capitalize(camelCaseString) {
        return camelCaseString.charAt(0).toUpperCase() + camelCaseString.slice(1);
    }

    _setupEventListeners() {
        // Listeners attached in _loadInterface via createToggleButton
        // The reset button listener is attached in _createResetCanvasButton
    }

    _updateConnectionStatus(status) {
        if (typeof status === 'undefined') {
            const gameSocket = getGameSocket();
            status = gameSocket ? gameSocket._socketStatus : 'disconnected';
        }

        this._socketStatus = status;
        const statusIndicator = document.getElementById(`${this.identifier}-connectionStatus`);
        const statusText = document.getElementById(`${this.identifier}-statusText`);

        if (statusIndicator && statusText) {
            statusIndicator.className = `module-status-indicator module-status-${status}`;
            statusText.textContent = status ? status.charAt(0).toUpperCase() + status.slice(1) : 'Unknown';
        }
    }

    _delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }

    _sendAndRenderDrawCommand(socket, start_game_coords, end_game_coords, color, thickness_game_units, isEraser = false, algo = 0) {
        if (!socket || socket.readyState !== WebSocket.OPEN) {
            return false;
        }
        if (!this._canvas || !this._ctx) {
            console.warn("Canvas o contexto 2D no disponibles para dibujar.");
            return false;
        }

        const p1x_norm = Math.max(0, Math.min(1, start_game_coords[0] / 100));
        const p1y_norm = Math.max(0, Math.min(1, start_game_coords[1] / 100));
        const p2x_norm = Math.max(0, Math.min(1, end_game_coords[0] / 100));
        const p2y_norm = Math.max(0, Math.min(1, end_game_coords[1] / 100));

        let numThickness = parseFloat(thickness_game_units);
        if (isNaN(numThickness)) {
            this.notify("warning", "Invalid thickness provided, defaulting to 10:", thickness_game_units);
            numThickness = 10;
        }

        this._ctx.strokeStyle = color;
        this._ctx.lineWidth = numThickness * (this._canvas.width / 100);
        this._ctx.lineCap = 'round';
        this._ctx.lineJoin = 'round';

        if (isEraser) {
            this._ctx.globalCompositeOperation = 'destination-out';
        }
        this._ctx.beginPath();
        this._ctx.moveTo(p1x_norm * this._canvas.width, p1y_norm * this._canvas.height);
        this._ctx.lineTo(p2x_norm * this._canvas.width, p2y_norm * this._canvas.height);
        this._ctx.stroke();

        if (isEraser) {
            this._ctx.globalCompositeOperation = 'source-over';
        }

        const gT = isEraser ? numThickness : 0 - numThickness;
        socket.send(`42["drawcmd",0,[${p1x_norm.toFixed(4)},${p1y_norm.toFixed(4)},${p2x_norm.toFixed(4)},${p2y_norm.toFixed(4)},false,${gT},"${color}",0,0,{}]]`);
        return true;
    }

    async _clearCanvas() {
        const socket = getGameSocket();
        if (!socket) {
            this.notify("warning", "No hay conexión al juego para limpiar el lienzo.");
            return;
        }
        if (!this._ctx || !this._canvas) {
            console.warn("Canvas o contexto 2D no disponibles para limpiar.");
            return;
        }

        this.notify("info", "Limpiando lienzo...");
        this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);

        const clearThickness = 100;
        const clearColor = '#FFFFFF';
        const steps = 5;

        for (let i = 0; i <= steps; i++) {
            this._sendAndRenderDrawCommand(socket, [0, (i / steps) * 100], [100, (i / steps) * 100], clearColor, clearThickness, true);
            await this._delay(5);
            this._sendAndRenderDrawCommand(socket, [(i / steps) * 100, 0], [(i / steps) * 100, 100], clearColor, clearThickness, true);
            await this._delay(5);
        }
        this.notify("success", "Lienzo limpiado.");
    }

    _getRandomColor(saturation = 100, lightness = 50) {
        const hue = Math.floor(Math.random() * 360);
        return `hsl(${hue}, ${saturation}%, ${lightness}%)`;
    }

    _stopAllVisualEffects() {
        if (this._visualEffectInterval) {
            clearInterval(this._visualEffectInterval);
            this._visualEffectInterval = null;
            this._visualEffectType = null;
            this._visualEffectFrame = 0;
        }

        const visualEffectToggleIds = [
            'black-white-visuals', 'rainbow-visuals',
            'linear-loading-effect', 'circular-loading-effect'
        ];
        visualEffectToggleIds.forEach(id => {
            const btn = this._ui[id];
            if (btn) btn.classList.remove('active');
        });

        this._clearCanvas();
    }

    // --- Toggle 1: JoinExit Bots ---
    _toggleJoinExitBots(button) {
        if (this._joinExitBotsInterval) {
            clearInterval(this._joinExitBotsInterval);
            this._joinExitBotsInterval = null;
            this._disconnectBots(this._joinExitBotInstances);
            this._joinExitBotInstances = [];
            button.classList.remove('active');
            this.notify("info", "Bucle de Join/Exit Bots detenido. Bots desconectados.");
        } else {
            const botManager = QBit.findGlobal("BotClientManager");
            if (!botManager || !botManager.siblings || botManager.siblings.length === 0) {
                this.notify("error", "El módulo 'BotClientManager' no está activo. No se pueden controlar bots.");
                return;
            }
            const managerInstance = botManager.siblings[0];
            if (!getGameSocket()) {
                this.notify("warning", "No conectado a una sala de Drawaria. Los bots no podrán unirse/salir.");
                return;
            }

            button.classList.add('active');
            this._joinExitBotCount = 0;
            this._joinExitBotInstances = [];
            this.notify("info", "Iniciando bucle de Join/Exit Bots...");

            const joinDisconnectCycle = async () => {
                if (!this._joinExitBotsInterval) return;

                const maxBots = 3;
                const currentRoomId = document.querySelector("#invurl")?.value || window.location.pathname.replace('/room/', '');

                for (let i = 0; i < maxBots; i++) {
                    if (!this._joinExitBotsInterval) break;
                    const botInterface = managerInstance.createBotClientInterface();
                    if (botInterface && botInterface.bot) {
                        botInterface.setClientName(`𒐫𒐫𒐫𒐫𒐫𒐫𒐫𒐫𒐫𒐫𒐫𒐫${this._joinExitBotCount++}`);
                        botInterface.bot.enterRoom(currentRoomId);
                        this._joinExitBotInstances.push(botInterface.bot);
                        this.notify("info", `Bot ${botInterface.bot.name} uniéndose.`);
                        await this._delay(500);
                    }
                }
                await this._delay(1000);

                for (const bot of this._joinExitBotInstances) {
                    if (!this._joinExitBotsInterval) break;
                    if (bot.getReadyState()) {
                        bot.disconnect();
                        this.notify("info", `Bot ${bot.name} desconectándose.`);
                        await this._delay(500);
                    }
                }
                this._joinExitBotInstances = [];
                await this._delay(1000);
            };

            this._joinExitBotsInterval = setInterval(joinDisconnectCycle, 3000);
            joinDisconnectCycle();
        }
    }

    _disconnectBots(botInstances) {
        for (const bot of botInstances) {
            if (bot && typeof bot.disconnect === 'function' && bot.getReadyState()) {
                try {
                    bot.disconnect();
                    this.notify("info", `Bot ${bot.name} forzosamente desconectado.`);
                } catch(e) {
                    this.notify("error", `Error desconectando bot ${bot.name}: ${e.message}`);
                }
            }
        }
    }

    // --- Toggle 2: Black and White Visuals ---
    _toggleBlackWhiteVisuals(button) {
        if (this._visualEffectInterval && this._visualEffectType === 'bw') {
            clearInterval(this._visualEffectInterval);
            this._visualEffectInterval = null;
            this._visualEffectType = null;
            this._visualEffectFrame = 0;
            this._clearCanvas();
            button.classList.remove('active');
            this.notify("info", "Efecto Visual B/N detenido.");
        } else {
            const socket = getGameSocket();
            if (!socket) {
                this.notify("error", "No conectado a una sala de Drawaria. No se puede iniciar el efecto visual.");
                return;
            }
            this._stopAllVisualEffects();
            button.classList.add('active');
            this._visualEffectType = 'bw';
            this._visualEffectFrame = 0;
            this.notify("info", "Iniciando efecto Visual B/N...");

            this._visualEffectInterval = setInterval(() => {
                const color = (this._visualEffectFrame % 2 === 0) ? "#000000" : "#FFFFFF";
                this._sendAndRenderDrawCommand(socket, [0, 0], [100, 100], color, 2000);
                this._visualEffectFrame++;
            }, 100);
        }
    }

    // --- Toggle 3: Rainbow Visuals ---
    _toggleRainbowVisuals(button) {
        if (this._visualEffectInterval && this._visualEffectType === 'rainbow') {
            clearInterval(this._visualEffectInterval);
            this._visualEffectInterval = null;
            this._visualEffectType = null;
            this._visualEffectFrame = 0;
            this._clearCanvas();
            button.classList.remove('active');
            this.notify("info", "Efecto Visual Arcoíris detenido.");
        } else {
            const socket = getGameSocket();
            if (!socket) {
                this.notify("error", "No conectado a una sala de Drawaria. No se puede iniciar el efecto visual.");
                return;
            }
            this._stopAllVisualEffects();
            button.classList.add('active');
            this._visualEffectType = 'rainbow';
            this._visualEffectFrame = 0;
            this._visualEffectColor = this._getRandomColor();
            this.notify("info", "Iniciando efecto Visual Arcoíris...");

            this._visualEffectInterval = setInterval(() => {
                this._visualEffectColor = this._getRandomColor();
                this._sendAndRenderDrawCommand(socket, [0, 0], [100, 100], this._visualEffectColor, 2000);
                this._visualEffectFrame++;
            }, 100);
        }
    }

    // --- Toggle 4: JoinExit Text Bots ---
    _toggleJoinExitTextBots(button) {
        if (this._joinExitTextBotsInterval) {
            clearInterval(this._joinExitTextBotsInterval);
            clearTimeout(this._textSpamLoopTimeout);
            this._joinExitTextBotsInterval = null;
            this._textSpamLoopTimeout = null;
            this._disconnectBots(this._textSpamBots);
            this._textSpamBots = [];
            button.classList.remove('active');
            this.notify("info", "Bucle de Join/Exit Text Bots detenido.");
        } else {
            const botManager = QBit.findGlobal("BotClientManager");
            if (!botManager || !botManager.siblings || botManager.siblings.length === 0) {
                this.notify("error", "El módulo 'BotClientManager' no está activo. No se pueden controlar bots.");
                return;
            }
            const managerInstance = botManager.siblings[0];
            if (!getGameSocket()) {
                this.notify("warning", "No conectado a una sala de Drawaria. Los bots no podrán unirse/spamear.");
                return;
            }

            button.classList.add('active');
            this.notify("info", "Iniciando bucle de Join/Exit Text Bots y spam de 'Hola'...");

            const cycleTextSpamBots = async () => {
                if (!this._joinExitTextBotsInterval) return;

                const maxBots = 3;
                const currentRoomId = document.querySelector("#invurl")?.value || window.location.pathname.replace('/room/', '');

                this._disconnectBots(this._textSpamBots);
                this._textSpamBots = [];
                await this._delay(1000);

                for (let i = 0; i < maxBots; i++) {
                    if (!this._joinExitTextBotsInterval) break;
                    const botInterface = managerInstance.createBotClientInterface();
                    if (botInterface && botInterface.bot) {
                        botInterface.setClientName(`𒐫𒐫𒐫𒐫𒐫𒐫𒐫𒐫𒐫𒐫𒐫𒐫${i}`);
                        botInterface.bot.enterRoom(currentRoomId);
                        this._textSpamBots.push(botInterface.bot);
                        this.notify("info", `TextBot ${botInterface.bot.name} uniéndose.`);
                        await this._delay(500);
                    }
                }
                await this._delay(2000);

                let spamCount = 0;
                const spamDuration = 3 * 60 * 1000;
                const spamInterval = 700;

                const spamStartTime = Date.now();
                const spamLoop = setInterval(() => {
                    if (!this._joinExitTextBotsInterval || (Date.now() - spamStartTime) >= spamDuration) {
                        clearInterval(spamLoop);
                        this.notify("info", "Spam de 'Hola' finalizado por tiempo.");
                        if (this._joinExitTextBotsInterval) {
                            this._textSpamLoopTimeout = setTimeout(cycleTextSpamBots, 1000);
                        }
                        return;
                    }
                    for (const bot of this._textSpamBots) {
                        if (bot.getReadyState()) {
                            bot.emit("chatmsg", "𒐫𒐫𒐫𒐫𒐫𒐫𒐫𒐫𒐫𒐫𒐫𒐫");
                        }
                    }
                    spamCount++;
                }, spamInterval);
                this.notify("info", "Bots spameando 'Hola' durante 3 minutos...");
            };

            this._joinExitTextBotsInterval = setInterval(() => {}, 60 * 1000 * 5);
            cycleTextSpamBots();
        }
    }

    // --- Toggle 5: Linear Loading Effect ---
    _toggleLinearLoadingEffect(button) {
        if (this._visualEffectInterval && this._visualEffectType === 'linear-loading') {
            clearInterval(this._visualEffectInterval);
            this._visualEffectInterval = null;
            this._visualEffectType = null;
            this._visualEffectFrame = 0;
            this._clearCanvas();
            button.classList.remove('active');
            this.notify("info", "Efecto de Carga Lineal detenido.");
        } else {
            const socket = getGameSocket();
            if (!socket) {
                this.notify("error", "No conectado a una sala de Drawaria. No se puede iniciar el efecto visual.");
                return;
            }
            this._stopAllVisualEffects();
            button.classList.add('active');
            this._visualEffectType = 'linear-loading';
            this._visualEffectFrame = 0;
            this.notify("info", "Iniciando efecto de Carga Lineal...");

            const boxSize = 100;
            const boxStartX = 0;
            const boxStartY = 0;

            const borderThickness = 8;
            const borderColor = "#FFFFFF00";

            const segmentLength = 25;
            const segmentThickness = 25;

            const animationSpeed = 4;
            const delayPerFrame = 150;

            const drawLinearLoadingFrame = () => {
                if (!getGameSocket() || !this._visualEffectInterval || !this._canvas || !this._ctx) {
                    this._stopAllVisualEffects(); return;
                }

                this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);

                this._sendAndRenderDrawCommand(socket, [boxStartX, boxStartY], [boxStartX + boxSize, boxStartY], borderColor, borderThickness);
                this._sendAndRenderDrawCommand(socket, [boxStartX + boxSize, boxStartY], [boxStartX + boxSize, boxStartY + boxSize], borderColor, borderThickness);
                this._sendAndRenderDrawCommand(socket, [boxStartX + boxSize, boxStartY + boxSize], [boxStartX, boxStartY + boxSize], borderColor, borderThickness);
                this._sendAndRenderDrawCommand(socket, [boxStartX, boxStartY + boxSize], [boxStartX, boxStartY], borderColor, borderThickness);

                const pathLength = boxSize * 4;
                const currentPathPos = (this._visualEffectFrame * animationSpeed) % pathLength;

                let segmentStart = [0, 0];
                let segmentEnd = [0, 0];
                let segmentColor = this._getRandomColor();

                if (currentPathPos < boxSize) {
                    segmentStart = [boxStartX + currentPathPos, boxStartY];
                    segmentEnd = [boxStartX + currentPathPos + segmentLength, boxStartY];
                } else if (currentPathPos < boxSize * 2) {
                    const sidePos = currentPathPos - boxSize;
                    segmentStart = [boxStartX + boxSize, boxStartY + sidePos];
                    segmentEnd = [boxStartX + boxSize, boxStartY + sidePos + segmentLength];
                } else if (currentPathPos < boxSize * 3) {
                    const sidePos = currentPathPos - boxSize * 2;
                    segmentStart = [boxStartX + boxSize - sidePos, boxStartY + boxSize];
                    segmentEnd = [boxStartX + boxSize - sidePos - segmentLength, boxStartY + boxSize];
                } else {
                    const sidePos = currentPathPos - boxSize * 3;
                    segmentStart = [boxStartX, boxStartY + boxSize - sidePos];
                    segmentEnd = [boxStartX, boxStartY + boxSize - sidePos - segmentLength];
                }

                this._sendAndRenderDrawCommand(socket, segmentStart, segmentEnd, segmentColor, segmentThickness);

                this._visualEffectFrame++;
            };
            this._visualEffectInterval = setInterval(drawLinearLoadingFrame, delayPerFrame);
        }
    }

    // --- Toggle 6: Circular Loading Effect ---
    _toggleCircularLoadingEffect(button) {
        if (this._visualEffectInterval && this._visualEffectType === 'circular-loading') {
            clearInterval(this._visualEffectInterval);
            this._visualEffectInterval = null;
            this._visualEffectType = null;
            this._clearCanvas();
            button.classList.remove('active');
            this.notify("info", "Efecto de Carga Circular detenido.");
        } else {
            const socket = getGameSocket();
            if (!socket) {
                this.notify("error", "No conectado a una sala de Drawaria. No se puede iniciar el efecto.");
                return;
            }
            this._stopAllVisualEffects();
            button.classList.add('active');
            this._visualEffectType = 'circular-loading';
            this._visualEffectFrame = 0;
            this.notify("info", "Iniciando efecto de Carga Circular...");

            const centerX = 50;
            const centerY = 50;
            const maxRadius = 40;
            const squareSize = 10;
            const thickness = 2;
            const delayPerFrame = 250;

            const numSquaresPerSpiral = 1;
            const spiralTurns = 3;
            const totalSquarePath = numSquaresPerSpiral * spiralTurns;

            const drawCircularLoadingFrame = () => {
                if (!getGameSocket() || !this._visualEffectInterval || !this._canvas || !this._ctx) {
                    this._stopAllVisualEffects(); return;
                }

                this._ctx.clearRect(0, 0, this._canvas.width, this._canvas.height);

                const baseRotation = (this._visualEffectFrame * 0.1) % (Math.PI * 2);

                for (let i = 0; i < numSquaresPerSpiral; i++) {
                    for (let j = 0; j < spiralTurns; j++) {
                        const squareIndex = i + (j * numSquaresPerSpiral);

                        const currentProgress = (this._visualEffectFrame - squareIndex) % totalSquarePath;
                        if (currentProgress < 0) continue;

                        const radius = maxRadius * (currentProgress / totalSquarePath);
                        const angle = baseRotation + (currentProgress / numSquaresPerSpiral) * (Math.PI * 2);

                        const x = centerX + radius * Math.cos(angle);
                        const y = centerY + radius * Math.sin(angle);

                        const opacity = 1 - (currentProgress / totalSquarePath);
                        if (opacity <= 0) continue;

                        const color = this._getRandomColor();

                        const halfSize = squareSize / 2;
                        const points = [
                            [x - halfSize, y - halfSize],
                            [x + halfSize, y - halfSize],
                            [x + halfSize, y + halfSize],
                            [x - halfSize, y + halfSize]
                        ];

                        for (let k = 0; k < 4; k++) {
                            const p1 = points[k];
                            const p2 = points[(k + 1) % 4];
                            this._sendAndRenderDrawCommand(socket, p1, p2, color, thickness);
                        }
                    }
                }

                this._visualEffectFrame++;
            };
            this._visualEffectInterval = setInterval(drawCircularLoadingFrame, delayPerFrame);
        }
    }

    // --- Canvas Expansion/Circular Logic (Unified by _setCanvasExpanded) ---
    _setCanvasExpanded(isExpanded, isCircular) {
        const leftbar = document.getElementById('leftbar');
        const rightbar = document.getElementById('rightbar');
        const mainDiv = document.getElementById('main');
        if (!this._canvas) this._canvas = document.getElementById('canvas');

        if (!leftbar || !rightbar || !mainDiv || !this._canvas) {
            console.warn("Elementos principales de Drawaria (leftbar, rightbar, main, canvas) no encontrados para expandir/colapsar. Reintentando cuando los elementos estén listos.");
            return false;
        }
        this._ctx = this._canvas.getContext('2d');

        if (this._originalCanvasWidth === 0 || this._originalCanvasHeight === 0) {
            this._originalCanvasWidth = this._canvas.width;
            this._originalCanvasHeight = this._canvas.height;
            if (this._originalCanvasHeight > 0) {
                this._originalCanvasAspectRatio = this._originalCanvasWidth / this._originalCanvasHeight;
            } else {
                this._originalCanvasAspectRatio = 1;
                console.warn("La altura original del canvas es cero, asumiendo una relación de aspecto 1:1 como alternativa.");
            }
            console.log(`Dimensiones originales del Canvas inicializadas: ${this._originalCanvasWidth}x${this._originalCanvasHeight}, Relación de Aspecto: ${this._originalCanvasAspectRatio}`);
        }

        // Update active states of individual toggles
        if (this._ui['expand-canvas']) {
            if (isExpanded) this._ui['expand-canvas'].classList.add('active');
            else this._ui['expand-canvas'].classList.remove('active');
        }
        if (this._ui['circular-canvas']) {
            if (isCircular) this._ui['circular-canvas'].classList.add('active');
            else this._ui['circular-canvas'].classList.remove('active');
        }

        if (isExpanded) {
            leftbar.style.display = 'none';
            rightbar.style.display = 'none';

            this._canvas.style.flexGrow = '1';
            this._canvas.style.width = 'auto';
            this._canvas.style.height = 'auto';
            this._canvas.style.maxWidth = '100%';
            this._canvas.style.maxHeight = '100%';

            setTimeout(() => {
                const availableWidthForCanvas = mainDiv.clientWidth;
                const availableHeightForCanvas = mainDiv.clientHeight;

                let newCanvasWidth, newCanvasHeight;

                if (isCircular) {
                    const size = Math.min(availableWidthForCanvas, availableHeightForCanvas);
                    newCanvasWidth = size;
                    newCanvasHeight = size;
                    this._canvas.style.borderRadius = '50%';
                    this._canvas.style.overflow = 'hidden';
                    this._canvas.style.margin = 'auto';
                    this._canvas.style.display = 'block';
                    localStorage.setItem(LS_KEY_CIRCULAR, 'true'); // Save circular state
                } else if (this._originalCanvasAspectRatio > 0 && availableWidthForCanvas > 0 && availableHeightForCanvas > 0) {
                    const potentialHeightByWidth = Math.round(availableWidthForCanvas / this._originalCanvasAspectRatio);
                    const potentialWidthByHeight = Math.round(availableHeightForCanvas * this._originalCanvasAspectRatio);

                    if (potentialHeightByWidth <= availableHeightForCanvas) {
                        newCanvasWidth = availableWidthForCanvas;
                        newCanvasHeight = potentialHeightByWidth;
                    } else {
                        newCanvasHeight = availableHeightForCanvas;
                        newCanvasWidth = potentialWidthByHeight;
                    }
                    this._canvas.style.borderRadius = '';
                    this._canvas.style.overflow = '';
                    this._canvas.style.margin = '';
                    this._canvas.style.display = '';
                    localStorage.setItem(LS_KEY_CIRCULAR, 'false'); // Save circular state
                } else {
                    newCanvasWidth = availableWidthForCanvas;
                    newCanvasHeight = availableHeightForCanvas;
                    this._canvas.style.borderRadius = '';
                    this._canvas.style.overflow = '';
                    this._canvas.style.margin = '';
                    this._canvas.style.display = '';
                    localStorage.setItem(LS_KEY_CIRCULAR, 'false'); // Save circular state
                    console.warn("Los cálculos para las nuevas dimensiones del canvas no pudieron mantener la relación de aspecto, o el espacio disponible es cero. Puede causar distorsión si no es circular.");
                }

                this._canvas.width = newCanvasWidth;
                this._canvas.height = newCanvasHeight;

                this._canvas.style.width = newCanvasWidth + 'px';
                this._canvas.style.height = newCanvasHeight + 'px';
                this._canvas.style.flexGrow = '0';
                this._canvas.style.maxWidth = '';
                this._canvas.style.maxHeight = '';


                console.log(`Canvas expandido. Es Circular: ${isCircular}. Espacio disponible: ${availableWidthForCanvas}x${availableHeightForCanvas}. Nuevos atributos del Canvas: ${newCanvasWidth}x${newCanvasHeight}`);

                window.dispatchEvent(new Event('resize'));

                localStorage.setItem(LS_KEY_EXPAND, 'true');
                this._updateResetButtonVisibility(); // Update button visibility after state change

            }, 150);
            return true;

        } else { // Collapsed state (i.e., reset)
            leftbar.style.display = '';
            rightbar.style.display = '';

            this._canvas.width = this._originalCanvasWidth;
            this._canvas.height = this._originalCanvasHeight;

            this._canvas.style.flexGrow = '';
            this._canvas.style.width = '';
            this._canvas.style.height = '';
            this._canvas.style.borderRadius = '';
            this._canvas.style.overflow = '';
            this._canvas.style.maxWidth = '';
            this._canvas.style.maxHeight = '';
            this._canvas.style.margin = '';
            this._canvas.style.display = '';

            console.log("Canvas colapsado y dimensiones/estilos restaurados.");

            window.dispatchEvent(new Event('resize'));

            localStorage.setItem(LS_KEY_EXPAND, 'false');
            localStorage.setItem(LS_KEY_CIRCULAR, 'false'); // Always reset circular to false when collapsing
            this._updateResetButtonVisibility(); // Update button visibility after state change

            return true;
        }
    }

    // --- NEW TOGGLE METHODS FOR CANVAS CONTROLS (Brought back) ---
    _toggleExpandCanvas(button) {
        const isCurrentlyExpanded = button.classList.contains('active');
        const newExpandedState = !isCurrentlyExpanded;

        const circularButton = this._ui['circular-canvas'];
        const isCurrentCircular = circularButton ? circularButton.classList.contains('active') : false;

        // Note: _setCanvasExpanded will handle adding/removing 'active' class on `button`
        this._setCanvasExpanded(newExpandedState, isCurrentCircular);
        this.notify("info", `Área de dibujo ${newExpandedState ? 'expandida' : 'colapsada'}.`);
    }

    _toggleCircularCanvas(button) {
        const isCurrentlyCircular = button.classList.contains('active');
        const newCircularState = !isCurrentlyCircular;

        const expandButton = this._ui['expand-canvas'];
        const isCurrentExpanded = expandButton ? expandButton.classList.contains('active') : false;

        if (newCircularState && !isCurrentExpanded) {
            // If making circular, and not already expanded, force expansion
            if (expandButton) expandButton.classList.add('active'); // Visually activate expand button
            localStorage.setItem(LS_KEY_EXPAND, 'true'); // Persist expanded state
            this.notify("warning", "El canvas se ha expandido para aplicar el efecto circular.");
            this._setCanvasExpanded(true, newCircularState); // Call with forced expansion
        } else {
            // Note: _setCanvasExpanded will handle adding/removing 'active' class on `button`
            this._setCanvasExpanded(isCurrentExpanded, newCircularState);
        }
        this.notify("info", `Canvas ${newCircularState ? 'hecho circular' : 'restaurado a forma original'}.`);
    }

    // --- Observer to initialize canvas state when elements are ready ---
    _initializeCanvasStateObserver(initialExpanded, initialCircular) {
        const observer = new MutationObserver((mutationsList, observerInstance) => {
            const mainDiv = document.getElementById('main');
            const canvasElement = document.getElementById('canvas');
            if (mainDiv && mainDiv.style.display !== 'none' && canvasElement) {
                console.log("Observer: #main y canvas visibles. Aplicando estado inicial del canvas.");
                this._canvas = canvasElement;
                this._ctx = canvasElement.getContext('2d');

                // Pass both initial states to setCanvasExpanded
                if (this._setCanvasExpanded(initialExpanded, initialCircular)) {
                    observerInstance.disconnect();
                    console.log("Observer desconectado: estado inicial del canvas aplicado.");
                }
            }
        });

        setTimeout(() => {
            observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style'] });
            const mainDiv = document.getElementById('main');
            const canvasElement = document.getElementById('canvas');
            if (mainDiv && mainDiv.style.display !== 'none' && canvasElement) {
                console.log("Observer (verificación inmediata): #main y canvas ya visibles. Aplicando estado inicial del canvas.");
                this._canvas = canvasElement;
                this._ctx = canvasElement.getContext('2d');
                if (this._setCanvasExpanded(initialExpanded, initialCircular)) {
                     observer.disconnect();
                     console.log("Observer desconectado (verificación inmediata): estado inicial del canvas aplicado.");
                }
            }
        }, 500);

        window.addEventListener('load', () => {
            const mainDiv = document.getElementById('main');
            const canvasElement = document.getElementById('canvas');
            if (mainDiv && mainDiv.style.display !== 'none' && canvasElement) {
                console.log("Window loaded (fallback): #main/canvas visible. Aplicando estado inicial del canvas.");
                this._canvas = canvasElement;
                this._ctx = canvasElement.getContext('2d');
                this._setCanvasExpanded(initialExpanded, initialCircular);
            }
        });
    }
}

// --- Sub-Module 9: DraggableActionMenuTool ---
// Creates an action menu with standard Cube Engine UI integration and customizable spam toggles.
class DraggableActionMenuTool extends QBit {
    _reportSpamInterval = null;
    _rulesSpamInterval = null;
    _afkSpamInterval = null;
    _allSpamCombinedInterval = null; // Single interval for the "Spam TODO" button

    // Current delay values for each spam type (initialized with defaults)
    _reportCurrentDelay = 100; // Changed default to 100ms
    _rulesCurrentDelay = 100;  // Changed default to 100ms
    _afkCurrentDelay = 100;    // Changed default to 100ms
    _allSpamCurrentDelay = 400; // Default delay for "Spam TODO"

    _ui = {}; // Store UI element references

    constructor() {
        super("Action Menu", '<i class="fas fa-bolt"></i>'); // Unique name and icon
        this._onStartup();
    }

    _onStartup() {
        this._loadInterface(); // Build the UI using Cube Engine's system
        this._setupEventListeners();
        this.notify("info", "Módulo 'Action Menu' cargado.");
    }

    _loadInterface() {
        const container = domMake.Tree("div", { id: `${this.identifier}-container`, class: "module-section" });
        this.htmlElements.section.appendChild(container);

        container.appendChild(domMake.Tree("div", { class: "module-section-title" }, ["Acciones Directas"]));

        const createActionButton = (id, text, actionFn) => {
            const button = domMake.Button(text);
            button.id = `${this.identifier}-${id}`;
            button.addEventListener('click', actionFn);
            return button;
        };

        const createSpamToggleWithDelay = (id, labelText, defaultDelay) => {
            const wrapper = domMake.Tree("div", { class: "module-form-group", style: "display: flex; align-items: center; gap: 5px;" });
            const inputToggle = domMake.Tree("input", { type: "checkbox", id: `${this.identifier}-${id}SpamToggle` });
            const labelToggle = domMake.Tree("label", { for: `${this.identifier}-${id}SpamToggle`, style: "font-size: 0.9em; color: var(--CE-color); flex-shrink: 0;" }, [labelText]);
            const inputDelay = domMake.Tree("input", {
                type: "number",
                min: "50", // Minimum reasonable delay for individual spam
                max: "5000", // Maximum reasonable delay
                value: defaultDelay,
                id: `${this.identifier}-${id}SpamDelayInput`,
                style: "width: 70px; padding: 2px; border: 1px solid var(--CE-color); border-radius: .25rem; background-color: var(--CE-bg_color); color: var(--CE-color); text-align: center;"
            });
            const labelMs = domMake.Tree("label", { for: `${this.identifier}-${id}SpamDelayInput`, style: "font-size: 0.9em; color: var(--CE-color); flex-shrink: 0;" }, ["ms"]);

            wrapper.appendAll(inputToggle, labelToggle, inputDelay, labelMs);

            this._ui[`${id}SpamToggle`] = inputToggle; // Store toggle checkbox reference
            this._ui[`${id}SpamDelayInput`] = inputDelay; // Store delay input reference

            // Initial setting of current delays
            this[`_${id}CurrentDelay`] = defaultDelay;

            // Add event listeners for the toggle and the delay input
            inputToggle.addEventListener('change', (e) => this._toggleSpam(id, e.target.checked));
            inputDelay.addEventListener('input', (e) => {
                const newDelay = parseInt(e.target.value);
                // Clamp input value to valid range
                const clampedDelay = Math.max(50, Math.min(5000, isNaN(newDelay) ? defaultDelay : newDelay));
                e.target.value = clampedDelay; // Update input field to clamped value
                this[`_${id}CurrentDelay`] = clampedDelay;
                this.notify("info", `Delay para ${labelText} ajustado a ${clampedDelay}ms.`);

                // If spam is active, restart it with new delay
                if (this._ui[`${id}SpamToggle`].checked) {
                    this._stopIndividualSpamInterval(id); // Stop current
                    this._startIndividualSpamInterval(id);  // Start with new delay
                }
            });

            return wrapper;
        };


        // --- Report Button & Toggle ---
        const reportGroup = domMake.Row({class: "module-btn-group"});
        this._ui.reportButton = createActionButton('report-button', 'Reportar', this._handleReport.bind(this));
        reportGroup.appendChild(this._ui.reportButton);
        container.appendChild(reportGroup);
        container.appendChild(createSpamToggleWithDelay('report', 'Spam Reportar', this._reportCurrentDelay));

        // --- Rules Button & Toggle ---
        const rulesGroup = domMake.Row({class: "module-btn-group"});
        this._ui.rulesButton = createActionButton('rules-button', 'Reglas', this._handleRules.bind(this));
        rulesGroup.appendChild(this._ui.rulesButton);
        container.appendChild(rulesGroup);
        container.appendChild(createSpamToggleWithDelay('rules', 'Spam Reglas', this._rulesCurrentDelay));

        // --- AFK Button & Toggle ---
        const afkGroup = domMake.Row({class: "module-btn-group"});
        this._ui.afkButton = createActionButton('afk-button', 'Estados AFK', this._handleToggleAFK.bind(this));
        afkGroup.appendChild(this._ui.afkButton);
        container.appendChild(afkGroup);
        container.appendChild(createSpamToggleWithDelay('afk', 'Spam Estados AFK', this._afkCurrentDelay));

        // --- NEW: All Spam Toggle with its own delay input ---
        const allSpamGroup = domMake.Row({class: "module-btn-group", style: "margin-top: 15px; display: flex; align-items: center; gap: 5px;"});
        this._ui.allSpamToggle = domMake.Button('Spam TODO');
        this._ui.allSpamToggle.id = `${this.identifier}-allSpamToggle`;
        this._ui.allSpamToggle.classList.add('module-toggle-button');

        this._ui.allSpamDelayInput = domMake.Tree("input", {
            type: "number",
            min: "50", // Minimum reasonable delay for combined spam
            max: "2000", // Maximum reasonable delay for combined spam
            value: this._allSpamCurrentDelay,
            id: `${this.identifier}-allSpamDelayInput`,
            style: "width: 70px; padding: 2px; border: 1px solid var(--CE-color); border-radius: .25rem; background-color: var(--CE-bg_color); color: var(--CE-color); text-align: center;"
        });
        const allSpamLabelMs = domMake.Tree("label", { for: `${this.identifier}-allSpamDelayInput`, style: "font-size: 0.9em; color: var(--CE-color); flex-shrink: 0;" }, ["ms"]);


        allSpamGroup.appendAll(this._ui.allSpamToggle, this._ui.allSpamDelayInput, allSpamLabelMs);
        container.appendChild(allSpamGroup);
    }

    _setupEventListeners() {
        // Event listeners for individual toggles and delay inputs are set up in `createSpamToggleWithDelay`.

        // NEW: Event listener for the "Spam TODO" button
        this._ui.allSpamToggle.addEventListener('click', () => {
            const isCurrentlyActive = this._ui.allSpamToggle.classList.contains('active');
            this._toggleAllSpam(!isCurrentlyActive); // Toggle the state
        });

        // NEW: Event listener for the "Spam TODO" delay input
        this._ui.allSpamDelayInput.addEventListener('input', (e) => {
            const newDelay = parseInt(e.target.value);
            // Clamp input value to valid range
            const clampedDelay = Math.max(50, Math.min(2000, isNaN(newDelay) ? this._allSpamCurrentDelay : newDelay));
            e.target.value = clampedDelay; // Update input field to clamped value
            this._allSpamCurrentDelay = clampedDelay;
            this.notify("info", `Delay para Spam TODO ajustado a ${clampedDelay}ms.`);

            // If "Spam TODO" is active, restart it with new delay
            if (this._ui.allSpamToggle.classList.contains('active')) {
                this._toggleAllSpam(false); // Stop current
                this._toggleAllSpam(true);  // Start with new delay
            }
        });
    }

    // --- Action Handlers ---
    _handleReport() {
        const socket = getGameSocket();
        if (socket && socket.readyState === WebSocket.OPEN) {
            socket.send(`42["clientnotify",-1,2,["", "Generic Report"]]`);
            return true;
        }
        return false;
    }

    _handleRules() {
        const socket = getGameSocket();
        if (socket && socket.readyState === WebSocket.OPEN) {
            socket.send(`42["clientnotify",-1,100,[2]]`);
            return true;
        }
        return false;
    }

    _handleToggleAFK() {
        const socket = getGameSocket();
        if (socket && socket.readyState === WebSocket.OPEN) {
            socket.send(`42["playerafk"]`);
            return true;
        }
        return false;
    }

    // --- Helper to start individual spam interval ---
    _startIndividualSpamInterval(type) {
        let intervalIdField;
        let actionFunction;
        let currentDelay;

        switch(type) {
            case 'report':
                intervalIdField = '_reportSpamInterval';
                actionFunction = this._handleReport.bind(this);
                currentDelay = this._reportCurrentDelay;
                break;
            case 'rules':
                intervalIdField = '_rulesSpamInterval';
                actionFunction = this._handleRules.bind(this);
                currentDelay = this._rulesCurrentDelay;
                break;
            case 'afk':
                intervalIdField = '_afkSpamInterval';
                actionFunction = this._handleToggleAFK.bind(this);
                currentDelay = this._afkCurrentDelay;
                break;
            default:
                console.error(`DraggableActionMenuTool: Tipo de spam individual desconocido: ${type}`);
                return;
        }

        if (this[intervalIdField]) clearInterval(this[intervalIdField]); // Clear existing interval

        this[intervalIdField] = setInterval(() => {
            if (getGameSocket() && getGameSocket().readyState === WebSocket.OPEN) {
                actionFunction();
            } else {
                console.error(`DraggableActionMenuTool: Conexión perdida, deteniendo spam de ${type}.`);
                this._toggleSpam(type, false); // Stop spam if disconnected
            }
        }, currentDelay);
        this.notify("info", `Spam de ${type} iniciado (cada ${currentDelay}ms).`);
    }

    // --- Helper to stop individual spam interval ---
    _stopIndividualSpamInterval(type) {
        let intervalIdField;
        switch(type) {
            case 'report': intervalIdField = '_reportSpamInterval'; break;
            case 'rules': intervalIdField = '_rulesSpamInterval'; break;
            case 'afk': intervalIdField = '_afkSpamInterval'; break;
            default: return;
        }

        if (this[intervalIdField]) {
            clearInterval(this[intervalIdField]);
            this[intervalIdField] = null;
            this.notify("info", `Spam de ${type} detenido.`);
        }
    }

    // --- Main toggle logic for individual spam buttons ---
    _toggleSpam(type, enable) {
        // If "Spam TODO" is active, prevent individual spam toggles from working
        if (this._ui.allSpamToggle.classList.contains('active')) {
            this.notify("warning", "El spam TODO está activo. Desactívalo para controlar los spams individuales.");
            // Reset the individual toggle's UI state if it was just clicked
            if (this._ui[`${type}SpamToggle`] && this._ui[`${type}SpamToggle`].checked !== enable) {
                this._ui[`${type}SpamToggle`].checked = !enable; // Revert checkbox state
            }
            return;
        }

        if (enable) {
            this._startIndividualSpamInterval(type);
        } else {
            this._stopIndividualSpamInterval(type);
        }
    }

    // --- NEW: Toggle All Spam ---
    _toggleAllSpam(enable) {
        // First, stop all individual spams (and uncheck their UI toggles)
        ['report', 'rules', 'afk'].forEach(type => {
            this._stopIndividualSpamInterval(type); // Stop the interval
            if (this._ui[`${type}SpamToggle`]) {
                this._ui[`${type}SpamToggle`].checked = false; // Uncheck the UI toggle
            }
        });

        if (enable) {
            const socket = getGameSocket();
            if (!socket || socket.readyState !== WebSocket.OPEN) {
                this.notify("error", "No conectado para iniciar spam TODO.");
                this._ui.allSpamToggle.classList.remove('active'); // Un-activate button
                return;
            }

            this._ui.allSpamToggle.classList.add('active');
            this.notify("info", `Spam TODO iniciado (cada ${this._allSpamCurrentDelay}ms).`);

            // Start the combined interval
            if (this._allSpamCombinedInterval) clearInterval(this._allSpamCombinedInterval);
            this._allSpamCombinedInterval = setInterval(() => {
                if (getGameSocket() && getGameSocket().readyState === WebSocket.OPEN) {
                    this._handleReport();
                    this._handleRules();
                    this._handleToggleAFK();
                } else {
                    console.error("DraggableActionMenuTool: Conexión perdida, deteniendo spam TODO.");
                    this._toggleAllSpam(false); // Stop all spam if disconnected
                }
            }, this._allSpamCurrentDelay); // Use the specific delay for "Spam TODO"

        } else { // Disable all spam
            this._ui.allSpamToggle.classList.remove('active');
            this.notify("info", "Spam TODO detenido.");

            // Clear the combined interval
            if (this._allSpamCombinedInterval) {
                clearInterval(this._allSpamCombinedInterval);
                this._allSpamCombinedInterval = null;
            }
        }
    }
}
// --- Sub-Module 10: PermanentRoomBotTool ---
// Manages a bot to keep the room active and perform customizable actions.
class PermanentRoomBotTool extends QBit {
    _joinButton;
    _leaveButton;
    _statusIndicator;
    _statusText;

    _controlledBot = null;

    _activityToggle;
    _activityIntervalId = null;

    _autoReconnectToggle;
    _autoReconnectInterval = null;

    _autoAfkToggle;
    _autoAfkInterval = null;

    _spamMessageInput;
    _spamSendButton;
    _spamRepeatedlyToggle;
    _spamRepeatedlyInterval = null;

    constructor() {
        super("Permanent Room Bot", '<i class="fas fa-crosshairs"></i>');
        this._onStartup();
    }

    _onStartup() {
        this._loadInterface();
        this._setupEventListeners();
        setInterval(() => this._updateStatus(), 1000);
        //this.notify("info", "Módulo 'Permanent Room Bot' cargado.");
    }

    _loadInterface() {
        const container = domMake.Tree("div", { class: "permanent-room-section" });
        this.htmlElements.section.appendChild(container);

        container.appendChild(domMake.Tree("div", { class: "permanent-room-section-title" }, ["Mantener Sala Activa con Bot"]));

        container.appendChild(domMake.Tree("p", { style: "font-size: 0.85em; text-align: center; margin-bottom: 10px; color: var(--CE-color);" }, [
            "Asegúrate de crear y nombrar tu bot en la sección ",
            domMake.Tree("strong", {}, ["'BotClientManager'"]),
            " antes de usarlo aquí."
        ]));

        const buttonGroup = domMake.Row({ class: "permanent-room-btn-group" });
        this._joinButton = domMake.Button('<i class="fas fa-sign-in-alt"></i> Unir Bot a Sala Actual');
        this._joinButton.addEventListener('click', () => this._joinRoom());
        this._leaveButton = domMake.Button('<i class="fas fa-sign-out-alt"></i> Desconectar Bot');
        this._leaveButton.disabled = true;
        this._leaveButton.addEventListener('click', () => this._leaveRoom());
        buttonGroup.appendAll(this._joinButton, this._leaveButton);
        container.appendChild(buttonGroup);

        container.appendChild(domMake.Tree("div", { style: "margin-top: 10px;" }, [
            this._statusIndicator = domMake.Tree("span", { id: `${this.identifier}-statusIndicator`, class: `permanent-room-status-indicator permanent-room-status-disconnected` }),
            this._statusText = domMake.Tree("span", { id: `${this.identifier}-statusText` }, ["Desconectado"])
        ]));

        container.appendChild(domMake.Tree("div", { class: "permanent-room-section-title", style: "margin-top: 20px;" }, ["Actividad del Bot"]));

        const createToggle = (id, labelText, defaultChecked = false) => {
            const wrapper = domMake.Tree("div", { class: "module-form-group" });
            const input = domMake.Tree("input", { type: "checkbox", id: `${this.identifier}-${id}`, checked: defaultChecked });
            wrapper.appendAll(input, domMake.Tree("label", { for: `${this.identifier}-${id}`, style: "display: inline-block; margin-left: 5px; font-size: 0.9em; color: var(--CE-color);" }, [labelText]));
            container.appendChild(wrapper);
            return input;
        };

        this._activityToggle = createToggle('activityToggle', 'Bot Activo (Mover/Chatear)');
        this._autoReconnectToggle = createToggle('autoReconnectToggle', 'Auto-Reconectar si se Desconecta');
        this._autoAfkToggle = createToggle('autoAfkToggle', 'Auto-AFK (Alternar)');

        container.appendChild(domMake.Tree("div", { class: "permanent-room-section-title", style: "margin-top: 20px;" }, ["Spam de Mensajes"]));
        const spamControlsGroup = domMake.Row({ class: "permanent-room-btn-group" });
        this._spamMessageInput = domMake.Tree("input", { type: "text", placeholder: "Mensaje de spam...", class: "module-form-control" });
        this._spamSendButton = domMake.Button('<i class="fas fa-paper-plane"></i> Enviar');
        spamControlsGroup.appendAll(this._spamMessageInput, this._spamSendButton);
        container.appendChild(spamControlsGroup);

        this._spamRepeatedlyToggle = createToggle('spamRepeatedlyToggle', 'Enviar Repetidamente (700ms Delay)');
    }

    _setupEventListeners() {
        this._joinButton.addEventListener('click', () => this._joinRoom());
        this._leaveButton.addEventListener('click', () => this._leaveRoom());
        this._activityToggle.addEventListener('change', () => this._toggleActivity());
        this._autoReconnectToggle.addEventListener('change', () => this._toggleAutoReconnect());
        this._autoAfkToggle.addEventListener('change', () => this._toggleAutoAfk());
        this._spamSendButton.addEventListener('click', () => this._sendSpamMessage());
        this._spamRepeatedlyToggle.addEventListener('change', () => this._toggleSpamRepeatedly());
    }

    /**
     * Attempts to get an available bot instance from BotClientManager.
     * Prefers a bot not currently controlled, or the first available one.
     * @returns {object|null} A BotClient instance if available, otherwise null.
     */
    _getAvailableBot() {
        // CORRECTED: Access findGlobal as a static method of QBit
        const botManagerClass = QBit.findGlobal("BotClientManager");
        if (!botManagerClass || !botManagerClass.siblings || botManagerClass.siblings.length === 0) {
            this.notify("warning", "No hay un 'BotClientManager' activo. Por favor, crea un bot desde la sección 'BotClientManager'.");
            return null;
        }
        const managerInstance = botManagerClass.siblings[0];
        if (!managerInstance || !managerInstance.children) {
             this.notify("warning", "El 'BotClientManager' no tiene bots listos. Crea un bot primero.");
             return null;
        }

        const availableBotInterfaces = managerInstance.children.filter(bci => bci.bot);

        if (availableBotInterfaces.length === 0) {
            this.notify("error", "No se encontró ningún bot activo en 'BotClientManager'. Crea un bot primero.");
            return null;
        }

        let botToControl = null;

        if (this._controlledBot && this._controlledBot.getReadyState()) {
            botToControl = this._controlledBot;
        } else {
            botToControl = availableBotInterfaces.find(bci => bci.bot && bci.bot.getReadyState())?.bot;
        }

        if (botToControl) {
            this.notify("info", `Asignado el bot "${botToControl.name}" para la persistencia de sala.`);
        } else {
            this.notify("error", "No se encontró ningún bot conectado y disponible en 'BotClientManager'.");
        }
        return botToControl;
    }

    /**
     * Updates the UI elements based on the controlled bot's connection status.
     */
    _updateStatus() {
        const isConnected = this._controlledBot && this._controlledBot.getReadyState();
        this._statusIndicator.className = `permanent-room-status-indicator permanent-room-status-${isConnected ? 'connected' : 'disconnected'}`;
        this._statusText.textContent = isConnected ? `Conectado como "${this._controlledBot.name}"` : "Desconectado";
        this._joinButton.disabled = isConnected;
        this._leaveButton.disabled = !isConnected;
        this._activityToggle.disabled = !isConnected;
        this._autoReconnectToggle.disabled = !isConnected && !this._autoReconnectInterval;
        this._autoAfkToggle.disabled = !isConnected;
        this._spamMessageInput.disabled = !isConnected;
        this._spamSendButton.disabled = !isConnected || this._spamRepeatedlyToggle.checked;
        this._spamRepeatedlyToggle.disabled = !isConnected;

        if (!isConnected) {
            this._stopActivity();
            if (this._activityToggle.checked) {
                this._activityToggle.checked = false;
            }
            this._stopAutoAfk();
            if (this._autoAfkToggle.checked) {
                this._autoAfkToggle.checked = false;
            }
            this._stopSpamRepeatedly();
            if (this._spamRepeatedlyToggle.checked) {
                this._spamRepeatedlyToggle.checked = false;
            }

            if (this._autoReconnectToggle.checked && !this._autoReconnectInterval) {
                 this._startAutoReconnect();
            }
        }
    }

    /**
     * Initiates the process for the bot to join the current room.
     */
    _joinRoom() {
        this._controlledBot = this._getAvailableBot();
        if (!this._controlledBot) {
            return;
        }

        const currentPath = window.location.pathname;
        const roomMatch = currentPath.match(/\/room\/([a-f0-9.-]+(?:[.]\d+)?)/i);

        if (!roomMatch || !roomMatch[1]) {
            this.notify("error", "No estás en una sala de Drawaria. Navega a una sala primero (ej. drawaria.online/room/...).");
            return;
        }

        const roomIdToJoin = roomMatch[1];

        this.notify("info", `Intentando unir el bot "${this._controlledBot.name}" a la sala actual: ${roomIdToJoin}`);
        this._controlledBot.enterRoom(roomIdToJoin);

        setTimeout(() => {
            if (this._activityToggle.checked) {
                this._startActivity();
            }
            if (this._autoAfkToggle.checked) {
                this._startAutoAfk();
            }
            if (this._spamRepeatedlyToggle.checked) {
                this._startSpamRepeatedly();
            }
        }, 2000);
    }

    /**
     * Disconnects the controlled bot from the room.
     */
    _leaveRoom() {
        if (this._controlledBot) {
            this.notify("info", `Desconectando el bot "${this._controlledBot.name}" de la sala.`);
            this._controlledBot.disconnect();
        }
        this._controlledBot = null;
    }

    /**
     * Toggles the bot's periodic activity (moving/chatting).
     */
    _toggleActivity() {
        if (this._activityToggle.checked) {
            this._startActivity();
        } else {
            this._stopActivity();
        }
    }

    /**
     * Starts the bot's periodic activity.
     */
    _startActivity() {
        if (this._activityIntervalId) clearInterval(this._activityIntervalId);
        if (!this._controlledBot || !this._controlledBot.getReadyState()) {
            this.notify("warning", "El bot no está conectado y listo para iniciar la actividad.");
            this._activityToggle.checked = false;
            return;
        }
        this.notify("info", "Actividad del bot iniciada (mover/chatear).");
        const bot = this._controlledBot;

        this._activityIntervalId = setInterval(() => {
            if (!bot.getReadyState()) {
                this.notify("warning", `El bot "${bot.name}" se ha desconectado, deteniendo actividad.`);
                this._stopActivity();
                return;
            }

            const action = Math.random();
            if (action < 0.6) {
                const x = Math.random() * 100;
                const y = Math.random() * 100;
                bot.emit("moveavatar", x, y);
            } else {
                const messages = [
                    "Manteniendo sala...",
                    "Hola a todos!",
                    "Continuando el dibujo más tarde!",
                    "Zzz...",
                    "Un saludo!",
                    "La sala está segura 😉"
                ];
                const msg = messages[Math.floor(Math.random() * messages.length)];
                bot.emit("chatmsg", msg);
            }
        }, 15000 + Math.random() * 10000);
    }

    /**
     * Stops the bot's periodic activity.
     */
    _stopActivity() {
        if (this._activityIntervalId) {
            clearInterval(this._activityIntervalId);
            this._activityIntervalId = null;
            this.notify("info", "Actividad del bot detenida.");
        }
        if (this._activityToggle.checked) {
            this._activityToggle.checked = false;
        }
    }

    // --- New: Toggle for Auto-Reconectar si se Desconecta ---
    _toggleAutoReconnect() {
        if (this._autoReconnectToggle.checked) {
            this._startAutoReconnect();
        } else {
            this._stopAutoReconnect();
        }
    }

    _startAutoReconnect() {
        if (this._autoReconnectInterval) clearInterval(this._autoReconnectInterval);
        if (!this._controlledBot) {
            this.notify("warning", "");
            this._autoReconnectToggle.checked = false;
            return;
        }
        this.notify("info", `Auto-reconexión activada para "${this._controlledBot.name}".`);
        this._autoReconnectInterval = setInterval(() => {
            if (!this._controlledBot.getReadyState()) {
                this.notify("info", `Intentando reconectar bot "${this._controlledBot.name}"...`);
                this._controlledBot.reconnect();
            }
        }, 30000);
    }

    _stopAutoReconnect() {
        if (this._autoReconnectInterval) {
            clearInterval(this._autoReconnectInterval);
            this._autoReconnectInterval = null;
            this.notify("info", "Auto-reconexión desactivada.");
        }
        if (this._autoReconnectToggle.checked) {
            this._autoReconnectToggle.checked = false;
        }
    }

    // --- New: Toggle for Auto-AFK (Alternar) ---
    _toggleAutoAfk() {
        if (this._autoAfkToggle.checked) {
            this._startAutoAfk();
        } else {
            this._stopAutoAfk();
        }
    }

    _startAutoAfk() {
        if (this._autoAfkInterval) clearInterval(this._autoAfkInterval);
        if (!this._controlledBot || !this._controlledBot.getReadyState()) {
            this.notify("warning", "Bot no conectado para AFK automático.");
            this._autoAfkToggle.checked = false;
            return;
        }
        this.notify("info", "AFK automático activado (el bot alternará su estado AFK).");
        this._autoAfkInterval = setInterval(() => {
            if (this._controlledBot.getReadyState()) {
                this._controlledBot.emit("playerafk");
                this.notify("info", `Bot "${this._controlledBot.name}" alternó estado AFK.`);
            }
        }, 60000);
    }

    _stopAutoAfk() {
        if (this._autoAfkInterval) {
            clearInterval(this._autoAfkInterval);
            this._autoAfkInterval = null;
            this.notify("info", "AFK automático desactivado.");
        }
        if (this._autoAfkToggle.checked) {
            this._autoAfkToggle.checked = false;
        }
    }

    // --- New: Message Spam Control ---
    _sendSpamMessage() {
        const message = this._spamMessageInput.value.trim();
        if (!message) {
            this.notify("warning", "El mensaje de spam no puede estar vacío.");
            return;
        }
        if (!this._controlledBot || !this._controlledBot.getReadyState()) {
            this.notify("error", "Bot no conectado para enviar mensaje.");
            return;
        }
        this._controlledBot.emit("chatmsg", message);
        this.notify("info", `Bot "${this._controlledBot.name}" envió: "${message}"`);
    }

    _toggleSpamRepeatedly() {
        if (this._spamRepeatedlyToggle.checked) {
            this._startSpamRepeatedly();
        } else {
            this._stopSpamRepeatedly();
        }
    }

    _startSpamRepeatedly() {
        if (this._spamRepeatedlyInterval) clearInterval(this._spamRepeatedlyInterval);
        const message = this._spamMessageInput.value.trim();
        if (!message) {
            this.notify("warning", "El mensaje de spam no puede estar vacío para enviar repetidamente.");
            this._spamRepeatedlyToggle.checked = false;
            return;
        }
        if (!this._controlledBot || !this._controlledBot.getReadyState()) {
            this.notify("error", "Bot no conectado para spam repetido.");
            this._spamRepeatedlyToggle.checked = false;
            return;
        }

        this._spamSendButton.disabled = true;

        this.notify("info", `Spam repetido iniciado para "${message}".`);
        this._spamRepeatedlyInterval = setInterval(() => {
            if (this._controlledBot.getReadyState()) {
                this._controlledBot.emit("chatmsg", message);
            }
        }, 700);
    }

    _stopSpamRepeatedly() {
        if (this._spamRepeatedlyInterval) {
            clearInterval(this._spamRepeatedlyInterval);
            this._spamRepeatedlyInterval = null;
            this.notify("info", "Spam repetido detenido.");
        }
        this._spamSendButton.disabled = false;
        if (this._spamRepeatedlyToggle.checked) {
            this._spamRepeatedlyToggle.checked = false;
        }
    }
}


// --- Sub-Module X: GeometryDashCubeOnlineDrawer (Renamed to GDToolsSuite) ---
// Allows users to draw Geometry Dash icons and objects on the Drawaria canvas.
class GeometryDashCubeOnlineDrawer extends QBit { // Renamed class for broader scope
  _currentCategory = 'cubes'; // Tracks active category: 'cubes', 'ships', 'balls', etc.
  _currentIconId = 1; // Current ID within the active category
  _canvasBrushImg = null; // Stores the loaded GD icon image for drawing (original, unrotated)
  _currentRotation = 0; // NEW: Current rotation for editor blocks (0, 90, 180, 270)
  _ui = {}; // UI elements references
  _mainCanvas = null; // Reference to the main game canvas
  _canvasClickHandler = null; // Stores the bound click handler
  _canvasClickHandlerAttached = false; // Flag to track if handler is attached
  _isActive = false; // Module active state
  _isDrawingIcon = false; // Flag to prevent multiple icon drawings simultaneously

  // Icon Categories Data
  _categories = {
    cubes: { prefix: 'cube_', maxId: 485, icon: 'fas fa-cube' },
    ships: { prefix: 'ship_', maxId: 169, icon: 'fas fa-plane' },
    balls: { prefix: 'ball_', maxId: 118, icon: 'fas fa-circle' },
    ufos: { prefix: 'ufo_', maxId: 149, icon: 'fas fa-space-shuttle' },
    waves: { prefix: 'wave_', maxId: 96, icon: 'fas fa-wave-square' },
    robots: { prefix: 'robot_', maxId: 68, icon: 'fas fa-robot' },
    spiders: { prefix: 'spider_', maxId: 69, icon: 'fas fa-spider' },
    swings: { prefix: 'swing_', maxId: 43, icon: 'fas fa-wind' },
    jetpacks: { prefix: 'jetpack_', maxId: 8, icon: 'fas fa-rocket' }
  };

  _difficulties = {
    easy: 'easy.png', normal: 'normal.png', hard: 'hard.png', harder: 'harder.png',
    insane: 'insane.png', 'demon-easy': 'demon-easy.png', 'demon-medium': 'demon-medium.png',
    'demon-hard': 'demon-hard.png', 'demon-insane': 'demon-insane.png', 'demon-extreme': 'demon-extreme.png',
    unrated: 'unrated.png', auto: 'auto.png'
  };
  _difficultyModifiers = ['-featured', '-epic', '-legendary', '-mythic'];

  _extras = {
    like: 'like.png', star: 'star.png', moon: 'moon.png', coin: 'coin.png',
    silvercoin: 'silvercoin.png', download: 'download.png', youtube: 'youtube.png',
    time: 'time.png', orbs: 'orbs.png', refresh: 'refresh.png', magnify: 'magnify.png'
  };

  _gauntlets = {
    fire: 'fire.png', ice: 'ice.png', poison: 'poison.png', shadow: 'shadow.png',
    lava: 'lava.png', bonus: 'bonus.png', chaos: 'chaos.png', demon: 'demon.png',
    time: 'time.png', crystal: 'crystal.png', magic: 'magic.png', spike: 'spike.png',
    monster: 'monster.png', doom: 'doom.png', death: 'death.png', forest: 'forest.png',
    force: 'force.png', water: 'water.png', haunted: 'haunted.png', power: 'power.png',
    halloween: 'halloween.png', treasure: 'treasure.png', inferno: 'inferno.png', portal: 'portal.png',
    strange: 'strange.png', fantasy: 'fantasy.png', christmas: 'christmas.png', mystery: 'mystery.png',
    cursed: 'cursed.png', cyborg: 'cyborg.png', castle: 'castle.png', world: 'world.png',
    galaxy: 'galaxy.png', universe: 'universe.png', discord: 'discord.png', ncs_i: 'ncs_i.png',
    ncs_ii: 'ncs_ii.png', space: 'space.png', cosmos: 'cosmos.png'
  };

  // --- NEW: Editor Blocks Data ---
  _editorBlocks = {
    spike: 'https://static.wikia.nocookie.net/geometry-dash-level-editor/images/c/cc/Retouch_2024112913572713.png',
    block: 'https://raw.githubusercontent.com/GDColon/GDBrowser/refs/heads/master/assets/objects/blocks/classic.png',
    portal: 'https://static.wikia.nocookie.net/geometry-dash-fan-ideas/images/d/de/CubePortal.png'
  };

  // NEW: Default optimization settings specifically for editor blocks
  // These are more conservative to prevent disconnections.
  _editorBlockDefaultOptimizations = {
    delayPerSegment: 100, // Higher delay per segment
    delayPerRow: 200,    // Higher delay per row
    qualityFactor: 3,    // Moderate quality factor (larger steps)
    targetSize: 40       // Smaller target drawing size (e.g., 40px)
  };

  constructor() {
    super("GD Tools", '<i class="fas fa-cube"></i>'); // Broader icon for the suite
    this._onStartup();
  }

  _onStartup() {
    this._mainCanvas = document.getElementById("canvas");
    if (!this._mainCanvas) {
      this.notify("warning", "Canvas principal del juego no encontrado al inicio. Algunas funciones se habilitarán más tarde.");
    }

    this._loadInterface();
    this._setupEventListeners();
    this._loadIconImage('cubes', 1); // Load initial cube image
    this._setModuleActive(false); // Set initial state
    this.notify("info", "Módulo 'GD Tools Suite' listo para pintar iconos y objetos.");
  }

  _loadInterface() {
    const container = domMake.Tree("div", { id: `${this.identifier}-container`, class: "module-section" });
    this.htmlElements.section.appendChild(container);

    // Module Toggle Button
    const moduleToggleGroup = domMake.Tree("div", { class: "module-btn-group", style: "margin-bottom:10px;" });
    this._ui.moduleToggleButton = domMake.Button('<i class="fas fa-power-off"></i> Activar Módulo');
    this._ui.moduleToggleButton.classList.add('module-toggle-button');
    this._ui.moduleToggleButton.addEventListener('click', () => this._toggleModuleActive());
    moduleToggleGroup.appendChild(this._ui.moduleToggleButton);
    container.appendChild(moduleToggleGroup);

    container.appendChild(domMake.Tree("div", { class: "module-section-title" }, ["GD Cubes & Objects Drawer"]));

    // --- Category Navigation Buttons ---
    const categoryNav = domMake.Tree("div", { class: "module-btn-group", style: "flex-wrap: wrap; margin-bottom: 15px;" });
    ['cubes', 'ships', 'balls', 'ufos', 'waves', 'robots', 'spiders', 'swings', 'jetpacks', 'difficulties', 'extras', 'gauntlets', 'editor_blocks'].forEach(catKey => {
      let iconClass = this._categories[catKey]?.icon || 'fas fa-question'; // Default icon
      if (catKey === 'difficulties') iconClass = 'fas fa-star';
      if (catKey === 'extras') iconClass = 'fas fa-plus';
      if (catKey === 'gauntlets') iconClass = 'fas fa-dice';
      if (catKey === 'editor_blocks') iconClass = 'fas fa-th-large'; // NEW: Icon for editor blocks

      const button = domMake.Button(`<i class="${iconClass}"></i> ${catKey.charAt(0).toUpperCase() + catKey.slice(1).replace('_', ' ')}`);
      button.classList.add('category-nav-button'); // Custom class for category nav
      button.addEventListener('click', () => this._changeCategory(catKey));
      categoryNav.appendChild(button);
      this._ui[`${catKey}NavButton`] = button; // Store reference
    });
    container.appendChild(categoryNav);

    // --- Preview Canvas (moved here to be universally accessible) ---
    // The preview canvas is shared across all categories to show the selected image.
    const previewContainer = domMake.Tree("div", { style: "display:flex;align-items:center;justify-content:center;gap:10px;margin-bottom:10px;" });
    this._ui.previewCanvas = domMake.Tree("canvas", { id: `${this.identifier}-previewCanvas`, width: "96", height: "96", style: "border:1px solid var(--CE-color);background:#222;" });
    previewContainer.appendChild(this._ui.previewCanvas);
    container.appendChild(previewContainer);

    // --- Common Controls (for icons - hidden when editor_blocks is active) ---
    this._ui.commonControlsSection = domMake.Tree('div', { id: `${this.identifier}-common-controls` });
    this._ui.commonControlsSection.innerHTML = `
        <div style="display:flex;align-items:center;justify-content:center;gap:10px;margin-bottom:10px;">
            <button id="${this.identifier}-prevBtn" class="artfx-button special">←</button>
            <button id="${this.identifier}-nextBtn" class="artfx-button special">→</button>
        </div>
        <div id="${this.identifier}-info" style="text-align:center;margin-top:5px;font-size:0.9em;color:var(--CE-color);"></div>
        <div class="module-btn-group" style="flex-wrap:wrap;margin-top:15px;">
            <input type="number" id="${this.identifier}-iconIdInput" min="1" value="1" placeholder="ID del Icono" class="module-form-control" style="flex:1 1 60%;">
            <button id="${this.identifier}-acceptIconBtn" class="artfx-button" style="flex:1 1 35%;">Aceptar ID</button>
            <button id="${this.identifier}-randomIconBtn" class="artfx-button special" style="width:100%;">Icono Random</button>
        </div>
    `;
    container.appendChild(this._ui.commonControlsSection);
    // Get references for common controls
    this._ui.prevBtn = this._ui.commonControlsSection.querySelector(`#${this.identifier}-prevBtn`);
    this._ui.nextBtn = this._ui.commonControlsSection.querySelector(`#${this.identifier}-nextBtn`);
    this._ui.info = this._ui.commonControlsSection.querySelector(`#${this.identifier}-info`);
    this._ui.iconIdInput = this._ui.commonControlsSection.querySelector(`#${this.identifier}-iconIdInput`);
    this._ui.acceptIconBtn = this._ui.commonControlsSection.querySelector(`#${this.identifier}-acceptIconBtn`);
    this._ui.randomIconBtn = this._ui.commonControlsSection.querySelector(`#${this.identifier}-randomIconBtn`);


    // --- Difficulties Section ---
    this._ui.difficultiesSection = domMake.Tree('div', { id: `${this.identifier}-difficulties-section`, style: "display:none;" }); // Hidden by default
    this._ui.difficultiesSection.innerHTML = `
        <div class="module-section-title" style="margin-top:15px;">Dificultades</div>
        <div class="module-btn-group" style="flex-wrap:wrap;">
            ${Object.keys(this._difficulties).map(dKey => `<button class="artfx-button difficulty-button" data-diff="${dKey}">${dKey.charAt(0).toUpperCase() + dKey.slice(1).replace('-', ' ')}</button>`).join('')}
        </div>
        <div class="module-section-title" style="margin-top:15px;">Modificadores</div>
        <div class="module-btn-group" style="flex-wrap:wrap;">
            ${this._difficultyModifiers.map(mod => `
                <input type="checkbox" id="${this.identifier}-mod${mod.replace('-', '')}Toggle" class="difficulty-modifier-toggle" data-mod="${mod}">
                <label for="${this.identifier}-mod${mod.replace('-', '')}Toggle" style="margin-right:10px;font-size:0.9em;color:var(--CE-color);">${mod.slice(1).charAt(0).toUpperCase() + mod.slice(2)}</label>
            `).join('')}
            <button id="${this.identifier}-clearModsBtn" class="artfx-button danger" style="flex:1 1 auto;">Limpiar Modificadores</button>
        </div>
    `;
    container.appendChild(this._ui.difficultiesSection);
    this._ui.difficultyButtons = this._ui.difficultiesSection.querySelectorAll('.difficulty-button');
    this._ui.difficultyModToggles = this._ui.difficultiesSection.querySelectorAll('.difficulty-modifier-toggle');
    this._ui.clearModsBtn = this._ui.difficultiesSection.querySelector(`#${this.identifier}-clearModsBtn`);


    // --- Extras Section ---
    this._ui.extrasSection = domMake.Tree('div', { id: `${this.identifier}-extras-section`, style: "display:none;" }); // Hidden by default
    this._ui.extrasSection.innerHTML = `
        <div class="module-section-title" style="margin-top:15px;">Extras</div>
        <div class="module-btn-group" style="flex-wrap:wrap;">
            ${Object.keys(this._extras).map(eKey => `<button class="artfx-button extra-button" data-extra="${eKey}">${eKey.charAt(0).toUpperCase() + eKey.slice(1)}</button>`).join('')}
        </div>
    `;
    container.appendChild(this._ui.extrasSection);
    this._ui.extraButtons = this._ui.extrasSection.querySelectorAll('.extra-button');


    // --- Gauntlets Section ---
    this._ui.gauntletsSection = domMake.Tree('div', { id: `${this.identifier}-gauntlets-section`, style: "display:none;" }); // Hidden by default
    this._ui.gauntletsSection.innerHTML = `
        <div class="module-section-title" style="margin-top:15px;">Gauntlets</div>
        <div class="module-btn-group" style="flex-wrap:wrap;">
            ${Object.keys(this._gauntlets).map(gKey => `<button class="artfx-button gauntlet-button" data-gauntlet="${gKey}">${gKey.charAt(0).toUpperCase() + gKey.slice(1)}</button>`).join('')}
        </div>
    `;
    container.appendChild(this._ui.gauntletsSection);
    this._ui.gauntletButtons = this._ui.gauntletsSection.querySelectorAll('.gauntlet-button');

    // --- NEW: Editor Blocks Section (Custom Controls) ---
    this._ui.editorBlocksSection = domMake.Tree('div', { id: `${this.identifier}-editorblocks-section`, style: "display:none;" }); // Hidden by default
    this._ui.editorBlocksSection.innerHTML = `
        <div class="module-section-title" style="margin-top:15px;">Bloques del Editor</div>
        <div class="module-btn-group" style="flex-wrap:wrap;">
            ${Object.keys(this._editorBlocks).map(ebKey => `<button class="artfx-button editor-block-button" data-eblock="${ebKey}">${ebKey.charAt(0).toUpperCase() + ebKey.slice(1)}</button>`).join('')}
        </div>
        <div class="module-btn-group" style="margin-top:10px;">
            <button id="${this.identifier}-rotateBlockBtn" class="artfx-button special"><i class="fas fa-sync-alt"></i> Rotar Objeto</button>
        </div>
    `;
    container.appendChild(this._ui.editorBlocksSection);
    this._ui.editorBlockButtons = this._ui.editorBlocksSection.querySelectorAll('.editor-block-button');
    this._ui.rotateBlockBtn = this._ui.editorBlocksSection.querySelector(`#${this.identifier}-rotateBlockBtn`);

    // --- NEW: Editor Block Specific Optimization Settings ---
    this._ui.editorBlockOptimizationSection = domMake.Tree('div', { id: `${this.identifier}-eb-opt-section`, style: "display:none;" }); // Hidden by default
    this._ui.editorBlockOptimizationSection.innerHTML = `
        <div class="module-section-title" style="margin-top:15px;">Optimización (Bloques Editor)</div>
        <div class="module-btn-group" style="flex-wrap:wrap;">
            <input type="number" id="${this.identifier}-ebDelayPerSegmentInput" min="0" max="500" value="${this._editorBlockDefaultOptimizations.delayPerSegment}" title="Retraso por segmento de línea (ms)" class="module-form-control">
            <label for="${this.identifier}-ebDelayPerSegmentInput">Delay Seg. (ms):</label>

            <input type="number" id="${this.identifier}-ebDelayPerRowInput" min="0" max="1000" value="${this._editorBlockDefaultOptimizations.delayPerRow}" title="Retraso por fila de píxeles (ms)" class="module-form-control">
            <label for="${this.identifier}-ebDelayPerRowInput">Delay Fila (ms):</label>

            <input type="number" id="${this.identifier}-ebQualityFactorInput" min="1" max="10" value="${this._editorBlockDefaultOptimizations.qualityFactor}" title="Factor de calidad (1=mejor, 10=peor, más rápido)" class="module-form-control">
            <label for="${this.identifier}-ebQualityFactorInput">Calidad (1-10):</label>

            <input type="number" id="${this.identifier}-ebTargetSizeInput" min="10" max="100" value="${this._editorBlockDefaultOptimizations.targetSize}" title="Tamaño objetivo del objeto en píxeles (en el canvas)" class="module-form-control">
            <label for="${this.identifier}-ebTargetSizeInput">Tamaño Obj. (px):</label>
        </div>
    `;
    container.appendChild(this._ui.editorBlockOptimizationSection);
    // Get references for editor block optimization controls
    this._ui.ebDelaySegmentInput = this._ui.editorBlockOptimizationSection.querySelector(`#${this.identifier}-ebDelayPerSegmentInput`);
    this._ui.ebDelayPerRowInput = this._ui.editorBlockOptimizationSection.querySelector(`#${this.identifier}-ebDelayPerRowInput`);
    this._ui.ebQualityFactorInput = this._ui.editorBlockOptimizationSection.querySelector(`#${this.identifier}-ebQualityFactorInput`);
    this._ui.ebTargetSizeInput = this._ui.editorBlockOptimizationSection.querySelector(`#${this.identifier}-ebTargetSizeInput`);


    // --- Global Optimization Settings (for all other categories) ---
    container.appendChild(domMake.Tree("div", { class: "module-section-title", style: "margin-top:15px;" }, ["Optimización de Dibujo (General)"]));
    const optSettingsGrid = domMake.Tree("div", { class: "module-btn-group", style: "flex-wrap:wrap;" });

    this._ui.delayPerSegmentInput = domMake.Tree("input", { type: "number", min: "0", max: "50", value: "40", title: "Retraso por segmento de línea (ms)", class: "module-form-control" });
    this._ui.delayPerSegmentLabel = domMake.Tree("label", { for: `${this.identifier}-delayPerSegmentInput` }, ["Delay Seg. (ms):"]);
    optSettingsGrid.append(domMake.Tree("div", { style: "flex:1 1 48%;" }, [this._ui.delayPerSegmentLabel, this._ui.delayPerSegmentInput]));

    this._ui.delayPerRowInput = domMake.Tree("input", { type: "number", min: "0", max: "200", value: "50", title: "Retraso por fila de píxeles (ms)", class: "module-form-control" });
    this._ui.delayPerRowLabel = domMake.Tree("label", { for: `${this.identifier}-delayPerRowInput` }, ["Delay Fila (ms):"]);
    optSettingsGrid.append(domMake.Tree("div", { style: "flex:1 1 48%;" }, [this._ui.delayPerRowLabel, this._ui.delayPerRowInput]));

    this._ui.qualityFactorInput = domMake.Tree("input", { type: "number", min: "1", max: "5", value: "4", title: "Factor de calidad (1=mejor, 5=peor, más rápido)", class: "module-form-control" });
    this._ui.qualityFactorLabel = domMake.Tree("label", { for: `${this.identifier}-qualityFactorInput` }, ["Calidad (1-5):"]);
    optSettingsGrid.append(domMake.Tree("div", { style: "flex:1 1 48%;" }, [this._ui.qualityFactorLabel, this._ui.qualityFactorInput]));

    this._ui.autoClearBeforeDrawToggle = domMake.Tree("input", { type: "checkbox", id: `${this.identifier}-autoClearToggle`, checked: false, title: "Limpiar el canvas antes de dibujar cada cubo" });
    this._ui.autoClearBeforeDrawLabel = domMake.Tree("label", { for: `${this.identifier}-autoClearToggle` }, ["Auto-Limpiar antes de dibujar"]);
    optSettingsGrid.append(domMake.Tree("div", { style: "flex:1 1 48%; display:flex; align-items:center;" }, [this._ui.autoClearBeforeDrawToggle, this._ui.autoClearBeforeDrawLabel]));

    container.appendChild(optSettingsGrid);

    // Instructions/Status
    this._ui.status = domMake.Tree("div", { style: "text-align:center;margin-top:10px;font-size:0.85em;color:var(--info);" }, ["Haz clic en el canvas principal para dibujar el icono."]);
    container.appendChild(this._ui.status);
  }

  _setupEventListeners() {
    // Common Icon Navigation
    this._ui.prevBtn.addEventListener('click', () => this._changeIconId(-1));
    this._ui.nextBtn.addEventListener('click', () => this._changeIconId(1));
    this._ui.iconIdInput.addEventListener('change', () => this._loadIconImage(this._currentCategory, parseInt(this._ui.iconIdInput.value) || 1));
    this._ui.acceptIconBtn.addEventListener('click', () => this._loadIconImage(this._currentCategory, parseInt(this._ui.iconIdInput.value) || 1));
    this._ui.randomIconBtn.addEventListener('click', () => this._loadRandomIcon());

    // Category Navigation Buttons
    ['cubes', 'ships', 'balls', 'ufos', 'waves', 'robots', 'spiders', 'swings', 'jetpacks'].forEach(catKey => {
      this._ui[`${catKey}NavButton`].addEventListener('click', () => this._changeCategory(catKey));
    });
    // Add listeners for difficulty, extras, gauntlet, and EDITOR BLOCKS nav buttons
    this._ui.difficultiesNavButton.addEventListener('click', () => this._changeCategory('difficulties'));
    this._ui.extrasNavButton.addEventListener('click', () => this._changeCategory('extras'));
    this._ui.gauntletsNavButton.addEventListener('click', () => this._changeCategory('gauntlets'));
    this._ui.editor_blocksNavButton.addEventListener('click', () => this._changeCategory('editor_blocks'));


    // Difficulties Buttons
    this._ui.difficultyButtons.forEach(button => {
      button.addEventListener('click', () => this._loadDifficultyIcon(button.dataset.diff));
    });
    this._ui.difficultyModToggles.forEach(checkbox => {
      checkbox.addEventListener('change', () => this._loadDifficultyIcon(this._ui.difficultiesSection.querySelector('.difficulty-button.active')?.dataset.diff));
    });
    this._ui.clearModsBtn.addEventListener('click', () => this._clearDifficultyModifiers());

    // Extras Buttons
    this._ui.extraButtons.forEach(button => {
      button.addEventListener('click', () => this._loadExtraIcon(button.dataset.extra));
    });

    // Gauntlets Buttons
    this._ui.gauntletButtons.forEach(button => {
      button.addEventListener('click', () => this._loadGauntletIcon(button.dataset.gauntlet));
    });

    // NEW: Editor Blocks Buttons
    this._ui.editorBlockButtons.forEach(button => {
      button.addEventListener('click', () => this._loadEditorBlock(button.dataset.eblock));
    });
    // NEW: Rotate button listener
    this._ui.rotateBlockBtn.addEventListener('click', () => this._rotateBrushImage());

    // Main Canvas Click Handler (attached/unattached by _setModuleActive)
    this._canvasClickHandler = this._handleCanvasClick.bind(this);
  }

  _toggleModuleActive() {
    this._setModuleActive(!this._isActive);
  }

  _setModuleActive(active) {
    this._isActive = active;
    if (active) {
      this._hookCanvasClick();
      this._ui.moduleToggleButton.innerHTML = '<i class="fas fa-power-off"></i> Desactivar Módulo';
      this._ui.moduleToggleButton.classList.add('active');
      this.notify("info", "Módulo 'GD Tools Suite' ACTIVADO.");
      // Initial load or refresh for the current category
      this._changeCategory(this._currentCategory); // Ensure UI matches and icon is loaded
    } else {
      this._unhookCanvasClick();
      this._ui.moduleToggleButton.innerHTML = '<i class="fas fa-power-off"></i> Activar Módulo';
      this._ui.moduleToggleButton.classList.remove('active');
      this.notify("info", "Módulo 'GD Tools Suite' DESACTIVADO.");
      // Stop any ongoing drawing if the module is deactivated
      this._isDrawingIcon = false;
      this._ui.status.textContent = "Módulo inactivo. Actívalo para usarlo.";

      // Reset autoClearBeforeDrawToggle when deactivating
      if (this._ui.autoClearBeforeDrawToggle) {
        this._ui.autoClearBeforeDrawToggle.checked = false;
      }
    }
    // Disable/enable global optimization controls based on module's active state
    this._ui.delayPerSegmentInput.disabled = !active;
    this._ui.delayPerRowInput.disabled = !active;
    this._ui.qualityFactorInput.disabled = !active;
    this._ui.autoClearBeforeDrawToggle.disabled = !active;

    // NEW: Disable/enable editor block optimization controls
    this._ui.ebDelaySegmentInput.disabled = !active;
    this._ui.ebDelayPerRowInput.disabled = !active;
    this._ui.ebQualityFactorInput.disabled = !active;
    this._ui.ebTargetSizeInput.disabled = !active;


    this._ui.previewCanvas.style.opacity = active ? '1' : '0.5';


    // Also disable/enable category nav buttons
    Object.values(this._ui).forEach(element => {
        if (element && element.classList && element.classList.contains('category-nav-button')) {
            element.disabled = !active;
        }
    });

    // Hide all specific sections when module is inactive
    if (!active) {
        this._ui.commonControlsSection.style.display = 'none';
        this._ui.difficultiesSection.style.display = 'none';
        this._ui.extrasSection.style.display = 'none';
        this._ui.gauntletsSection.style.display = 'none';
        this._ui.editorBlocksSection.style.display = 'none';
        this._ui.editorBlockOptimizationSection.style.display = 'none'; // NEW
    }
  }

  _changeCategory(newCategory) {
    // Hide all category-specific sections
    this._ui.commonControlsSection.style.display = 'none';
    this._ui.difficultiesSection.style.display = 'none';
    this._ui.extrasSection.style.display = 'none';
    this._ui.gauntletsSection.style.display = 'none';
    this._ui.editorBlocksSection.style.display = 'none';
    this._ui.editorBlockOptimizationSection.style.display = 'none'; // NEW: Hide editor block specific settings

    // Remove 'active' class from all category nav buttons
    Object.values(this._ui).forEach(element => {
        if (element && element.classList && element.classList.contains('category-nav-button')) {
            element.classList.remove('active');
        }
    });

    // Remove 'active' class from all specific buttons (difficulties, extras, gauntlets, editor blocks)
    this._ui.difficultyButtons.forEach(btn => btn.classList.remove('active'));
    this._ui.extraButtons.forEach(btn => btn.classList.remove('active'));
    this._ui.gauntletButtons.forEach(btn => btn.classList.remove('active'));
    this._ui.editorBlockButtons.forEach(btn => btn.classList.remove('active'));

    // Show the relevant section and set 'active' class on button
    this._currentCategory = newCategory;

    // Reset rotation when switching to a non-editor-block category, or setting default for editor blocks
    this._currentRotation = 0; // Reset rotation for new category/object

    if (newCategory === 'difficulties') {
        this._ui.difficultiesSection.style.display = 'block';
        const activeDiffButton = this._ui.difficultiesSection.querySelector('.difficulty-button.active');
        if (activeDiffButton) {
            this._loadDifficultyIcon(activeDiffButton.dataset.diff);
        } else {
            this._loadDifficultyIcon('easy');
            this._ui.difficultiesSection.querySelector('.difficulty-button[data-diff="easy"]')?.classList.add('active');
        }
    } else if (newCategory === 'extras') {
        this._ui.extrasSection.style.display = 'block';
    } else if (newCategory === 'gauntlets') {
        this._ui.gauntletsSection.style.display = 'block';
    } else if (newCategory === 'editor_blocks') {
        this._ui.editorBlocksSection.style.display = 'block'; // Show the custom editor block controls
        this._ui.editorBlockOptimizationSection.style.display = 'block'; // NEW: Show editor block specific optimization settings

        const defaultBlock = Object.keys(this._editorBlocks)[0];
        if (defaultBlock) {
            this._loadEditorBlock(defaultBlock);
            this._ui.editorBlocksSection.querySelector(`.editor-block-button[data-eblock="${defaultBlock}"]`)?.classList.add('active');
        }
    } else { // All other regular icon categories
        this._ui.commonControlsSection.style.display = 'block'; // Show common controls
        this._currentIconId = 1; // Reset ID for icon categories
        this._loadIconImage(this._currentCategory, this._currentIconId);
    }
    // Set active class on the new category button
    const newCategoryButton = this._ui[`${newCategory}NavButton`];
    if (newCategoryButton) {
        newCategoryButton.classList.add('active');
    }
    this._ui.status.textContent = `Categoría: ${newCategory.charAt(0).toUpperCase() + newCategory.slice(1).replace('_', ' ')}.`;
  }

  _updateInfo() {
    if (this._categories[this._currentCategory]) {
      this._ui.info.textContent = `Icono #${this._currentIconId} / ${this._categories[this._currentCategory].maxId}`;
      this._ui.iconIdInput.max = this._categories[this._currentCategory].maxId; // Update max for input
    } else {
      this._ui.info.textContent = `Selecciona un icono`; // This text is for categories without numerical IDs
      this._ui.iconIdInput.max = 999; // Default max for non-icon categories, but input is hidden anyway.
    }
  }

  _changeIconId(delta) {
    if (!this._categories[this._currentCategory]) return; // Should not happen if common controls are hidden
    let n = this._currentIconId + delta;
    n = this._clamp(n, 1, this._categories[this._currentCategory].maxId);
    this._loadIconImage(this._currentCategory, n);
  }

  _loadRandomIcon() {
    if (!this._categories[this._currentCategory]) return; // Should not happen if common controls are hidden
    const maxId = this._categories[this._currentCategory].maxId;
    const randomId = Math.floor(Math.random() * maxId) + 1;
    this._loadIconImage(this._currentCategory, randomId);
  }

  async _loadIconImage(categoryKey, id) {
    if (!this._isActive) return;
    const category = this._categories[categoryKey];
    if (!category) return;

    this._currentCategory = categoryKey; // Update current category
    this._currentIconId = this._clamp(id, 1, category.maxId);
    this._ui.iconIdInput.value = this._currentIconId;
    this._updateInfo();

    const imageUrl = `https://gdbrowser.com/iconkit/premade/${category.prefix}${this._currentIconId}.png`;
    this._ui.status.textContent = `Cargando ${categoryKey.slice(0, -1)} #${this._currentIconId}...`;

    const img = new window.Image();
    img.crossOrigin = "anonymous";
    img.onload = () => {
      this._canvasBrushImg = img; // Store the original image
      this._drawPreviewWithRotation(img, 0); // Always draw without rotation for icons
      this._ui.status.textContent = "Listo. Haz click en el canvas principal para dibujar.";
    };
    img.onerror = () => {
      this._canvasBrushImg = null;
      this._drawPreviewError();
      this.notify("error", `Fallo al cargar la imagen de ${categoryKey.slice(0, -1)} #${id}.`);
      this._ui.status.textContent = "Error al cargar imagen.";
    };
    img.src = imageUrl;
  }

  async _loadDifficultyIcon(difficultyKey) {
    if (!this._isActive) return;
    this._currentCategory = 'difficulties'; // Set active category
    this._ui.difficultyButtons.forEach(btn => btn.classList.remove('active')); // Clear active state
    const clickedButton = this._ui.difficultiesSection.querySelector(`.difficulty-button[data-diff="${difficultyKey}"]`);
    if(clickedButton) clickedButton.classList.add('active');

    let imageUrl = `https://gdbrowser.com/assets/difficulties/${this._difficulties[difficultyKey]}`;

    // Apply modifiers
    let activeModifiers = [];
    this._ui.difficultyModToggles.forEach(checkbox => {
      if (checkbox.checked) {
        activeModifiers.push(checkbox.dataset.mod);
      }
    });

    this._canvasBrushImg = null; // Clear current image immediately
    this._ui.status.textContent = `Cargando ${difficultyKey} ...`;
    this._drawPreviewWithRotation(null, 0); // Clear preview

    const tryLoad = async (url) => {
        return new Promise(resolve => {
            const img = new Image();
            img.crossOrigin = "anonymous";
            img.onload = () => {
                this._canvasBrushImg = img; // Store the original image
                this._drawPreviewWithRotation(img, 0); // Draw to preview without rotation
                resolve(true);
            };
            img.onerror = () => {
                resolve(false);
            };
            img.src = url;
        });
    };

    let loaded = false;
    if (activeModifiers.length > 0) {
        const baseName = this._difficulties[difficultyKey].replace('.png', '');
        for (const mod of activeModifiers) {
            const modifiedUrl = `https://gdbrowser.com/assets/difficulties/${baseName}${mod}.png`;
            loaded = await tryLoad(modifiedUrl);
            if (loaded) {
                this.notify("info", `Cargada ${difficultyKey} con modificador ${mod}.`);
                break;
            }
        }
    }
    if (!loaded) {
        loaded = await tryLoad(imageUrl);
    }

    if (loaded) {
        this._ui.status.textContent = "Listo. Haz click para dibujar la dificultad.";
    } else {
        this._canvasBrushImg = null;
        this._drawPreviewError();
        this.notify("error", `Fallo al cargar la imagen de dificultad "${difficultyKey}".`);
        this._ui.status.textContent = "Error al cargar imagen de dificultad.";
    }
  }

  _clearDifficultyModifiers() {
    this._ui.difficultyModToggles.forEach(checkbox => checkbox.checked = false);
    // Reload the current difficulty icon to reflect the change
    const currentDiff = this._ui.difficultiesSection.querySelector('.difficulty-button.active')?.dataset.diff;
    if(currentDiff) this._loadDifficultyIcon(currentDiff);
    else this.notify("info", "Modificadores de dificultad limpiados.");
  }

  async _loadExtraIcon(extraKey) {
    if (!this._isActive) return;
    this._currentCategory = 'extras'; // Set active category
    this._ui.extraButtons.forEach(btn => btn.classList.remove('active'));
    const clickedButton = this._ui.extrasSection.querySelector(`.extra-button[data-extra="${extraKey}"]`);
    if(clickedButton) clickedButton.classList.add('active');

    const imageUrl = `https://gdbrowser.com/assets/${this._extras[extraKey]}`;
    this._ui.status.textContent = `Cargando extra: ${extraKey}...`;
    this._drawPreviewWithRotation(null, 0); // Clear preview

    const img = new window.Image();
    img.crossOrigin = "anonymous";
    img.onload = () => {
      this._canvasBrushImg = img; // Store the original image
      this._drawPreviewWithRotation(img, 0); // Draw to preview without rotation
      this.notify("info", `Cargado extra: ${extraKey}.`);
      this._ui.status.textContent = "Listo. Haz click para dibujar el extra.";
    };
    img.onerror = () => {
      this._canvasBrushImg = null;
      this._drawPreviewError();
      this.notify("error", `Fallo al cargar la imagen extra "${extraKey}".`);
      this._ui.status.textContent = "Error al cargar extra.";
    };
    img.src = imageUrl;
  }

  async _loadGauntletIcon(gauntletKey) {
    if (!this._isActive) return;
    this._currentCategory = 'gauntlets'; // Set active category
    this._ui.gauntletButtons.forEach(btn => btn.classList.remove('active'));
    const clickedButton = this._ui.gauntletsSection.querySelector(`.gauntlet-button[data-gauntlet="${gauntletKey}"]`);
    if(clickedButton) clickedButton.classList.add('active');

    const imageUrl = `https://gdbrowser.com/assets/gauntlets/${this._gauntlets[gauntletKey]}`;
    this._ui.status.textContent = `Cargando gauntlet: ${gauntletKey}...`;
    this._drawPreviewWithRotation(null, 0); // Clear preview

    const img = new window.Image();
    img.crossOrigin = "anonymous";
    img.onload = () => {
      this._canvasBrushImg = img; // Store the original image
      this._drawPreviewWithRotation(img, 0); // Draw to preview without rotation
      this.notify("info", `Cargado gauntlet: ${gauntletKey}.`);
      this._ui.status.textContent = "Listo. Haz click para dibujar el gauntlet.";
    };
    img.onerror = () => {
      this._canvasBrushImg = null;
      this._drawPreviewError();
      this.notify("error", `Fallo al cargar la imagen gauntlet "${gauntletKey}".`);
      this._ui.status.textContent = "Error al cargar gauntlet.";
    };
    img.src = imageUrl;
  }

  // --- NEW: Function to load editor block images ---
  async _loadEditorBlock(blockKey) {
    if (!this._isActive) return;
    this._currentCategory = 'editor_blocks'; // Set active category
    this._ui.editorBlockButtons.forEach(btn => btn.classList.remove('active'));
    const clickedButton = this._ui.editorBlocksSection.querySelector(`.editor-block-button[data-eblock="${blockKey}"]`);
    if(clickedButton) clickedButton.classList.add('active');

    const imageUrl = this._editorBlocks[blockKey];
    if (!imageUrl) {
        this.notify("error", `URL de imagen no encontrada para el bloque de editor "${blockKey}".`);
        this._ui.status.textContent = "Error: bloque no encontrado.";
        this._drawPreviewError();
        return;
    }

    this._ui.status.textContent = `Cargando bloque: ${blockKey}...`;
    this._drawPreviewWithRotation(null, 0); // Clear preview immediately

    const img = new window.Image();
    img.crossOrigin = "anonymous";
    img.onload = () => {
      this._canvasBrushImg = img; // Store the ORIGINAL image
      this._currentRotation = 0; // Reset rotation when loading a new block
      this._drawPreviewWithRotation(img, this._currentRotation); // Draw with current (reset) rotation
      this.notify("info", `Cargado bloque: ${blockKey}.`);
      this._ui.status.textContent = "Listo. Haz click para dibujar el bloque.";
    };
    img.onerror = () => {
      this._canvasBrushImg = null;
      this._drawPreviewError();
      this.notify("error", `Fallo al cargar la imagen del bloque "${blockKey}".`);
      this._ui.status.textContent = "Error al cargar bloque.";
    };
    img.src = imageUrl;
  }

  // NEW: Helper to draw image to preview canvas with rotation
  _drawPreviewWithRotation(img, angle) {
    const ctx = this._ui.previewCanvas.getContext("2d");
    const previewSize = 96; // Fixed size for preview canvas
    ctx.clearRect(0, 0, previewSize, previewSize);

    if (!img) {
      // If no image, show placeholder
      ctx.fillStyle = "#d00";
      ctx.font = "14px Arial";
      ctx.fillText("NO IMG", 18, 55);
      return;
    }

    ctx.save();
    ctx.translate(previewSize / 2, previewSize / 2); // Move origin to center
    ctx.rotate(angle * Math.PI / 180); // Rotate

    // Calculate drawing size and position to fit while maintaining aspect ratio
    let drawWidth, drawHeight;
    const maxDim = Math.max(img.width, img.height);
    const scale = previewSize / maxDim * 0.9; // Scale to fit, leave a small margin
    drawWidth = img.width * scale;
    drawHeight = img.height * scale;

    ctx.drawImage(img, -drawWidth / 2, -drawHeight / 2, drawWidth, drawHeight); // Draw image centered
    ctx.restore();
  }

  // NEW: Helper to draw an error message on the preview canvas
  _drawPreviewError() {
    const ctx = this._ui.previewCanvas.getContext("2d");
    ctx.clearRect(0, 0, 96, 96);
    ctx.fillStyle = "#d00";
    ctx.font = "14px Arial";
    ctx.fillText("NO IMG", 18, 55);
  }

  // NEW: Function to rotate the current brush image
  _rotateBrushImage() {
    if (!this._canvasBrushImg) {
      this.notify("warning", "No hay imagen cargada para rotar.");
      return;
    }
    if (this._currentCategory !== 'editor_blocks') {
        this.notify("warning", "La rotación solo está disponible para 'Bloques del Editor'.");
        return;
    }

    this._currentRotation = (this._currentRotation + 90) % 360;
    this._drawPreviewWithRotation(this._canvasBrushImg, this._currentRotation);
    this.notify("info", `Objeto rotado a ${this._currentRotation}°.`);
    this._ui.status.textContent = `Objeto rotado a ${this._currentRotation}°. Listo para dibujar.`;
  }


  _hookCanvasClick() {
    if (!this._mainCanvas) {
        this._mainCanvas = document.getElementById("canvas"); // Try to get it again
        if (!this._mainCanvas) {
            this.notify("error", "Canvas principal no disponible. No se puede adjuntar el click handler.");
            return;
        }
    }
    if (!this._canvasClickHandlerAttached) {
      this._mainCanvas.addEventListener("click", this._canvasClickHandler);
      this._canvasClickHandlerAttached = true;
      this.notify("info", "Click handler para GD Tools habilitado.");
    }
  }

  _unhookCanvasClick() {
    if (this._mainCanvas && this._canvasClickHandlerAttached) {
      this._mainCanvas.removeEventListener("click", this._canvasClickHandler);
      this._canvasClickHandlerAttached = false;
      this.notify("info", "Click handler para GD Tools deshabilitado.");
    }
  }

  async _handleCanvasClick(ev) {
    if (this._isDrawingIcon) {
        this.notify("warning", "Ya se está dibujando un icono. Espera a que termine.");
        return;
    }
    if (!this._isActive) {
        this.notify("warning", "El módulo GD Tools está inactivo. Actívalo primero.");
        return;
    }
    if (!this._canvasBrushImg) {
        this.notify("warning", "No hay imagen cargada para dibujar.");
        return;
    }
    const socket = this._getGameSocket(); // Using the module's helper
    if (!socket) {
        this.notify("error", "No hay WebSocket principal. Conéctate a la sala para dibujar.");
        this._ui.status.textContent = "¡Sin conexión!";
        return;
    }
    if (!this._mainCanvas) {
        this.notify("error", "El canvas principal no está disponible. No se puede dibujar el icono.");
        return;
    }

    this._isDrawingIcon = true; // Set drawing flag

    const rect = this._mainCanvas.getBoundingClientRect();
    const clickX_display_px = ev.clientX - rect.left;
    const clickY_display_px = ev.clientY - rect.top;

    const scaleToInternalCanvas = this._mainCanvas.width / rect.width;
    const clickX_internal_px = clickX_display_px * scaleToInternalCanvas;
    const clickY_internal_px = clickY_display_px * scaleToInternalCanvas;

    // --- NEW: Select optimization parameters based on category ---
    let delayPerSegment, delayPerRow, qualityFactor, targetDrawingSize_px;
    const autoClear = this._ui.autoClearBeforeDrawToggle.checked;

    if (this._currentCategory === 'editor_blocks') {
        delayPerSegment = parseInt(this._ui.ebDelaySegmentInput.value) || this._editorBlockDefaultOptimizations.delayPerSegment;
        delayPerRow = parseInt(this._ui.ebDelayPerRowInput.value) || this._editorBlockDefaultOptimizations.delayPerRow;
        qualityFactor = parseInt(this._ui.ebQualityFactorInput.value) || this._editorBlockDefaultOptimizations.qualityFactor;
        targetDrawingSize_px = parseInt(this._ui.ebTargetSizeInput.value) || this._editorBlockDefaultOptimizations.targetSize;
    } else {
        delayPerSegment = parseInt(this._ui.delayPerSegmentInput.value) || 1;
        delayPerRow = parseInt(this._ui.delayPerRowInput.value) || 20;
        qualityFactor = parseInt(this._ui.qualityFactorInput.value) || 1;
        targetDrawingSize_px = 64; // Default for icons, difficulties, extras, gauntlets
    }
    // -----------------------------------------------------------

    this._ui.status.textContent = "Dibujando objeto GD...";
    this.notify("info", "Dibujando objeto GD en el lienzo...");

    if (autoClear) {
        await this._clearCanvas(socket);
        await this._delay(100);
    }

    // --- Create a temporary canvas for the *rotated* image data ---
    // Use the stored original brush image for rotation.
    const originalImgWidth = this._canvasBrushImg.width;
    const originalImgHeight = this._canvasBrushImg.height;

    let rotatedTempCanvas = document.createElement("canvas");
    let tempCtx = rotatedTempCanvas.getContext("2d");

    // Determine rotated dimensions
    let rotatedWidth = originalImgWidth;
    let rotatedHeight = originalImgHeight;
    if (this._currentRotation === 90 || this._currentRotation === 270) {
        rotatedWidth = originalImgHeight;
        rotatedHeight = originalImgWidth;
    }
    rotatedTempCanvas.width = rotatedWidth;
    rotatedTempCanvas.height = rotatedHeight;

    // Draw the original image onto the temp canvas with rotation
    tempCtx.save();
    tempCtx.translate(rotatedWidth / 2, rotatedHeight / 2);
    tempCtx.rotate(this._currentRotation * Math.PI / 180);
    // Adjust drawing position based on original dimensions to keep it centered
    tempCtx.drawImage(this._canvasBrushImg, -originalImgWidth / 2, -originalImgHeight / 2);
    tempCtx.restore();

    // Get pixel data from the (now rotated) temporary canvas
    const imgData = tempCtx.getImageData(0, 0, rotatedWidth, rotatedHeight).data;

    // Calculate scale factor based on the rotated image dimensions
    const scaleFactor_drawing = targetDrawingSize_px / Math.max(rotatedWidth, rotatedHeight);

    let currentLineStart_asset_px = null;
    let currentLineColor = null;
    let totalLinesDrawn = 0;

    for (let py = 0; py < rotatedHeight; py += qualityFactor) {
        if (!this._isActive || !this._isDrawingIcon) {
            this.notify("info", "Dibujo de objeto detenido por el usuario.");
            break;
        }

        currentLineStart_asset_px = null;
        currentLineColor = null;

        for (let px = 0; px < rotatedWidth; px += qualityFactor) {
            const idx = (py * rotatedWidth + px) * 4;
            const r = imgData[idx], g = imgData[idx+1], b = imgData[idx+2], a = imgData[idx+3];
            const hexColor = "#" + [r,g,b].map(v=>v.toString(16).padStart(2,'0')).join('');

            if (a > 10) { // If pixel is mostly opaque
                if (currentLineStart_asset_px === null) {
                    currentLineStart_asset_px = px;
                    currentLineColor = hexColor;
                } else if (hexColor !== currentLineColor) { // Color change, draw previous segment
                    const startX_draw = clickX_internal_px + (currentLineStart_asset_px - rotatedWidth/2) * scaleFactor_drawing;
                    const startY_draw = clickY_internal_px + (py - rotatedHeight/2) * scaleFactor_drawing;
                    const endX_draw = clickX_internal_px + (px - qualityFactor - rotatedWidth/2) * scaleFactor_drawing;
                    const endY_draw = startY_draw;

                    const thickness = Math.max(1, Math.round(scaleFactor_drawing * qualityFactor));
                    this._sendDrawCommand(socket, [startX_draw, startY_draw], [endX_draw, endY_draw], currentLineColor, thickness);
                    totalLinesDrawn++;

                    currentLineStart_asset_px = px;
                    currentLineColor = hexColor;
                }
            } else { // Transparent pixel
                if (currentLineStart_asset_px !== null) { // If a segment was active, draw it
                    const startX_draw = clickX_internal_px + (currentLineStart_asset_px - rotatedWidth/2) * scaleFactor_drawing;
                    const startY_draw = clickY_internal_px + (py - rotatedHeight/2) * scaleFactor_drawing;
                    const endX_draw = clickX_internal_px + (px - qualityFactor - rotatedWidth/2) * scaleFactor_drawing;
                    const endY_draw = startY_draw;

                    const thickness = Math.max(1, Math.round(scaleFactor_drawing * qualityFactor));
                    this._sendDrawCommand(socket, [startX_draw, startY_draw], [endX_draw, endY_draw], currentLineColor, thickness);
                    totalLinesDrawn++;

                    currentLineStart_asset_px = null;
                    currentLineColor = null;
                }
            }
        }
        // After iterating through a row, if a segment is still active, draw it to the end of the row
        if (currentLineStart_asset_px !== null) {
            const startX_draw = clickX_internal_px + (currentLineStart_asset_px - rotatedWidth/2) * scaleFactor_drawing;
            const startY_draw = clickY_internal_px + (py - rotatedHeight/2) * scaleFactor_drawing;
            const endX_draw = clickX_internal_px + (rotatedWidth - 1 - rotatedWidth/2) * scaleFactor_drawing;
            const endY_draw = startY_draw;

            const thickness = Math.max(1, Math.round(scaleFactor_drawing * qualityFactor));
            this._sendDrawCommand(socket, [startX_draw, startY_draw], [endX_draw, endY_draw], currentLineColor, thickness);
            totalLinesDrawn++;

            currentLineStart_asset_px = null;
            currentLineColor = null;
        }
        await this._delay(delayPerRow);
    }

    this._isDrawingIcon = false;
    this.notify("success", `Objeto GD dibujado en el canvas (${totalLinesDrawn} líneas). Visible para todos.`);
    this._ui.status.textContent = "Listo. Haz click de nuevo para otro objeto.";
  }

  // --- Helpers ---
  _getGameSocket() {
    if (globalThis.sockets && globalThis.sockets.length > 0) {
      return globalThis.sockets.find(s =>
        s.url.includes("drawaria.online/socket.io") && s.readyState === WebSocket.OPEN
      );
    }
    return null;
  }

  _delay(ms) { return new Promise(resolve => setTimeout(resolve, ms)); }
  _clamp(value, min, max) { return Math.max(min, Math.min(max, value)); }

  _sendDrawCommand(socket, start_px, end_px, color, thickness) {
      if (!this._mainCanvas) {
          console.error("Error: _mainCanvas is null in _sendDrawCommand. Cannot send drawing command.");
          return;
      }
      const normX1 = (start_px[0] / this._mainCanvas.width);
      const normY1 = (start_px[1] / this._mainCanvas.height);
      const normX2 = (end_px[0] / this._mainCanvas.width);
      const normY2 = (end_px[1] / this._mainCanvas.height);

      const ctx = this._mainCanvas.getContext('2d');
      ctx.strokeStyle = color;
      ctx.lineWidth = thickness;
      ctx.lineCap = 'round';
      ctx.beginPath();
      ctx.moveTo(start_px[0], start_px[1]);
      ctx.lineTo(end_px[0], end_px[1]);
      ctx.stroke();

      // The 0 - thickness is a common trick in Drawaria to use relative line thickness
      // and ensure it's drawn correctly on other clients if the server interprets negative.
      socket.send(`42["drawcmd",0,[${normX1.toFixed(4)},${normY1.toFixed(4)},${normX2.toFixed(4)},${normY2.toFixed(4)},false,${0 - thickness},"${color}",0,0,{}]]`);
  }

  async _clearCanvas(socket) {
        if (!this._mainCanvas) {
            this.notify("error", "Canvas not found, cannot clear locally.");
            return;
        }
        if (!socket || socket.readyState !== WebSocket.OPEN) {
            this.notify("error", "Not connected to Drawaria. Cannot send clear command to server.");
            return;
        }

        const ctx = this._mainCanvas.getContext('2d');
        ctx.clearRect(0, 0, this._mainCanvas.width, this._mainCanvas.height);

        this.notify("info", "Enviando comandos de limpieza...");
        const clearThickness = 2000;
        const clearColor = '#FFFFFF';
        const steps = 5; // Send multiple broad strokes to ensure full clear

        for (let i = 0; i <= steps; i++) {
            // Using normalized coordinates for clear commands
            // Draw horizontal lines across the canvas
            this._sendDrawCommand(socket, [0, (i / steps)], [this._mainCanvas.width, (i / steps)], clearColor, clearThickness);
            await this._delay(5);
            // Draw vertical lines across the canvas
            this._sendDrawCommand(socket, [(i / steps), 0], [(i / steps), this._mainCanvas.height], clearColor, clearThickness);
            await this._delay(5);
        }
        this.notify("success", "Lienzo limpiado para todos.");
  }

  // Cube Engine calls this when module is hidden
  onHideContent() {
    this._unhookCanvasClick();
    this.notify("info", "Pincel de cubos desactivado al ocultar el módulo.");
  }

  // Cube Engine calls this when module is shown
  onShowContent() {
    // Only re-hook if the module was active when hidden
    if (this._isActive) {
        this._hookCanvasClick();
        this.notify("info", "Pincel de cubos activado. Haz clic en el lienzo principal.");
    }
  }
}



// --- Sub-Module X: MinecraftDrawer ---
// Allows users to draw Minecraft mobs, items, and blocks on the Drawaria canvas.

class MinecraftDrawer extends QBit {


  // Data for Minecraft images, categorized.
  _minecraftImages = {
    mobs: [
      "https://minecraft.wiki/images/Allay_JE1_BE1.png", // #1
      "https://minecraft.wiki/images/Cod.png", // #8
      "https://minecraft.wiki/images/Salmon.png", // #21
      "https://minecraft.wiki/images/Skeleton_Horse.png", // #23
      "https://minecraft.wiki/images/Strider.png", // #27
      "https://minecraft.wiki/images/Wandering_Trader.png", // #32
      "https://minecraft.wiki/images/Bee.png", // #33
      "https://minecraft.wiki/images/Dolphin.png", // #35
      "https://minecraft.wiki/images/Drowned.png", // #36
      "https://minecraft.wiki/images/Iron_Golem_JE2_BE2.png", // #40
      "https://minecraft.wiki/images/Llama.png", // #41
      "https://minecraft.wiki/images/Piglin.png", // #43
      "https://minecraft.wiki/images/Endermite.png", // #54
      "https://minecraft.wiki/images/Ender_Dragon.png", // #55
      // Removed "https://minecraft.wiki/images/Ghast.png"
      // Removed "https://minecraft.wiki/images/Guardian.png"
      "https://minecraft.wiki/images/Zombie_Villager.png", // New # of original #79
      "https://minecraft.wiki/images/Skeleton_Horseman.png", // New # of original #83
      "https://minecraft.wiki/images/Agent.png", // New # of original #88
      //"https://minecraft.wiki/images/Player.png", // New # of original #91
      "https://minecraft.wiki/images/Beast_Boy.png", // New # of original #92
      //"https://minecraft.wiki/images/Black_Steve.png", // New # of original #93
      "https://minecraft.wiki/images/Rana.png", // New # of original #94
      "https://minecraft.wiki/images/Diamond_Chicken.png", // New # of original #96
      "https://minecraft.wiki/images/Mars.png", // New # of original #99
      "https://minecraft.wiki/images/Moobloom.png", // New # of original #100
      "https://minecraft.wiki/images/Moon_Cow.png", // New # of original #101
      "https://minecraft.wiki/images/Friendly_Wither.png", // New # of original #103
      "https://minecraft.wiki/images/Redstone_Bug.png", // New # of original #106
      "https://minecraft.wiki/images/Smiling_Creeper.png", // New # of original #107
      "https://minecraft.wiki/images/Pigman.png", // New # of original #108
      "https://minecraft.wiki/images/Chinese_Alligator.png", // New # of original #110
      "https://minecraft.wiki/images/Golden_Monkey.png", // New # of original #111
      "https://minecraft.wiki/images/White-Lipped_Deer.png", // New # of original #112
      "https://minecraft.wiki/images/Iceologer.png", // New # of original #115
      "https://minecraft.wiki/images/Glare.png" // New # of original #116
    ],
    items: [
      "https://minecraft.wiki/images/Wheat.png", // #14
      "https://minecraft.wiki/images/Map.png", // #26
      "https://minecraft.wiki/images/Spawn_Egg.png" // #30
    ],
    blocks: [
      "https://minecraft.wiki/images/Dirt.png" // #2
    ]
  };

  _currentCategory = 'mobs'; // 'mobs', 'items', 'blocks'
  _currentIndex = 0; // Current index within the selected category
  _canvasBrushImg = null; // Stores the loaded Minecraft image
  _ui = {}; // UI elements references
  _mainCanvas = null; // Reference to the main game canvas (will be set in _onStartup)
  _canvasClickHandler = null; // Stores the bound click handler
  _canvasClickHandlerAttached = false; // Flag to track if handler is attached
  _isActive = false; // Module active state
  _isDrawingCube = false; // Flag to prevent multiple image drawings simultaneously (retained original name)


  constructor() {
    super("Minecraft Drawer", '<i class="fas fa-gem"></i>'); // Unique name and icon for Minecraft
    this._onStartup();
  }

  _onStartup() {
    // Attempt to find the main canvas immediately and store its reference.
    this._mainCanvas = document.getElementById("canvas");
    if (!this._mainCanvas) {
      // If canvas is not immediately available, notify and retry later for listeners.
      this.notify("warning", "Canvas principal del juego no encontrado al inicio. Algunas funciones se habilitarán más tarde.");
    }

    this._loadInterface(); // Build the UI
    this._setupEventListeners(); // Setup basic listeners

    // Load initial image based on default category and index
    this._loadMinecraftImage(this._currentCategory, this._currentIndex);

    // Module starts disabled by default
    this._setModuleActive(false); // Set initial state

    this.notify("info", "Módulo 'Minecraft Drawer' listo para pintar objetos visibles para todos.");
  }

  _loadInterface() {
    const container = domMake.Tree("div", {
      id: `${this.identifier}-container`,
      class: "module-section"
    });
    this.htmlElements.section.appendChild(container);

    // Module Toggle Button
    const moduleToggleGroup = domMake.Tree("div", {
      class: "module-btn-group",
      style: "margin-bottom:10px;"
    });
    this._ui.moduleToggleButton = domMake.Button('<i class="fas fa-power-off"></i> Activar Módulo');
    this._ui.moduleToggleButton.classList.add('module-toggle-button'); // Custom class for styling
    this._ui.moduleToggleButton.addEventListener('click', () => this._toggleModuleActive());
    moduleToggleGroup.appendChild(this._ui.moduleToggleButton);
    container.appendChild(moduleToggleGroup);

    container.appendChild(domMake.Tree("div", {
      class: "module-section-title"
    }, ["Minecraft Online Drawer"]));

    // NEW: Category Selection
    container.appendChild(domMake.Tree("div", {
      class: "module-section-title",
      style: "margin-top:15px;"
    }, ["Seleccionar Categoría"]));
    const categoryButtonGroup = domMake.Tree("div", {
      class: "module-btn-group",
      style: "margin-bottom:10px;"
    });
    this._ui.mobsCategoryBtn = domMake.Button('Mobs', {
      class: "artfx-button"
    });
    this._ui.itemsCategoryBtn = domMake.Button('Items', {
      class: "artfx-button"
    });
    this._ui.blocksCategoryBtn = domMake.Button('Bloques', {
      class: "artfx-button"
    });

    this._ui.mobsCategoryBtn.addEventListener('click', () => this._setCategory('mobs'));
    this._ui.itemsCategoryBtn.addEventListener('click', () => this._setCategory('items'));
    this._ui.blocksCategoryBtn.addEventListener('click', () => this._setCategory('blocks'));

    categoryButtonGroup.append(this._ui.mobsCategoryBtn, this._ui.itemsCategoryBtn, this._ui.blocksCategoryBtn);
    container.appendChild(categoryButtonGroup);


    // Image navigation (Prev/Next buttons)
    const navRow = domMake.Tree("div", {
      style: "display:flex;align-items:center;justify-content:center;gap:10px;margin-bottom:10px;"
    });
    this._ui.prevBtn = domMake.Button('←', {
      class: "artfx-button special"
    });
    this._ui.canvas = domMake.Tree("canvas", {
      width: 96,
      height: 96,
      style: "border:1px solid var(--CE-color);background:#222;"
    });
    this._ui.nextBtn = domMake.Button('→', {
      class: "artfx-button special"
    });
    navRow.append(this._ui.prevBtn, this._ui.canvas, this._ui.nextBtn);
    container.appendChild(navRow);

    // Image number info
    this._ui.info = domMake.Tree("div", {
      style: "text-align:center;margin-top:5px;font-size:0.9em;color:var(--CE-color);"
    });
    container.appendChild(this._ui.info);

    // Icon Searcher
    container.appendChild(domMake.Tree("div", {
      class: "module-section-title",
      style: "margin-top:15px;"
    }, ["Buscar por ID"]));
    const searchRow = domMake.Row({
      style: "display:flex;align-items:center;gap:5px;"
    });
    this._ui.iconIdInput = domMake.Tree("input", {
      type: "number",
      min: "1",
      value: "1",
      placeholder: "ID del Objeto",
      class: "module-form-control"
    }); // Max will be set dynamically
    this._ui.acceptIconBtn = domMake.Button('Aceptar ID', {
      class: "artfx-button"
    });
    searchRow.appendAll(this._ui.iconIdInput, this._ui.acceptIconBtn);
    container.appendChild(searchRow);

    // Random Icon Button
    const randomRow = domMake.Row({
      style: "margin-top:10px;"
    });
    this._ui.randomIconBtn = domMake.Button('Objeto Random', {
      class: "artfx-button special",
      style: "width:100%;"
    });
    randomRow.appendChild(this._ui.randomIconBtn);
    container.appendChild(randomRow);

    // Optimization Settings
    container.appendChild(domMake.Tree("div", {
      class: "module-section-title",
      style: "margin-top:15px;"
    }, ["Optimización de Dibujo"]));
    const optSettingsGrid = domMake.Tree("div", {
      class: "module-btn-group",
      style: "flex-wrap:wrap;"
    }); // Using btn-group for grid-like layout



    this._ui.delayPerSegmentInput = domMake.Tree("input", {
      type: "number",
      min: "0",
      max: "50",
      value: "120", // Increased for more optimization
      title: "Retraso por segmento de línea (ms)",
      class: "module-form-control"
    });
    this._ui.delayPerSegmentLabel = domMake.Tree("label", {
      for: `${this.identifier}-delayPerSegmentInput`
    }, ["Delay Segmento (ms):"]);
    optSettingsGrid.append(domMake.Tree("div", {
      style: "flex:1 1 48%;"
    }, [this._ui.delayPerSegmentLabel, this._ui.delayPerSegmentInput]));

    this._ui.delayPerRowInput = domMake.Tree("input", {
      type: "number",
      min: "0",
      max: "200",
      value: "130", // Increased for more optimization
      title: "Retraso por fila de píxeles (ms)",
      class: "module-form-control"
    });
    this._ui.delayPerRowLabel = domMake.Tree("label", {
      for: `${this.identifier}-delayPerRowInput`
    }, ["Delay Fila (ms):"]);
    optSettingsGrid.append(domMake.Tree("div", {
      style: "flex:1 1 48%;"
    }, [this._ui.delayPerRowLabel, this._ui.delayPerRowInput]));

    this._ui.qualityFactorInput = domMake.Tree("input", {
      type: "number",
      min: "1",
      max: "5",
      value: "6", // Increased for more aggressive optimization (lower quality, faster drawing)
      title: "Factor de calidad (1=mejor, 5=peor, más rápido)",
      class: "module-form-control"
    });
    this._ui.qualityFactorLabel = domMake.Tree("label", {
      for: `${this.identifier}-qualityFactorInput`
    }, ["Calidad (1-5):"]);
    optSettingsGrid.append(domMake.Tree("div", {
      style: "flex:1 1 48%;"
    }, [this._ui.qualityFactorLabel, this._ui.qualityFactorInput]));

    this._ui.autoClearBeforeDrawToggle = domMake.Tree("input", {
      type: "checkbox",
      id: `${this.identifier}-autoClearToggle`,
      checked: false, // ← Desmarcado por defecto
      title: "Limpiar el canvas antes de dibujar cada objeto"
    });

    this._ui.autoClearBeforeDrawLabel = domMake.Tree("label", {
      for: `${this.identifier}-autoClearToggle`
    }, ["Auto-Limpiar antes de dibujar"]);
    optSettingsGrid.append(domMake.Tree("div", {
      style: "flex:1 1 48%; display:flex; align-items:center;"
    }, [this._ui.autoClearBeforeDrawToggle, this._ui.autoClearBeforeDrawLabel]));

    container.appendChild(optSettingsGrid);

    // Instructions/Status
    this._ui.status = domMake.Tree("div", {
      style: "text-align:center;margin-top:10px;font-size:0.85em;color:var(--info);"
    }, ["Haz clic en el canvas principal para dibujar el objeto donde hiciste clic (será visible para todos)."]);
    container.appendChild(this._ui.status);

    // Initial category selection UI update
    this._updateCategoryButtons();
  }

  _setupEventListeners() {
    this._ui.prevBtn.addEventListener('click', () => this._changeImage(-1));
    this._ui.nextBtn.addEventListener('click', () => this._changeImage(1));

    // Setup for new buttons
    this._ui.acceptIconBtn.addEventListener('click', () => this._loadMinecraftImage(this._currentCategory, parseInt(this._ui.iconIdInput.value) - 1 || 0)); // Convert to 0-based index
    this._ui.randomIconBtn.addEventListener('click', () => {
      const imagesInCurrentCategory = this._minecraftImages[this._currentCategory];
      if (imagesInCurrentCategory.length > 0) {
        const randomIndex = Math.floor(Math.random() * imagesInCurrentCategory.length);
        this._loadMinecraftImage(this._currentCategory, randomIndex);
      } else {
        this.notify("warning", `No hay objetos en la categoría '${this._currentCategory}' para seleccionar al azar.`);
      }
    });

    // _mainCanvas reference is set in _onStartup for robustness.
    // The click handler will be bound/unbound based on module active state.
    this._canvasClickHandler = this._handleCanvasClick.bind(this);
  }

  // NEW: Toggle module active state
  _toggleModuleActive() {
    this._setModuleActive(!this._isActive);
  }

  // NEW: Set module active state
  _setModuleActive(active) {
    this._isActive = active;
    if (active) {
      this._hookCanvasClick();
      this._ui.moduleToggleButton.innerHTML = '<i class="fas fa-power-off"></i> Desactivar Módulo';
      this._ui.moduleToggleButton.classList.add('active'); // Add 'active' class for styling
      this.notify("info", "Módulo 'Minecraft Drawer' ACTIVADO.");
    } else {
      this._unhookCanvasClick();
      this._ui.moduleToggleButton.innerHTML = '<i class="fas fa-power-off"></i> Activar Módulo';
      this._ui.moduleToggleButton.classList.remove('active'); // Remove 'active' class
      this.notify("info", "Módulo 'Minecraft Drawer' DESACTIVADO.");
      // --- CHANGE: Reset autoClearBeforeDrawToggle when deactivating ---
      this._ui.autoClearBeforeDrawToggle.checked = false;
      // ------------------------------------------------------------------
    }
    // Disable/enable other UI elements based on module's active state
    this._ui.prevBtn.disabled = !active;
    this._ui.nextBtn.disabled = !active;
    this._ui.iconIdInput.disabled = !active;
    this._ui.acceptIconBtn.disabled = !active;
    this._ui.randomIconBtn.disabled = !active;
    // Disable/enable category selection buttons
    this._ui.mobsCategoryBtn.disabled = !active;
    this._ui.itemsCategoryBtn.disabled = !active;
    this._ui.blocksCategoryBtn.disabled = !active;
    this._ui.canvas.style.opacity = active ? '1' : '0.5'; // Visual feedback for disabled state
    this._ui.delayPerSegmentInput.disabled = !active;
    this._ui.delayPerRowInput.disabled = !active;
    this._ui.qualityFactorInput.disabled = !active;
    this._ui.autoClearBeforeDrawToggle.disabled = !active;
    // Status message updated when starting/stopping.
  }

  // NEW: Set active category
  _setCategory(category) {
    if (this._currentCategory === category) return; // No change needed

    this._currentCategory = category;
    this._currentIndex = 0; // Reset index when changing category
    this._updateCategoryButtons(); // Update active button styling
    this._loadMinecraftImage(this._currentCategory, this._currentIndex);
  }

  // NEW: Update category button styling
  _updateCategoryButtons() {
    this._ui.mobsCategoryBtn.classList.toggle('active', this._currentCategory === 'mobs');
    this._ui.itemsCategoryBtn.classList.toggle('active', this._currentCategory === 'items');
    this._ui.blocksCategoryBtn.classList.toggle('active', this._currentCategory === 'blocks');
  }

  _changeImage(delta) {
    const imagesInCurrentCategory = this._minecraftImages[this._currentCategory];
    if (imagesInCurrentCategory.length === 0) {
      this.notify("warning", `No hay objetos en la categoría '${this._currentCategory}'.`);
      return;
    }

    let newIndex = this._currentIndex + delta;

    if (newIndex < 0) newIndex = imagesInCurrentCategory.length - 1;
    if (newIndex >= imagesInCurrentCategory.length) newIndex = 0;

    this._loadMinecraftImage(this._currentCategory, newIndex);
  }

  _updateInfo() {
    const imagesInCurrentCategory = this._minecraftImages[this._currentCategory];
    const categoryNameMap = {
      mobs: "Mob",
      items: "Item",
      blocks: "Bloque"
    };
    const categoryDisplayName = categoryNameMap[this._currentCategory] || "Objeto";

    this._ui.info.textContent = `${categoryDisplayName} #${this._currentIndex + 1} / ${imagesInCurrentCategory.length}`;
    this._ui.iconIdInput.max = imagesInCurrentCategory.length; // Update max for input field
  }

  _loadMinecraftImage(category, index) {
    const imagesInCurrentCategory = this._minecraftImages[category];
    if (!imagesInCurrentCategory || imagesInCurrentCategory.length === 0) {
      this.notify("error", `No hay imágenes en la categoría '${category}'.`);
      const ctx = this._ui.canvas.getContext("2d");
      ctx.clearRect(0, 0, 96, 96);
      ctx.fillStyle = "#d00";
      ctx.font = "14px Arial";
      ctx.fillText("NO IMG", 18, 55);
      this._canvasBrushImg = null;
      this._ui.status.textContent = `Error: No hay imágenes para ${category}.`;
      this._updateInfo(); // Update info to reflect 0/0
      return;
    }

    // Ensure index is within bounds (0-based)
    index = this._clamp(index, 0, imagesInCurrentCategory.length - 1);
    this._currentCategory = category;
    this._currentIndex = index;
    this._updateInfo();
    this._ui.iconIdInput.value = index + 1; // Update the input field (1-based for UI)

    const ctx = this._ui.canvas.getContext("2d");
    ctx.clearRect(0, 0, 96, 96); // Clear preview canvas
    const img = new window.Image();
    img.crossOrigin = "anonymous"; // Important for CORS for images from different domains
    img.onload = () => {
      ctx.clearRect(0, 0, 96, 96);
      ctx.drawImage(img, 0, 0, 96, 96); // Draw to fit preview canvas
      this._canvasBrushImg = img;
      this._ui.status.textContent = "Listo. Haz click en el canvas principal para dibujar.";
    };
    img.onerror = (e) => { // Added 'e' to access error event details
      ctx.clearRect(0, 0, 96, 96);
      ctx.fillStyle = "#d00";
      ctx.font = "14px Arial";
      ctx.fillText("NO IMG", 18, 55);
      this._canvasBrushImg = null;
      const failedUrl = imagesInCurrentCategory[index];
      console.error(`Error al cargar imagen de Minecraft (Categoría: ${category}, Índice: ${index}, URL: ${failedUrl}):`, e);
      this.notify("error", `Fallo al cargar la imagen de ${category} #${index+1}. (Ver consola para detalles)`);
      this._ui.status.textContent = "Error al cargar imagen del objeto.";
    };
    img.src = imagesInCurrentCategory[index];
  }

  // Gets the active game WebSocket (shared helper)
  _getGameSocket() {
    if (globalThis.sockets && globalThis.sockets.length > 0) {
      return globalThis.sockets.find(s =>
        s.url.includes("drawaria.online/socket.io") && s.readyState === WebSocket.OPEN
      );
    }
    return null;
  }

  // Helper for async delay (replaces Promise + setTimeout syntax)
  _delay(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  // Clamps a value between a min and max
  _clamp(value, min, max) {
    return Math.max(min, Math.min(max, value));
  }

  // Handles click events on the main game canvas to draw the image
  async _handleCanvasClick(ev) {
    if (this._isDrawingCube) { // Prevent multiple simultaneous drawings
      this.notify("warning", "Ya se está dibujando un objeto. Espera a que termine.");
      return;
    }
    if (!this._isActive) { // Only proceed if module is active
      this.notify("warning", "El módulo Minecraft Drawer está inactivo. Actívalo primero.");
      return;
    }
    if (!this._canvasBrushImg) {
      this.notify("warning", "No hay imagen cargada para dibujar.");
      return;
    }
    const socket = this._getGameSocket();
    if (!socket) {
      this.notify("error", "No hay WebSocket principal. Conéctate a la sala para usar el pincel de Minecraft.");
      this._ui.status.textContent = "¡Sin conexión!";
      return;
    }
    // Ensure _mainCanvas is valid before proceeding
    if (!this._mainCanvas) {
      this.notify("error", "El canvas principal no está disponible. No se puede dibujar el objeto.");
      return;
    }

    this._isDrawingCube = true; // Set drawing flag

    const rect = this._mainCanvas.getBoundingClientRect();
    // Coordenadas del click en píxeles del canvas visible (escalado por CSS)
    const clickX_display_px = ev.clientX - rect.left;
    const clickY_display_px = ev.clientY - rect.top;

    // Convertir coordenadas del display a píxeles internos del canvas de Drawaria
    const scaleToInternalCanvas = this._mainCanvas.width / rect.width;
    const clickX_internal_px = clickX_display_px * scaleToInternalCanvas;
    const clickY_internal_px = clickY_display_px * scaleToInternalCanvas;

    // Get optimization settings from UI
    const delayPerSegment = parseInt(this._ui.delayPerSegmentInput.value) || 80; // Default updated to match UI value
    const delayPerRow = parseInt(this._ui.delayPerRowInput.value) || 100; // Default updated to match UI value
    const qualityFactor = parseInt(this._ui.qualityFactorInput.value) || 6; // Default updated to match UI value
    const autoClear = this._ui.autoClearBeforeDrawToggle.checked;

    this._ui.status.textContent = "Pintando objeto Minecraft...";
    this.notify("info", "Dibujando objeto Minecraft en el lienzo...");

    if (autoClear) {
      await this._clearCanvas(socket); // Clear before drawing
      await this._delay(100); // Give a small moment for clear to process
    }

    // Usamos un canvas temporal para obtener los datos de píxeles del cubo cargado
    const tempCanvas = document.createElement("canvas");
    tempCanvas.width = this._canvasBrushImg.width;
    tempCanvas.height = this._canvasBrushImg.height;
    const tempCtx = tempCanvas.getContext("2d");
    tempCtx.drawImage(this._canvasBrushImg, 0, 0);

    const imgData = tempCtx.getImageData(0, 0, tempCanvas.width, tempCanvas.height).data;

    // Define el tamaño objetivo del objeto a dibujar en el lienzo del juego (en píxeles del canvas de Drawaria)
    // Un tamaño de 64x64 píxeles del asset es un buen balance.
    const targetImageWidth_px = 64; // Tamaño en píxeles internos del canvas de Drawaria
    const scaleFactor_drawing = targetImageWidth_px / tempCanvas.width; // Escala del asset original al tamaño objetivo

    let currentLineStart_asset_px = null; // Pixel 'x' de inicio de la línea en el asset original
    let currentLineColor = null;
    let totalLinesDrawn = 0;

    for (let py = 0; py < tempCanvas.height; py += qualityFactor) { // Apply qualityFactor to rows
      if (!this._isActive) { // Allow stopping mid-draw
        this.notify("info", "Dibujo de objeto detenido por el usuario.");
        break;
      }

      currentLineStart_asset_px = null; // Reset for each row
      currentLineColor = null;

      for (let px = 0; px < tempCanvas.width; px += qualityFactor) { // Apply qualityFactor to columns
        const idx = (py * tempCanvas.width + px) * 4;
        const r = imgData[idx],
          g = imgData[idx + 1],
          b = imgData[idx + 2],
          a = imgData[idx + 3];
        const hexColor = "#" + [r, g, b].map(v => v.toString(16).padStart(2, '0')).join('');

        if (a > 10) { // Si el pixel del asset es visible (no transparente)
          if (currentLineStart_asset_px === null) {
            currentLineStart_asset_px = px;
            currentLineColor = hexColor;
          } else if (hexColor !== currentLineColor) {
            // Fin de la línea actual: dibujar el segmento anterior
            const startX_draw = clickX_internal_px + (currentLineStart_asset_px - tempCanvas.width / 2) * scaleFactor_drawing;
            const startY_draw = clickY_internal_px + (py - tempCanvas.height / 2) * scaleFactor_drawing;
            const endX_draw = clickX_internal_px + (px - qualityFactor - tempCanvas.width / 2) * scaleFactor_drawing; // 'px - qualityFactor' para terminar antes del pixel actual
            const endY_draw = startY_draw; // Misma Y para línea horizontal

            const thickness = Math.max(1, Math.round(scaleFactor_drawing * qualityFactor)); // Thickness scales with quality
            this._sendDrawCommand(socket, [startX_draw, startY_draw], [endX_draw, endY_draw], currentLineColor, thickness);
            totalLinesDrawn++;

            // Iniciar nueva línea
            currentLineStart_asset_px = px;
            currentLineColor = hexColor;
          }
        } else { // Pixel transparente
          if (currentLineStart_asset_px !== null) {
            // Fin de la línea por transparencia
            const startX_draw = clickX_internal_px + (currentLineStart_asset_px - tempCanvas.width / 2) * scaleFactor_drawing;
            const startY_draw = clickY_internal_px + (py - tempCanvas.height / 2) * scaleFactor_drawing;
            const endX_draw = clickX_internal_px + (px - qualityFactor - tempCanvas.width / 2) * scaleFactor_drawing;
            const endY_draw = startY_draw;

            const thickness = Math.max(1, Math.round(scaleFactor_drawing * qualityFactor));
            this._sendDrawCommand(socket, [startX_draw, startY_draw], [endX_draw, endY_draw], currentLineColor, thickness);
            totalLinesDrawn++;

            currentLineStart_asset_px = null;
            currentLineColor = null;
          }
        }
      }
      // Al final de cada fila, dibujar el último segmento si existe
      if (currentLineStart_asset_px !== null) {
        const startX_draw = clickX_internal_px + (currentLineStart_asset_px - tempCanvas.width / 2) * scaleFactor_drawing;
        const startY_draw = clickY_internal_px + (py - tempCanvas.height / 2) * scaleFactor_drawing;
        const endX_draw = clickX_internal_px + (tempCanvas.width - 1 - tempCanvas.width / 2) * scaleFactor_drawing; // Hasta el final de la fila del asset
        const endY_draw = startY_draw;

        const thickness = Math.max(1, Math.round(scaleFactor_drawing * qualityFactor));
        this._sendDrawCommand(socket, [startX_draw, startY_draw], [endX_draw, endY_draw], currentLineColor, thickness);
        totalLinesDrawn++;

        currentLineStart_asset_px = null;
        currentLineColor = null;
      }
      // Pausar después de cada fila para no sobrecargar el servidor
      await this._delay(delayPerRow);
    }

    this._isDrawingCube = false; // Reset drawing flag
    this.notify("success", `Objeto Minecraft dibujado en el canvas (${totalLinesDrawn} líneas). Visible para todos.`);
    this._ui.status.textContent = "Listo. Haz click de nuevo para otro objeto.";
  }



  _hookCanvasClick() {
    // Ensure _mainCanvas is valid before attaching listener
    if (!this._mainCanvas) {
      this._mainCanvas = document.getElementById("canvas"); // Try to get it again
      if (!this._mainCanvas) {
        this.notify("error", "Canvas principal no disponible. No se puede adjuntar el click handler.");
        return;
      }
    }

    if (!this._canvasClickHandlerAttached) { // Only attach if not already attached
      this._mainCanvas.addEventListener("click", this._canvasClickHandler);
      this._canvasClickHandlerAttached = true; // Flag to track attachment
      this.notify("info", "Click handler para Minecraft Drawer habilitado.");
    }
  }



  _unhookCanvasClick() {
    if (this._mainCanvas && this._canvasClickHandlerAttached) {
      this._mainCanvas.removeEventListener("click", this._canvasClickHandler);
      this._canvasClickHandlerAttached = false; // Reset flag
      this.notify("info", "Click handler para Minecraft Drawer deshabilitado.");
    }
  }



  // Send a draw command to the game server (coordinates are in pixel values of the mainCanvas)
  _sendDrawCommand(socket, start_px, end_px, color, thickness, isEraser = false) { // Added isEraser parameter
    // Ensure _mainCanvas is valid before using its dimensions
    if (!this._mainCanvas) {
      console.error("Error: _mainCanvas is null in _sendDrawCommand. Cannot send drawing command.");
      return;
    }

    // Convert pixel coordinates to normalized (0-1) for the server
    const normX1 = (start_px[0] / this._mainCanvas.width);
    const normY1 = (start_px[1] / this._mainCanvas.height);
    const normX2 = (end_px[0] / this._mainCanvas.width);
    const normY2 = (end_px[1] / this._mainCanvas.height);

    // Draw locally for immediate feedback (optional for this tool, as it paints over)
    const ctx = this._mainCanvas.getContext('2d');
    ctx.strokeStyle = color;
    ctx.lineWidth = thickness;
    ctx.lineCap = 'round';
    ctx.beginPath();
    ctx.moveTo(start_px[0], start_px[1]);
    ctx.lineTo(end_px[0], end_px[1]);
    ctx.stroke();

    // Send command to the game server
    // thickness is positive here, but sent as negative in the protocol for draw
    socket.send(`42["drawcmd",0,[${normX1.toFixed(4)},${normY1.toFixed(4)},${normX2.toFixed(4)},${normY2.toFixed(4)},${isEraser},${0 - thickness},"${color}",0,0,{}]]`);
  }



  // Clears the canvas for all (and locally)
  async _clearCanvas(socket) {
    if (!this._mainCanvas || !this._mainCanvas.getContext('2d')) {
      this.notify("error", "Canvas not found, cannot clear locally.");
      return;
    }
    if (!socket || socket.readyState !== WebSocket.OPEN) {
      this.notify("error", "Not connected to Drawaria. Cannot send clear command to server.");
      return;
    }

    const ctx = this._mainCanvas.getContext('2d');
    ctx.clearRect(0, 0, this._mainCanvas.width, this._mainCanvas.height);

    this.notify("info", "Enviando comandos de limpieza...");
    const clearThickness = 2000;
    const clearColor = '#FFFFFF';
    const steps = 5;

    for (let i = 0; i <= steps; i++) {
      this._sendDrawCommand(socket, [0.01, (i / steps)], [0.99, (i / steps)], clearColor, clearThickness, true); // true for isEraser
      await this._delay(5);
      this._sendDrawCommand(socket, [(i / steps), 0.01], [(i / steps), 0.99], clearColor, clearThickness, true); // true for isEraser
      await this._delay(5);
    }
    this.notify("success", "Lienzo limpiado para todos.");
  }


  // Cube Engine calls this when module is hidden
  onHideContent() {
    this._unhookCanvasClick();
    this.notify("info", "Pincel de Minecraft desactivado al ocultar el módulo.");
  }



  // Cube Engine calls this when module is shown
  onShowContent() {
    // Only hook if the module is currently active.
    // If it was inactive when hidden, it should remain inactive until explicitly activated by the user.
    if (this._isActive) {
      this._hookCanvasClick();
      this.notify("info", "Pincel de Minecraft activado. Haz clic en el lienzo principal.");
    }
  }

}


// Ensure QBit.Styles is defined for CSS rules before the class definition
// (This is usually handled by the main Cube Engine script, but good to ensure context)
if (typeof QBit === 'undefined' || !QBit.Styles) {
    console.error("QBit or QBit.Styles is not defined. Ensure Cube Engine is loaded first.");
}

// --- Sub-Module 14: WordHelperTool ---
// Allows users to easily input words into chat and provides word suggestions.
class WordHelperTool extends QBit {
    // Removed static dummy1 and dummy2 as per user request.
    // The module will be loaded via DrawariaTools.loadExtension.
    // Private properties for internal state and UI elements
    _isActive = false; // Toggle state for the module
    _allWordLists = {
        "en": [
            "about", "acid", "acorn", "act", "actor", "actress", "add", "addition", "address", "aeroplane", "afternoon", "air conditioner", "air", "airbag", "aircraft carrier", "aircraft", "airline", "airport", "alarm clock", "alley", "alligator", "ammo", "ammunition", "angle", "angry", "animal", "ankle", "answer", "ant", "anteater", "antelope", "anvil", "ape", "apple", "apron", "arch", "area", "arm", "armadillo", "armor", "armory", "armpit", "arms", "army", "arrow", "arsenal", "art", "artillery", "ash", "asphalt", "ate", "attack", "aubergine", "auto", "automatic", "automobile", "autumn", "average", "axe", "axis", "baby", "back door", "back", "bacon", "bag", "bake", "balance", "ball", "ballet", "bamboo", "banana", "band", "bar", "barbecue", "barracks", "barrier", "base", "baseball", "basement", "basin", "basket", "basketball", "bat", "bath", "bathing suit", "bathroom", "bathtub", "battery", "battle", "battlefield", "battleship", "bay", "beach", "beachball", "beam", "beans", "bear", "beautiful", "beaver", "bed", "bedroom", "bee", "beech", "beef", "beehive", "beer", "beets", "bell", "belly button", "below zero", "belt", "berries", "berry", "between", "bicycle", "bike", "bikini", "bin", "birch", "bird", "biscuit", "bison", "bit", "bite", "bitter", "black", "blackberry", "blade", "blender", "blinds", "blizzard", "blouse", "blow", "blue", "blueberry", "boar", "board", "boat", "body", "boil", "bolt", "bomb", "bone", "book", "bookmark", "bookshelf", "boot", "border", "bottle", "bow and arrow", "bow", "bowl", "box", "boy", "bra", "brain", "brake", "brakes", "branch", "brass", "bread", "breadcrumbs", "breakfast", "breast", "breath", "brick", "bridge", "bright", "brilliant", "broccoli", "brook", "broom", "brown", "brush", "bucket", "bud", "building", "bulb", "bull", "bullet", "bunny", "buns", "burn", "burst", "bus", "bush", "business", "butter", "butterfly", "button", "buy", "cab", "cabin", "cactus", "cake", "calculator", "calendar", "camel", "camera", "camouflage", "camp", "canal", "canary", "candle", "candy", "cannon ball", "cannon", "canvas", "captain", "car", "card", "cardigan", "carpet", "carriage", "carrot", "cart", "cartoon", "cash", "cat", "catapult", "cattle", "cave", "cavern", "cd-rom", "ceiling", "cellar", "chain", "chair", "chalk", "channel", "chasm", "chat", "cheap", "cheek", "cheese", "chef", "chemical", "cherry", "chess", "chest", "chestnut", "chew", "chick", "chicken", "chief", "chili", "chimney", "chin", "chips", "chisel", "chocolate", "circle", "circus", "citrus", "clam", "classroom", "clean", "clear", "click", "cliff", "clock", "closet", "cloth", "clothes", "cloud", "cloves", "club", "coach", "coal", "coast", "coat", "cobbler", "cocktail", "coconut", "coffee", "cold", "collar", "color", "column", "comb", "combat", "comedy", "commander", "commercial", "company", "compass", "computer", "concert", "concrete", "conflict", "connect", "connection", "continent", "contrast", "control", "convoy", "cook", "cookbook", "cookie", "copper", "copy", "cord", "cork", "corkscrew", "corn", "corner", "cornflakes", "corridor", "cosine", "costume", "cottage", "cotton", "couch", "cough", "country", "courgette", "court", "cover", "cow", "crab", "crack", "crackers", "crash", "crayons", "cream", "credit", "creek", "crew", "crime", "crisp", "crossing", "crunch", "crush", "crust", "cube", "cucumber", "cuisine", "cup", "cupboard", "cupcake", "currants", "currency", "current", "curtain", "curtains", "curve", "cushion", "cut", "cutlery", "cycle", "cyclist", "cylinder", "dairy products", "dairy", "dance", "dandelion", "danger", "dark", "darkness", "date", "dawn", "deep", "deer", "degree", "delete", "dent", "desert", "desk", "dessert", "destruction", "dial", "diameter", "dictionary", "diet", "digestion", "digital", "dim", "dining room", "dinner", "dip", "direction", "directory", "dirty", "disco", "discussion", "dish towel", "dish washer", "dish", "dishwasher", "display", "distance", "dive", "divide", "diving", "division", "dock", "dog", "doghouse", "doll", "dollar", "dolphin", "dome", "donkey", "donut", "door bell", "door", "doorbell", "doorknob", "doorway", "dough", "down", "downstairs", "drain", "drama", "drapes", "drawer", "dress", "dried", "drill", "drink", "drinks", "drive", "driver", "drop", "drum", "drumsticks", "dry", "dryer", "duck", "dune", "dusk", "dust", "dvd", "ear", "early", "earring", "earth", "east", "eat", "edge", "education", "egg", "eggplant", "elastic", "elbow", "electric", "electronic", "elephant", "eleven", "elk", "ellipse", "elm", "email", "encyclopedia", "enemy", "engine", "english", "enter", "entrance", "entry", "envelope", "equal", "equator", "eraser", "error", "euro", "even", "evening", "exam", "exit", "expansion", "experiment", "eye", "face", "fact", "fall", "fan", "farm", "farmer", "fashion", "fast", "fat", "faucet", "fax", "feast", "feather", "fed", "feet", "female", "fence", "ferret", "ferry", "fiction", "field", "fig", "fight", "film", "finger", "fins", "fir", "fire", "fireplace", "first", "fish", "fishing", "fitness", "fixed", "flag", "flame", "flare", "flash", "flat", "flight", "flood", "floor", "flour", "flower", "fly", "focus", "fog", "fold", "folder", "food", "foot", "football", "force", "forehead", "forest", "fork", "form", "formula", "fort", "foundation", "fountain", "fox", "fraction", "frame", "free", "freezer", "fridge", "friend", "fries", "frog", "front door", "front", "fruit", "fry", "frying pan", "fuel", "full", "furnace", "furniture", "fuse box", "future", "gallery", "game", "gap", "garage door", "garage", "garbage", "garden", "garlic", "gas", "gasoline", "gate", "gear", "general", "geography", "geometry", "get", "geyser", "ginger", "giraffe", "girl", "give", "glacier", "glass", "glasses", "globe", "glove", "gloves", "glue", "goal", "goat", "gold", "golf", "good", "goose", "gps", "grain", "grape", "grapefruit", "grapes", "graph", "grass", "gray", "great", "greater", "green tea", "green", "greens", "grenade launcher", "grey", "grill", "grip", "grizzly bear", "group", "grow", "growth", "guest", "guide", "guinea pig", "guitar", "gull", "gun", "gutters", "gym shoes", "gym", "gymnastics", "hair dryer", "hair", "hairbrush", "half", "hallway", "halo", "ham", "hamburger", "hammer", "hamster", "hand", "handle", "happy", "harbor", "hare", "hat", "hate", "haystack", "head", "headlights", "headline", "healthy", "heart", "heat", "hedgehog", "helicopter", "helmet", "help", "hen", "herbs", "hero", "hi-fi", "high", "highway", "hill", "hinge", "hip", "hippo", "hippopotamus", "history", "hit", "hive", "hockey", "hoe", "hog", "hole", "holiday", "hollow", "home", "homework", "honey", "hook", "hope", "horn", "horse", "hose", "hot sauce", "hot", "hotdog", "hotel", "hour", "house", "hovercraft", "humor", "hundred", "hundredth", "hunger", "hungry", "ice cream", "ice", "iceberg", "icecream", "icicle", "idea", "increase", "ink", "inlet", "inn", "insect", "instrument", "integer", "internet", "interview", "invent", "iron", "island", "ivy", "jam", "jaw", "jeans", "jelly", "jellyfish", "jet", "jewel", "jewellery", "join", "journey", "judge", "jug", "juice", "jump", "kangaroo", "keep", "ketchup", "kettle", "key", "keyboard", "kick", "kid", "kidney", "kilometer", "kind", "kiss", "kitchen", "kiwi", "knee", "knife", "knob", "knot", "koala", "kph", "ladder", "ladle", "lake", "lamb", "lamp", "land", "lane", "language", "laptop", "laser", "laugh", "laundry", "lavatory", "law", "lawn mower", "lawn", "lead", "leaf", "learn", "leather", "left", "leg", "lemon", "lemonade", "lemur", "leopard", "lesson", "letter", "level", "library", "lift", "light switch", "light", "lightning", "lights", "like", "lime", "line", "lion", "lip", "lipstick", "liquid", "list", "liver", "living room", "loaf", "lobster", "lock", "loft", "lollipop", "long", "look", "loop", "loud", "lounge", "love", "low", "lunch", "machine", "magazine", "magic", "mailbox", "maize", "make-up", "male", "mallet", "mammoth", "man", "mango", "map", "maple", "mark", "market", "marmalade", "marshmallow", "mass", "mat", "match", "math", "mathematician", "mathematics", "mayonnaise", "meadow", "meal", "mean", "meat", "meatball", "meatballs", "meatloaf", "medal", "median", "meeting", "melon", "melt", "memorize", "memory", "menu", "merge", "message", "metal", "mice", "microwave", "middle", "midnight", "mile", "mobile phone", "model", "mole", "money", "monkey", "month", "moon", "moose", "mop", "morning", "motion", "motor", "motorcycle", "mountain peak", "mountain", "mouse", "mouth", "move", "mph", "mug", "multiply", "murk", "muscle", "museum", "mushroom", "music", "mussel", "mustard", "nail", "name", "napkin", "narrow", "nature", "neck", "need", "needle", "negative", "neighbour", "nest", "net", "network", "new", "news", "newspaper", "night", "night-vision goggles", "noise", "noon", "north", "nose", "note", "notebook", "null", "number", "nursery", "nut", "oak", "oasis", "oat", "ocean", "octagon", "octopus", "odd", "offer", "office", "oil", "omelet", "onion", "online", "open", "opera", "orange", "orb", "orchestra", "order", "ordinal", "organ", "outdoors", "outlet", "outside", "oval", "oven", "owner", "oyster", "page", "paint", "painting", "palm tree", "palm", "pan", "pancake", "pantry", "pants", "paper", "parabola", "parallel", "parallelogram", "park", "parrot", "part", "pass", "passenger", "passport", "past", "pasta", "paste", "pattern", "pea pod", "pea", "peace", "peach", "peak", "peanut", "pear", "pedal", "pedestrian", "pen", "pencil", "pepper", "percent", "performance", "perfume", "perimeter", "perpendicular", "person", "petrol", "phone", "photo", "photogragh", "physical", "pickaxe", "pickle", "picnic", "picture frame", "picture", "pie", "pig", "pillow", "pilot", "pin", "pineapple", "pinecone", "pinenut", "pink", "pipe", "pizza", "plain", "plane", "planet", "plant", "plate", "play", "playground", "pleasure", "plum", "plus", "pocket", "point", "poison", "polar bear", "police", "polish", "polished", "pond", "pool", "poor", "pop", "popcorn", "popsicle", "pork", "position", "post", "pot", "potato", "potatoes", "powder", "power", "present", "price", "print", "printer", "profit", "program", "programme", "project", "property", "public", "pudding", "puddles", "pull", "pump", "pumpkin", "punch", "punishment", "pupil", "purple", "push", "put", "pyramid", "quality", "question", "quick", "quiet", "rabbit", "radar", "radio", "radish", "radius", "rail", "railway", "rain", "rainbow", "raisin", "rake", "ram", "range", "rank", "raspberry", "rat", "rate", "raw", "ray", "razor", "reaction", "reason", "receipt", "recipe", "record", "recording", "rectangle", "red", "refridgerator", "refrigerator", "relation", "request", "rest", "restaurant", "results", "reward", "rhino", "rhombus", "ribs", "rice", "ride", "right", "ring", "rise", "river delta", "river", "road", "roast", "rock", "rocket", "rod", "roll", "rollover", "romantic", "roof", "room", "root", "rope", "rose", "rough", "round", "row", "rub", "rubbish", "rug", "rule", "ruler", "run", "rye", "safe", "sail", "sailboat", "sailor", "salad", "salmon", "salt water", "salt", "salute", "same", "sand dune", "sand", "sandals", "sandcastle", "sandwich", "sauce", "sausage", "saw", "saxophone", "say", "scale", "scarecrow", "scene", "school", "science", "scissors", "score", "screen", "screw", "screwdriver", "scuba", "sculpture", "scythe", "sea cave", "sea star", "sea", "seafood", "seagull", "seal", "season", "seat", "seaweed", "second", "secret", "secretary", "see", "seed", "self", "sell", "send", "sense", "separate", "serious", "servant", "sesame", "set", "shade", "shadow", "shaft", "shake", "sharp", "sharpener", "sheep", "sheet", "shelf", "shell", "shine", "shiny", "ship", "shirt", "shoe", "shoes", "shoot", "short", "shorts", "shot", "shoulder", "shovel", "shower", "shrimp", "shut", "side", "sign", "silk", "silver", "sine", "sink", "size", "ski", "skin", "skirt", "skunk", "sky", "sled", "sleep", "sleeve", "slice", "slide", "slip", "slope", "slow", "small", "smash", "smell", "smile", "smoke", "smooth", "snack", "snake", "sneeze", "sniper", "snow", "snowball", "snowboard", "snowfall", "snowflake", "snowman", "soap", "sock", "socks", "soda", "sofa", "soft", "software", "soldier", "sole", "solid", "song", "sound", "soup", "sour", "south", "space", "spade", "spaghetti", "spark", "speakers", "speed", "speedometer", "sphere", "spices", "spicy", "spider", "sponge", "spoon", "spray", "spring", "sprinkle", "square", "squash", "squid", "squirrel", "stadium", "stage", "staircase", "stairs", "stairway", "stamp", "star", "starfish", "start", "station", "steak", "steam", "steel", "stem", "step", "steps", "stew", "stick", "sticky", "stiff", "stockings", "stomach", "stone", "stop", "store", "storm", "story", "stove", "straight", "straw", "strawberry", "stream", "street", "strength", "stretch", "strong", "student", "studio", "submarine", "substance", "subtract", "subway", "sugar", "suit", "sum", "summer", "sun hat", "sun", "sunburn", "sunflower", "sunglasses", "sunhat", "sunlight", "sunny", "sunrise", "sunset", "supper", "support", "surf", "surprise", "sushi", "swamp", "sweater", "sweatshirt", "sweet", "swim", "swimming costume", "swimming pool", "swimsuit", "swine", "switch", "symbol", "symmetry", "syrup", "t-shirt", "table", "tail", "take", "talk", "tall", "tan", "tank", "tap", "tape", "taste", "tax", "taxi", "tea", "teacher", "team", "teapot", "teeth", "telephone", "television", "temperature", "tennis", "tent", "terminal", "terrain", "text", "theater", "thick", "thin", "thing", "thought", "thread", "threshold", "throat", "thumb", "thunder", "thunderstorm", "ticket", "tide", "tie", "tiger", "tight", "tights", "time", "tin", "tire", "tired", "toast", "toaster", "toe", "toilet paper", "toilet", "toll road", "tomato", "tongue", "tooth", "toothbrush", "toothpaste", "top", "torch", "touch", "tour", "tourist", "towel", "town", "toy", "trade", "train", "trainers", "tram", "transport", "trash can", "travel", "tray", "tree", "trees", "trick", "trim", "trip", "trousers", "truck", "tsunami", "tub", "tuba", "tummy", "tunnel", "turkey", "turn", "twist", "umbrella", "under", "underground", "underpants", "underwater", "underwear", "union", "unit", "upstairs", "use", "vacation", "vacuum cleaner", "value", "vampire bat", "vanilla", "variable", "vase", "vegetable", "vehicle", "vent", "vertex", "vessel", "vest", "video", "view", "vine", "vinegar", "viola", "violent", "violin", "visit", "vitamin", "voice", "volcano", "volleyball", "volume", "voyage", "wafer", "waffle", "waistcoat", "walk", "wall", "walnut", "walrus", "war", "wardrobe", "warm", "warrior", "wash", "washer", "washing machine", "waste basket", "waste", "watch", "water bottle", "water", "waterfall", "watermelon", "wave", "waves", "wax", "way", "weapon", "weather", "web", "week", "weekend", "weight", "welcome mat", "well", "west", "wet", "whale", "wheat", "wheel", "whip", "whistle", "white", "whiteboard", "wide", "wild", "wildlife", "willow", "win", "wind", "window", "wine", "wing", "winter", "wire", "wise", "wolf", "woman", "wood", "wool", "word", "work", "workbench", "world map", "worm", "wrench", "wrist", "wrong", "x-axis", "x-coordinate", "y-axis", "y-coordinate", "yacht", "yard", "year", "yellow light", "yellow", "yogurt", "yolk", "young", "zebra", "zero", "zoo"
        ].map(word => String(word).toLowerCase()),
        "es": [
            "abajo", "abeja", "abierto", "abismo", "acantilado", "acerca de", "acero", "ácido", "acto", "adición", "agrios", "agua", "agudo", "aguja", "agujero", "aire acondicionado", "ajo", "ala", "alacena", "albóndiga", "alce", "alfiler", "Alfombra de bienvenida", "alfombra", "algas marinas", "algodón", "alimentados", "almacenar", "almeja", "almuerzo", "alto", "amanecer", "amargo", "amarillo", "amigo", "amor", "amortiguar", "amplio", "ángulo", "anillo", "animal", "año", "antílope", "aplastar", "apoyo", "apretado", "apretón", "arándano", "árbol", "arco", "ardilla", "arena", "armadillo", "armario", "arriba", "arroyo", "Arroyo", "arroz", "Art º", "asado", "asiento", "áspero", "aspiradora", "ataque", "atrás", "audición", "aureola", "automático", "avión", "ayuda", "azúcar", "azul", "bahía", "bajo", "ballena", "bambú", "banda", "bandeja", "bandera", "bañera", "baño", "banquete", "bar", "barato", "barbilla", "barra", "barraviento", "base", "basura", "baya", "bebé", "beber", "bellota", "berenjena", "Beso", "biblioteca", "bien", "bisagra", "bisonte", "blanco", "bloquear", "boca", "bocadillo", "bodega", "bola", "boleto", "bolígrafo", "bollos", "bolsillo", "bolso", "bomba", "borde", "bota", "bote de basura", "bote", "botella", "botón", "brazo", "brecha", "brillante", "brillar", "brindis", "brócoli", "bueno", "bulbo", "buque", "Burro", "buzón", "caballo", "cabeza", "cable", "cabra", "cadena", "café", "caja de fusibles", "caja", "cajón", "calabacín", "calabaza", "calamar", "calcetín", "calentar", "calidad", "caliente", "calle", "calor", "calzoncillos", "cama", "cámara", "camarón", "camello", "caminar", "camino", "camisa", "campana", "campo", "canal", "canaletas", "canción", "cangilón", "cangrejo", "canguro", "cansado", "capa", "cara", "caramelo", "carbón", "carne de vaca", "carne", "carril", "carro", "carta", "Casa de perro", "casa", "cascada", "castaña", "castigo", "castor", "caverna", "cebolla", "cebra", "cena", "centeno", "cepillo", "cera", "cerca", "cerdo", "Cerdo", "cerebro", "Cereza", "cerrar", "cesta", "chaleco", "chile", "chispa", "chocolate", "chorizo", "cielo", "ciencia", "ciervo", "circulo", "ciruela", "claro", "clavos de olor", "clima", "coala", "cobre", "cocina", "cocinar", "cocinero", "Coco", "cohete", "cola", "colegio", "colina", "collar", "color", "columna", "comer", "comercio", "comida", "comienzo", "comió", "completo", "conducción", "conducto", "conejillo de indias", "Conejo", "conexión", "congelador", "conservar en vinagre", "continente", "contraste", "controlar", "copos de maíz", "corazón", "corcho", "Cordero", "Correcto", "correr", "corriente", "cortar", "corteza", "cortinas", "corto", "cosa", "costa", "costillas", "crecimiento", "crédito", "crema", "Crema", "crepe", "crimen", "crujido", "crujiente", "cuadrado", "cuadro", "cubrir", "cuchara", "cucharón", "cuchillo", "cuello", "cuenca", "cuenco", "cuerno", "cuero", "cuerpo", "cuesta abajo", "cueva del mar", "curva", "dar", "debajo", "decir", "dedo del pie", "dedo", "delfín", "Delgado", "Derecho", "desagüe", "desayuno", "desierto", "despensa", "destello", "destrucción", "desván", "detener", "diente de león", "dieta", "difícil", "digestión", "dinero", "dirección", "dirigir", "discusión", "disfraz", "distancia", "dividir", "división", "doblez", "Dom", "dormir", "ducha", "dulce", "Duna de arena", "duna", "dupdo", "ecuador", "edificio", "educación", "eje", "Ejército", "elástico", "eléctrico", "elefante", "empresa", "empujar", "enojado", "ensalada", "entrada", "Entrada", "Entre", "entrenadores", "entrenar", "enviar", "equilibrar", "erizo", "error", "escala", "escalera", "escenario", "Escoba", "escritura", "espacio", "espada", "espaguetis", "especias", "espejo", "esperando", "esperanza", "espolvorear", "esponja", "estación", "estaño", "estanque", "estante", "este", "estera", "estofado", "estómago", "estornudar", "estrecho", "estrella", "estufa", "expansión", "extraño", "falda", "feliz", "ficción", "fijo", "filete", "firmar", "físico", "flor", "florero", "formar", "foto", "frambuesa", "fregona", "freír", "freno", "frente", "fresa", "frijoles", "frío", "frotar", "Fruta", "fuego", "fuerte", "fuerza", "fumar", "Fundación", "futuro", "galleta", "Galleta", "galletas", "ganado", "gancho", "garaje", "garganta", "gato", "géiser", "gelatina", "general", "genial", "girasol", "giro", "glaciar", "glass", "glasses", "globo", "glove", "gloves", "glue", "goal", "goat", "gold", "golf", "good", "goose", "gps", "grain", "grape", "grapefruit", "grapes", "graph", "grass", "gray", "great", "greater", "green tea", "green", "greens", "grenade launcher", "grey", "grill", "grip", "grizzly bear", "group", "grow", "growth", "guest", "guide", "guinea pig", "guitar", "gull", "gun", "gutters", "gym shoes", "gym", "gymnastics", "hair dryer", "hair", "hairbrush", "half", "hallway", "halo", "ham", "hamburger", "hammer", "hamster", "hand", "handle", "happy", "harbor", "hare", "hat", "hate", "haystack", "head", "headlights", "headline", "healthy", "heart", "heat", "hedgehog", "helicopter", "helmet", "help", "hen", "herbs", "hero", "hi-fi", "high", "highway", "hill", "hinge", "hip", "hippo", "hippopotamus", "history", "hit", "hive", "hockey", "hoe", "hog", "hole", "holiday", "hollow", "home", "homework", "honey", "hook", "hope", "horn", "horse", "hose", "hot sauce", "hot", "hotdog", "hotel", "hour", "house", "hovercraft", "humor", "hundred", "hundredth", "hunger", "hungry", "ice cream", "ice", "iceberg", "icecream", "icicle", "idea", "increase", "ink", "inlet", "inn", "insect", "instrument", "integer", "internet", "interview", "invent", "iron", "island", "ivy", "jam", "jaw", "jeans", "jelly", "jellyfish", "jet", "jewel", "jewellery", "join", "journey", "judge", "jug", "juice", "jump", "kangaroo", "keep", "ketchup", "kettle", "key", "keyboard", "kick", "kid", "kidney", "kilometer", "kind", "kiss", "kitchen", "kiwi", "knee", "knife", "knob", "knot", "koala", "kph", "ladder", "ladle", "lake", "lamb", "lamp", "land", "lane", "language", "laptop", "laser", "laugh", "laundry", "lavatory", "law", "lawn mower", "lawn", "lead", "leaf", "learn", "leather", "left", "leg", "lemon", "lemonade", "lemur", "leopard", "lesson", "letter", "level", "library", "lift", "light switch", "light", "lightning", "lights", "like", "lime", "line", "lion", "lip", "lipstick", "liquid", "list", "liver", "living room", "loaf", "lobster", "lock", "loft", "lollipop", "long", "look", "loop", "loud", "lounge", "love", "low", "lunch", "machine", "magazine", "magic", "mailbox", "maize", "make-up", "male", "mallet", "mammoth", "man", "mango", "map", "maple", "mark", "market", "marmalade", "marshmallow", "mass", "mat", "match", "math", "mathematician", "mathematics", "mayonnaise", "meadow", "meal", "mean", "meat", "meatball", "meatballs", "meatloaf", "medal", "median", "meeting", "melon", "melt", "memorize", "memory", "menu", "merge", "message", "metal", "mice", "microwave", "middle", "midnight", "mile", "mobile phone", "model", "mole", "money", "monkey", "month", "moon", "moose", "mop", "morning", "motion", "motor", "motorcycle", "mountain peak", "mountain", "mouse", "mouth", "move", "mph", "mug", "multiply", "murk", "muscle", "museum", "mushroom", "music", "mussel", "mustard", "nail", "name", "napkin", "narrow", "nature", "neck", "need", "needle", "negative", "neighbour", "nest", "net", "network", "new", "news", "newspaper", "night", "night-vision goggles", "noise", "noon", "north", "nose", "note", "notebook", "null", "number", "nursery", "nut", "oak", "oasis", "oat", "ocean", "octagon", "octopus", "odd", "offer", "office", "oil", "omelet", "onion", "online", "open", "opera", "orange", "orb", "orchestra", "order", "ordinal", "organ", "outdoors", "outlet", "outside", "oval", "oven", "owner", "oyster", "page", "paint", "painting", "palm tree", "palm", "pan", "pancake", "pantry", "pants", "paper", "parabola", "parallel", "parallelogram", "park", "parrot", "part", "pass", "passenger", "passport", "past", "pasta", "paste", "pattern", "pea pod", "pea", "peace", "peach", "peak", "peanut", "pear", "pedal", "pedestrian", "pen", "pencil", "pepper", "percent", "performance", "perfume", "perimeter", "perpendicular", "person", "petrol", "phone", "photo", "photogragh", "physical", "pickaxe", "pickle", "picnic", "picture frame", "picture", "pie", "pig", "pillow", "pilot", "pin", "pineapple", "pinecone", "pinenut", "pink", "pipe", "pizza", "plain", "plane", "planet", "plant", "plate", "play", "playground", "pleasure", "plum", "plus", "pocket", "point", "poison", "polar bear", "police", "polish", "polished", "pond", "pool", "poor", "pop", "popcorn", "popsicle", "pork", "position", "post", "pot", "potato", "potatoes", "powder", "power", "present", "price", "print", "printer", "profit", "program", "programme", "project", "property", "public", "pudding", "puddles", "pull", "pump", "pumpkin", "punch", "punishment", "pupil", "purple", "push", "put", "pyramid", "quality", "question", "quick", "quiet", "rabbit", "radar", "radio", "radish", "radius", "rail", "railway", "rain", "rainbow", "raisin", "rake", "ram", "range", "rank", "raspberry", "rat", "rate", "raw", "ray", "razor", "reaction", "reason", "receipt", "recipe", "record", "recording", "rectangle", "red", "refridgerator", "refrigerator", "relation", "request", "rest", "restaurant", "results", "reward", "rhino", "rhombus", "ribs", "rice", "ride", "right", "ring", "rise", "river delta", "river", "road", "roast", "rock", "rocket", "rod", "roll", "rollover", "romantic", "roof", "room", "root", "rope", "rose", "rough", "round", "row", "rub", "rubbish", "rug", "rule", "ruler", "run", "rye", "safe", "sail", "sailboat", "sailor", "salad", "salmon", "salt water", "salt", "salute", "same", "sand dune", "sand", "sandals", "sandcastle", "sandwich", "sauce", "sausage", "saw", "saxophone", "say", "scale", "scarecrow", "scene", "school", "science", "scissors", "score", "screen", "screw", "screwdriver", "scuba", "sculpture", "scythe", "sea cave", "sea star", "sea", "seafood", "seagull", "seal", "season", "seat", "seaweed", "second", "secret", "secretary", "see", "seed", "self", "sell", "send", "sense", "separate", "serious", "servant", "sesame", "set", "shade", "shadow", "shaft", "shake", "sharp", "sharpener", "sheep", "sheet", "shelf", "shell", "shine", "shiny", "ship", "shirt", "shoe", "shoes", "shoot", "short", "shorts", "shot", "shoulder", "shovel", "shower", "shrimp", "shut", "side", "sign", "silk", "silver", "sine", "sink", "size", "ski", "skin", "skirt", "skunk", "sky", "sled", "sleep", "sleeve", "slice", "slide", "slip", "slope", "slow", "small", "smash", "smell", "smile", "smoke", "smooth", "snack", "snake", "sneeze", "sniper", "snow", "snowball", "snowboard", "snowfall", "snowflake", "snowman", "soap", "sock", "socks", "soda", "sofa", "soft", "software", "soldier", "sole", "solid", "song", "sound", "soup", "sour", "south", "space", "spade", "spaghetti", "spark", "speakers", "speed", "speedometer", "sphere", "spices", "spicy", "spider", "sponge", "spoon", "spray", "spring", "sprinkle", "square", "squash", "squid", "squirrel", "stadium", "stage", "staircase", "stairs", "stairway", "stamp", "star", "starfish", "start", "station", "steak", "steam", "steel", "stem", "step", "steps", "stew", "stick", "sticky", "stiff", "stockings", "stomach", "stone", "stop", "store", "storm", "story", "stove", "straight", "straw", "strawberry", "stream", "street", "strength", "stretch", "strong", "student", "studio", "submarine", "substance", "subtract", "subway", "sugar", "suit", "sum", "summer", "sun hat", "sun", "sunburn", "sunflower", "sunglasses", "sunhat", "sunlight", "sunny", "sunrise", "sunset", "supper", "support", "surf", "surprise", "sushi", "swamp", "sweater", "sweatshirt", "sweet", "swim", "swimming costume", "swimming pool", "swimsuit", "swine", "switch", "symbol", "symmetry", "syrup", "t-shirt", "table", "tail", "take", "talk", "tall", "tan", "tank", "tap", "tape", "taste", "tax", "taxi", "tea", "teacher", "team", "teapot", "teeth", "telephone", "television", "temperature", "tennis", "tent", "terminal", "terrain", "text", "theater", "thick", "thin", "thing", "thought", "thread", "threshold", "throat", "thumb", "thunder", "thunderstorm", "ticket", "tide", "tie", "tiger", "tight", "tights", "time", "tin", "tire", "tired", "toast", "toaster", "toe", "toilet paper", "toilet", "toll road", "tomato", "tongue", "tooth", "toothbrush", "toothpaste", "top", "torch", "touch", "tour", "tourist", "towel", "town", "toy", "trade", "train", "trainers", "tram", "transport", "trash can", "travel", "tray", "tree", "trees", "trick", "trim", "trip", "trousers", "truck", "tsunami", "tub", "tuba", "tummy", "tunnel", "turkey", "turn", "twist", "umbrella", "under", "underground", "underpants", "underwater", "underwear", "union", "unit", "upstairs", "use", "vacation", "vacuum cleaner", "value", "vampire bat", "vanilla", "variable", "vase", "vegetable", "vehicle", "vent", "vertex", "vessel", "vest", "video", "view", "vine", "vinegar", "viola", "violent", "violin", "visit", "vitamin", "voice", "volcano", "volleyball", "volume", "voyage", "wafer", "waffle", "waistcoat", "walk", "wall", "walnut", "walrus", "war", "wardrobe", "warm", "warrior", "wash", "washer", "washing machine", "waste basket", "waste", "watch", "water bottle", "water", "waterfall", "watermelon", "wave", "waves", "wax", "way", "weapon", "weather", "web", "week", "weekend", "weight", "welcome mat", "well", "west", "wet", "whale", "wheat", "wheel", "whip", "whistle", "white", "whiteboard", "wide", "wild", "wildlife", "willow", "win", "wind", "window", "wine", "wing", "winter", "wire", "wise", "wolf", "woman", "wood", "wool", "word", "work", "workbench", "world map", "worm", "wrench", "wrist", "wrong", "x-axis", "x-coordinate", "y-axis", "y-coordinate", "yacht", "yard", "year", "yellow light", "yellow", "yogurt", "yolk", "young", "zebra", "zero", "zoo"
        ].map(word => String(word).toLowerCase()),
        "ru": [
            "порядковый", "шкаф", "виноград", "ножницы", "соединять", "обсуждение", "розовый", "кросовки", "онлайн", "туалет", "ножка", "сэндвич", "запись", "фитнес", "лоб", "потрогать", "значение", "оазис", "женщина", "девятнадцать", "карандаш", "конфликт", "компакт-диски", "карта мира", "сто", "собака", "ток", "голод", "высокий", "синий", "болт", "зебра", "плавать", "чек", "Часы", "турист", "путешествие", "заход солнца", "ключ", "удовольствие", "корабль", "киви", "искра", "боеприпасов", "асфальт", "береза", "ягода", "костюм", "суши", "морская свинка", "плечо", "ритм", "безопасный", "Пальто", "еда", "минус", "надежда", "напитки", "дюна", "морж", "нос", "желудь", "отрицательный", "Телефон", "сухари", "микроволновая печь", "лоток", "шея", "расческа", "учитель", "купальник", "лосось", "кора", "выиграть", "мультфильм", "бетон", "яблоко", "глаз", "плюс", "постель", "машина", "реактивный самолет", "параллельно", "Гаражная дверь", "мебель", "порог", "дыхание", "колготки", "континент", "аэропорт", "пончик", "воск", "часы", "черный", "ручка", "такси", "камин", "стакан", "диаметр", "судья", "свободно", "мусор", "кастрюля", "поездка", "бритва", "миллиона", "Фото", "подмышка", "городок", "темнота", "яд", "галерея", "каретка", "встреча", "дымоход", "пишу", "зефир", "арсенал", "ось", "холодильник", "Ежик", "служащий", "холм", "рыба", "утро", "сияние", "единица измерения", "неправильный", "локоть", "время", "плита", "шина", "шестнадцать", "пустыня", "коврик", "завтрак", "говорить", "зубная щетка", "программа", "удар", "лагерь", "трюк", "десятый", "группа", "звук", "жидкость", "шорты", "тень", "шестерня", "визит", "косинус", "пряный", "ягненок", "борьба", "маленький", "чеснок", "объем", "луч", "угол", "владелец", "февраль", "фиксированный", "портфолио", "бисквит", "рукав", "Работа", "скорость", "крем", "скат", "перекатывать", "магия", "плоский", "луг", "прочность", "служба поддержки", "гроза", "тысяча", "лось", "журнал", "огни", "налог", "олень", "замок", "лифт", "лето", "гвоздь", "быстрый", "смотреть", "фуфайка", "закладка", "проходить", "ребра", "вспышка", "правитель", "гаечный ключ", "транспорт", "кость", "провод", "ваза", "пещера", "слон", "сахар", "плавательный бассейн", "тысячный", "дверь", "справиться", "ластик", "автобус", "щетка для волос", "восемь", "игрушка", "целое число", "поп", "смысл", "марш", "железо", "актер", "каталог", "сказать", "акт", "минут", "мамонт", "сообщение", "карман", "домашнее задание", "чили", "коала", "физическое", "большой палец", "шесть", "муравьед", "серьезный", "сам", "уксус", "день отдыха", "шарнир", "мотыга", "труба", "смеситель", "мягкий", "солнце", "гора", "грудь", "зелень", "Новости", "мелки", "газонокосилка", "автомобиль", "леопард", "фестиваль", "новый", "Кальмар", "морская звезда", "Заголовок", "шоколад", "вкус", "моль", "происхождения",
            "броня", "жесткий", "выход", "нажмите", "прибыль", "мороженое", "темно", "смородина", "кондиционер", "губка", "наводнение", "ко", "ла", "ко", "роткая", "снег", "суп", "меры", "кетчуп", "солдат", "набирать номер", "желе", "месяц", "кувшин", "паук", "порядок", "валюта", "фрикаделька", "резать", "нить", "один", "прихожая", "километров в час", "первый", "телевидение", "помидор", "четыре", "вещь", "летать", "перпендикуляр", "чайник", "башмак", "свет", "фрукты", "цунами", "кислый", "лазер", "гиппопотам", "умножать", "молочные продукты", "почтовый ящик", "громить", "фен", "пистолет", "пеший туризм", "идти", "у-координата", "радиус", "видео", "Омар", "транспортное средство", "ранг", "змея", "назад", "детская площадка", "скульптура", "геометрия", "лимонад", "Топ", "гамбургер", "двенадцать", "причина", "семя", "Попкорн", "серый", "факт", "дискотека", "отдельный", "секретарь", "формула", "сквош", "верблюд", "петля", "электрический", "канарейка", "горчичный", "гостиная", "клуб", "питание", "ботинок", "погружение", "высушенный", "арбуз", "рулон", "лягушка", "острый соус", "черный ход", "платная дорога", "червь", "грузовая машина", "смех", "тур", "передняя дверь", "небо", "цена", "дверной проем", "реактивный самолет", "Пальто", "еда", "минус", "надежда", "напитки", "дюна", "морж", "нос", "желудь", "отрицательный", "Телефон", "сухари", "микроволновая печь", "лоток", "шея", "расческа", "учитель", "купальник", "лосось", "кора", "выиграть", "мультфильм", "бетон", "яблоко", "глаз", "плюс", "постель", "машина", "реактивный самолет", "параллельно", "Гаражная дверь", "мебель", "порог", "дыхание", "колготки", "континент", "аэропорт", "пончик", "воск", "часы", "черный", "ручка", "такси", "камин", "стакан", "диаметр", "судья", "свободно", "мусор", "кастрюля", "поездка", "бритва", "миллиона", "Фото", "подмышка", "городок", "темнота", "яд", "галерея", "каретка", "встреча", "дымоход", "пишу", "зефир", "арсенал", "ось", "холодильник", "Ежик", "служащий", "холм", "рыба", "утро", "сияние", "единица измерения", "неправильный", "локоть", "время", "плита", "шина", "шестнадцать", "пустыня", "коврик", "завтрак", "говорить", "зубная щетка", "программа", "удар", "лагерь", "трюк", "десятый", "группа", "звук", "жидкость", "шорты", "тень", "шестерня", "визит", "косинус", "пряный", "ягненок", "борьба", "маленький", "чеснок", "объем", "луч", "угол", "владелец", "февраль", "фиксированный", "портфолио", "бисквит", "рукав", "Работа", "скорость", "крем", "скат", "перекатывать", "магия", "плоский", "луг", "прочность", "служба поддержки", "гроза", "тысяча", "лось", "журнал", "огни", "налог", "олень", "замок", "лифт", "лето", "гвоздь", "быстрый", "смотреть", "фуфайка", "закладка", "проходить", "ребра", "вспышка", "правитель", "гаечный ключ", "транспорт", "кость", "провод", "ваза", "пещера", "слон", "сахар", "плавательный бассейн", "тысячный", "дверь", "справиться", "ластик", "автобус", "щетка для волос", "восемь", "игрушка", "целое число", "поп", "смысл", "марш", "железо", "актер", "каталог", "сказать", "акт", "минут", "мамонт", "сообщение", "карман", "домашнее задание", "чили", "коала", "физическое", "большой палец", "шесть", "муравьед", "серьезный", "сам", "уксус", "день отдыха", "шарнир", "мотыга", "труба", "смеситель", "мягкий", "солнце", "гора", "грудь", "зелень", "Новости", "мелки", "газонокосилка", "автомобиль", "леопард", "фестиваль", "новый", "Кальмар", "морская звезда", "Заголовок", "шоколад", "вкус", "моль", "происхождения"
        ].map(word => String(word).toLowerCase())
    };
    _isInstantWinActive = false; // Flag to control instant win batches
    // UI elements references (adjusted for QBit's domMake and internal referencing)
    _ui = {};
    // Observers (will be stored as properties to manage their lifecycle)
    _wordTipObserver = null;
    _refreshWordObserver = null;
    _infotextObserver = null;
    // We removed the original jQuery modal patching as it caused conflicts.
    // The module should not interfere with global jQuery behavior directly.
    constructor() {
        super("Drawaria Word Helper", '<i class="fas fa-book"></i>'); // Name and icon
        this._onStartup();
    }
    _onStartup() {
        // We removed the _fixModalAccessibility call here as its content was problematic.
        // If modal accessibility needs fixing, it should be done globally by Cube Engine,
        // or by a separate module specifically designed to patch Bootstrap/jQuery behavior safely.
        // Load the UI for the module
        this._loadInterface();
        // Initial setup for instant win stopper, using window.jQuery where needed.
        this._setupGlobalInstantWinStopperListeners();
        this.notify("info", "Módulo 'Drawaria Word Helper' cargado.");
    }
    // Removed _fixModalAccessibility as it caused conflicts.
    _loadInterface() {
        const container = domMake.Tree("div", { id: `${this.identifier}-container`, class: "module-section" });
        this.htmlElements.section.appendChild(container);
        // Module Toggle Button
        const moduleToggleGroup = domMake.Row({ class: "module-btn-group", style: "margin-bottom:10px;" });
        this._ui.moduleToggleButton = domMake.Button('<i class="fas fa-power-off"></i> Activar Word Helper');
        this._ui.moduleToggleButton.classList.add('module-toggle-button');
        this._ui.moduleToggleButton.addEventListener('click', () => this._toggleActiveState());
        moduleToggleGroup.appendChild(this._ui.moduleToggleButton);
        container.appendChild(moduleToggleGroup);
        // Hints Panel (initially hidden)
        this._ui.hintsPanel = domMake.Tree('div', {
            id: 'hintsPanel',
            style: 'display:none; background: #eeeeee; overflow-wrap: anywhere; border: 4px solid #eeeeee; border-radius: 2px; overflow-y: scroll; height: 180px; width:100%; margin: 8px 0 0 0; color: #3675ce;'
        });
        this._ui.hintsBox = domMake.Tree('span', { id: 'hintsBox' });
        this._ui.hintsPanel.appendChild(this._ui.hintsBox);
        container.appendChild(this._ui.hintsPanel); // Appended once to the module's container
        // Instant Win Button (initially disabled)
        this._ui.instantWinButton = domMake.Button('Adivinar Rapido', {
            id: 'instantWinButton',
            style: 'width:100%; padding: 5px; margin-top: 5px; background: #28a745; color: white; border: none; border-radius: 3px; cursor: pointer;'
        });
        this._ui.instantWinButton.disabled = true; // Disabled until module is active
        container.appendChild(this._ui.instantWinButton); // Appended once to the module's container
        // Add event listeners for UI elements
        this._setupEventListeners();
    }
    _setupEventListeners() {
        // Instant Win Button click handler
        this._ui.instantWinButton.addEventListener('click', this._handleInstantWinClick.bind(this));
        // Delegate click for hint words on the hintsPanel
        // Use standard DOM event listener and check target to replicate jQuery's .on() delegation
        this._ui.hintsPanel.addEventListener('click', (event) => {
            // Check if the clicked element is a hint link
            if (event.target.tagName === 'A' && event.target.classList.contains('hintClick')) {
                if (!this._isActive) return; // Only process if module is active
                const $inputChat = window.jQuery('#chatbox_textinput'); // Use window.jQuery for game input
                const $sendButton = window.jQuery('#chatattop-sendbutton'); // Use window.jQuery for game send button
                $inputChat.val(event.target.textContent); // Use textContent instead of innerHTML
                $sendButton.click();
               // this.notify("info", `Sugerencia enviada: "${event.target.textContent}"`);
            }
        });
    }
    _toggleActiveState() {
        this._isActive = !this._isActive;
        if (this._isActive) {
            this._ui.moduleToggleButton.innerHTML = '<i class="fas fa-power-off"></i> Desactivar Word Helper';
            this._ui.moduleToggleButton.classList.add('active');
            //this.notify("info", "Módulo 'Drawaria Word Helper' ACTIVADO.");
            this._activateWordHelperLogic();
        } else {
            this._ui.moduleToggleButton.innerHTML = '<i class="fas fa-power-off"></i> Activar Word Helper';
            this._ui.moduleToggleButton.classList.remove('active');
            //this.notify("info", "Módulo 'Drawaria Word Helper' DESACTIVADO.");
            this._deactivateWordHelperLogic();
        }
        // Update instant win button state based on _isActive
        this._ui.instantWinButton.disabled = !this._isActive;
    }
    _activateWordHelperLogic() {
        // Use window.jQuery for selecting game elements
        const $targetWord = window.jQuery('#targetword_tip');
        const $inputChat = window.jQuery('#chatbox_textinput');
        const $refreshListElem = window.jQuery('#wordchooser-refreshlist');
        const $rightBar = window.jQuery('#rightbar'); // Still using for positioning context from original
        // The hintsPanel and instantWinButton are now children of the module's container
        // and are NOT re-parented here. Their visibility is controlled by display style.
        this._ui.hintsPanel.style.display = 'block'; // Show main panels
        this._ui.instantWinButton.style.display = 'block';
        // Attach input listener for assist function
        $inputChat.on('input.wordhelper', this._assist.bind(this));
        // Observers for word tip and refresh list
        if ($targetWord.length && !this._wordTipObserver) {
            this._wordTipObserver = new MutationObserver((mutations) => {
                mutations.forEach((mutation) => {
                    // Check `this._isActive` within the observer callback
                    if (!this._isActive) return;
                    const isTargetWordHiddenCompletely = mutation.target.style.display === 'none';
                    if (!isTargetWordHiddenCompletely) { // Panel remains visible if not completely hidden
                        this._ui.hintsPanel.style.display = 'block';
                        this._ui.instantWinButton.style.display = 'block';
                        this._assist(); // Regenerate hints when word tip changes
                    } else {
                        // If targetWord is completely hidden (e.g., during drawing phase)
                        // the helper panel might become less relevant. We keep it active,
                        // but _assist() might show "no words" or just all words based on logic.
                    }
                });
            });
            this._wordTipObserver.observe($targetWord[0], { attributes: true, attributeFilter: ['style'], childList: true, subtree: true });
            //this.notify("debug", "Word tip observer activated.");
        }
        if ($refreshListElem.length && !this._refreshWordObserver) {
            this._refreshWordObserver = new MutationObserver((mutations) => {
                if (!this._isActive) return; // Check `this._isActive`
                if (mutations[0].target.disabled === false) {
                    setTimeout(() => this._assist(), 50); // Small delay to allow DOM to update
                }
            });
            this._refreshWordObserver.observe($refreshListElem[0], { attributes: true });
            //this.notify("debug", "Refresh word observer activated.");
        }
        // Observer for room type to start panel if keywords are present in infotext.
        const infotextElement = window.jQuery('#infotext')[0]; // Use window.jQuery
        const roomKeywords = /\слов|Palabras|Word/;
        if (infotextElement && !this._infotextObserver) {
            this._infotextObserver = new MutationObserver((mutations) => {
                mutations.forEach((mutation) => {
                    if (this._isActive && roomKeywords.test(mutation.target.textContent)) {
                        this.notify("debug", "Infotext indicates word room. Running assist().");
                        this._ui.hintsPanel.style.display = 'block';
                        this._ui.instantWinButton.style.display = 'block';
                        this._assist();
                    } else if (this._isActive && !roomKeywords.test(mutation.target.textContent)) {
                        window.jQuery(this._ui.hintsBox).empty(); // Use window.jQuery
                        window.jQuery(this._ui.hintsBox).append('<span style="color: gray;">No es una sala de palabras.</span>'); // Use window.jQuery
                    }
                });
            });
            this._infotextObserver.observe(infotextElement, { childList: true, subtree: true });
           // this.notify("debug", "Infotext observer activated.");
            // Immediate check in case already in a word room on module activation
            if (roomKeywords.test(infotextElement.textContent)) {
                //this.notify("debug", "Infotext already shows word room. Immediate assist().");
                this._ui.hintsPanel.style.display = 'block';
                this._ui.instantWinButton.style.display = 'block';
                this._assist();
            }
        }
        // this.notify("info", "Lógica del Word Helper activada.");
    }
    _deactivateWordHelperLogic() {
        const $inputChat = window.jQuery('#chatbox_textinput');
        // Hide main panels
        this._ui.hintsPanel.style.display = 'none';
        this._ui.instantWinButton.style.display = 'none';
        // Detach input listener
        $inputChat.off('input.wordhelper'); // Use namespace to detach only our listener
        // Disconnect observers
        if (this._wordTipObserver) {
            this._wordTipObserver.disconnect();
            this._wordTipObserver = null;
            //this.notify("debug", "Word tip observer deactivated.");
        }
        if (this._refreshWordObserver) {
            this._refreshWordObserver.disconnect();
            this._refreshWordObserver = null;
            //this.notify("debug", "Refresh word observer deactivated.");
        }
        if (this._infotextObserver) {
             this._infotextObserver.disconnect();
             this._infotextObserver = null;
             //this.notify("debug", "Infotext observer deactivated.");
        }
        // Ensure instant win spam is stopped
        this._stopInstantWinBatches();
        //this.notify("info", "Lógica del Word Helper desactivada.");
    }
    // --- Start Instant Win Stopper (Global Listener Setup) ---
    // This function will set up the global Socket.IO event hooks.
    // It should be called ONCE when the script loads, and `_isInstantWinActive`
    // will be checked inside `_stopInstantWinBatches`.
    _setupGlobalInstantWinStopperListeners() {
        if (this._globalStopperListenersSetup) return; // Only set up once per script execution
        // Use `window._io.events` directly as these are global game event hooks.
        // This assumes _io and _io.events are available globally by Drawaria.
        if (window._io && window._io.events) {
            const eventsToStop = [
                'mc_turn_wordguessed',   // Any player guesses the word
                'uc_turn_wordguessedself', // You guess the word
                'bc_turn_results',       // Turn results (word guessed or turn ended)
                'bc_turn_abort'          // Turn aborted
            ];
            eventsToStop.forEach(eventName => {
                // Store the original event handler to call it afterwards
                const originalEventHandler = window._io.events[eventName];
                // Use window.jQuery to bind events on `window._io.events` if it's the game's pattern
                if (typeof window.jQuery !== 'undefined') {
                    window.jQuery(window._io.events).on(eventName, (...args) => {
                        this._stopInstantWinBatches();
                        if (originalEventHandler) {
                            originalEventHandler(...args);
                        }
                    });
                } else { // Fallback to native addEventListener if jQuery is not available
                    // This assumes window._io.events is a direct DOM Node or EventTarget
                    // which is not typical for a plain object, but a safeguard.
                    if (typeof window._io.events.addEventListener === 'function') {
                        window._io.events.addEventListener(eventName, (...args) => {
                            this._stopInstantWinBatches();
                            if (originalEventHandler) {
                                originalEventHandler(...args);
                            }
                        });
                    } else {
                        // If window._io.events is a plain object, direct assignment is the only way
                        // (already handled by the initial `originalEventHandler` grab).
                        // No need for a second listener, as the original is already wrapped.
                    }
                }
            });
            this._globalStopperListenersSetup = true; // Mark as setup
            this.notify("debug", "Global Instant Win Stopper listeners hooked.");
        } else {
            this.notify("warning", "window._io.events no está disponible. La detención automática del Instant Win podría no funcionar.");
        }
    }
    // --- Helper to stop sending batches (called from global hooks and UI click) ---
    _stopInstantWinBatches() {
        if (this._isInstantWinActive) {
            this._isInstantWinActive = false;
            // Access window.jQuery elements for update
            if (typeof window.jQuery !== 'undefined' && window.jQuery(this._ui.instantWinButton).length) {
                window.jQuery(this._ui.instantWinButton).prop('disabled', false).text('Adivinar Rapido');
            }
            if (typeof window.jQuery !== 'undefined' && window.jQuery(this._ui.hintsBox).length) {
                window.jQuery(this._ui.hintsBox).prepend('<span style="color: green; font-weight: bold;">Envío de batches detenido.</span><br>');
            }
            this.notify("info", "Envío de batches detenido automáticamente.");
        }
    }
    // --- Helper to generate 99-char batches ---
    _generateNinetyNineCharBatches(wordList) {
        const batches = [];
        let currentBatch = "";
        const maxChars = 99; // Server imposed chat message limit
        if (!wordList || wordList.length === 0) {
           // this.notify("warning", "Lista de palabras vacía. No se generarán batches.");
            return [];
        }
        // Shuffle the word list to avoid sending the same order every time
        const shuffledWordList = [...wordList].sort(() => 0.5 - Math.random());
        for (let i = 0; i < shuffledWordList.length; i++) {
            const word = shuffledWordList[i];
            // If adding the word (with a preceding space if not the first word in batch) exceeds maxChars
            if (currentBatch.length === 0 || (currentBatch.length + 1 + word.length <= maxChars)) {
                currentBatch += (currentBatch.length === 0 ? "" : " ") + word;
            } else {
                // Current word doesn't fit, push current batch and start new one
                batches.push(currentBatch);
                currentBatch = word; // Start new batch with current word
            }
        }
        // Add the last batch if not empty
        if (currentBatch.length > 0) {
            batches.push(currentBatch);
        }
        //this.notify("debug", `Generados ${batches.length} batches de ${maxChars} caracteres.`);
        return batches;
    }
    // --- Handler for Instant Win button click ---
    async _handleInstantWinClick() {
        if (!this._isActive) return; // Only process if module is active
        if (this._isInstantWinActive) {
            // If already active, this click is to STOP
            this._isInstantWinActive = false; // Toggle off
            if (typeof window.jQuery !== 'undefined') {
                window.jQuery(this._ui.instantWinButton).prop('disabled', false).text('Adivinar Rapido');
            }
           // this.notify("info", 'Envío de batches detenido manualmente.');
            if (typeof window.jQuery !== 'undefined') {
                window.jQuery(this._ui.hintsBox).prepend('<span style="color: green; font-weight: bold;">Envío detenido manualmente.</span><br>');
            }
            return;
        }
        // If not active, this click is to START
        if (!this._allWordLists) {
            this.notify("error", "La lista de palabras no está disponible. Recarga la página.");
            alert('Error: La lista de palabras no está cargada. Por favor, recarga la página.');
            return;
        }
        const $inputChat = window.jQuery('#chatbox_textinput'); // Use window.jQuery for game input
        const $sendButton = window.jQuery('#chatattop-sendbutton'); // Use window.jQuery for game send button
        const gameLang = window.jQuery('#langselector').val(); // Get current game language from selector
        const wordsForLang = this._allWordLists[gameLang] || this._allWordLists['en']; // Fallback to English
        if (!wordsForLang || wordsForLang.length === 0) {
            this.notify("warning", `Lista de palabras vacía para el idioma '${gameLang}'.`);
            alert(`No se pudo encontrar la lista de palabras para el idioma '${gameLang}'.`);
            return;
        }
        const batchesToSend = this._generateNinetyNineCharBatches(wordsForLang);
        if (batchesToSend.length === 0) {
            this.notify("warning", "No se pudieron generar batches de palabras para enviar.");
            alert('No se pudieron generar batches para enviar. Intenta de nuevo.');
            return;
        }
        // this.notify("warning", `Iniciando envío de ${batchesToSend.length} batches agresivamente. ALTA PROBABILIDAD DE SILENCIAMIENTO/DESCONEXIÓN.`);
        if (typeof window.jQuery !== 'undefined') {
            window.jQuery(this._ui.instantWinButton).text('Detener Envío');
        }
        this._isInstantWinActive = true; // Activate the sending flag
        if (typeof window.jQuery !== 'undefined') {
            window.jQuery(this._ui.instantWinButton).prop('disabled', false);
        }
        const delayBetweenBatches = 700; // Delay in milliseconds between sending each batch
        for (let i = 0; i < batchesToSend.length; i++) {
            if (!this._isInstantWinActive) { // Check if the flag has been deactivated (by user or game event)
               // this.notify("info", "Proceso de envío de batches interrumpido.");
                break;
            }
            const batch = batchesToSend[i];
            // Ensure chat input is visible and enabled before attempting to send
            if ($inputChat.is(':hidden') || $inputChat.prop('disabled')) {
                this.notify("error", 'Chat input no está visible o está deshabilitado. Deteniendo envío.');
                this._isInstantWinActive = false; // Deactivate flag if sending fails
                break;
            }
            $inputChat.val(batch); // Set chat input value
            $sendButton.click(); // Simulate click on send button
            // this.notify("debug", `Enviado Batch ${i+1}/${batchesToSend.length}: ${batch.substring(0, 30)}...`);
            await new Promise(resolve => setTimeout(resolve, delayBetweenBatches)); // Pause
        }
        // Finalization of the cycle (either by completion or interruption)
        if (this._isInstantWinActive) { // If it finished without interruption
            this.notify("success", 'Proceso de envío de batches completado. Revisa el chat para el resultado.');
            if (typeof window.jQuery !== 'undefined') {
                window.jQuery(this._ui.hintsBox).prepend('<span style="color: green; font-weight: bold;">Envío de batches completado. Revisa el chat.</span><br>');
            }
        }
        if (typeof window.jQuery !== 'undefined') {
            window.jQuery(this._ui.instantWinButton).prop('disabled', false);
            window.jQuery(this._ui.instantWinButton).text('Adivinar Rapido');
        }
        this._isInstantWinActive = false; // Ensure flag is false at the end
    }
    // --- Helper function for displaying hints in the panel ---
    _assist() {
        if (!this._isActive) return; // Only run if module is active
        if (typeof window.jQuery === 'undefined') {
            this.notify("warning", "jQuery no está disponible para la función de asistencia.");
            return;
        }
        window.jQuery(this._ui.hintsBox).empty(); // Use window.jQuery
        if (!this._allWordLists) {
            window.jQuery(this._ui.hintsBox).append('<span style="color: gray;">Cargando lista de palabras...</span>'); // Use window.jQuery
            return;
        }
        const gameLang = window.jQuery('#langselector').val(); // Get current game language from selector
        const currentLangWordList = this._allWordLists[gameLang] || this._allWordLists['en']; // Fallback to English words
        if (!currentLangWordList || currentLangWordList.length === 0) {
            window.jQuery(this._ui.hintsBox).append(`<span style="color:red; font-weight:bold;">Lista de palabras no disponible para '${gameLang}'.</span>`); // Use window.jQuery
            return;
        }
        const $targetWord = window.jQuery('#targetword_tip'); // The element showing the target word pattern (e.g., _ _ _ _)
        const targetWordText = $targetWord.text();
        // Determine if the target word pattern is generic or empty/placeholder-like
        // This is the key modification to always show words.
        const isGenericPattern = !targetWordText ||
                                 targetWordText.replace(/\s/g, '').replace(/_/g, '').trim() === '' ||
                                 targetWordText.includes('?');
        let hints;
        if (isGenericPattern) {
            // If generic pattern, display ALL words from the list
            hints = currentLangWordList;
        } else {
            // If a specific pattern, filter words that match the pattern
            const regexPattern = targetWordText.replace(/\s/g, '').replace(/_/g, '.'); // Convert "_ _ A _ _" to ". . A . ."
            const wordRegex = new RegExp(`^${regexPattern}$`, 'i'); // Create case-insensitive regex
            hints = currentLangWordList.filter(word => wordRegex.test(word)); // Filter words matching the pattern
        }
        // Display hints in the hintsBox
        if (hints.length === 0) {
            window.jQuery(this._ui.hintsBox).append('<span style="color:red; font-weight:bold">Lo siento, no se encontró ninguna palabra!</span>'); // Use window.jQuery
        } else {
            window.jQuery(this._ui.hintsBox).append('<span style="color:green; font-weight:bold">Haz clic en cualquier palabra para enviarla: </span><br>'); // Use window.jQuery
            const inputVal = window.jQuery('#chatbox_textinput').val().toLowerCase(); // Current text in chat input (use window.jQuery)
            let matchingHints = hints.filter(hint => inputVal === '' || hint.toLowerCase().includes(inputVal));
            let nonMatchingHints = hints.filter(hint => inputVal !== '' && !hint.toLowerCase().includes(inputVal));
            // Sort matching hints: exact match first, then by length
            matchingHints.sort((a, b) => {
                const aExact = a.toLowerCase() === inputVal;
                const bExact = b.toLowerCase() === inputVal;
                if (aExact && !bExact) return -1;
                if (!aExact && bExact) return 1;
                return a.length - b.length; // Secondary sort by length
            });
            // Construct HTML for hints, making matching ones visually distinct
            let html = matchingHints.map(hint => `<a href="javascript:void(0);" class="hintClick">${hint}</a>`).join(', ');
            if (nonMatchingHints.length > 0) {
                // The style for non-matching hints is now handled by CSS via the .hintClick class
                html += (html ? ', ' : '') + nonMatchingHints.map(hint => `<a href="javascript:void(0);" class="hintClick">${hint}</a>`).join(', ');
            }
            window.jQuery(this._ui.hintsBox).append(html); // Use window.jQuery
        }
    }
    // Called by Cube Engine when the module's parent section is hidden
    onHideContent() {
        this._deactivateWordHelperLogic();
        this.notify("debug", "Word Helper logic deactivated due to module hide.");
    }
    // Called by Cube Engine when the module's parent section is shown
    onShowContent() {
        // If module was active, re-activate logic
        if (this._isActive) {
            this._activateWordHelperLogic();
            this.notify("debug", "Word Helper logic reactivated due to module show.");
        }
    }
}




})();

// ... (Your existing Cube Engine code and other modules) ...

// START Unified DOM Injection Modules
(function() {
    'use strict';
    const QBit = globalThis["QBit"];

    if (typeof QBit === 'undefined') {
        console.error("[Drawaria Modules] QBit no está definido. Los scripts no pueden inicializarse.");
        return;
    }

    // --- Global Styles for both modules ---
    QBit.Styles.addRules([
        `#transparentBrushControlPanel, #imageUploadControlPanel {
            background: none;
            padding: 5px 10px;
            border-radius: 0;
            font-family: sans-serif;
            color: #fff;
            user-select: none;
            width: 100%;
            box-sizing: border-box;
            line-height: normal;
        }`,
        `#transparentBrushControlPanel hr, #imageUploadControlPanel hr {
            border-color: rgba(255, 255, 255, 0.2);
            margin: 5px 0;
        }`,
        `#transparentBrushControlPanel h6, #imageUploadControlPanel h6 {
            margin: 5px 0 8px;
            text-align: left;
            font-size: 0.9em;
            color: #ccc;
            line-height: normal;
        }`,
        // Transparent Brush specific styles
        `#transparentBrushControlPanel .transparent-brush-toggle-btn {
            width: 100%;
            padding: 6px 0;
            border: none;
            border-radius: 5px;
            color: #fff;
            font-weight: bold;
            cursor: pointer;
            background: #492;
        }`,
        `#transparentBrushControlPanel .transparent-brush-toggle-btn.active {
            background: #2a5;
        }`,
        `#transparentBrushControlPanel .slider-container {
            margin-top: 10px;
            display: flex;
            align-items: center;
            justify-content: space-between;
            font-size: 0.9em;
            color: var(--CE-color);
        }`,
        `#transparentBrushControlPanel .slider-container label {
            margin-right: 8px;
            margin-bottom: 0;
            line-height: normal;
        }`,
        `#transparentBrushControlPanel .opacity-slider {
            flex-grow: 1;
            margin: 0 8px;
            -webkit-appearance: none;
            height: 6px;
            background: #ddd;
            outline: none;
            border-radius: 3px;
        }`,
        `#transparentBrushControlPanel .opacity-slider::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 16px;
            height: 16px;
            border-radius: 50%;
            background: #666;
            cursor: grab;
        }`,
        `#transparentBrushControlPanel .opacity-slider::-moz-range-thumb {
            width: 16px;
            height: 16px;
            border-radius: 50%;
            background: #666;
            cursor: grab;
        }`,
        `#transparentBrushControlPanel .percent-display {
            display: inline-block;
            min-width: 35px;
            text-align: right;
            font-size: 0.9em;
        }`,
        // Image Upload specific styles
        `@import url('https://fonts.googleapis.com/icon?family=Material+Icons');`,
        `.material-icons {
            font-family: 'Material Icons';
            font-weight: normal;
            font-style: normal;
            line-height: 1;
            letter-spacing: normal;
            text-transform: none;
            display: inline-block;
            white-space: nowrap;
        }`,
        `#imageUploadControlPanel .upload-btn {
            width: 100%;
            padding: 6px 0;
            border: none;
            border-radius: 5px;
            color: #fff;
            font-weight: bold;
            cursor: pointer;
            background: #007bff; /* Primary color */
        }`,
        `#imageUploadControlPanel .upload-btn i {
            font-size: 16px;
            vertical-align: middle;
            margin-right: 5px;
        }`,
        `#imageUploadControlPanel .upload-area {
            border: 2px dashed #ffffff80;
            border-radius: 5px;
            padding: 15px;
            text-align: center;
            font-size: 0.8em;
            color: #FF0000;
            margin-top: 10px;
            cursor: pointer;
            transition: border-color 0.2s ease, background-color 0.2s ease;
        }`,
        `#imageUploadControlPanel .upload-area.drag-over {
            border-color: #00ff00;
            background-color: #ffffff1a;
        }`
    ]);

    // --- TransparentBrushFunctionality Class ---
    class TransparentBrushFunctionality {
        _transparentMode = false;
        _customAlpha = 0.3;
        _ctx = null; // Canvas 2D context

        // UI element references
        _ui = {};

        constructor() {
            this._toggleTransparentMode = this._toggleTransparentMode.bind(this);
            this._updateSliderValue = this._updateSliderValue.bind(this);
            this._hookBrush = this._hookBrush.bind(this);
        }

        // Public method to be called by a global initializer
        _createUI() {
            const targetInsertionPoint = document.getElementById('downloadcanvas');
            if (!targetInsertionPoint) {
                // This module's UI depends on 'downloadcanvas' being present.
                // If it's not here, the global initializer will retry all modules.
                return false; // Signal that UI could not be created yet
            }

            const wrapper = document.createElement('div');
            wrapper.id = 'transparentBrushControlPanel';

            const separator = document.createElement('hr');
            separator.style.cssText = 'border-color: rgba(255, 255, 255, 0.2); margin: 5px 0;';
            wrapper.appendChild(separator);

            const title = document.createElement('h6');
            title.style.cssText = 'margin: 5px 0 8px; text-align: left; font-size: 0.9em; color: #ccc; line-height: normal;';
            title.textContent = 'Pincel Transparente';
            wrapper.appendChild(title);

            this._ui.toggleBtn = document.createElement('button');
            this._ui.toggleBtn.className = 'btn btn-block btn-sm transparent-brush-toggle-btn';
            this._ui.toggleBtn.style.cssText = 'width: 100%; padding: 6px 0; border: none; border-radius: 5px; color: #fff; font-weight: bold; cursor: pointer; background: #492;';
            this._ui.toggleBtn.textContent = 'Pincel: OFF';
            this._ui.toggleBtn.addEventListener('click', this._toggleTransparentMode);
            wrapper.appendChild(this._ui.toggleBtn);

            const sliderContainer = document.createElement('div');
            sliderContainer.className = 'slider-container';
            sliderContainer.style.cssText = 'margin-top: 10px; display: flex; align-items: center; justify-content: space-between; font-size: 0.9em; color: var(--CE-color);';

            const label = document.createElement('label');
            label.style.cssText = 'margin-right: 8px; margin-bottom: 0; line-height: normal;';
            label.textContent = 'Opacidad:';
            this._ui.slider = document.createElement('input');
            this._ui.slider.type = 'range';
            this._ui.slider.min = '0.05';
            this._ui.slider.max = '1';
            this._ui.slider.step = '0.01';
            this._ui.slider.value = '0.3';
            this._ui.slider.className = 'opacity-slider';
            this._ui.slider.style.cssText = 'flex-grow: 1; margin: 0 8px; -webkit-appearance: none; height: 6px; background: #ddd; outline: none; border-radius: 3px;';
            this._ui.slider.addEventListener('input', this._updateSliderValue);
            this._ui.percent = document.createElement('span');
            this._ui.percent.className = 'percent-display';
            this._ui.percent.style.cssText = 'display: inline-block; min-width: 35px; text-align: right; font-size: 0.9em;';
            this._ui.percent.textContent = '30%';

            sliderContainer.append(label, this._ui.slider, this._ui.percent);
            wrapper.appendChild(sliderContainer);

            targetInsertionPoint.parentNode.insertBefore(wrapper, targetInsertionPoint.nextSibling);

            if (typeof QBit !== 'undefined' && QBit.notify) {
                QBit.notify("info", "Panel de control de Pincel Transparente insertado en el menú.");
            } else {
                console.log("[Drawaria Transparency Script] Panel de control insertado en el menú.");
            }

            // After UI is inserted, try to hook canvas
            this._tryHookCanvas();
            return true; // Signal that UI was created
        }

        // --- Event handlers for UI controls ---
        _toggleTransparentMode() {
            this._transparentMode = !this._transparentMode;
            this._ui.toggleBtn.textContent = this._transparentMode ? 'Pincel: ON' : 'Pincel: OFF';
            this._ui.toggleBtn.classList.toggle('active', this._transparentMode);
            if (typeof QBit !== 'undefined' && QBit.notify) {
                QBit.notify("info", `Pincel transparente: ${this._transparentMode ? 'ON' : 'OFF'}`);
            } else {
                console.log(`[Drawaria Transparency Script] Pincel transparente: ${this._transparentMode ? 'ON' : 'OFF'}`);
            }
        }

        _updateSliderValue() {
            this._customAlpha = parseFloat(this._ui.slider.value);
            this._ui.percent.textContent = Math.round(this._customAlpha * 100) + "%";
            // console.log(`[Drawaria Transparency Script] Opacidad ajustada a: ${this._customAlpha}`);
        }

        // --- Brush hooking logic ---
        _hookBrush() {
            const canvas = document.querySelector('canvas');
            if (!canvas) {
                return false;
            }
            if (!canvas.getContext) {
                return false;
            }

            this._ctx = canvas.getContext('2d');
            if (!this._ctx) {
                return false;
            }
            if (this._ctx._pincelTransparenteHooked) {
                return true;
            }

            const origStroke = this._ctx.stroke;
            const origFill = this._ctx.fill;
            const self = this; // Capture 'this' context for use in overrides

            this._ctx.stroke = function() {
                let prevAlpha = this.globalAlpha;
                if (self._transparentMode) {
                    this.globalAlpha = self._customAlpha;
                }
                origStroke.apply(this, arguments);
                this.globalAlpha = prevAlpha;
            };

            this._ctx.fill = function() {
                let prevAlpha = this.globalAlpha;
                if (self._transparentMode) {
                    this.globalAlpha = self._customAlpha;
                }
                origFill.apply(this, arguments);
                this.globalAlpha = prevAlpha;
            };

            this._ctx._pincelTransparenteHooked = true;
            if (typeof QBit !== 'undefined' && QBit.notify) {
                QBit.notify("info", "Canvas context enganchado exitosamente!");
            } else {
                console.log("[Drawaria Transparency Script] Canvas context enganchado exitosamente!");
            }
            return true;
        }

        _tryHookCanvas() {
            let hookAttempts = 0;
            const maxHookAttempts = 60; // Try for 60 seconds

            const attemptHook = () => {
                if (!this._hookBrush()) {
                    hookAttempts++;
                    if (hookAttempts < maxHookAttempts) {
                        setTimeout(attemptHook, 1000);
                    } else {
                        if (typeof QBit !== 'undefined' && QBit.notify) {
                            QBit.notify("error", "No se pudo enganchar el canvas después de varios intentos. Asegúrate de estar en Drawaria.online.");
                        } else {
                            console.error("[Drawaria Transparency Script] No se pudo enganchar el canvas después de varios intentos. Asegúrate de estar en Drawaria.online.");
                        }
                    }
                }
            };

            if (document.readyState === 'loading') {
                document.addEventListener('DOMContentLoaded', attemptHook);
            } else {
                attemptHook();
            }
        }
    }

    // --- ImageUploadModule Class ---
    class ImageUploadModule {
        _mainCanvas = null;
        _ctx = null;
        _ui = {};

        constructor() {
            // No direct _initialize call here. It will be triggered by a global event.
        }

        // Public method to be called by a global initializer
        _createUI() {
            this._mainCanvas = document.getElementById('canvas');
            if (this._mainCanvas) {
                this._ctx = this._mainCanvas.getContext('2d');
            } else {
                console.warn("[Drawaria Image Upload] Canvas principal no encontrado al inicio. Algunas funciones pueden no estar disponibles.");
            }

            // Set fixed canvas dimensions as per original script
            this._resizeCanvas();

            // FIX: Target 'transparentBrushControlPanel' for insertion point, and insert AFTER it.
            const targetInsertionPoint = document.getElementById('transparentBrushControlPanel');
            if (!targetInsertionPoint) {
                // If the target element is not found, this module cannot create its UI yet.
                // Signal to the global initializer that it needs to retry.
                return false;
            }

            const wrapper = document.createElement('div');
            wrapper.id = 'imageUploadControlPanel';

            const separator = document.createElement('hr');
            separator.style.cssText = 'border-color: rgba(255, 255, 255, 0.2); margin: 5px 0;';
            wrapper.appendChild(separator);

            const title = document.createElement('h6');
            title.style.cssText = 'margin: 5px 0 8px; text-align: left; font-size: 0.9em; color: #ccc; line-height: normal;';
            title.textContent = 'Subir Imagen';
            wrapper.appendChild(title);

            this._ui.uploadBtn = document.createElement('button');
            this._ui.uploadBtn.className = 'btn btn-block btn-sm upload-btn';
            this._ui.uploadBtn.innerHTML = '<i class="fas fa-upload"></i><span>Subir Imagen</span>';
            this._ui.uploadBtn.addEventListener('click', () => this._triggerFileUpload());
            wrapper.appendChild(this._ui.uploadBtn);

            const dragDropArea = document.createElement('div');
            dragDropArea.className = 'upload-area';
            dragDropArea.textContent = 'O arrastra una imagen aquí (imagen de internet)';
            dragDropArea.addEventListener('dragover', this._handleDragOver.bind(this));
            dragDropArea.addEventListener('drop', this._handleDrop.bind(this));
            this._ui.dragDropArea = dragDropArea; // Store reference
            wrapper.appendChild(dragDropArea);


            // Insert BEFORE 'downloadsavedcanvas'
            const nextSiblingElement = document.getElementById('downloadsavedcanvas');
            if (nextSiblingElement && nextSiblingElement.parentNode) {
                nextSiblingElement.parentNode.insertBefore(wrapper, nextSiblingElement);
            } else {
                // Fallback: If 'downloadsavedcanvas' is not found, insert right after 'transparentBrushControlPanel'
                targetInsertionPoint.parentNode.insertBefore(wrapper, targetInsertionPoint.nextSibling);
                console.warn("[Drawaria Image Upload] Could not find #downloadsavedcanvas, falling back to inserting after #transparentBrushControlPanel.");
            }

            if (typeof QBit !== 'undefined' && QBit.notify) {
                QBit.notify("info", "Panel de Subida de Imagen insertado en el menú.");
            } else {
                console.log("[Drawaria Image Upload] Panel de Subida de Imagen insertado en el menú.");
            }

            // Setup D&D on the canvas itself (can be done immediately if mainCanvas is found)
            this._setupDragAndDrop();
            return true; // Signal that UI was created
        }

        _resizeCanvas() {
            if (this._mainCanvas) {
                this._mainCanvas.height = 650;
                this._mainCanvas.width = 780;
                if (typeof QBit !== 'undefined' && QBit.notify) {
                    QBit.notify("info", `Canvas redimensionado a ${this._mainCanvas.width}x${this._mainCanvas.height}.`);
                }
            }
        }

        _triggerFileUpload() {
            let input = document.createElement('input');
            input.type = 'file';
            input.accept = 'image/*';
            input.onchange = () => {
                let file = input.files[0];
                if (file) {
                    this._handleFileSelect(file);
                }
            };
            input.click();
        }

        _handleFileSelect(file) {
            let reader = new FileReader();
            reader.onload = (e) => {
                let img = new Image();
                img.onload = () => {
                    this._handleImageLoad(img, this._mainCanvas);
                    if (typeof QBit !== 'undefined' && QBit.notify) {
                        QBit.notify("success", "Imagen cargada y dibujada en el canvas.");
                    }
                };
                img.onerror = () => {
                     if (typeof QBit !== 'undefined' && QBit.notify) {
                        QBit.notify("error", "Error al cargar el archivo de imagen.");
                    }
                };
                img.src = e.target.result;
            };
            reader.readAsDataURL(file);
        }

        _handleImageLoad(img, targetCanvas) {
            if (!targetCanvas || !this._ctx) {
                if (typeof QBit !== 'undefined' && QBit.notify) {
                    QBit.notify("error", "Canvas no disponible para dibujar la imagen.");
                }
                return;
            }
            let ctx = this._ctx;
            ctx.clearRect(0, 0, targetCanvas.width, targetCanvas.height); // Clear previous drawing

            let scaleX = targetCanvas.width / img.width;
            let scaleY = targetCanvas.height / img.height;
            let scale = Math.min(scaleX, scaleY); // Maintain aspect ratio

            let xOffset = (targetCanvas.width - img.width * scale) / 2;
            let yOffset = (targetCanvas.height - img.height * scale) / 2;

            ctx.drawImage(img, xOffset, yOffset, img.width * scale, img.height * scale);
        }

        _setupDragAndDrop() {
            // Apply drag and drop listeners directly to the main game canvas
            if (this._mainCanvas) {
                this._mainCanvas.addEventListener('dragover', this._handleDragOver.bind(this));
                this._mainCanvas.addEventListener('drop', this._handleDrop.bind(this));

                // Add visual feedback for drag-over to the D&D area in the UI
                if (this._ui.dragDropArea) {
                    this._ui.dragDropArea.addEventListener('dragenter', (e) => {
                        e.preventDefault();
                        this._ui.dragDropArea.classList.add('drag-over');
                    });
                    this._ui.dragDropArea.addEventListener('dragleave', (e) => {
                        e.preventDefault();
                        this._ui.dragDropArea.classList.remove('drag-over');
                    });
                    this._ui.dragDropArea.addEventListener('drop', (e) => {
                        this._ui.dragDropArea.classList.remove('drag-over'); // Remove class on drop
                    });
                }
            } else {
                console.warn("[Drawaria Image Upload] Canvas principal no disponible para configurar Drag & Drop.");
            }
        }

        _handleDragOver(event) {
            event.preventDefault(); // Necessary to allow a drop
            event.dataTransfer.dropEffect = 'copy'; // Visual feedback
        }

        async _handleDrop(event) {
            event.preventDefault(); // Prevent default browser handling of dropped items

            if (this._ui.dragDropArea) { // Remove drag-over class if dropped directly on canvas
                 this._ui.dragDropArea.classList.remove('drag-over');
            }


            if (event.dataTransfer.files && event.dataTransfer.files.length > 0) {
                // Handle dropped file (e.g., from desktop)
                const file = event.dataTransfer.files[0];
                if (file.type.startsWith('image/')) {
                    this._handleFileSelect(file);
                    if (typeof QBit !== 'undefined' && QBit.notify) {
                        QBit.notify("info", `Archivo de imagen '${file.name}' arrastrado.`);
                    }
                } else {
                    if (typeof QBit !== 'undefined' && QBit.notify) {
                        QBit.notify("warning", "El archivo arrastrado no es una imagen.");
                    }
                }
            } else if (event.dataTransfer.getData('text/uri-list')) {
                // Handle dropped URL (e.g., image from another tab)
                const imageUrl = event.dataTransfer.getData('text/uri-list');
                this._loadImageFromUrl(imageUrl);
                if (typeof QBit !== 'undefined' && QBit.notify) {
                    QBit.notify("info", `URL de imagen '${imageUrl.substring(0,50)}...' arrastrada.`);
                }
            } else if (event.dataTransfer.getData('text/html')) {
                // Try to extract image URL from HTML content (e.g., dragging an image element)
                const html = event.dataTransfer.getData('text/html');
                const parser = new DOMParser();
                const doc = parser.parseFromString(html, 'text/html');
                const img = doc.querySelector('img');
                if (img && img.src) {
                    this._loadImageFromUrl(img.src);
                    if (typeof QBit !== 'undefined' && QBit.notify) {
                        QBit.notify("info", `Imagen HTML arrastrada desde '${img.src.substring(0,50)}...'.`);
                    }
                } else {
                    if (typeof QBit !== 'undefined' && QBit.notify) {
                        QBit.notify("warning", "Contenido arrastrado no es un archivo de imagen ni una URL de imagen directamente.");
                    }
                }
            } else {
                if (typeof QBit !== 'undefined' && QBit.notify) {
                    QBit.notify("warning", "Tipo de contenido arrastrado no soportado.");
                }
            }
        }

        async _loadImageFromUrl(url) {
            try {
                // Use fetch for cross-origin requests, but it will be subject to CORS policies.
                const response = await fetch(url, { mode: 'cors' });
                if (!response.ok) {
                    throw new Error(`HTTP error! status: ${response.status}`);
                }
                const blob = await response.blob();
                if (!blob.type.startsWith('image/')) {
                    throw new Error('La URL no apunta a un tipo de imagen válido.');
                }

                const reader = new FileReader();
                reader.onload = (e) => {
                    let img = new Image();
                    img.onload = () => {
                        this._handleImageLoad(img, this._mainCanvas);
                        if (typeof QBit !== 'undefined' && QBit.notify) {
                            QBit.notify("success", "Imagen de URL cargada y dibujada.");
                        }
                    };
                    img.onerror = () => {
                        if (typeof QBit !== 'undefined' && QBit.notify) {
                            QBit.notify("error", "Error al cargar la imagen desde la URL. Puede ser un problema de CORS.");
                        }
                    };
                    img.src = e.target.result;
                };
                reader.onerror = (e) => {
                    if (typeof QBit !== 'undefined' && QBit.notify) {
                        QBit.notify("error", `Error al leer la URL como blob: ${e.message}`);
                    }
                };
                reader.readAsDataURL(blob);

            } catch (error) {
                if (typeof QBit !== 'undefined' && QBit.notify) {
                    QBit.notify("error", `Fallo al cargar imagen desde URL: ${error.message}. Puede ser un problema de CORS.`);
                } else {
                     console.error(`[Drawaria Image Upload] Fallo al cargar imagen desde URL: ${error.message}. Puede ser un problema de CORS.`, error);
                }
            }
        }
    }

    // --- Centralized Module Register and Initializer ---
    // This array will hold references to modules that need to create their UI
    // after a specific DOM element (downloadcanvas) is ready.
    window._cubeEngineDomModules = window._cubeEngineDomModules || [];

    // Register TransparentBrushModule
    window._cubeEngineDomModules.push({
        name: "TransparentBrush",
        instance: new TransparentBrushFunctionality(),
        priority: 10 // Lower number means higher priority (created earlier)
    });

    // Register ImageUploadModule
    window._cubeEngineDomModules.push({
        name: "ImageUpload",
        instance: new ImageUploadModule(),
        priority: 20 // Higher priority than TransparentBrush to be inserted after it.
    });

    // The main initializer function that runs when downloadcanvas is found
    if (!window._domInsertionObserversInitialized) {
        window._domInsertionObserversInitialized = true;

        const initDOMModulesWhenReady = () => {
            const downloadCanvasBtn = document.getElementById('downloadcanvas');
            if (downloadCanvasBtn) {
                // Sort modules by priority
                window._cubeEngineDomModules.sort((a, b) => a.priority - b.priority);

                // Iterate through registered modules and attempt to create their UI
                window._cubeEngineDomModules.forEach(moduleInfo => {
                    const instance = moduleInfo.instance;
                    // Only try to create UI if it hasn't been created yet by this function
                    if (!moduleInfo.uiCreated) {
                        if (instance && typeof instance._createUI === 'function') {
                            const uiCreatedSuccessfully = instance._createUI(); // _createUI returns true on success
                            if (uiCreatedSuccessfully) {
                                moduleInfo.uiCreated = true; // Mark as created
                            }
                        } else {
                            console.warn(`[Global DOM Initializer] Module instance for ${moduleInfo.name} not found or _createUI not callable.`);
                        }
                    }
                });

                // Check if all registered modules have successfully created their UI
                const allUIsCreated = window._cubeEngineDomModules.every(m => m.uiCreated);

                if (!allUIsCreated) {
                    // If some UIs are not yet created (e.g., their parent elements weren't ready),
                    // retry all modules after a short delay.
                    setTimeout(initDOMModulesWhenReady, 500);
                } else {
                    // All UIs created, can stop observing/retrying for initial load.
                    // (Actual observer disconnect logic would be more complex if using MutationObserver for #downloadcanvas itself)
                    console.log("[Global DOM Initializer] All registered DOM UIs attempted creation.");
                }

            } else {
                // downloadcanvas not found yet, retry initDOMModulesWhenReady
                setTimeout(initDOMModulesWhenReady, 500);
            }
        };

        // Start the DOM module initialization process
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', initDOMModulesWhenReady);
        } else {
            initDOMModulesWhenReady();
        }
    }

})();

})();