Drawaria OSINT Sidebar Companion

Librería Auxiliar OSINT (Sidebar). QBit Bridge, OpenCV Safe, YouTube/eBay Scraper.

此脚本不应直接安装。它是供其他脚本使用的外部库,要使用该库请加入元指令 // @require https://update.greasyfork.org/scripts/570442/1779451/Drawaria%20OSINT%20Sidebar%20Companion.js

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Drawaria OSINT Sidebar Companion
// @namespace    drawaria.osint.sidebar
// @version      8.1.4
// @description  Sidebar 100% Nativo (Zero Dependencies). DOM Hijacker, Light Theme, API & DuckDuckGo.
// @author       YouTubeDrawaria
// @match        https://drawaria.online/profile/*
// @match        https://drawaria.online/avatar/cache/*
// @match        https://*.drawaria.online/profile/*
// @match        https://*.drawaria.online/avatar/cache/*
// @match        file:///*/drawaria.online/*.html
// @icon         https://www.google.com/s2/favicons?sz=64&domain=drawaria.online
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @connect      bing.com
// @connect      www.bing.com
// @connect      yandex.com
// @connect      lens.google.com
// @connect      google.com
// @connect      duckduckgo.com
// @connect      downloads.khinsider.com
// @connect      vgmtreasurechest.com
// @connect      www.myinstants.com
// @connect      wikipedia.org
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @connect      drawaria.online
// @connect      wikipedia.org
// @connect      duckduckgo.com
// @connect      wikipedia.org
// @license      MIT
// ==/UserScript==

(function () {
    'use strict';

    // Evitar inyecciones múltiples
    if (document.getElementById('osint-sidebar-wrapper')) return;

    // =========================================================================
    // 1. QBIT BRIDGE (Capa de Compatibilidad y Tema Claro Nativo)
    // =========================================================================
    class DOMCreate {
        Element() { return document.createElement.apply(document, arguments); }
        TextNode() { return document.createTextNode.apply(document, arguments); }
        Tree(type, attrs, childrenArray =[]) {
            const el = this.Element(type);
            childrenArray.forEach(child => {
                if (typeof child === "string") el.appendChild(this.TextNode(child));
                else if (child) el.appendChild(child);
            });
            for (const attr in attrs) {
                if (attr === "className" || attr === "class") el.className = attrs[attr];
                else el.setAttribute(attr, attrs[attr]);
            }
            el.appendAll = function (...nodes) { nodes.forEach((n) => el.appendChild(n)); };
            return el;
        }
        Button(content) {
            let btn = this.Tree("button", { class: "qbit-btn" });
            btn.innerHTML = content;
            return btn;
        }
        Row() { return this.Tree("div", { class: "qbit-row" }); }
    }

    globalThis.domMake = new DOMCreate();
    globalThis.generate = { uuidv4: () => crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).substring(2) };

    class Stylizer {
        constructor() {
            this.element = document.createElement("style");
            document.head ? document.head.appendChild(this.element) : document.addEventListener("DOMContentLoaded", () => document.head.appendChild(this.element));
        }
        addRules(rules =[]) { rules.forEach(rule => this.element.appendChild(document.createTextNode(rule))); }
    }

    class QBit {
        static Styles = new Stylizer();
        static register(ext) { return QBit; }
        static bind(ext, target) { return QBit; }

        constructor(name, icon) {
            this.name = name;
            this.icon = icon;
            this.htmlElements = {};
            this.#onStartupBase();
        }

        #onStartupBase() {
            this.htmlElements.details = domMake.Tree("details", { open: true, class: "qbit-details" });
            this.htmlElements.summary = domMake.Tree("summary", {},[this.icon + " " + this.name]);
            this.htmlElements.section = domMake.Tree("section", { class: "qbit-section" });
            this.htmlElements.details.appendAll(this.htmlElements.summary, this.htmlElements.section);
        }

        notify(level, msg) { console.log(`[${this.name}]`, msg); }
    }
    globalThis.QBit = QBit;

    // CSS GLOBAL DEL SIDEBAR LIGHT THEME (ANCLADO A LA IZQUIERDA)
    QBit.Styles.addRules([
        `#osint-sidebar-wrapper { position: fixed; top: 0; left: 0; height: 100vh; display: flex; z-index: 999999; transform: translateX(0); transition: transform 0.3s ease-in-out; }`,
        `#osint-sidebar-wrapper.collapsed { transform: translateX(-350px); }`,
        `#osint-sidebar-container { width: 350px; height: 100%; overflow-y: auto; overflow-x: hidden; background: #ffffff; border-right: 1px solid #dee2e6; padding: 15px; box-sizing: border-box; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; box-shadow: 4px 0 15px rgba(0,0,0,0.1); color: #212529; }`,
        `#osint-sidebar-container::-webkit-scrollbar { width: 6px; }`,
        `#osint-sidebar-container::-webkit-scrollbar-thumb { background: #007bff; border-radius: 3px; }`,
        `#osint-sidebar-toggle { width: 25px; height: 60px; background: #f8f9fa; color: #007bff; border: 1px solid #dee2e6; border-left: none; display: flex; align-items: center; justify-content: center; cursor: pointer; border-radius: 0 8px 8px 0; align-self: center; box-shadow: 3px 0 5px rgba(0,0,0,0.1); font-size: 14px; user-select: none; transition: 0.2s; font-weight: bold; }`,
        `#osint-sidebar-toggle:hover { background: #e2e6ea; }`,

        /* Estilos Internos QBit (White Theme) */
        `.qbit-details { background: #ffffff; border: 1px solid #dee2e6; border-radius: 5px; margin-bottom: 8px; box-shadow: 0 1px 3px rgba(0,0,0,0.05); }`,
        `.qbit-details summary { background: #f8f9fa; color: #007bff; padding: 8px 10px; font-weight: bold; cursor: pointer; border-radius: 4px 4px 0 0; font-size: 0.9em; user-select: none; border-bottom: 1px solid #dee2e6; }`,
        `.qbit-section { padding: 10px; font-size: 0.85em; display: flex; flex-direction: column; gap: 8px; color: #212529; }`,
        `.qbit-btn { background: #f8f9fa; color: #212529; border: 1px solid #dee2e6; padding: 6px; border-radius: 4px; cursor: pointer; font-weight: 600; transition: 0.2s; width: 100%; box-sizing: border-box; text-align: center; display: inline-block; text-decoration: none; }`,
        `.qbit-btn:hover { background: #e2e6ea; }`,
        `.qbit-row { display: flex; gap: 5px; width: 100%; flex-wrap: wrap; justify-content: center; align-items: center; }`,

        /* Tarjetas de Búsqueda */
        `.osint-card { background: #ffffff; border: 1px solid #dee2e6; border-left: 3px solid #007bff; padding: 8px; border-radius: 4px; margin-bottom: 5px; box-shadow: 0 1px 2px rgba(0,0,0,0.05); }`,
        `.osint-card a { color: #007bff; text-decoration: none; font-weight: bold; display: block; margin-bottom: 4px; font-size: 1em; }`,

        /* Utilidades para inputs */
        `.osint-input-sm { width: 48%; border: 1px solid #dee2e6; padding: 4px; border-radius: 3px; font-size: 0.9em; box-sizing: border-box; background: #ffffff; color: #212529; }`,
        `.icon-list { display: flex; flex-wrap: wrap; gap: 5px; justify-content: center; margin-bottom: 10px; }`,
        `.icon-list label.icon { width: 30px; height: 30px; display: flex; align-items: center; justify-content: center; border: 1px solid #dee2e6; border-radius: 4px; background: #f8f9fa; cursor: pointer; color: #495057; font-size: 1.2em; }`,
        `.icon-list input:checked + label.icon { background: #007bff; color: white; border-color: #0056b3; }`
    ]);

