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 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 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);
    }

})();