TriX Executor (BETA) for Territorial.io

Work in progress...

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

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         TriX Executor (BETA) for Territorial.io
// @namespace    https://greasyfork.org/en/users/COURTESYCOIL
// @version      Beta-Indus-2024.09.07a
// @description  Work in progress...
// @author       Painsel
// @match        *://territorial.io/*
// @require      https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/prism.min.js
// @require      https://cdnjs.cloudflare.com/ajax/libs/prism/1.29.0/components/prism-javascript.min.js
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        unsafeWindow
// @run-at       document-start
// @license      MIT
// @icon         data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0iIzAwYWFmZiI+PHBhdGggZD0iTTE4LjM2IDIyLjA1bC00LjQyLTQuNDJDMTMuNDcgMTcuODcgMTMgMTguMTcgMTMgMTguNUMxMyAyMC45OSAxNS4wMSAyMyAxNy41IDIzaDMuNTRjLTIuNDUtMS40OC00LjQyLTMuNDUtNS5LTUuOTV6TTggMTNjMS42NiAwIDMuMTgtLjU5IDQuMzgtMS42MkwxMCAxMy41VjIybDIuNS0yLjVMMTggMTMuOTZjLjM1LS40OC42NS0xIC44Ny0xLjU1QzE4LjYxIDEzLjQxIDE4IDEyLjM0IDE4IDEyVjBoLTJ2MTJjMCAuMzQtLjAyLjY3LS4wNiAxLS4zMy4xOC0uNjguMy0xLjA0LjM3LTEuNzMgMC0zLjI3LS45My00LjE2LTIuMzZMNiAxMy42VjVINXY4eiIvPjwvc3ZnPg==
// ==/UserScript==

/* global Prism, unsafeWindow */