// =========================================================================
    // 2. EXTRACCIÓN DE IMAGEN Y DETECCIÓN DE ENTORNO (Reg, Unreg, Local)
    // =========================================================================
    function autoLoadImageContext() {
        const href = window.location.href;
        const isLocal = href.startsWith('file:');
        const isUnregistered = href.includes('/avatar/cache/');
        const isRegistered = href.includes('/profile/');

        const fetchAndDispatchBase64 = (url) => {
            GM_xmlhttpRequest({
                method: "GET", url: url, responseType: "blob",
                onload: (res) => {
                    const reader = new FileReader();
                    reader.onloadend = () => window.dispatchEvent(new CustomEvent('osint-image-loaded', { detail: { base64: reader.result, url: url } }));
                    reader.readAsDataURL(res.response);
                }
            });
        };

        if (isLocal) {
            // Entorno Local: Hacer hook al input file inyectado por el Script 2
            const hookLocalFile = setInterval(() => {
                const fileInput = document.getElementById('file-selector');
                if (fileInput && !fileInput.dataset.hijacked) {
                    fileInput.dataset.hijacked = 'true'; // Marcar para evitar hooks duplicados
                    fileInput.addEventListener('change', (e) => {
                        if (e.target.files && e.target.files[0]) {
                            const reader = new FileReader();
                            reader.onloadend = () => {
                                window.dispatchEvent(new CustomEvent('osint-image-loaded', { detail: { base64: reader.result, url: 'local_file' } }));
                            };
                            reader.readAsDataURL(e.target.files[0]);
                        }
                    });
                    clearInterval(hookLocalFile);
                }
            }, 500);
        } else if (isUnregistered) {
            // Entorno Unregistered: La URL limpia es la imagen en sí misma
            fetchAndDispatchBase64(href.split('?')[0]);
        } else if (isRegistered) {
            // Entorno Registered: Esperar a que el Script 2 o el juego carguen la imagen en el DOM
            let retries = 0;
            const findImg = setInterval(() => {
                const img = document.querySelector('img[src*="/avatar/"]');
                if (img && img.src) {
                    clearInterval(findImg);
                    fetchAndDispatchBase64(new URL(img.src, window.location.origin).href);
                }
                if (++retries > 20) clearInterval(findImg);
            }, 500);
        }
    }

    // =========================================================================
    // 3. MÓDULOS DE PROPIEDADES DE IMAGEN (Ligeros y Nativos)
    // =========================================================================

    class ImageProperties extends QBit {
        constructor() {
            super("Image properties", '📏');
            this.resultsDisplay = domMake.Tree("pre", { style: "white-space: pre-wrap; margin: 0; color: #212529; font-family: monospace;" },["Esperando imagen..."]);
            this.htmlElements.section.appendChild(this.resultsDisplay);
            window.addEventListener('osint-image-loaded', (e) => this.calculateProperties(e.detail.base64));
        }

        calculateProperties(base64) {
            const img = new Image();
            img.onload = () => {
                const gcd = (a, b) => b === 0 ? a : gcd(b, a % b);
                const divisor = gcd(img.width, img.height);
                const aspectRatio = `${img.width / divisor}:${img.height / divisor}`;

                const sizeInBytes = Math.floor((base64.length * (3/4)) - (base64.indexOf('=') > 0 ? (base64.length - base64.indexOf('=')) : 0));
                const sizeInKb = (sizeInBytes / 1024).toFixed(2);

                this.resultsDisplay.textContent =
                    `Dimensiones   : ${img.width} x ${img.height} px\n` +
                    `Aspect Ratio  : ${aspectRatio}\n` +
                    `Peso Estimado : ${sizeInKb} KB\n` +
                    `Formato       : ${base64.substring(5, base64.indexOf(';'))}`;
            };
            img.src = base64;
        }
    }

    class ReverseSearchHub extends QBit {
        constructor() {
            super("Reverse Search", '🌐');
            this.container = domMake.Tree("div", { class: "qbit-row" }, ["Esperando imagen..."]);
            this.htmlElements.section.appendChild(this.container);
            window.addEventListener('osint-image-loaded', (e) => this.renderButtons(e.detail.url));
        }

        renderButtons(url) {
            this.container.innerHTML = "";
            if (!url || !url.startsWith("http")) {
                this.container.innerHTML = "<span style='color: #dc3545;'>Se requiere una URL HTTP válida.</span>";
                return;
            }

            const encUrl = encodeURIComponent(url);

            const createLink = (name, link) => {
                const a = document.createElement("a");
                a.href = link;
                a.target = "_blank";
                a.className = "qbit-btn";
                a.textContent = name;
                this.container.appendChild(a);
            };

            createLink("Google Lens", `https://lens.google.com/uploadbyurl?url=${encUrl}`);
            createLink("Yandex", `https://yandex.com/images/search?rpt=imageview&url=${encUrl}`);
            createLink("Bing", `https://www.bing.com/images/searchbyimage?cbir=sbi&imgurl=${encUrl}`);
        }
    }

    // =========================================================================
    // 3. MÓDULOS DE PROPIEDADES DE IMAGEN (RESTAURADOS Y COMPLETOS)
    // =========================================================================

    class ImageAnalyzer extends QBit {
        constructor() {
            super("Image Analyzer", '📊');
            this.lastAnalysisText = "";
            this.#setupUI();
            window.addEventListener('osint-image-loaded', (e) => this.analyzeImage(e.detail.base64));
        }

        #setupUI() {
            const section = this.htmlElements.section;

            // Contenedor visual de colores (Swatches)
            this.swatchContainer = domMake.Tree("div", {
                style: "display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; justify-content: center; padding: 5px; border: 1px solid #dee2e6; border-radius: 4px; background: #f8f9fa;"
            });

            // Pantalla de resultados técnicos
            this.resultsDisplay = domMake.Tree("pre", {
                style: "white-space: pre-wrap; margin: 0; color: #212529; font-family: monospace; font-size: 0.85em; background: #fff; padding: 8px; border: 1px solid #dee2e6; border-radius: 4px;"
            }, ["Esperando imagen..."]);

            // Botones de Exportación
            const actionRow = domMake.Row();
            actionRow.style.marginTop = "10px";

            const btnCopy = domMake.Button('📋 Copiar Resultados');
            btnCopy.onclick = () => {
                if(!this.lastAnalysisText) return;
                navigator.clipboard.writeText(this.lastAnalysisText);
                this.notify("info", "Resultados copiados al portapapeles.");
            };

            const btnDownload = domMake.Button('💾 Descargar TXT');
            btnDownload.onclick = () => {
                if(!this.lastAnalysisText) return;
                const blob = new Blob([this.lastAnalysisText], { type: 'text/plain' });
                const url = URL.createObjectURL(blob);
                const a = document.createElement('a');
                a.href = url;
                a.download = `osint_analysis_${Date.now()}.txt`;
                a.click();
                URL.revokeObjectURL(url);
            };

            actionRow.appendAll(btnCopy, btnDownload);

            section.appendAll(
                domMake.Tree("div", { style: "font-weight: bold; font-size: 0.85em; margin-bottom: 4px;" }, ["🎨 Colores Dominantes:"]),
                this.swatchContainer,
                this.resultsDisplay,
                actionRow
            );
        }

        analyzeImage(base64) {
            this.resultsDisplay.textContent = "Analizando...";
            this.swatchContainer.innerHTML = "";

            const img = new Image();
            img.onload = () => {
                const canvas = document.createElement('canvas');
                canvas.width = img.width;
                canvas.height = img.height;
                const ctx = canvas.getContext('2d');
                ctx.drawImage(img, 0, 0);

                const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
                const data = imageData.data;

                let colorMap = {};
                let totalBrightness = 0;
                let validPixels = 0;

                // Análisis completo de píxeles
                for (let i = 0; i < data.length; i += 4) {
                    const r = data[i];
                    const g = data[i + 1];
                    const b = data[i + 2];
                    const a = data[i + 3];

                    if (a > 20) { // Omitir pixeles transparentes
                        // Color Hex
                        const hex = `#${r.toString(16).padStart(2,'0')}${g.toString(16).padStart(2,'0')}${b.toString(16).padStart(2,'0')}`.toUpperCase();
                        colorMap[hex] = (colorMap[hex] || 0) + 1;

                        // Brillo Avanzado (Luminancia Percibida)
                        totalBrightness += (0.299 * r + 0.587 * g + 0.114 * b);
                        validPixels++;
                    }
                }

                // Obtener Top Colores
                const sortedColors = Object.entries(colorMap)
                    .sort((a, b) => b[1] - a[1])
                    .slice(0, 10);

                const avgBright = validPixels > 0 ? (totalBrightness / validPixels).toFixed(2) : 0;
                const brightPercent = ((avgBright / 255) * 100).toFixed(1);

                // Generar Swatches (Bloques de colores)
                sortedColors.forEach(([hex, count]) => {
                    const swatch = domMake.Tree("div", {
                        title: `Color: ${hex} (Click para copiar HEX)`,
                        style: `width: 25px; height: 25px; background: ${hex}; border: 1px solid #dee2e6; border-radius: 3px; cursor: pointer;`
                    });
                    swatch.onclick = () => {
                        navigator.clipboard.writeText(hex);
                        this.notify("info", `Copiado: ${hex}`);
                    };
                    this.swatchContainer.appendChild(swatch);
                });

                // Preparar texto para exportación y visualización
                this.lastAnalysisText =
                    `--- REPORTE DE ANÁLISIS OSINT ---\n` +
                    `Dimensiones: ${img.width}x${img.height}\n` +
                    `Píxeles Sólidos: ${validPixels}\n` +
                    `Brillo Avanzado: ${avgBright}/255 (${brightPercent}%)\n` +
                    `Clasificación: ${avgBright > 127 ? 'Clara' : 'Oscura'}\n\n` +
                    `PALETA DETECTADA:\n` +
                    sortedColors.map((c, i) => `${i+1}. ${c[0]} (${((c[1]/validPixels)*100).toFixed(1)}%)`).join('\n');

                this.resultsDisplay.textContent =
                    `✨ Brillo: ${brightPercent}% (${avgBright})\n` +
                    `📏 Tamaño: ${img.width}x${img.height} px\n` +
                    `✅ Píxeles: ${validPixels}\n` +
                    `🎨 Colores: ${Object.keys(colorMap).length} únicos`;
            };
            img.src = base64;
        }
    }

