TrixBox

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         TrixBox
// @namespace    http://tampermonkey.net/
// @version      0.3.6
// @description  TriX Executor's ChatBox (for territorial.io)!
// @author       Painsel
// @match        https://territorial.io/*
// @match        https://fxclient.github.io/FXclient/*
// @match        https://discord.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_info
// ==/UserScript==
 
/*
  Copyright (c) 2025 Painsel
  All Rights Reserved.
 
  This script is proprietary software. You may not use, copy, modify, merge,
  publish, distribute, sublicense, and/or sell copies of the Software.
 
  UNAUTHORIZED COPYING, DISTRIBUTION, OR MODIFICATION OF THIS SCRIPT,
  EITHER IN WHOLE OR IN PART, IS STRICTLY PROHIBITED.
*/
 
(function() {
    'use strict';
 
    // --- 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;
            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); }
    };
 
    // --- 2. CHATTABLE CUSTOM COMMANDS ---
    // Initialize custom commands object before chat loads
    window.chattable = window.chattable || {};
    window.chattable.commands = {
        cf: function() {
            const result = Math.random() < 0.5 ? 'Heads' : 'Tails';
            if (typeof chattable !== 'undefined' && chattable.sendMessage) {
                chattable.sendMessage(`🪙 Coinflip Result: **${result}**`);
            }
        },
        coinflip: function() {
            const result = Math.random() < 0.5 ? 'Heads' : 'Tails';
            if (typeof chattable !== 'undefined' && chattable.sendMessage) {
                chattable.sendMessage(`🪙 Coinflip Result: **${result}**`);
            }
        },
        userinfo: function() {
            if (typeof chattable !== 'undefined' && chattable.sendMessage) {
                chattable.sendMessage('📊 User info command executed!');
            }
        },
        stats: function(fullstring) {
            if (typeof chattable !== 'undefined' && chattable.sendMessage) {
                chattable.sendMessage('📈 Stats: Chat is fully loaded and operational!');
            }
        }
    };

    // --- 2A. THEME STORAGE AND SELECTOR ---
    const themes = [
        'Amber', 'Amdroid', 'Comment Section', 'Confidential', 'Glass',
        'Hacker Terminal', 'Kick', 'Moderno', 'Notepad', 'Pastel Pink',
        'Retrowave Red', 'Tendo', 'Wannabe XP', 'Professional', 'Bytechat'
    ];

    let currentTheme = localStorage.getItem('trixbox-theme') || 'Tendo';
    let notificationSound = localStorage.getItem('trixbox-notification-sound') || 'default';
    let customCSSEnabled = localStorage.getItem('trixbox-custom-css') === 'true';

    const applyTheme = (themeName) => {
        localStorage.setItem('trixbox-theme', themeName);
        currentTheme = themeName;
        // Defer initialization until library is loaded
        setTimeout(() => {
            if (typeof chattable !== 'undefined' && chattable.initialize) {
                chattable.initialize({
                    theme: themeName.toLowerCase().replace(/\s+/g, ''),
                    stylesheet: customCSSEnabled ? localStorage.getItem('trixbox-custom-styles') : undefined
                });
            }
        }, 100);
    };

    // CSS Styling configuration
    const customStyleManager = {
        getDefaultStyles: () => `
/* TrixBox Default Styles */
:root {
  --notification-sfx: ${notificationSound === 'none' ? 'none' : 'default'};
  --advanced-layout: true;
}

/* Message styling */
.allMessages {
  padding: 8px;
  border-radius: 4px;
  margin: 4px 0;
}

.allMessages.sent {
  background: rgba(88, 101, 242, 0.1);
}

.allMessages.received {
  background: rgba(79, 84, 92, 0.1);
}

/* Badge styling */
.mod, .owner, .beta {
  border-radius: 3px;
  padding: 2px 6px;
  font-size: 11px;
  font-weight: bold;
}

/* Input styling */
#input {
  font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
  font-size: 14px;
}

#input:empty::before {
  content: "Type a message...";
  color: #72767d;
}

/* Settings and UI elements */
#settings {
  opacity: 0.8;
  transition: opacity 0.2s;
}

#settings:hover {
  opacity: 1;
}

/* Timestamp styling */
#timestamp {
  font-size: 11px;
  color: #72767d;
}

/* Reply styling */
.reply {
  border-left: 3px solid #5865f2;
  padding-left: 8px;
  opacity: 0.8;
}

/* Context menu */
.ctxMenuOption {
  padding: 8px 12px;
  border-radius: 4px;
  transition: background 0.15s;
}

.ctxMenuOption:hover {
  background: #5865f2;
}

/* Emoji styling */
.emoji {
  transition: transform 0.2s;
}

.emoji:hover {
  transform: scale(1.2);
}

/* Load more button */
#loadMore {
  background: #5865f2;
  color: white;
  border: none;
  padding: 8px 16px;
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.2s;
}

#loadMore:hover {
  background: #4752c4;
}

/* Scroll to bottom button */
#scrollToBottom {
  background: #5865f2;
  color: white;
  border: none;
  padding: 8px 16px;
  border-radius: 4px;
  cursor: pointer;
}

/* Pinned messages */
.pinned {
  background: rgba(88, 101, 242, 0.2);
  border-left: 4px solid #5865f2;
  padding: 8px;
}

/* Highlight mentions */
.highlight {
  background: #faa61a;
  padding: 0 4px;
  border-radius: 3px;
  font-weight: bold;
}

/* Reply banner */
#replyBanner {
  background: #2f3136;
  border-top: 2px solid #5865f2;
  padding: 8px 12px;
  font-size: 13px;
}

/* Emoji tray */
#emojiTray {
  background: #2f3136;
  border: 1px solid #202225;
}

.emojiInGrid {
  padding: 6px;
  border-radius: 4px;
  cursor: pointer;
  transition: transform 0.1s;
}

.emojiInGrid:hover {
  transform: scale(1.15);
}

#emojiTrayToggle {
  opacity: 0.8;
  transition: opacity 0.2s;
}

#emojiTrayToggle:hover {
  opacity: 1;
}

/* Typing indicator */
#is_typing {
  font-style: italic;
  color: #72767d;
}

/* Tutorial */
#shadow {
  display: none;
}

/* Settings menu */
#settingsWindow {
  background: #2f3136;
}

#settingsMenu {
  background: #36393f;
  color: #dcddde;
}

#account, #name, #dltranscript, #save, #closeSettingsBtn {
  background: #5865f2;
  color: white;
  border: none;
  padding: 8px 16px;
  border-radius: 4px;
  cursor: pointer;
  transition: background 0.2s;
}

#account:hover, #name:hover, #dltranscript:hover, #save:hover, #closeSettingsBtn:hover {
  background: #4752c4;
}

/* Dialogue modal */
.dialogueModal {
  background: #36393f;
  color: #dcddde;
  border-radius: 8px;
  padding: 20px;
}

/* Background */
#background {
  background-color: #36393f;
}

/* Top banner */
#top_banner {
  background: linear-gradient(135deg, #5865f2, #4752c4);
}
        `,
        
        applyCustomStyles: (css) => {
            localStorage.setItem('trixbox-custom-styles', css);
            // Defer initialization until library is loaded
            setTimeout(() => {
                if (typeof chattable !== 'undefined' && chattable.initialize) {
                    chattable.initialize({
                        stylesheet: css
                    });
                }
            }, 100);
        },
        
        resetStyles: () => {
            localStorage.removeItem('trixbox-custom-styles');
            customCSSEnabled = false;
            localStorage.setItem('trixbox-custom-css', 'false');
            applyTheme(currentTheme);
        }
    };

    // --- 2B. PROFILE PICTURES STORAGE ---
    let profilePictures = JSON.parse(localStorage.getItem('trixbox-profiles')) || {};

    const setProfilePicture = (userId, imageUrl) => {
        profilePictures[userId] = imageUrl;
        localStorage.setItem('trixbox-profiles', JSON.stringify(profilePictures));
    };

    const getProfilePicture = (userId) => {
        return profilePictures[userId] || null;
    };

    // --- 2C. EMBEDS STORAGE ---
    let embeds = JSON.parse(localStorage.getItem('trixbox-embeds')) || {};

    const addEmbed = (messageId, embedData) => {
        embeds[messageId] = embedData;
        localStorage.setItem('trixbox-embeds', JSON.stringify(embeds));
    };

    const getEmbed = (messageId) => {
        return embeds[messageId] || null;
    };

    // --- 2D. FILE ATTACHMENTS STORAGE ---
    let attachments = JSON.parse(localStorage.getItem('trixbox-attachments')) || {};

    const addAttachment = (messageId, fileData) => {
        attachments[messageId] = fileData;
        localStorage.setItem('trixbox-attachments', JSON.stringify(attachments));
    };

    const getAttachment = (messageId) => {
        return attachments[messageId] || null;
    };

    // --- 2E. CHANGELOG MODAL ---
    const showChangelogModal = () => {
        const changelog = `
            <h2>TrixBox v0.3.2 - Changelog</h2>
            <div style="text-align: left; font-size: 13px; line-height: 1.6;">
                <h3 style="color: #faa61a;">🔧 v0.3.2 Bug Fixes & Improvements:</h3>
                <ul>
                    <li><strong>Fixed Chattable Initialization Error:</strong> Resolved "chattable.initialize is not a function" error</li>
                    <li><strong>Improved Library Loading:</strong> Added retry mechanism with exponential backoff</li>
                    <li><strong>Better Error Handling:</strong> Deferred initialization prevents timing issues</li>
                    <li><strong>Theme Loading Optimization:</strong> Themes now load reliably on first attempt</li>
                    <li><strong>Custom Styles Support:</strong> CSS application now works correctly with library lifecycle</li>
                </ul>
                <h3 style="color: #faa61a;">🆕 v0.3.1 Updates:</h3>
                <ul>
                    <li><strong>Improved Settings Modal:</strong> Better organization with expandable sections</li>
                    <li><strong>Enhanced CSS Editor:</strong> Syntax highlighting for CSS with better textarea</li>
                    <li><strong>Chattable API Integration:</strong> Full support for custom commands and themes</li>
                    <li><strong>Sound Testing:</strong> Preview notification sounds before saving</li>
                    <li><strong>Settings Info Panel:</strong> View current theme, CSS status, and notification settings</li>
                </ul>
                <h3>✨ v0.3.0 Features:</h3>
                <ul>
                    <li><strong>!cf / !coinflip Command:</strong> Flip a coin in chat with !cf or !coinflip</li>
                    <li><strong>Custom Commands API:</strong> Integrated Chattable API for custom command system</li>
                    <li><strong>Custom Profile Pictures:</strong> Set custom profile pictures for users</li>
                    <li><strong>Embeds Support:</strong> Send rich embeds in messages</li>
                    <li><strong>File Attachments:</strong> Attach files with .p4 and .mp3 player support</li>
                    <li><strong>Custom Themes:</strong> Choose from 15 unique themes (Amber, Glass, Hacker Terminal, Kick, Moderno, Notepad, Pastel Pink, Retrowave Red, Tendo, Wannabe XP, Professional, Bytechat, etc)</li>
                    <li><strong>Advanced CSS Styling:</strong> Edit and apply custom CSS styles directly in settings</li>
                    <li><strong>Notification Sound Control:</strong> Customize or disable notification sounds</li>
                </ul>
                <h3>🔧 Improvements:</h3>
                <ul>
                    <li>Integrated Chattable Library with custom command support</li>
                    <li>Added persistent theme selection using localStorage</li>
                    <li>Added profile picture and embed storage system</li>
                    <li>Enhanced chat functionality with command parsing and API integration</li>
                    <li>Added comprehensive default CSS styles for all chat elements</li>
                    <li>Settings modal now includes advanced customization options</li>
                </ul>
                <h3>📝 Technical:</h3>
                <ul>
                    <li>Version bumped from 0.2.9 to 0.3.1</li>
                    <li>Chattable API fully integrated for theme and custom command handling</li>
                    <li>localStorage optimization for better performance</li>
                </ul>
            </div>
        `;
        
        const modalStyle = `
            .trixbox-changelog-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-changelog-content { background: ${theme.modalBg}; color: ${theme.text}; padding: 30px; border-radius: 10px; text-align: center; font-family: sans-serif; box-shadow: 0 5px 15px rgba(0,0,0,0.5); max-width: 500px; max-height: 600px; overflow-y: auto; }
            .trixbox-changelog-content h2 { margin-top: 0; color: ${theme.buttonPrimary}; }
            .trixbox-changelog-content h3 { margin: 20px 0 10px 0; color: ${theme.buttonPrimary}; font-size: 15px; }
            .trixbox-changelog-content ul { list-style-position: inside; margin: 10px 0; padding: 0; }
            .trixbox-changelog-content li { margin: 5px 0; text-align: left; }
            .trixbox-changelog-btn { color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; margin: 10px 10px 0 0; font-size: 14px; background: ${theme.buttonPrimary}; }
        `;
        
        if (!document.getElementById('trixbox-changelog-style')) {
            const styleSheet = document.createElement("style");
            styleSheet.id = 'trixbox-changelog-style';
            styleSheet.innerText = modalStyle;
            document.head.appendChild(styleSheet);
        }
        
        const modalOverlay = document.createElement('div');
        modalOverlay.className = 'trixbox-changelog-overlay';
        modalOverlay.innerHTML = `
            <div class="trixbox-changelog-content">
                ${changelog}
                <button class="trixbox-changelog-btn">Close</button>
            </div>
        `;
        document.body.appendChild(modalOverlay);
        
        modalOverlay.querySelector('.trixbox-changelog-btn').onclick = () => { modalOverlay.remove(); };
        modalOverlay.onclick = (e) => {
            if (e.target === modalOverlay) modalOverlay.remove();
        };
    };

    // --- 3. 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>`;
    // **FIX IS HERE**: Changed z-index to zIndex
    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 AND LINK BLOCKERS ---
    const chatContainer = document.createElement('div');
    chatContainer.id = 'trixbox-container';
    Object.assign(chatContainer.style, {
        position: 'fixed', bottom: '15px', 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'
    });
 
    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'
    });
 
    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 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'
    });
    dragHeader.appendChild(closeButton);

    // Settings/Themes button
    const settingsBtn = document.createElement('button');
    settingsBtn.innerHTML = '⚙️';
    Object.assign(settingsBtn.style, {
        background: 'none', border: 'none', color: 'rgb(80, 80, 120)',
        fontSize: '18px', lineHeight: '1', cursor: 'pointer', padding: '0 8px', marginRight: '5px'
    });
    dragHeader.insertBefore(settingsBtn, closeButton);

    // Changelog button
    const changelogBtn = document.createElement('button');
    changelogBtn.innerHTML = '📋';
    Object.assign(changelogBtn.style, {
        background: 'none', border: 'none', color: 'rgb(80, 80, 120)',
        fontSize: '18px', lineHeight: '1', cursor: 'pointer', padding: '0 8px', marginRight: '5px'
    });
    dragHeader.insertBefore(changelogBtn, settingsBtn);
    chatContainer.appendChild(dragHeader);

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

    // --- 3A. ADVANCED SETTINGS MODAL ---
    const showAdvancedSettings = () => {
        const settingsStyle = `
            .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); max-width: 500px; max-height: 700px; overflow-y: auto; }
            .trixbox-settings-content h2 { margin-top: 0; color: ${theme.buttonPrimary}; text-align: center; }
            .trixbox-settings-section { margin: 20px 0; padding: 15px; background: ${theme.buttonSecondary}; border-radius: 5px; }
            .trixbox-settings-section h3 { margin: 0 0 10px 0; color: ${theme.buttonPrimary}; font-size: 14px; }
            .trixbox-settings-label { display: block; margin: 10px 0 5px 0; font-size: 13px; color: ${theme.text}; }
            .trixbox-settings-input, .trixbox-settings-select, .trixbox-settings-textarea { width: 100%; padding: 8px; margin: 5px 0 10px 0; background: #36393f; color: ${theme.text}; border: 1px solid #202225; border-radius: 4px; font-family: monospace; font-size: 12px; box-sizing: border-box; }
            .trixbox-settings-textarea { min-height: 150px; resize: vertical; }
            .trixbox-settings-btn { color: white; border: none; padding: 10px 15px; border-radius: 5px; cursor: pointer; margin: 5px 5px 5px 0; font-size: 13px; }
            .trixbox-settings-btn.primary { background: ${theme.buttonPrimary}; }
            .trixbox-settings-btn.secondary { background: #5865f2; }
            .trixbox-settings-btn.danger { background: #f04747; }
            .trixbox-settings-btn:hover { opacity: 0.9; }
            .trixbox-sound-preview { padding: 10px; background: #2f3136; border-radius: 4px; margin: 10px 0; }
            .trixbox-sound-preview button { padding: 8px 12px; font-size: 12px; }
        `;
        
        if (!document.getElementById('trixbox-settings-style')) {
            const styleSheet = document.createElement("style");
            styleSheet.id = 'trixbox-settings-style';
            styleSheet.innerText = settingsStyle;
            document.head.appendChild(styleSheet);
        }
        
        const modalOverlay = document.createElement('div');
        modalOverlay.className = 'trixbox-settings-overlay';
        
        const savedStyles = localStorage.getItem('trixbox-custom-styles') || customStyleManager.getDefaultStyles();
        
        modalOverlay.innerHTML = `
            <div class="trixbox-settings-content">
                <h2>⚙️ Advanced Settings</h2>
                
                <div class="trixbox-settings-section">
                    <h3>🔊 Notification Sound</h3>
                    <label class="trixbox-settings-label">Sound Type:</label>
                    <select class="trixbox-settings-select" id="notification-sound-select">
                        <option value="default" ${notificationSound === 'default' ? 'selected' : ''}>Default</option>
                        <option value="none" ${notificationSound === 'none' ? 'selected' : ''}>Disabled</option>
                        <option value="custom">Custom URL</option>
                    </select>
                    <div class="trixbox-sound-preview">
                        <button class="trixbox-settings-btn secondary" id="test-sound-btn">🔊 Test Sound</button>
                    </div>
                    <label class="trixbox-settings-label">Custom Sound URL (if selected above):</label>
                    <input type="text" class="trixbox-settings-input" id="custom-sound-url" placeholder="https://example.com/sound.mp3" value="${localStorage.getItem('trixbox-custom-sound-url') || ''}">
                </div>
                
                <div class="trixbox-settings-section">
                    <h3>🎨 Custom CSS Styles</h3>
                    <p style="font-size: 12px; color: #72767d; margin: 10px 0;">Edit the CSS below to customize chat appearance. Leave blank to use theme defaults.</p>
                    <textarea class="trixbox-settings-textarea" id="custom-css-input" placeholder="Enter custom CSS here...">${savedStyles}</textarea>
                    <button class="trixbox-settings-btn primary" id="apply-css-btn">✅ Apply Styles</button>
                    <button class="trixbox-settings-btn secondary" id="reset-css-btn">↻ Reset to Default</button>
                    <button class="trixbox-settings-btn secondary" id="load-default-btn">📋 Load Default CSS</button>
                </div>
                
                <div class="trixbox-settings-section">
                    <h3>ℹ️ Settings Info</h3>
                    <p style="font-size: 12px; color: #72767d; margin: 0;">
                        Current Theme: <strong>${currentTheme}</strong><br>
                        Custom Styles: <strong>${customCSSEnabled ? 'Enabled' : 'Disabled'}</strong><br>
                        Notification: <strong>${notificationSound === 'default' ? 'Default' : notificationSound === 'none' ? 'Disabled' : 'Custom'}</strong>
                    </p>
                </div>
                
                <div style="text-align: center;">
                    <button class="trixbox-settings-btn primary" id="close-settings-btn">Close Settings</button>
                </div>
            </div>
        `;
        document.body.appendChild(modalOverlay);
        
        // Event listeners
        document.getElementById('notification-sound-select').addEventListener('change', (e) => {
            notificationSound = e.target.value;
            localStorage.setItem('trixbox-notification-sound', notificationSound);
        });
        
        document.getElementById('test-sound-btn').addEventListener('click', () => {
            const audio = new Audio();
            if (notificationSound === 'custom') {
                audio.src = document.getElementById('custom-sound-url').value;
            } else if (notificationSound !== 'none') {
                audio.src = 'data:audio/wav;base64,UklGRiYAAABXQVZFZm10IBAAAAABAAEAQB8AAAB9AAACABAAZGF0YQIAAAAAAA==';
            }
            if (audio.src) audio.play().catch(() => {});
        });
        
        document.getElementById('apply-css-btn').addEventListener('click', () => {
            const css = document.getElementById('custom-css-input').value;
            customStyleManager.applyCustomStyles(css);
            customCSSEnabled = true;
            localStorage.setItem('trixbox-custom-css', 'true');
            alert('✅ Custom styles applied!');
        });
        
        document.getElementById('reset-css-btn').addEventListener('click', () => {
            customStyleManager.resetStyles();
            document.getElementById('custom-css-input').value = customStyleManager.getDefaultStyles();
            alert('✅ Styles reset to default theme!');
        });
        
        document.getElementById('load-default-btn').addEventListener('click', () => {
            document.getElementById('custom-css-input').value = customStyleManager.getDefaultStyles();
        });
        
        document.getElementById('custom-sound-url').addEventListener('change', (e) => {
            localStorage.setItem('trixbox-custom-sound-url', e.target.value);
        });
        
        document.getElementById('close-settings-btn').addEventListener('click', () => {
            modalOverlay.remove();
        });
        
        modalOverlay.addEventListener('click', (e) => {
            if (e.target === modalOverlay) modalOverlay.remove();
        });
    };

    // --- 3A. THEME SELECTOR MODAL ---
    const showThemeSelector = () => {
        const themeSelectorStyle = `
            .trixbox-theme-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-theme-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); max-width: 400px; max-height: 500px; overflow-y: auto; }
            .trixbox-theme-content h2 { margin-top: 0; color: ${theme.buttonPrimary}; }
            .trixbox-theme-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; margin: 20px 0; }
            .trixbox-theme-option { padding: 12px; background: ${theme.buttonSecondary}; border: 2px solid transparent; border-radius: 5px; cursor: pointer; transition: all 0.2s; color: ${theme.text}; }
            .trixbox-theme-option:hover { background: ${theme.buttonPrimary}; }
            .trixbox-theme-option.active { border-color: ${theme.buttonPrimary}; background: ${theme.buttonPrimary}; }
            .trixbox-theme-btn { color: white; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; margin: 10px 5px 0 0; font-size: 14px; background: ${theme.buttonPrimary}; }
        `;
        
        if (!document.getElementById('trixbox-theme-style')) {
            const styleSheet = document.createElement("style");
            styleSheet.id = 'trixbox-theme-style';
            styleSheet.innerText = themeSelectorStyle;
            document.head.appendChild(styleSheet);
        }
        
        const modalOverlay = document.createElement('div');
        modalOverlay.className = 'trixbox-theme-overlay';
        
        let themeOptionsHTML = '<div class="trixbox-theme-grid">';
        themes.forEach(t => {
            const isActive = t === currentTheme ? 'active' : '';
            themeOptionsHTML += `<div class="trixbox-theme-option ${isActive}" data-theme="${t}">${t}</div>`;
        });
        themeOptionsHTML += '</div>';
        
        modalOverlay.innerHTML = `
            <div class="trixbox-theme-content">
                <h2>Choose Theme</h2>
                ${themeOptionsHTML}
                <button class="trixbox-theme-btn close-theme-btn">Close</button>
            </div>
        `;
        document.body.appendChild(modalOverlay);
        
        modalOverlay.querySelectorAll('.trixbox-theme-option').forEach(option => {
            option.addEventListener('click', () => {
                const themeName = option.getAttribute('data-theme');
                modalOverlay.querySelectorAll('.trixbox-theme-option').forEach(o => o.classList.remove('active'));
                option.classList.add('active');
                applyTheme(themeName);
            });
        });
        
        modalOverlay.querySelector('.close-theme-btn').onclick = () => { modalOverlay.remove(); };
        modalOverlay.onclick = (e) => {
            if (e.target === modalOverlay) modalOverlay.remove();
        };
    };

    // Create the chat library script
    const chatLibraryScript = document.createElement('script');
    chatLibraryScript.src = 'https://iframe.chat/scripts/main.min.js';
    document.head.appendChild(chatLibraryScript);

    // Create the iframe FIRST (before initialization)
    const chatIframe = document.createElement('iframe');
    chatIframe.src = 'https://iframe.chat/embed?chat=15234533';
    chatIframe.id = 'chattable';
    Object.assign(chatIframe.style, {
        width: '100%', height: '100%', border: 'none',
        backgroundColor: 'transparent'
    });
    iframeWrapper.appendChild(chatIframe);
    document.body.appendChild(chatContainer);

    // Initialize Chattable after iframe and library are ready
    const initializeChattable = () => {
        if (window.chattable && window.chattable.initialize && typeof window.chattable.initialize === 'function') {
            const initConfig = {
                theme: currentTheme.toLowerCase().replace(/\s+/g, '')
            };
            
            // Add custom stylesheet if CSS is enabled
            const customStyles = localStorage.getItem('trixbox-custom-styles');
            if (customCSSEnabled && customStyles) {
                initConfig.stylesheet = customStyles;
            }
            
            window.chattable.initialize(initConfig);
            console.log('TrixBox: Chattable initialized with config:', initConfig);
        } else {
            // Retry if library not ready
            if (!window.chattableInitRetries) {
                window.chattableInitRetries = 0;
            }
            window.chattableInitRetries++;
            
            if (window.chattableInitRetries < 50) {
                setTimeout(initializeChattable, 100);
            } else {
                console.error('TrixBox: Failed to initialize Chattable after 50 attempts');
            }
        }
    };

    // Wait for DOM to be fully loaded before initializing
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', () => {
            setTimeout(initializeChattable, 1000);
        });
    } else {
        setTimeout(initializeChattable, 1000);
    }
 
    const headerLinkBlocker = document.createElement('div');
    Object.assign(headerLinkBlocker.style, { position: 'absolute', top: '0px', left: '0px', width: '100px', height: '45px', zIndex: '2' });
    iframeWrapper.appendChild(headerLinkBlocker);
 
    const settingsLinkBlocker = document.createElement('div');
    Object.assign(settingsLinkBlocker.style, {
        position: 'absolute', bottom: '12px', left: '50%',
        transform: 'translateX(-50%)',
        width: '140px',
        height: '25px',
        zIndex: '2'
    });
    iframeWrapper.appendChild(settingsLinkBlocker);
 
    // --- 4. ADD FUNCTIONALITY (TOGGLE, DRAG, ETC.) ---
    toggleIcon.addEventListener('click', () => { chatContainer.style.display = 'flex'; toggleIcon.style.display = 'none'; });
 
    closeButton.addEventListener('click', (e) => {
        e.stopPropagation();
        chatContainer.style.display = 'none';
        toggleIcon.style.display = 'flex';
    });

    settingsBtn.addEventListener('click', (e) => {
        e.stopPropagation();
        showAdvancedSettings();
    });

    changelogBtn.addEventListener('click', (e) => {
        e.stopPropagation();
        showChangelogModal();
    });
 
    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';
    });

    // Show changelog on first load or when version changes
    const lastSeenVersion = localStorage.getItem('trixbox-version');
    if (lastSeenVersion !== '0.3.2') {
        setTimeout(() => showChangelogModal(), 2000);
        localStorage.setItem('trixbox-version', '0.3.2');
    }
 
    // --- 5. RUN THE UPDATE CHECKER ---
    checkForUpdates();
})();