(function() {
    'use strict';

    // --- Global State for Advanced Features ---
    let interceptionEnabled = false;
    let isLoggerSuspended = false;
    let packetLogHistory = [];
    let queuedMessages = new Map();
    let renderInterceptorQueue = () => {};
    let customWs = null;

    let updateConnectionStatus = () => {};
    let updatePingDisplay = () => {};
    let logPacketCallback = () => {};

    // --- WebSocket Proxy Setup ---
    const OriginalWebSocket = unsafeWindow.WebSocket;
    unsafeWindow.WebSocket = function(url, protocols) {
        if (!url.includes('/s52/')) { return new OriginalWebSocket(url, protocols); }
        console.log(`[TriX] Intercepting WebSocket connection to: ${url}`);
        const ws = new OriginalWebSocket(url, protocols);
        let originalOnMessageHandler = null;
        const originalSend = ws.send.bind(ws);
        ws.send = function(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('send', `[Forwarded] ${decision.data}`);
                    } else { logPacketCallback('send', `[Blocked] ${data}`); }
                });
            } else { logPacketCallback('send', data); return originalSend(data); }
        };
        Object.defineProperty(ws, 'onmessage', {
            get: () => originalOnMessageHandler,
            set: (handler) => {
                originalOnMessageHandler = handler;
                ws.addEventListener('message', event => {
                    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('receive', `[Forwarded] ${decision.data}`);
                                if (originalOnMessageHandler) originalOnMessageHandler({ data: decision.data });
                            } else { logPacketCallback('receive', `[Blocked] ${event.data}`); }
                        });
                    } else { logPacketCallback('receive', event.data); if (originalOnMessageHandler) originalOnMessageHandler(event); }
                });
            },
            configurable: true
        });
        ws.addEventListener('open', () => updateConnectionStatus('connected', 'Connection established.'));
        ws.addEventListener('close', (event) => {
            if (unsafeWindow.webSocketManager && unsafeWindow.webSocketManager.activeSocket === ws) unsafeWindow.webSocketManager.activeSocket = null;
            updateConnectionStatus('disconnected', `Disconnected. Code: ${event.code}`);
            updatePingDisplay('---');
        });
        ws.addEventListener('error', () => 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';
        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}
            
            /* --- Refined UI Theme --- */
            :host { --accent-color: #3b82f6; --bg-darker: #1e1e22; --bg-dark: #2a2a30; --border-color: #444; }
            @keyframes trix-fade-in { from { opacity: 0; transform: scale(0.95); } to { opacity: 1; transform: scale(1); } }
            @keyframes trix-slide-in { from { transform: translateX(100%); opacity: 0; } to { transform: translateX(0); opacity: 1; } }
            
            #trix-toggle-btn { position: fixed; top: 15px; right: 15px; z-index: 99999; width: 50px; height: 50px; background-color: rgba(30, 30, 34, 0.8); color: #00aaff; border: 2px solid #00aaff; border-radius: 50%; cursor: pointer; display: flex; align-items: center; justify-content: center; font-size: 24px; transition: all 0.3s ease; backdrop-filter: blur(5px); font-family: monospace; box-shadow: 0 0 10px rgba(0, 170, 255, 0.5); }
            #trix-toggle-btn:hover { transform: scale(1.1) rotate(15deg); box-shadow: 0 0 15px #00aaff; }

            #trix-container { position: fixed; top: 80px; right: 15px; width: 500px; min-height: 450px; z-index: 99998; color: #e5e7eb; font-family: 'Segoe UI', 'Roboto', sans-serif; border-radius: 10px; overflow: hidden; box-shadow: 0 5px 25px rgba(0,0,0,0.5); display: flex; flex-direction: column; backdrop-filter: blur(10px); animation: trix-slide-in 0.5s cubic-bezier(0.25, 0.46, 0.45, 0.94) forwards; resize: both; 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: #9ca3af; 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: #e5e7eb; 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: #9ca3af; 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; }
            .trix-button:hover { filter: brightness(1.1); }
            .trix-button:disabled { background-color: #555; cursor: not-allowed; filter: 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: #2d2d2d; border: 1px solid var(--border-color); color: #ccc; 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: #d1d5db; 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: start; }
            #trix-ws-url { width: 100%; }
            #trix-ws-connect-btn.connected { background-color: #dc3545; }
            #trix-ws-console { flex-grow: 1; background: #1e1e22; 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: #1e1e22; 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: #2d2d2d; color: #ccc; 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, #trix-storage-view-container, #trix-script-injector-container { flex-grow: 1; display: flex; flex-direction: column; }
            #trix-packet-log { flex-grow: 1; background: #1e1e22; border: 1px solid var(--border-color); border-radius: 5px; padding: 10px; overflow-y: auto; 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: #3a3a42; border: 1px solid #555; color: #ccc; 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 { color: #f08d49; }

            .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: #3a3a42; color: #eee; 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: #e5e7eb; 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: 0 5px 15px rgba(0,0,0,0.3); 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: #9ca3af; }
            .music-progress-container { width: 100%; background: #4b5563; 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: #9ca3af; }
            .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: #e5e7eb; 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: #4b5563; 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: #e5e7eb; 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, .modal-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, .modal-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, .modal-body { overflow-y: auto; padding: 10px; }
            .modal-body pre { white-space: pre-wrap; word-break: break-all; margin: 0; }
            .music-song-item { 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: #9ca3af; }
        `;
        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: '', 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'} }
        };

        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 };
            }
            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>`;
            
            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>`;

            shadowRoot.append(toggleBtn, container, musicToggle, musicPlayer, musicMenu, modal);
            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-grid">
                            <input type="text" id="trix-ws-url" class="trix-input" 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>
                        </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();
            } 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"></div>
                        <div class="trix-action-bar">
                             <button id="trix-suspend-log-btn" class="trix-button">Suspend Log</button>
                             <button id="trix-clear-log-btn" class="trix-button">Clear Log</button>
                        </div>
                    </div>`;
                renderTabs();
                const suspendBtn = $('#trix-suspend-log-btn');
                if (suspendBtn && isLoggerSuspended) {
                    suspendBtn.classList.add('suspended');
                    suspendBtn.textContent = 'Resume Log';
                }
                renderFullPacketLog();
            } 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 = () => { logToWsClientConsole('Connection closed.', 'system'); connectBtn.textContent = 'Connect'; connectBtn.classList.remove('connected'); connectBtn.disabled = false; customWs = null; };
            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');
                }
            });
        }

        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) { 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') {
                const log = $('#trix-packet-log');
                if (log) log.innerHTML = '';
                packetLogHistory = []; // Clear the history array
            }
            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);
                logPacketCallback('inject', 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 === '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 renderFullPacketLog() {
            const logContainer = $('#trix-packet-log');
            if (!logContainer) return;
            logContainer.innerHTML = '';
            const fragment = document.createDocumentFragment();
            packetLogHistory.forEach(packet => {
                fragment.appendChild(createPacketElement(packet));
            });
            logContainer.appendChild(fragment);
            logContainer.scrollTop = logContainer.scrollHeight;
        }

        logPacketCallback = function(type, data) {
            const packet = { type, data };
            packetLogHistory.push(packet);

            if (state.activeTabId === 'logger' && !isLoggerSuspended) {
                const logContainer = $('#trix-packet-log');
                if (logContainer) {
                    const item = createPacketElement(packet);
                    logContainer.appendChild(item);
                    logContainer.scrollTop = logContainer.scrollHeight;
                }
            }
        };
        
        loadState();
        ui = createUI();
        applySettings();
        renderActiveView();
        initEventListeners();
        
        const musicPlayer = initMusicPlayer();
        initDraggable(ui.musicPlayer, [$('.music-drag-handle', ui.musicPlayer)]);

        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);
    }

    function initMusicPlayer() {
        const player = {
            audio: document.createElement('audio'),
            songList: [
                { 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" }
            ],
            isPlaying: false,
            currentSongIndex: 0,
            volume: 0.5,
            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) => {
            player.currentSongIndex = index;
            const song = player.songList[index];
            player.audio.src = song.url;
            player.audio.loop = player.loop;
            titleEl.textContent = song.title;
            artistEl.textContent = song.artist;
            renderSongList();
            updateSpecialEffects();
        };
        
        const playSong = () => {
            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 = player.currentSongIndex + 1; if (newIndex >= player.songList.length) newIndex = 0; loadSong(newIndex); playSong(); };
        const prevSong = () => { let newIndex = player.currentSongIndex - 1; if (newIndex < 0) newIndex = player.songList.length - 1; loadSong(newIndex); playSong(); };

        const setVolume = (value) => {
            player.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);
        };
        
        const renderSongList = (filter = '') => {
            songListEl.innerHTML = '';
            const filteredList = player.songList.filter(song => song.title.toLowerCase().includes(filter) || song.artist.toLowerCase().includes(filter));
            filteredList.forEach((song) => {
                const originalIndex = player.songList.indexOf(song);
                const item = document.createElement('div');
                item.className = 'music-song-item';
                if (originalIndex === player.currentSongIndex) item.classList.add('playing');
                item.innerHTML = `<span class="music-song-title">${song.title}</span><span class="music-song-artist">${song.artist}</span>`;
                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(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'); });

        // Initialize UI states
        setVolume(player.volume);
        autoplayBtn.classList.toggle('active', player.autoplay);
        loadSong(player.currentSongIndex);
        renderSongList();
        
        return player;
    }

    function showLoadingScreen(callback) {
        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-percentage">0.0%</div>
                <div class="loading-message">${loadingMessages[0]}</div>
            </div>`;
        
        // Append styles directly here to ensure they are active immediately at document-start
        const style = document.createElement('style');
        style.textContent = `
            #trix-loading-screen { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background-color: #000; z-index: 999999; display: flex; flex-direction: column; align-items: center; justify-content: center; color: #fff; font-family: 'Segoe UI', sans-serif; opacity: 0; transition: opacity 1.5s ease-in-out; }
            #trix-loading-screen.visible { opacity: 1; }
            .loading-content { opacity: 1; transition: opacity 1s ease-in-out; }
            .loading-content.fade-out { opacity: 0; }
            .loading-header { font-size: 5rem; font-weight: bold; color: #00aaff; text-shadow: 2px 2px 0px #0077cc, 4px 4px 0px #0055aa, 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, #00aaff, #3b82f6); border-radius: 10px; transition: width 0.1s linear; }
            .loading-percentage { font-size: 1.2rem; margin-bottom: 2rem; }
            .loading-message { font-style: italic; color: #9ca3af; min-height: 1.2em; }
        `;
        document.head.appendChild(style);
        document.body.appendChild(loadingScreen);
        
        setTimeout(() => loadingScreen.classList.add('visible'), 10);

        const progressBar = loadingScreen.querySelector('.loading-bar-progress');
        const percentageText = loadingScreen.querySelector('.loading-percentage');
        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;

        function loadingLoop(timestamp) {
            if (!startTime) startTime = timestamp;
            const elapsed = timestamp - startTime;
            const progress = Math.min(elapsed / randomDuration, 1);
            
            progressBar.style.width = `${progress * 100}%`;
            percentageText.textContent = `${(progress * 100).toFixed(1)}%`;

            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) {
                requestAnimationFrame(loadingLoop);
            } else {
                percentageText.textContent = '100.0%';
                setTimeout(() => {
                    loadingContent.classList.add('fade-out');
                    setTimeout(() => {
                        loadingScreen.classList.remove('visible');
                        setTimeout(() => {
                            loadingScreen.remove();
                            style.remove();
                            callback();
                        }, 1500);
                    }, 1000);
                }, 500);
            }
        }
        requestAnimationFrame(loadingLoop);
    }

    function run() {
        showLoadingScreen(() => {
            waitForElement('#canvasA', initialize);
        });
    }

    function waitForElement(selector, callback) {
        const check = () => {
            if (document.querySelector(selector)) {
                callback();
            } else {
                setTimeout(check, 100);
            }
        };
        check();
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', run);
    } else {
        run();
    }

})();