class ImageToolsFull extends QBit {
    constructor() {
        super("Image Editor Pro Elite", '🛠️');

        this.injectStyles();

        this.interactiveCanvas = document.createElement('canvas');
        this.interactiveCanvas.style.cssText = `
            max-width: 100%;
            height: auto;
            display: block;
            margin: 8px auto;
            border: 1px dashed #adb5bd;
            border-radius: 4px;
            cursor: crosshair;
            background: repeating-conic-gradient(#f8f9fa 0% 25%, #e9ecef 0% 50%) 50% / 15px 15px;
            box-shadow: 0 4px 8px rgba(0,0,0,0.05);
            transition: all 0.3s ease;
        `;
        this.originalWidth = 0;
        this.originalHeight = 0;
        this.originalImageBase64OnLoad = null;
        this.currentImageBase64 = null;
        this.history = [];
        this.currentHistoryIndex = -1;
        this.isProcessing = false;

        this.loadInterface();
        window.addEventListener('osint-image-loaded', (e) => this.loadImageBase64(e.detail.base64));
    }

    injectStyles() {
        if (document.getElementById('iet-custom-styles')) return;
        const style = document.createElement('style');
        style.id = 'iet-custom-styles';
        style.textContent = `
            .iet-tab-container { display: flex; flex-wrap: wrap; gap: 4px; margin-bottom: 10px; background: #e9ecef; padding: 4px; border-radius: 6px; }
            .iet-tab { flex: 1 1 auto; text-align: center; padding: 6px 8px; background: transparent; border: none; border-radius: 4px; cursor: pointer; transition: all 0.2s ease; font-size: 11px; font-weight: 600; color: #495057; display: flex; align-items: center; justify-content: center; gap: 4px; }
            .iet-tab:hover { background: #dee2e6; color: #212529; }
            .iet-tab.active { background: #fff; color: #0d6efd; box-shadow: 0 1px 4px rgba(0,0,0,0.08); }

            .iet-panel { display: none; padding: 8px; background: #fff; border: 1px solid #dee2e6; border-radius: 6px; margin-bottom: 8px; box-shadow: 0 2px 6px rgba(0,0,0,0.03); }
            .iet-panel.active { display: block; animation: fadeIn 0.3s ease; }
            @keyframes fadeIn { from { opacity: 0; transform: translateY(3px); } to { opacity: 1; transform: translateY(0); } }

            .iet-btn { padding: 6px 10px; border: none; border-radius: 4px; font-weight: 600; cursor: pointer; transition: all 0.2s; color: white; background: #0d6efd; font-size: 11px; display: inline-flex; align-items: center; justify-content: center; gap: 4px; margin: 2px 0; box-shadow: 0 1px 3px rgba(13,110,253,0.15); width: 100%; box-sizing: border-box; }
            .iet-btn:hover { filter: brightness(1.1); transform: translateY(-1px); box-shadow: 0 2px 5px rgba(13,110,253,0.25); }
            .iet-btn:active { transform: translateY(0); box-shadow: 0 1px 2px rgba(13,110,253,0.15); }

            .iet-btn-danger { background: #dc3545; box-shadow: 0 1px 3px rgba(220,53,69,0.15); }
            .iet-btn-danger:hover { box-shadow: 0 2px 5px rgba(220,53,69,0.25); }

            .iet-btn-success { background: #28a745; box-shadow: 0 1px 3px rgba(40,167,69,0.15); }
            .iet-btn-success:hover { box-shadow: 0 2px 5px rgba(40,167,69,0.25); }

            .iet-btn-warning { background: #ffc107; color: #212529; box-shadow: 0 1px 3px rgba(255,193,7,0.15); }
            .iet-btn-warning:hover { box-shadow: 0 2px 5px rgba(255,193,7,0.25); }

            .iet-btn-secondary { background: #6c757d; box-shadow: 0 1px 3px rgba(108,117,125,0.15); }
            .iet-btn-secondary:hover { box-shadow: 0 2px 5px rgba(108,117,125,0.25); }

            .iet-input-group { margin-bottom: 6px; display: flex; flex-direction: column; gap: 2px; width: 100%; }
            .iet-input-group label { font-size: 10px; font-weight: 600; color: #495057; display: flex; align-items: center; gap: 2px; }
            .iet-slider { width: 100%; accent-color: #0d6efd; cursor: pointer; height: 10px; margin: 4px 0; }
            .iet-input-text { padding: 4px 6px; border: 1px solid #ced4da; border-radius: 4px; font-size: 11px; outline: none; transition: border-color 0.2s; box-sizing: border-box; width: 100%; }
            .iet-input-text:focus { border-color: #0d6efd; box-shadow: 0 0 0 2px rgba(13,110,253,0.2); }

            .iet-section-title { font-size: 12px; font-weight: bold; color: #212529; margin-bottom: 6px; padding-bottom: 4px; border-bottom: 1px solid #e9ecef; }
            .iet-tool-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(130px, 1fr)); gap: 8px; }
            .iet-tool-card { background: #f8f9fa; padding: 6px; border-radius: 6px; border: 1px solid #e9ecef; }

            .iet-select { padding: 4px 6px; border: 1px solid #ced4da; border-radius: 4px; font-size: 11px; outline: none; width: 100%; background: white; cursor: pointer; margin-bottom: 4px; }
        `;
        document.head.appendChild(style);
    }

    createStyledBtn(text, icon, variantClass = '') {
        return domMake.Tree("button", { class: `iet-btn ${variantClass}` }, [icon + " " + text]);
    }

    createControlGroup(labelText, inputElement, buttonElement) {
        const group = domMake.Tree("div", { class: "iet-input-group" });
        const label = domMake.Tree("label", {}, [labelText]);
        group.appendAll(label, inputElement);
        if (buttonElement) group.appendChild(buttonElement);
        return group;
    }

    loadImageBase64(base64) {
        this.currentImageBase64 = base64;
        this.originalImageBase64OnLoad = base64;
        this.history = [base64];
        this.currentHistoryIndex = 0;

        const img = new Image();
        img.onload = () => {
            this.originalWidth = img.naturalWidth;
            this.originalHeight = img.naturalHeight;
            this.interactiveCanvas.width = img.naturalWidth;
            this.interactiveCanvas.height = img.naturalHeight;
            this.interactiveCanvas.getContext('2d').drawImage(img, 0, 0);
            this.notify("success", "Imagen cargada en el editor interactivo.");
        };
        img.src = base64;
    }

    async loadInterface() {
        const section = this.htmlElements.section;
        const tabsContainer = domMake.Tree("div", { class: "iet-tab-container" });
        const panelsContainer = domMake.Tree("div");

        const categories = [
            { id: "cat-geo", title: "Geometría", icon: "📐", build: this.buildGeometryUI.bind(this) },
            { id: "cat-adj", title: "Ajustes", icon: "☀️", build: this.buildAdjustmentsUI.bind(this) },
            { id: "cat-fil", title: "Filtros y Marcos", icon: "🎨", build: this.buildFiltersUI.bind(this) },
            { id: "cat-adv", title: "Efectos", icon: "✨", build: this.buildAdvancedUI.bind(this) }
        ];

        categories.forEach((cat, index) => {
            const tabBtn = domMake.Tree("button", { class: `iet-tab ${index === 0 ? 'active' : ''}` }, [cat.icon + " " + cat.title]);
            const panel = domMake.Tree("div", { id: cat.id, class: `iet-panel ${index === 0 ? 'active' : ''}` });

            panel.appendChild(cat.build());

            tabBtn.addEventListener("click", () => {
                document.querySelectorAll(".iet-tab").forEach(t => t.classList.remove("active"));
                document.querySelectorAll(".iet-panel").forEach(p => p.classList.remove("active"));
                tabBtn.classList.add("active");
                panel.classList.add("active");
            });

            tabsContainer.appendChild(tabBtn);
            panelsContainer.appendChild(panel);
        });

        section.appendChild(tabsContainer);
        section.appendChild(panelsContainer);
        section.appendChild(this.interactiveCanvas);

        const bottomRow = domMake.Tree("div", { style: "display: flex; gap: 6px; margin-top: 10px;" });

        const btnReset = this.createStyledBtn("Orig.", "🔙", "iet-btn-danger");
        btnReset.style.flex = "1";
        btnReset.addEventListener("click", () => {
            if(this.originalImageBase64OnLoad) this.loadImageBase64(this.originalImageBase64OnLoad);
        });

        const btnUndo = this.createStyledBtn("Deshacer", "↩️", "iet-btn-warning");
        btnUndo.style.flex = "1";
        btnUndo.addEventListener("click", () => this.undo());

        const btnRedo = this.createStyledBtn("Rehacer", "↪️", "iet-btn-secondary");
        btnRedo.style.flex = "1";
        btnRedo.addEventListener("click", () => this.redo());

        const btnDownload = this.createStyledBtn("Guardar", "💾", "iet-btn-success");
        btnDownload.style.flex = "1";
        btnDownload.addEventListener("click", () => this.exportImage());

        bottomRow.appendAll(btnReset, btnUndo, btnRedo, btnDownload);
        section.appendChild(bottomRow);
    }

