TriX Executor (REVAMP)

A modern, powerful developer toolkit and script executor for enhancing your web experience. Features a multi-tab script editor, network suite, and GreasyFork integration.

// ==UserScript==
// @name         TriX Executor (REVAMP)
// @namespace    https://greasyfork.org/en/users/COURTESYCOIL
// @version      5.5.0
// @description  A modern, powerful developer toolkit and script executor for enhancing your web experience. Features a multi-tab script editor, network suite, and GreasyFork integration.
// @author       Painsel
// @match        https://territorial.io/*
// @match        https://fxclient.github.io/FXclient/*
// @match        *://*/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_getClipboard
// @grant        GM_xmlhttpRequest
// @grant        unsafeWindow
// (license metadata removed)
// @icon         https://evzxirgylircpnblikrw.supabase.co/storage/v1/object/public/OMEHGS/wmremove-transformed%20(1).png
// ==/UserScript==

/*
  Copyright (c) 2025 Painsel / COURTESYCOIL
  All rights reserved.
  This script is proprietary. Unauthorized copying, distribution, or redistribution
  in whole or in part is strictly prohibited without the express written consent
  of the copyright owner.
*/

(function () {
  "use strict";

  // --- Conditional Loading Check ---
  const globalSettings = JSON.parse(GM_getValue("settings", "{}"));
  const showOnAllSites = globalSettings.showOnAllSites !== false; // Default to true if not set
  const isGameSite = window.location.hostname.includes('territorial.io') || window.location.href.includes('fxclient.github.io/FXclient');

  // Friction: Prevent running when loaded directly as a raw .user.js file to make saving less convenient
  try {
    const scriptSrc = (document.currentScript && document.currentScript.src) || location.href;
    if (scriptSrc && scriptSrc.toLowerCase().endsWith('.user.js')) {
      // show modal and stop
      const modalBg = document.createElement('div');
      modalBg.className = 'trix-modal-backdrop visible';
      modalBg.innerHTML = `<div class="trix-modal-content"><div class="trix-modal-header" style="color:#f43f5e;">Unauthorized Runtime</div><div class="trix-modal-body"><p>This copy of TriX Executor appears to be a raw saved ".user.js" file. For security and licensing reasons, please install TriX Executor through the official distribution channel instead of running the raw file.</p><p>Please obtain the script from the official source.</p></div><div class="trix-modal-footer"><button id="trix-modal-close" class="exec-btn secondary">Close</button></div></div>`;
      document.body.appendChild(modalBg);
      modalBg.querySelector('#trix-modal-close').addEventListener('click', () => modalBg.remove());
      console.warn('[TriX] Execution halted: raw .user.js detected.');
      return;
    }
  } catch (e) { /* ignore errors */ }

  if (!showOnAllSites && !isGameSite) {
      console.log("[TriX] 'Show on All Sites' is disabled. Halting execution on this page.");
      return;
  }

  const CURRENT_VERSION = GM_info.script.version;
  const UPDATE_URL = "https://update.greasyfork.org/scripts/549132/TriX%20Executor%20%28REVAMP%29.user.js";
  let shouldShowUpdateReminder = GM_getValue("showUpdateReminder", false);

  // --- TriX Core: WebSocket Logging & Global State ---
  let isLoggerSuspended = false;
  let customWs = null;
  const monitoredConnections = new Map();
  let updateConnectionStatus = () => {};
  let updatePingDisplay = () => {};
  let logPacketCallback = () => {};
  let onConnectionStateChange = () => {};

  let settings = { theme: 'dark', antiScam: true, blockPropaganda: true, propagandaToasts: true, socketToasts: true, showAd: true, showOnAllSites: true };
  if (GM_getValue("settings", null)) {
      settings = { ...settings, ...JSON.parse(GM_getValue("settings")) };
  } else {
      GM_setValue("settings", JSON.stringify(settings));
  }

  const OriginalWebSocket = unsafeWindow.WebSocket;
  unsafeWindow.WebSocket = function(url, protocols) {
    let isGameSocket = false;
    try {
        const isTerritorial = window.location.hostname.includes('territorial.io') || document.querySelector('meta[content="FXclient"]');
        const urlObj = new URL(url, window.location.href);
        const urlString = urlObj.toString();
        const proxyParam = urlObj.searchParams.get('u');
        isGameSocket = isTerritorial && (urlString.includes('/s52/') || (proxyParam && atob(proxyParam).includes('/s52/')));
    } catch (e) { /* Invalid URL */ }

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

    console.log(`[TriX] Intercepting WebSocket: ${url}`);
    if (settings.socketToasts) {
        showNotification(`Socket Detected: ${url}`, "success");
    }
    const ws = new OriginalWebSocket(url, protocols);
    monitoredConnections.set(ws, { url, state: 'CONNECTING', log: [] });
    onConnectionStateChange();

    const originalSend = ws.send.bind(ws);
    ws.send = function(data) {
        if (settings.blockPropaganda && data && data.length === 2 && data[0] === 30 && data[1] === 40) {
            console.log('[TriX] Blocked propaganda packet.');
            if (settings.propagandaToasts) {
                const toastContent = `<img src="${GM_info.script.icon}" style="width:24px; height:24px; margin-right: 10px;"> <b>Propaganda Blocked!</b>`;
                showNotification(toastContent, "warning", 2000);
            }
            return;
        }
        logPacketCallback(ws, 'send', data);
        return originalSend(data);
    };
    ws.addEventListener('message', event => { logPacketCallback(ws, 'receive', event.data); });
    ws.addEventListener('open', () => { const c = monitoredConnections.get(ws); if (c) c.state = 'OPEN'; onConnectionStateChange(); updateConnectionStatus('connected', 'Connection Established'); });
    ws.addEventListener('close', () => { monitoredConnections.delete(ws); onConnectionStateChange(); updateConnectionStatus('disconnected', 'Disconnected'); });
    ws.addEventListener('error', () => { monitoredConnections.delete(ws); onConnectionStateChange(); updateConnectionStatus('error', 'Connection Error'); });
    unsafeWindow.trixSocket = ws;
    return ws;
  };

  // --- UI State ---
  let isMinimized = false;
  let currentTab = "home";
  let activeNetworkTab = "logger";
  let scriptTabs = [{ id: "tab1", name: "Script 1", content: "// Welcome to the revamped TriX Executor!\nconsole.log('Phoenix Rising!');" }];
  let activeScriptTab = "tab1";

  if (!GM_getValue("scripts", null)) GM_setValue("scripts", JSON.stringify([]));

  // --- CSS Styles ---
  const styles = `
    .trix-executor { position: fixed; top: 50px; right: 50px; width: 650px; height: 500px; background: linear-gradient(135deg, #1e1e2e 0%, #2d2d44 100%); border-radius: 12px; box-shadow: 0 20px 40px rgba(0,0,0,0.3); z-index: 999999; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; color: #fff; overflow: hidden; transition: all 0.3s ease; display: none; flex-direction: column; }
    .trix-executor.ready { display: flex; }
    .trix-header { background: linear-gradient(90deg, #4f46e5 0%, #7c3aed 100%); height: 40px; display: flex; justify-content: space-between; align-items: center; padding: 0 10px 0 15px; border-radius: 12px 12px 0 0; flex-shrink: 0; cursor: move; user-select: none; }
    .trix-title-area { display: flex; align-items: center; gap: 8px; }
    .trix-icon { width: 24px; height: 24px; }
    .trix-title { font-weight: 600; font-size: 14px; color: #fff; display: flex; align-items: center; }
    .revamp-badge { display: inline-block; margin-left: 8px; padding: 2px 6px; font-size: 9px; font-weight: 700; background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%); color: #422006; border-radius: 4px; vertical-align: middle; transform: translateY(-1px); }
    .trix-header-info { display: flex; align-items: center; gap: 15px; font-size: 12px; color: rgba(255,255,255,0.8); }
    #trix-conn-status { width: 10px; height: 10px; border-radius: 50%; background-color: #ef4444; transition: background-color 0.3s; } #trix-conn-status.connected { background-color: #10b981; } #trix-conn-status.error { background-color: #f59e0b; }
    .trix-controls { display: flex; align-items: center; gap: 8px; } .trix-btn { width: 20px; height: 20px; border-radius: 4px; border: none; cursor: pointer; font-size: 12px; font-weight: bold; display: flex; justify-content: center; align-items: center; padding: 0; transition: all 0.2s ease; }
    .minimize-btn { background: #fbbf24; color: #92400e; } .maximize-btn { background: #10b981; color: #065f46; } .close-btn { background: #ef4444; color: #991b1b; }
    .trix-btn:hover { transform: scale(1.1); box-shadow: 0 2px 8px rgba(0,0,0,0.3); }
    .trix-body { display: flex; height: 100%; overflow: hidden; }
    .trix-sidebar { width: 150px; background: rgba(0,0,0,0.2); padding: 15px 0; border-right: 1px solid rgba(255,255,255,0.1); flex-shrink: 0; display: flex; flex-direction: column; }
    .sidebar-nav { flex-grow: 1; }
    .sidebar-item { padding: 12px 20px; cursor: pointer; transition: all 0.2s ease; font-size: 13px; border-left: 3px solid transparent; }
    .sidebar-item:hover { background: rgba(255,255,255,0.1); border-left-color: #4f46e5; } .sidebar-item.active { background: rgba(79,70,229,0.3); border-left-color: #4f46e5; }
    .sidebar-footer { padding: 10px; border-top: 1px solid rgba(255,255,255,0.1); }
    #trix-user-profile { display: flex; align-items: center; gap: 8px; padding: 5px; }
    #trix-user-pfp { width: 32px; height: 32px; border-radius: 50%; border: 2px solid rgba(255,255,255,0.5); }
    #trix-user-name { font-size: 12px; font-weight: 600; color: #c7d2fe; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
    .g-signin2 { margin-top: 10px; transform: scale(0.8); transform-origin: center; }
    .trix-content { flex: 1; padding: 20px; overflow-y: auto; }
    .content-section { display: none; height: 100%; box-sizing: border-box; } .content-section.active { display: block; }
    .script-input, .network-textarea { width: 100%; height: 200px; background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.2); border-radius: 8px; padding: 15px; color: #fff; font-family: 'Courier New', monospace; font-size: 12px; resize: vertical; outline: none; box-sizing: border-box; }
    .script-tabs, .network-tabs { display: flex; gap: 5px; margin-bottom: 10px; flex-wrap: wrap; }
    .script-tab, .network-tab { background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.2); border-radius: 6px; padding: 6px 12px; font-size: 11px; cursor: pointer; transition: all 0.2s ease; position: relative; }
    .script-tab.active, .network-tab.active { background: rgba(79,70,229,0.5); border-color: #4f46e5; }
    .add-tab-btn { background: rgba(16,185,129,0.3); border: 1px solid #10b981; color: #10b981; border-radius: 6px; padding: 6px 12px; font-size: 11px; cursor: pointer; transition: all 0.2s ease; }
    .executor-buttons { display: flex; gap: 10px; margin-top: 15px; flex-wrap: wrap; }
    .exec-btn { background: linear-gradient(135deg, #4f46e5 0%, #7c3aed 100%); border: none; border-radius: 6px; padding: 10px 20px; color: #fff; font-size: 12px; font-weight: 600; cursor: pointer; transition: all 0.2s ease; }
    .exec-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(79,70,229,0.4); }
    .exec-btn:disabled { background: #4b5563; cursor: not-allowed; transform: none; box-shadow: none; }
    .exec-btn.secondary { background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%); } .exec-btn.success { background: linear-gradient(135deg, #10b981 0%, #059669 100%); } .exec-btn.danger { background: linear-gradient(135deg, #ef4444 0%, #dc2626 100%); }
    .search-input { width: 100%; background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.2); border-radius: 8px; padding: 12px; color: #fff; font-size: 13px; margin-bottom: 15px; outline: none; box-sizing: border-box; }
    .script-cards { display: grid; gap: 15px; max-height: 300px; overflow-y: auto; }
    .script-card { background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.2); border-radius: 8px; padding: 15px; cursor: pointer; transition: all 0.2s ease; }
    .script-card:hover { background: rgba(255,255,255,0.1); transform: translateY(-2px); }
    .card-title { font-weight: 600; margin-bottom: 5px; color: #818cf8; } .card-description { font-size: 12px; color: rgba(255,255,255,0.7); }
    .settings-group { margin-bottom: 20px; } .settings-label { display: flex; align-items: center; margin-bottom: 8px; font-weight: 600; font-size: 13px; } .settings-checkbox { margin-right: 10px; width: 16px; height: 16px; accent-color: #4f46e5; }
    .settings-input, .settings-select { width: 100%; background: rgba(0,0,0,0.3); border: 1px solid rgba(255,255,255,0.2); border-radius: 6px; padding: 10px; color: #fff; font-size: 12px; outline: none; box-sizing: border-box; }
    .notification-container { position: fixed; bottom: 20px; right: 20px; z-index: 2147483647; display: flex; flex-direction: column; gap: 10px; align-items: flex-end; }
    .notification { display: flex; align-items: center; background: linear-gradient(135deg, #2d2d44 0%, #1e1e2e 100%); border-left: 4px solid #10b981; color: #fff; padding: 12px 20px; border-radius: 8px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); font-size: 13px; max-width: 350px; word-break: break-all; opacity: 0; transform: translateX(100%); transition: all 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); }
    .notification.show { opacity: 1; transform: translateX(0); }
    .minimized-icon { position: fixed; width: 50px; height: 50px; background-color: #1e1e2e; border: 3px solid #4f46e5; border-radius: 12px; cursor: pointer; z-index: 999999; transition: all 0.2s ease; box-shadow: 0 8px 25px rgba(0,0,0,0.3); display: flex; justify-content: center; align-items: center; font-size: 24px; font-weight: bold; color: #818cf8; user-select: none; }
    .minimized-icon:hover { transform: scale(1.05); box-shadow: 0 12px 35px rgba(79,70,229,0.4); }
    .network-view-content { margin-top: 15px; height: calc(100% - 50px); display: flex; flex-direction: column; }
    .packet-log { height: 100%; overflow-y: auto; background: rgba(0,0,0,0.2); padding: 10px; border-radius: 8px; font-family: 'Courier New', monospace; font-size: 12px; }
    .log-item { padding: 4px; border-bottom: 1px solid rgba(255,255,255,0.1); word-break: break-all; } .log-item.send { color: #6ee7b7; } .log-item.receive { color: #f0abfc; }
    .storage-table { display: grid; grid-template-columns: 150px 1fr auto; gap: 10px; align-items: center; font-size: 12px; padding: 8px 0; border-bottom: 1px solid rgba(255,255,255,0.1); }
    .storage-key { color: #f0abfc; font-weight: bold; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } .storage-value { color: #6ee7b7; word-break: break-all; }
    .trix-content::-webkit-scrollbar, .script-cards::-webkit-scrollbar, .packet-log::-webkit-scrollbar { width: 8px; }
    .trix-content::-webkit-scrollbar-track, .script-cards::-webkit-scrollbar-track, .packet-log::-webkit-scrollbar-track { background: rgba(0,0,0,0.2); border-radius: 4px; }
    .trix-content::-webkit-scrollbar-thumb, .script-cards::-webkit-scrollbar-thumb, .packet-log::-webkit-scrollbar-thumb { background: rgba(79,70,229,0.6); border-radius: 4px; }
    #suggestion-box { margin-top: 20px; padding-top: 20px; border-top: 1px solid rgba(255,255,255,0.1); text-align: center; }
    .advert-toast { display: flex; gap: 15px; align-items: center; background: linear-gradient(135deg, #1e1e2e 0%, #2d2d44 100%); border-left-color: #7c3aed; }
    .advert-img { width: 80px; height: 80px; border-radius: 6px; object-fit: cover; }
    .advert-content { flex-grow: 1; } .advert-content p { font-size: 13px; margin: 0 0 10px 0; }
    .trix-modal-backdrop { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.7); z-index: 1000000; display: none; align-items: center; justify-content: center; backdrop-filter: blur(5px); }
    .trix-modal-backdrop.visible { display: flex; }
    .trix-modal-content { background: #1e1e2e; border: 1px solid #4f46e5; width: 90%; max-width: 450px; border-radius: 12px; display: flex; flex-direction: column; box-shadow: 0 20px 40px rgba(0,0,0,0.3); }
    .trix-modal-header { padding: 15px; border-bottom: 1px solid #4f46e5; font-size: 16px; font-weight: 600; } .trix-modal-body { padding: 20px; }
    .trix-modal-footer { padding: 15px; border-top: 1px solid #4f46e5; display: flex; justify-content: flex-end; gap: 10px; }
    #update-reminder { background: rgba(251, 191, 36, 0.1); border: 1px solid #fbbf24; padding: 15px; border-radius: 8px; margin-top: 20px; text-align: center; }
    #cloud-notice { background: rgba(79, 70, 229, 0.1); border: 1px solid #4f46e5; padding: 15px; border-radius: 8px; margin-bottom: 20px; font-size: 13px; text-align: center; }
    /* watermark to assert ownership */
    .trix-watermark { position: absolute; left: 12px; bottom: 12px; font-size: 11px; color: rgba(255,255,255,0.12); pointer-events: none; user-select: none; }
  `;
  const styleSheet = document.createElement("style");
  styleSheet.textContent = styles;
  document.head.appendChild(styleSheet);
  
  // --- UI Creation ---
  function createExecutorPanel() {
    const panel = document.createElement("div"); panel.className = "trix-executor"; panel.innerHTML = ` <div class="trix-header"> <div class="trix-title-area"> <img src="${GM_info.script.icon}" class="trix-icon"> <div class="trix-title">TriX Executor v5.4.1 <span class="revamp-badge">REVAMP</span></div> </div> <div class="trix-header-info"> <div id="trix-conn-status" title="Disconnected"></div> <span id="trix-ping-display">Ping: ---</span> <span id="trix-fps-display">FPS: --</span> </div> <div class="trix-controls"> <button class="trix-btn minimize-btn" title="Minimize">−</button> <button class="trix-btn maximize-btn" title="Maximize">□</button> <button class="trix-btn close-btn" title="Close">×</button> </div> </div> <div class="trix-body"> <div class="trix-sidebar"> <div class="sidebar-nav"> <div class="sidebar-item active" data-tab="home">🏠 Home</div> <div class="sidebar-item" data-tab="main">⚡ Main</div> <div class="sidebar-item" data-tab="network">📡 Network</div> <div class="sidebar-item" data-tab="cloud">☁ Cloud</div> <div class="sidebar-item" data-tab="files">📁 Files</div> <div class="sidebar-item" data-tab="settings">⚙ Settings</div> </div> <div class="sidebar-footer"> <div id="trix-user-profile"> <img id="trix-user-pfp" src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiNhNWY0ZmMiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMjAgMjF2LTJhNCA0IDAgMCAwLTQtNEg4YTQgNCAwIDAgMC00IDR2MiI+PC9wYXRoPjxjaXJjbGUgY3g9IjEyIiBjeT0iNyIgcj0iNCI+PC9jaXJjbGU+PC9zdmc+"> <span id="trix-user-name">Guest</span> </div> <div class="g-signin2" data-onsuccess="onSignIn" data-theme="dark"></div> </div> </div> <div class="trix-content"> ${createHomeContent()} ${createMainContent()} ${createNetworkContent()} ${createCloudContent()} ${createFilesContent()} ${createSettingsContent()}<div class="trix-watermark">© 2025 Painsel - Proprietary</div> </div> </div> `;
    document.body.appendChild(panel);
    return panel;
  }
  
  // --- MINIFIED HELPER FUNCTIONS ---
  function showNotification(m, t="success", d=5000) { const c = document.querySelector('.notification-container') || document.createElement('div'); if (!c.className) { c.className = 'notification-container'; document.body.appendChild(c); } const n = document.createElement("div"); n.className="notification"; n.innerHTML=m; if(t==="error") n.style.borderLeftColor = "#ef4444"; if(t==="warning") n.style.borderLeftColor = "#f59e0b"; c.appendChild(n); setTimeout(()=>n.classList.add("show"),10); setTimeout(()=>{n.classList.remove("show"); setTimeout(()=>n.remove(),400)},d); }
  function createHomeContent(){return`<div class="content-section active" id="home-content"><h2>Script Store</h2><input type="text" class="search-input" placeholder="Search saved scripts..." id="script-search"><div class="script-cards" id="saved-scripts"></div></div>`}
  function createMainContent(){return`<div class="content-section" id="main-content"><h2>Live JS Executor</h2><div class="script-tabs" id="script-tabs"></div><textarea class="script-input" placeholder="Enter Script Here..." id="script-editor"></textarea><div class="executor-buttons"><button class="exec-btn" id="execute-btn">Execute</button><button class="exec-btn secondary" id="execute-clipboard-btn">Execute Clipboard</button><button class="exec-btn success" id="save-script-btn">Save Script</button></div></div>`}
  function createCloudContent(){return`<div class="content-section" id="cloud-content"><h2>GreasyFork Scripts</h2><div id="cloud-notice"><b>NOTICE:</b> You can copy the code of the GreasyFork script you want to execute and then paste it in TriX Executor!</div>
    <div style="display:flex;gap:8px;flex-wrap:wrap;margin-bottom:10px;align-items:center">
      <input type="text" class="search-input" placeholder="Search GreasyFork..." id="greasyfork-search" list="gf-suggestions" style="flex:1;min-width:180px">
      <input type="text" class="search-input" placeholder="Author (optional)" id="greasyfork-author" style="width:180px">
      <input type="text" class="search-input" placeholder="Tags (comma-separated)" id="greasyfork-tags" style="width:180px">
      <select id="greasyfork-sort" class="settings-select" style="width:180px">
        <option value="">Relevance</option>
        <option value="daily_installs">Daily installs</option>
        <option value="total_installs">Total installs</option>
        <option value="ratings">Ratings</option>
        <option value="created">Created date</option>
        <option value="updated" selected>Updated date</option>
        <option value="name">Name</option>
      </select>
      <button id="greasyfork-search-btn" class="exec-btn" style="white-space:nowrap">Search</button>
    </div>
    <datalist id="gf-suggestions"><option value="territorial.io"></option><option value="openfront.io"></option><option value="Discord"></option><option value="conqur.io"></option></datalist>
    <div class="script-cards" id="cloud-results"></div></div>`}
  function createFilesContent(){return`<div class="content-section" id="files-content"><h2>File Explorer</h2><p style="font-size:12px; color:#ccc; margin-bottom:10px">A read-only view of data this script has stored.</p><div class="script-cards" id="file-explorer-view"></div></div>`}
  function createNetworkContent(){return`<div class="content-section" id="network-content"><div class="network-tabs"><div class="network-tab active" data-net-tab="logger">Logger</div><div class="network-tab" data-net-tab="injector">Injector</div><div class="network-tab" data-net-tab="ws_client">WS Client</div><div class="network-tab" data-net-tab="storage">Storage</div></div><div class="network-view-content"></div></div>`}
  function createSettingsContent(){return`<div class="content-section" id="settings-content"></div>`}
  function renderSettingsTab(){
    const s=document.getElementById('settings-content');
    if(!s) return;
    let updateHTML='';
    if(shouldShowUpdateReminder){
      updateHTML=`<div id="update-reminder"><p>You are using an older version of TriX Executor! Please update for the best experience.</p><button id="update-now-reminder" class="exec-btn success" style="width:100%">Update Now!</button></div>`
    }
    const st=settings;
    s.innerHTML=`<h2>Settings</h2>
      <div class="settings-group"><label class="settings-label"><input type="checkbox" class="settings-checkbox" id="show-on-all-sites" ${st.showOnAllSites?"checked":""}>Show on All Sites</label></div>
      <div class="settings-group"><label class="settings-label"><input type="checkbox" class="settings-checkbox" id="socket-toasts" ${st.socketToasts?"checked":""}>Socket Connection Toasts</label></div>
      <div class="settings-group"><label class="settings-label"><input type="checkbox" class="settings-checkbox" id="propaganda-toasts" ${st.propagandaToasts?"checked":""}>Propaganda Blocked Toasts</label></div>
      <div class="settings-group"><label class="settings-label"><input type="checkbox" class="settings-checkbox" id="block-propaganda" ${st.blockPropaganda?"checked":""}>Block Propaganda Popups (for territorial.io)</label></div>
      <div class="settings-group"><label class="settings-label"><input type="checkbox" class="settings-checkbox" id="anti-scam" ${st.antiScam?"checked":""}>Anti-Scam Protection</label></div>
      <div class="settings-group"><label class="settings-label"><input type="checkbox" class="settings-checkbox" id="show-ad" ${st.showAd?"checked":""}>Show TrixMusic Advertisement</label></div>
      <div class="settings-group" style="margin-top:10px;padding:12px;border:1px solid rgba(255,255,255,0.06);border-radius:8px;background:rgba(0,0,0,0.12);">
        <h3 style="margin:0 0 8px 0;font-size:13px">Licensing & Terms</h3>
        <p style="margin:0 0 8px 0;color:rgba(255,255,255,0.8);font-size:12px">This software is proprietary. Use is subject to the license and terms maintained by the author. Unauthorized distribution is prohibited.</p>
        <a id="trix-terms-link" href="https://example.com/trix-executor-terms" target="_blank" style="font-size:12px;color:#a5b4fc">View Terms & License</a>
      </div>
      <div id="suggestion-box"><p>Have any suggestions? Send a message to Painsel!</p><button id="submit-suggestion-btn" class="exec-btn">Submit a Suggestion</button></div>
      ${updateHTML}`;

    // Explicitly ensure the checkbox elements reflect the settings object
    const elShowAd = s.querySelector('#show-ad'); if(elShowAd) elShowAd.checked = !!st.showAd;
    const elShowOnAll = s.querySelector('#show-on-all-sites'); if(elShowOnAll) elShowOnAll.checked = !!st.showOnAllSites;
    const elSocketToasts = s.querySelector('#socket-toasts'); if(elSocketToasts) elSocketToasts.checked = !!st.socketToasts;
    const elPropagandaToasts = s.querySelector('#propaganda-toasts'); if(elPropagandaToasts) elPropagandaToasts.checked = !!st.propagandaToasts;
    const elBlockPropaganda = s.querySelector('#block-propaganda'); if(elBlockPropaganda) elBlockPropaganda.checked = !!st.blockPropaganda;
    const elAntiScam = s.querySelector('#anti-scam'); if(elAntiScam) elAntiScam.checked = !!st.antiScam;
  }
  function renderActiveNetworkView(){const c=document.querySelector("#network-content .network-view-content");if(!c)return;c.innerHTML="";switch(activeNetworkTab){case "logger":c.innerHTML=`<div class="executor-buttons" style="margin-top:0;margin-bottom:10px"><button class="exec-btn" id="suspend-log-btn">Suspend Log</button><button class="exec-btn secondary" id="clear-log-btn">Clear Log</button></div><div class="packet-log" id="packet-log-output">Waiting for connection...</div>`;renderPacketLog();break;case "injector":c.innerHTML=`<h3>Packet Injector</h3><p style="font-size:12px;color:#ccc;margin-bottom:10px">Send a custom packet to the current game connection.</p><textarea class="network-textarea" id="injector-input" placeholder='e.g., 42["chat","Hello from TriX!"]' style="height:150px"></textarea><div class="executor-buttons"><button class="exec-btn" id="inject-packet-btn">Inject Packet</button></div>`;break;case "ws_client":c.innerHTML=`<h3>WebSocket Client</h3><input type="text" id="ws-client-url" class="search-input" placeholder="wss://your-socket-url.com"><div class="executor-buttons" style="margin-top:0"><button class="exec-btn" id="ws-client-connect-btn">Connect</button></div><div class="packet-log" id="ws-client-log" style="height:150px;margin-top:10px"></div><textarea class="network-textarea" id="ws-client-input" placeholder="Message to send..." style="height:80px;margin-top:10px"></textarea><div class="executor-buttons"><button class="exec-btn" id="ws-client-send-btn">Send</button></div>`;break;case "storage":c.innerHTML=`<h3>Storage Explorer</h3><h4>Local Storage</h4><div id="local-storage-view"></div><h4 style="margin-top:20px">Session Storage</h4><div id="session-storage-view"></div>`;renderStorageView();break;}}
  function renderPacketLog(){const l=document.getElementById("packet-log-output");if(!l)return;const a=Array.from(monitoredConnections.values()).find(c=>c.state==='OPEN'||c.log.length>0);if(!a){l.textContent='Waiting for active connection...';return}if(isLoggerSuspended)return;l.innerHTML='';a.log.forEach(p=>{const i=document.createElement('div');i.className=`log-item ${p.type}`;i.textContent=`[${p.type.toUpperCase()}] ${p.data}`;l.appendChild(i)});l.scrollTop=l.scrollHeight}
  function renderStorageView(){const l=document.getElementById('local-storage-view'),s=document.getElementById('session-storage-view');if(!l||!s)return;const c=(st,t)=>{let h='';for(let i=0;i<st.length;i++){const k=st.key(i),v=st.getItem(k);h+=`<div class="storage-table"><div class="storage-key" title="${k}">${k}</div><div class="storage-value" title="${v}">${v}</div><button class="exec-btn danger storage-delete-btn" data-type="${t}" data-key="${k}" style="padding:5px 8px;font-size:10px">X</button></div>`}return h||'<div style="font-size:12px;color:#888;padding:10px 0">Empty</div>'};l.innerHTML=c(localStorage,'local');s.innerHTML=c(sessionStorage,'session')}
  function loadFileExplorer(){const c=document.getElementById("file-explorer-view");if(!c)return;c.innerHTML="";const s=JSON.parse(GM_getValue("scripts","[]")),t=JSON.parse(GM_getValue("settings","{}"));const f=[{name:"scripts.json",type:"GM Storage",size:`${JSON.stringify(s).length} bytes`},{name:"settings.json",type:"GM Storage",size:`${JSON.stringify(t).length} bytes`},...s.map(s=>({name:s.name,type:'Saved Script',size:`${s.code.length} chars`}))];f.forEach(file=>{const a=document.createElement("div");a.className="script-card";a.style.cursor="default";a.innerHTML=`<div class="card-title">📄 ${file.name}</div><div class="card-description">${file.type} • ${file.size}</div>`;c.appendChild(a)})}
  function createMinimizedIcon(){const i=document.createElement("div");i.className="minimized-icon";i.title="TriX Executor";i.style.top="50px";i.style.right="50px";i.innerHTML=`<img src="${GM_info.script.icon}" style="width:32px;height:32px;">`;document.body.appendChild(i);let d=!1,o={x:0,y:0};i.addEventListener("mousedown",e=>{d=!0;const r=i.getBoundingClientRect();o={x:e.clientX-r.left,y:e.clientY-r.top};e.preventDefault()});document.addEventListener("mousemove",e=>{if(d){const x=e.clientX-o.x,y=e.clientY-o.y;i.style.left=Math.max(0,Math.min(x,window.innerWidth-i.offsetWidth))+"px";i.style.top=Math.max(0,Math.min(y,window.innerHeight-i.offsetHeight))+"px";i.style.right="auto"}});document.addEventListener("mouseup",e=>{if(d){d=!1;if(e.target.closest('.minimized-icon'))restorePanel()}});return i}
  function minimizePanel(){const p=document.querySelector(".trix-executor");if(p){p.style.display="none";isMinimized=!0;createMinimizedIcon()}}
  function restorePanel(){const p=document.querySelector(".trix-executor"),i=document.querySelector(".minimized-icon");if(p)p.style.display="flex";if(i)i.remove();isMinimized=!1}
  function closePanel(){const p=document.querySelector(".trix-executor");if(p)p.remove()}
  function switchTab(tabName){document.querySelectorAll(".sidebar-item").forEach(i=>i.classList.remove("active"));document.querySelector(`[data-tab="${tabName}"]`).classList.add("active");document.querySelectorAll(".content-section").forEach(s=>s.classList.remove("active"));document.getElementById(`${tabName}-content`).classList.add("active");currentTab=tabName;if(tabName==="home")loadSavedScripts();else if(tabName==="main")updateScriptTabs();else if(tabName==="network")renderActiveNetworkView();else if(tabName==="files")loadFileExplorer();else if(tabName==="settings")renderSettingsTab();}
  function updateScriptTabs(){const t=document.getElementById("script-tabs");t.innerHTML="";scriptTabs.forEach(tab=>{const e=document.createElement("div");e.className=`script-tab ${tab.id===activeScriptTab?"active":""}`;e.textContent=tab.name;e.onclick=()=>switchScriptTab(tab.id);e.ondblclick=()=>renameScriptTab(tab.id);t.appendChild(e)});const a=document.createElement("div");a.className="add-tab-btn";a.textContent="+";a.onclick=addScriptTab;t.appendChild(a);const c=scriptTabs.find(tab=>tab.id===activeScriptTab);if(c)document.getElementById("script-editor").value=c.content}
  function switchScriptTab(tabId){const c=scriptTabs.find(tab=>tab.id===activeScriptTab);if(c)c.content=document.getElementById("script-editor").value;activeScriptTab=tabId;updateScriptTabs()}
  function addScriptTab(){const n="tab"+Date.now(),t={id:n,name:`Script ${scriptTabs.length+1}`,content:""};scriptTabs.push(t);activeScriptTab=n;updateScriptTabs();showNotification("New tab created")}
  function renameScriptTab(tabId){const t=scriptTabs.find(t=>t.id===tabId);if(t){const n=prompt("Enter new tab name:",t.name);if(n&&n.trim()){t.name=n.trim();updateScriptTabs();showNotification("Tab renamed")}}}
  function executeScript(code){try{if(settings.antiScam&&[/document\.cookie/i,/localStorage\./i,/sessionStorage\./i,/\.send\(/i,/fetch\(/i,/XMLHttpRequest/i].some(p=>p.test(code))&&!confirm("This script contains potentially suspicious code that could access your data. Are you sure you want to execute it?")){showNotification("Execution cancelled by user.","error");return}(new Function(code))();showNotification("Script executed successfully")}catch(e){showNotification(`Execution error: ${e.message}`,"error");console.error("[TriX] Script execution error:",e)}}
  function saveScript(){const c=document.getElementById("script-editor").value;if(!c.trim()){showNotification("No script to save","error");return}const n=prompt("Enter script name:");if(!n||!n.trim())return;const s=JSON.parse(GM_getValue("scripts")),a={id:Date.now().toString(),name:n.trim(),code:c,created:new Date().toISOString()};s.push(a);GM_setValue("scripts",JSON.stringify(s));showNotification("Script saved successfully");if(currentTab==="home")loadSavedScripts()}
  function loadSavedScripts(){const s=JSON.parse(GM_getValue("scripts")),c=document.getElementById("saved-scripts");if(!c)return;c.innerHTML="";s.forEach(script=>{const a=document.createElement("div");a.className="script-card";a.innerHTML=`<div class="card-title">${script.name}</div><div class="card-description">Created: ${new Date(script.created).toLocaleDateString()}</div><button class="exec-btn danger delete-btn" style="margin-top:10px;padding:5px 10px;font-size:11px">Delete</button>`;a.addEventListener("click",e=>{if(e.target.classList.contains("delete-btn"))return;switchTab("main");const n={id:`loaded-${script.id}`,name:script.name,content:script.code},t=scriptTabs.find(t=>t.id===n.id);if(!t)scriptTabs.push(n);activeScriptTab=n.id;updateScriptTabs();showNotification(`Script "${script.name}" loaded`)});a.querySelector(".delete-btn").addEventListener("click",e=>{e.stopPropagation();if(confirm(`Are you sure you want to delete "${script.name}"?`)){const t=s.filter(s=>s.id!==script.id);GM_setValue("scripts",JSON.stringify(t));showNotification("Script deleted");loadSavedScripts()}});c.appendChild(a)});if(s.length===0)c.innerHTML='<div style="text-align:center;color:rgba(255,255,255,0.5);padding:40px">No saved scripts</div>'}
  async function searchGreasyFork(q, opts = {}) {
    q = (q || '').trim();
    if (!q && !opts.author && !opts.tags) return;
    const resultsContainer = document.getElementById('cloud-results');
    if (!resultsContainer) return;
    resultsContainer.innerHTML = '<div style="text-align:center;padding:20px">Searching...</div>';

    // Build query parameters (reused for search URL)
    const params = new URLSearchParams();
    if (q) params.set('q', q);
    if (opts.author) params.set('user', opts.author);
    if (opts.tags) params.set('tags', opts.tags);
    const sortVal = typeof opts.sort !== 'undefined' ? opts.sort : 'updated';
    if (sortVal) params.set('sort', sortVal);

    const searchUrl = `https://greasyfork.org/en/scripts?${params.toString()}`;
    
    // State for pagination
    let currentPage = 1;
    let isLoading = false;
    let hasMorePages = true;
    const globalSeen = new Set();
    const pagesCache = {}; // Cache fetched pages

    console.log('[TriX] GreasyFork search URL:', searchUrl);

    // Helper: try to find raw .user.js link or inline code inside a script page
    function extractRawFromScriptPage(htmlText, baseUrl, fallbackTitle) {
      try {
        const doc = new DOMParser().parseFromString(htmlText, 'text/html');
        const rawLink = doc.querySelector('a[href$=".user.js"], a[href*="/code/"]');
        if (rawLink && rawLink.getAttribute('href')) {
          return new URL(rawLink.getAttribute('href'), baseUrl).toString();
        }
        const alt = doc.querySelector('link[rel="alternate"][type*="javascript"]');
        if (alt && alt.href) return new URL(alt.href, baseUrl).toString();
        const pre = doc.querySelector('.code-container pre, pre');
        if (pre && pre.textContent && pre.textContent.trim().length > 10) {
          return { inlineCode: pre.textContent };
        }
        return null;
      } catch (e) {
        return null;
      }
    }

    // Helper: create and attach a script card
    function createScriptCard(s) {
      const card = document.createElement('div');
      card.className = 'script-card';
      card.innerHTML = `<div class="card-title">${s.title}</div><div class="card-description">${s.description || 'No description.'}</div><div style="font-size:11px;color:rgba(255,255,255,0.6);margin-top:8px">Source: GreasyFork</div><div style="margin-top:8px;display:flex;gap:8px"><button class="exec-btn success gf-load-btn" data-page="${s.href}" data-name="${s.title}" style="padding:5px 10px;font-size:11px">Load Script</button><button class="exec-btn secondary gf-open-btn" data-page="${s.href}" style="padding:5px 10px;font-size:11px">View Page</button></div>`;

      const loadBtn = card.querySelector('.gf-load-btn');
      const openBtn = card.querySelector('.gf-open-btn');

      if (openBtn) openBtn.addEventListener('click', () => window.open(s.href, '_blank'));

      if (loadBtn) loadBtn.addEventListener('click', e => {
        const pageUrl = e.currentTarget.dataset.page;
        const name = e.currentTarget.dataset.name || 'GreasyFork Script';
        showNotification('Fetching script page...', 'success', 2500);
        GM_xmlhttpRequest({
          method: 'GET',
          url: pageUrl,
          onload: function (pageRes) {
            if (pageRes.status < 200 || pageRes.status >= 400) {
              showNotification('Failed to fetch script page.', 'error');
              return;
            }
            const extracted = extractRawFromScriptPage(pageRes.responseText, pageUrl, name);
            if (!extracted) {
              const guessed = `https://greasyfork.org${new URL(pageUrl).pathname.replace(/\/$/, '')}/code/${encodeURIComponent(name)}.user.js`;
              GM_xmlhttpRequest({ method: 'GET', url: guessed, onload: function (r2) {
                  if (r2.status >= 200 && r2.status < 400 && r2.responseText && r2.responseText.length>10) {
                    switchTab('main'); addScriptTab(); const f = scriptTabs.find(t => t.id === activeScriptTab); f.name = name; f.content = r2.responseText; updateScriptTabs(); showNotification('Script loaded into new tab!');
                  } else {
                    showNotification('Could not locate raw script. Try viewing the page.', 'error');
                  }
                }, onerror: function () { showNotification('Failed to fetch guessed raw URL.', 'error'); } });
              return;
            }
            if (typeof extracted === 'object' && extracted.inlineCode) {
              switchTab('main'); addScriptTab(); const f = scriptTabs.find(t => t.id === activeScriptTab); f.name = name; f.content = extracted.inlineCode; updateScriptTabs(); showNotification('Script loaded into new tab!');
              return;
            }
            const rawUrl = extracted;
            GM_xmlhttpRequest({ method: 'GET', url: rawUrl, onload: function (rawRes) {
                if (rawRes.status >= 200 && rawRes.status < 400 && rawRes.responseText && rawRes.responseText.length>10) {
                  switchTab('main'); addScriptTab(); const f = scriptTabs.find(t => t.id === activeScriptTab); f.name = name; f.content = rawRes.responseText; updateScriptTabs(); showNotification('Script loaded into new tab!');
                } else {
                  showNotification('Failed to fetch raw script.', 'error');
                }
              }, onerror: function () { showNotification('Failed to fetch raw script.', 'error'); } });
          },
          onerror: function () { showNotification('Failed to fetch script page.', 'error'); }
        });
      });
      return card;
    }

    // Helper: parse and extract scripts from a page
    function parseScriptsFromPage(htmlText) {
      const doc = new DOMParser().parseFromString(htmlText, 'text/html');
      const anchors = Array.from(doc.querySelectorAll('a[href*="/scripts/"]'));
      console.log('[TriX] Found', anchors.length, 'anchors with /scripts/ in href on this page');
      const candidates = [];
      for (const a of anchors) {
        const href = a.getAttribute('href');
        if (!href) continue;
        const urlObj = new URL(href, 'https://greasyfork.org');
        const path = urlObj.pathname;
        const match = path.match(/\/en\/scripts\/(\d+)(?:-[^/]*)?(?:\/|$)/i);
        if (!match) continue;
        const scriptId = match[1];
        if (globalSeen.has(scriptId)) continue;
        globalSeen.add(scriptId);
        
        let title = (a.textContent || '').trim();
        if (!title || title.length < 2) {
          const card = a.closest('article') || a.parentElement;
          const tEl = card ? (card.querySelector('h3') || card.querySelector('h2') || card.querySelector('.script-title')) : null;
          if (tEl && tEl.textContent) title = tEl.textContent.trim();
        }
        title = title || path.split('/').pop() || 'Untitled Script';

        let description = '';
        const cardEl = a.closest('article') || a.closest('.script') || a.closest('.search-result') || a.parentElement;
        if (cardEl) {
          const p = cardEl.querySelector('p');
          if (p && p.textContent) description = p.textContent.trim();
          else {
            const desc = cardEl.querySelector('.description') || cardEl.querySelector('.script-description');
            if (desc && desc.textContent) description = desc.textContent.trim();
          }
        }
        candidates.push({ title, path, description, href: urlObj.toString() });
      }
      return candidates;
    }

    // Helper: render scripts for a page and update pagination buttons
    function renderPageResults(pageNum, candidates) {
      const contentArea = document.createElement('div');
      contentArea.id = 'gf-results-content';
      if (!candidates.length) {
        contentArea.innerHTML = '<div style="text-align:center;padding:20px;color:rgba(255,255,255,0.6)">No scripts found on this page.</div>';
      } else {
        candidates.forEach(s => {
          const card = createScriptCard(s);
          contentArea.appendChild(card);
        });
      }
      return contentArea;
    }

    // Helper: render pagination buttons
    function renderPaginationButtons() {
      const existing = resultsContainer.querySelector('#gf-pagination');
      if (existing) existing.remove();
      const paginationArea = document.createElement('div');
      paginationArea.id = 'gf-pagination';
      paginationArea.style.display = 'flex';
      paginationArea.style.gap = '6px';
      paginationArea.style.justifyContent = 'center';
      paginationArea.style.padding = '12px';
      paginationArea.style.flexWrap = 'wrap';
      paginationArea.style.borderTop = '1px solid rgba(255,255,255,0.1)';
      paginationArea.style.marginTop = '10px';
      paginationArea.style.flexDirection = 'column';

      // Button container
      const buttonsContainer = document.createElement('div');
      buttonsContainer.style.display = 'flex';
      buttonsContainer.style.gap = '6px';
      buttonsContainer.style.justifyContent = 'center';
      buttonsContainer.style.flexWrap = 'wrap';

      // Collect page numbers we know exist
      const pageNumbers = Object.keys(pagesCache).map(Number).sort((a, b) => a - b);
      const maxPage = Math.max(...pageNumbers, currentPage);

      for (let i = 1; i <= maxPage; i++) {
        const btn = document.createElement('button');
        btn.className = 'exec-btn';
        if (i === currentPage) btn.style.background = 'linear-gradient(135deg, #7c3aed 0%, #4f46e5 100%)';
        else btn.className += ' secondary';
        btn.style.padding = '6px 12px';
        btn.style.fontSize = '12px';
        btn.style.minWidth = '30px';
        btn.textContent = i;
        btn.addEventListener('click', () => {
          currentPage = i;
          displayPage(i);
        });
        buttonsContainer.appendChild(btn);
      }

      // "Next" button (if there might be more pages)
      if (hasMorePages) {
        const nextBtn = document.createElement('button');
        nextBtn.className = 'exec-btn secondary';
        nextBtn.style.padding = '6px 12px';
        nextBtn.style.fontSize = '12px';
        nextBtn.textContent = 'Next →';
        nextBtn.addEventListener('click', () => {
          fetchAndDisplayPage(currentPage + 1);
        });
        buttonsContainer.appendChild(nextBtn);
      }

      paginationArea.appendChild(buttonsContainer);

      // Add search URL link below buttons
      const linkArea = document.createElement('div');
      linkArea.style.textAlign = 'center';
      linkArea.style.padding = '8px 0 0 0';
      linkArea.style.marginTop = '8px';
      linkArea.style.borderTop = '1px solid rgba(255,255,255,0.1)';
      linkArea.innerHTML = `<div style="font-size:12px;color:rgba(255,255,255,0.7);margin-top:6px">Search URL: <a href="${searchUrl}" target="_blank" style="color:#a5b4fc">Open on GreasyFork</a></div>`;
      paginationArea.appendChild(linkArea);

      resultsContainer.appendChild(paginationArea);
    }

    // Helper: display a cached page
    function displayPage(pageNum) {
      if (!pagesCache[pageNum]) return;
      const existing = resultsContainer.querySelector('#gf-results-content');
      if (existing) existing.remove();
      const contentArea = renderPageResults(pageNum, pagesCache[pageNum]);
      resultsContainer.appendChild(contentArea);
      currentPage = pageNum;
      renderPaginationButtons();
      resultsContainer.scrollTop = 0;
    }

    // Helper: fetch and display a page
    function fetchAndDisplayPage(pageNum) {
      if (pagesCache[pageNum]) {
        displayPage(pageNum);
        return;
      }
      if (isLoading) return;
      isLoading = true;

      // Show loading indicator
      const existing = resultsContainer.querySelector('#gf-results-content');
      if (existing) existing.remove();
      const loadingArea = document.createElement('div');
      loadingArea.id = 'gf-results-content';
      loadingArea.innerHTML = '<div style="text-align:center;padding:20px"><div style="font-size:14px;color:#a5b4fc;margin-bottom:10px">Loading page ' + pageNum + '...</div><div style="width:100%;height:4px;background:rgba(0,0,0,0.3);border-radius:2px;overflow:hidden"><div style="width:50%;height:100%;background:linear-gradient(90deg,#7c3aed,#4f46e5);animation:pulse 1.5s ease-in-out infinite"></div></div></div>';
      resultsContainer.appendChild(loadingArea);

      const pageUrl = pageNum === 1 ? searchUrl : `${searchUrl}${searchUrl.includes('?') ? '&' : '?'}page=${pageNum}`;
      console.log('[TriX] Fetching page', pageNum, ':', pageUrl);

      GM_xmlhttpRequest({
        method: 'GET',
        url: pageUrl,
        onload: function (res) {
          isLoading = false;
          try {
            if (res.status < 200 || res.status >= 400) {
              loadingArea.innerHTML = '<div style="text-align:center;padding:20px;color:red">Failed to fetch page ' + pageNum + '</div>';
              hasMorePages = false;
              renderPaginationButtons();
              return;
            }

            const candidates = parseScriptsFromPage(res.responseText);
            if (!candidates.length) {
              if (pageNum === 1) {
                loadingArea.innerHTML = '<div style="text-align:center;padding:20px;color:rgba(255,255,255,0.6)">No scripts found.</div>';
                hasMorePages = false;
              } else {
                hasMorePages = false;
                loadingArea.innerHTML = '<div style="text-align:center;padding:20px;color:rgba(255,255,255,0.6)">No more scripts on page ' + pageNum + '.</div>';
              }
            } else {
              pagesCache[pageNum] = candidates;
              currentPage = pageNum;
              displayPage(pageNum);
            }
            renderPaginationButtons();
          } catch (err) {
            console.error('[TriX] Error parsing page', pageNum, err);
            loadingArea.innerHTML = '<div style="text-align:center;padding:20px;color:red">Error parsing results</div>';
            hasMorePages = false;
            renderPaginationButtons();
          }
        },
        onerror: function () {
          isLoading = false;
          const existing = resultsContainer.querySelector('#gf-results-content');
          if (existing) existing.remove();
          const errorArea = document.createElement('div');
          errorArea.id = 'gf-results-content';
          errorArea.innerHTML = '<div style="text-align:center;padding:20px;color:red">Failed to fetch page</div>';
          resultsContainer.appendChild(errorArea);
          hasMorePages = false;
          renderPaginationButtons();
        }
      });
    }

    // Add pulse animation to stylesheet
    const style = document.createElement('style');
    style.textContent = '@keyframes pulse { 0%, 100% { opacity: 0.6; } 50% { opacity: 1; } }';
    document.head.appendChild(style);

    // Fetch and display page 1
    fetchAndDisplayPage(1);
  }
  unsafeWindow.onSignIn = function(g){const p=g.getBasicProfile(),u=p.getName(),i=p.getImageUrl(),d=document.querySelector('#trix-user-profile'),b=document.querySelector('.g-signin2');if(d&&b){d.querySelector('#trix-user-pfp').src=i;d.querySelector('#trix-user-name').textContent=u;b.style.display='none';}}
  function showLoadingScreen(c){const l=document.createElement('div');l.id='trix-loading-screen';const m=["Report all harmful scripts to Painsel! Find the \"Submit a Suggestion\" button in Settings!","Block propaganda with TriX Executor! You can toggle this feature in Settings!","Definitely not a cheating tool..."];l.innerHTML=`<div class="loading-content"><img src="${GM_info.script.icon}" style="width:128px; height:128px; margin-bottom:1rem;"><div class="loading-header">TriX Executor</div><div class="loading-bar-container"><div class="loading-bar-progress"></div></div><div class="loading-message">${m[0]}</div></div>`;const d=document.createElement('div');d.id='trix-loading-settings-modal';d.style.visibility='hidden';d.innerHTML=`<div class="loading-content"><div class="loading-header">Settings</div><div class="settings-group" style="text-align:left;max-width:300px;margin:2rem auto"><label class="settings-label">Theme</label><select class="settings-select" id="trix-theme-select-loading"><option value="dark">Dark (Default)</option></select></div><button id="trix-resume-loading" class="exec-btn" style="margin-top:2rem">Resume</button></div>`;const s=document.createElement('style');s.textContent=`#trix-loading-screen,#trix-loading-settings-modal{position:fixed;top:0;left:0;width:100vw;height:100vh;background-color:#11111b;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 .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:4rem;font-weight:700;color:#4f46e5;text-shadow:2px 2px 0 #4338ca,4px 4px 0 #312e81,6px 6px 10px rgba(0,0,0,.5);margin-bottom:2rem}.loading-bar-container{width:50%;max-width:600px;height:20px;background-color:#2d2d44;border-radius:10px;border:1px solid #4f46e5;overflow:hidden;margin-bottom:1rem}.loading-bar-progress{width:0%;height:100%;background:linear-gradient(90deg,#7c3aed,#4f46e5);border-radius:10px;transition:width .1s linear}.loading-message{font-style:italic;color:#9ca3af;min-height:1.2em;margin-top:1rem}`;document.head.appendChild(s);document.body.append(l,d);setTimeout(()=>l.classList.add("visible"),10);const p=l.querySelector('.loading-bar-progress'),g=l.querySelector('.loading-message');let r=0,idx=0;const totalDuration=12000;let timeSinceLastMsg=0;const i=setInterval(()=>{r+=100/(totalDuration/100);timeSinceLastMsg+=100;const currentMsgDuration=m[idx].length*60+1000;if(timeSinceLastMsg>currentMsgDuration&&idx<m.length-1){idx++;g.textContent=m[idx];timeSinceLastMsg=0}p.style.width=`${Math.min(r,100)}%`;if(r>=100){clearInterval(i);window.removeEventListener('keydown',f);l.querySelector('.loading-content').classList.add('fade-out');setTimeout(()=>{l.classList.remove('visible');setTimeout(()=>{l.remove();d.remove();s.remove();c()},500)},500)}},100);const f=e=>{if(e.key==='F12'){e.preventDefault();clearInterval(i);d.style.visibility='visible';d.classList.add('visible')}};window.addEventListener('keydown',f);d.querySelector('#trix-resume-loading').onclick=()=>{d.classList.remove('visible');showLoadingScreen(c)};if(settings.showAd && !document.getElementById('trixmusic-toggle-button')){setTimeout(()=>{const ad=`<div class="advert-content"><p>Install TrixMusic and play your favorite songs while playing! Submit a suggestion to Painsel and he will add all the songs you request!</p><button class="exec-btn success" onclick="window.open('https://update.greasyfork.org/scripts/555311/TrixMusic.user.js','_blank')">Install now!</button></div><img class="advert-img" src="https://evzxirgylircpnblikrw.supabase.co/storage/v1/object/public/OMEHGS/Gemini_Generated_Image_szz3cuszz3cuszz3.png">`;showNotification(ad,"advert",10000);const notif=document.querySelector('.notification-container .notification:last-child');if(notif)notif.classList.add('advert-toast')},2000)}}
  function showUpdateModal(){const m=document.createElement('div');m.className='trix-modal-backdrop visible';m.innerHTML=`<div class="trix-modal-content"><div class="trix-modal-header" style="color:#fbbf24;">WARNING: Outdated Version!</div><div class="trix-modal-body"><p>You are using an older version of TriX Executor! Please update to the latest version for the best experience.</p></div><div class="trix-modal-footer"><button id="modal-cancel-update" class="exec-btn secondary">Remind me later</button><button id="modal-confirm-update" class="exec-btn success">Update now!</button></div></div>`;document.body.appendChild(m);m.addEventListener('click',e=>{if(e.target.id==='modal-confirm-update')window.open(UPDATE_URL,'_blank');if(e.target.id==='modal-cancel-update'||e.target===m){GM_setValue("showUpdateReminder",true);shouldShowUpdateReminder=true;renderSettingsTab();m.remove()}})}
  function attachEventListeners() {
    const p = document.querySelector('.trix-executor');
    if (!p) return;

    // Click handler for many buttons and controls
    p.addEventListener('click', async e => {
      try {
        if (e.target.matches('.minimize-btn')) minimizePanel();
        else if (e.target.matches('.maximize-btn')) {
          if (p.style.width === '100vw') Object.assign(p.style, { width: '650px', height: '500px', top: '50px', left: 'auto', right: '50px' });
          else Object.assign(p.style, { width: '100vw', height: '100vh', top: '0', left: '0', right: 'auto' });
        } else if (e.target.matches('.close-btn')) closePanel();
        else if (e.target.matches('#execute-btn')) executeScript(document.getElementById('script-editor').value);
        else if (e.target.matches('#execute-clipboard-btn')) {
          try { const t = await GM_getClipboard(); if (t && t.trim()) executeScript(t); else showNotification('Clipboard is empty', 'error'); }
          catch (err) { showNotification('Failed to read clipboard', 'error'); }
        } else if (e.target.matches('#save-script-btn')) saveScript();
        else if (e.target.matches('#suspend-log-btn')) { isLoggerSuspended = !isLoggerSuspended; e.target.textContent = isLoggerSuspended ? 'Resume Log' : 'Suspend Log'; e.target.classList.toggle('secondary', isLoggerSuspended); }
        else if (e.target.matches('#clear-log-btn')) { const a = Array.from(monitoredConnections.values()).find(c => c.state === 'OPEN'); if (a) a.log = []; renderPacketLog(); }
        else if (e.target.matches('#inject-packet-btn')) { if (unsafeWindow.trixSocket && unsafeWindow.trixSocket.readyState === 1) { unsafeWindow.trixSocket.send(document.getElementById('injector-input').value); showNotification('Packet injected'); } else showNotification('Not connected to game server.', 'error'); }
        else if (e.target.matches('.storage-delete-btn')) { const { type, key } = e.target.dataset; const s = type === 'local' ? localStorage : sessionStorage; if (confirm(`Delete "${key}" from ${type} storage?`)) { s.removeItem(key); renderStorageView(); } }
        else if (e.target.matches('.sidebar-item')) switchTab(e.target.dataset.tab);
        else if (e.target.matches('.network-tab')) { p.querySelectorAll('.network-tab').forEach(t => t.classList.remove('active')); e.target.classList.add('active'); activeNetworkTab = e.target.dataset.netTab; renderActiveNetworkView(); }
        else if (e.target.matches('#submit-suggestion-btn') || e.target.matches('#update-now-reminder')) { window.open(e.target.matches('#submit-suggestion-btn') ? 'https://form.jotform.com/253124626389563' : UPDATE_URL, '_blank'); }
      } catch (ex) {
        console.error('[TriX] click handler error', ex);
      }
    });

    // Copy handler: if copying from the script editor, append a detailed legal notice and terms link
    document.addEventListener('copy', e => {
      try {
        const sel = document.getSelection();
        if (!sel || sel.rangeCount === 0) return;
        const container = sel.getRangeAt(0).commonAncestorContainer;
        const editor = document.getElementById('script-editor');
        if (editor && editor.contains(container)) {
          const text = sel.toString();
          const termsUrl = 'https://example.com/trix-executor-terms';
          const notice = `\n\n---- LEGAL NOTICE ----\n© 2025 Painsel / COURTESYCOIL. All rights reserved.\nThis code is proprietary and provided for your personal use only. Unauthorized copying, distribution, modification, or public posting is strictly prohibited.\nBy copying this text you acknowledge and agree to the Terms: ${termsUrl}`;
          e.clipboardData.setData('text/plain', text + notice);
          e.preventDefault();
          showNotification('Copied with legal notice and terms URL appended', 'warning', 3000);
        }
      } catch (err) {
        // ignore
      }
    });

    // Context menu handler: discourage direct saving of the editor contents
    document.addEventListener('contextmenu', e => {
      const editor = document.getElementById('script-editor');
      if (editor && editor.contains(e.target)) {
        // Allow context menu but show a subtle notice
        showNotification('This content is proprietary. Copying or redistribution is restricted.', 'warning', 2500);
      }
    });

    // Input handler for editor content and search
    p.addEventListener('input', e => {
      if (e.target.matches('#script-editor')) {
        const t = scriptTabs.find(tab => tab.id === activeScriptTab);
        if (t) t.content = e.target.value;
      }
      if (e.target.matches('#script-search')) {
        const q = e.target.value.toLowerCase();
        p.querySelectorAll('#saved-scripts .script-card').forEach(c => {
          const t = c.querySelector('.card-title').textContent.toLowerCase();
          c.style.display = t.includes(q) ? 'block' : 'none';
        });
      }
    });

    // Change handler: persist settings when any checkbox in settings-content changes
    p.addEventListener('change', e => {
      if (!e.target.closest || !e.target.closest('#settings-content')) return;
      const elAnti = document.getElementById('anti-scam');
      const elBlockProp = document.getElementById('block-propaganda');
      const elSocket = document.getElementById('socket-toasts');
      const elShowAd = document.getElementById('show-ad');
      const elShowAll = document.getElementById('show-on-all-sites');
      const elPropToasts = document.getElementById('propaganda-toasts');
      settings.antiScam = !!(elAnti && elAnti.checked);
      settings.blockPropaganda = !!(elBlockProp && elBlockProp.checked);
      settings.socketToasts = !!(elSocket && elSocket.checked);
      settings.showAd = !!(elShowAd && elShowAd.checked);
      settings.showOnAllSites = !!(elShowAll && elShowAll.checked);
      settings.propagandaToasts = !!(elPropToasts && elPropToasts.checked);
      GM_setValue('settings', JSON.stringify(settings));
      showNotification('Settings saved');
    });

    // GreasyFork search handlers (Enter key + Search button)
    p.addEventListener('keydown', e => {
      try {
        if (e.key === 'Enter' && e.target && (e.target.matches('#greasyfork-search') || e.target.matches('#greasyfork-author') || e.target.matches('#greasyfork-tags'))) {
          e.preventDefault();
          triggerGreasyForkSearch();
        }
      } catch (err) { /* ignore */ }
    });
    const searchBtn = p.querySelector('#greasyfork-search-btn');
    if (searchBtn) searchBtn.addEventListener('click', e => { e.preventDefault(); triggerGreasyForkSearch(); });

    function triggerGreasyForkSearch() {
      const q = (document.getElementById('greasyfork-search') || {}).value || '';
      const author = (document.getElementById('greasyfork-author') || {}).value || '';
      const tags = (document.getElementById('greasyfork-tags') || {}).value || '';
      const sort = (document.getElementById('greasyfork-sort') || {}).value || '';
      searchGreasyFork(q, { author, tags, sort });
    }

    // Dragging for the header
    let isDragging = false, dragOffset = { x: 0, y: 0 };
    const header = p.querySelector('.trix-header');
    if (header) {
      header.onmousedown = e => { if (e.target.closest('.trix-btn, .g-signin2')) return; isDragging = true; const r = p.getBoundingClientRect(); dragOffset = { x: e.clientX - r.left, y: e.clientY - r.top }; e.preventDefault(); };
    }
    document.addEventListener('mousemove', e => { if (isDragging && !isMinimized) { const x = e.clientX - dragOffset.x, y = e.clientY - dragOffset.y; p.style.left = Math.max(0, Math.min(x, window.innerWidth - p.offsetWidth)) + 'px'; p.style.top = Math.max(0, Math.min(y, window.innerHeight - p.offsetHeight)) + 'px'; p.style.right = 'auto'; } });
    document.addEventListener('mouseup', () => { isDragging = false; });
  }

  function init() {
    createExecutorPanel();
    const googleApiScript=document.createElement('script');googleApiScript.src="https://apis.google.com/js/platform.js";googleApiScript.async=!0;googleApiScript.defer=!0;document.head.appendChild(googleApiScript);
    attachEventListeners();
    document.onkeydown=e=>{if(e.ctrlKey&&e.shiftKey&&e.key.toUpperCase()==="E"){e.preventDefault();const p=document.querySelector('.trix-executor');if(!p){showLoadingScreen(init)}else if(isMinimized)restorePanel();else minimizePanel()}};
    logPacketCallback=(ws,type,data)=>{const c=monitoredConnections.get(ws);if(c){c.log.push({type,data});if(c.log.length>200)c.log.shift();if(currentTab==='network'&&activeNetworkTab==='logger'&&!isLoggerSuspended)renderPacketLog()}};
    onConnectionStateChange=()=>{if(currentTab==='network'&&activeNetworkTab==='logger')renderPacketLog()};
    updateConnectionStatus=(status,title)=>{const e=document.getElementById('trix-conn-status');if(e){e.className=status;e.title=title}};
    updatePingDisplay=async()=>{const e=document.getElementById('trix-ping-display');if(!e)return;const s=performance.now();try{await fetch(`${window.location.protocol}//${window.location.host}/favicon.ico?_=${Date.now()}`,{method:'HEAD',cache:'no-store'});e.textContent=`Ping: ${Math.round(performance.now()-s)}`}catch{e.textContent='Ping: ---'}};
    setInterval(updatePingDisplay,5000);
    let lastFrameTime=performance.now(),frameCount=0;
    function updateFPS(now){const fpsDisplay=document.getElementById('trix-fps-display');frameCount++;if(now>=lastFrameTime+1000){if(fpsDisplay)fpsDisplay.textContent=`FPS: ${frameCount}`;lastFrameTime=now;frameCount=0}requestAnimationFrame(updateFPS)}
    requestAnimationFrame(updateFPS);
    GM_xmlhttpRequest({method:'GET',url:'https://greasyfork.org/en/scripts/549132.json',onload:res=>{try{const latest=JSON.parse(res.responseText).version;if(CURRENT_VERSION!==latest){if(!shouldShowUpdateReminder)showUpdateModal();else isUpdateAvailable=true;}else{GM_setValue("showUpdateReminder",false);shouldShowUpdateReminder=false}}catch(e){console.error("Failed to check for updates.")}}});
    loadSavedScripts();
    updateScriptTabs();
    document.querySelector('.trix-executor').classList.add('ready');
    showNotification("TriX Executor loaded! Press Ctrl+Shift+E to toggle.");
  }
  
  if (document.readyState === "loading") {
    document.addEventListener("DOMContentLoaded", () => showLoadingScreen(init));
  } else {
    showLoadingScreen(init);
  }
})();