TriX Executor (BETA) for Territorial.io

A powerful, multi-tabbed, persistent code execution and network logging environment for developers. Now with configurable themes, Anti-Lag mode, a music player, and more.

目前為 2025-09-17 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 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/COURTESYCOIL
// @version      Beta-Phoenix-2024.10.05a
// @description  A powerful, multi-tabbed, persistent code execution and network logging environment for developers. Now with configurable themes, Anti-Lag mode, a music player, and more.
// @author       Painsel
// @include      *
// @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+PHBhdGggZD0iTTE4LjM2IDIyLjA1bC00LjQyLTQuNDJDMTMuNDcgMTcuODcgMTMgMTguMTcgMTMgMTguNUMxMyAyMC45OSAxNS4wMSAyMyAxNy41IDIzaDMuNTRjLTIuNDUtMS40OC00LjQyLTMuNDUtNS5LTUuOTV6TTggMTNjMS42NiAwIDMuMTgtLjU5IDQuMzgtMS42MkwxMCAxMy41VjIybDIuNS0yLjVMMTggMTMuOTZjLjM1LS40OC42NS0xIC44Ny0xLjU1QzE4LjYxIDEzLjQxIDE4IDEyLjM0IDE4IDEyVjBoLTJ2MTJjMCAuMzQtLjAyLjY3LS4wNiAxLS4zMy4xOC0uNjguMy0xLjA0LjM3LTEuNzMgMC0zLjI3LS45My00LjE2LTIuMzZMNiAxMy42VjVINXY4eiIvPjwvc3ZnPg==
// ==/UserScript==
 
/* global Prism, unsafeWindow */
 
