API Interceptor + DOM Inspector

Fixed version with better memory management and storage

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         API Interceptor + DOM Inspector
// @namespace    http://tampermonkey.net/
// @version      1.0.0
// @description  Fixed version with better memory management and storage
// @author       gl4.manu
// @match        *://*/*
// @grant        GM_log
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_addStyle
// @grant        GM_listValues
// @run-at       document-start
// @noframes
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Configuration
    const CONFIG = {
        enabled: true,
        maxLogEntries: 50, // Reduced for performance
        maxBodySize: 1000, // Smaller body size
        maxStorageSize: 500000, // ~500KB max storage
        autoSaveInterval: 5000,
        persistData: true,
        persistKey: 'devtools_pro_v4',
        logToConsole: false, // Disable console logging for performance
        compressData: true, // Compress stored data
        cleanupInterval: 60000, // Cleanup every minute
        maxTooltipAge: 3000 // Auto-hide tooltip after 3 seconds
    };

    // Storage
    let interceptedRequests = [];
    let requestId = 0;
    let expandedItems = new Set();
    let inspectorActive = false;
    let currentTooltip = null;
    let tooltipTimeout = null;
    let currentHoverElement = null;
    let originalFetch = null;
    let saveTimeout = null;
    let updateQueue = [];
    let updateTimer = null;

    // ==================== MEMORY MANAGEMENT ====================

    function cleanupOldData() {
        // Remove old entries beyond limit
        if (interceptedRequests.length > CONFIG.maxLogEntries) {
            const removed = interceptedRequests.splice(CONFIG.maxLogEntries);
            removed.forEach(r => expandedItems.delete(r.id));
        }

        // Clear old tooltips
        if (currentTooltip && currentTooltip.parentNode) {
            clearTooltip();
        }

        // Force garbage collection hint
        if (interceptedRequests.length > CONFIG.maxLogEntries * 0.8) {
            console.log('[Cleanup] Reduced to', interceptedRequests.length, 'entries');
        }
    }

    function compressData(data) {
        if (!CONFIG.compressData) return JSON.stringify(data);

        // Simple compression: remove unnecessary whitespace and truncate long strings
        const compressed = JSON.stringify(data, (key, value) => {
            if (typeof value === 'string' && value.length > 500) {
                return value.substring(0, 500) + '...[truncated]';
            }
            return value;
        });

        return compressed;
    }

    function decompressData(compressed) {
        try {
            return JSON.parse(compressed);
        } catch(e) {
            return null;
        }
    }

    function getStorageSize() {
        let total = 0;
        const keys = GM_listValues();
        for (let key of keys) {
            const value = GM_getValue(key, '');
            total += value.length;
        }
        return total;
    }

    function saveData() {
        if (!CONFIG.persistData) return;

        try {
            // Check storage size
            if (getStorageSize() > CONFIG.maxStorageSize) {
                console.warn('[Storage] Size limit reached, clearing old data');
                const oldData = GM_getValue(CONFIG.persistKey, '');
                if (oldData.length > CONFIG.maxStorageSize * 0.8) {
                    // Keep only last 25 entries
                    const trimmed = interceptedRequests.slice(0, 25);
                    const compressed = compressData({
                        version: '4.1.0',
                        timestamp: Date.now(),
                        requests: trimmed,
                        requestId: requestId
                    });
                    GM_setValue(CONFIG.persistKey, compressed);
                    return;
                }
            }

            const dataToSave = {
                version: '4.1.0',
                timestamp: Date.now(),
                requests: interceptedRequests.slice(0, CONFIG.maxLogEntries),
                requestId: requestId,
                expandedIds: Array.from(expandedItems)
            };

            const compressed = compressData(dataToSave);
            GM_setValue(CONFIG.persistKey, compressed);

        } catch(e) {
            console.error('[Storage] Save failed:', e);
        }
    }

    function loadData() {
        if (!CONFIG.persistData) return false;

        try {
            const saved = GM_getValue(CONFIG.persistKey, null);
            if (!saved) return false;

            const data = decompressData(saved);
            if (!data) return false;

            // Check age (max 2 hours)
            const maxAge = 7200000;
            if (Date.now() - data.timestamp > maxAge) {
                console.log('[Storage] Data too old, clearing');
                GM_deleteValue(CONFIG.persistKey);
                return false;
            }

            interceptedRequests = data.requests || [];
            requestId = data.requestId || 0;
            expandedItems = new Set(data.expandedIds || []);

            console.log(`[Storage] Loaded ${interceptedRequests.length} requests`);
            return true;
        } catch(e) {
            console.error('[Storage] Load failed:', e);
            return false;
        }
    }

    // ==================== OPTIMIZED UI UPDATES ====================

    function queueUpdate() {
        if (updateTimer) clearTimeout(updateTimer);
        updateTimer = setTimeout(() => {
            updateLogsDisplay();
            updateTimer = null;
        }, 300);
    }

    function updateLogsDisplay() {
        const container = document.getElementById('logs-container');
        if (!container) return;

        const searchTerm = document.querySelector('.search-box')?.value.toLowerCase() || '';
        const filtered = searchTerm ?
            interceptedRequests.filter(log =>
                log.url.toLowerCase().includes(searchTerm) ||
                log.method.toLowerCase().includes(searchTerm)
            ) : interceptedRequests;

        // Virtual scrolling: only render first 20 for performance
        const renderLimit = 20;
        const toRender = filtered.slice(0, renderLimit);
        const hasMore = filtered.length > renderLimit;

        container.innerHTML = toRender.map(log => {
            const isExpanded = expandedItems.has(log.id);
            const hasJWT = !!log.jwt;

            return `
                <div class="log-entry" data-id="${log.id}">
                    <div class="log-header" onclick="window.toggleLog(${log.id})">
                        <span class="expand-icon">${isExpanded ? '▼' : '▶'}</span>
                        <span class="log-method">${escapeHtml(log.method)}</span>
                        <span class="log-url" title="${escapeHtml(log.url)}">${escapeHtml(truncate(log.url, 50))}</span>
                        ${log.status ? `<span class="log-status">${log.status}</span>` : ''}
                        ${hasJWT ? '<span class="badge badge-jwt">🔑</span>' : ''}
                    </div>
                    <div class="log-details ${isExpanded ? 'expanded' : ''}">
                        ${renderDetails(log)}
                    </div>
                </div>
            `;
        }).join('');

        if (hasMore) {
            container.innerHTML += `<div style="text-align:center;padding:5px;color:#888;">+ ${filtered.length - renderLimit} more requests (search to see all)</div>`;
        }

        updateStats(filtered.length);
    }

    function renderDetails(log) {
        let html = '';

        if (log.jwt) {
            html += `<div class="detail-section">
                <div class="detail-title">🔑 JWT</div>
                <div class="detail-content jwt-token">${escapeHtml(truncate(log.jwt, 100))}</div>
            </div>`;
        }

        if (log.requestHeaders) {
            html += `<div class="detail-section">
                <div class="detail-title">📤 Headers</div>
                <div class="detail-content"><pre>${escapeHtml(truncate(JSON.stringify(log.requestHeaders, null, 2), 300))}</pre></div>
            </div>`;
        }

        if (log.responseBody) {
            html += `<div class="detail-section">
                <div class="detail-title">📥 Body</div>
                <div class="detail-content"><pre>${escapeHtml(truncate(log.responseBody, 300))}</pre></div>
            </div>`;
        }

        if (log.error) {
            html += `<div class="detail-section">
                <div class="detail-title">❌ Error</div>
                <div class="detail-content">${escapeHtml(log.error)}</div>
            </div>`;
        }

        html += `<div class="detail-section">
            <div class="detail-title">⏱️ ${new Date(log.timestamp).toLocaleTimeString()}</div>
        </div>`;

        return html;
    }

    function escapeHtml(str) {
        if (!str) return '';
        return str.replace(/[&<>]/g, function(m) {
            if (m === '&') return '&amp;';
            if (m === '<') return '&lt;';
            if (m === '>') return '&gt;';
            return m;
        });
    }

    function truncate(str, len) {
        if (!str) return '';
        return str.length > len ? str.substring(0, len) + '...' : str;
    }

    function updateStats(filteredCount) {
        const statsBar = document.getElementById('stats-bar');
        if (statsBar) {
            const countSpan = document.getElementById('request-count');
            if (countSpan) countSpan.textContent = interceptedRequests.length;
        }
    }

    // ==================== FIXED DOM INSPECTOR ====================

    function clearTooltip() {
        if (tooltipTimeout) clearTimeout(tooltipTimeout);
        if (currentTooltip && currentTooltip.parentNode) {
            currentTooltip.parentNode.removeChild(currentTooltip);
        }
        currentTooltip = null;
    }

    function showTooltipFixed(element, x, y) {
        clearTooltip();

        const tooltip = document.createElement('div');
        tooltip.className = 'dom-inspector-tooltip';

        const tagName = element.tagName.toLowerCase();
        const id = element.id ? `#${element.id}` : '';
        const classes = element.className ? `.${element.className.split(' ')[0]}` : '';

        tooltip.innerHTML = `
            <div><strong>${tagName}${id}${classes}</strong></div>
            <div style="font-size:9px;margin-top:4px;">
                <button onclick="window.copySelector('${escapeHtml(getElementSelector(element))}')">Copy Selector</button>
                <button onclick="window.copyXPath('${escapeHtml(getElementXPath(element))}')">Copy XPath</button>
                <button onclick="window.editElement()">Edit</button>
            </div>
        `;

        document.body.appendChild(tooltip);
        currentTooltip = tooltip;

        // Position
        let left = x + 15;
        let top = y - 30;
        const rect = tooltip.getBoundingClientRect();
        if (left + rect.width > window.innerWidth) left = x - rect.width - 15;
        if (top + rect.height > window.innerHeight) top = y - rect.height - 15;
        if (top < 0) top = 10;
        if (left < 0) left = 10;

        tooltip.style.left = `${left}px`;
        tooltip.style.top = `${top}px`;

        // Auto-hide after 3 seconds
        tooltipTimeout = setTimeout(clearTooltip, CONFIG.maxTooltipAge);

        window.copySelector = (selector) => {
            navigator.clipboard.writeText(selector);
            showNotification('✅ Selector copied');
            clearTooltip();
        };

        window.copyXPath = (xpath) => {
            navigator.clipboard.writeText(xpath);
            showNotification('✅ XPath copied');
            clearTooltip();
        };

        window.editElement = () => {
            const newHTML = prompt('Edit HTML:', element.outerHTML);
            if (newHTML && newHTML !== element.outerHTML) {
                try {
                    const temp = document.createElement('div');
                    temp.innerHTML = newHTML;
                    element.parentNode.replaceChild(temp.firstChild, element);
                    showNotification('✅ Element updated');
                } catch(e) {
                    showNotification('❌ Invalid HTML');
                }
            }
            clearTooltip();
        };
    }

    function getElementSelector(element) {
        if (element.id) return `#${element.id}`;
        if (element.className && typeof element.className === 'string') {
            const cls = element.className.split(' ')[0];
            if (cls) return `${element.tagName.toLowerCase()}.${cls}`;
        }
        return element.tagName.toLowerCase();
    }

    function getElementXPath(element) {
        if (element.id) return `//*[@id="${element.id}"]`;
        if (element === document.body) return '/html/body';

        let ix = 0;
        const siblings = element.parentNode.childNodes;
        for (let i = 0; i < siblings.length; i++) {
            const sibling = siblings[i];
            if (sibling === element) {
                const parentPath = element.parentNode === document.body ? '/html/body' : getElementXPath(element.parentNode);
                return parentPath + '/' + element.tagName.toLowerCase() + '[' + (ix + 1) + ']';
            }
            if (sibling.nodeType === 1 && sibling.tagName === element.tagName) ix++;
        }
        return '';
    }

    function showNotification(msg) {
        const notif = document.createElement('div');
        notif.textContent = msg;
        notif.style.cssText = `
            position: fixed;
            bottom: 80px;
            right: 20px;
            background: #00ff00;
            color: #000;
            padding: 5px 10px;
            border-radius: 4px;
            font-size: 11px;
            z-index: 1000000;
            animation: fadeOut 1.5s ease-out;
        `;
        document.body.appendChild(notif);
        setTimeout(() => notif.remove(), 1500);
    }

    // ==================== FIXED EVENT HANDLERS ====================

    function initDOMInspectorFixed() {
        let inspectorBtn = null;

        const addControls = () => {
            const container = document.getElementById('logs-container');
            if (container && !document.getElementById('inspector-toggle')) {
                const controls = document.createElement('div');
                controls.style.cssText = 'display:flex;gap:5px;margin-bottom:10px;';
                controls.innerHTML = `
                    <button id="inspector-toggle" style="background:#333;color:#0f0;border:1px solid #0f0;padding:3px 8px;border-radius:3px;cursor:pointer;">🔍 DOM Inspector OFF</button>
                    <button id="inspector-clear-highlight" style="background:#333;color:#0f0;border:1px solid #0f0;padding:3px 8px;border-radius:3px;cursor:pointer;">✨ Clear</button>
                `;

                const statsBar = document.getElementById('stats-bar');
                if (statsBar) {
                    statsBar.parentNode.insertBefore(controls, statsBar.nextSibling);
                }

                inspectorBtn = document.getElementById('inspector-toggle');
                const clearBtn = document.getElementById('inspector-clear-highlight');

                if (inspectorBtn) {
                    inspectorBtn.addEventListener('click', toggleInspector);
                }
                if (clearBtn) {
                    clearBtn.addEventListener('click', () => {
                        if (currentHoverElement) {
                            currentHoverElement.classList.remove('dom-inspector-highlight');
                            currentHoverElement = null;
                        }
                        clearTooltip();
                    });
                }
            } else {
                setTimeout(addControls, 200);
            }
        };

        function toggleInspector() {
            inspectorActive = !inspectorActive;

            if (inspectorActive) {
                inspectorBtn.textContent = '🔍 DOM Inspector ON';
                inspectorBtn.style.background = '#0f0';
                inspectorBtn.style.color = '#000';
                document.body.style.cursor = 'crosshair';
                document.addEventListener('mouseover', onMouseOver);
                document.addEventListener('click', onClick, true);
            } else {
                inspectorBtn.textContent = '🔍 DOM Inspector OFF';
                inspectorBtn.style.background = '#333';
                inspectorBtn.style.color = '#0f0';
                document.body.style.cursor = '';
                document.removeEventListener('mouseover', onMouseOver);
                document.removeEventListener('click', onClick, true);
                if (currentHoverElement) {
                    currentHoverElement.classList.remove('dom-inspector-highlight');
                    currentHoverElement = null;
                }
                clearTooltip();
            }
        }

        function onMouseOver(e) {
            if (!inspectorActive) return;

            // Ignore inspector buttons
            if (e.target.closest('#inspector-toggle') || e.target.closest('#inspector-clear-highlight')) {
                return;
            }

            if (currentHoverElement) {
                currentHoverElement.classList.remove('dom-inspector-highlight');
            }

            currentHoverElement = e.target;
            currentHoverElement.classList.add('dom-inspector-highlight');
            showTooltipFixed(currentHoverElement, e.clientX, e.clientY);
        }

        function onClick(e) {
            if (!inspectorActive) return;
            if (e.target.closest('#inspector-toggle') || e.target.closest('#inspector-clear-highlight')) {
                return;
            }

            e.preventDefault();
            e.stopPropagation();

            const element = e.target;
            const action = prompt(
                'DOM Actions:\n' +
                '1 - Copy Selector\n' +
                '2 - Copy XPath\n' +
                '3 - Edit HTML\n' +
                '4 - Log to Console\n' +
                '5 - Hide Element'
            );

            switch(action) {
                case '1':
                    navigator.clipboard.writeText(getElementSelector(element));
                    showNotification('✅ Selector copied');
                    break;
                case '2':
                    navigator.clipboard.writeText(getElementXPath(element));
                    showNotification('✅ XPath copied');
                    break;
                case '3':
                    const newHTML = prompt('Edit HTML:', element.outerHTML);
                    if (newHTML && newHTML !== element.outerHTML) {
                        const temp = document.createElement('div');
                        temp.innerHTML = newHTML;
                        element.parentNode.replaceChild(temp.firstChild, element);
                        showNotification('✅ Updated');
                    }
                    break;
                case '4':
                    console.log('[DOM]', element);
                    showNotification('✅ Check console (F12)');
                    break;
                case '5':
                    element.style.display = 'none';
                    showNotification('✅ Hidden');
                    break;
            }

            clearTooltip();
            return false;
        }

        addControls();
    }

    // ==================== API INTERCEPTORS (Optimized) ====================

    function extractJWT(text) {
        if (!text || typeof text !== 'string') return null;
        const match = text.match(/eyJ[a-zA-Z0-9_-]+\.eyJ[a-zA-Z0-9_-]+\.[a-zA-Z0-9_-]+/);
        return match ? match[0] : null;
    }

    function addLogEntry(entry) {
        if (!CONFIG.enabled) return;

        // Remove duplicate consecutive requests (optional)
        const lastEntry = interceptedRequests[0];
        if (lastEntry && lastEntry.url === entry.url && lastEntry.method === entry.method && Date.now() - lastEntry.timestamp < 100) {
            return; // Skip duplicate within 100ms
        }

        entry.id = requestId++;
        entry.timestamp = Date.now();

        interceptedRequests.unshift(entry);

        // Cleanup old entries
        if (interceptedRequests.length > CONFIG.maxLogEntries) {
            const removed = interceptedRequests.pop();
            expandedItems.delete(removed.id);
        }

        // Queue UI update
        queueUpdate();

        // Queue save
        if (saveTimeout) clearTimeout(saveTimeout);
        saveTimeout = setTimeout(() => saveData(), CONFIG.autoSaveInterval);
    }

    function interceptFetch() {
        if (!window.fetch) return;
        originalFetch = window.fetch;

        window.fetch = async function(...args) {
            const [resource, config = {}] = args;
            const url = resource instanceof Request ? resource.url : resource.toString();
            const method = config.method || (resource instanceof Request ? resource.method : 'GET');
            const startTime = Date.now();

            // Extract JWT from headers
            let headers = config.headers || {};
            if (resource instanceof Request) {
                headers = Object.fromEntries(resource.headers.entries());
            }
            const authHeader = headers['authorization'] || headers['Authorization'];
            const jwt = authHeader ? extractJWT(authHeader) : null;

            addLogEntry({ type: 'fetch', method, url, jwt });

            try {
                const response = await originalFetch.apply(this, args);
                addLogEntry({
                    type: 'fetch', method, url,
                    status: response.status,
                    duration: Date.now() - startTime
                });
                return response;
            } catch(error) {
                addLogEntry({ type: 'fetch', method, url, error: error.message });
                throw error;
            }
        };
    }

    function interceptXHR() {
        if (!window.XMLHttpRequest) return;
        const XHR = window.XMLHttpRequest;
        const originalOpen = XHR.prototype.open;
        const originalSend = XHR.prototype.send;

        XHR.prototype.open = function(method, url) {
            this._data = { method, url, startTime: Date.now() };
            return originalOpen.apply(this, arguments);
        };

        XHR.prototype.send = function(body) {
            if (this._data) {
                // Extract JWT from headers if any
                let jwt = null;
                if (this._data.requestHeaders) {
                    const authHeader = this._data.requestHeaders['Authorization'] || this._data.requestHeaders['authorization'];
                    if (authHeader) jwt = extractJWT(authHeader);
                }
                addLogEntry({ type: 'xhr', ...this._data, jwt });
            }

            this.addEventListener('loadend', function() {
                if (this._data) {
                    addLogEntry({
                        type: 'xhr',
                        method: this._data.method,
                        url: this._data.url,
                        status: this.status,
                        duration: Date.now() - this._data.startTime
                    });
                }
            });
            return originalSend.apply(this, arguments);
        };
    }

    // ==================== UI CREATION ====================

    function createFloatingPanel() {
        GM_addStyle(`
            #api-interceptor-panel {
                position: fixed;
                bottom: 10px;
                right: 10px;
                width: 500px;
                max-height: 450px;
                background: #1e1e1e;
                color: #e0e0e0;
                border-radius: 6px;
                z-index: 999999;
                font-family: monospace;
                font-size: 11px;
                overflow: hidden;
                box-shadow: 0 2px 10px rgba(0,0,0,0.5);
                display: none;
                flex-direction: column;
                border: 1px solid #0f0;
            }

            #api-interceptor-panel.active { display: flex; }

            .panel-header {
                padding: 6px 10px;
                background: #2a2a2a;
                border-bottom: 1px solid #0f0;
                cursor: move;
                display: flex;
                justify-content: space-between;
                align-items: center;
            }

            .panel-header h3 {
                margin: 0;
                font-size: 11px;
                color: #0f0;
            }

            .panel-controls button {
                background: #333;
                color: #0f0;
                border: 1px solid #0f0;
                padding: 2px 5px;
                cursor: pointer;
                font-size: 9px;
                border-radius: 3px;
            }

            .panel-controls button:hover {
                background: #0f0;
                color: #000;
            }

            .panel-content {
                overflow-y: auto;
                padding: 8px;
                max-height: 400px;
            }

            .log-entry {
                background: #252525;
                border-left: 2px solid #0f0;
                margin-bottom: 4px;
                padding: 4px 6px;
                font-size: 10px;
            }

            .log-header {
                cursor: pointer;
                display: flex;
                gap: 6px;
                align-items: center;
                flex-wrap: wrap;
            }

            .log-method {
                color: #ff6b6b;
                font-weight: bold;
            }

            .log-url {
                color: #4ecdc4;
                overflow: hidden;
                text-overflow: ellipsis;
                white-space: nowrap;
                flex: 1;
            }

            .log-status {
                color: #ffe66d;
            }

            .badge-jwt {
                background: #ffd700;
                color: #000;
                padding: 0 3px;
                border-radius: 2px;
            }

            .log-details {
                display: none;
                margin-top: 5px;
                padding-top: 5px;
                border-top: 1px solid #444;
            }

            .log-details.expanded { display: block; }

            .detail-section {
                margin-bottom: 5px;
            }

            .detail-title {
                color: #0f0;
                font-size: 9px;
                margin-bottom: 2px;
            }

            .detail-content {
                background: #1a1a1a;
                padding: 4px;
                border-radius: 3px;
                font-size: 9px;
                overflow-x: auto;
                max-height: 80px;
            }

            .jwt-token {
                color: #ffd700;
                word-break: break-all;
            }

            .dom-inspector-highlight {
                outline: 2px solid #0f0 !important;
                background-color: rgba(0,255,0,0.1) !important;
                cursor: crosshair !important;
            }

            .dom-inspector-tooltip {
                position: fixed;
                background: #1e1e1e;
                color: #0f0;
                padding: 6px 10px;
                border-radius: 4px;
                font-size: 10px;
                z-index: 1000000;
                border: 1px solid #0f0;
                pointer-events: auto;
            }

            .dom-inspector-tooltip button {
                background: #333;
                color: #0f0;
                border: 1px solid #0f0;
                padding: 2px 6px;
                margin: 0 2px;
                cursor: pointer;
                font-size: 9px;
                border-radius: 3px;
            }

            .toggle-panel-btn {
                position: fixed;
                bottom: 10px;
                right: 10px;
                width: 32px;
                height: 32px;
                background: #0f0;
                color: #000;
                border: none;
                border-radius: 50%;
                cursor: pointer;
                z-index: 999998;
                font-size: 16px;
                display: flex;
                align-items: center;
                justify-content: center;
            }

            .search-box {
                width: 100%;
                padding: 4px 6px;
                margin-bottom: 8px;
                background: #2a2a2a;
                border: 1px solid #0f0;
                color: #0f0;
                border-radius: 3px;
                font-size: 10px;
            }

            .stats-bar {
                background: #2a2a2a;
                padding: 3px 6px;
                font-size: 9px;
                margin-bottom: 8px;
                border-radius: 3px;
                display: flex;
                justify-content: space-between;
            }

            @keyframes fadeOut {
                0% { opacity: 1; }
                70% { opacity: 1; }
                100% { opacity: 0; }
            }
        `);

        // Toggle button
        const toggleBtn = document.createElement('button');
        toggleBtn.className = 'toggle-panel-btn';
        toggleBtn.innerHTML = '🛠️';
        document.body.appendChild(toggleBtn);

        // Panel
        const panel = document.createElement('div');
        panel.id = 'api-interceptor-panel';
        panel.innerHTML = `
            <div class="panel-header">
                <h3>🛠️ DevTools Pro</h3>
                <div class="panel-controls">
                    <button id="clear-logs">Clear</button>
                    <button id="export-logs">Export</button>
                    <button id="close-panel">×</button>
                </div>
            </div>
            <div class="panel-content">
                <input type="text" class="search-box" placeholder="Filter...">
                <div class="stats-bar">
                    <span>📡 <span id="request-count">0</span></span>
                    <span>💾 Auto-save</span>
                </div>
                <div id="logs-container"></div>
            </div>
        `;
        document.body.appendChild(panel);

        // Draggable
        let dragData = { active: false };
        const header = panel.querySelector('.panel-header');

        header.addEventListener('mousedown', (e) => {
            if (e.target.tagName === 'BUTTON') return;
            dragData = {
                active: true,
                startX: e.clientX,
                startY: e.clientY,
                startLeft: panel.offsetLeft,
                startTop: panel.offsetTop
            };
            panel.style.left = dragData.startLeft + 'px';
            panel.style.top = dragData.startTop + 'px';
            panel.style.right = 'auto';
            panel.style.bottom = 'auto';
            e.preventDefault();
        });

        document.addEventListener('mousemove', (e) => {
            if (!dragData.active) return;
            panel.style.left = (dragData.startLeft + e.clientX - dragData.startX) + 'px';
            panel.style.top = (dragData.startTop + e.clientY - dragData.startY) + 'px';
        });

        document.addEventListener('mouseup', () => {
            dragData.active = false;
        });

        // Event handlers
        toggleBtn.onclick = () => panel.classList.toggle('active');
        document.getElementById('close-panel').onclick = () => panel.classList.remove('active');
        document.getElementById('clear-logs').onclick = () => {
            if (confirm('Clear all?')) {
                interceptedRequests = [];
                expandedItems.clear();
                requestId = 0;
                updateLogsDisplay();
                saveData();
            }
        };
        document.getElementById('export-logs').onclick = () => {
            const data = JSON.stringify(interceptedRequests, null, 2);
            const blob = new Blob([data], { type: 'application/json' });
            const url = URL.createObjectURL(blob);
            const a = document.createElement('a');
            a.href = url;
            a.download = `devtools-${Date.now()}.json`;
            a.click();
            URL.revokeObjectURL(url);
        };

        document.querySelector('.search-box').addEventListener('input', () => updateLogsDisplay());

        return panel;
    }

    // ==================== INIT ====================

    function init() {
        console.log('[DevTools Pro] v4.1 - Optimized');

        // Load saved data
        loadData();

        // Setup interceptors
        interceptFetch();
        interceptXHR();

        // Create UI
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                createFloatingPanel();
                initDOMInspectorFixed();
                updateLogsDisplay();

                // Periodic cleanup
                setInterval(() => {
                    cleanupOldData();
                    if (CONFIG.persistData) saveData();
                }, CONFIG.cleanupInterval);
            });
        } else {
            createFloatingPanel();
            initDOMInspectorFixed();
            updateLogsDisplay();
            setInterval(() => {
                cleanupOldData();
                if (CONFIG.persistData) saveData();
            }, CONFIG.cleanupInterval);
        }

        // Save on unload
        window.addEventListener('beforeunload', () => saveData());
    }

    init();
})();