Drawaria Geometry Dash Drawer

A module to draw Geometry Dash icons and objects on the canvas using a pixel-to-line algorithm.

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Drawaria Geometry Dash Drawer
// @namespace    drawaria.modded.gddrawer
// @version      1.0.0
// @description  A module to draw Geometry Dash icons and objects on the canvas using a pixel-to-line algorithm.
// @author       YouTubeDrawaria
// @match        https://drawaria.online/*
// @grant        GM_addStyle
// @run-at       document-end
// @license      MIT
// @icon         https://www.google.com/s2/favicons?sz=64&domain=gdbrowser.com
// @require      https://kit.fontawesome.com/a7a0b82b9e.js
// ==/UserScript==

(function() {
    'use strict';

    // --- Standalone Utilities and QBit Replacements ---

    // === 1. Notification System (replaces ModBase.notify) ===
    function notify(level, message, moduleName = "GD Drawer") {
        let color = "#6c757d"; // log
        if (level === "error") color = "#dc3545";
        else if (level === "warning") color = "#ffc107";
        else if (level === "info") color = "#17a2b8";
        else if (level === "success") color = "#28a745";

        console.log(`%c${moduleName}: ${message}`, `color: ${color}`);

        const loggingContainer = document.getElementById("chatbox_messages");
        if (loggingContainer) {
            const chatmessage = createElement("div", { className: `chatmessage systemchatmessage5`, style: `color: ${color};`, dataset: { ts: Date.now().toString() } }, [`${moduleName}: ${message}`]);
            loggingContainer.appendChild(chatmessage);
            loggingContainer.scrollTop = loggingContainer.scrollHeight;
        }
    }

    // === 2. domMake Replacements (for building UI) ===
    function createElement(tag, attrs = {}, children = []) {
        const el = document.createElement(tag);
        for (const attr in attrs) {
            if (attr === "className") {
                el.className = attrs[attr];
            } else if (attr === "style") {
                 el.style.cssText = attrs[attr];
            } else if (attr === "title") {
                el.setAttribute("title", attrs[attr]);
            } else if (attr === "checked") {
                el.checked = attrs[attr];
            } else if (attr === "dataset") {
                 for (const key in attrs.dataset) {
                    el.dataset[key] = attrs.dataset[key];
                 }
            } else {
                el.setAttribute(attr, attrs[attr]);
            }
        }
        children.forEach(child => {
            if (typeof child === "string") {
                el.innerHTML += child; // Use innerHTML for text with icons/HTML
            } else if (child) {
                el.appendChild(child);
            }
        });
        return el;
    }

    function createButton(content, className = "artfx-button", attrs = {}) {
        const btn = createElement("button", { className: className, ...attrs });
        if (typeof content === "string") {
            btn.innerHTML = content;
        } else {
            // Assumes content is already an element if not a string
            btn.appendChild(content);
        }
        return btn;
    }

    function createTree(tag, attrs, children) {
        return createElement(tag, attrs, children);
    }

    function createRow(attrs = {}, children = []) {
        const row = createElement("div", { className: "module-btn-group", ...attrs });
        children.forEach(child => {
            if (child) row.appendChild(child);
        });
        row.appendAll = (...elements) => elements.forEach(el => row.appendChild(el));
        return row;
    }


    // === 3. Socket Management (replaces globalThis.sockets and _getGameSocket) ===
    const activeSockets = [];
    if (window.WebSocket) {
        const originalWebSocketSend = WebSocket.prototype.send;
        WebSocket.prototype.send = function(...args) {
            if (this.url.includes("drawaria.online/socket.io") && activeSockets.indexOf(this) === -1) {
                activeSockets.push(this);
                this.addEventListener('close', () => {
                    const pos = activeSockets.indexOf(this);
                    if (~pos) activeSockets.splice(pos, 1);
                });
            }
            return originalWebSocketSend.apply(this, args);
        };
    }

    function getPrimarySocket(skipNotification = false) {
        const primarySocket = activeSockets.find(s => s.readyState === WebSocket.OPEN);
        if (!primarySocket) {
            if (!skipNotification) notify("warning", "No active WebSocket connections found.", "GD Drawer");
            return null;
        }
        return primarySocket;
    }

    // === 4. General Utilities ===
    const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
    const clamp = (value, min, max) => Math.max(min, Math.min(max, value));


    // --- Adapted GeometryDashCubeOnlineDrawer Class ---
    class GeometryDashCubeOnlineDrawerStandalone {
      _identifier = 'gddrawer';
      _containerId = `${this._identifier}-menu-container`;
      _contentId = `${this._identifier}-content`;

      _currentCategory = 'cubes';
      _currentIconId = 1;
      _canvasBrushImg = null;
      _currentRotation = 0;
      _ui = {};
      _mainCanvas = null;
      _canvasClickHandler = null;
      _canvasClickHandlerAttached = false;
      _isActive = false;
      _isDrawingIcon = false;


      _categories = {
        cubes: { prefix: 'cube_', maxId: 485, icon: 'fas fa-cube', name: 'Cubos' },
        ships: { prefix: 'ship_', maxId: 169, icon: 'fas fa-plane', name: 'Naves' },
        balls: { prefix: 'ball_', maxId: 118, icon: 'fas fa-circle', name: 'Bolas' },
        ufos: { prefix: 'ufo_', maxId: 149, icon: 'fas fa-space-shuttle', name: 'OVNIs' },
        waves: { prefix: 'wave_', maxId: 96, icon: 'fas fa-wave-square', name: 'Ondas' },
        robots: { prefix: 'robot_', maxId: 68, icon: 'fas fa-robot', name: 'Robots' },
        spiders: { prefix: 'spider_', maxId: 69, icon: 'fas fa-spider', name: 'Arañas' },
        swings: { prefix: 'swing_', maxId: 43, icon: 'fas fa-wind', name: 'Swings' },
        jetpacks: { prefix: 'jetpack_', maxId: 8, icon: 'fas fa-rocket', name: 'Jetpacks' },
        difficulties: { name: 'Dificultades', icon: 'fas fa-star' },
        extras: { name: 'Extras', icon: 'fas fa-plus' },
        gauntlets: { name: 'Guanteletes', icon: 'fas fa-dice' },
        editor_blocks: { name: 'Bloques Editor', icon: 'fas fa-th-large' }
      };

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


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


      _editorBlockDefaultOptimizations = {
        delayPerSegment: 100,
        delayPerRow: 200,
        qualityFactor: 3,
        targetSize: 40
      };

      constructor() {
        this._canvasClickHandler = this._handleCanvasClick.bind(this);
        this._createMenuStructure();
        this._onStartup();
      }

      _onStartup() {
        this._mainCanvas = document.getElementById("canvas");
        this._loadInterface();
        this._setupEventListeners();
        this._loadIconImage('cubes', 1);
        this._setModuleActive(false);
        notify("info", " 'GD Drawer' Loaded. Click the square icon to open.", "GD Drawer");
      }

      _createMenuStructure() {
          const menuContainer = createTree("div", {
              id: this._containerId,
              className: "gd-drawer-menu",
              style: `
                  position: fixed;
                  top: 10%;
                  left: 50px;
                  width: 300px;
                  display: none;
              `
          });

          const header = createTree("div", {
              className: "gd-drawer-menu-header",
              style: `
                  padding: 8px;
                  background-color: #007bff; /* Primary color fallback */
                  color: white;
                  cursor: grab;
                  border-top-left-radius: .25rem;
                  border-top-right-radius: .25rem;
                  display: flex;
                  justify-content: space-between;
                  align-items: center;
              `
          }, [createTree("span", {}, ["🔳 GD Cubes & Objects"]), createButton("X", "btn-close-menu")]);

          header.querySelector(".btn-close-menu").addEventListener("click", () => {
              menuContainer.style.display = "none";
              this._setModuleActive(false);
          });

          const content = createTree("div", {
              id: this._contentId,
              className: "gd-drawer-menu-content",
              style: `padding: 10px;`
          });

          menuContainer.append(header, content);
          document.body.appendChild(menuContainer);
          this._makeDraggable(menuContainer, header);

          this._ui.menuContainer = menuContainer;
          this._ui.section = content;
          this._ui.header = header;
      }

      _makeDraggable(element, handle = element) {
          let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;

          const dragMouseDown = (e) => {
              e = e || window.event;
              e.preventDefault();
              pos3 = e.clientX;
              pos4 = e.clientY;
              document.onmouseup = closeDragElement;
              document.onmousemove = elementDrag;
          };

          const elementDrag = (e) => {
              e = e || window.event;
              e.preventDefault();
              pos1 = pos3 - e.clientX;
              pos2 = pos4 - e.clientY;
              pos3 = e.clientX;
              pos4 = e.clientY;
              element.style.top = (element.offsetTop - pos2) + "px";
              element.style.left = (element.offsetLeft - pos1) + "px";
          };

          const closeDragElement = () => {
              document.onmouseup = null;
              document.onmousemove = null;
          };

          handle.onmousedown = dragMouseDown;
      }


      _loadInterface() {
        const container = createTree("div", { id: `${this._identifier}-container`, className: "module-section" });
        this._ui.section.appendChild(container);

        // Toggle Button
        const moduleToggleGroup = createTree("div", { className: "module-btn-group", style: "margin-bottom:10px;" });
        this._ui.moduleToggleButton = createButton('<i class="fas fa-power-off"></i> Activar Módulo', 'module-toggle-button');
        this._ui.moduleToggleButton.addEventListener('click', () => this._toggleModuleActive());
        moduleToggleGroup.appendChild(this._ui.moduleToggleButton);
        container.appendChild(moduleToggleGroup);

        container.appendChild(createTree("div", { className: "module-section-title" }, ["GD Cubes & Objects Drawer"]));

        // Category Navigation
        const categoryNav = createTree("div", { className: "module-btn-group", style: "flex-wrap: wrap; margin-bottom: 15px;" });
        Object.keys(this._categories).forEach(catKey => {
            const catInfo = this._categories[catKey];
            const name = catInfo.name || catKey.charAt(0).toUpperCase() + catKey.slice(1).replace('_', ' ');
            const iconClass = catInfo.icon;
            const button = createButton(`<i class="${iconClass}"></i> ${name}`, 'artfx-button category-nav-button');
            button.addEventListener('click', () => this._changeCategory(catKey));
            categoryNav.appendChild(button);
            this._ui[`${catKey}NavButton`] = button;
        });
        container.appendChild(categoryNav);

        // Preview and General Nav
        const previewContainer = createTree("div", { style: "display:flex;align-items:center;justify-content:center;gap:10px;margin-bottom:10px;" });
        this._ui.previewCanvas = createTree("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);


        this._ui.commonControlsSection = createTree('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(--info);"></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);

        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 = createTree('div', { id: `${this._identifier}-difficulties-section`, style: "display:none;" });
        let diffButtonsHtml = 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('');
        let modTogglesHtml = this._difficultyModifiers.map(mod => `
            <div style="display:flex;align-items:center;flex:1 1 48%;">
                <input type="checkbox" id="${this._identifier}-mod${mod.replace('-', '')}Toggle" class="difficulty-modifier-toggle" data-mod="${mod}" style="margin-right:5px; transform: scale(1.5);">
                <label for="${this._identifier}-mod${mod.replace('-', '')}Toggle" style="font-size:0.9em;color:var(--CE-color);">${mod.slice(1).charAt(0).toUpperCase() + mod.slice(2)}</label>
            </div>
        `).join('');
        this._ui.difficultiesSection.innerHTML = `
            <div class="module-section-title" style="margin-top:15px;">Dificultades</div>
            <div class="module-btn-group" style="flex-wrap:wrap;">${diffButtonsHtml}</div>
            <div class="module-section-title" style="margin-top:15px;">Modificadores</div>
            <div class="module-btn-group" style="flex-wrap:wrap; justify-content:space-between;">
                ${modTogglesHtml}
                <button id="${this._identifier}-clearModsBtn" class="artfx-button danger" style="width:100%;">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 = createTree('div', { id: `${this._identifier}-extras-section`, style: "display:none;" });
        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}" style="flex:1 1 30%;">${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 = createTree('div', { id: `${this._identifier}-gauntlets-section`, style: "display:none;" });
        this._ui.gauntletsSection.innerHTML = `
            <div class="module-section-title" style="margin-top:15px;">Guanteletes</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}" style="flex:1 1 30%;">${gKey.charAt(0).toUpperCase() + gKey.slice(1)}</button>`).join('')}
            </div>
        `;
        container.appendChild(this._ui.gauntletsSection);
        this._ui.gauntletButtons = this._ui.gauntletsSection.querySelectorAll('.gauntlet-button');


        // Editor Blocks Section
        this._ui.editorBlocksSection = createTree('div', { id: `${this._identifier}-editorblocks-section`, style: "display:none;" });
        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}" style="flex:1 1 30%;">${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" style="width:100%;"><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`);


        // Editor Block Optimization Section
        this._ui.editorBlockOptimizationSection = createTree('div', { id: `${this._identifier}-eb-opt-section`, style: "display:none;" });
        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; justify-content:space-between;">
                <div style="flex:1 1 48%;"><label for="${this._identifier}-ebDelayPerSegmentInput">Delay Seg. (ms):</label><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"></div>
                <div style="flex:1 1 48%;"><label for="${this._identifier}-ebDelayPerRowInput">Delay Fila (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"></div>
                <div style="flex:1 1 48%;"><label for="${this._identifier}-ebQualityFactorInput">Calidad (1-10):</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"></div>
                <div style="flex:1 1 48%;"><label for="${this._identifier}-ebTargetSizeInput">Tamaño Obj. (px):</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"></div>
            </div>
        `;
        container.appendChild(this._ui.editorBlockOptimizationSection);

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


        // General Optimization Section
        container.appendChild(createTree("div", { class: "module-section-title", style: "margin-top:15px;" }, ["Optimización de Dibujo (General)"]));
        const optSettingsGrid = createTree("div", { class: "module-btn-group", style: "flex-wrap:wrap; justify-content:space-between;" });

        this._ui.delayPerSegmentInput = createTree("input", { type: "number", id: `${this._identifier}-delayPerSegmentInput`, min: "0", max: "50", value: "40", title: "Retraso por segmento de línea (ms)", class: "module-form-control" });
        this._ui.delayPerSegmentLabel = createTree("label", { for: `${this._identifier}-delayPerSegmentInput` }, ["Delay Seg. (ms):"]);
        optSettingsGrid.append(createTree("div", { style: "flex:1 1 48%;" }, [this._ui.delayPerSegmentLabel, this._ui.delayPerSegmentInput]));

        this._ui.delayPerRowInput = createTree("input", { type: "number", id: `${this._identifier}-delayPerRowInput`, min: "0", max: "200", value: "50", title: "Retraso por fila de píxeles (ms)", class: "module-form-control" });
        this._ui.delayPerRowLabel = createTree("label", { for: `${this._identifier}-delayPerRowInput` }, ["Delay Fila (ms):"]);
        optSettingsGrid.append(createTree("div", { style: "flex:1 1 48%;" }, [this._ui.delayPerRowLabel, this._ui.delayPerRowInput]));

        this._ui.qualityFactorInput = createTree("input", { type: "number", id: `${this._identifier}-qualityFactorInput`, 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 = createTree("label", { for: `${this._identifier}-qualityFactorInput` }, ["Calidad (1-5):"]);
        optSettingsGrid.append(createTree("div", { style: "flex:1 1 48%;" }, [this._ui.qualityFactorLabel, this._ui.qualityFactorInput]));

        this._ui.autoClearBeforeDrawToggle = createTree("input", { type: "checkbox", id: `${this._identifier}-autoClearToggle`, checked: false, title: "Limpiar el canvas antes de dibujar cada cubo", style: "margin-right: 5px; transform: scale(1.5);" });
        this._ui.autoClearBeforeDrawLabel = createTree("label", { for: `${this._identifier}-autoClearToggle`, style: "flex-grow: 1; text-align: left;" }, ["Auto-Limpiar antes de dibujar"]);
        optSettingsGrid.append(createTree("div", { style: "flex:1 1 48%; display:flex; align-items:center; flex-direction:row;" }, [this._ui.autoClearBeforeDrawToggle, this._ui.autoClearBeforeDrawLabel]));

        container.appendChild(optSettingsGrid);


        this._ui.status = createTree("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() {

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


        Object.keys(this._categories).forEach(catKey => {
            this._ui[`${catKey}NavButton`].addEventListener('click', () => this._changeCategory(catKey));
        });

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


        this._ui.extraButtons.forEach(button => {
          button.addEventListener('click', () => this._loadExtraIcon(button.dataset.extra));
        });


        this._ui.gauntletButtons.forEach(button => {
          button.addEventListener('click', () => this._loadGauntletIcon(button.dataset.gauntlet));
        });


        this._ui.editorBlockButtons.forEach(button => {
          button.addEventListener('click', () => this._loadEditorBlock(button.dataset.eblock));
        });

        this._ui.rotateBlockBtn.addEventListener('click', () => this._rotateBrushImage());
      }

      _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._changeCategory(this._currentCategory); // Ensure UI reflects current category state
        } else {
          this._unhookCanvasClick();
          this._ui.moduleToggleButton.innerHTML = '<i class="fas fa-power-off"></i> Activar Módulo';
          this._ui.moduleToggleButton.classList.remove('active');
          this._isDrawingIcon = false;
          this._ui.status.textContent = "Módulo inactivo. Actívalo para usarlo.";
        }

        const controlsToToggle = [
            this._ui.prevBtn, this._ui.nextBtn, this._ui.iconIdInput, this._ui.acceptIconBtn,
            this._ui.randomIconBtn, this._ui.delayPerSegmentInput, this._ui.delayPerRowInput,
            this._ui.qualityFactorInput, this._ui.autoClearBeforeDrawToggle,
            this._ui.ebDelaySegmentInput, this._ui.ebDelayPerRowInput, this._ui.ebQualityFactorInput,
            this._ui.ebTargetSizeInput, this._ui.rotateBlockBtn, this._ui.clearModsBtn
        ];

        controlsToToggle.forEach(el => {
          if (el) el.disabled = !active;
        });

        this._ui.previewCanvas.style.opacity = active ? '1' : '0.5';

        Object.values(this._ui).forEach(element => {
            if (element && element.classList && element.classList.contains('category-nav-button')) {
                element.disabled = !active;
            }
        });

        // Toggle visibility/display of sections
        const display = active ? 'block' : 'none';

        // Only show the currently active category section when the module is 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';

        if (active) {
            const sectionMap = {
                'cubes': this._ui.commonControlsSection,
                'ships': this._ui.commonControlsSection,
                'balls': this._ui.commonControlsSection,
                'ufos': this._ui.commonControlsSection,
                'waves': this._ui.commonControlsSection,
                'robots': this._ui.commonControlsSection,
                'spiders': this._ui.commonControlsSection,
                'swings': this._ui.commonControlsSection,
                'jetpacks': this._ui.commonControlsSection,
                'difficulties': this._ui.difficultiesSection,
                'extras': this._ui.extrasSection,
                'gauntlets': this._ui.gauntletsSection,
                'editor_blocks': this._ui.editorBlocksSection,
            };
            const currentSection = sectionMap[this._currentCategory];
            if (currentSection) currentSection.style.display = 'block';
            if (this._currentCategory === 'editor_blocks') {
                this._ui.editorBlockOptimizationSection.style.display = 'block';
            }
        }
      }

      _changeCategory(newCategory) {
        // Hide all 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';

        // Clear active states on buttons
        Object.values(this._ui).forEach(element => {
            if (element && element.classList && element.classList.contains('category-nav-button')) {
                element.classList.remove('active');
            }
        });

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

        this._currentCategory = newCategory;
        this._currentRotation = 0; // Reset rotation on category change

        // Show relevant section and load default/current icon
        if (newCategory === 'difficulties') {
            this._ui.difficultiesSection.style.display = 'block';
            // Try to load the currently selected difficulty, or 'easy' if none
            const activeDiffButton = this._ui.difficultiesSection.querySelector('.difficulty-button.active') || this._ui.difficultiesSection.querySelector('.difficulty-button[data-diff="easy"]');
            if (activeDiffButton) {
                this._loadDifficultyIcon(activeDiffButton.dataset.diff);
                activeDiffButton.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';
            this._ui.editorBlockOptimizationSection.style.display = 'block';
            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 {
            this._ui.commonControlsSection.style.display = 'block';
            this._currentIconId = 1;
            this._loadIconImage(this._currentCategory, this._currentIconId);
        }

        const newCategoryButton = this._ui[`${newCategory}NavButton`];
        if (newCategoryButton) {
            newCategoryButton.classList.add('active');
        }
        this._ui.status.textContent = `Categoría: ${this._categories[newCategory].name || newCategory.charAt(0).toUpperCase() + newCategory.slice(1).replace('_', ' ')}.`;
      }

      _updateInfo() {
        if (this._categories[this._currentCategory] && this._categories[this._currentCategory].maxId) {
          this._ui.info.textContent = `Icono #${this._currentIconId} / ${this._categories[this._currentCategory].maxId}`;
          this._ui.iconIdInput.max = this._categories[this._currentCategory].maxId;
          this._ui.iconIdInput.style.display = 'block';
          this._ui.acceptIconBtn.style.display = 'block';
          this._ui.randomIconBtn.style.display = 'block';
        } else {
          this._ui.info.textContent = `Selecciona un icono`;
          this._ui.iconIdInput.style.display = 'none';
          this._ui.acceptIconBtn.style.display = 'none';
          this._ui.randomIconBtn.style.display = 'none';
        }
      }

      _changeIconId(delta) {
        if (!this._categories[this._currentCategory] || !this._categories[this._currentCategory].maxId) return;
        let n = this._currentIconId + delta;
        n = clamp(n, 1, this._categories[this._currentCategory].maxId);
        this._loadIconImage(this._currentCategory, n);
      }

      _loadRandomIcon() {
        if (!this._categories[this._currentCategory] || !this._categories[this._currentCategory].maxId) return;
        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 || !category.prefix) return;

        this._currentCategory = categoryKey;
        this._currentIconId = 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;
          this._currentRotation = 0;
          this._drawPreviewWithRotation(img, 0);
          this._ui.status.textContent = "Listo. Haz click en el canvas principal para dibujar.";
        };
        img.onerror = () => {
          this._canvasBrushImg = null;
          this._drawPreviewError();
          notify("error", `Error al cargar icono GD: ${categoryKey} #${this._currentIconId}.`);
          this._ui.status.textContent = "Error al cargar imagen.";
        };
        img.src = imageUrl;
      }

      async _loadDifficultyIcon(difficultyKey) {
        if (!this._isActive) return;
        this._currentCategory = 'difficulties';

        this._ui.difficultyButtons.forEach(btn => btn.classList.remove('active'));
        const clickedButton = this._ui.difficultiesSection.querySelector(`.difficulty-button[data-diff="${difficultyKey}"]`);
        if(clickedButton) clickedButton.classList.add('active');

        this._ui.status.textContent = `Cargando ${difficultyKey} ...`;
        this._drawPreviewWithRotation(null, 0);
        this._currentRotation = 0;
        this._canvasBrushImg = null;

        let activeModifiers = [];
        this._ui.difficultyModToggles.forEach(checkbox => {
          if (checkbox.checked) {
            activeModifiers.push(checkbox.dataset.mod);
          }
        });

        const tryLoad = async (url) => {
            return new Promise(resolve => {
                const img = new Image();
                img.crossOrigin = "anonymous";
                img.onload = () => {
                    this._canvasBrushImg = img;
                    this._drawPreviewWithRotation(img, 0);
                    this._ui.status.textContent = "Listo. Haz click para dibujar la dificultad.";
                    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) break;
            }
        }
        if (!loaded) {
            const imageUrl = `https://gdbrowser.com/assets/difficulties/${this._difficulties[difficultyKey]}`;
            loaded = await tryLoad(imageUrl);
        }

        if (!loaded) {
            this._canvasBrushImg = null;
            this._drawPreviewError();
            notify("error", `Error al cargar dificultad GD: ${difficultyKey}.`);
            this._ui.status.textContent = "Error al cargar imagen de dificultad.";
        }
      }

      _clearDifficultyModifiers() {
        this._ui.difficultyModToggles.forEach(checkbox => checkbox.checked = false);

        const currentDiff = this._ui.difficultiesSection.querySelector('.difficulty-button.active')?.dataset.diff;
        if(currentDiff) this._loadDifficultyIcon(currentDiff);
      }

      async _loadExtraIcon(extraKey) {
        if (!this._isActive) return;
        this._currentCategory = 'extras';
        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);
        this._currentRotation = 0;

        const img = new window.Image();
        img.crossOrigin = "anonymous";
        img.onload = () => {
          this._canvasBrushImg = img;
          this._drawPreviewWithRotation(img, 0);
          this._ui.status.textContent = "Listo. Haz click para dibujar el extra.";
        };
        img.onerror = () => {
          this._canvasBrushImg = null;
          this._drawPreviewError();
          notify("error", `Error al cargar extra GD: ${extraKey}.`);
          this._ui.status.textContent = "Error al cargar extra.";
        };
        img.src = imageUrl;
      }

      async _loadGauntletIcon(gauntletKey) {
        if (!this._isActive) return;
        this._currentCategory = 'gauntlets';
        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);
        this._currentRotation = 0;

        const img = new window.Image();
        img.crossOrigin = "anonymous";
        img.onload = () => {
          this._canvasBrushImg = img;
          this._drawPreviewWithRotation(img, 0);
          this._ui.status.textContent = "Listo. Haz click para dibujar el guantelete.";
        };
        img.onerror = () => {
          this._canvasBrushImg = null;
          this._drawPreviewError();
          notify("error", `Error al cargar guantelete GD: ${gauntletKey}.`);
          this._ui.status.textContent = "Error al cargar guantelete.";
        };
        img.src = imageUrl;
      }


      async _loadEditorBlock(blockKey) {
        if (!this._isActive) return;
        this._currentCategory = 'editor_blocks';
        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) {
            notify("error", `Bloque del editor GD no encontrado: ${blockKey}.`);
            this._ui.status.textContent = "Error: bloque no encontrado.";
            this._drawPreviewError();
            return;
        }

        this._ui.status.textContent = `Cargando bloque: ${blockKey}...`;
        this._drawPreviewWithRotation(null, 0);

        const img = new window.Image();
        img.crossOrigin = "anonymous";
        img.onload = () => {
          this._canvasBrushImg = img;
          this._currentRotation = 0;
          this._drawPreviewWithRotation(img, this._currentRotation);
          this._ui.status.textContent = "Listo. Haz click para dibujar el bloque.";
        };
        img.onerror = () => {
          this._canvasBrushImg = null;
          this._drawPreviewError();
          notify("error", `Error al cargar bloque del editor GD: ${blockKey}.`);
          this._ui.status.textContent = "Error al cargar bloque.";
        };
        img.src = imageUrl;
      }


      _drawPreviewWithRotation(img, angle) {
        const ctx = this._ui.previewCanvas.getContext("2d");
        const previewSize = 96;
        ctx.clearRect(0, 0, previewSize, previewSize);

        if (!img) {
          ctx.fillStyle = "var(--danger)";
          ctx.font = "14px Arial";
          ctx.fillText("NO IMG", 18, 55);
          return;
        }

        ctx.save();
        ctx.translate(previewSize / 2, previewSize / 2);
        ctx.rotate(angle * Math.PI / 180);

        let drawWidth, drawHeight;
        const maxDim = Math.max(img.width, img.height);
        const scale = previewSize / maxDim * 0.9;
        drawWidth = img.width * scale;
        drawHeight = img.height * scale;

        ctx.drawImage(img, -drawWidth / 2, -drawHeight / 2, drawWidth, drawHeight);
        ctx.restore();
      }


      _drawPreviewError() {
        const ctx = this._ui.previewCanvas.getContext("2d");
        ctx.clearRect(0, 0, 96, 96);
        ctx.fillStyle = "var(--danger)";
        ctx.font = "14px Arial";
        ctx.fillText("NO IMG", 18, 55);
      }


      _rotateBrushImage() {
        if (!this._canvasBrushImg || this._currentCategory !== 'editor_blocks') {
            return;
        }

        this._currentRotation = (this._currentRotation + 90) % 360;
        this._drawPreviewWithRotation(this._canvasBrushImg, this._currentRotation);

        this._ui.status.textContent = `Objeto rotado a ${this._currentRotation}°. Listo para dibujar.`;
      }


      _hookCanvasClick() {
        if (!this._mainCanvas) {
            this._mainCanvas = document.getElementById("canvas");
            if (!this._mainCanvas) return;
        }
        if (!this._canvasClickHandlerAttached) {
          this._mainCanvas.addEventListener("click", this._canvasClickHandler);
          this._canvasClickHandlerAttached = true;
          notify("info", "Canvas click handler attached.", "GD Drawer");
        }
      }

      _unhookCanvasClick() {
        if (this._mainCanvas && this._canvasClickHandlerAttached) {
          this._mainCanvas.removeEventListener("click", this._canvasClickHandler);
          this._canvasClickHandlerAttached = false;
          notify("info", "Canvas click handler detached.", "GD Drawer");
        }
      }

      async _handleCanvasClick(ev) {
        if (this._isDrawingIcon || !this._isActive || !this._canvasBrushImg || !this._mainCanvas) {
            return;
        }

        const socket = getPrimarySocket();
        if (!socket) {
            this._ui.status.textContent = "¡Sin conexión! (Revisa la consola)";
            return;
        }

        this._isDrawingIcon = true;

        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;


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


        this._ui.status.textContent = "Dibujando objeto GD...";


        if (autoClear) {
            await this._clearCanvas(socket);
            await delay(100);
        }

        const originalImgWidth = this._canvasBrushImg.width;
        const originalImgHeight = this._canvasBrushImg.height;

        let rotatedTempCanvas = document.createElement("canvas");
        let tempCtx = rotatedTempCanvas.getContext("2d");


        let rotatedWidth = originalImgWidth;
        let rotatedHeight = originalImgHeight;
        if (this._currentRotation === 90 || this._currentRotation === 270) {
            rotatedWidth = originalImgHeight;
            rotatedHeight = originalImgWidth;
        }
        rotatedTempCanvas.width = rotatedWidth;
        rotatedTempCanvas.height = rotatedHeight;


        tempCtx.save();
        tempCtx.translate(rotatedWidth / 2, rotatedHeight / 2);
        tempCtx.rotate(this._currentRotation * Math.PI / 180);

        tempCtx.drawImage(this._canvasBrushImg, -originalImgWidth / 2, -originalImgHeight / 2);
        tempCtx.restore();


        const imgData = tempCtx.getImageData(0, 0, rotatedWidth, rotatedHeight).data;


        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) {
                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 (currentLineStart_asset_px === null) {
                        currentLineStart_asset_px = px;
                        currentLineColor = hexColor;
                    } else if (hexColor !== currentLineColor) {
                        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 {
                    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 + (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;
                    }
                }
            }

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

        this._isDrawingIcon = false;
        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.";
      }


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

          // Drawaria protocol uses negative thickness for brush tool
          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) return;
        if (!socket || socket.readyState !== WebSocket.OPEN) return;

        const ctx = this._mainCanvas.getContext('2d');
        ctx.clearRect(0, 0, this._mainCanvas.width, this._mainCanvas.height);

        const w = this._mainCanvas.width;
        const h = this._mainCanvas.height;
        const clearThickness = 2000;
        const clearColor = '#FFFFFF';
        const steps = 5;

        for (let i = 0; i <= steps; i++) {
            // Horizontal sweeps (using pixel coordinates)
            this._sendDrawCommand(socket, [0, (i / steps) * h], [w, (i / steps) * h], clearColor, clearThickness);
            await delay(5);
            // Vertical sweeps (using pixel coordinates)
            this._sendDrawCommand(socket, [(i / steps) * w, 0], [(i / steps) * w, h], clearColor, clearThickness);
            await delay(5);
        }
        notify("success", "Lienzo limpiado para todos.");
      }
    }

    // --- Style Injection ---
    GM_addStyle(`
        /* Custom Variables for standalone appearance, mimicking CE */
        :root {
            --light: #f8f9fa;
            --dark: #212529;
            --primary: #007bff;
            --secondary: #6c757d;
            --success: #28a745;
            --info: #17a2b8;
            --danger: #dc3545;
            --warning: #ffc107;

            /* GD-Themed CE-like Variables */
            --CE-bg_color: #1a1a1a;   /* Dark background */
            --CE-color: #f8f9fa;      /* Light text color */
            --CE-input-bg: #444;      /* Dark input background */
            --CE-btn-special: #007bff; /* Blue color (Primary) */
            --CE-btn-active: #ff8c00;  /* Dark Orange active color */
        }

        .gd-drawer-menu {
            line-height: normal;
            font-size: 1rem;
            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.5);
            z-index: 9999;
            font-family: Arial, sans-serif;
            color: var(--CE-color);
            max-height: 80vh;
            overflow-y: auto;
        }

        .gd-drawer-menu-header {
            cursor: grab;
            background-color: #0080ff !important; /* Brighter GD Blue */
        }

        .gd-drawer-menu .btn-close-menu {
            background-color: var(--danger);
            color: white;
            padding: 3px 8px;
            font-size: 0.8em;
            border: none;
            line-height: 1;
            border-radius: .25rem;
            cursor: pointer;
        }

        .module-section-title {
            text-align: center;
            font-weight: bold;
            color: var(--CE-btn-active);
            margin: 10px 0;
            font-size: 1.1em;
            border-bottom: 1px dashed var(--CE-color);
            padding-bottom: 5px;
        }

        .module-btn-group {
            display: flex;
            gap: 5px;
            margin-bottom: 5px;
        }

        /* General Button Style */
        .artfx-button {
            padding: 5px 10px;
            font-size: 0.9em;
            cursor: pointer;
            border: 1px solid var(--secondary);
            border-radius: .25rem;
            background-color: var(--secondary);
            color: var(--dark);
            transition: background-color 0.2s, color 0.2s, border-color 0.2s;
            display: inline-flex;
            align-items: center;
            justify-content: center;
            flex: 1;
            text-align: center;
        }
        .artfx-button.special {
            background-color: var(--CE-btn-special);
            color: white;
            border-color: var(--CE-btn-special);
        }
        .artfx-button:disabled {
            opacity: 0.5;
            cursor: not-allowed;
        }
        .artfx-button i {
            margin-right: 5px;
        }

        /* Category Buttons */
        .category-nav-button {
            flex: 1 1 30%;
            font-size: 0.8em;
            padding: 5px;
            background-color: var(--CE-input-bg);
            color: var(--CE-color);
            border-color: var(--CE-input-bg);
        }
        .category-nav-button.active, .difficulty-button.active, .extra-button.active, .gauntlet-button.active, .editor-block-button.active {
            background-color: var(--CE-btn-active) !important;
            color: white !important;
            border-color: var(--CE-btn-active) !important;
        }

        /* Toggle Button */
        .module-toggle-button {
            width: 100%;
            background-color: var(--danger);
            color: white;
            font-weight: bold;
        }
        .module-toggle-button.active {
            background-color: var(--success);
        }

        /* Form Controls */
        .module-form-control {
            padding: 5px;
            border: 1px solid var(--secondary);
            border-radius: .25rem;
            background-color: var(--CE-input-bg);
            color: var(--CE-color);
            text-align: center;
        }

        /* Input and Label Layout */
        .module-btn-group > div {
            display: flex;
            flex-direction: column;
            gap: 2px;
        }
        .module-btn-group > div label {
            font-size: 0.8em;
            color: var(--CE-color);
        }
        .module-btn-group > div input[type="number"] {
             width: 100%;
        }
    `);

    // --- Module Initialization ---
    function initializeGDDrawer() {
        if (document.body && document.getElementById("canvas")) {
            const drawer = new GeometryDashCubeOnlineDrawerStandalone();

            // Floating toggle button
            const toggleButton = createElement("button", {
                id: "open-gd-drawer-menu",
                style: `
                    position: fixed;
                    bottom: 80px;
                    right: 20px;
                    width: 50px;
                    height: 50px;
                    border-radius: 50%;
                    background-color: #0080ff; /* GD Blue */
                    color: white;
                    border: none;
                    box-shadow: 0 2px 5px rgba(0,0,0,0.3);
                    font-size: 1.5em;
                    cursor: pointer;
                    z-index: 9998;
                `
            }, ['🔳']);

            document.body.appendChild(toggleButton);

            toggleButton.addEventListener("click", () => {
                const menu = document.getElementById(drawer._containerId);
                if (menu) {
                    const isVisible = menu.style.display === "block";
                    menu.style.display = isVisible ? "none" : "block";
                    if (!isVisible) {
                        notify("info", "GD Drawer Menu opened.", "GD Drawer");
                        if (!drawer._canvasBrushImg) {
                            drawer._loadIconImage(drawer._currentCategory, drawer._currentIconId);
                        }
                    } else {
                        notify("info", "GD Drawer Menu closed.", "GD Drawer");
                        drawer._setModuleActive(false);
                    }
                }
            });

        } else {
            setTimeout(initializeGDDrawer, 100);
        }
    }

    initializeGDDrawer();

})();