    undo() {
        if (this.currentHistoryIndex > 0) {
            this.currentHistoryIndex--;
            this.currentImageBase64 = this.history[this.currentHistoryIndex];
            this.redrawCanvas();
        } else {
            this.notify("warning", "No hay más acciones para deshacer.");
        }
    }

    redo() {
        if (this.currentHistoryIndex < this.history.length - 1) {
            this.currentHistoryIndex++;
            this.currentImageBase64 = this.history[this.currentHistoryIndex];
            this.redrawCanvas();
        } else {
            this.notify("warning", "No hay acciones para rehacer.");
        }
    }

    redrawCanvas() {
        if (!this.currentImageBase64) return;
        const img = new Image();
        img.onload = () => {
            this.originalWidth = img.naturalWidth;
            this.originalHeight = img.naturalHeight;
            this.interactiveCanvas.width = this.originalWidth;
            this.interactiveCanvas.height = this.originalHeight;
            this.interactiveCanvas.getContext('2d').drawImage(img, 0, 0);
        };
        img.src = this.currentImageBase64;
    }

    exportImage() {
        if (!this.currentImageBase64) return;
        const link = document.createElement("a");
        link.href = this.interactiveCanvas.toDataURL("image/png");
        link.download = `osint_edited_${Date.now()}.png`;
        link.click();
    }

    async processImageTransform(transformFunction) {
        if (!this.currentImageBase64 || this.isProcessing) return;

        try {
            this.isProcessing = true;
            const img = new Image();
            img.src = this.currentImageBase64;
            await new Promise(r => img.onload = r);

            const canvas = document.createElement('canvas');
            canvas.width = this.originalWidth;
            canvas.height = this.originalHeight;
            const ctx = canvas.getContext('2d');
            ctx.drawImage(img, 0, 0);

            this.history = this.history.slice(0, this.currentHistoryIndex + 1);

            await transformFunction(ctx, canvas.width, canvas.height, img);

            this.originalWidth = canvas.width;
            this.originalHeight = canvas.height;

            this.currentImageBase64 = canvas.toDataURL('image/png');
            this.history.push(this.currentImageBase64);
            this.currentHistoryIndex++;

            this.redrawCanvas();
        } catch (error) {
            console.error(error);
            this.notify("error", "Error procesando imagen.");
        } finally {
            this.isProcessing = false;
        }
    }

    applyConvolution(ctx, w, h, matrix, factor = 1, bias = 0) {
        let imgData = ctx.getImageData(0, 0, w, h);
        let src = new Uint8ClampedArray(imgData.data);
        let data = imgData.data;
        let side = Math.round(Math.sqrt(matrix.length));
        let half = Math.floor(side / 2);

        for (let y = 0; y < h; y++) {
            for (let x = 0; x < w; x++) {
                let r = 0, g = 0, b = 0;
                for (let cy = 0; cy < side; cy++) {
                    for (let cx = 0; cx < side; cx++) {
                        let scy = y + cy - half;
                        let scx = x + cx - half;
                        if (scy >= 0 && scy < h && scx >= 0 && scx < w) {
                            let srcOff = (scy * w + scx) * 4;
                            let wt = matrix[cy * side + cx];
                            r += src[srcOff] * wt;
                            g += src[srcOff + 1] * wt;
                            b += src[srcOff + 2] * wt;
                        }
                    }
                }
                let off = (y * w + x) * 4;
                data[off] = factor * r + bias;
                data[off + 1] = factor * g + bias;
                data[off + 2] = factor * b + bias;
            }
        }
        ctx.putImageData(imgData, 0, 0);
    }

    // ================= PANELES DE HERRAMIENTAS =================

    buildGeometryUI() {
        const grid = domMake.Tree("div", { class: "iet-tool-grid" });

        // Redimensionar
        const cardResize = domMake.Tree("div", { class: "iet-tool-card" });
        cardResize.appendChild(domMake.Tree("div", { class: "iet-section-title" }, ["↕️ Escalar"]));
        const scaleInp = domMake.Tree("input", { type: "number", value: 100, class: "iet-input-text" });
        const btnResize = this.createStyledBtn("Aplicar", "📏");
        btnResize.addEventListener("click", () => {
            let s = parseFloat(scaleInp.value) / 100;
            this.processImageTransform((ctx, w, h, img) => {
                let nW = Math.max(1, Math.round(w * s));
                let nH = Math.max(1, Math.round(h * s));
                ctx.canvas.width = nW; ctx.canvas.height = nH;
                ctx.drawImage(img, 0, 0, nW, nH);
            });
        });
        cardResize.appendChild(this.createControlGroup("Porcentaje (%)", scaleInp, btnResize));

        // Recortar
        const cardCrop = domMake.Tree("div", { class: "iet-tool-card" });
        cardCrop.appendChild(domMake.Tree("div", { class: "iet-section-title" }, ["✂️ Recortar"]));
        const cropInp = domMake.Tree("input", { type: "range", min: 1, max: 40, value: 10, class: "iet-slider" });
        const btnCrop = this.createStyledBtn("Recortar", "🗡️");
        btnCrop.addEventListener("click", () => {
            let margin = parseFloat(cropInp.value) / 100;
            this.processImageTransform((ctx, w, h, img) => {
                let cX = Math.round(w * margin);
                let cY = Math.round(h * margin);
                let nW = w - (cX * 2);
                let nH = h - (cY * 2);
                if (nW < 10 || nH < 10) return;
                ctx.canvas.width = nW; ctx.canvas.height = nH;
                ctx.drawImage(img, cX, cY, nW, nH, 0, 0, nW, nH);
            });
        });
        cardCrop.appendChild(this.createControlGroup("Margen", cropInp, btnCrop));

        // Voltear / Rotar Rápido
        const cardRotate = domMake.Tree("div", { class: "iet-tool-card" });
        cardRotate.appendChild(domMake.Tree("div", { class: "iet-section-title" }, ["🔄 Orientación Rápida"]));
        const btnH = this.createStyledBtn("Flip Horiz.", "↔️");
        const btnRot = this.createStyledBtn("Rotar 90°", "↻");
        btnH.addEventListener("click", () => this.processImageTransform((ctx, w, h, img) => {
            ctx.translate(w, 0); ctx.scale(-1, 1); ctx.drawImage(img, 0, 0);
        }));
        btnRot.addEventListener("click", () => this.processImageTransform((ctx, w, h, img) => {
            ctx.canvas.width = h; ctx.canvas.height = w;
            ctx.translate(h/2, w/2); ctx.rotate(Math.PI/2); ctx.drawImage(img, -w/2, -h/2);
        }));
        cardRotate.appendAll(btnH, btnRot);

        // Rotación Libre 360 (NUEVO)
        const cardFreeRot = domMake.Tree("div", { class: "iet-tool-card" });
        cardFreeRot.appendChild(domMake.Tree("div", { class: "iet-section-title" }, ["🎡 Rotación Libre"]));
        const degInp = domMake.Tree("input", { type: "range", min: 0, max: 360, value: 0, class: "iet-slider" });
        const btnFreeRot = this.createStyledBtn("Aplicar Rotación", "⚙️");
        btnFreeRot.addEventListener("click", () => this.processImageTransform((ctx, w, h, img) => {
            let deg = parseInt(degInp.value);
            let rad = deg * Math.PI / 180;
            // Recalcular el bounding box para que no se corte la imagen
            let newW = Math.abs(w * Math.cos(rad)) + Math.abs(h * Math.sin(rad));
            let newH = Math.abs(w * Math.sin(rad)) + Math.abs(h * Math.cos(rad));
            ctx.canvas.width = newW; ctx.canvas.height = newH;
            ctx.translate(newW / 2, newH / 2);
            ctx.rotate(rad);
            ctx.drawImage(img, -w / 2, -h / 2);
        }));
        cardFreeRot.appendChild(this.createControlGroup("Grados (0 - 360)", degInp, btnFreeRot));

        grid.appendAll(cardResize, cardCrop, cardRotate, cardFreeRot);
        return grid;
    }

