TrixBox

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

Per 17-11-2025. Zie de nieuwste versie.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

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

})();