(function() {
    'use strict';
 
    // --- Conditional Execution Check ---
    const isTerritorialDomain = window.location.hostname.includes('territorial.io');
    const urlParams = new URLSearchParams(window.location.search);
    let isProxy = false;
    if (urlParams.has('__cpo')) {
        try {
            if (atob(urlParams.get('__cpo')).includes('territorial.io')) {
                isProxy = true;
            }
        } catch (e) { /* Ignore invalid base64 */ }
    }
    if (!isTerritorialDomain && !isProxy) {
        return;
    }
 
    // --- Global State & Settings ---
    let settings = {
        theme: 'vscode', // vscode, retro, crimson
        gameLogToasts: true
    };
    let interceptionEnabled = false;
    let isLoggerSuspended = false;
    let queuedMessages = new Map();
    let renderInterceptorQueue = () => {};
    let customWs = null;
 
    const monitoredConnections = new Map();
    let viewingConnection = null;
 
    let updateConnectionStatus = () => {};
    let updatePingDisplay = () => {};
    let logPacketCallback = () => {};
    let showToastCallback = null;
    let connectionUrlQueue = [];
    let onConnectionStateChange = () => {};
    
    const savedSettings = GM_getValue('trixSettings', {});
    settings = { ...settings, ...savedSettings };
 
    // --- WebSocket Proxy Setup ---
    const OriginalWebSocket = unsafeWindow.WebSocket;
    unsafeWindow.WebSocket = function(url, protocols) {
        let isGameSocket = false;
        // Check 1: Direct connection
        if (url.includes('/s52/')) {
            isGameSocket = true;
        } else {
            // Check 2: Proxy connection (e.g., __cpw.php?u=BASE64_URL)
            try {
                const parsedUrl = new URL(url);
                const proxyParam = parsedUrl.searchParams.get('u');
                if (proxyParam) {
                    const decodedUrl = atob(proxyParam);
                    if (decodedUrl.includes('/s52/')) {
                        isGameSocket = true;
                    }
                }
            } catch (e) { /* Not a valid URL or proxy format, ignore */ }
        }

        if (!isGameSocket) {
            return new OriginalWebSocket(url, protocols);
        }

        console.log(`[TriX] Intercepting WebSocket connection to: ${url}`);

        if (showToastCallback) {
            showToastCallback(url);
        } else {
            connectionUrlQueue.push(url);
        }

        const ws = new OriginalWebSocket(url, protocols);
        monitoredConnections.set(ws, { url, state: 'CONNECTING', log: [] });
        onConnectionStateChange();
        
        let originalOnMessageHandler = null;
        const originalSend = ws.send.bind(ws);

        ws.send = function(data) {
            logPacketCallback(ws, 'send', data);
            if (interceptionEnabled) {
                const messageId = `send-${Date.now()}-${Math.random()}`;
                const promise = new Promise(resolve => queuedMessages.set(messageId, { direction: 'send', data, resolve }));
                renderInterceptorQueue();
                promise.then(decision => {
                    queuedMessages.delete(messageId);
                    renderInterceptorQueue();
                    if (decision.action === 'forward') {
                        originalSend(decision.data);
                        logPacketCallback(ws, 'send-forwarded', `[Forwarded] ${decision.data}`);
                    } else { 
                        logPacketCallback(ws, 'send-blocked', `[Blocked] ${data}`); 
                    }
                });
            } else { 
                return originalSend(data); 
            }
        };
        Object.defineProperty(ws, 'onmessage', {
            get: () => originalOnMessageHandler,
            set: (handler) => {
                originalOnMessageHandler = handler;
                ws.addEventListener('message', event => {
                    logPacketCallback(ws, 'receive', event.data);
                    if (interceptionEnabled) {
                        const messageId = `recv-${Date.now()}-${Math.random()}`;
                        const promise = new Promise(resolve => queuedMessages.set(messageId, { direction: 'receive', data: event.data, resolve }));
                        renderInterceptorQueue();
                        promise.then(decision => {
                            queuedMessages.delete(messageId);
                            renderInterceptorQueue();
                            if (decision.action === 'forward') {
                                logPacketCallback(ws, 'receive-forwarded', `[Forwarded] ${decision.data}`);
                                if (originalOnMessageHandler) originalOnMessageHandler({ data: decision.data });
                            } else { 
                                logPacketCallback(ws, 'receive-blocked', `[Blocked] ${event.data}`);
                            }
                        });
                    } else { 
                        if (originalOnMessageHandler) originalOnMessageHandler(event); 
                    }
                });
            },
            configurable: true
        });
        ws.addEventListener('open', () => {
            const conn = monitoredConnections.get(ws);
            if(conn) conn.state = 'OPEN';
            onConnectionStateChange();
            updateConnectionStatus('connected', 'Connection established.');
        });
        ws.addEventListener('close', (event) => {
            monitoredConnections.delete(ws);
            onConnectionStateChange();
            if (unsafeWindow.webSocketManager && unsafeWindow.webSocketManager.activeSocket === ws) {
                unsafeWindow.webSocketManager.activeSocket = null;
                updateConnectionStatus('disconnected', `Disconnected. Code: ${event.code}`);
                updatePingDisplay('---');
            }
        });
        ws.addEventListener('error', () => {
            monitoredConnections.delete(ws);
            onConnectionStateChange();
            if (unsafeWindow.webSocketManager && unsafeWindow.webSocketManager.activeSocket === ws) {
                 updateConnectionStatus('error', 'A connection error occurred.');
            }
        });
        unsafeWindow.webSocketManager = { activeSocket: ws };
        return ws;
    };
 
 
    // --- Main script logic ---
    function initialize() {
        const shadowHost = document.createElement('div');
        shadowHost.id = 'trix-host';
        shadowHost.dataset.theme = settings.theme;
        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,.attr-value,.token.regex,.token.variable{color:#7ec699}.token.operator,.token.entity,.token.url{color:#67cdcc}
            
            /* --- THEMES --- */
            :host { /* VSCode Default Theme */
                --accent-color: #3b82f6; --accent-color-hover: #4c8df6;
                --bg-darker: #1e1e22; --bg-dark: #2a2a30; --bg-medium: #3a3a42;
                --border-color: #444; --text-color: #e5e7eb; --text-muted: #9ca3af;
                --button-shadow: none; --container-shadow: 0 5px 25px rgba(0,0,0,0.5);
            }
            :host([data-theme="retro"]) {
                --accent-color: #00ff9b; --accent-color-hover: #3affb2;
                --bg-darker: #121212; --bg-dark: #1d1d1d; --bg-medium: #282828;
                --border-color: #00ff9b; --text-color: #00ff9b; --text-muted: #00b36d;
                --button-shadow: 2px 2px 0px #00b36d; --container-shadow: 4px 4px 0px #00b36d, 8px 8px 15px rgba(0,0,0,0.5);
                font-family: 'Courier New', monospace !important;
            }
            :host([data-theme="crimson"]) {
                --accent-color: #e11d48; --accent-color-hover: #f43f5e;
                --bg-darker: #1f0a0e; --bg-dark: #2c0e14; --bg-medium: #4c1d27;
                --border-color: #881337; --text-color: #fce7f3; --text-muted: #fda4af;
                --button-shadow: 0 0 8px rgba(225, 29, 72, 0.5); --container-shadow: 0 5px 25px rgba(0,0,0,0.5);
            }
 
            @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: var(--accent-color); border: 2px solid var(--accent-color); 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 var(--accent-color); }
            #trix-toggle-btn:hover { transform: scale(1.1) rotate(15deg); box-shadow: 0 0 15px var(--accent-color); }
 
            #trix-container { position: fixed; top: 80px; right: 15px; width: 500px; min-height: 450px; z-index: 99998; color: var(--text-color); font-family: 'Segoe UI', 'Roboto', sans-serif; border-radius: 10px; overflow: hidden; box-shadow: var(--container-shadow); 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; background-color: rgba(30, 30, 34, 0.9); border: 1px solid var(--border-color); }
            #trix-container.hidden { display: none; }
            #trix-header, #trix-footer { cursor: move; user-select: none; flex-shrink: 0; }
            #trix-header { padding: 10px 15px; display: flex; justify-content: space-between; align-items: center; background-color: rgba(0,0,0,0.3); }
            .trix-title { font-size: 16px; font-weight: 600; }
            .trix-version-tag { font-size: 10px; font-weight: 300; opacity: 0.7; margin-left: 8px; }
            #trix-header-controls { display: flex; align-items: center; gap: 15px; font-size: 12px; }
            #trix-close-btn { cursor: pointer; font-size: 20px; font-weight: bold; opacity: 0.7; }
            #trix-close-btn:hover { opacity: 1; color: #ef4444; }
 
            #trix-conn-status { width: 10px; height: 10px; border-radius: 50%; }
            #trix-conn-status.connected { background-color: #28a745; } #trix-conn-status.disconnected { background-color: #dc3545; } #trix-conn-status.connecting { background-color: #ffc107; }
            .ping-good { color: #28a745; } .ping-ok { color: #ffc107; } .ping-bad { color: #dc3545; }
 
            #trix-content { padding: 0 15px 15px 15px; flex-grow: 1; display: flex; flex-direction: column; overflow: hidden; background-color: var(--bg-darker); }
            .trix-tabs { display: flex; flex-wrap: wrap; flex-shrink: 0; }
            .trix-tab { background: var(--bg-dark); color: var(--text-muted); padding: 8px 12px; cursor: pointer; border-top: 2px solid transparent; border-left: 1px solid transparent; border-right: 1px solid transparent; border-radius: 5px 5px 0 0; margin-right: 2px; position: relative; }
            .trix-tab.active { background: var(--bg-darker); font-weight: 600; color: var(--text-color); border-top-color: var(--accent-color); }
            .trix-tab-name { padding-right: 15px; } .trix-tab-close { position: absolute; top: 50%; right: 5px; transform: translateY(-50%); opacity: 0.6; } .trix-tab-close:hover { opacity: 1; color: #ef4444; }
            #trix-new-tab-btn { background: none; border: none; color: var(--text-muted); font-size: 20px; cursor: pointer; padding: 5px 10px; }
            .trix-tab-rename-input { background: transparent; border: 1px solid var(--accent-color); color: inherit; font-family: inherit; font-size: inherit; padding: 0; margin: 0; width: 100px; }
 
            .trix-view { display: flex; flex-direction: column; flex-grow: 1; padding-top: 10px; overflow: hidden; }
            .trix-action-bar { display: flex; gap: 10px; margin-top: 10px; flex-shrink: 0; }
            .trix-button { background-color: var(--accent-color); color: white; border: none; padding: 10px 15px; cursor: pointer; border-radius: 6px; flex-grow: 1; transition: all 0.2s; font-weight: 600; box-shadow: var(--button-shadow); }
            .trix-button:hover { filter: brightness(1.1); background-color: var(--accent-color-hover); }
            .trix-button:disabled { background-color: #555; cursor: not-allowed; filter: none; box-shadow: none; }
            #trix-suspend-log-btn.suspended { background-color: #ffc107; color: #1e1e22; }
            #trix-suspend-log-btn.suspended:hover { background-color: #ffc107; filter: brightness(1.1); }
            .trix-input { background: var(--bg-dark); border: 1px solid var(--border-color); color: var(--text-color); padding: 8px; border-radius: 5px; font-family: monospace; box-sizing: border-box; }
            .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-welcome-view h2 { margin-top: 0; color: #fff; }
            #trix-welcome-view p { color: var(--text-color); line-height: 1.6; }
            #trix-welcome-view code { background: var(--bg-dark); padding: 2px 5px; border-radius: 4px; font-family: Consolas, monospace; }
 
            #trix-ws-client-view { display: grid; grid-template-rows: auto auto 1fr auto; gap: 10px; height: 100%; }
            .trix-ws-client-grid { display: grid; grid-template-columns: 1fr auto; gap: 10px; align-items: center; }
            .trix-ws-client-controls { display: flex; gap: 10px; align-items: center; }
            .trix-ws-client-controls label { display: flex; align-items: center; gap: 5px; font-size: 12px; cursor: pointer; }
            #trix-ws-url { width: 100%; }
            #trix-ws-connect-btn.connected { background-color: #dc3545; }
            #trix-ws-console { flex-grow: 1; background: var(--bg-darker); border: 1px solid var(--border-color); border-radius: 5px; padding: 10px; overflow-y: auto; font-family: monospace; font-size: 12px; white-space: pre-wrap; word-break: break-all; }
            .ws-console-send { color: #7ec699; } .ws-console-receive { color: #cc99cd; } .ws-console-system { color: #f08d49; }
            #trix-ws-message { width: 100%; min-height: 80px; resize: vertical; }
            .trix-ws-message-controls, .trix-injector-message-controls { display: flex; flex-direction: column; gap: 5px; }
 
            #trix-injector-view { display: flex; flex-direction: column; flex-grow: 1; }
            .trix-injector-grid { display: grid; grid-template-columns: 1fr auto; gap: 10px; }
            #trix-injector-message { flex-grow: 1; resize: vertical; min-height: 100px; }
            
            #trix-interceptor-view { flex-grow: 1; display: flex; flex-direction: column; }
            #trix-interceptor-controls { display: flex; gap: 10px; margin-bottom: 10px; }
            #trix-toggle-interception-btn.active { background-color: #dc3545; box-shadow: 0 0 8px #dc3545; }
            #trix-interceptor-queue { flex-grow: 1; background: var(--bg-darker); border: 1px solid var(--border-color); border-radius: 5px; padding: 10px; overflow-y: auto; font-family: monospace; font-size: 12px; }
            .interceptor-item { border: 1px solid var(--border-color); border-radius: 4px; padding: 8px; margin-bottom: 8px; background: var(--bg-dark); }
            .interceptor-item-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 5px; font-weight: bold; }
            .interceptor-item-header .send { color: #7ec699; } .interceptor-item-header .receive { color: #cc99cd; }
            .interceptor-item textarea { width: 100%; box-sizing: border-box; background: var(--bg-dark); color: var(--text-color); border: 1px solid var(--border-color); min-height: 40px; font-family: inherit; }
            .interceptor-item-actions { display: flex; gap: 5px; margin-top: 5px; }
            .interceptor-btn { flex-grow: 1; padding: 4px; font-size: 11px; }
            .interceptor-forward-btn { background-color: #28a745; }
            .interceptor-block-btn { background-color: #dc3545; }
 
            #trix-packet-log-view { flex-grow: 1; display: flex; flex-direction: column; }
            #trix-packet-log-content { flex-grow: 1; overflow-y: auto; }
            #trix-packet-log { flex-grow: 1; background: var(--bg-darker); border: 1px solid var(--border-color); border-radius: 5px; padding: 10px; font-family: monospace; font-size: 12px; }
            .packet-item { display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #333; padding: 4px 2px; }
            .packet-data { flex-grow: 1; word-break: break-all; }
            .packet-actions { display: flex; gap: 5px; margin-left: 10px; }
            .packet-action-btn { background: var(--bg-medium); border: 1px solid var(--border-color); color: var(--text-color); cursor: pointer; font-size: 11px; padding: 2px 6px; border-radius: 3px; }
            .packet-action-btn:hover { background: var(--accent-color); color: white; border-color: var(--accent-color); }
            .packet-inject .packet-meta, .packet-send-forwarded .packet-meta, .packet-receive-forwarded .packet-meta { color: #f08d49; }
            .packet-send-blocked .packet-meta, .packet-receive-blocked .packet-meta { color: #dc3545; }
            
            .connection-card { background: var(--bg-dark); border-left: 4px solid; border-radius: 6px; padding: 12px; margin-bottom: 10px; cursor: pointer; transition: all 0.2s ease-in-out; }
            .connection-card:hover { background-color: var(--bg-medium); transform: scale(1.02); }
            .card-header { display: flex; justify-content: space-between; align-items: center; font-weight: bold; }
            .card-state { display: flex; align-items: center; gap: 6px; font-size: 12px; }
            .card-state-dot { width: 10px; height: 10px; border-radius: 50%; }
            .card-state.OPEN { color: #28a745; } .card-state.OPEN .card-state-dot { background-color: #28a745; }
            .card-state.CONNECTING { color: #ffc107; } .card-state.CONNECTING .card-state-dot { background-color: #ffc107; }
            .card-url { font-family: monospace; font-size: 12px; color: var(--text-muted); margin-top: 5px; word-break: break-all; }
            
            .trix-editor-area { position: relative; flex-grow: 1; background: #2d2d2d; border: 1px solid var(--border-color); border-radius: 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; 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-storage-view { flex-grow: 1; overflow-y: auto; font-family: monospace; font-size: 13px; }
            .storage-table { margin-top: 10px; } .storage-header { font-weight: bold; color: var(--accent-color); border-bottom: 1px solid var(--border-color); padding-bottom: 5px; margin-bottom: 5px; }
            .storage-row { display: flex; align-items: center; border-bottom: 1px solid #333; padding: 4px 2px; }
            .storage-key { color: #cc99cd; min-width: 100px; max-width: 100px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
            .storage-value { color: #7ec699; flex-grow: 1; cursor: pointer; word-break: break-all; }
            .storage-value-input { background: var(--bg-medium); color: var(--text-color); border: 1px solid var(--accent-color); width: 100%; font-family: inherit; font-size: inherit; }
            .storage-delete { cursor: pointer; color: #ef4444; opacity: 0.5; margin-left: 10px; font-weight: bold; }
            .storage-delete:hover { opacity: 1; }
            
            #trix-music-toggle-btn { position: fixed; bottom: 20px; left: 20px; z-index: 99999; width: 50px; height: 50px; background-color: rgba(30, 30, 34, 0.8); color: var(--text-color); border: 1px solid var(--border-color); border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(5px); transition: all 0.3s ease; }
            #trix-music-toggle-btn:hover { transform: scale(1.1); box-shadow: 0 0 10px var(--accent-color); color: var(--accent-color); }
            #trix-music-player { display: flex; flex-direction: column; position: fixed; bottom: 80px; left: 20px; width: 300px; background: rgba(30, 30, 34, 0.9); border: 1px solid var(--border-color); backdrop-filter: blur(10px); border-radius: 8px; padding: 15px; z-index: 99998; box-shadow: var(--container-shadow); opacity: 0; transform: scale(0.95) translateY(10px); transition: opacity 0.3s ease, transform 0.3s ease, box-shadow 0.3s ease, border-color 0.3s ease; pointer-events: none; }
            #trix-music-player.visible { opacity: 1; transform: scale(1) translateY(0); pointer-events: auto; }
            
            @keyframes rainbow-glow { 0%{box-shadow:0 0 10px 2px hsl(0,100%,60%)}16%{box-shadow:0 0 10px 2px hsl(60,100%,60%)}33%{box-shadow:0 0 10px 2px hsl(120,100%,60%)}50%{box-shadow:0 0 10px 2px hsl(180,100%,60%)}66%{box-shadow:0 0 10px 2px hsl(240,100%,60%)}83%{box-shadow:0 0 10px 2px hsl(300,100%,60%)}100%{box-shadow:0 0 10px 2px hsl(360,100%,60%)} }
            #trix-music-player.rainbow-outline { animation: rainbow-glow 2s linear infinite; border-color: transparent; }
            
            .music-drag-handle { position: absolute; top: 0; left: 0; width: 100%; height: 20px; cursor: move; }
            .music-info { text-align: center; margin-bottom: 10px; }
            .music-title { font-size: 16px; font-weight: 600; display: block; }
            .music-artist { font-size: 12px; color: var(--text-muted); }
            .music-progress-container { width: 100%; background: var(--bg-medium); border-radius: 5px; cursor: pointer; height: 6px; margin-bottom: 5px; }
            .music-progress-bar { background: var(--accent-color); width: 0%; height: 100%; border-radius: 5px; }
            .music-time { display: flex; justify-content: space-between; font-size: 11px; color: var(--text-muted); }
            .music-controls { display: flex; justify-content: space-around; align-items: center; margin-bottom: 10px; }
            .music-options-bar { display: flex; justify-content: space-around; align-items: center; width: 100%; margin-bottom: 10px; }
            .music-control-btn { background: none; border: none; color: var(--text-color); cursor: pointer; padding: 5px; opacity: 0.8; transition: all 0.2s; }
            .music-control-btn:hover { opacity: 1; color: var(--accent-color); }
            .music-control-btn.active { color: var(--accent-color); }
            .music-play-btn { font-size: 24px; width: 40px; height: 40px; display: flex; align-items: center; justify-content: center; }
            .music-volume-container { display: flex; align-items: center; justify-content: center; position: relative; }
            .music-volume-slider { -webkit-appearance: none; appearance: none; width: 80px; height: 5px; background: var(--bg-medium); border-radius: 5px; outline: none; transition: opacity 0.3s; }
            .music-volume-slider::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 12px; height: 12px; background: var(--text-color); cursor: pointer; border-radius: 50%; }
            #trix-volume-indicator { position: absolute; top: -30px; left: 50%; transform: translateX(-50%); background: rgba(0,0,0,0.7); color: white; padding: 4px 8px; border-radius: 4px; font-size: 12px; opacity: 0; transition: opacity 0.3s; pointer-events: none; }
            #trix-volume-indicator.visible { opacity: 1; }
            #trix-music-menu, #trix-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 100000; display: flex; align-items: center; justify-content: center; backdrop-filter: blur(5px); opacity: 0; transition: opacity 0.3s; pointer-events: none; }
            #trix-music-menu.visible, #trix-modal.visible { opacity: 1; pointer-events: auto; }
            .music-menu-content { background: var(--bg-darker); border: 1px solid var(--border-color); width: 90%; max-width: 500px; max-height: 80%; border-radius: 8px; display: flex; flex-direction: column; }
            .music-menu-header { padding: 15px; border-bottom: 1px solid var(--border-color); display: flex; justify-content: space-between; align-items: center; }
            #music-search-input { width: 100%; box-sizing: border-box; }
            .music-song-list { overflow-y: auto; padding: 10px; flex-grow: 1; }
            .modal-body { overflow-y: auto; padding: 10px; }
            .modal-body pre { white-space: pre-wrap; word-break: break-all; margin: 0; }
            .music-song-item { display: flex; justify-content: space-between; align-items: center; padding: 10px 15px; cursor: pointer; border-radius: 4px; transition: background-color 0.2s; }
            .music-song-item:hover, .music-song-item.playing { background-color: var(--bg-dark); }
            .music-song-title { display: block; } .music-song-artist { font-size: 12px; color: var(--text-muted); }
            .music-song-delete { font-size: 18px; color: #ef4444; opacity: 0.5; cursor: pointer; padding: 0 5px; }
            .music-song-delete:hover { opacity: 1; }
            .music-menu-footer { padding: 15px; border-top: 1px solid var(--border-color); display: flex; flex-direction: column; gap: 10px; }
            .supabase-tip { font-size: 11px; color: var(--text-muted); text-align: center; margin: 5px 0 0 0; }
            
            /* --- Toast Notifications --- */
            #trix-toast-container { position: fixed; bottom: 20px; right: 20px; z-index: 100001; display: flex; flex-direction: column; gap: 10px; align-items: flex-end; }
            @keyframes trix-toast-in { from { transform: translateX(110%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
            @keyframes trix-toast-out { from { opacity: 1; transform: scale(1); } to { opacity: 0; transform: scale(0.9); } }
            .trix-toast { background-color: var(--bg-dark); color: var(--text-color); border-left: 5px solid var(--accent-color); padding: 15px; border-radius: 8px; box-shadow: var(--container-shadow); width: 350px; animation: trix-toast-in 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94); position: relative; overflow: hidden; cursor: pointer; }
            .trix-toast.fade-out { animation: trix-toast-out 0.4s ease-in forwards; }
            .toast-header { display: flex; justify-content: space-between; align-items: center; font-weight: bold; pointer-events: none; }
            .toast-timer { font-size: 11px; color: var(--text-muted); }
            .toast-body { font-size: 14px; margin-top: 5px; word-break: break-all; pointer-events: none; max-height: 150px; overflow-y: auto; }
            .toast-body code { background: var(--bg-darker); padding: 2px 4px; border-radius: 4px; }
            .toast-body .toast-log-entry { display: table; width: 100%; font: 12px sans-serif; }
            .toast-body .toast-log-timestamp { color: var(--text-muted); padding-right: 0.4em; display: table-cell; width: 6ch; text-align: end; }
            .toast-actions { margin-top: 10px; }
            .toast-button { background: var(--accent-color); color: white; border: none; padding: 5px 10px; font-size: 12px; border-radius: 4px; cursor: pointer; transition: filter 0.2s; }
            .toast-button:hover { filter: brightness(1.1); }
            .toast-progress { position: absolute; bottom: 0; left: 0; height: 4px; background-color: var(--accent-color); width: 100%; opacity: 0.7; pointer-events: none; }
        `;
        shadowRoot.appendChild(styleElement);
 
        let state = {
            tabs: [], activeTabId: null,
            settings: { position: { top: '80px', right: '15px' }, size: { width: '500px', height: '450px' } },
            wsClient: { url: 'wss://echo.websocket.events', protocols: '', autoConnect: false, savedMessages: { 'Example JSON': '{"action":"ping","id":123}'} },
            injector: { savedMessages: { 'Example Chat': '42["chat","Hello from TriX!"]'} },
            musicPlayer: { volume: 0.5, currentSongIndex: 0, position: { bottom: '80px', left: '20px'}, playlist: [] }
        };
 
        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.tabs = savedState.tabs || [];
                state.activeTabId = savedState.activeTabId;
                state.settings = { ...state.settings, ...savedState.settings };
                state.wsClient = { ...state.wsClient, ...savedState.wsClient };
                state.injector = { ...state.injector, ...savedState.injector };
                state.musicPlayer = { ...state.musicPlayer, ...savedState.musicPlayer };
            }
             if (!state.musicPlayer.playlist || state.musicPlayer.playlist.length === 0) {
                state.musicPlayer.playlist = [
                    { title: "Animal I Have Become", artist: "Skillet", url: "https://www.dropbox.com/scl/fi/b3lgo2fkz85h0m3nklx3b/Skilet_-_Animal_I_Have_Become_-mp3.pm-1.mp3?rlkey=yok2i5r5in404ili6ozf776px&st=5xmvo7c5&dl=1" },
                    { title: "Passo Bem Solto", artist: "ATLXS", url: "https://evzxirgylircpnblikrw.supabase.co/storage/v1/object/public/MOOZIK/ATLXS_-_PASSO_BEM_SOLTO_-_Slowed_@BaseNaija%20(2).mp3" },
                    { title: "FROM THE SCREEN TO THE-", artist: "KSI (Audio: Ben4062)", url: "https://www.myinstants.com/media/sounds/from-the-screen-to-the-ring.mp3" },
                    { title: "Honeypie", artist: "JAWNY", url: "https://evzxirgylircpnblikrw.supabase.co/storage/v1/object/public/MOOZIK/JAWNY_-_Honeypie_muzonov.net_(mp3.pm).mp3" },
                    { title: "Kamin", artist: "EMIN ft. JONY", url: "https://evzxirgylircpnblikrw.supabase.co/storage/v1/object/public/MOOZIK/EMIN_Ft_JONY_-_.mp3" },
                    { title: "It Has To Be This Way", artist: "Jamie Christopherson", url: "https://evzxirgylircpnblikrw.supabase.co/storage/v1/object/public/MOOZIK/Metal%20Gear%20Rising-%20Revengeance%20OST%20-%20It%20Has%20To%20Be%20This%20Way%20_Senator%20Battle_%20-%20Jamie%20Christopherson%20-%20SoundLoadMate.com.mp3" }
                ];
            }
            const defaultTabs = [
                { id: 'welcome', name: 'Welcome', isPermanent: true },
                { id: 'logger', name: 'Packet Logger', isPermanent: true },
                { id: 'interceptor', name: 'Interceptor', isPermanent: true },
                { id: 'injector', name: 'Injector', isPermanent: true },
                { id: 'ws_client', name: 'WS Client', isPermanent: true },
                { id: 'storage', name: 'Storage', isPermanent: true },
                { id: Date.now(), name: "My First Script", code: "// Welcome to TriX Executor!\nconsole.log('Hello from a user script!');" }
            ];
            ['welcome', 'logger', 'interceptor', 'injector', 'ws_client', 'storage'].forEach((id, index) => {
                const defaultTab = defaultTabs.find(d => d.id === id);
                if (!state.tabs.find(t => t.id === id)) { state.tabs.splice(index, 0, defaultTab); }
            });
             if (!state.tabs.find(t => t.code)) { state.tabs.push(defaultTabs.find(t => t.code)); }
            if (!state.activeTabId || !state.tabs.find(t => t.id === state.activeTabId)) { state.activeTabId = state.tabs[0].id; }
        }
        
        const saveState = debounce(() => {
            const container = $('#trix-container');
            const musicPlayer = $('#trix-music-player');
            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 };
            }
            if (musicPlayer) {
                state.musicPlayer.position = { top: musicPlayer.style.top, left: musicPlayer.style.left, bottom: musicPlayer.style.bottom, right: musicPlayer.style.right };
            }
            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">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-ping-display">Ping: ---</span>
                        <span id="trix-fps-display">FPS: --</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);"></div>`;
            
            const musicToggle = document.createElement('div');
            musicToggle.id = 'trix-music-toggle-btn';
            musicToggle.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M9 18V5l12-2v13"></path><circle cx="6" cy="18" r="3"></circle><circle cx="18" cy="16" r="3"></circle></svg>`;
            
            const musicPlayer = document.createElement('div');
            musicPlayer.id = 'trix-music-player';
            musicPlayer.innerHTML = `
                <div class="music-drag-handle"></div>
                <div class="music-info"><span class="music-title">Select a Song</span><span class="music-artist">...</span></div>
                <div class="music-progress-container"><div class="music-progress-bar"></div></div>
                <div class="music-time"><span id="music-current-time">0:00</span><span id="music-duration">0:00</span></div>
                <div class="music-controls">
                    <button class="music-control-btn" id="music-prev-btn" title="Previous"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M6 6h2v12H6zm3.5 6 8.5 6V6z"/></svg></button>
                    <button class="music-control-btn" id="music-rewind-btn" title="Back 10s"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M11.999 5v2.999l-4-3-4 3v10l4-3 4 3V19l-8-6 8-6z m8 0v2.999l-4-3-4 3v10l4-3 4 3V19l-8-6 8-6z"/></svg></button>
                    <button class="music-control-btn music-play-btn" id="music-play-pause-btn"><svg width="32" height="32" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg></button>
                    <button class="music-control-btn" id="music-forward-btn" title="Forward 10s"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="m3.999 19 8-6-8-6v12zm8 0 8-6-8-6v12z"/></svg></button>
                    <button class="music-control-btn" id="music-next-btn" title="Next"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M6 18l8.5-6L6 6v12zM16 6v12h2V6h-2z"/></svg></button>
                </div>
                <div class="music-options-bar">
                    <button class="music-control-btn" id="music-playlist-btn" title="Playlist"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M3 13h2v-2H3v2zm0 4h2v-2H3v2zm0-8h2V7H3v2zm4 4h14v-2H7v2zm0 4h14v-2H7v2zM7 7v2h14V7H7z"/></svg></button>
                    <button class="music-control-btn" id="music-loop-btn" title="Loop"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="M7 7h10v3l4-4-4-4v3H5v6h2V7zm10 10H7v-3l-4 4 4 4v-3h12v-6h-2v4z"/></svg></button>
                    <button class="music-control-btn" id="music-autoplay-btn" title="Autoplay"><svg width="20" height="20" viewBox="0 0 24 24" fill="currentColor"><path d="m10 16.5 6-4.5-6-4.5v9zM12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm0 18c-4.41 0-8-3.59-8-8s3.59-8 8-8 8 3.59 8 8-3.59 8-8 8z"></path></svg></button>
                </div>
                <div class="music-volume-container">
                    <input type="range" min="0" max="1" step="0.01" class="music-volume-slider" id="music-volume-slider">
                    <div id="trix-volume-indicator">100%</div>
                </div>`;
 
            const musicMenu = document.createElement('div');
            musicMenu.id = 'trix-music-menu';
            musicMenu.innerHTML = `
                <div class="music-menu-content">
                    <div class="music-menu-header"><input type="text" id="music-search-input" class="trix-input" placeholder="Search songs..."></div>
                    <div class="music-song-list"></div>
                    <div class="music-menu-footer">
                        <input type="text" id="add-song-title" class="trix-input" placeholder="Song Title">
                        <input type="text" id="add-song-artist" class="trix-input" placeholder="Artist Name">
                        <input type="url" id="add-song-url" class="trix-input" placeholder="Direct Music URL">
                        <button id="add-song-btn" class="trix-button" style="flex-grow:0;">Add to Playlist</button>
                        <p class="supabase-tip">Tip: Use a public Supabase bucket for reliable music URLs.</p>
                    </div>
                </div>`;
            
            const modal = document.createElement('div');
            modal.id = 'trix-modal';
            modal.innerHTML = `
                <div class="modal-content">
                    <div class="modal-header">
                        <span>Full Packet Data</span>
                        <span id="trix-modal-close" style="cursor:pointer;font-size:20px;">&times;</span>
                    </div>
                    <div class="modal-body">
                        <pre id="trix-modal-body-content"></pre>
                    </div>
                </div>`;
            
            const toastContainer = document.createElement('div');
            toastContainer.id = 'trix-toast-container';
 
            shadowRoot.append(toggleBtn, container, musicToggle, musicPlayer, musicMenu, modal, toastContainer);
            return { toggleBtn, container, musicPlayer, modal };
        }
        
        function applySettings() {
            const container = $('#trix-container');
            const musicPlayer = $('#trix-music-player');
            if (container) { Object.assign(container.style, state.settings.position, state.settings.size); }
            if (musicPlayer && state.musicPlayer.position) { Object.assign(musicPlayer.style, state.musicPlayer.position); }
        }
 
        updateConnectionStatus = function(status, title) {
            const statusIndicator = $('#trix-conn-status');
            if (statusIndicator) {
                statusIndicator.className = '';
                statusIndicator.classList.add(status);
                statusIndicator.title = title;
            }
        };
 
        updatePingDisplay = function(ping) {
            const pingDisplay = $('#trix-ping-display');
            if (!pingDisplay) return;
            pingDisplay.className = '';
            if (typeof ping === 'number') {
                pingDisplay.textContent = `Ping: ${ping}`;
                if (ping < 100) pingDisplay.classList.add('ping-good');
                else if (ping < 200) pingDisplay.classList.add('ping-ok');
                else pingDisplay.classList.add('ping-bad');
            } else {
                pingDisplay.textContent = `Ping: ${ping}`;
            }
        };
 
        async function measurePing() {
            const startTime = performance.now();
            const url = `${window.location.protocol}//${window.location.host}/favicon.ico?_ts=${Date.now()}`;
            try {
                const response = await fetch(url, { method: 'HEAD', cache: 'no-store' });
                if (response.ok) {
                    const endTime = performance.now();
                    updatePingDisplay(Math.round(endTime - startTime));
                } else {
                    updatePingDisplay('Error');
                }
            } catch (error) {
                updatePingDisplay('Error');
            }
        }
 
        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 === 'welcome') {
                 content.innerHTML = `
                    <div class="trix-view">
                        <div class="trix-tabs"></div>
                        <div id="trix-welcome-view" style="padding: 10px;">
                            <h2>Welcome to TriX Executor!</h2>
                            <p>This is a powerful tool for web developers and gamers. Here's what you can do:</p>
                            <ul>
                                <li><b>Packet Logger:</b> See all network traffic, with tools to copy, view, or suspend the log.</li>
                                <li><b>Interceptor:</b> Pause, edit, or block WebSocket messages in real-time.</li>
                                <li><b>Injector:</b> Send custom packets to the live game server.</li>
                                <li><b>WS Client:</b> Test any WebSocket server by sending custom messages.</li>
                                <li><b>Storage:</b> View and edit <code>localStorage</code> and <code>sessionStorage</code> directly.</li>
                                <li><b>Scripts:</b> Create and run your own JavaScript snippets on the page.</li>
                            </ul>
                        </div>
                    </div>`;
                renderTabs();
            } else if (activeTab.id === 'ws_client') {
                 content.innerHTML = `
                    <div id="trix-ws-client-view" class="trix-view">
                        <div class="trix-tabs"></div>
                        <div class="trix-ws-client-controls">
                            <input type="text" id="trix-ws-url" class="trix-input" style="flex-grow:1;" placeholder="WebSocket URL (e.g., wss://...)" value="${state.wsClient.url}">
                            <button id="trix-ws-connect-btn" class="trix-button" style="flex-grow:0;">Connect</button>
                            <label><input type="checkbox" id="trix-ws-auto-connect"> Auto-Connect</label>
                        </div>
                        <div id="trix-ws-console"><div class="ws-console-system">Status: Disconnected</div></div>
                        <div class="trix-ws-client-grid">
                            <textarea id="trix-ws-message" class="trix-input" placeholder="Enter message to send..."></textarea>
                            <div class="trix-ws-message-controls">
                                <button id="trix-ws-send-btn" class="trix-button">Send</button>
                                <select id="trix-ws-saved-messages" class="trix-input"><option value="">Load</option></select>
                                <button id="trix-ws-save-msg-btn" class="trix-button" style="background-color:#0d6efd;">Save</button>
                                <button id="trix-ws-del-msg-btn" class="trix-button" style="background-color:#6c757d;">Del</button>
                            </div>
                        </div>
                    </div>`;
                renderTabs();
                renderWsSavedMessages();

                const autoConnectCheck = $('#trix-ws-auto-connect');
                autoConnectCheck.checked = state.wsClient.autoConnect;
                autoConnectCheck.addEventListener('change', () => {
                    state.wsClient.autoConnect = autoConnectCheck.checked;
                    saveState();
                });

                if (state.wsClient.autoConnect && (!customWs || customWs.readyState > 1)) {
                    connectCustomWs();
                }

            } else if (activeTab.id === 'interceptor') {
                 content.innerHTML = `
                    <div id="trix-interceptor-view" class="trix-view">
                        <div class="trix-tabs"></div>
                        <div id="trix-interceptor-controls" class="trix-action-bar">
                            <button id="trix-toggle-interception-btn" class="trix-button">Enable Interception</button>
                            <button id="trix-forward-all-btn" class="trix-button" style="background-color:#28a745;">Forward All</button>
                            <button id="trix-block-all-btn" class="trix-button" style="background-color:#dc3545;">Block All</button>
                        </div>
                        <div id="trix-interceptor-queue"></div>
                        <div class="trix-status-bar">Warning: Enabling interception may cause disconnects if messages are not processed quickly.</div>
                    </div>`;
                renderTabs();
                renderInterceptorQueue();
                const toggleBtn = $('#trix-toggle-interception-btn');
                if (interceptionEnabled) {
                    toggleBtn.classList.add('active');
                    toggleBtn.textContent = 'Interception Enabled';
                }
            } else if (activeTab.id === 'injector') {
                content.innerHTML = `
                    <div id="trix-injector-view" class="trix-view">
                        <div class="trix-tabs"></div>
                        <div class="trix-injector-grid" style="flex-grow:1; display:flex; flex-direction:column;">
                            <textarea id="trix-injector-message" class="trix-input" placeholder="Enter packet data to inject..."></textarea>
                            <div class="trix-injector-grid">
                                <button id="trix-inject-btn" class="trix-button">Inject Packet</button>
                                <div class="trix-injector-message-controls">
                                    <select id="trix-injector-saved-messages" class="trix-input"><option value="">Load</option></select>
                                    <button id="trix-injector-save-msg-btn" class="trix-button" style="background-color:#0d6efd;">Save</button>
                                    <button id="trix-injector-del-msg-btn" class="trix-button" style="background-color:#6c757d;">Del</button>
                                </div>
                            </div>
                        </div>
                        <div class="trix-status-bar">Inject packets into the live game connection.</div>
                    </div>`;
                renderTabs();
                renderInjectorSavedMessages();
            } else 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-content"></div>
                    </div>`;
                renderTabs();
                renderPacketLoggerView(); 
            } else if (activeTab.id === 'storage') {
                content.innerHTML = `
                    <div id="trix-storage-view-container" class="trix-view">
                        <div class="trix-tabs"></div>
                        <div id="trix-storage-view"></div>
                        <div class="trix-action-bar">
                            <button id="trix-refresh-storage-btn" class="trix-button">Refresh</button>
                            <button id="trix-add-storage-btn" class="trix-button">Add Local Storage Entry</button>
                        </div>
                    </div>`;
                renderTabs();
                renderStorageView();
            } else {
                content.innerHTML = `
                    <div id="trix-script-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();
            }
        }
        
        renderInterceptorQueue = function() {
            const queueContainer = $('#trix-interceptor-queue');
            if (!queueContainer) return;
            queueContainer.innerHTML = '';
            if (queuedMessages.size === 0) { queueContainer.textContent = 'No messages pending.'; return; }
            for (const [id, msg] of queuedMessages.entries()) {
                const item = document.createElement('div');
                item.className = 'interceptor-item';
                item.dataset.messageId = id;
                item.innerHTML = `
                    <div class="interceptor-item-header"><span class="${msg.direction}">${msg.direction.toUpperCase()}</span></div>
                    <textarea>${msg.data}</textarea>
                    <div class="interceptor-item-actions">
                        <button class="trix-button interceptor-btn interceptor-forward-btn">Forward</button>
                        <button class="trix-button interceptor-btn interceptor-block-btn">Block</button>
                    </div>`;
                queueContainer.appendChild(item);
            }
        };
 
        function renderStorageView() {
            const view = $('#trix-storage-view');
            if (!view) return;
            view.innerHTML = '';
            const storages = { 'Local Storage': unsafeWindow.localStorage, 'Session Storage': unsafeWindow.sessionStorage };
            for (const [name, storage] of Object.entries(storages)) {
                const container = document.createElement('div');
                container.className = 'storage-table';
                container.innerHTML = `<div class="storage-header">${name} (${storage.length} items)</div>`;
                for (let i = 0; i < storage.length; i++) {
                    const key = storage.key(i);
                    const value = storage.getItem(key);
                    const row = document.createElement('div');
                    row.className = 'storage-row';
                    row.dataset.key = key;
                    row.dataset.storage = name === 'Local Storage' ? 'local' : 'session';
                    row.innerHTML = `<span class="storage-key" title="${key}">${key}</span><span class="storage-value">${value}</span><span class="storage-delete" title="Delete key">✖</span>`;
                    container.appendChild(row);
                }
                view.appendChild(container);
            }
        }
        
        function renderWsSavedMessages() {
            const select = $('#trix-ws-saved-messages');
            if (!select) return;
            select.innerHTML = '<option value="">Load Message</option>';
            for (const name in state.wsClient.savedMessages) {
                const option = document.createElement('option');
                option.value = name;
                option.textContent = name;
                select.appendChild(option);
            }
        }
        
        function renderInjectorSavedMessages() {
            const select = $('#trix-injector-saved-messages');
            if (!select) return;
            select.innerHTML = '<option value="">Load Packet</option>';
            for (const name in state.injector.savedMessages) {
                const option = document.createElement('option');
                option.value = name;
                option.textContent = name;
                select.appendChild(option);
            }
        }
 
        function logToWsClientConsole(message, type = 'system') {
            const consoleEl = $('#trix-ws-console');
            if (!consoleEl) return;
            const line = document.createElement('div');
            line.className = `ws-console-${type}`;
            line.textContent = `[${type.toUpperCase()}] ${message}`;
            consoleEl.appendChild(line);
            consoleEl.scrollTop = consoleEl.scrollHeight;
        }
 
        function connectCustomWs() {
            const urlInput = $('#trix-ws-url');
            const connectBtn = $('#trix-ws-connect-btn');
            if (customWs && customWs.readyState < 2) { customWs.close(); return; }
            state.wsClient.url = urlInput.value.trim();
            saveState();
            if (!state.wsClient.url) { logToWsClientConsole('Error: URL cannot be empty.', 'system'); return; }
            connectBtn.textContent = 'Connecting...';
            connectBtn.disabled = true;
            logToWsClientConsole(`Connecting to ${state.wsClient.url}...`, 'system');
            customWs = new OriginalWebSocket(state.wsClient.url);
            customWs.onopen = () => { logToWsClientConsole('Connection established.', 'system'); connectBtn.textContent = 'Disconnect'; connectBtn.classList.add('connected'); connectBtn.disabled = false; };
            customWs.onmessage = (event) => { logToWsClientConsole(event.data, 'receive'); };
            customWs.onclose = (event) => {
                logToWsClientConsole(`Connection closed. Code: ${event.code}`, 'system');
                connectBtn.textContent = 'Connect';
                connectBtn.classList.remove('connected');
                connectBtn.disabled = false;
                customWs = null;

                if (state.wsClient.autoConnect) {
                    logToWsClientConsole('Auto-reconnect enabled. Retrying in 3 seconds...', 'system');
                    setTimeout(connectCustomWs, 3000);
                } else {
                    showToast('WS Client Disconnected', `The connection to <code>${state.wsClient.url}</code> was closed.`, {
                        action: { text: 'Reconnect', callback: connectCustomWs }
                    });
                }
            };
            customWs.onerror = () => { logToWsClientConsole('Connection error.', 'system'); connectBtn.textContent = 'Connect'; connectBtn.classList.remove('connected'); connectBtn.disabled = false; customWs = null; };
        }
 
        function renderEditor() {
            const editorArea = $('.trix-editor-area');
            if (!editorArea) { editor = null; return; }
            const activeTab = state.tabs.find(t => t.id === state.activeTabId);
            if (!activeTab || typeof activeTab.code !== 'string') { 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);
            const resizeObserver = new ResizeObserver(debounce(saveState, 500));
            resizeObserver.observe(ui.container);
 
            ui.modal.addEventListener('click', (e) => {
                if (e.target.id === 'trix-modal' || e.target.id === 'trix-modal-close') {
                    ui.modal.classList.remove('visible');
                }
            });

            // Define the action for the global callback
            onConnectionStateChange = () => {
                if (state.activeTabId === 'logger') {
                    renderPacketLoggerView();
                }
            };
        }
 
        function handleContainerClick(e) {
            const target = e.target;
            const tabEl = target.closest('.trix-tab');
            if (tabEl && !target.classList.contains('trix-tab-close')) {
                const tabId = isNaN(parseInt(tabEl.dataset.tabId)) ? tabEl.dataset.tabId : parseInt(tabEl.dataset.tabId);
                if (tabId !== state.activeTabId) { 
                    viewingConnection = null; // Reset logger view when changing tabs
                    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(); }
            }
            if (target.id === 'trix-inject-btn') injectPacket();
            if (target.classList.contains('storage-delete')) {
                const row = target.closest('.storage-row');
                const { key, storage } = row.dataset;
                if (confirm(`Delete key "${key}"?`)) {
                    (storage === 'local' ? unsafeWindow.localStorage : unsafeWindow.sessionStorage).removeItem(key);
                    renderStorageView();
                }
            } else if (target.classList.contains('storage-value')) {
                const row = target.closest('.storage-row');
                const { key, storage } = row.dataset;
                const input = document.createElement('input');
                input.type = 'text';
                input.className = 'storage-value-input';
                input.value = target.textContent;
                target.replaceWith(input);
                input.focus();
                const finishEdit = () => {
                    (storage === 'local' ? unsafeWindow.localStorage : unsafeWindow.sessionStorage).setItem(key, input.value);
                    renderStorageView();
                };
                input.addEventListener('blur', finishEdit, { once: true });
                input.addEventListener('keydown', e => { if (e.key === 'Enter') finishEdit(); if(e.key === 'Escape') renderStorageView(); });
            }
            if (target.id === 'trix-toggle-interception-btn') {
                interceptionEnabled = !interceptionEnabled;
                target.classList.toggle('active', interceptionEnabled);
                target.textContent = interceptionEnabled ? 'Interception Enabled' : 'Enable Interception';
            }
            if (target.id === 'trix-forward-all-btn') {
                for (const msg of queuedMessages.values()) { msg.resolve({ action: 'forward', data: msg.data }); }
            }
            if (target.id === 'trix-block-all-btn') {
                for (const msg of queuedMessages.values()) { msg.resolve({ action: 'block' }); }
            }
            const messageItem = target.closest('.interceptor-item');
            if (messageItem) {
                const messageId = messageItem.dataset.messageId;
                const message = queuedMessages.get(messageId);
                if (!message) return;
                if (target.classList.contains('interceptor-forward-btn')) {
                    const editedData = messageItem.querySelector('textarea').value;
                    message.resolve({ action: 'forward', data: editedData });
                } else if (target.classList.contains('interceptor-block-btn')) {
                    message.resolve({ action: 'block' });
                }
            }
            if (target.id === 'trix-ws-connect-btn') connectCustomWs();
            if (target.id === 'trix-ws-send-btn') {
                if (customWs && customWs.readyState === 1) {
                    const message = $('#trix-ws-message').value;
                    customWs.send(message);
                    logToWsClientConsole(message, 'send');
                } else { logToWsClientConsole('Error: Not connected.', 'system'); }
            }
            if (target.id === 'trix-ws-save-msg-btn') {
                const message = $('#trix-ws-message').value;
                if (!message) return;
                const name = prompt('Enter a name for this message:', 'My Message');
                if (name) { state.wsClient.savedMessages[name] = message; saveState(); renderWsSavedMessages(); }
            }
            if (target.id === 'trix-ws-del-msg-btn') {
                const select = $('#trix-ws-saved-messages');
                const name = select.value;
                if (name && confirm(`Delete saved message "${name}"?`)) { delete state.wsClient.savedMessages[name]; saveState(); renderWsSavedMessages(); }
            }
            if (target.id === 'trix-injector-save-msg-btn') {
                const message = $('#trix-injector-message').value;
                if (!message) return;
                const name = prompt('Enter a name for this packet:', 'My Packet');
                if (name) { state.injector.savedMessages[name] = message; saveState(); renderInjectorSavedMessages(); }
            }
            if (target.id === 'trix-injector-del-msg-btn') {
                const select = $('#trix-injector-saved-messages');
                const name = select.value;
                if (name && confirm(`Delete saved packet "${name}"?`)) { delete state.injector.savedMessages[name]; saveState(); renderInjectorSavedMessages(); }
            }
            if (target.id === 'trix-refresh-storage-btn') renderStorageView();
            if (target.id === 'trix-add-storage-btn') {
                const key = prompt('Enter new key:');
                if (key) { const value = prompt(`Enter value for "${key}":`); unsafeWindow.localStorage.setItem(key, value); renderStorageView(); }
            }
            if (target.id === 'trix-clear-log-btn' && viewingConnection) {
                const conn = monitoredConnections.get(viewingConnection);
                if (conn) conn.log = [];
                renderPacketLoggerView();
            }
            if (target.id === 'trix-suspend-log-btn') {
                isLoggerSuspended = !isLoggerSuspended;
                target.classList.toggle('suspended', isLoggerSuspended);
                target.textContent = isLoggerSuspended ? 'Resume Log' : 'Suspend Log';
            }
            
            const wsSavedMessagesSelect = target.closest('#trix-ws-saved-messages');
            if(wsSavedMessagesSelect && wsSavedMessagesSelect.value) {
                $('#trix-ws-message').value = state.wsClient.savedMessages[wsSavedMessagesSelect.value];
            }
            const injectorSavedMessagesSelect = target.closest('#trix-injector-saved-messages');
            if(injectorSavedMessagesSelect && injectorSavedMessagesSelect.value) {
                $('#trix-injector-message').value = state.injector.savedMessages[injectorSavedMessagesSelect.value];
            }
        }
        
        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;
            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();
            };
            input.addEventListener('blur', finishEditing, { once: true });
            input.addEventListener('keydown', e => { if (e.key === 'Enter') finishEditing(); else if (e.key === 'Escape') renderTabs(); });
        }
 
        function initDraggable(container, handles) {
            let isDragging = false, offsetX, offsetY;
            handles.forEach(handle => {
                handle.addEventListener('mousedown', e => {
                    if (e.target.closest('button, input, select, textarea')) 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) {
                    container.style.left = `${e.clientX - offsetX}px`;
                    container.style.top = `${Math.max(0, e.clientY - offsetY)}px`;
                }
            });
            document.addEventListener('mouseup', () => { if (isDragging) { isDragging = false; document.body.style.userSelect = ''; saveState(); } });
        }
 
        function injectPacket() {
            const statusBar = $('#trix-injector-view .trix-status-bar');
            const messageInput = $('#trix-injector-message');
            if (!statusBar || !messageInput) return;
 
            const ws = unsafeWindow.webSocketManager?.activeSocket;
            if (ws && ws.readyState === 1) {
                const message = messageInput.value;
                ws.send(message);
                statusBar.textContent = `Success: Packet injected at ${new Date().toLocaleTimeString()}`;
            } else {
                statusBar.textContent = 'Error: Not connected to the game server.';
            }
        }
 
        function executeScript() {
            const statusBar = $('#trix-script-injector-container .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.'; return; }
            try {
                new Function(activeTab.code)();
                statusBar.textContent = `Success: Executed '${activeTab.name}' at ${new Date().toLocaleTimeString()}`;
            } catch (error) {
                console.error('TriX Executor Error:', error);
                statusBar.textContent = `Error: ${error.message}`;
            }
        }
 
        function createPacketElement({ type, data }) {
            const item = document.createElement('div');
            item.className = `packet-item packet-${type}`;
            item.dataset.fullData = data;
        
            const dataSpan = document.createElement('span');
            dataSpan.className = 'packet-data';
            dataSpan.innerHTML = `<span class="packet-meta">[${type.toUpperCase()}]</span> ${data}`;
        
            const actionsDiv = document.createElement('div');
            actionsDiv.className = 'packet-actions';
        
            const copyBtn = document.createElement('button');
            copyBtn.className = 'packet-action-btn';
            copyBtn.textContent = 'Copy';
            copyBtn.onclick = (e) => {
                navigator.clipboard.writeText(data).then(() => {
                    e.target.textContent = 'Copied!';
                    setTimeout(() => e.target.textContent = 'Copy', 1000);
                });
            };
            actionsDiv.appendChild(copyBtn);
        
            if (type.startsWith('receive')) {
                const viewBtn = document.createElement('button');
                viewBtn.className = 'packet-action-btn';
                viewBtn.textContent = 'View';
                viewBtn.onclick = () => {
                    $('#trix-modal-body-content').textContent = data;
                    ui.modal.classList.add('visible');
                };
                actionsDiv.appendChild(viewBtn);
            }
        
            item.appendChild(dataSpan);
            item.appendChild(actionsDiv);
            return item;
        }
 
        function renderPacketLoggerView() {
            const content = $('#trix-packet-log-content');
            if (!content) return;
            content.innerHTML = '';
        
            if (viewingConnection) {
                // --- DETAILED LOG VIEW ---
                const conn = monitoredConnections.get(viewingConnection);
                if (!conn) { // Connection might have closed while viewing
                    viewingConnection = null;
                    renderPacketLoggerView(); // Go back to monitor
                    return;
                }
        
                const backButton = document.createElement('button');
                backButton.className = 'trix-button';
                backButton.textContent = '← Back To Monitor';
                backButton.style.marginBottom = '10px';
                backButton.style.flexGrow = 0;
                backButton.onclick = () => {
                    viewingConnection = null;
                    renderPacketLoggerView();
                };
                
                const logContainer = document.createElement('div');
                logContainer.id = 'trix-packet-log';
                logContainer.style.height = 'calc(100% - 90px)'; // Adjust for buttons
                
                const fragment = document.createDocumentFragment();
                conn.log.forEach(packet => fragment.appendChild(createPacketElement(packet)));
                logContainer.appendChild(fragment);
                
                const actionBar = document.createElement('div');
                actionBar.className = 'trix-action-bar';
                actionBar.innerHTML = `
                    <button id="trix-suspend-log-btn" class="trix-button">Suspend Log</button>
                    <button id="trix-clear-log-btn" class="trix-button">Clear Log</button>
                `;
                
                content.append(backButton, logContainer, actionBar);
                logContainer.scrollTop = logContainer.scrollHeight;
        
                const suspendBtn = $('#trix-suspend-log-btn');
                if (suspendBtn && isLoggerSuspended) {
                    suspendBtn.classList.add('suspended');
                    suspendBtn.textContent = 'Resume Log';
                }
        
            } else {
                // --- MONITOR VIEW ---
                if (monitoredConnections.size === 0) {
                    content.textContent = 'Waiting for WebSocket connections...';
                    return;
                }
                
                monitoredConnections.forEach((conn, ws) => {
                    const card = document.createElement('div');
                    card.className = 'connection-card';
                    card.style.borderLeftColor = conn.state === 'OPEN' ? '#28a745' : '#ffc107';
                    card.innerHTML = `
                        <div class="card-header">
                            <span>Active Connection</span>
                            <span class="card-state ${conn.state}"><div class="card-state-dot"></div>${conn.state}</span>
                        </div>
                        <div class="card-url">${conn.url}</div>
                    `;
                    card.addEventListener('click', () => {
                        viewingConnection = ws;
                        renderPacketLoggerView();
                    });
                    content.appendChild(card);
                });
            }
        }
 
        logPacketCallback = function(ws, type, data) {
            if (isLoggerSuspended) return;
            
            const conn = monitoredConnections.get(ws);
            if (conn) {
                conn.log.push({ type, data });
                
                // If we are currently viewing this connection's log, append the new packet live
                if (viewingConnection === ws) {
                    const logContainer = $('#trix-packet-log');
                    if (logContainer) {
                        const item = createPacketElement({ type, data });
                        logContainer.appendChild(item);
                        logContainer.scrollTop = logContainer.scrollHeight;
                    }
                }
            }
        };

        function showToast(title, body, options = {}) {
            const toastContainer = $('#trix-toast-container');
            if (!toastContainer) return;
     
            const toast = document.createElement('div');
            toast.className = 'trix-toast';
     
            const duration = options.duration || (Math.random() * (16000 - 8000) + 8000);
            const startTime = performance.now();
     
            let actionsHTML = '';
            if (options.action) {
                actionsHTML = `<div class="toast-actions"><button class="toast-button">${options.action.text}</button></div>`;
            }
            
            toast.innerHTML = `
                <div class="toast-header">
                    <span>${title}</span>
                    <span class="toast-timer">${(duration / 1000).toFixed(1)}s</span>
                </div>
                <div class="toast-body">${body}</div>
                ${actionsHTML}
                <div class="toast-progress"></div>
            `;
     
            toastContainer.appendChild(toast);
     
            const timerEl = toast.querySelector('.toast-timer');
            const progressEl = toast.querySelector('.toast-progress');
            
            const intervalId = setInterval(() => {
                const elapsed = performance.now() - startTime;
                const remaining = duration - elapsed;
                if (remaining <= 0) {
                    clearInterval(intervalId);
                    return;
                }
                timerEl.textContent = `${(remaining / 1000).toFixed(1)}s`;
                progressEl.style.width = `${(remaining / duration) * 100}%`;
            }, 100);
     
            const dismissToast = () => {
                clearInterval(intervalId);
                clearTimeout(timeoutId);
                toast.classList.add('fade-out');
                toast.addEventListener('animationend', () => {
                    if (toast.parentNode) toast.parentNode.removeChild(toast);
                }, { once: true });
            };

            if (options.action) {
                toast.querySelector('.toast-button').addEventListener('click', () => {
                    options.action.callback();
                    dismissToast();
                });
            }
            
            const timeoutId = setTimeout(dismissToast, duration);
            
            toast.addEventListener('click', (e) => {
                if (!e.target.classList.contains('toast-button')) {
                    dismissToast();
                }
            });
        }
        
        loadState();
        ui = createUI();
        applySettings();
        renderActiveView();
        initEventListeners();
        
        const musicPlayer = initMusicPlayer();
        initDraggable(ui.musicPlayer, [$('.music-drag-handle', ui.musicPlayer)]);

        const toastOnConnect = (url) => {
            showToast('New Connection!', `<code>${url}</code>`, {
                action: {
                    text: 'Save to WS Client',
                    callback: () => {
                        state.wsClient.url = url;
                        saveState();
                        const urlInput = $('#trix-ws-url');
                        if (urlInput) urlInput.value = url;
                        showToast('Success', 'URL saved to WS Client.', { duration: 3000 });
                    }
                }
            });
        };
        showToastCallback = toastOnConnect;
        connectionUrlQueue.forEach(url => showToastCallback(url));
        connectionUrlQueue = [];
 
        setInterval(measurePing, 2000);
 
        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);

        // --- Game Log Monitor ---
        let gameLogObserver;
        let logMessageQueue = [];
        let logToastTimer = null;

        function processAndShowLogToast() {
            if (logMessageQueue.length === 0) return;

            const toastBody = logMessageQueue.map(item => {
                return `<div class="toast-log-entry"><span class="toast-log-timestamp">${item.timestamp}</span><span>${item.message}</span></div>`;
            }).join('');

            showToast('Game Log Update', toastBody, { duration: 8000 });
            logMessageQueue = [];
        }

        function observeGameLog() {
            if (gameLogObserver) gameLogObserver.disconnect();
            const gameLogContainer = document.querySelector("body > div:nth-child(4) > div:nth-child(1)");

            if (gameLogContainer) {
                gameLogObserver = new MutationObserver((mutations) => {
                    if (!settings.gameLogToasts) return;
                    let hasNewNodes = false;
                    for (const mutation of mutations) {
                        if (mutation.type === 'childList') {
                            mutation.addedNodes.forEach(node => {
                                if (node.nodeType === Node.ELEMENT_NODE && node.style.display === 'table') {
                                    const timestampNode = node.querySelector('span:first-child');
                                    const messageNode = node.querySelector('span:last-child');
                                    if (timestampNode && messageNode) {
                                        logMessageQueue.push({
                                            timestamp: timestampNode.textContent,
                                            message: messageNode.innerHTML
                                        });
                                        hasNewNodes = true;
                                    }
                                }
                            });
                        }
                    }

                    if (hasNewNodes) {
                        clearTimeout(logToastTimer);
                        logToastTimer = setTimeout(processAndShowLogToast, 1500);
                    }
                });
                gameLogObserver.observe(gameLogContainer, { childList: true, subtree: true });
            } else {
                setTimeout(observeGameLog, 1000);
            }
        }
        observeGameLog();
    }
 
    function initMusicPlayer() {
        const player = {
            audio: document.createElement('audio'),
            isPlaying: false,
            loop: false,
            autoplay: true
        };
 
        const $ = (sel) => document.querySelector('#trix-host').shadowRoot.querySelector(sel);
        const playerEl = $('#trix-music-player');
        const titleEl = $('.music-title');
        const artistEl = $('.music-artist');
        const playPauseBtn = $('#music-play-pause-btn');
        const progressBar = $('.music-progress-bar');
        const progressContainer = $('.music-progress-container');
        const currentTimeEl = $('#music-current-time');
        const durationEl = $('#music-duration');
        const volumeSlider = $('#music-volume-slider');
        const volumeIndicator = $('#trix-volume-indicator');
        const loopBtn = $('#music-loop-btn');
        const autoplayBtn = $('#music-autoplay-btn');
        const menuEl = $('#trix-music-menu');
        const songListEl = $('.music-song-list');
        let volumeTimeout;
        
        const updateSpecialEffects = () => {
            playerEl.classList.toggle('rainbow-outline', player.isPlaying);
        };
 
        const loadSong = (index) => {
            if (index < 0 || index >= state.musicPlayer.playlist.length) {
                if (state.musicPlayer.playlist.length > 0) {
                    index = 0;
                } else {
                    return; // No songs to load
                }
            }
            state.musicPlayer.currentSongIndex = index;
            const song = state.musicPlayer.playlist[index];
            player.audio.src = song.url;
            player.audio.loop = player.loop;
            titleEl.textContent = song.title;
            artistEl.textContent = song.artist;
            renderSongList();
            updateSpecialEffects();
        };
        
        const playSong = () => {
            if (!player.audio.src) return;
            player.isPlaying = true;
            player.audio.play().catch(e => console.error("Audio play failed:", e));
            playPauseBtn.innerHTML = `<svg width="32" height="32" viewBox="0 0 24 24" fill="currentColor"><path d="M6 19h4V5H6v14zm8-14v14h4V5h-4z"/></svg>`;
            updateSpecialEffects();
        };
        const pauseSong = () => {
            player.isPlaying = false;
            player.audio.pause();
            playPauseBtn.innerHTML = `<svg width="32" height="32" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>`;
            updateSpecialEffects();
        };
        
        const nextSong = () => { let newIndex = state.musicPlayer.currentSongIndex + 1; if (newIndex >= state.musicPlayer.playlist.length) newIndex = 0; loadSong(newIndex); playSong(); };
        const prevSong = () => { let newIndex = state.musicPlayer.currentSongIndex - 1; if (newIndex < 0) newIndex = state.musicPlayer.playlist.length - 1; loadSong(newIndex); playSong(); };
 
        const setVolume = (value) => {
            state.musicPlayer.volume = value;
            player.audio.volume = value;
            volumeSlider.value = value;
            volumeIndicator.textContent = `${Math.round(value * 100)}%`;
            volumeIndicator.classList.add('visible');
            clearTimeout(volumeTimeout);
            volumeTimeout = setTimeout(() => volumeIndicator.classList.remove('visible'), 1000);
            saveState();
        };
        
        const renderSongList = (filter = '') => {
            songListEl.innerHTML = '';
            const filteredList = state.musicPlayer.playlist.filter(song => song.title.toLowerCase().includes(filter) || song.artist.toLowerCase().includes(filter));
            filteredList.forEach((song) => {
                const originalIndex = state.musicPlayer.playlist.indexOf(song);
                const item = document.createElement('div');
                item.className = 'music-song-item';
                if (originalIndex === state.musicPlayer.currentSongIndex) item.classList.add('playing');
                item.innerHTML = `<div><span class="music-song-title">${song.title}</span><span class="music-song-artist">${song.artist}</span></div><span class="music-song-delete" title="Remove song">&times;</span>`;
                
                item.querySelector('.music-song-delete').addEventListener('click', (e) => {
                    e.stopPropagation();
                    if (confirm(`Are you sure you want to remove "${song.title}"?`)) {
                        const wasPlaying = (originalIndex === state.musicPlayer.currentSongIndex);
                        const isCurrentlyPlaying = player.isPlaying;

                        state.musicPlayer.playlist.splice(originalIndex, 1);
                        
                        if (wasPlaying) {
                            pauseSong();
                            if (state.musicPlayer.playlist.length > 0) {
                                if (state.musicPlayer.currentSongIndex >= state.musicPlayer.playlist.length) {
                                    state.musicPlayer.currentSongIndex = 0;
                                }
                                loadSong(state.musicPlayer.currentSongIndex);
                                if(isCurrentlyPlaying) playSong();
                            } else {
                                player.audio.src = '';
                                titleEl.textContent = 'Select a Song';
                                artistEl.textContent = '...';
                                state.musicPlayer.currentSongIndex = 0;
                            }
                        } else if (originalIndex < state.musicPlayer.currentSongIndex) {
                            state.musicPlayer.currentSongIndex--;
                        }
                        
                        saveState();
                        renderSongList();
                    }
                });
                
                item.addEventListener('click', () => { loadSong(originalIndex); playSong(); menuEl.classList.remove('visible'); });
                songListEl.appendChild(item);
            });
        };
 
        $('#trix-music-toggle-btn').addEventListener('click', () => playerEl.classList.toggle('visible'));
        playPauseBtn.addEventListener('click', () => player.isPlaying ? pauseSong() : playSong());
        $('#music-next-btn').addEventListener('click', nextSong);
        $('#music-prev-btn').addEventListener('click', prevSong);
        $('#music-forward-btn').addEventListener('click', () => player.audio.currentTime += 10);
        $('#music-rewind-btn').addEventListener('click', () => player.audio.currentTime -= 10);
        volumeSlider.addEventListener('input', (e) => setVolume(parseFloat(e.target.value)));
 
        loopBtn.addEventListener('click', () => {
            player.loop = !player.loop;
            player.audio.loop = player.loop;
            loopBtn.classList.toggle('active', player.loop);
        });
 
        autoplayBtn.addEventListener('click', () => {
            player.autoplay = !player.autoplay;
            autoplayBtn.classList.toggle('active', player.autoplay);
        });
        
        player.audio.addEventListener('timeupdate', () => {
            const { currentTime, duration } = player.audio;
            if (duration) {
                progressBar.style.width = `${(currentTime / duration) * 100}%`;
                const formatTime = (s) => `${Math.floor(s/60)}:${String(Math.floor(s%60)).padStart(2,'0')}`;
                currentTimeEl.textContent = formatTime(currentTime);
                durationEl.textContent = formatTime(duration);
            }
        });
        player.audio.addEventListener('ended', () => {
            if (player.autoplay) {
                nextSong();
            } else {
                pauseSong();
                player.audio.currentTime = 0;
            }
        });
        progressContainer.addEventListener('click', (e) => {
            if (!isNaN(player.audio.duration)) {
                player.audio.currentTime = (e.offsetX / progressContainer.clientWidth) * player.audio.duration;
            }
        });
        $('#music-playlist-btn').addEventListener('click', () => menuEl.classList.add('visible'));
        $('#music-search-input').addEventListener('input', (e) => renderSongList(e.target.value.toLowerCase()));
        menuEl.addEventListener('click', (e) => { if(e.target.id === 'trix-music-menu') menuEl.classList.remove('visible'); });
 
        $('#add-song-btn').addEventListener('click', () => {
            const titleInput = $('#add-song-title');
            const artistInput = $('#add-song-artist');
            const urlInput = $('#add-song-url');
            const title = titleInput.value.trim();
            const artist = artistInput.value.trim();
            const url = urlInput.value.trim();

            if (!title || !artist || !url) { alert('Please fill out all fields.'); return; }
            try { new URL(url); } catch (_) { alert('Please enter a valid URL.'); return; }

            state.musicPlayer.playlist.push({ title, artist, url });
            saveState();
            renderSongList();
            titleInput.value = ''; artistInput.value = ''; urlInput.value = '';
        });

        // Initialize UI states
        setVolume(state.musicPlayer.volume);
        autoplayBtn.classList.toggle('active', player.autoplay);
        loadSong(state.musicPlayer.currentSongIndex);
        renderSongList();
        
        return player;
    }
 
    function showLoadingScreen() {
        const themeVars = {
            vscode: { accent: '#00aaff', shadow1: '#0077cc', shadow2: '#0055aa' },
            retro: { accent: '#00ff9b', shadow1: '#00b36d', shadow2: '#008752' },
            crimson: { accent: '#e11d48', shadow1: '#be123c', shadow2: '#881337' }
        };
        const currentTheme = themeVars[settings.theme] || themeVars.vscode;
 
        const style = document.createElement('style');
        style.textContent = `
            #trix-loading-screen, #trix-loading-settings-modal { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background-color: #000; z-index: 2147483647; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #fff; font-family: 'Segoe UI', sans-serif; opacity: 0; transition: opacity 0.5s ease-in-out; pointer-events: none; }
            #trix-loading-screen.visible, #trix-loading-settings-modal.visible { opacity: 1; pointer-events: auto; }
            .loading-content { opacity: 1; transition: opacity 1s ease-in-out; text-align: center; }
            .loading-content.fade-out { opacity: 0; }
            .loading-header { font-size: 5rem; font-weight: bold; color: ${currentTheme.accent}; text-shadow: 2px 2px 0px ${currentTheme.shadow1}, 4px 4px 0px ${currentTheme.shadow2}, 6px 6px 10px rgba(0,0,0,0.5); margin-bottom: 2rem; }
            .loading-bar-container { width: 50%; max-width: 600px; height: 20px; background-color: #2a2a30; border-radius: 10px; border: 1px solid #444; overflow: hidden; margin-bottom: 1rem; }
            .loading-bar-progress { width: 0%; height: 100%; background: linear-gradient(90deg, ${currentTheme.accent}, ${currentTheme.shadow1}); border-radius: 10px; transition: width 0.1s linear; }
            .loading-info { display: flex; justify-content: center; gap: 20px; font-size: 1.2rem; margin-bottom: 2rem; }
            .loading-message { font-style: italic; color: #9ca3af; min-height: 1.2em; }
            #trix-loading-settings-modal .settings-grid { display: grid; grid-template-columns: 1fr 2fr; gap: 1rem 2rem; align-items: center; margin-top: 2rem; }
            #trix-loading-settings-modal .settings-label { text-align: right; font-weight: bold; }
            #trix-loading-settings-modal select, #trix-loading-settings-modal input[type="checkbox"] { background: #2a2a30; color: white; border: 1px solid #444; padding: 5px; border-radius: 4px; }
            #trix-loading-settings-modal input[type="checkbox"] { width: 20px; height: 20px; }
            #trix-loading-settings-modal .trix-button { background-color: ${currentTheme.accent}; color: white; border: none; padding: 10px 15px; cursor: pointer; border-radius: 6px; flex-grow: 0; width: 200px; margin-top: 2rem; transition: filter 0.2s; font-weight: 600; }
            #trix-loading-settings-modal .trix-button:hover { filter: brightness(1.1); }
        `;
        document.documentElement.appendChild(style);
 
        const loadingScreen = document.createElement('div');
        loadingScreen.id = 'trix-loading-screen';
        const loadingMessages = [ "Disable all other extensions to reduce disconnections (Error 1009).", "the worst it can do is crash your game, right?", "Definitely not a cheating tool...", "did you know? I didnt know", "you can report any harmful scripts to me. (my discord: painsel)", "wanna break from the ads?", "I'm not sure how some features even work, if you're a developer pls help me (my discord: painsel)" ];
        loadingScreen.innerHTML = `
            <div class="loading-content">
                <div class="loading-header">TriX Executor</div>
                <div class="loading-bar-container"><div class="loading-bar-progress"></div></div>
                <div class="loading-info"><span class="loading-percentage">0.0%</span><span class="loading-timer">...s left</span></div>
                <div class="loading-message">${loadingMessages[0]}</div>
            </div>`;
        
        const settingsModal = document.createElement('div');
        settingsModal.id = 'trix-loading-settings-modal';
        settingsModal.style.visibility = 'hidden';
        settingsModal.innerHTML = `
            <div class="loading-content">
                <div class="loading-header">Settings</div>
                <div class="settings-grid">
                    <label class="settings-label" for="trix-theme-select">Theme:</label>
                    <select id="trix-theme-select">
                        <option value="vscode">VS Code (Default)</option>
                        <option value="retro">Retro 3D</option>
                        <option value="crimson">Crimson</option>
                    </select>
                </div>
                <button id="trix-resume-loading" class="trix-button">Resume</button>
            </div>`;
 
        document.documentElement.appendChild(loadingScreen);
        document.documentElement.appendChild(settingsModal);
 
        const themeSelect = settingsModal.querySelector('#trix-theme-select');
        themeSelect.value = settings.theme;
 
        const saveSettings = () => GM_setValue('trixSettings', settings);
 
        themeSelect.onchange = () => {
            settings.theme = themeSelect.value;
            saveSettings();
        };
        
        setTimeout(() => loadingScreen.classList.add('visible'), 10);
 
        const progressBar = loadingScreen.querySelector('.loading-bar-progress');
        const percentageText = loadingScreen.querySelector('.loading-percentage');
        const timerText = loadingScreen.querySelector('.loading-timer');
        const messageText = loadingScreen.querySelector('.loading-message');
        const loadingContent = loadingScreen.querySelector('.loading-content');
 
        const randomDuration = Math.random() * (50000 - 10000) + 10000;
        let startTime = null;
        let lastMessageChange = 0;
        let currentMessageIndex = 0;
        let isPaused = false;
        let pauseTime = 0;
        let animFrameId;
 
        function loadingLoop(timestamp) {
            if (!startTime) startTime = timestamp;
            if (isPaused) {
                animFrameId = requestAnimationFrame(loadingLoop);
                return;
            }
 
            const elapsed = timestamp - startTime;
            const progress = Math.min(elapsed / randomDuration, 1);
            
            progressBar.style.width = `${progress * 100}%`;
            percentageText.textContent = `${(progress * 100).toFixed(1)}%`;
            const secondsLeft = (randomDuration - elapsed) / 1000;
            timerText.textContent = `${Math.max(0, secondsLeft).toFixed(1)}s left`;
 
            if (elapsed > lastMessageChange + 4000) {
                lastMessageChange = elapsed;
                let newMessageIndex;
                do {
                    newMessageIndex = Math.floor(Math.random() * loadingMessages.length);
                } while (newMessageIndex === currentMessageIndex);
                currentMessageIndex = newMessageIndex;
                messageText.textContent = loadingMessages[currentMessageIndex];
            }
 
            if (progress < 1) {
                animFrameId = requestAnimationFrame(loadingLoop);
            } else {
                window.removeEventListener('keydown', f12Handler);
                percentageText.textContent = '100.0%';
                timerText.textContent = '0.0s left';
                setTimeout(() => {
                    loadingContent.classList.add('fade-out');
                    setTimeout(() => {
                        loadingScreen.classList.remove('visible');
                        settingsModal.classList.remove('visible');
                        setTimeout(() => {
                            loadingScreen.remove();
                            settingsModal.remove();
                            style.remove();
                        }, 1500);
                    }, 1000);
                }, 500);
            }
        }
 
        const f12Handler = (e) => {
            if (e.key === 'F12') {
                e.preventDefault();
                isPaused = true;
                pauseTime = performance.now();
                settingsModal.style.visibility = 'visible';
                settingsModal.classList.add('visible');
            }
        };
 
        settingsModal.querySelector('#trix-resume-loading').onclick = () => {
            settingsModal.classList.remove('visible');
            setTimeout(() => settingsModal.style.visibility = 'hidden', 500);
            startTime += (performance.now() - pauseTime);
            isPaused = false;
        };
 
        window.addEventListener('keydown', f12Handler);
        animFrameId = requestAnimationFrame(loadingLoop);
    }
 
    function run() {
        let loadCount = GM_getValue('trixLoadCount', 0);
        GM_setValue('trixLoadCount', loadCount + 1);
 
        if ((loadCount + 1) % 5 === 1) {
             showLoadingScreen();
        }
 
        waitForElement('#canvasA', initialize);
    }
 
    function waitForElement(selector, callback) {
        const observer = new MutationObserver((mutations, obs) => {
            if (document.querySelector(selector)) {
                obs.disconnect();
                callback();
            }
        });
        observer.observe(document.documentElement, {
            childList: true,
            subtree: true
        });
    }
 
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', run);
    } else {
        run();
    }
 
})();