    buildAdjustmentsUI() {
        const grid = domMake.Tree("div", { class: "iet-tool-grid" });

        // Brillo / Contraste
        const cardBC = domMake.Tree("div", { class: "iet-tool-card" });
        cardBC.appendChild(domMake.Tree("div", { class: "iet-section-title" }, ["☀️ Luz"]));
        const bInp = domMake.Tree("input", { type: "range", min: -100, max: 100, value: 0, class: "iet-slider" });
        const cInp = domMake.Tree("input", { type: "range", min: -100, max: 100, value: 0, class: "iet-slider" });
        const btnBC = this.createStyledBtn("Aplicar Luz", "✨");
        btnBC.addEventListener("click", () => this.processImageTransform((ctx, w, h) => {
            let imgData = ctx.getImageData(0,0,w,h); let data = imgData.data;
            let b = parseFloat(bInp.value)/100, c = (parseFloat(cInp.value)+100)/100;
            let intercept = 128 * (1 - c);
            for(let i=0; i<data.length; i+=4){
                for(let j=0; j<3; j++) {
                    let val = data[i+j] + (b * 255);
                    data[i+j] = Math.max(0, Math.min(255, (val * c) + intercept));
                }
            }
            ctx.putImageData(imgData, 0, 0);
        }));
        cardBC.appendAll(this.createControlGroup("Brillo", bInp), this.createControlGroup("Contraste", cInp, btnBC));

        // Saturación
        const cardSat = domMake.Tree("div", { class: "iet-tool-card" });
        cardSat.appendChild(domMake.Tree("div", { class: "iet-section-title" }, ["🎨 Saturación"]));
        const sInp = domMake.Tree("input", { type: "range", min: 0, max: 200, value: 150, class: "iet-slider" });
        const btnSat = this.createStyledBtn("Aplicar Color", "🌈");
        btnSat.addEventListener("click", () => this.processImageTransform((ctx, w, h) => {
            let imgData = ctx.getImageData(0,0,w,h); let data = imgData.data; let s = parseFloat(sInp.value)/100;
            for(let i=0; i<data.length; i+=4){
                let gray = 0.299*data[i] + 0.587*data[i+1] + 0.114*data[i+2];
                data[i] = Math.min(255, Math.max(0, gray + (data[i] - gray)*s));
                data[i+1] = Math.min(255, Math.max(0, gray + (data[i+1] - gray)*s));
                data[i+2] = Math.min(255, Math.max(0, gray + (data[i+2] - gray)*s));
            }
            ctx.putImageData(imgData, 0, 0);
        }));
        cardSat.appendChild(this.createControlGroup("Nivel", sInp, btnSat));

        // Border Radius (NUEVO)
        const cardBorder = domMake.Tree("div", { class: "iet-tool-card" });
        cardBorder.appendChild(domMake.Tree("div", { class: "iet-section-title" }, ["🔲 Bordes Redondeados"]));
        const radInp = domMake.Tree("input", { type: "range", min: 0, max: 50, value: 10, class: "iet-slider" });
        const btnRadius = this.createStyledBtn("Aplicar Radius", "⭕");
        btnRadius.addEventListener("click", () => this.processImageTransform((ctx, w, h, img) => {
            let percentage = parseFloat(radInp.value) / 100;
            let radius = percentage * Math.min(w, h); // Max radio = 50% = Círculo perfecto si es cuadrado
            ctx.clearRect(0, 0, w, h);
            ctx.beginPath();
            if (ctx.roundRect) {
                ctx.roundRect(0, 0, w, h, radius);
            } else {
                // Fallback para navegadores antiguos
                ctx.moveTo(radius, 0); ctx.lineTo(w - radius, 0); ctx.quadraticCurveTo(w, 0, w, radius);
                ctx.lineTo(w, h - radius); ctx.quadraticCurveTo(w, h, w - radius, h);
                ctx.lineTo(radius, h); ctx.quadraticCurveTo(0, h, 0, h - radius);
                ctx.lineTo(0, radius); ctx.quadraticCurveTo(0, 0, radius, 0);
            }
            ctx.clip();
            ctx.drawImage(img, 0, 0, w, h);
        }));
        cardBorder.appendChild(this.createControlGroup("Curvatura (%)", radInp, btnRadius));

        // Binarizar e Invertir
        const cardBin = domMake.Tree("div", { class: "iet-tool-card" });
        cardBin.appendChild(domMake.Tree("div", { class: "iet-section-title" }, ["🔀 Otros"]));
        const tInp = domMake.Tree("input", { type: "range", min: 0, max: 255, value: 128, class: "iet-slider" });
        const btnThresh = this.createStyledBtn("Umbral", "🦓");
        btnThresh.addEventListener("click", () => this.processImageTransform((ctx, w, h) => {
            let imgData = ctx.getImageData(0,0,w,h); let data = imgData.data; let th = parseInt(tInp.value);
            for(let i=0; i<data.length; i+=4){
                let v = (0.299*data[i] + 0.587*data[i+1] + 0.114*data[i+2]) >= th ? 255 : 0;
                data[i] = data[i+1] = data[i+2] = v;
            }
            ctx.putImageData(imgData, 0, 0);
        }));

        const btnInv = this.createStyledBtn("Invertir", "💡");
        btnInv.addEventListener("click", () => this.processImageTransform((ctx, w, h) => {
            let imgData = ctx.getImageData(0,0,w,h); let data = imgData.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(imgData, 0, 0);
        }));

        cardBin.appendAll(this.createControlGroup("Binarizar", tInp, btnThresh), btnInv);

        grid.appendAll(cardBC, cardSat, cardBorder, cardBin);
        return grid;
    }

