TrixBox

TriX Executor's ChatBox (for territorial.io)!

Pada tanggal 18 November 2025. Lihat %(latest_version_link).

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         TrixBox
// @namespace    http://tampermonkey.net/
// @version      0.7.7
// @description  TriX Executor's ChatBox (for territorial.io)!
// @author       Painsel
// @match        https://territorial.io/*
// @match        https://fxclient.github.io/FXclient/*
// @grant        GM_xmlhttpRequest
// @grant        GM_info
// ==/UserScript==

(function() {
    'use strict';

    const CHATTABLE_HEADER_HEIGHT = 45; // pixels

    // --- Theme Colors (for UI elements outside the iframe) ---
    const theme = {
        icon: '#2f3136',
        modalBg: '#2f3136',
        text: '#dcddde',
        buttonPrimary: '#5865f2',
        buttonSecondary: '#4f545c'
    };
 
    const SCRIPT_UPDATE_URL = 'https://update.greasyfork.org/scripts/555536/TrixBox.meta.js';
    const SCRIPT_INSTALL_URL = 'https://update.greasyfork.org/scripts/555536/TrixBox.user.js';
 
    // --- 1. UPDATE CHECKING LOGIC ---
 
    const showUpdateModal = () => {
        const modalStyle = `
            .trixbox-modal-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 100000; display: flex; align-items: center; justify-content: center; }
            .trixbox-modal-content { background: ${theme.modalBg}; color: ${theme.text}; padding: 25px; border-radius: 10px; text-align: center; font-family: sans-serif; box-shadow: 0 5px 15px rgba(0,0,0,0.5); }
            .trixbox-modal-content h2 { margin-top: 0; }
            .trixbox-modal-content p { margin: 15px 0; }
            .trixbox-modal-btn { color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; margin: 0 10px; font-size: 14px; }
            .trixbox-modal-btn.primary { background: ${theme.buttonPrimary}; }
            .trixbox-modal-btn.secondary { background: ${theme.buttonSecondary}; }
        `;
        const styleSheet = document.createElement("style");
        styleSheet.innerText = modalStyle;
        document.head.appendChild(styleSheet);
 
        const modalOverlay = document.createElement('div');
        modalOverlay.className = 'trixbox-modal-overlay';
        modalOverlay.innerHTML = `
            <div class="trixbox-modal-content">
                <h2>OUTDATED VERSION</h2>
                <p>You are using an Outdated version of TrixBox.<br>Please update for the best experience!</p>
                <button id="trixbox-update-btn" class="trixbox-modal-btn primary">Update Now!</button>
                <button id="trixbox-later-btn" class="trixbox-modal-btn secondary">Remind Me Later!</button>
            </div>
        `;
        document.body.appendChild(modalOverlay);
 
        document.getElementById('trixbox-update-btn').onclick = () => { window.location.href = SCRIPT_INSTALL_URL; };
        document.getElementById('trixbox-later-btn').onclick = () => { modalOverlay.remove(); };
    };
 
    const checkForUpdates = () => {
        try {
            const localVersion = GM_info.script.version;
            const lastSeenVersion = localStorage.getItem('trixbox-last-version');
            
            // Show changelog if version has changed
            if (lastSeenVersion !== localVersion) {
                localStorage.setItem('trixbox-last-version', localVersion);
                setTimeout(showChangelog, 500);
            }
            
            GM_xmlhttpRequest({
                method: 'GET',
                url: SCRIPT_UPDATE_URL,
                onload: function(response) {
                    if (response.status !== 200) return;
                    const remoteVersionMatch = response.responseText.match(/@version\s+([0-9.]+)/);
                    if (remoteVersionMatch && remoteVersionMatch[1]) {
                        if (remoteVersionMatch[1] > localVersion) {
                            showUpdateModal();
                        }
                    }
                }
            });
        } catch (e) { console.error('TrixBox: Update check failed.', e); }
    };

    const showChangelog = () => {
        const modalStyle = `
            .trixbox-changelog-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 100002; display: flex; align-items: center; justify-content: center; }
            .trixbox-changelog-content { background: ${theme.modalBg}; color: ${theme.text}; padding: 25px; border-radius: 10px; font-family: sans-serif; box-shadow: 0 5px 15px rgba(0,0,0,0.5); width: 400px; max-height: 70vh; overflow-y: auto; }
            .trixbox-changelog-content h2 { margin-top: 0; text-align: center; color: ${theme.buttonPrimary}; }
            .trixbox-changelog-section { margin: 15px 0; }
            .trixbox-changelog-version { font-weight: bold; color: ${theme.buttonPrimary}; margin-bottom: 8px; }
            .trixbox-changelog-list { list-style: none; padding-left: 0; margin: 5px 0; }
            .trixbox-changelog-list li { margin: 5px 0; padding-left: 20px; position: relative; }
            .trixbox-changelog-list li:before { content: "✓"; position: absolute; left: 0; color: ${theme.buttonPrimary}; }
            .trixbox-changelog-btn { width: 100%; color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; margin-top: 15px; font-size: 14px; background: ${theme.buttonPrimary}; }
            .trixbox-changelog-btn:hover { opacity: 0.9; }
        `;
        const styleSheet = document.createElement("style");
        styleSheet.innerText = modalStyle;
        document.head.appendChild(styleSheet);

        const overlay = document.createElement('div');
        overlay.className = 'trixbox-changelog-overlay';
        overlay.innerHTML = `
            <div class="trixbox-changelog-content">
                <h2>What's New in v0.6.7</h2>
                <div class="trixbox-changelog-section">
                    <div class="trixbox-changelog-version">v0.6.7 - Configurable Settings</div>
                    <ul class="trixbox-changelog-list">
                        <li>Configurable hotkey for disabling chat focus in main settings</li>
                        <li>New TrixBox settings header with toggle controls</li>
                        <li>Click-outside toggle for New TrixBox window</li>
                        <li>Persistent settings via localStorage</li>
                        <li>Enhanced settings modals with better UX</li>
                    </ul>
                </div>
                <div class="trixbox-changelog-section">
                    <div class="trixbox-changelog-version">Previous - v0.6.4 Features</div>
                    <ul class="trixbox-changelog-list">
                        <li>TrixBox Voice Chat - Triple chat system with voice support</li>
                        <li>Auto-launch voice chat connection via postMessage API</li>
                        <li>Microphone and camera permissions enabled</li>
                        <li>Enhanced UI with three chat selection options</li>
                        <li>Mobile optimizations with navbar-safe positioning</li>
                    </ul>
                </div>
                <button class="trixbox-changelog-btn">Got it!</button>
            </div>
        `;
        document.body.appendChild(overlay);

        overlay.querySelector('button').addEventListener('click', () => {
            overlay.remove();
        });

        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) {
                overlay.remove();
            }
        });
    };
 
    // --- 2. CREATE THE TOGGLE ICON ---
    const toggleIcon = document.createElement('div');
    toggleIcon.id = 'trixbox-toggle-icon';
    toggleIcon.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="${theme.text}" width="28px" height="28px"><path d="M20 2H4c-1.1 0-2 .9-2 2v18l4-4h14c1.1 0 2-.9 2-2V4c0-1.1-.9-2-2-2z"/></svg>`;
    Object.assign(toggleIcon.style, {
        backgroundColor: theme.icon,
        position: 'fixed', bottom: '20px', right: '20px', width: '50px', height: '50px',
        borderRadius: '50%', display: 'flex', alignItems: 'center',
        justifyContent: 'center', cursor: 'pointer', zIndex: '99998',
        boxShadow: '0 2px 8px rgba(0,0,0,0.3)', transition: 'transform 0.2s ease-in-out'
    });
    toggleIcon.onmouseover = () => { toggleIcon.style.transform = 'scale(1.1)'; };
    toggleIcon.onmouseout = () => { toggleIcon.style.transform = 'scale(1.0)'; };
    document.body.appendChild(toggleIcon);

    // --- 3. CREATE THE CHATBOX ---
    const chatContainer = document.createElement('div');
    chatContainer.id = 'trixbox-container';
    Object.assign(chatContainer.style, {
        position: 'fixed', bottom: 'auto', top: '70px', right: '15px', width: '350px', height: '500px',
        zIndex: '99999', display: 'none', flexDirection: 'column',
        boxShadow: '0 4px 12px rgba(0,0,0,0.3)', borderRadius: '8px', overflow: 'hidden',
        touchAction: 'manipulation', WebkitUserSelect: 'none'
    });

    const dragHeader = document.createElement('div');
    dragHeader.id = 'trixbox-header';
    Object.assign(dragHeader.style, {
        height: '30px', backgroundColor: 'rgb(210, 210, 255)', cursor: 'move',
        userSelect: 'none', display: 'flex', alignItems: 'center',
        justifyContent: 'space-between', padding: '0 5px 0 15px',
        position: 'relative', zIndex: '1'
    });

    const headerTitle = document.createElement('span');
    headerTitle.textContent = 'TrixBox Chat';
    Object.assign(headerTitle.style, {
        color: 'white', fontWeight: 'bold', fontSize: '14px',
        textShadow: '1px 1px 0 rgb(160, 160, 230), 2px 2px 0 rgba(0, 0, 0, 0.15)'
    });
    dragHeader.appendChild(headerTitle);

    const headerButtons = document.createElement('div');
    headerButtons.style.display = 'flex';
    headerButtons.style.alignItems = 'center';

    // Connection status indicator
    const connectionStatus = document.createElement('div');
    connectionStatus.id = 'trixbox-connection-status';
    Object.assign(connectionStatus.style, {
        width: '12px', height: '12px', borderRadius: '50%', backgroundColor: '#888888',
        marginRight: '8px', cursor: 'pointer', transition: 'background-color 0.3s'
    });
    connectionStatus.title = 'Loading...';
    headerButtons.appendChild(connectionStatus);

    const roomSelector = document.createElement('select');
    roomSelector.id = 'room-selector';
    roomSelector.className = 'top-bar-dropdown';
    Object.assign(roomSelector.style, {
        padding: '4px 8px', marginRight: '8px', backgroundColor: 'rgb(220, 220, 240)',
        color: 'rgb(80, 80, 120)', border: '1px solid rgb(160, 160, 200)',
        borderRadius: '3px', cursor: 'pointer', fontSize: '12px', fontWeight: 'bold'
    });
    
    const generalOption = document.createElement('option');
    generalOption.value = '15234533';
    generalOption.textContent = 'General';
    roomSelector.appendChild(generalOption);
    
    const loungeOption = document.createElement('option');
    loungeOption.value = '21517181';
    loungeOption.textContent = 'Lounge';
    roomSelector.appendChild(loungeOption);
    
    roomSelector.addEventListener('change', (e) => {
        const newRoomId = e.target.value;
        chatIframe.src = `https://iframe.chat/embed?chat=${newRoomId}`;
        // Reinitialize Chattable for the new room
        setTimeout(initializeChattable, 500);
    });
    
    headerButtons.appendChild(roomSelector);

    const settingsButton = document.createElement('button');
    settingsButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="rgb(80, 80, 120)" width="18px" height="18px"><path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.69-1.62-0.92L14.4,2.23C14.38,2,14.17,1.84,13.92,1.84H9.92 c-0.25,0-0.47,0.16-0.54,0.39L9.04,4.95C8.45,5.18,7.92,5.49,7.42,5.87L5.03,4.91c-0.22-0.08-0.47,0-0.59,0.22L2.52,8.45 c-0.11,0.2-0.06,0.47,0.12,0.61l2.03,1.58C4.59,11.36,4.56,11.68,4.56,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.69,1.62,0.92l0.34,2.72 c0.07,0.23,0.29,0.39,0.54,0.39h3.99c0.25,0,0.47-0.16,0.54-0.39l0.34-2.72c0.59-0.23,1.12-0.54,1.62-0.92l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.11-0.2,0.06-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/></svg>`;
    Object.assign(settingsButton.style, {
        background: 'none', border: 'none', cursor: 'pointer',
        padding: '0 8px', lineHeight: '1', display: 'flex', alignItems: 'center'
    });
    settingsButton.title = 'Settings';
    headerButtons.appendChild(settingsButton);

    const reloadButton = document.createElement('button');
    reloadButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="rgb(80, 80, 120)" width="18px" height="18px"><path d="M7 7v10c0 1.1.9 2 2 2h10c1.1 0 2-.9 2-2V7M17 7V5c0-1.1-.9-2-2-2H9c-1.1 0-2 .9-2 2v2M7 11h10M9 14h6M7 17h10" stroke="rgb(80, 80, 120)" stroke-width="1.5" fill="none"/><path d="M16 4l3-3m0 6l-3-3" stroke="rgb(80, 80, 120)" stroke-width="1.5" fill="none" stroke-linecap="round"/></svg>`;
    Object.assign(reloadButton.style, {
        background: 'none', border: 'none', cursor: 'pointer',
        padding: '0 8px', lineHeight: '1', display: 'flex', alignItems: 'center'
    });
    reloadButton.title = 'Reload Chat';
    reloadButton.addEventListener('click', (e) => {
        e.stopPropagation();
        const currentSrc = chatIframe.src;
        chatIframe.src = '';
        setTimeout(() => {
            chatIframe.src = currentSrc;
            setTimeout(initializeChattable, 1000);
        }, 100);
    });
    headerButtons.appendChild(reloadButton);

    const minimizeButton = document.createElement('button');
    minimizeButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="rgb(80, 80, 120)" width="16px" height="16px"><path d="M19 13H5v-2h14v2z"/></svg>`;
    Object.assign(minimizeButton.style, {
        background: 'none', border: 'none', cursor: 'pointer',
        padding: '0 8px', lineHeight: '1', display: 'flex', alignItems: 'center'
    });
    minimizeButton.title = 'Minimize';
    minimizeButton.addEventListener('click', (e) => {
        e.stopPropagation();
        if (chatContainer.style.height === '45px') {
            chatContainer.style.height = '500px';
            iframeWrapper.style.display = 'flex';
            minimizeButton.title = 'Minimize';
        } else {
            chatContainer.style.height = '45px';
            iframeWrapper.style.display = 'none';
            minimizeButton.title = 'Maximize';
        }
    });
    headerButtons.appendChild(minimizeButton);

    const closeButton = document.createElement('button');
    closeButton.innerHTML = '&times;';
    Object.assign(closeButton.style, {
        background: 'none', border: 'none', color: 'rgb(80, 80, 120)',
        fontSize: '24px', lineHeight: '1', cursor: 'pointer', padding: '0 8px'
    });
    headerButtons.appendChild(closeButton);
    dragHeader.appendChild(headerButtons);
    chatContainer.appendChild(dragHeader);

    const iframeWrapper = document.createElement('div');
    Object.assign(iframeWrapper.style, {
        flexGrow: '1', overflow: 'hidden', position: 'relative'
    });
    chatContainer.appendChild(iframeWrapper);

    // Load Chattable library (ensures up-to-date version)
    const chatLibraryScript = document.createElement('script');
    chatLibraryScript.src = 'https://iframe.chat/scripts/main.min.js';
    document.head.appendChild(chatLibraryScript);

    // Also add direct script tag for redundancy and automatic updates
    const chattableScript = document.createElement('script');
    chattableScript.src = 'https://iframe.chat/scripts/main.min.js';
    document.head.appendChild(chattableScript);

    const chatIframe = document.createElement('iframe');
    chatIframe.src = 'https://iframe.chat/embed?chat=15234533';
    chatIframe.id = 'chattable';
    Object.assign(chatIframe.style, {
        border: 'none', backgroundColor: 'transparent', position: 'absolute',
        height: `calc(100% + ${CHATTABLE_HEADER_HEIGHT}px)`, width: '100%',
        top: `-${CHATTABLE_HEADER_HEIGHT}px`, left: '0',
        touchAction: 'manipulation', WebkitTouchCallout: 'none'
    });
    iframeWrapper.appendChild(chatIframe);
    document.body.appendChild(chatContainer);

    // Mobile input fix: Prevent enter key from creating new lines
    document.addEventListener('DOMContentLoaded', () => {
        setTimeout(() => {
            try {
                const iframeDoc = chatIframe.contentDocument || chatIframe.contentWindow.document;
                if (iframeDoc) {
                    const inputField = iframeDoc.querySelector('textarea, input[type="text"], [contenteditable]');
                    if (inputField) {
                        inputField.addEventListener('keydown', (e) => {
                            if (e.key === 'Enter' && !e.shiftKey) {
                                e.preventDefault();
                                // Trigger send by submitting the form or finding send button
                                const sendBtn = iframeDoc.querySelector('[class*="send"], button[aria-label*="send"], button:last-of-type');
                                if (sendBtn) sendBtn.click();
                            }
                        });
                    }
                }
            } catch (e) {
                console.warn('TrixBox: Mobile input fix - cross-origin iframe, cannot modify input behavior');
            }
        }, 2000);
    });

    // --- 4. ADD FUNCTIONALITY ---
    toggleIcon.addEventListener('click', (e) => {
        e.stopPropagation();
        showChatSelectionModal();
    });

    const showChatSelectionModal = () => {
        const modalStyle = `
            .trixbox-selection-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 100001; display: flex; align-items: center; justify-content: center; }
            .trixbox-selection-content { background: ${theme.modalBg}; color: ${theme.text}; padding: 25px; border-radius: 10px; text-align: center; font-family: sans-serif; box-shadow: 0 5px 15px rgba(0,0,0,0.5); }
            .trixbox-selection-content h2 { margin-top: 0; margin-bottom: 20px; }
            .trixbox-selection-btn-group { display: flex; flex-direction: column; gap: 10px; }
            .trixbox-selection-btn { flex: 1; color: white; border: none; padding: 12px 20px; border-radius: 5px; cursor: pointer; font-size: 14px; font-weight: bold; width: 100%; }
            .trixbox-selection-btn.main { background: ${theme.buttonPrimary}; }
            .trixbox-selection-btn.new { background: #7289da; }
            .trixbox-selection-btn.voice { background: #43b581; }
            .trixbox-selection-btn:hover { opacity: 0.9; }
        `;
        const styleSheet = document.createElement("style");
        styleSheet.innerText = modalStyle;
        document.head.appendChild(styleSheet);

        const overlay = document.createElement('div');
        overlay.className = 'trixbox-selection-overlay';
        overlay.innerHTML = `
            <div class="trixbox-selection-content">
                <h2>Select Chat</h2>
                <div class="trixbox-selection-btn-group">
                    <button id="trixbox-main-btn" class="trixbox-selection-btn main">TrixBox Main</button>
                    <button id="trixbox-new-btn" class="trixbox-selection-btn new">New TrixBox</button>
                    <button id="trixbox-voice-btn" class="trixbox-selection-btn voice">Voice Chat</button>
                </div>
            </div>
        `;
        document.body.appendChild(overlay);

        document.getElementById('trixbox-main-btn').addEventListener('click', () => {
            overlay.remove();
            resetActivityBadge();
            chatContainer.style.display = 'flex';
            toggleIcon.style.display = 'none';
        });

        document.getElementById('trixbox-new-btn').addEventListener('click', () => {
            overlay.remove();
            showNewTrixBox();
        });

        document.getElementById('trixbox-voice-btn').addEventListener('click', () => {
            overlay.remove();
            showVoiceChat();
        });

        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) {
                overlay.remove();
            }
        });
    };

    const showNewTrixBoxSettingsModal = (newChatContainer) => {
        const modalStyle = `
            .trixbox-new-settings-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 100001; display: flex; align-items: center; justify-content: center; }
            .trixbox-new-settings-content { background: ${theme.modalBg}; color: ${theme.text}; padding: 25px; border-radius: 10px; text-align: left; font-family: sans-serif; box-shadow: 0 5px 15px rgba(0,0,0,0.5); width: 350px; max-height: 80vh; overflow-y: auto; }
            .trixbox-new-settings-content h2 { margin-top: 0; text-align: center; }
            .trixbox-new-settings-group { margin: 20px 0; }
            .trixbox-new-settings-group label { display: block; margin-bottom: 8px; font-weight: bold; }
            .trixbox-new-settings-toggle { display: flex; align-items: center; gap: 10px; margin: 10px 0; }
            .trixbox-toggle-switch { width: 50px; height: 24px; background: #4f545c; border: none; border-radius: 12px; cursor: pointer; position: relative; transition: background 0.3s; }
            .trixbox-toggle-switch.active { background: ${theme.buttonPrimary}; }
            .trixbox-toggle-switch::after { content: ''; position: absolute; top: 2px; left: 2px; width: 20px; height: 20px; background: white; border-radius: 50%; transition: left 0.3s; }
            .trixbox-toggle-switch.active::after { left: 28px; }
            .trixbox-new-settings-btn-group { display: flex; gap: 10px; margin-top: 20px; }
            .trixbox-new-settings-btn { flex: 1; color: white; border: none; padding: 10px; border-radius: 5px; cursor: pointer; font-size: 14px; }
            .trixbox-new-settings-btn.save { background: ${theme.buttonPrimary}; }
            .trixbox-new-settings-btn.cancel { background: ${theme.buttonSecondary}; }
        `;
        const styleSheet = document.createElement("style");
        styleSheet.innerText = modalStyle;
        document.head.appendChild(styleSheet);

        const overlay = document.createElement('div');
        overlay.className = 'trixbox-new-settings-overlay';
        overlay.innerHTML = `
            <div class="trixbox-new-settings-content">
                <h2>New TrixBox Settings</h2>
                <div class="trixbox-new-settings-group">
                    <label>Close on Click Outside</label>
                    <button id="trixbox-new-close-toggle" class="trixbox-toggle-switch"></button>
                    <small style="color: #99aab5; font-size: 12px; display: block; margin-top: 5px;">Toggle: On/Off</small>
                </div>
                <div class="trixbox-new-settings-btn-group">
                    <button id="trixbox-new-settings-save" class="trixbox-new-settings-btn save">Save</button>
                    <button id="trixbox-new-settings-cancel" class="trixbox-new-settings-btn cancel">Cancel</button>
                </div>
            </div>
        `;
        document.body.appendChild(overlay);

        const closeToggle = document.getElementById('trixbox-new-close-toggle');
        const saveBtn = document.getElementById('trixbox-new-settings-save');
        const cancelBtn = document.getElementById('trixbox-new-settings-cancel');
        
        const isCloseOnClick = localStorage.getItem('trixbox-new-close-on-click') !== 'false';
        if (isCloseOnClick) {
            closeToggle.classList.add('active');
        }

        closeToggle.addEventListener('click', () => {
            closeToggle.classList.toggle('active');
        });

        saveBtn.addEventListener('click', () => {
            const isEnabled = closeToggle.classList.contains('active');
            localStorage.setItem('trixbox-new-close-on-click', isEnabled ? 'true' : 'false');
            overlay.remove();
        });

        cancelBtn.addEventListener('click', () => {
            overlay.remove();
        });

        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) {
                overlay.remove();
            }
        });
    };

    const showNewTrixBox = () => {
        const newChatContainer = document.createElement('div');
        newChatContainer.id = 'trixbox-new-container';
        Object.assign(newChatContainer.style, {
            position: 'fixed', bottom: 'auto', top: '70px', right: '15px', width: '350px', height: '500px',
            zIndex: '99999', display: 'flex', flexDirection: 'column',
            boxShadow: '0 4px 12px rgba(0,0,0,0.3)', borderRadius: '8px', overflow: 'hidden'
        });

        const newDragHeader = document.createElement('div');
        Object.assign(newDragHeader.style, {
            height: '30px', backgroundColor: 'rgb(210, 210, 255)', cursor: 'move',
            userSelect: 'none', display: 'flex', alignItems: 'center',
            justifyContent: 'space-between', padding: '0 5px 0 15px',
            position: 'relative', zIndex: '1'
        });

        const newHeaderTitle = document.createElement('span');
        newHeaderTitle.textContent = 'New TrixBox';
        Object.assign(newHeaderTitle.style, {
            color: 'white', fontWeight: 'bold', fontSize: '14px',
            textShadow: '1px 1px 0 rgb(160, 160, 230), 2px 2px 0 rgba(0, 0, 0, 0.15)'
        });
        newDragHeader.appendChild(newHeaderTitle);

        const newCloseButton = document.createElement('button');
        newCloseButton.innerHTML = '&times;';
        Object.assign(newCloseButton.style, {
            background: 'none', border: 'none', color: 'rgb(80, 80, 120)',
            fontSize: '24px', lineHeight: '1', cursor: 'pointer', padding: '0 8px'
        });
        newCloseButton.addEventListener('click', () => {
            newChatContainer.remove();
            toggleIcon.style.display = 'flex';
        });

        const newSettingsButton = document.createElement('button');
        newSettingsButton.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="rgb(80, 80, 120)" width="16px" height="16px"><path d="M19.14,12.94c0.04-0.3,0.06-0.61,0.06-0.94c0-0.32-0.02-0.64-0.07-0.94l2.03-1.58c0.18-0.14,0.23-0.41,0.12-0.61 l-1.92-3.32c-0.12-0.22-0.37-0.29-0.59-0.22l-2.39,0.96c-0.5-0.38-1.03-0.69-1.62-0.92L14.4,2.23C14.38,2,14.17,1.84,13.92,1.84H9.92 c-0.25,0-0.47,0.16-0.54,0.39L9.04,4.95C8.45,5.18,7.92,5.49,7.42,5.87L5.03,4.91c-0.22-0.08-0.47,0-0.59,0.22L2.52,8.45 c-0.11,0.2-0.06,0.47,0.12,0.61l2.03,1.58C4.59,11.36,4.56,11.68,4.56,12s0.02,0.64,0.07,0.94l-2.03,1.58 c-0.18,0.14-0.23,0.41-0.12,0.61l1.92,3.32c0.12,0.22,0.37,0.29,0.59,0.22l2.39-0.96c0.5,0.38,1.03,0.69,1.62,0.92l0.34,2.72 c0.07,0.23,0.29,0.39,0.54,0.39h3.99c0.25,0,0.47-0.16,0.54-0.39l0.34-2.72c0.59-0.23,1.12-0.54,1.62-0.92l2.39,0.96 c0.22,0.08,0.47,0,0.59-0.22l1.92-3.32c0.11-0.2,0.06-0.47-0.12-0.61L19.14,12.94z M12,15.6c-1.98,0-3.6-1.62-3.6-3.6 s1.62-3.6,3.6-3.6s3.6,1.62,3.6,3.6S13.98,15.6,12,15.6z"/></svg>`;
        Object.assign(newSettingsButton.style, {
            background: 'none', border: 'none', cursor: 'pointer',
            padding: '0 8px', lineHeight: '1', display: 'flex', alignItems: 'center'
        });
        newSettingsButton.title = 'Settings';
        newSettingsButton.addEventListener('click', (e) => {
            e.stopPropagation();
            showNewTrixBoxSettingsModal(newChatContainer);
        });

        newDragHeader.appendChild(newSettingsButton);
        newDragHeader.appendChild(newCloseButton);
        newChatContainer.appendChild(newDragHeader);

        const newIframeWrapper = document.createElement('div');
        Object.assign(newIframeWrapper.style, {
            flexGrow: '1', overflow: 'hidden', position: 'relative'
        });

        const newIframe = document.createElement('iframe');
        newIframe.src = 'https://www5.cbox.ws/box/?boxid=959661&boxtag=LgpZi2';
        newIframe.width = '100%';
        newIframe.height = '100%';
        newIframe.allowTransparency = 'yes';
        newIframe.allow = 'autoplay';
        newIframe.frameBorder = '0';
        newIframe.marginHeight = '0';
        newIframe.marginWidth = '0';
        newIframe.scrolling = 'auto';
        Object.assign(newIframe.style, {
            border: 'none', position: 'absolute', top: '0', left: '0'
        });
        newIframeWrapper.appendChild(newIframe);
        newChatContainer.appendChild(newIframeWrapper);

        // Hide and disable Cbox text in iframe footer
        try {
            const hideStyle = document.createElement('style');
            hideStyle.textContent = `
                a[href*="cbox.ws"],
                a[href*="www.cbox.ws"],
                .btn.Right.Interactive.Hand,
                iframe ~ a[href*="cbox.ws"],
                div[align="center"] a[href*="cbox.ws"] {
                    display: none !important;
                    pointer-events: none !important;
                    visibility: hidden !important;
                }
            `;
            document.head.appendChild(hideStyle);
        } catch (e) { }
        document.body.appendChild(newChatContainer);

        // Click outside to close New TrixBox (if enabled)
        const closeOnClickOutside = () => {
            const shouldCloseOnClick = localStorage.getItem('trixbox-new-close-on-click') !== 'false';
            if (shouldCloseOnClick) {
                document.addEventListener('mousedown', (e) => {
                    if (!newChatContainer.contains(e.target) && newChatContainer.parentElement) {
                        newChatContainer.remove();
                        toggleIcon.style.display = 'flex';
                    }
                }, { once: true });
            }
        };
        closeOnClickOutside();

        // Drag functionality for new chat
        let isDragging = false, offsetX, offsetY;
        newDragHeader.addEventListener('mousedown', (e) => {
            if (e.target !== newDragHeader && e.target !== newHeaderTitle) return;
            isDragging = true;
            offsetX = e.clientX - newChatContainer.getBoundingClientRect().left;
            offsetY = e.clientY - newChatContainer.getBoundingClientRect().top;
            newIframe.style.pointerEvents = 'none';
        });

        document.addEventListener('mousemove', (e) => {
            if (!isDragging) return;
            newChatContainer.style.left = `${e.clientX - offsetX}px`;
            newChatContainer.style.top = `${e.clientY - offsetY}px`;
            newChatContainer.style.bottom = 'auto';
            newChatContainer.style.right = 'auto';
        });

        document.addEventListener('mouseup', () => {
            if (!isDragging) return;
            isDragging = false;
            newIframe.style.pointerEvents = 'auto';
        });
    };

    const showVoiceChat = () => {
        const voiceChatContainer = document.createElement('div');
        voiceChatContainer.id = 'trixbox-voice-container';
        Object.assign(voiceChatContainer.style, {
            position: 'fixed', bottom: 'auto', top: '70px', right: '15px', width: '400px', height: '600px',
            zIndex: '99999', display: 'flex', flexDirection: 'column',
            boxShadow: '0 4px 12px rgba(0,0,0,0.3)', borderRadius: '8px', overflow: 'hidden'
        });

        const voiceDragHeader = document.createElement('div');
        Object.assign(voiceDragHeader.style, {
            height: '30px', backgroundColor: 'rgb(210, 210, 255)', cursor: 'move',
            userSelect: 'none', display: 'flex', alignItems: 'center',
            justifyContent: 'space-between', padding: '0 5px 0 15px',
            position: 'relative', zIndex: '1'
        });

        const voiceHeaderTitle = document.createElement('span');
        voiceHeaderTitle.textContent = 'TrixBox Voice Chat';
        Object.assign(voiceHeaderTitle.style, {
            color: 'white', fontWeight: 'bold', fontSize: '14px',
            textShadow: '1px 1px 0 rgb(160, 160, 230), 2px 2px 0 rgba(0, 0, 0, 0.15)'
        });
        voiceDragHeader.appendChild(voiceHeaderTitle);

        const voiceCloseButton = document.createElement('button');
        voiceCloseButton.innerHTML = '&times;';
        Object.assign(voiceCloseButton.style, {
            background: 'none', border: 'none', color: 'rgb(80, 80, 120)',
            fontSize: '24px', lineHeight: '1', cursor: 'pointer', padding: '0 8px'
        });
        voiceCloseButton.addEventListener('click', () => {
            voiceChatContainer.remove();
            toggleIcon.style.display = 'flex';
        });
        voiceDragHeader.appendChild(voiceCloseButton);
        voiceChatContainer.appendChild(voiceDragHeader);

        const voiceIframeWrapper = document.createElement('div');
        Object.assign(voiceIframeWrapper.style, {
            flexGrow: '1', overflow: 'hidden', position: 'relative'
        });

        const voiceIframe = document.createElement('iframe');
        voiceIframe.src = 'https://zp1v56uxy8rdx5ypatb0ockcb9tr6a-oci3--5173--cf284e50.local-credentialless.webcontainer-api.io/embed/test-6armm1';
        voiceIframe.width = '400';
        voiceIframe.height = '600';
        voiceIframe.frameBorder = '0';
        voiceIframe.allowFullscreen = true;
        voiceIframe.allow = 'microphone; camera; autoplay';
        Object.assign(voiceIframe.style, {
            border: 'none', position: 'absolute', top: '0', left: '0',
            width: '100%', height: '100%'
        });
        voiceIframeWrapper.appendChild(voiceIframe);
        voiceChatContainer.appendChild(voiceIframeWrapper);

        document.body.appendChild(voiceChatContainer);

        // Auto-click the connect button when voice chat iframe loads (using postMessage to bypass CORS)
        voiceIframe.addEventListener('load', () => {
            setTimeout(() => {
                voiceIframe.contentWindow.postMessage({ action: 'clickConnect' }, '*');
            }, 500);
        });

        // Drag functionality for voice chat
        let isDragging = false, offsetX, offsetY;
        voiceDragHeader.addEventListener('mousedown', (e) => {
            if (e.target !== voiceDragHeader && e.target !== voiceHeaderTitle) return;
            isDragging = true;
            offsetX = e.clientX - voiceChatContainer.getBoundingClientRect().left;
            offsetY = e.clientY - voiceChatContainer.getBoundingClientRect().top;
            voiceIframe.style.pointerEvents = 'none';
        });

        document.addEventListener('mousemove', (e) => {
            if (!isDragging) return;
            voiceChatContainer.style.left = `${e.clientX - offsetX}px`;
            voiceChatContainer.style.top = `${e.clientY - offsetY}px`;
            voiceChatContainer.style.bottom = 'auto';
            voiceChatContainer.style.right = 'auto';
        });

        document.addEventListener('mouseup', () => {
            if (!isDragging) return;
            isDragging = false;
            voiceIframe.style.pointerEvents = 'auto';
        });
    };

    closeButton.addEventListener('click', (e) => {
        e.stopPropagation();
        chatContainer.style.display = 'none';
        toggleIcon.style.display = 'flex';
    });

    settingsButton.addEventListener('click', (e) => {
        e.stopPropagation();
        showSettingsModal();
    });

    const showGuideModal = () => {
        const modalStyle = `
            .trixbox-guide-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 100001; display: flex; align-items: center; justify-content: center; }
            .trixbox-guide-content { background: ${theme.modalBg}; color: ${theme.text}; padding: 25px; border-radius: 10px; font-family: sans-serif; box-shadow: 0 5px 15px rgba(0,0,0,0.5); width: 500px; max-height: 80vh; overflow-y: auto; }
            .trixbox-guide-content h2 { margin-top: 0; text-align: center; color: ${theme.buttonPrimary}; }
            .trixbox-guide-section { margin: 20px 0; }
            .trixbox-guide-section h4 { color: ${theme.buttonPrimary}; margin-top: 15px; margin-bottom: 10px; }
            .trixbox-guide-table { width: 100%; border-collapse: collapse; margin: 10px 0; }
            .trixbox-guide-table th, .trixbox-guide-table td { border: 1px solid #4f545c; padding: 8px; text-align: left; font-size: 13px; }
            .trixbox-guide-table th { background: #4f545c; color: white; font-weight: bold; }
            .trixbox-guide-table code { background: #36393f; color: #88ff88; padding: 2px 4px; border-radius: 2px; }
            .trixbox-guide-btn { width: 100%; color: white; border: none; padding: 10px; border-radius: 5px; cursor: pointer; font-size: 14px; background: ${theme.buttonPrimary}; margin-top: 15px; }
            .trixbox-guide-btn:hover { opacity: 0.9; }
        `;
        const styleSheet = document.createElement("style");
        styleSheet.innerText = modalStyle;
        document.head.appendChild(styleSheet);

        const overlay = document.createElement('div');
        overlay.className = 'trixbox-guide-overlay';
        overlay.innerHTML = `
            <div class="trixbox-guide-content">
                <h2>Message Markdown & Commands</h2>
                
                <div class="trixbox-guide-section">
                    <h4>✨ Message Markdown</h4>
                    <table class="trixbox-guide-table">
                        <tr><th>Syntax</th><th>Result</th></tr>
                        <tr><td><code>*text*</code></td><td><i>Italic</i></td></tr>
                        <tr><td><code>**text**</code></td><td><b>Bold</b></td></tr>
                        <tr><td><code>~~text~~</code></td><td><s>Strikethrough</s></td></tr>
                        <tr><td><code>\`text\`</code></td><td><code>Code</code></td></tr>
                        <tr><td><code>&gt;text</code></td><td>Blockquote</td></tr>
                        <tr><td><code>[Link](url)</code></td><td>Hyperlink</td></tr>
                        <tr><td><code>![Alt](url)</code></td><td>Image</td></tr>
                    </table>
                </div>

                <div class="trixbox-guide-section">
                    <h4>⚙️ Default Commands</h4>
                    <table class="trixbox-guide-table">
                        <tr><th>Command</th><th>Description</th></tr>
                        <tr><td><code>!connect</code></td><td>Reconnect to chat</td></tr>
                        <tr><td><code>!online</code></td><td>Show online users</td></tr>
                        <tr><td><code>!tutorial</code></td><td>Show chat tutorial</td></tr>
                        <tr><td><code>!help</code></td><td>Show help</td></tr>
                    </table>
                    <small style="color: #99aab5; font-size: 11px; display: block; margin-top: 8px;">Owner/Moderator commands: !clear, !lock, !unlock, !reset, !attach</small>
                </div>

                <div class="trixbox-guide-section">
                    <h4>🎮 Custom Commands</h4>
                    <p style="font-size: 13px; margin: 5px 0;"><code>!coinflip</code> or <code>!cf</code> - Flip a coin (Heads/Tails)</p>
                    <p style="font-size: 13px; margin: 5px 0;"><code>!8ball</code> - Ask the Magic 8-Ball</p>
                    <p style="font-size: 13px; margin: 5px 0;"><code>!roll</code> - Roll a d20 or <code>!roll d6</code> for custom dice</p>
                    <p style="font-size: 13px; margin: 5px 0;"><code>!quote</code> - Get a random inspirational quote</p>
                    <p style="font-size: 13px; margin: 5px 0;"><code>!ping</code> - Check latency/ping</p>
                    <p style="font-size: 13px; margin: 5px 0;"><code>!joke</code> - Get a random joke</p>
                    <p style="font-size: 13px; margin: 5px 0;"><code>!trivia</code> - Get a random trivia fact</p>
                </div>

                <button id="trixbox-guide-close" class="trixbox-guide-btn">Got it!</button>
            </div>
        `;
        document.body.appendChild(overlay);

        document.getElementById('trixbox-guide-close').addEventListener('click', () => {
            overlay.remove();
        });

        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) {
                overlay.remove();
            }
        });
    };

    const showSettingsModal = () => {
        const modalStyle = `
            .trixbox-settings-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 100001; display: flex; align-items: center; justify-content: center; }
            .trixbox-settings-content { background: ${theme.modalBg}; color: ${theme.text}; padding: 25px; border-radius: 10px; text-align: left; font-family: sans-serif; box-shadow: 0 5px 15px rgba(0,0,0,0.5); width: 350px; max-height: 80vh; overflow-y: auto; }
            .trixbox-settings-content h2 { margin-top: 0; text-align: center; }
            .trixbox-settings-group { margin: 20px 0; }
            .trixbox-settings-group label { display: block; margin-bottom: 8px; font-weight: bold; }
            .trixbox-settings-group input[type="text"], .trixbox-settings-group input[type="file"] { width: 100%; padding: 8px; background: #36393f; color: ${theme.text}; border: 1px solid #4f545c; border-radius: 4px; box-sizing: border-box; }
            .trixbox-settings-group input[type="text"]::placeholder { color: #99aab5; }
            .trixbox-profile-preview { width: 60px; height: 60px; border-radius: 50%; background: #4f545c; margin: 10px auto; overflow: hidden; display: flex; align-items: center; justify-content: center; }
            .trixbox-profile-preview img { width: 100%; height: 100%; object-fit: cover; }
            .trixbox-settings-btn-group { display: flex; gap: 10px; margin-top: 20px; }
            .trixbox-settings-btn { flex: 1; color: white; border: none; padding: 10px; border-radius: 5px; cursor: pointer; font-size: 14px; }
            .trixbox-settings-btn.save { background: ${theme.buttonPrimary}; }
            .trixbox-settings-btn.cancel { background: ${theme.buttonSecondary}; }
        `;
        const styleSheet = document.createElement("style");
        styleSheet.innerText = modalStyle;
        document.head.appendChild(styleSheet);

        const overlay = document.createElement('div');
        overlay.className = 'trixbox-settings-overlay';
        overlay.innerHTML = `
            <div class="trixbox-settings-content">
                <h2>Chat Settings</h2>
                <div class="trixbox-settings-group">
                    <label>Username</label>
                    <input type="text" id="trixbox-username-input" placeholder="Enter your username" maxlength="32">
                </div>
                <div class="trixbox-settings-group">
                    <label>Profile Picture</label>
                    <div class="trixbox-profile-preview" id="trixbox-profile-preview"></div>
                    <input type="file" id="trixbox-profile-upload" accept="image/*">
                </div>
                <div class="trixbox-settings-group">
                    <label>Disable Focus Hotkey</label>
                    <input type="text" id="trixbox-hotkey-input" placeholder="Enter key (e.g., Escape, e, q)" maxlength="20">
                    <small style="color: #99aab5; font-size: 12px; display: block; margin-top: 5px;">Press any key or type a key name</small>
                </div>
                <div class="trixbox-settings-group">
                    <label>Notification Sound</label>
                    <input type="text" id="trixbox-notif-sound-input" placeholder="Enter URL or 'none' to disable" maxlength="500">
                    <small style="color: #99aab5; font-size: 12px; display: block; margin-top: 5px;">Example: https://www.myinstants.com/media/sounds/he-he-he-ha-clash-royale-deep-fried.mp3 or type 'none'</small>
                </div>
                <div class="trixbox-settings-group">
                    <label>Chat Theme</label>
                    <select id="trixbox-theme-select" style="width: 100%; padding: 8px; background: #36393f; color: ${theme.text}; border: 1px solid #4f545c; border-radius: 4px; box-sizing: border-box; cursor: pointer;">
                        <option value="tendo">Tendo (Default)</option>
                        <option value="retrowave-red">Retrowave Red</option>
                        <option value="notepad">Notepad</option>
                        <option value="cyberpunk">Cyberpunk</option>
                        <option value="dark-discord">Dark Discord</option>
                        <option value="light">Light</option>
                        <option value="high-contrast">High Contrast</option>
                    </select>
                    <small style="color: #99aab5; font-size: 12px; display: block; margin-top: 5px;">Choose your preferred chat theme</small>
                </div>
                <div class="trixbox-settings-btn-group">
                    <button id="trixbox-settings-guide" class="trixbox-settings-btn save" style="background: #43b581;">Guide</button>
                    <button id="trixbox-settings-save" class="trixbox-settings-btn save">Save</button>
                    <button id="trixbox-settings-cancel" class="trixbox-settings-btn cancel">Cancel</button>
                </div>
            </div>
        `;
        document.body.appendChild(overlay);

        const usernameInput = document.getElementById('trixbox-username-input');
        const profileUpload = document.getElementById('trixbox-profile-upload');
        const profilePreview = document.getElementById('trixbox-profile-preview');
        const hotkeyInput = document.getElementById('trixbox-hotkey-input');
        const notifSoundInput = document.getElementById('trixbox-notif-sound-input');
        const themeSelect = document.getElementById('trixbox-theme-select');
        const guideBtn = document.getElementById('trixbox-settings-guide');
        const saveBtn = document.getElementById('trixbox-settings-save');
        const cancelBtn = document.getElementById('trixbox-settings-cancel');
        let selectedProfileImage = localStorage.getItem('trixbox-profile-image') || null;

        guideBtn.addEventListener('click', (e) => {
            e.stopPropagation();
            overlay.remove();
            showGuideModal();
        });

        usernameInput.value = localStorage.getItem('trixbox-username') || '';
        hotkeyInput.value = localStorage.getItem('trixbox-disable-focus-key') || 'Escape';
        notifSoundInput.value = localStorage.getItem('trixbox-notification-sound') || '';
        themeSelect.value = localStorage.getItem('trixbox-selected-theme') || 'tendo';

        // Capture hotkey input
        hotkeyInput.addEventListener('keydown', (e) => {
            e.preventDefault();
            hotkeyInput.value = e.key;
        });

        if (selectedProfileImage) {
            profilePreview.innerHTML = `<img src="${selectedProfileImage}" alt="Profile">`;
        }

        profileUpload.addEventListener('change', (e) => {
            const file = e.target.files[0];
            if (file) {
                const reader = new FileReader();
                reader.onload = (event) => {
                    selectedProfileImage = event.target.result;
                    profilePreview.innerHTML = `<img src="${selectedProfileImage}" alt="Profile">`;
                };
                reader.readAsDataURL(file);
            }
        });

        saveBtn.addEventListener('click', () => {
            const username = usernameInput.value.trim();
            const hotkey = hotkeyInput.value.trim() || 'Escape';
            const notifSound = notifSoundInput.value.trim() || '';
            const selectedTheme = themeSelect.value;
            if (username) {
                localStorage.setItem('trixbox-username', username);
                localStorage.setItem('trixbox-disable-focus-key', hotkey);
                localStorage.setItem('trixbox-notification-sound', notifSound);
                localStorage.setItem('trixbox-selected-theme', selectedTheme);
                if (selectedProfileImage) {
                    localStorage.setItem('trixbox-profile-image', selectedProfileImage);
                }
                // Update CSS variable for notification sound
                updateNotificationSoundVariable(notifSound);
                if (typeof chattable !== 'undefined') {
                    if (typeof chattable.setName === 'function') {
                        chattable.setName(username);
                    }
                    // Load selected theme with proper async handling
                    if (typeof chattable.loadTheme === 'function') {
                        try {
                            const themePromise = chattable.loadTheme(selectedTheme);
                            if (themePromise && typeof themePromise.catch === 'function') {
                                themePromise.catch(err => {
                                    console.warn('TrixBox: Theme load failed, attempting reinitialize', err);
                                    // Fallback: reinitialize with theme
                                    if (typeof chattable.reinitialize === 'function') {
                                        chattable.reinitialize({ theme: selectedTheme });
                                    }
                                });
                            }
                        } catch (themeErr) {
                            console.warn('TrixBox: Theme load error, attempting reinitialize', themeErr);
                            // Fallback: reinitialize with theme
                            if (typeof chattable.reinitialize === 'function') {
                                chattable.reinitialize({ theme: selectedTheme });
                            }
                        }
                    }
                    // Send profile picture to other users via payload
                    if (selectedProfileImage && typeof chattable.sendPayload === 'function') {
                        chattable.sendPayload({
                            type: 'profile-picture',
                            username: username,
                            image: selectedProfileImage
                        });
                    }
                }
                overlay.remove();
            } else {
                alert('Please enter a username');
            }
        });

        cancelBtn.addEventListener('click', () => {
            overlay.remove();
        });

        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) {
                overlay.remove();
            }
        });
    };

    let isDragging = false, offsetX, offsetY;
    dragHeader.addEventListener('mousedown', (e) => {
        if (e.target !== dragHeader && e.target !== headerTitle) return;
        isDragging = true;
        offsetX = e.clientX - chatContainer.getBoundingClientRect().left;
        offsetY = e.clientY - chatContainer.getBoundingClientRect().top;
        chatIframe.style.pointerEvents = 'none';
    });

    document.addEventListener('mousemove', (e) => {
        if (!isDragging) return;
        chatContainer.style.left = `${e.clientX - offsetX}px`;
        chatContainer.style.top = `${e.clientY - offsetY}px`;
        chatContainer.style.bottom = 'auto';
        chatContainer.style.right = 'auto';
    });

    document.addEventListener('mouseup', () => {
        if (!isDragging) return;
        isDragging = false;
        chatIframe.style.pointerEvents = 'auto';
    });

    // --- NOTIFICATION SOUND UTILITY ---
    const updateNotificationSoundVariable = (soundValue) => {
        // Store in window for access in message hook
        window.trixboxCustomNotificationSound = soundValue && soundValue.toLowerCase() !== 'none' ? soundValue : null;
        
        // Suppress Chattable's default notification sound
        injectNotificationSoundIntoChatIframe(soundValue);
    };

    const injectNotificationSoundIntoChatIframe = (soundValue) => {
        try {
            // Completely suppress Chattable's default notification sound
            const css = `
                :root {
                    --notification-sfx: none !important;
                }
                /* Hide and disable Chattable's default notification audio */
                audio[data-notification="true"],
                audio[class*="notification"],
                audio[class*="sound"] {
                    display: none !important;
                    visibility: hidden !important;
                    pointer-events: none !important;
                    volume: 0 !important;
                }
            `;
            chatIframe.contentWindow.postMessage(css, '*');
        } catch (e) {
            // Will work once iframe is loaded, retry on initialization
        }
    };

    const playNotificationSound = () => {
        const soundValue = window.trixboxCustomNotificationSound || localStorage.getItem('trixbox-notification-sound') || '';
        if (soundValue && soundValue.toLowerCase() !== 'none') {
            try {
                const audio = new Audio(soundValue);
                audio.volume = 0.5;
                audio.play().catch(err => console.warn('TrixBox: Could not play notification sound', err));
            } catch (e) {
                console.warn('TrixBox: Error playing notification sound', e);
            }
        }
    };

    // Initialize notification sound on script load
    const initializeNotificationSound = () => {
        const savedSound = localStorage.getItem('trixbox-notification-sound') || '';
        updateNotificationSoundVariable(savedSound);
    };

    // --- 6. INITIALIZE THE CHAT ---
    let hasInitializedUsername = false; // Flag to prevent repeated setName calls
    const initializeChattable = () => {
        if (typeof chattable !== 'undefined' && chattable.initialize) {
            try {
                // --- 5. DEFINE CUSTOM COMMANDS (MUST BE BEFORE INITIALIZE) ---
                chattable.commands = {
                    "coinflip": function(fullCommand) {
                        const result = Math.random() < 0.5 ? "Heads" : "Tails";
                        chattable.sendMessage(`🪙 Coin flip result: **${result}**`, "TrixBox Bot", "robot", false);
                    },
                    "cf": function(fullCommand) {
                        const result = Math.random() < 0.5 ? "Heads" : "Tails";
                        chattable.sendMessage(`🪙 Coin flip result: **${result}**`, "TrixBox Bot", "robot", false);
                    },
                    "8ball": function(fullCommand) {
                        const responses = [
                            "It is certain.", "It is decidedly so.", "Without a doubt.", "Yes definitely.",
                            "You may rely on it.", "As I see it, yes.", "Most likely.", "Outlook good.",
                            "Yes.", "Signs point to yes.", "Reply hazy, try again.", "Ask again later.",
                            "Better not tell you now.", "Cannot predict now.", "Concentrate and ask again.",
                            "Don't count on it.", "My reply is no.", "My sources say no.", "Outlook not so good.",
                            "Very doubtful."
                        ];
                        const response = responses[Math.floor(Math.random() * responses.length)];
                        chattable.sendMessage(`🔮 Magic 8-Ball: **${response}**`, "TrixBox Bot", "robot", false);
                    },
                    "roll": function(fullCommand) {
                        const match = fullCommand.match(/d(\d+)/i);
                        const sides = match ? parseInt(match[1]) : 20;
                        const result = Math.floor(Math.random() * sides) + 1;
                        chattable.sendMessage(`🎲 Roll d${sides}: **${result}**`, "TrixBox Bot", "robot", false);
                    },
                    "quote": function(fullCommand) {
                        const quotes = [
                            "The only way to do great work is to love what you do. - Steve Jobs",
                            "Innovation distinguishes between a leader and a follower. - Steve Jobs",
                            "Life is what happens when you're busy making other plans. - John Lennon",
                            "The future belongs to those who believe in the beauty of their dreams. - Eleanor Roosevelt",
                            "It is during our darkest moments that we must focus to see the light. - Aristotle",
                            "The only impossible journey is the one you never begin. - Tony Robbins",
                            "Success is not final, failure is not fatal. - Winston Churchill"
                        ];
                        const quote = quotes[Math.floor(Math.random() * quotes.length)];
                        chattable.sendMessage(`💬 **${quote}**`, "TrixBox Bot", "robot", false);
                    },
                    "ping": function(fullCommand) {
                        const latency = Math.floor(Math.random() * 100) + 10;
                        chattable.sendMessage(`📡 Pong! Latency: **${latency}ms**`, "TrixBox Bot", "robot", false);
                    },
                    "joke": function(fullCommand) {
                        const jokes = [
                            "Why don't scientists trust atoms? Because they make up everything!",
                            "What do you call a fake noodle? An impasta!",
                            "Why don't eggs tell jokes? They'd crack each other up!",
                            "What did the ocean say to the beach? Nothing, it just waved.",
                            "Why did the scarecrow win an award? Because he was outstanding in his field!"
                        ];
                        const joke = jokes[Math.floor(Math.random() * jokes.length)];
                        chattable.sendMessage(`😂 **${joke}**`, "TrixBox Bot", "robot", false);
                    },
                    "trivia": function(fullCommand) {
                        const trivia = [
                            "A group of flamingos is called a 'flamboyance'.",
                            "Honey never spoils. Archaeologists have found 3000-year-old honey that was still edible!",
                            "Octopuses have three hearts and blue blood.",
                            "The Great Wall of China is not visible from space with the naked eye.",
                            "A lightning bolt is five times hotter than the surface of the sun."
                        ];
                        const fact = trivia[Math.floor(Math.random() * trivia.length)];
                        chattable.sendMessage(`🧠 Trivia: **${fact}**`, "TrixBox Bot", "robot", false);
                    }
                };

                const savedTheme = localStorage.getItem('trixbox-selected-theme') || 'tendo';
                chattable.initialize({
                    theme: savedTheme
                });

                // Load event: Auto-set username from territorial.io if not set (only once)
                chattable.on('load', function() {
                    try {
                        // Only set username once to avoid repeated permission prompts
                        if (hasInitializedUsername) return;
                        hasInitializedUsername = true;

                        const savedUsername = localStorage.getItem('trixbox-username');
                        if (!savedUsername) {
                            // Try to get name from territorial.io's localStorage
                            const territorialName = localStorage.getItem('d122');
                            if (territorialName) {
                                localStorage.setItem('trixbox-username', territorialName);
                                if (typeof chattable.setName === 'function') {
                                    chattable.setName(territorialName);
                                }
                            }
                        } else {
                            // Set stored username
                            if (typeof chattable.setName === 'function') {
                                chattable.setName(savedUsername);
                            }
                        }
                    } catch (e) {
                        console.warn('TrixBox: Could not set username on load', e);
                    }
                });

                // Suppress default notification sound and setup custom audio playback
                setTimeout(() => {
                    injectNotificationSoundIntoChatIframe();
                    
                    // Message event listener for activity monitoring
                    if (chattable.on) {
                        chattable.on('message', function(data) {
                            playNotificationSound();
                            updateMessageActivityBadge();
                        });
                    }
                }, 500);

                // Connection event listener: show online users
                chattable.on('connection', function(userList) {
                    window.trixboxCurrentUsers = userList;
                    updateConnectionStatus(userList);
                });

                // Handle profile picture payloads after initialization
                chattable.on('payload', function(data) {
                    if (data.type === 'profile-picture') {
                        localStorage.setItem(`trixbox-profile-${data.username}`, data.image);
                    }
                });

                // Off event handler for cleanup
                chattable.on('off', function() {
                    updateConnectionStatus({});
                    console.log('TrixBox: Disconnected from chat');
                });
            } catch (e) {
                console.warn('TrixBox: Chattable initialization error, retrying...', e);
                // Reinitialize on error after delay
                setTimeout(() => {
                    if (typeof chattable !== 'undefined' && chattable.reinitialize) {
                        try {
                            chattable.reinitialize();
                        } catch (reinitErr) {
                            console.warn('TrixBox: Reinitialize failed, full reinit...', reinitErr);
                            setTimeout(initializeChattable, 500);
                        }
                    } else {
                        setTimeout(initializeChattable, 500);
                    }
                }, 500);
            }
        } else {
            // Retry if chattable is not yet loaded
            setTimeout(initializeChattable, 500);
        }
    };

    chatLibraryScript.onload = function() {
        // Wait a tick for chattable to be fully ready
        setTimeout(initializeChattable, 100);
    };

    // --- CONNECTION STATUS MANAGER ---
    const updateConnectionStatus = (userList) => {
        const statusDot = document.getElementById('trixbox-connection-status');
        const userCount = Object.keys(userList || {}).length;
        if (userCount > 0) {
            statusDot.style.backgroundColor = '#43b581'; // Green
            statusDot.title = `Online: ${userCount} user${userCount !== 1 ? 's' : ''}`;
        } else {
            statusDot.style.backgroundColor = '#f04747'; // Red
            statusDot.title = 'Offline';
        }
    };

    // Show user list on status dot click
    document.addEventListener('click', (e) => {
        if (e.target.id === 'trixbox-connection-status') {
            showOnlineUsersModal();
        }
    });

    const showOnlineUsersModal = () => {
        const modalStyle = `
            .trixbox-users-overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0,0,0,0.8); z-index: 100001; display: flex; align-items: center; justify-content: center; }
            .trixbox-users-content { background: ${theme.modalBg}; color: ${theme.text}; padding: 25px; border-radius: 10px; font-family: sans-serif; box-shadow: 0 5px 15px rgba(0,0,0,0.5); width: 300px; max-height: 60vh; overflow-y: auto; }
            .trixbox-users-content h2 { margin-top: 0; text-align: center; color: ${theme.buttonPrimary}; }
            .trixbox-users-list { list-style: none; padding: 0; margin: 0; }
            .trixbox-users-list li { padding: 10px; border-bottom: 1px solid #4f545c; display: flex; align-items: center; gap: 10px; }
            .trixbox-users-list li:last-child { border-bottom: none; }
            .trixbox-users-online-dot { width: 8px; height: 8px; background: #43b581; border-radius: 50%; }
            .trixbox-users-close { width: 100%; color: white; border: none; padding: 10px; border-radius: 5px; cursor: pointer; background: ${theme.buttonPrimary}; margin-top: 15px; }
        `;
        const styleSheet = document.createElement('style');
        styleSheet.innerText = modalStyle;
        document.head.appendChild(styleSheet);

        // Get current user list
        let userList = window.trixboxCurrentUsers || {};

        const userListHtml = Object.entries(userList).map(([uid, name]) => 
            `<li><div class="trixbox-users-online-dot"></div><span>${name}</span></li>`
        ).join('');

        const overlay = document.createElement('div');
        overlay.className = 'trixbox-users-overlay';
        overlay.innerHTML = `
            <div class="trixbox-users-content">
                <h2>Online Users (${Object.keys(userList).length})</h2>
                <ul class="trixbox-users-list">
                    ${userListHtml || '<li>No users online</li>'}
                </ul>
                <button class="trixbox-users-close">Close</button>
            </div>
        `;
        document.body.appendChild(overlay);

        overlay.querySelector('button').addEventListener('click', () => {
            overlay.remove();
        });

        overlay.addEventListener('click', (e) => {
            if (e.target === overlay) {
                overlay.remove();
            }
        });
    };

    // --- MESSAGE ACTIVITY BADGE ---
    let messageActivityCount = 0;
    const updateMessageActivityBadge = () => {
        messageActivityCount++;
        toggleIcon.style.position = 'relative';
        
        let badge = document.getElementById('trixbox-activity-badge');
        if (!badge) {
            badge = document.createElement('div');
            badge.id = 'trixbox-activity-badge';
            Object.assign(badge.style, {
                position: 'absolute', top: '-5px', right: '-5px',
                background: '#f04747', color: 'white', fontSize: '11px',
                fontWeight: 'bold', borderRadius: '50%', width: '20px', height: '20px',
                display: 'flex', alignItems: 'center', justifyContent: 'center',
                zIndex: '99999'
            });
            toggleIcon.appendChild(badge);
        }
        
        badge.textContent = messageActivityCount > 99 ? '99+' : messageActivityCount;
        badge.style.display = 'flex';
    };

    // Reset badge when chat is opened
    const resetActivityBadge = () => {
        messageActivityCount = 0;
        const badge = document.getElementById('trixbox-activity-badge');
        if (badge) badge.style.display = 'none';
    };

    // Wrap the chat selection to reset badge
    document.addEventListener('click', (e) => {
        if (e.target === toggleIcon || (e.target.parentElement === toggleIcon)) {
            resetActivityBadge();
        }
    });

    // Reset badge when main chat opens
    const originalToggle = toggleIcon.addEventListener;
    const mainOpenListener = () => {
        resetActivityBadge();
    };

    // Fallback initialization if script loads via other method
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => {
            setTimeout(initializeChattable, 500);
        });
    } else {
        setTimeout(initializeChattable, 500);
    }

    // --- 7. INITIALIZE NOTIFICATION SOUND ---
    initializeNotificationSound();

    // --- 8. RUN THE UPDATE CHECKER ---
    checkForUpdates();

    // --- 8. HOTKEY HANDLING - Press configured key to release chat focus and use game hotkeys ---
    document.addEventListener('keydown', (e) => {
        const disableFocusKey = localStorage.getItem('trixbox-disable-focus-key') || 'Escape';
        if (e.key === disableFocusKey || (disableFocusKey === 'Escape' && (e.key === 'Escape' || e.keyCode === 27))) {
            // Remove focus from any active chat input
            document.activeElement.blur();
            
            // Hide main chat
            if (chatContainer.style.display === 'flex') {
                chatContainer.style.display = 'none';
                toggleIcon.style.display = 'flex';
            }
            
            // Hide any open chat windows
            const voiceContainer = document.getElementById('trixbox-voice-container');
            const newContainer = document.getElementById('trixbox-new-container');
            
            if (voiceContainer) voiceContainer.remove();
            if (newContainer) newContainer.remove();
            
            toggleIcon.style.display = 'flex';
        }
    });

})();