TriX Executor (BETA) for Territorial.io

BEST EXPLOIT IN GAME1! REMINDER: it is still in a very early stage. report any bugs or issues to the author and please dont judge too harshly at this stage. Join TriX Discord server to receive more updates: https://discord.gg/Zp7cqqZzXX have fun cheating :D

当前为 2025-09-11 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         TriX Executor (BETA) for Territorial.io
// @namespace    https://greasyfork.org/en/users/your-username
// @version      Beta-Cygnus-2024.03.05
// @description  BEST EXPLOIT IN GAME1! REMINDER: it is still in a very early stage. report any bugs or issues to the author and please dont judge too harshly at this stage. Join TriX Discord server to receive more updates: https://discord.gg/Zp7cqqZzXX have fun cheating :D
// @author       Painsel
// @match        *://territorial.io/*
// @require      https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-javascript.min.js
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        unsafeWindow
// @run-at       document-start
// @license      MIT
// @icon         data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzAwYWFmZiI+PHBhdGggZD0iTTE4LjM2IDIyLjA1bC00LjQyLTQuNDJDMTMuNDcgMTcuODcgMTMgMTguMTcgMTMgMTguNUMxMyAyMC45OSAxNS4wMSAyMyAxNy41IDIzaDMuNTRjLTIuNDUtMS40OC00LjQyLTMuNDUtNS45LTUuOTV6TTggMTNjMS42NiAwIDMuMTgtLjU5IDQuMzgtMS42MkwxMCAxMy41VjIybDIuNS0yLjVMMTggMTMuOTZjLjM1LS40OC42NS0xIC44Ny0xLjU1QzE4LjYxIDEzLjQxIDE4IDEyLjM0IDE4IDEyVjBoLTJ2MTJjMCAuMzQtLjAyLjY3LS4wNiAxLS4zMy4xOC0uNjguMy0xLjA0LjM3LTEuNzMgMC0zLjI3LS45My00LjE2LTIuMzZMNiAxMy42VjVINXY4eiIvPjwvc3ZnPg==
// ==/UserScript==

/* global Prism, unsafeWindow */