    buildFiltersUI() {
        const grid = domMake.Tree("div", { class: "iet-tool-grid" });

        // Estilos Clásicos (Grises, Sepia)
        const cardStyle = domMake.Tree("div", { class: "iet-tool-card" });
        cardStyle.appendChild(domMake.Tree("div", { class: "iet-section-title" }, ["🎞️ Clásicos"]));
        const btnGray = this.createStyledBtn("B&N", "🌚");
        btnGray.addEventListener("click", () => this.processImageTransform((ctx, w, h) => {
            let imgData = ctx.getImageData(0,0,w,h); let data = imgData.data;
            for(let i=0; i<data.length; i+=4){
                let avg = data[i]*0.299 + data[i+1]*0.587 + data[i+2]*0.114;
                data[i] = data[i+1] = data[i+2] = avg;
            }
            ctx.putImageData(imgData, 0, 0);
        }));
        const btnSepia = this.createStyledBtn("Sepia", "📜");
        btnSepia.addEventListener("click", () => this.processImageTransform((ctx, w, h) => {
            let imgData = ctx.getImageData(0,0,w,h); let data = imgData.data;
            for(let i=0; i<data.length; i+=4){
                let r = data[i], g = data[i+1], b = data[i+2];
                data[i] = Math.min(255, (r * 0.393) + (g * 0.769) + (b * 0.189));
                data[i+1] = Math.min(255, (r * 0.349) + (g * 0.686) + (b * 0.168));
                data[i+2] = Math.min(255, (r * 0.272) + (g * 0.534) + (b * 0.131));
            }
            ctx.putImageData(imgData, 0, 0);
        }));
        cardStyle.appendAll(btnGray, btnSepia);

        // Overlays y Efectos (NUEVO - 10 Opciones)
        const cardOverlay = domMake.Tree("div", { class: "iet-tool-card" });
        cardOverlay.appendChild(domMake.Tree("div", { class: "iet-section-title" }, ["🌌 Filtros Ópticos"]));
        const selOverlay = domMake.Tree("select", { class: "iet-select" });
        ["Scanlines", "Matrix", "Nieve", "Fuego", "Agua", "Light Leak", "Ruido TV", "Sepia Antiguo", "Viñeta Profunda", "Arcoíris"].forEach((o, i) => {
            selOverlay.appendChild(domMake.Tree("option", { value: i }, [o]));
        });
        const btnOverlay = this.createStyledBtn("Aplicar Filtro", "🔮");
        btnOverlay.addEventListener("click", () => this.processImageTransform((ctx, w, h, img) => {
            let type = parseInt(selOverlay.value);
            ctx.globalCompositeOperation = "source-over";
            if (type === 0) { // Scanlines
                ctx.fillStyle = "rgba(0,0,0,0.3)";
                for(let y=0; y<h; y+=4) ctx.fillRect(0, y, w, 2);
            } else if (type === 1) { // Matrix
                ctx.fillStyle = "rgba(0, 255, 0, 0.15)";
                ctx.fillRect(0, 0, w, h);
                ctx.fillStyle = "rgba(0, 255, 0, 0.6)";
                for(let i=0; i<100; i++) ctx.fillRect(Math.random()*w, Math.random()*h, 2, Math.random()*50);
            } else if (type === 2) { // Nieve
                ctx.fillStyle = "rgba(255,255,255,0.7)";
                for(let i=0; i<(w*h)/1000; i++) {
                    ctx.beginPath(); ctx.arc(Math.random()*w, Math.random()*h, Math.random()*3+1, 0, Math.PI*2); ctx.fill();
                }
            } else if (type === 3) { // Fuego
                let grad = ctx.createLinearGradient(0, h, 0, h/2);
                grad.addColorStop(0, "rgba(255, 69, 0, 0.6)"); grad.addColorStop(1, "rgba(255, 215, 0, 0)");
                ctx.fillStyle = grad; ctx.globalCompositeOperation = "color-dodge"; ctx.fillRect(0, 0, w, h);
            } else if (type === 4) { // Agua
                ctx.fillStyle = "rgba(0, 105, 148, 0.3)";
                ctx.globalCompositeOperation = "overlay"; ctx.fillRect(0, 0, w, h);
            } else if (type === 5) { // Light Leak
                let grad = ctx.createRadialGradient(0, 0, 0, 0, 0, w/1.5);
                grad.addColorStop(0, "rgba(255, 100, 50, 0.6)"); grad.addColorStop(1, "rgba(255, 100, 50, 0)");
                ctx.fillStyle = grad; ctx.globalCompositeOperation = "screen"; ctx.fillRect(0, 0, w, h);
            } else if (type === 6) { // Ruido TV
                let imgData = ctx.getImageData(0,0,w,h); let d = imgData.data;
                for(let i=0; i<d.length; i+=4) { let val = (Math.random()-0.5)*50; d[i]+=val; d[i+1]+=val; d[i+2]+=val; }
                ctx.putImageData(imgData, 0, 0);
            } else if (type === 7) { // Sepia Antiguo
                ctx.fillStyle = "rgba(112, 66, 20, 0.3)"; ctx.globalCompositeOperation = "color"; ctx.fillRect(0,0,w,h);
                ctx.strokeStyle = "rgba(0,0,0,0.2)"; ctx.lineWidth = 1; ctx.globalCompositeOperation = "source-over";
                for(let i=0; i<20; i++) { ctx.beginPath(); ctx.moveTo(Math.random()*w, 0); ctx.lineTo(Math.random()*w, h); ctx.stroke(); }
            } else if (type === 8) { // Viñeta Profunda
                let grad = ctx.createRadialGradient(w/2, h/2, w/4, w/2, h/2, w/1.2);
                grad.addColorStop(0, "rgba(0,0,0,0)"); grad.addColorStop(1, "rgba(0,0,0,0.9)");
                ctx.fillStyle = grad; ctx.fillRect(0, 0, w, h);
            } else if (type === 9) { // Arcoíris
                let grad = ctx.createLinearGradient(0, 0, w, h);
                grad.addColorStop(0,"rgba(255,0,0,0.2)"); grad.addColorStop(0.33,"rgba(0,255,0,0.2)"); grad.addColorStop(0.66,"rgba(0,0,255,0.2)"); grad.addColorStop(1,"rgba(255,0,255,0.2)");
                ctx.fillStyle = grad; ctx.globalCompositeOperation = "color"; ctx.fillRect(0, 0, w, h);
            }
            ctx.globalCompositeOperation = "source-over"; // Reset
        }));
        cardOverlay.appendAll(selOverlay, btnOverlay);

        // Marcos (NUEVO - 10 Opciones)
        const cardFrame = domMake.Tree("div", { class: "iet-tool-card" });
        cardFrame.appendChild(domMake.Tree("div", { class: "iet-section-title" }, ["🖼️ Marcos Generativos"]));
        const selFrame = domMake.Tree("select", { class: "iet-select" });
        ["Polaroid", "Cine", "Tecnológico", "Medieval", "Oro", "Neón", "Grunge", "Naturaleza", "Cómic", "Sello Postal"].forEach((o, i) => {
            selFrame.appendChild(domMake.Tree("option", { value: i }, [o]));
        });
        const btnFrame = this.createStyledBtn("Aplicar Marco", "🎨");
        btnFrame.addEventListener("click", () => this.processImageTransform((ctx, w, h, img) => {
            let type = parseInt(selFrame.value);
            let minDim = Math.min(w, h);
            ctx.globalCompositeOperation = "source-over";

            if (type === 0) { // Polaroid
                let bw = minDim * 0.05, bh = minDim * 0.15;
                let nW = w + bw*2, nH = h + bw + bh;
                ctx.canvas.width = nW; ctx.canvas.height = nH;
                ctx.fillStyle = "white"; ctx.fillRect(0, 0, nW, nH);
                ctx.shadowColor = "rgba(0,0,0,0.3)"; ctx.shadowBlur = 10;
                ctx.drawImage(img, bw, bw, w, h); ctx.shadowBlur = 0; // Reset
            } else if (type === 1) { // Cine
                let bar = h * 0.12;
                ctx.fillStyle = "black"; ctx.fillRect(0, 0, w, bar); ctx.fillRect(0, h - bar, w, bar);
            } else if (type === 2) { // Tecnológico
                ctx.strokeStyle = "#00ffff"; ctx.lineWidth = minDim * 0.02;
                ctx.strokeRect(w*0.05, h*0.05, w*0.9, h*0.9);
                ctx.lineWidth = minDim * 0.05;
                ctx.beginPath(); ctx.moveTo(w*0.05, h*0.1); ctx.lineTo(w*0.05, h*0.05); ctx.lineTo(w*0.1, h*0.05); ctx.stroke();
                ctx.beginPath(); ctx.moveTo(w*0.95, h*0.1); ctx.lineTo(w*0.95, h*0.05); ctx.lineTo(w*0.9, h*0.05); ctx.stroke();
                ctx.beginPath(); ctx.moveTo(w*0.05, h*0.9); ctx.lineTo(w*0.05, h*0.95); ctx.lineTo(w*0.1, h*0.95); ctx.stroke();
                ctx.beginPath(); ctx.moveTo(w*0.95, h*0.9); ctx.lineTo(w*0.95, h*0.95); ctx.lineTo(w*0.9, h*0.95); ctx.stroke();
            } else if (type === 3) { // Medieval
                ctx.strokeStyle = "#3e2723"; ctx.lineWidth = minDim * 0.06; ctx.strokeRect(0, 0, w, h);
                ctx.strokeStyle = "#ffb300"; ctx.lineWidth = minDim * 0.01; ctx.strokeRect(minDim*0.03, minDim*0.03, w - minDim*0.06, h - minDim*0.06);
            } else if (type === 4) { // Oro
                let grad = ctx.createLinearGradient(0, 0, w, h);
                grad.addColorStop(0, "#bf953f"); grad.addColorStop(0.5, "#fcf6ba"); grad.addColorStop(1, "#b38728");
                ctx.strokeStyle = grad; ctx.lineWidth = minDim * 0.05; ctx.strokeRect(0, 0, w, h);
            } else if (type === 5) { // Neón
                ctx.strokeStyle = "#ff00ff"; ctx.lineWidth = minDim * 0.03;
                ctx.shadowColor = "#ff00ff"; ctx.shadowBlur = 20;
                ctx.strokeRect(minDim*0.05, minDim*0.05, w - minDim*0.1, h - minDim*0.1); ctx.shadowBlur = 0;
            } else if (type === 6) { // Grunge
                ctx.fillStyle = "black";
                for(let i=0; i<w; i+=10) { ctx.fillRect(i, 0, 10, Math.random()*minDim*0.05); ctx.fillRect(i, h - Math.random()*minDim*0.05, 10, minDim*0.05); }
                for(let i=0; i<h; i+=10) { ctx.fillRect(0, i, Math.random()*minDim*0.05, 10); ctx.fillRect(w - Math.random()*minDim*0.05, i, minDim*0.05, 10); }
            } else if (type === 7) { // Naturaleza
                ctx.strokeStyle = "#2e7d32"; ctx.lineWidth = minDim * 0.04; ctx.strokeRect(0, 0, w, h);
                ctx.fillStyle = "#4caf50";
                ctx.beginPath(); ctx.ellipse(minDim*0.05, minDim*0.05, minDim*0.06, minDim*0.03, Math.PI/4, 0, Math.PI*2); ctx.fill();
                ctx.beginPath(); ctx.ellipse(w - minDim*0.05, h - minDim*0.05, minDim*0.06, minDim*0.03, Math.PI/4, 0, Math.PI*2); ctx.fill();
            } else if (type === 8) { // Cómic
                ctx.strokeStyle = "black"; ctx.lineWidth = minDim * 0.05; ctx.strokeRect(0, 0, w, h);
                ctx.strokeStyle = "white"; ctx.lineWidth = minDim * 0.01; ctx.strokeRect(minDim*0.025, minDim*0.025, w - minDim*0.05, h - minDim*0.05);
            } else if (type === 9) { // Sello Postal
                let b = minDim * 0.06;
                ctx.canvas.width = w + b*2; ctx.canvas.height = h + b*2;
                ctx.fillStyle = "white"; ctx.fillRect(0, 0, w + b*2, h + b*2);
                ctx.drawImage(img, b, b, w, h);
                ctx.globalCompositeOperation = "destination-out";
                ctx.fillStyle = "black";
                for(let i=0; i<w+b*2; i+=b*1.5) { ctx.beginPath(); ctx.arc(i, 0, b*0.4, 0, Math.PI*2); ctx.arc(i, h+b*2, b*0.4, 0, Math.PI*2); ctx.fill(); }
                for(let i=0; i<h+b*2; i+=b*1.5) { ctx.beginPath(); ctx.arc(0, i, b*0.4, 0, Math.PI*2); ctx.arc(w+b*2, i, b*0.4, 0, Math.PI*2); ctx.fill(); }
            }
        }));
        cardFrame.appendAll(selFrame, btnFrame);

        grid.appendAll(cardStyle, cardOverlay, cardFrame);
        return grid;
    }

