Mini DevTools v3 - Secret Inspector

DevTools ẩn với Sources Browser + Pause Debug

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Mini DevTools v3 - Secret Inspector
// @namespace    http://tampermonkey.net/
// @version      3.0
// @description  DevTools ẩn với Sources Browser + Pause Debug
// @author       You
// @match        *://*/*
// @grant        none
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // ========== CONFIG ==========
    const TOGGLE_KEY = '`';

    // ========== STATE ==========
    let isPaused = false;
    let pauseInterval = null;
    let currentTab = 'console';

    // ========== TẠO GIAO DIỆN ==========
    const panel = document.createElement('div');
    panel.id = 'secret-devtools-panel';
    panel.innerHTML = `
        <div id="sdt-header">
            <span>🔧 Mini DevTools</span>
            <div style="display:flex;align-items:center;gap:4px;">
                <button id="sdt-btn-network">🌐 Network</button>
                <button id="sdt-btn-console" class="active">📜 Console</button>
                <button id="sdt-btn-sources">📁 Sources</button>
                <button id="sdt-btn-storage">💾 Storage</button>
                <button id="sdt-btn-elements">📋 DOM</button>
                <button id="sdt-btn-iframe">🖼️ Iframe</button>
                <span style="color:#555;margin:0 8px;">|</span>
                <button id="sdt-btn-pause" style="background:#8b0000;color:#fff;">⏯️ Pause</button>
                <button id="sdt-btn-close">❌</button>
            </div>
        </div>
        <div id="sdt-body">
            <div id="sdt-main-area" style="display:flex;flex:1;overflow:hidden;">
                <div id="sdt-file-list" style="display:none;width:250px;background:#252526;overflow-y:auto;border-right:1px solid #3e3e3e;padding:5px;"></div>
                <div id="sdt-content-area" style="flex:1;display:flex;flex-direction:column;overflow:hidden;">
                    <div id="sdt-log" style="flex:1;overflow-y:auto;padding:10px;background:#1e1e1e;font-size:11px;line-height:1.5;white-space:pre-wrap;word-break:break-all;"></div>
                    <div id="sdt-source-viewer" style="display:none;flex:1;overflow:auto;background:#1e1e1e;position:relative;">
                        <div id="sdt-source-header" style="background:#333;padding:4px 10px;color:#aaa;font-size:11px;border-bottom:1px solid #555;"></div>
                        <pre id="sdt-source-code" style="margin:0;padding:10px;color:#d4d4d4;font-size:11px;line-height:1.6;counter-reset:line;white-space:pre;tab-size:2;"></pre>
                    </div>
                    <textarea id="sdt-console-input" placeholder="Gõ lệnh JS rồi Enter (Shift+Enter xuống dòng)..."></textarea>
                </div>
            </div>
        </div>
    `;

    // ========== STYLE ==========
    const style = document.createElement('style');
    style.textContent = `
        #secret-devtools-panel {
            position:fixed;bottom:0;left:0;right:0;height:400px;background:#1e1e1e;
            color:#d4d4d4;font-family:'Consolas','Courier New',monospace;font-size:12px;
            z-index:999999;display:none;flex-direction:column;border-top:3px solid #007acc;
            resize:vertical;overflow:hidden;min-height:150px;
        }
        #secret-devtools-panel.show-flex { display:flex !important; }
        #sdt-header {
            display:flex;justify-content:space-between;align-items:center;
            padding:6px 10px;background:#252526;border-bottom:1px solid #3e3e3e;
            cursor:move;user-select:none;flex-shrink:0;
        }
        #sdt-header span { font-weight:bold;color:#007acc;font-size:13px; }
        #sdt-header button {
            background:#3e3e3e;color:#ccc;border:1px solid #555;
            padding:4px 10px;cursor:pointer;border-radius:3px;font-size:11px;
            white-space:nowrap;
        }
        #sdt-header button:hover { background:#555; }
        #sdt-header button.active { background:#007acc;color:white;border-color:#007acc; }
        #sdt-body { flex:1;display:flex;flex-direction:column;overflow:hidden;min-height:0; }
        #sdt-main-area { flex:1;overflow:hidden;min-height:0; }
        #sdt-console-input {
            width:100%;background:#2d2d2d;color:#4fc1ff;border:none;
            border-top:1px solid #3e3e3e;padding:8px 10px;
            font-family:'Consolas',monospace;font-size:12px;outline:none;
            box-sizing:border-box;flex-shrink:0;
        }
        #sdt-log .log-info { color:#4fc1ff; }
        #sdt-log .log-warn { color:#ffcc00; }
        #sdt-log .log-error { color:#ff6b6b; }
        #sdt-log .log-success { color:#4caf50; }
        #sdt-log .log-network { color:#ce9178; }
        #sdt-log .log-trace { color:#888; }
        #sdt-file-list .file-item {
            padding:4px 8px;cursor:pointer;font-size:11px;
            border-radius:2px;word-break:break-all;
        }
        #sdt-file-list .file-item:hover { background:#3e3e3e; }
        #sdt-file-list .file-item.selected { background:#007acc;color:white; }
        #sdt-file-list .file-folder {
            padding:4px 8px;cursor:pointer;font-size:11px;
            color:#e8ab53;font-weight:bold;
        }
        #sdt-source-code .line-number {
            display:inline-block;width:40px;color:#555;text-align:right;
            margin-right:12px;user-select:none;
        }
    `;

    // ========== APPEND TO DOM ==========
    function appendUI() {
        if (!document.head || !document.body) return false;
        if (document.getElementById('secret-devtools-panel')) return true;
        document.head.appendChild(style);
        document.body.appendChild(panel);
        return true;
    }
    if (!appendUI()) {
        document.addEventListener('DOMContentLoaded', appendUI);
        if (document.readyState !== 'loading') appendUI();
    }

    // ========== DOM REFS CACHE ==========
    function qs(id) { return document.getElementById(id); }

    function getLogEl() { return qs('sdt-log'); }
    function getInputEl() { return qs('sdt-console-input'); }
    function getFileListEl() { return qs('sdt-file-list'); }
    function getSourceViewerEl() { return qs('sdt-source-viewer'); }
    function getSourceCodeEl() { return qs('sdt-source-code'); }
    function getSourceHeaderEl() { return qs('sdt-source-header'); }
    function getPanelEl() { return qs('secret-devtools-panel'); }
    function getPauseBtn() { return qs('sdt-btn-pause'); }
    function getMainAreaEl() { return qs('sdt-main-area'); }
    function getContentAreaEl() { return qs('sdt-content-area'); }

    // ========== LOG FUNCTION ==========
    function dtLog(msg, type) {
        type = type || 'info';
        var logDiv = getLogEl();
        if (!logDiv) return;
        var line = document.createElement('div');
        line.className = 'log-' + type;
        var time = new Date().toLocaleTimeString();
        line.textContent = '[' + time + '] ' + msg;
        logDiv.appendChild(line);
        logDiv.scrollTop = logDiv.scrollHeight;
    }

    // ========== OVERRIDE CONSOLE ==========
    var origConsoleLog = console.log.bind(console);
    var origConsoleWarn = console.warn.bind(console);
    var origConsoleError = console.error.bind(console);

    console.log = function() {
        origConsoleLog.apply(console, arguments);
        var args = Array.prototype.slice.call(arguments);
        dtLog(args.map(function(a) {
            return typeof a === 'object' ? JSON.stringify(a, null, 2) : String(a);
        }).join(' '), 'info');
    };
    console.warn = function() {
        origConsoleWarn.apply(console, arguments);
        dtLog(Array.prototype.slice.call(arguments).join(' '), 'warn');
    };
    console.error = function() {
        origConsoleError.apply(console, arguments);
        dtLog(Array.prototype.slice.call(arguments).join(' '), 'error');
    };

    // ========== NETWORK INTERCEPT ==========
    (function() {
        var origFetch = window.fetch;
        window.fetch = function() {
            var url = typeof arguments[0] === 'string' ? arguments[0] : arguments[0].url;
            var startTime = performance.now();
            dtLog('📡 FETCH → ' + url, 'network');
            return origFetch.apply(this, arguments).then(function(response) {
                var duration = (performance.now() - startTime).toFixed(1);
                dtLog('📡 FETCH ← ' + response.status + ' (' + duration + 'ms) ' + url, 'network');
                return response;
            });
        };

        var origXHROpen = XMLHttpRequest.prototype.open;
        var origXHRSend = XMLHttpRequest.prototype.send;
        XMLHttpRequest.prototype.open = function(method, url) {
            this._sdt_url = url;
            this._sdt_method = method;
            return origXHROpen.apply(this, arguments);
        };
        XMLHttpRequest.prototype.send = function() {
            var self = this;
            dtLog('📡 XHR ' + self._sdt_method + ' → ' + self._sdt_url, 'network');
            self.addEventListener('load', function() {
                dtLog('📡 XHR ← ' + self.status + ' ' + self._sdt_url, 'network');
            });
            return origXHRSend.apply(self, arguments);
        };

        var OrigWS = window.WebSocket;
        window.WebSocket = function(url) {
            var ws = new OrigWS(url);
            dtLog('🔌 WS CONNECT → ' + url, 'network');
            var origSend = ws.send;
            ws.send = function(data) {
                var preview = typeof data === 'string' ? data.substring(0, 200) : '[Binary]';
                dtLog('🔌 WS SEND → ' + preview, 'network');
                return origSend.apply(ws, arguments);
            };
            ws.addEventListener('message', function(e) {
                var preview = typeof e.data === 'string' ? e.data.substring(0, 200) : '[Binary]';
                dtLog('🔌 WS RECV ← ' + preview, 'network');
            });
            return ws;
        };
        window.WebSocket.prototype = OrigWS.prototype;
    })();

    // ========== IFRAME UTILS ==========
    function getGameIframe() {
        var iframes = document.querySelectorAll('iframe');
        for (var i = 0; i < iframes.length; i++) {
            try {
                if (iframes[i].contentWindow) return iframes[i];
            } catch(e) {}
        }
        return iframes[0] || null;
    }

    function getGameWindow() {
        var iframe = getGameIframe();
        if (iframe) {
            try {
                return iframe.contentWindow;
            } catch(e) {
                dtLog('⚠️ Không truy cập được iframe (cross-origin)', 'warn');
                return null;
            }
        }
        return window;
    }

    // ========== EXECUTE COMMAND ==========
    function executeCommand(cmd) {
        var gameWindow = getGameWindow();
        var ctx = gameWindow || window;
        try {
            var result = (function() { return eval(cmd); }).call(ctx);
            if (result !== undefined) {
                dtLog(String(result), 'success');
            }
        } catch(e) {
            dtLog('❌ ' + e.message, 'error');
        }
    }

    // ========== SOURCES TAB ==========
    var sourceFiles = {};

    function collectSourceFiles() {
        sourceFiles = {};
        var gameWindow = getGameWindow();
        var ctx = gameWindow || window;
        var doc = gameWindow ? (getGameIframe() ? getGameIframe().contentDocument : document) : document;

        // Thu thập script tags
        if (doc) {
            var scripts = doc.querySelectorAll('script[src]');
            scripts.forEach(function(s, i) {
                sourceFiles['scripts/' + (s.src.split('/').pop() || ('script_' + i + '.js'))] = {
                    url: s.src,
                    type: 'script'
                };
            });
            // Inline scripts
            var inlineScripts = doc.querySelectorAll('script:not([src])');
            inlineScripts.forEach(function(s, i) {
                if (s.textContent && s.textContent.trim().length > 0) {
                    sourceFiles['inline/inline_' + i + '.js'] = {
                        content: s.textContent,
                        type: 'inline'
                    };
                }
            });
        }

        // Performance entries (đã load)
        if (window.performance && window.performance.getEntriesByType) {
            var entries = window.performance.getEntriesByType('resource');
            entries.forEach(function(e) {
                if (e.initiatorType === 'script' || e.initiatorType === 'link' || e.initiatorType === 'xmlhttprequest') {
                    var name = e.name.split('/').pop() || e.name;
                    if (!sourceFiles[e.name]) {
                        sourceFiles[e.name] = { url: e.name, type: e.initiatorType };
                    }
                }
            });
        }
    }

    function fetchSourceContent(url, callback) {
        try {
            fetch(url).then(function(r) {
                return r.text();
            }).then(function(text) {
                callback(null, text);
            }).catch(function(err) {
                callback(err.message);
            });
        } catch(e) {
            callback(e.message);
        }
    }

    function showSourceContent(key, fileObj) {
        var viewer = getSourceViewerEl();
        var codeEl = getSourceCodeEl();
        var headerEl = getSourceHeaderEl();
        var logEl = getLogEl();
        var fileListEl = getFileListEl();

        if (!viewer || !codeEl) return;

        // Highlight selected
        if (fileListEl) {
            var items = fileListEl.querySelectorAll('.file-item');
            items.forEach(function(it) { it.classList.remove('selected'); });
            var targetItem = fileListEl.querySelector('[data-key="' + CSS.escape(key) + '"]');
            if (targetItem) targetItem.classList.add('selected');
        }

        // Show viewer, hide log
        viewer.style.display = 'block';
        if (logEl) logEl.style.display = 'none';

        if (headerEl) headerEl.textContent = '📄 ' + key;

        if (fileObj.content) {
            codeEl.innerHTML = renderSourceWithLineNumbers(fileObj.content);
        } else if (fileObj.url) {
            codeEl.textContent = 'Đang tải...';
            fetchSourceContent(fileObj.url, function(err, content) {
                if (err) {
                    codeEl.textContent = '// Lỗi tải file: ' + err;
                } else {
                    codeEl.innerHTML = renderSourceWithLineNumbers(content);
                    fileObj.content = content;
                }
            });
        }
    }

    function renderSourceWithLineNumbers(code) {
        var lines = code.split('\n');
        var html = '';
        for (var i = 0; i < lines.length; i++) {
            var lineNum = i + 1;
            html += '<span class="line-number">' + lineNum + '</span>' +
                    escapeHTML(lines[i]) + '\n';
        }
        return html;
    }

    function escapeHTML(str) {
        var div = document.createElement('div');
        div.textContent = str;
        return div.innerHTML;
    }

    function renderFileList() {
        var fileListEl = getFileListEl();
        if (!fileListEl) return;
        fileListEl.innerHTML = '';

        collectSourceFiles();

        var keys = Object.keys(sourceFiles).sort();
        if (keys.length === 0) {
            fileListEl.innerHTML = '<div style="padding:10px;color:#888;">Không tìm thấy file</div>';
            return;
        }

        // Nhóm theo thư mục
        var folders = {};
        keys.forEach(function(k) {
            var parts = k.split('/');
            var folder = parts.length > 1 ? parts[0] : '(root)';
            if (!folders[folder]) folders[folder] = [];
            folders[folder].push(k);
        });

        Object.keys(folders).sort().forEach(function(folder) {
            var folderDiv = document.createElement('div');
            folderDiv.className = 'file-folder';
            folderDiv.textContent = '📁 ' + folder;
            fileListEl.appendChild(folderDiv);

            folders[folder].forEach(function(key) {
                var item = document.createElement('div');
                item.className = 'file-item';
                item.setAttribute('data-key', key);
                var fileName = key.split('/').pop();
                item.textContent = '  📄 ' + fileName;
                item.addEventListener('click', function() {
                    showSourceContent(key, sourceFiles[key]);
                });
                fileListEl.appendChild(item);
            });
        });

        dtLog('📁 Đã thu thập ' + keys.length + ' file', 'success');
    }

    // ========== PAUSE/RESUME ==========
    function pauseExecution() {
        if (isPaused) return;
        isPaused = true;
        var btn = getPauseBtn();
        if (btn) {
            btn.textContent = '▶️ Resume';
            btn.style.background = '#006400';
        }
        dtLog('⏸️ ĐÃ PAUSE - Trang đang bị đóng băng', 'warn');

        // Sync loop giữ trang đóng băng
        pauseInterval = setInterval(function() {
            // Không làm gì, chỉ giữ CPU bận và ngăn event loop chạy tiếp
            var start = Date.now();
            while (Date.now() - start < 50) {
                // Busy wait 50ms
            }
        }, 0);

        // Chặn tất cả timer mới
        var origSetTimeout = window.setTimeout;
        var origSetInterval = window.setInterval;
        var origRequestAnimationFrame = window.requestAnimationFrame;

        window._sdt_origSetTimeout = origSetTimeout;
        window._sdt_origSetInterval = origSetInterval;
        window._sdt_origRAF = origRequestAnimationFrame;

        window.setTimeout = function() { return 0; };
        window.setInterval = function() { return 0; };
        window.requestAnimationFrame = function() { return 0; };
    }

    function resumeExecution() {
        if (!isPaused) return;
        isPaused = false;
        var btn = getPauseBtn();
        if (btn) {
            btn.textContent = '⏯️ Pause';
            btn.style.background = '#8b0000';
        }
        dtLog('▶️ ĐÃ RESUME - Trang tiếp tục chạy', 'success');

        if (pauseInterval) {
            clearInterval(pauseInterval);
            pauseInterval = null;
        }

        if (window._sdt_origSetTimeout) {
            window.setTimeout = window._sdt_origSetTimeout;
            window.setInterval = window._sdt_origSetInterval;
            window.requestAnimationFrame = window._sdt_origRAF;
        }
    }

    // ========== TAB SWITCHING ==========
    function switchTab(tab) {
        currentTab = tab;
        var logEl = getLogEl();
        var inputEl = getInputEl();
        var fileListEl = getFileListEl();
        var sourceViewerEl = getSourceViewerEl();

        // Reset view
        if (logEl) logEl.style.display = 'block';
        if (inputEl) inputEl.style.display = 'block';
        if (fileListEl) fileListEl.style.display = 'none';
        if (sourceViewerEl) sourceViewerEl.style.display = 'none';

        // Update buttons
        var buttons = ['network', 'console', 'sources', 'storage', 'elements', 'iframe'];
        buttons.forEach(function(b) {
            var btn = qs('sdt-btn-' + b);
            if (btn) btn.classList.remove('active');
        });
        var activeBtn = qs('sdt-btn-' + tab);
        if (activeBtn) activeBtn.classList.add('active');

        // Tab-specific logic
        switch(tab) {
            case 'network':
                if (logEl) logEl.innerHTML = '';
                dtLog('🌐 Đang theo dõi request...', 'info');
                break;
            case 'console':
                break;
            case 'sources':
                if (fileListEl) fileListEl.style.display = 'block';
                if (sourceViewerEl) sourceViewerEl.style.display = 'block';
                if (logEl) logEl.style.display = 'none';
                renderFileList();
                break;
            case 'storage':
                if (logEl) logEl.innerHTML = '';
                showStorage();
                break;
            case 'elements':
                if (logEl) logEl.innerHTML = '';
                showGlobalVars();
                break;
            case 'iframe':
                if (logEl) logEl.innerHTML = '';
                showIframeInfo();
                break;
        }
    }

    function showStorage() {
        dtLog('💾 === LOCAL STORAGE ===', 'info');
        var gameWin = getGameWindow();
        var ls = gameWin ? gameWin.localStorage : localStorage;
        for (var i = 0; i < ls.length; i++) {
            var key = ls.key(i);
            var val = ls.getItem(key);
            dtLog(key + ': ' + val.substring(0, 200) + (val.length > 200 ? '...' : ''), 'success');
        }
        dtLog('💾 === SESSION STORAGE ===', 'info');
        var ss = gameWin ? gameWin.sessionStorage : sessionStorage;
        for (var j = 0; j < ss.length; j++) {
            var skey = ss.key(j);
            var sval = ss.getItem(skey);
            dtLog(skey + ': ' + sval.substring(0, 200) + (sval.length > 200 ? '...' : ''), 'success');
        }
    }

    function showGlobalVars() {
        dtLog('📋 === GLOBAL VARIABLES (window/iframeWindow) ===', 'info');
        var gameWin = getGameWindow();
        var ctx = gameWin || window;
        var keys = Object.keys(ctx).filter(function(k) {
            return !k.startsWith('webkit') &&
                   !k.startsWith('on') &&
                   typeof ctx[k] !== 'function' &&
                   k !== 'undefined' &&
                   k !== 'NaN' &&
                   k !== 'Infinity';
        });
        dtLog('Tìm thấy ' + keys.length + ' biến global', 'info');
        keys.slice(0, 50).forEach(function(k) {
            var val = ctx[k];
            var type = typeof val;
            if (type === 'object' && val !== null) {
                var preview = Array.isArray(val) ? 'Array[' + val.length + ']' : 'Object{' + Object.keys(val).length + ' keys}';
                dtLog('📦 ' + k + ': ' + preview, 'success');
            } else {
                dtLog('📌 ' + k + ': ' + String(val).substring(0, 100), 'success');
            }
        });
        if (keys.length > 50) dtLog('...(còn ' + (keys.length - 50) + ' biến nữa, giới hạn hiển thị)', 'warn');
    }

    function showIframeInfo() {
        var iframe = getGameIframe();
        if (iframe) {
            dtLog('🖼️ Tìm thấy iframe: ' + (iframe.src || '(no src)'), 'success');
            try {
                var doc = iframe.contentDocument;
                if (doc) {
                    dtLog('   ✅ Same-origin - có thể truy cập nội dung', 'success');
                    dtLog('   Canvas: ' + doc.querySelectorAll('canvas').length, 'info');
                    dtLog('   Scripts: ' + doc.querySelectorAll('script').length, 'info');
                }
            } catch(e) {
                dtLog('   ⚠️ Cross-origin - không truy cập được', 'warn');
            }
        } else {
            dtLog('⚠️ Không tìm thấy iframe', 'warn');
        }
    }

    // ========== INIT EVENTS ==========
    function initEvents() {
        var closeBtn = qs('sdt-btn-close');
        var inputEl = getInputEl();
        var pauseBtn = getPauseBtn();

        // Close
        if (closeBtn) {
            closeBtn.addEventListener('click', function() {
                var p = getPanelEl();
                if (p) p.classList.remove('show-flex');
            });
        }

        // Console input
        if (inputEl) {
            inputEl.addEventListener('keydown', function(e) {
                if (e.key === 'Enter' && !e.shiftKey) {
                    e.preventDefault();
                    var cmd = this.value.trim();
                    if (cmd) {
                        dtLog('> ' + cmd, 'info');
                        executeCommand(cmd);
                        this.value = '';
                    }
                }
            });
        }

        // Pause/Resume
        if (pauseBtn) {
            pauseBtn.addEventListener('click', function() {
                if (isPaused) {
                    resumeExecution();
                } else {
                    pauseExecution();
                }
            });
        }

        // Tab buttons
        var tabs = ['network', 'console', 'sources', 'storage', 'elements', 'iframe'];
        tabs.forEach(function(tab) {
            var btn = qs('sdt-btn-' + tab);
            if (btn) {
                btn.addEventListener('click', function() {
                    switchTab(tab);
                });
            }
        });
    }

    // ========== KEYBOARD TOGGLE ==========
    document.addEventListener('keydown', function(e) {
        if (e.key === TOGGLE_KEY && !e.ctrlKey && !e.altKey && !e.metaKey) {
            if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.isContentEditable) {
                return;
            }
            e.preventDefault();
            e.stopPropagation();
            var p = getPanelEl();
            if (p) {
                if (p.classList.contains('show-flex')) {
                    p.classList.remove('show-flex');
                    if (isPaused) resumeExecution();
                } else {
                    p.classList.add('show-flex');
                }
            }
        }
    }, true);

    // ========== STARTUP ==========
    function startup() {
        if (!appendUI()) {
            setTimeout(startup, 50);
            return;
        }
        initEvents();
        dtLog('🔧 Mini DevTools v3 đã sẵn sàng', 'success');
        dtLog('📁 Tab Sources: xem & duyệt code game', 'info');
        dtLog('⏯️ Nút Pause: đóng băng game để debug', 'info');
        dtLog('💡 Bấm ` để bật/tắt', 'info');
    }

    setTimeout(startup, 100);

})();