(function() {
    'use strict';

    let updateConnectionStatus = () => {}; // Will be assigned by the UI

    // --- WebSocket Manager ---
    const webSocketManager = {
        activeSocket: null,
        send: function(data) {
            if (this.activeSocket && this.activeSocket.readyState === WebSocket.OPEN) {
                this.activeSocket.send(data);
                return true;
            }
            console.warn('[TriX] Could not send packet: No active WebSocket connection.');
            return false;
        }
    };

    // --- WebSocket Proxy Setup ---
    const OriginalWebSocket = unsafeWindow.WebSocket;
    let logPacketCallback = () => {};

    unsafeWindow.WebSocket = function(url, protocols) {
        const isGameSocket = url.includes('/s52/');
        if (!isGameSocket) {
            return new OriginalWebSocket(url, protocols);
        }

        console.log(`[TriX] Intercepting WebSocket connection to: ${url}`);
        updateConnectionStatus('connecting', 'Attempting to connect...');
        const ws = new OriginalWebSocket(url, protocols);

        webSocketManager.activeSocket = ws;
        console.log('[TriX] Active game socket registered.');

        ws.addEventListener('open', () => {
            updateConnectionStatus('connected', 'Connection established.');
        });
        ws.addEventListener('close', (event) => {
            if (webSocketManager.activeSocket === ws) {
                webSocketManager.activeSocket = null;
                console.log('[TriX] Active game socket unregistered (connection closed).');
            }
            updateConnectionStatus('disconnected', `Disconnected. Code: ${event.code}`);
        });
        ws.addEventListener('error', () => {
            updateConnectionStatus('error', 'A connection error occurred.');
        });


        const originalSend = ws.send.bind(ws);
        ws.send = function(data) {
            logPacketCallback('send', data);
            return originalSend(data);
        };

        ws.addEventListener('message', (event) => {
            logPacketCallback('receive', event.data);
        });

        return ws;
    };

    // --- Main script logic ---
    function initialize() {
        const shadowHost = document.createElement('div');
        shadowHost.id = 'trix-host';
        document.body.appendChild(shadowHost);
        const shadowRoot = shadowHost.attachShadow({ mode: 'open' });

        const styleElement = document.createElement('style');
        styleElement.textContent = `
            /* PrismJS Theme */
            code[class*="language-"],pre[class*="language-"]{color:#ccc;background:0 0;font-family:Consolas,Monaco,'Andale Mono','Ubuntu Mono',monospace;font-size:1em;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;line-height:1.5;tab-size:4;}pre[class*="language-"]{padding:1em;margin:.5em 0;overflow:auto}:not(pre)>code[class*="language-"],pre[class*="language-"]{background:#2d2d2d}:not(pre)>code[class*="language-"]{padding:.1em;border-radius:.3em;white-space:normal}.token.comment{color:#999}.token.punctuation{color:#ccc}.token.tag{color:#e2777a}.token.function-name{color:#6196cc}.token.boolean,.token.number,.token.function{color:#f08d49}.token.property,.token.class-name,.token.constant,.token.symbol{color:#f8c555}.token.selector,.token.important,.token.atrule,.token.keyword,.token.builtin{color:#cc99cd}.token.string,.token.char,.token.attr-value,.token.regex,.token.variable{color:#7ec699}.token.operator,.token.entity,.token.url{color:#67cdcc}

            /* --- TriX Executor UI --- */
            @keyframes trix-fade-in { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
            @keyframes trix-slide-in { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }

            #trix-toggle-btn { position: fixed; top: 15px; right: 15px; z-index: 99999; width: 50px; height: 50px; background-color: rgba(30, 30, 34, 0.8); color: #00aaff; border: 2px solid #00aaff; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 24px; transition: all 0.3s ease; backdrop-filter: blur(5px); font-family: monospace; box-shadow: 0 0 10px rgba(0, 170, 255, 0.5); }
            #trix-toggle-btn:hover { transform: scale(1.1) rotate(15deg); box-shadow: 0 0 15px #00aaff; }

            #trix-container { position: fixed; top: 80px; right: 15px; width: 450px; min-height: 400px; z-index: 99998; color: #fff; border-radius: 10px; overflow: hidden; box-shadow: 0 5px 25px rgba(0,0,0,0.5); display: flex; flex-direction: column; backdrop-filter: blur(10px); animation: trix-slide-in 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; resize: both; }
            #trix-container.hidden { display: none; }

            #trix-container[data-theme='dark-knight'] { background-color: rgba(30, 30, 34, 0.9); border: 1px solid #444; }
            #trix-container[data-theme='arctic-light'] { background-color: rgba(240, 240, 255, 0.9); border: 1px solid #ccc; color: #111; }
            #trix-container[data-theme='crimson'] { background-color: rgba(43, 8, 8, 0.9); border: 1px solid #8B0000; color: #f1f1f1; }
            #trix-container[data-theme='arctic-light'] .trix-tab, #trix-container[data-theme='arctic-light'] .trix-status-bar, #trix-container[data-theme='arctic-light'] #trix-packet-log { background: #e0e0e8; }
            #trix-container[data-theme='arctic-light'] .trix-tab.active { background: #f0f0ff; color: #0055aa; }
            #trix-container[data-theme='arctic-light'] .trix-editor-area { background: #f0f0ff; border-color: #aaa; }
            #trix-container[data-theme='crimson'] .trix-tab.active { color: #ff8b8b; }

            #trix-header, #trix-footer { cursor: move; user-select: none; }
            #trix-header { padding: 10px 15px; font-weight: bold; font-size: 16px; display: flex; justify-content: space-between; align-items: center; }
            #trix-container[data-theme='dark-knight'] #trix-header { background-color: rgba(0,0,0,0.3); }
            #trix-container[data-theme='arctic-light'] #trix-header { background-color: rgba(0,0,0,0.1); }
            #trix-container[data-theme='crimson'] #trix-header { background-color: rgba(139, 0, 0, 0.5); }
            .trix-title-3d { color: #E0E4E8; font-family: 'Segoe UI', 'Roboto', sans-serif; font-weight: 900; font-size: 18px; letter-spacing: 1px; text-shadow: 0 0 2px rgba(255, 255, 255, 0.6), 0 0 10px #00aaff, 1px 1px 0 #0f121a, 2px 2px 0 #0f121a, 3px 3px 0 #0f121a, 4px 4px 5px rgba(0, 0, 0, 0.5); }
            .trix-version-tag { font-size: 10px; font-weight: 300; opacity: 0.7; margin-left: 8px; vertical-align: middle; }
            #trix-header-controls { display: flex; align-items: center; gap: 15px; }
            #trix-fps-display { font-size: 12px; font-weight: normal; opacity: 0.7; }
            #trix-close-btn { cursor: pointer; font-size: 20px; font-weight: bold; padding: 0 5px; }
            #trix-close-btn:hover { color: #ff5555; }

            #trix-conn-status { width: 12px; height: 12px; border-radius: 50%; transition: background-color 0.5s ease; }
            #trix-conn-status.disconnected { background-color: #dc3545; box-shadow: 0 0 5px #dc3545; }
            #trix-conn-status.connecting { background-color: #ffc107; box-shadow: 0 0 5px #ffc107; animation: trix-pulse 1.5s infinite; }
            #trix-conn-status.connected { background-color: #28a745; box-shadow: 0 0 5px #28a745; }
            @keyframes trix-pulse { 0% { opacity: 1; } 50% { opacity: 0.4; } 100% { opacity: 1; } }

            #trix-content { padding: 0 15px 15px 15px; flex-grow: 1; display: flex; flex-direction: column; }
            .trix-tabs { display: flex; flex-wrap: wrap; border-bottom: 1px solid #555; }
            .trix-tab { background: #2a2a30; padding: 8px 12px; cursor: pointer; border-radius: 5px 5px 0 0; margin-right: 4px; position: relative; transition: background 0.2s; }
            .trix-tab:hover { background: #3a3a42; }
            .trix-tab.active { background: #1e1e22; font-weight: bold; color: #00aaff; }
            .trix-tab-name { padding-right: 15px; -webkit-user-select: none; user-select: none; }
            .trix-tab-close { position: absolute; top: 50%; right: 5px; transform: translateY(-50%); font-size: 14px; opacity: 0.6; }
            .trix-tab-close:hover { opacity: 1; color: #ff5555; }
            #trix-new-tab-btn { background: none; border: none; color: #00aaff; font-size: 20px; cursor: pointer; padding: 5px 10px; }
            
            /* NEW: Tab Renaming Input */
            .trix-tab-rename-input { background: transparent; border: 1px solid #00aaff; color: inherit; font-family: inherit; font-size: inherit; padding: 0; margin: 0; width: 100px; }

            .trix-view { display: flex; flex-direction: column; flex-grow: 1; margin-top: 10px; }
            .trix-editor-area { position: relative; flex-grow: 1; margin-top: -1px; background: #2d2d2d; border: 1px solid #555; border-radius: 0 0 5px 5px; }
            .trix-editor-area textarea, .trix-editor-area pre { margin: 0; padding: 10px; font-family: 'Fira Code', 'Consolas', monospace; font-size: 14px; line-height: 1.5; white-space: pre; word-wrap: normal; width: 100%; height: 100%; box-sizing: border-box; position: absolute; top: 0; left: 0; overflow: auto; }
            .trix-editor-area textarea { z-index: 1; background: transparent; color: inherit; resize: none; border: none; outline: none; -webkit-text-fill-color: transparent; }
            .trix-editor-area pre { z-index: 0; pointer-events: none; }

            .trix-action-bar { display: flex; gap: 10px; margin-top: 10px; }
            .trix-button { background-color: #007bff; color: white; border: none; padding: 8px 12px; cursor: pointer; border-radius: 5px; transition: background-color 0.2s; flex-grow: 1; }
            #trix-execute-btn { background-color: #28a745; }
            .trix-status-bar { margin-top: 10px; padding: 5px; background: rgba(0,0,0,0.2); font-size: 12px; border-radius: 3px; min-height: 1em; }
            .trix-status-success { color: #28a745; }
            .trix-status-error { color: #dc3545; }

            #trix-packet-log-view { flex-grow: 1; display: flex; flex-direction: column; }
            #trix-packet-log { flex-grow: 1; background: #1e1e22; border: 1px solid #555; border-radius: 5px; padding: 10px; overflow-y: auto; font-family: monospace; font-size: 12px; white-space: pre-wrap; word-break: break-all; }
            .packet-item { padding: 2px 0; border-bottom: 1px solid #333; }
            .packet-send { color: #7ec699; }
            .packet-receive { color: #cc99cd; }
            .packet-meta { opacity: 0.6; font-size: 10px; margin-right: 10px; }
            #trix-packet-input { flex-grow: 1; background: #2d2d2d; border: 1px solid #555; color: #ccc; padding: 8px; border-radius: 5px; font-family: monospace; }
            #trix-send-packet-btn, #trix-clear-log-btn { flex-grow: 0 !important; width: 80px; }

            #trix-settings-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.6); z-index: 100000; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(5px); }
            .trix-settings-content { background: #1e1e22; padding: 20px; border-radius: 8px; width: 300px; box-shadow: 0 0 20px rgba(0,0,0,0.5); animation: trix-fade-in 0.3s; }
        `;
        shadowRoot.appendChild(styleElement);

        let state = {
            tabs: [],
            activeTabId: null,
            settings: { theme: 'dark-knight', position: { top: '80px', left: 'auto', right: '15px' }, size: { width: '450px', height: '400px' } }
        };

        const $ = (selector, parent = shadowRoot) => parent.querySelector(selector);
        const debounce = (func, delay) => { let t; return (...a) => { clearTimeout(t); t = setTimeout(() => func.apply(this, a), delay); }; };

        function loadState() {
            const savedState = GM_getValue('trixExecutorState');
            if (savedState) state = { ...state, ...savedState };
            const defaultTabs = [
                { id: 'logger', name: 'Packet Logger', code: null, isPermanent: true },
                { id: Date.now(), name: "Welcome", code: "// Welcome to TriX Executor!\nconsole.log('Hello from TriX!');" }
            ];
            if (!state.tabs || state.tabs.length === 0) state.tabs = defaultTabs;
            if (!state.tabs.find(t => t.id === 'logger')) state.tabs.unshift(defaultTabs[0]);
            if (!state.activeTabId || !state.tabs.find(t => t.id === state.activeTabId)) {
                state.activeTabId = state.tabs[0].id;
            }
        }

        const saveState = debounce(() => {
            const container = $('#trix-container');
            if (container) {
                state.settings.position = { top: container.style.top, left: container.style.left, right: container.style.right, bottom: 'auto' };
                state.settings.size = { width: container.style.width, height: container.style.height };
            }
            GM_setValue('trixExecutorState', state);
        }, 500);

        let ui, editor;

        function createUI() {
            const toggleBtn = document.createElement('div');
            toggleBtn.id = 'trix-toggle-btn'; toggleBtn.title = 'Toggle TriX Executor (BETA)'; toggleBtn.innerHTML = 'X';
            const container = document.createElement('div');
            container.id = 'trix-container'; container.classList.add('hidden');
            container.innerHTML = `
                <div id="trix-header">
                    <div><span class="trix-title-3d">TriX Executor</span><span class="trix-version-tag">(v${GM_info.script.version})</span></div>
                    <div id="trix-header-controls">
                        <span id="trix-conn-status" class="disconnected" title="No connection"></span>
                        <span id="trix-fps-display"></span>
                        <span id="trix-close-btn" title="Close">✖</span>
                    </div>
                </div>
                <div id="trix-content"></div>
                <div id="trix-footer" style="padding:10px; text-align:center; background:rgba(0,0,0,0.2);">
                     <button id="trix-settings-btn" class="trix-button" style="flex-grow:0; padding: 5px 15px;">Settings</button>
                </div>`;
            shadowRoot.append(toggleBtn, container);
            return { toggleBtn, container };
        }

        updateConnectionStatus = function(status, title) {
            const statusIndicator = $('#trix-conn-status');
            if (statusIndicator) {
                statusIndicator.className = ''; // Clear existing classes
                statusIndicator.classList.add(status);
                statusIndicator.title = title;
            }
        };

        function renderTabs() {
            const tabsContainer = $('.trix-tabs');
            if (!tabsContainer) return;
            tabsContainer.innerHTML = '';
            state.tabs.forEach(tab => {
                const tabEl = document.createElement('div');
                tabEl.className = 'trix-tab'; tabEl.dataset.tabId = tab.id;
                if (tab.id === state.activeTabId) tabEl.classList.add('active');
                tabEl.innerHTML = `<span class="trix-tab-name">${tab.name}</span>` + (!tab.isPermanent ? `<span class="trix-tab-close">x</span>` : '');
                tabsContainer.appendChild(tabEl);
            });
            const newTabBtn = document.createElement('button');
            newTabBtn.id = 'trix-new-tab-btn'; newTabBtn.textContent = '+';
            tabsContainer.appendChild(newTabBtn);
            renderEditor();
        }

        function renderActiveView() {
            const content = $('#trix-content');
            const activeTab = state.tabs.find(t => t.id === state.activeTabId);
            content.innerHTML = '';

            if (activeTab.id === 'logger') {
                content.innerHTML = `
                    <div id="trix-packet-log-view" class="trix-view">
                        <div class="trix-tabs"></div>
                        <div id="trix-packet-log"></div>
                        <div class="trix-action-bar">
                             <input type="text" id="trix-packet-input" placeholder="Enter packet data to send...">
                             <button id="trix-send-packet-btn" class="trix-button">Send</button>
                             <button id="trix-clear-log-btn" class="trix-button">Clear</button>
                        </div>
                        <div class="trix-status-bar">Ready.</div>
                    </div>`;
                $('#trix-clear-log-btn').addEventListener('click', () => $('#trix-packet-log').innerHTML = '');
                $('#trix-send-packet-btn').addEventListener('click', sendManualPacket);
                $('#trix-packet-input').addEventListener('keydown', (e) => {
                    if (e.key === 'Enter') sendManualPacket();
                });
                renderTabs();
            } else {
                content.innerHTML = `
                    <div id="trix-injector-container" class="trix-view">
                        <div class="trix-tabs"></div>
                        <div class="trix-editor-area"></div>
                        <div class="trix-action-bar">
                            <button id="trix-execute-btn" class="trix-button">Execute</button>
                            <button id="trix-clear-btn" class="trix-button">Clear</button>
                        </div>
                        <div class="trix-status-bar">Ready.</div>
                    </div>`;
                renderTabs();
            }
        }

        function sendManualPacket() {
            const input = $('#trix-packet-input');
            const statusBar = $('.trix-status-bar');
            if (!input || !statusBar) return;

            const data = input.value;
            if (!data) return;

            const success = webSocketManager.send(data);
            if (success) {
                statusBar.textContent = `Packet sent successfully at ${new Date().toLocaleTimeString()}`;
                statusBar.className = 'trix-status-bar trix-status-success';
                input.value = '';
            } else {
                statusBar.textContent = 'Error: No active WebSocket connection.';
                statusBar.className = 'trix-status-bar trix-status-error';
            }
        }

        function renderEditor() {
            const editorArea = $('.trix-editor-area');
            if (!editorArea) { editor = null; return; }
            const activeTab = state.tabs.find(t => t.id === state.activeTabId);
            if (!activeTab) { editorArea.innerHTML = ''; editor = null; return; }

            editorArea.innerHTML = `
                <textarea spellcheck="false" autocapitalize="off" autocomplete="off" autocorrect="off"></textarea>
                <pre class="language-js"><code></code></pre>`;
            editor = { textarea: $('textarea', editorArea), pre: $('pre', editorArea), code: $('code', editorArea) };
            editor.textarea.value = activeTab.code;
            highlightCode(activeTab.code);
            addEditorEventListeners();
        }

        function highlightCode(code) { if (editor) editor.code.innerHTML = Prism.highlight(code + '\n', Prism.languages.javascript, 'javascript'); }

        function addEditorEventListeners() {
            if (!editor) return;
            editor.textarea.addEventListener('input', () => {
                const activeTab = state.tabs.find(t => t.id === state.activeTabId);
                if (activeTab) { activeTab.code = editor.textarea.value; highlightCode(activeTab.code); saveState(); }
            });
            editor.textarea.addEventListener('scroll', () => { if (editor) { editor.pre.scrollTop = editor.textarea.scrollTop; editor.pre.scrollLeft = editor.textarea.scrollLeft; }});
            editor.textarea.addEventListener('keydown', e => {
                if (e.key === 'Tab') {
                    e.preventDefault(); const s = e.target.selectionStart, end = e.target.selectionEnd;
                    e.target.value = e.target.value.substring(0, s) + '  ' + e.target.value.substring(end);
                    e.target.selectionStart = e.target.selectionEnd = s + 2;
                    editor.textarea.dispatchEvent(new Event('input'));
                }
            });
        }

        function initEventListeners() {
            ui.toggleBtn.addEventListener('click', () => ui.container.classList.toggle('hidden'));
            $('#trix-close-btn').addEventListener('click', () => ui.container.classList.add('hidden'));
            initDraggable(ui.container, [$('#trix-header'), $('#trix-footer')]);
            ui.container.addEventListener('click', handleContainerClick);
            ui.container.addEventListener('dblclick', handleContainerDblClick); // NEW: Double-click listener
            $('#trix-settings-btn').addEventListener('click', showSettingsModal);
            const resizeObserver = new ResizeObserver(debounce(saveState, 500));
            resizeObserver.observe(ui.container);
        }

        function handleContainerClick(e) {
            const target = e.target;
            const tabEl = target.closest('.trix-tab');
            if (tabEl && !target.classList.contains('trix-tab-close')) {
                const tabId = tabEl.dataset.tabId === 'logger' ? 'logger' : parseInt(tabEl.dataset.tabId, 10);
                if (tabId !== state.activeTabId) { state.activeTabId = tabId; renderActiveView(); saveState(); }
            }
            if (target.classList.contains('trix-tab-close')) {
                const tabId = parseInt(tabEl.dataset.tabId, 10);
                state.tabs = state.tabs.filter(t => t.id !== tabId);
                if (state.activeTabId === tabId) state.activeTabId = state.tabs[0].id;
                renderActiveView(); saveState();
            }
            if (target.id === 'trix-new-tab-btn') {
                const newId = Date.now(), newName = `Script ${state.tabs.length}`;
                state.tabs.push({ id: newId, name: newName, code: `// ${newName}` });
                state.activeTabId = newId;
                renderActiveView(); saveState();
            }
            if (target.id === 'trix-execute-btn') executeScript();
            if (target.id === 'trix-clear-btn') {
                 const activeTab = state.tabs.find(t => t.id === state.activeTabId);
                 if (activeTab) { activeTab.code = ''; renderEditor(); saveState(); }
            }
        }
        
        // NEW: Handler for double-clicking to rename tabs
        function handleContainerDblClick(e) {
            const nameEl = e.target.closest('.trix-tab-name');
            if (!nameEl) return;
            
            const tabEl = nameEl.closest('.trix-tab');
            const tabId = parseInt(tabEl.dataset.tabId, 10);
            const tab = state.tabs.find(t => t.id === tabId);

            if (!tab || tab.isPermanent) return; // Don't rename permanent tabs

            const input = document.createElement('input');
            input.type = 'text';
            input.className = 'trix-tab-rename-input';
            input.value = tab.name;

            nameEl.replaceWith(input);
            input.focus();
            input.select();

            const finishEditing = () => {
                const newName = input.value.trim();
                if (newName) {
                    tab.name = newName;
                    saveState();
                }
                renderTabs(); // Re-render to show the final name and remove input
            };
            
            input.addEventListener('blur', finishEditing, { once: true });
            input.addEventListener('keydown', e => {
                if (e.key === 'Enter') {
                    finishEditing();
                } else if (e.key === 'Escape') {
                    renderTabs(); // Cancel by just re-rendering
                }
            });
        }

        function initDraggable(container, handles) {
            let isDragging = false, offsetX, offsetY;
            handles.forEach(handle => {
                handle.addEventListener('mousedown', e => {
                    if (e.target.closest('#trix-header-controls') || e.target.closest('.trix-title-3d') || e.target.closest('#trix-settings-btn')) return;
                    isDragging = true;
                    offsetX = e.clientX - container.offsetLeft;
                    offsetY = e.clientY - container.offsetTop;
                    container.style.right = 'auto';
                    container.style.bottom = 'auto';
                    document.body.style.userSelect = 'none';
                });
            });

            document.addEventListener('mousemove', e => {
                if (isDragging) {
                    let newTop = e.clientY - offsetY;
                    container.style.left = `${e.clientX - offsetX}px`;
                    container.style.top = `${Math.max(0, newTop)}px`;
                }
            });
            document.addEventListener('mouseup', () => {
                if (isDragging) {
                    isDragging = false;
                    document.body.style.userSelect = '';
                    saveState();
                }
            });
        }

        function executeScript() {
            const statusBar = $('.trix-status-bar');
            if (!statusBar) return;
            const activeTab = state.tabs.find(t => t.id === state.activeTabId);
            if (!activeTab || !activeTab.code) { statusBar.textContent = 'Nothing to execute.'; statusBar.className = 'trix-status-bar'; return; }
            try {
                new Function(activeTab.code)();
                statusBar.textContent = `Success: Executed '${activeTab.name}' at ${new Date().toLocaleTimeString()}`;
                statusBar.className = 'trix-status-bar trix-status-success';
            } catch (error) {
                console.error('TriX Executor Error:', error);
                statusBar.textContent = `Error: ${error.message}`;
                statusBar.className = 'trix-status-bar trix-status-error';
            }
        }

        function showSettingsModal() {
            const modal = document.createElement('div');
            modal.id = 'trix-settings-modal';
            modal.innerHTML = `
                <div class="trix-settings-content">
                    <h3>Settings</h3> <label for="trix-theme-select">Theme:</label>
                    <select id="trix-theme-select">
                        <option value="dark-knight">Dark Knight</option> <option value="arctic-light">Arctic Light</option> <option value="crimson">Crimson</option>
                    </select>
                    <button id="trix-settings-close">Close</button>
                </div>`;
            shadowRoot.appendChild(modal);
            $('#trix-theme-select').value = state.settings.theme;
            $('#trix-theme-select').addEventListener('change', e => { state.settings.theme = e.target.value; applySettings(); saveState(); });
            $('#trix-settings-close').addEventListener('click', () => modal.remove());
            modal.addEventListener('click', e => { if (e.target.id === 'trix-settings-modal') modal.remove(); });
        }

        function applySettings() {
            const container = $('#trix-container');
            container.dataset.theme = state.settings.theme;
            Object.assign(container.style, state.settings.position, state.settings.size);
        }

        logPacketCallback = function(type, data) {
            const logContainer = $('#trix-packet-log');
            if (!logContainer) return;
            const item = document.createElement('div');
            item.className = `packet-item packet-${type}`;
            let formattedData = data;
            if (data instanceof ArrayBuffer) formattedData = `[ArrayBuffer, ${data.byteLength} bytes]`;
            else if (data instanceof Blob) formattedData = `[Blob, ${data.size} bytes, type: ${data.type}]`;
            item.innerHTML = `<span class="packet-meta">[${type.toUpperCase()}]</span> ${formattedData}`;
            logContainer.appendChild(item);
            logContainer.scrollTop = logContainer.scrollHeight;
        };

        loadState();
        ui = createUI();
        applySettings();
        renderActiveView();
        initEventListeners();

        let lastFrameTime = performance.now(), frameCount = 0;
        const fpsDisplay = $('#trix-fps-display');
        function updateFPS(now) {
            frameCount++;
            if (now >= lastFrameTime + 1000) {
                if (fpsDisplay) fpsDisplay.textContent = `FPS: ${frameCount}`;
                lastFrameTime = now;
                frameCount = 0;
            }
            requestAnimationFrame(updateFPS);
        }
        requestAnimationFrame(updateFPS);
    }

    function waitForElement(selector, callback) {
        const maxRetries = 40;
        const retryInterval = 250;
        let retryCount = 0;

        const checkInterval = setInterval(() => {
            const element = document.querySelector(selector);
            if (element) {
                console.log(`[TriX] Target element '${selector}' found. Initializing script.`);
                clearInterval(checkInterval);
                callback();
            } else {
                retryCount++;
                console.log(`[TriX] Target element not found. Retry ${retryCount}/${maxRetries}...`);
                if (retryCount >= maxRetries) {
                    clearInterval(checkInterval);
                    console.error(`[TriX] Failed to find target element '${selector}' after ${maxRetries} retries. Aborting initialization.`);
                }
            }
        }, retryInterval);
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => waitForElement('#canvasA', initialize));
    } else {
        waitForElement('#canvasA', initialize);
    }

})();