    buildAdvancedUI() {
        const grid = domMake.Tree("div", { class: "iet-tool-grid" });

        // Tono (Hue) - (RESTAURADO)
        const cardHue = domMake.Tree("div", { class: "iet-tool-card" });
        cardHue.appendChild(domMake.Tree("div", { class: "iet-section-title" }, ["🌈 Ajuste de Tono (HUE)"]));
        const hueInp = domMake.Tree("input", { type: "range", min: 0, max: 360, value: 0, class: "iet-slider" });
        const btnHue = this.createStyledBtn("Girar Colores", "🔄");
        btnHue.addEventListener("click", () => this.processImageTransform((ctx, w, h) => {
            let imgData = ctx.getImageData(0,0,w,h); let data = imgData.data;
            let hueShift = parseInt(hueInp.value);
            for(let i=0; i<data.length; i+=4){
                let hsl = rgbToHsl(data[i], data[i+1], data[i+2]);
                hsl[0] = (hsl[0] + hueShift) % 360;
                let rgb = hslToRgb(hsl[0], hsl[1], hsl[2]);
                data[i] = rgb[0]; data[i+1] = rgb[1]; data[i+2] = rgb[2];
            }
            ctx.putImageData(imgData, 0, 0);
        }));
        cardHue.appendChild(this.createControlGroup("Grados HUE (0-360)", hueInp, btnHue));

        // Blur & Pixelate
        const cardObscure = domMake.Tree("div", { class: "iet-tool-card" });
        cardObscure.appendChild(domMake.Tree("div", { class: "iet-section-title" }, ["💧 Ocultar"]));
        const blurInp = domMake.Tree("input", { type: "range", min: 0, max: 10, value: 2, class: "iet-slider" });
        const btnBlur = this.createStyledBtn("Blur", "🌫️");
        btnBlur.addEventListener("click", () => this.processImageTransform((ctx, w, h) => {
            let r = parseInt(blurInp.value); if(r===0) return;
            let imgData = ctx.getImageData(0,0,w,h); let px = imgData.data; let out = new Uint8ClampedArray(px.length);
            for(let y=0; y<h; y++){
                for(let x=0; x<w; x++){
                    let rs=0, gs=0, bs=0, count=0;
                    for(let dy=-r; dy<=r; dy++){
                        for(let dx=-r; dx<=r; dx++){
                            let nx=x+dx, ny=y+dy;
                            if(nx>=0 && nx<w && ny>=0 && ny<h){
                                let idx=(ny*w+nx)*4; rs+=px[idx]; gs+=px[idx+1]; bs+=px[idx+2]; count++;
                            }
                        }
                    }
                    let i=(y*w+x)*4; out[i]=rs/count; out[i+1]=gs/count; out[i+2]=bs/count; out[i+3]=px[i+3];
                }
            }
            imgData.data.set(out); ctx.putImageData(imgData, 0, 0);
        }));

        const pxInp = domMake.Tree("input", { type: "range", min: 2, max: 50, value: 10, class: "iet-slider" });
        const btnPx = this.createStyledBtn("Pixelar", "👾");
        btnPx.addEventListener("click", () => this.processImageTransform((ctx, w, h, img) => {
            let size = parseInt(pxInp.value);
            ctx.imageSmoothingEnabled = false;
            ctx.drawImage(img, 0, 0, w/size, h/size);
            ctx.drawImage(ctx.canvas, 0, 0, w/size, h/size, 0, 0, w, h);
        }));

        cardObscure.appendAll(this.createControlGroup("Fuerza Blur", blurInp, btnBlur), this.createControlGroup("Bloque Pixel", pxInp, btnPx));

        // Convoluciones (Nitidez, Bordes)
        const cardConv = domMake.Tree("div", { class: "iet-tool-card" });
        cardConv.appendChild(domMake.Tree("div", { class: "iet-section-title" }, ["👁️ Detalles Exactos"]));
        const btnSharp = this.createStyledBtn("Nitidez", "🔪");
        btnSharp.addEventListener("click", () => this.processImageTransform((ctx, w, h) => {
            const matrix = [ 0, -1, 0, -1, 5, -1, 0, -1, 0 ];
            this.applyConvolution(ctx, w, h, matrix);
        }));
        const btnEdge = this.createStyledBtn("Bordes", "🖊️");
        btnEdge.addEventListener("click", () => this.processImageTransform((ctx, w, h) => {
            const matrix = [ -1, -1, -1, -1, 8, -1, -1, -1, -1 ];
            this.applyConvolution(ctx, w, h, matrix);
        }));
        cardConv.appendAll(btnSharp, btnEdge);

        // Ruido
        const cardNoise = domMake.Tree("div", { class: "iet-tool-card" });
        cardNoise.appendChild(domMake.Tree("div", { class: "iet-section-title" }, ["🔊 Ruido"]));
        const nSlider = domMake.Tree("input", { type: "range", min: 0, max: 100, value: 30, class: "iet-slider" });
        const btnNoise = this.createStyledBtn("Suavizar", "🧹");
        btnNoise.addEventListener("click", () => this.processImageTransform((ctx, w, h) => {
            let imgData = ctx.getImageData(0,0,w,h); let data = imgData.data;
            const intensity = parseInt(nSlider.value)/100;
            for(let i=0; i<data.length; i+=4){
                const avg = (data[i] + data[i+1] + data[i+2])/3;
                data[i] = data[i+1] = data[i+2] = avg * (1-intensity) + data[i] * intensity;
            }
            ctx.putImageData(imgData, 0, 0);
        }));
        cardNoise.appendChild(this.createControlGroup("Fuerza", nSlider, btnNoise));

        grid.appendAll(cardHue, cardObscure, cardConv, cardNoise);
        return grid;
    }
}

// ================= FUNCIONES DE COLOR GLOBALES =================
function 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 * 360, s, l];
}

function hslToRgb(h, s, l) {
    let r, g, b;
    if (s === 0) { r = g = b = l; } else {
        const hue2rgb = (p, q, t) => {
            if (t < 0) t += 1;
            if (t > 1) t -= 1;
            if (t < 1 / 6) return p + (q - p) * 6 * t;
            if (t < 1 / 2) return q;
            if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
            return p;
        };
        const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        const p = 2 * l - q;
        r = hue2rgb(p, q, h / 360 + 1 / 3);
        g = hue2rgb(p, q, h / 360);
        b = hue2rgb(p, q, h / 360 - 1 / 3);
    }
    return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}

// ================= HELPERS =================
function hexToRgb(hex) {
    const r = parseInt(hex.slice(1, 3), 16);
    const g = parseInt(hex.slice(3, 5), 16);
    const b = parseInt(hex.slice(5, 7), 16);
    return [r, g, b];
}

function rgbToHsl(r, g, b) {
    r /= 255; g /= 255; b /= 255;
    const max = Math.max(r, g, b);
    const 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 * 360, s, l];
}

function hslToRgb(h, s, l) {
    let r, g, b;

    if (s === 0) {
        r = g = b = l;
    } else {
        const hue2rgb = (p, q, t) => {
            if (t < 0) t += 1;
            if (t > 1) t -= 1;
            if (t < 1 / 6) return p + (q - p) * 6 * t;
            if (t < 1 / 2) return q;
            if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
            return p;
        };

        const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        const p = 2 * l - q;
        r = hue2rgb(p, q, h / 360 + 1 / 3);
        g = hue2rgb(p, q, h / 360);
        b = hue2rgb(p, q, h / 360 - 1 / 3);
    }

    return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
// ================= HELPERS =================
function hexToRgb(hex) {
    const r = parseInt(hex.slice(1, 3), 16);
    const g = parseInt(hex.slice(3, 5), 16);
    const b = parseInt(hex.slice(5, 7), 16);
    return [r, g, b];
}

function rgbToHsl(r, g, b) {
    r /= 255; g /= 255; b /= 255;
    const max = Math.max(r, g, b);
    const 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 * 360, s, l];
}

