Drawaria OSINT Sidebar Companion

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

This script should not be not be installed directly. It is a library for other scripts to include with the meta directive // @require https://update.greatest.deepsurf.us/scripts/570442/1779451/Drawaria%20OSINT%20Sidebar%20Companion.js

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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

})();