TrixBox

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

当前为 2025-11-17 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         TrixBox
// @namespace    http://tampermonkey.net/
// @version      0.6.9
// @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';

    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 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();
            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: 10px 0;"><code>!coinflip</code> - Flip a coin (Heads/Tails)</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://example.com/sound.mp3 or type 'none'</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 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') || '';

        // 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() || '';
            if (username) {
                localStorage.setItem('trixbox-username', username);
                localStorage.setItem('trixbox-disable-focus-key', hotkey);
                localStorage.setItem('trixbox-notification-sound', notifSound);
                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);
                    }
                    // 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) => {
        if (soundValue && soundValue.toLowerCase() !== 'none') {
            // Set as CSS variable with URL
            document.documentElement.style.setProperty('--notification-sfx', `url('${soundValue}')`);
        } else {
            // Disable notifications by setting to 'none'
            document.documentElement.style.setProperty('--notification-sfx', 'none');
        }
        
        // Also inject CSS into iframe to override Chattable's default notification sound
        injectNotificationSoundIntoChatIframe(soundValue);
    };

    const injectNotificationSoundIntoChatIframe = (soundValue) => {
        try {
            const css = `
                :root {
                    ${soundValue && soundValue.toLowerCase() !== 'none' 
                        ? `--notification-sfx: url('${soundValue}');` 
                        : `--notification-sfx: none;`}
                }
            `;
            chatIframe.contentWindow.postMessage({ type: 'css', content: css }, '*');
        } catch (e) {
            // Will work once iframe is loaded, retry on initialization
        }
    };

    const playNotificationSound = () => {
        const soundValue = localStorage.getItem('trixbox-notification-sound') || '';
        if (soundValue && soundValue.toLowerCase() !== 'none') {
            try {
                const audio = new Audio(soundValue);
                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);
    };

    // --- 5. DEFINE CUSTOM COMMANDS ---
    chattable.commands = {
        "coinflip": function(fullCommand) {
            const result = Math.random() < 0.5 ? "Heads" : "Tails";
            chattable.sendMessage(`🪙 Coin flip result: **${result}**`, "TrixBox", "", false);
        }
    };

    // --- 6. INITIALIZE THE CHAT ---
    const initializeChattable = () => {
        if (typeof chattable !== 'undefined' && chattable.initialize) {
            try {
                chattable.initialize({
                    theme: "tendo"
                });

                // Inject custom notification sound CSS into iframe after initialization
                setTimeout(() => {
                    const savedSound = localStorage.getItem('trixbox-notification-sound') || '';
                    const css = `
                        :root {
                            ${savedSound && savedSound.toLowerCase() !== 'none' 
                                ? `--notification-sfx: url('${savedSound}');` 
                                : `--notification-sfx: none;`}
                        }
                    `;
                    try {
                        chatIframe.contentWindow.postMessage(css, '*');
                    } catch (e) { }
                }, 500);

                // Handle profile picture payloads after initialization
                chattable.on('payload', function(data) {
                    if (data.type === 'profile-picture') {
                        localStorage.setItem(`trixbox-profile-${data.username}`, data.image);
                    }
                });
            } catch (e) {
                console.warn('TrixBox: Chattable initialization error, retrying...', e);
                setTimeout(initializeChattable, 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);
    };

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

})();