function hslToRgb(h, s, l) {
    let r, g, b;

    if (s === 0) {
        r = g = b = l;
    } else {
        const hue2rgb = (p, q, t) => {
            if (t < 0) t += 1;
            if (t > 1) t -= 1;
            if (t < 1 / 6) return p + (q - p) * 6 * t;
            if (t < 1 / 2) return q;
            if (t < 2 / 3) return p + (q - p) * (2 / 3 - t) * 6;
            return p;
        };

        const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
        const p = 2 * l - q;
        r = hue2rgb(p, q, h / 360 + 1 / 3);
        g = hue2rgb(p, q, h / 360);
        b = hue2rgb(p, q, h / 360 - 1 / 3);
    }

    return [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}

    // =========================================================================
    // 4. GRUPO B: NUEVOS MÓDULOS DE BÚSQUEDA (API & HTML ESTÁTICO)
    // =========================================================================

    class WikipediaLore extends QBit {
        constructor() {
            super("Wikipedia Lore Finder", '📚');
            this.container = domMake.Tree("div");
            this.htmlElements.section.appendChild(this.container);
            this.htmlElements.details.open = true;
        }

        search(keyword) {
            this.container.innerHTML = `<div style="text-align:center; padding: 10px; color:#007bff;">Buscando "${keyword}" en Wikipedia...</div>`;

            // API Oficial de Wikipedia (JSON)
            GM_xmlhttpRequest({
                method: "GET",
                url: `https://en.wikipedia.org/w/api.php?action=query&format=json&list=search&srsearch=${encodeURIComponent(keyword)}&utf8=1&origin=*`,
                onload: (res) => {
                    try {
                        const data = JSON.parse(res.responseText);
                        const results = data.query.search;
                        this.container.innerHTML = "";
                        let count = 0;

                        results.forEach(item => {
                            if (count >= 3) return;
                            let title = item.title;
                            let snippet = item.snippet + "...";
                            let link = `https://en.wikipedia.org/?curid=${item.pageid}`;

                            let card = domMake.Tree("div", { class: "osint-card" });
                            card.innerHTML = `<a href="${link}" target="_blank" style="font-size:0.9em;">${title}</a>
                                              <div style="font-size:0.8em; color:#495057; margin-top:4px;">${snippet}</div>`;
                            this.container.appendChild(card);
                            count++;
                        });

                        if(count === 0) this.container.innerHTML = "<div style='color:#6c757d; text-align:center;'>Sin resultados en Wikipedia.</div>";
                    } catch (e) {
                        this.container.innerHTML = `<div style="color:#dc3545; padding:5px;">Error API Wiki: ${e.message}</div>`;
                    }
                },
                onerror: (e) => {
                    this.container.innerHTML = `<div style="color:#dc3545; padding:5px;">Error Red Wiki: ${e.statusText}</div>`;
                }
            });
        }
    }

    class DuckDuckGoPivot extends QBit {
        constructor() {
            super("Best Results", '🔎');
            this.container = domMake.Tree("div");
            this.htmlElements.section.appendChild(this.container);
            this.htmlElements.details.open = true;
        }

        search(keyword) {
            this.container.innerHTML = `<div style="text-align:center; padding: 10px; color:#007bff;">Buscando "${keyword}" en DDG...</div>`;

            // Versión HTML Estática de DuckDuckGo (Sin JS)
            GM_xmlhttpRequest({
                method: "GET",
                url: `https://duckduckgo.com/html/?q=${encodeURIComponent(keyword)}`,
                headers: {
                    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
                    "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
                },
                onload: (res) => {
                    try {
                        const parser = new DOMParser();
                        const doc = parser.parseFromString(res.responseText, "text/html");
                        const items = doc.querySelectorAll('.result');

                        this.container.innerHTML = "";
                        let count = 0;

                        items.forEach(item => {
                            if (count >= 5) return;
                            const a = item.querySelector('.result__a');
                            if (!a) return;

                            let title = a.textContent || "";
                            let rawLink = a.getAttribute('href') || "";

                            // Limpiar redirecciones de DDG
                            let link = rawLink;
                            if (link.includes('uddg=')) {
                                try { link = decodeURIComponent(link.split('uddg=')[1].split('&')[0]); } catch(e){}
                            } else if (!link.startsWith('http')) {
                                link = 'https://duckduckgo.com' + link;
                            }

                            let card = domMake.Tree("div", { class: "osint-card" });
                            card.innerHTML = `<a href="${link}" target="_blank" style="font-size:0.9em; word-break: break-all;">${title}</a>`;
                            this.container.appendChild(card);
                            count++;
                        });

                        if(count === 0) this.container.innerHTML = "<div style='color:#6c757d; text-align:center;'>Sin resultados en DuckDuckGo.</div>";
                    } catch (e) {
                        this.container.innerHTML = `<div style="color:#dc3545; padding:5px;">Error Scraping DDG: ${e.message}</div>`;
                    }
                },
                onerror: (e) => {
                    this.container.innerHTML = `<div style="color:#dc3545; padding:5px;">Error Red DDG: ${e.statusText}</div>`;
                }
            });
        }
    }

    // =========================================================================
    // 5. DOM HIJACKER (Sincronización Silenciosa)
    // =========================================================================
    function getCleanText(btn) {
        const clone = btn.cloneNode(true);
        const span = clone.querySelector('span');
        if (span) span.remove();
        return clone.textContent.trim();
    }

    function initDOMHijacker(wikiModule, ddgModule) {
        setInterval(() => {
            const picker = document.getElementById('identity-picker-ui');
            if (!picker) return;

            const buttons = picker.querySelectorAll('button:not([data-osint-hijacked])');
            buttons.forEach(btn => {
                btn.setAttribute('data-osint-hijacked', 'true');
                btn.addEventListener('click', () => {
                    const cleanText = getCleanText(btn);
                    if (cleanText && cleanText.length > 0 && !cleanText.includes('Scanning...')) {
                        console.log(`[DOM Hijacker] Capturada keyword: "${cleanText}"`);
                        wikiModule.search(cleanText);
                        ddgModule.search(cleanText);
                    }
                });
            });
        }, 1000);
    }

// =========================================================================
    // 6. ENSAMBLAJE FINAL, SUPERVIVENCIA AL DOM WIPE Y LÓGICA DE EXPANSIÓN
    // =========================================================================
    let sidebarWrapperInstance = null; // Singleton para guardar la estructura base

    function mountSidebar() {
        if (sidebarWrapperInstance) {
            // Si la instancia ya existe pero el Script 2 la borró del DOM, la reinyectamos
            if (!document.getElementById('osint-sidebar-wrapper')) {
                document.body.appendChild(sidebarWrapperInstance);
            }
            return;
        }

        const wrapper = document.createElement("div");
        wrapper.id = "osint-sidebar-wrapper";
        wrapper.classList.add("collapsed");

        const container = document.createElement("div");
        container.id = "osint-sidebar-container";

        const toggle = document.createElement("div");
        toggle.id = "osint-sidebar-toggle";
        toggle.innerHTML = '▶';

        toggle.addEventListener("click", () => {
            const isCollapsed = wrapper.classList.toggle("collapsed");
            toggle.innerHTML = isCollapsed ? '▶' : '◀';
        });

        // Cabecera Principal
        const header = document.createElement("h4");
        header.style.margin = "0 0 15px 0";
        header.style.color = "#007bff";
        header.style.textAlign = "center";
        header.style.borderBottom = "2px solid #dee2e6";
        header.style.paddingBottom = "5px";
        header.innerHTML = '🔍 EXTRA INFO';
        container.appendChild(header);

        wrapper.appendChild(container);
        wrapper.appendChild(toggle);
        document.body.appendChild(wrapper);
        sidebarWrapperInstance = wrapper; // Guardamos el DOM generado en memoria

        // Inicializar Módulos solo la primera vez
        const propsMod = new ImageProperties();
        const searchHub = new ReverseSearchHub();
        const analyzerMod = new ImageAnalyzer();
        const toolsMod = new ImageToolsFull(); // El nuevo Image Editor Pro Elite
        const wikiMod = new WikipediaLore();
        const ddgMod = new DuckDuckGoPivot();

        // Inyectar en el contenedor
        container.appendChild(propsMod.htmlElements.details);
        container.appendChild(searchHub.htmlElements.details);
        container.appendChild(analyzerMod.htmlElements.details);
        container.appendChild(wikiMod.htmlElements.details);
        container.appendChild(ddgMod.htmlElements.details);
        container.appendChild(toolsMod.htmlElements.details);

        // Activar el Hijacker de clics con los nuevos módulos
        initDOMHijacker(wikiMod, ddgMod);

        // Ejecutar Auto-Inferencia de imagen
        autoLoadImageContext();
    }

    // EL ESCUDO: Vigila si el body sufre un innerHTML (como hace el Script 2)
    // y restaura el Sidebar instantáneamente sin perder el estado de las herramientas.
    function enforceSidebarSurvival() {
        const observer = new MutationObserver(() => {
            if (document.body && !document.getElementById('osint-sidebar-wrapper')) {
                mountSidebar();
            }
        });
        observer.observe(document.documentElement, { childList: true, subtree: true });
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => {
            mountSidebar();
            enforceSidebarSurvival();
        });
    } else {
        mountSidebar();
        enforceSidebarSurvival();
    }

})();