Greasy Fork

Greasy Fork is available in English.

SLITHER.IO STANDALONE CHAT MOD (NOW TALK WITH SLITHER GAMERS!)

Adds the 143X community chat with profiles, GIFs, Discord integration, and settings to Slither.io.

// ==UserScript==
// @name         SLITHER.IO STANDALONE CHAT MOD (NOW TALK WITH SLITHER GAMERS!)
// @namespace    http://tampermonkey.net/
// @version      11.6
// @description  Adds the 143X community chat with profiles, GIFs, Discord integration, and settings to Slither.io.
// @author       dxxthly & waynesg
// @match        http://slither.io/
// @match        https://slither.io/
// @match        http://slither.com/io
// @match        https://slither.com/io
// @grant        none
// @icon         https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcQUNcRl2Rh40pZLhgffYGFDRLbYJ4qfMNwddQ&s
// ==/UserScript==

(function() {
    'use strict';

    // --- NEW: THE UNBREAKABLE GUARDIAN ---
    // This injects a high-priority stylesheet to override any malicious CSS.
    (function createGuardianStylesheet() {
        const guardianStyle = document.createElement('style');
        guardianStyle.id = 'mod-guardian-styles';
        // The '!important' flag ensures these rules win against any injected styles.
        guardianStyle.textContent = `
            html {
                filter: none !important;
                transform: none !important;
            }
            body {
                transform: none !important;
            }
        `;
        (document.head || document.documentElement).appendChild(guardianStyle);
        console.log('Guardian Stylesheet is active.');
    })();
    // --- END OF GUARDIAN ---

    let hasInitialized = false;

    // =================================================================================
    // INITIALIZATION CHECKER
    // This is the new, robust way to start the script.
    // It will repeatedly check if the slither.io page is ready.
    // =================================================================================
    const initChecker = setInterval(() => {
        // We check for the 'login' div, which is a reliable indicator that the core page has loaded.
        if (document.getElementById('login') && !hasInitialized) {
            hasInitialized = true; // Prevents the script from running multiple times
            clearInterval(initChecker); // Stop checking once we've started
            main(); // Run the main script logic
        }
    }, 100); // Check every 100 milliseconds

    // REPLACE WITH THIS
    const systemAccounts = [
        "system",
        "discord_bot",
        "system_badge" // <-- THE FIX IS HERE
    ];

    // =================================================================================
    // MAIN SCRIPT FUNCTION
    // All of our chat logic now lives inside this function.
    // =================================================================================
    function main() {
        // --- CONFIGURATION ---
        const config = {
            chatMaxMessages: 75,
            firebaseConfig: {
                apiKey: "AIzaSyCtTloqGNdhmI3Xt0ta11vF0MQJHiKpO7Q",
                authDomain: "chatforslither.firebaseapp.com",
                databaseURL: "https://chatforslither-default-rtdb.firebaseio.com",
                projectId: "chatforslither",
                storageBucket: "chatforslither.appspot.com",
                messagingSenderId: "1045559625491",
                appId: "1:1045559625491:web:79eb8200eb87edac00bce6"
            },
            devList: [{ uid: "CiOpgh1RLBg3l5oXn0SAho66Po93" }, { uid: "PZA5qgKWsPTXc278pyx7NwROf313" }, { uid: "P75eMwh756Rb6h1W6iqQfHN2Dm92" }],
            vipMembers: [{ uid: "crcOY9hoRrfayStCxMVm7Zdx2W92", name: "stevao" }, { uid: "DhGhICAZwkRa7wuMsyquM9a5uO92", name: "LUANBLAYNER" }]
        };

        // --- STYLES (CSS INJECTION) ---
        const style = document.createElement('style');
        style.textContent = `
            #chat-container { position: fixed; left: 20px; top: 100px; width: 380px; height: 400px; z-index: 9999; display: flex; flex-direction: column; background: rgba(28, 28, 32, 0.97); border: 1px solid #4CAF50; border-radius: 8px; box-shadow: 0 5px 20px rgba(0,0,0,0.3); overflow: hidden; user-select: none; resize: both; min-width: 320px; min-height: 250px; }
            #chat-header { display: flex; align-items: center; border-bottom: 1px solid #4CAF50; background: rgba(0,0,0,0.2); cursor: move; }
            .chat-tab { flex: 1; padding: 10px 12px; text-align: center; cursor: pointer; font-weight: 500; transition: background 0.2s, color 0.2s; }
            #chat-tab-main { background: rgba(76, 175, 80, 0.25); color: #fff; } #chat-tab-users { background: transparent; color: #ccc; }
            #chat-settings-btn, #chat-hide-btn { background: none; border: none; color: #ccc; font-size: 20px; padding: 0 12px; cursor: pointer; transition: color 0.2s; border-left: 1px solid rgba(255,255,255,0.1); }
            #chat-settings-btn:hover, #chat-hide-btn:hover { color: white; }
            #chat-area { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
            #chat-body, #online-users { flex: 1; padding: 10px 15px; overflow-y: auto; display: flex; flex-direction: column; gap: 5px; scrollbar-width: thin; scrollbar-color: #4CAF50 rgba(0,0,0,0.2); }
            #online-users { display: none; }
            #chat-input-container { display: flex; align-items: center; border-top: 1px solid #4CAF50; }
            #chat-input { flex-grow: 1; padding: 12px 15px; border: none; background: transparent; color: #e0e0e0; outline: none; font-size: 14px; }
            .chat-username { font-weight: bold; cursor: pointer; text-decoration: underline dotted; }
            .chat-modal-overlay { display: none; position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; z-index: 10001; background: rgba(0,0,0,0.75); align-items: center; justify-content: center; }
            .chat-modal-content { background: #2E2E34; border-radius: 10px; padding: 25px 30px; min-width: 400px; display: flex; flex-direction: column; gap: 15px; border: 1px solid #555; }
            .chat-modal-content h2 { margin: 0 0 10px 0; color: #4CAF50; text-align: center; }
            .settings-field { display: flex; flex-direction: column; gap: 5px; } .settings-field label { color: #bbb; font-size: 0.9em; }
            .settings-field input[type="text"] { width: 100%; box-sizing: border-box; padding: 10px; background: #222; border: 1px solid #555; border-radius: 5px; color: #eee; font-size: 1em; }
            .color-input-wrapper { display: flex; align-items: center; gap: 10px; padding: 5px; background: #222; border: 1px solid #555; border-radius: 5px; }
            .color-input-wrapper input[type="color"] { width: 30px; height: 30px; border: none; background: none; cursor: pointer; padding: 0; }
            .color-input-wrapper input[type="text"] { flex-grow: 1; border: none; background: none; color: #eee; padding: 5px; }
            .modal-buttons { display: flex; justify-content: flex-end; gap: 10px; margin-top: 10px; }
            .modal-btn { padding: 9px 20px; border: none; border-radius: 5px; font-size: 0.95em; cursor: pointer; }
            #settings-save-btn { background: #4CAF50; color: #fff; } #settings-cancel-btn { background: #555; color: #fff; }
            .profile-popup { position: fixed; z-index: 10002; display: flex; flex-direction: column; align-items: center; left: 50%; top: 50%; transform: translate(-50%, -50%); min-width: 280px; background: #2A2A2F; color: #e0e0e0; border-radius: 12px; border: 1px solid #4CAF50; box-shadow: 0 8px 32px rgba(0,0,0,0.35); padding: 24px 30px; }
            .profile-popup .avatar { width: 72px; height: 72px; border-radius: 50%; object-fit: cover; border: 3px solid #4CAF50; margin-bottom: 16px; }
            .profile-popup .close-btn { position: absolute; top: 12px; right: 12px; background: none; border: none; color: #aaa; font-size: 1.6em; cursor: pointer; }
        `;
        document.head.appendChild(style);

        // --- HELPER FUNCTIONS ---
        const escapeHTML = (str) => { const p = document.createElement('p'); p.appendChild(document.createTextNode(str)); return p.innerHTML; };
        const isDev = (uid) => config.devList.some(dev => dev.uid === uid);
        const isVip = (uid, name) => config.vipMembers.some(vip => vip.uid === uid && vip.name.toLowerCase() === (name || '').toLowerCase());
        const rainbowTextStyle = (name) => name.split('').map((char, i) => `<span style="color:${["#ef3550","#f48fb1","#7e57c2","#2196f3","#26c6da","#43a047","#eeff41","#f9a825","#ff5722"][i % 9]}; font-weight: bold;">${char}</span>`).join('');
        const vipGlowStyle = (name, color) => `<span style="color:#fff;font-weight:bold;text-shadow:0 0 5px #fff, 0 0 10px ${color}, 0 0 15px ${color};">${name}</span>`;

        // --- UI & MODAL CREATION ---
        const chatContainer = document.createElement('div'); chatContainer.id = 'chat-container';
        chatContainer.innerHTML = `
            <div id="chat-header"> <div id="chat-tab-main" class="chat-tab">143X Chat</div> <div id="chat-tab-users" class="chat-tab">Online Users</div> <button id="chat-settings-btn" title="Settings">⚙️</button> <button id="chat-hide-btn" title="Hide Chat">×</button> </div>
            <div id="chat-area"> <div id="chat-body"><p style="color:#888; text-align:center;">Initializing...</p></div> <div id="online-users"></div> <div id="chat-input-container"><input id="chat-input" type="text" placeholder="Connecting..." disabled></div> </div>`;
        document.body.appendChild(chatContainer);
        makeDraggable(chatContainer, document.getElementById('chat-header'));
        const settingsModal = document.createElement('div'); settingsModal.id = 'settings-modal-overlay'; settingsModal.className = 'chat-modal-overlay';
        settingsModal.innerHTML = `
            <div id="settings-modal" class="chat-modal-content">
                <h2>Chat & Profile Settings</h2>
                <div class="settings-field"><label>Nickname</label><input type="text" id="settings-nickname" maxlength="20"></div>
                                <div class="settings-field"><label>Chat Name Color</label>
                    <div class="color-input-wrapper">
                        <input type="color" id="settings-name-color">
                    </div>
                </div>
                <div class="settings-field"><label>Profile Avatar URL</label><input type="text" id="settings-avatar"></div>
                <div class="settings-field"><label>Profile Motto</label><input type="text" id="settings-motto" maxlength="60"></div>
                <div class="modal-buttons"><button id="settings-cancel-btn" class="modal-btn">Cancel</button><button id="settings-save-btn" class="modal-btn">Save</button></div>
            </div>`;
        document.body.appendChild(settingsModal);

        // --- FIREBASE & CHAT LOGIC ---
        function loadFirebaseAndInit() {
            const script1 = document.createElement('script'); script1.src = 'https://www.gstatic.com/firebasejs/8.10.0/firebase-app.js';
            script1.onload = () => { const script2 = document.createElement('script'); script2.src = 'https://www.gstatic.com/firebasejs/8.10.0/firebase-database.js';
                script2.onload = () => { const script3 = document.createElement('script'); script3.src = 'https://www.gstatic.com/firebasejs/8.10.0/firebase-auth.js';
                    script3.onload = () => initializeChat(window.firebase); document.head.appendChild(script3); }; document.head.appendChild(script2); }; document.head.appendChild(script1);
        }

        function initializeChat(firebase) {
            if (!firebase.apps.length) firebase.initializeApp(config.firebaseConfig);
            const auth = firebase.auth(); const db = firebase.database();
            auth.signInAnonymously().catch(err => console.error("Firebase Sign-In Error:", err));
            auth.onAuthStateChanged(user => {
                if (user) {
                    const chatInput = document.getElementById('chat-input'); chatInput.disabled = false; chatInput.placeholder = 'Type and press Enter...'; document.getElementById('chat-body').innerHTML = '';
                    let nickname = localStorage.getItem("slitherChatNickname");
                    if (!nickname) { nickname = prompt("Welcome! Please enter a nickname:", "Player") || "Anon"; localStorage.setItem("slitherChatNickname", nickname); }
                    const userRef = db.ref("onlineUsers/" + user.uid);
                    userRef.onDisconnect().remove();
                    const localData = { name: nickname, chatNameColor: localStorage.getItem("slitherChatNameColor") || "#FFD700", profileAvatar: localStorage.getItem("slitherChatAvatar") || "", profileMotto: localStorage.getItem("slitherChatMotto") || "" };
                    userRef.update({ ...localData, uid: user.uid, lastActive: firebase.database.ServerValue.TIMESTAMP });
                    setInterval(() => userRef.update({ lastActive: Date.now() }), 45000);
                    listenForOnlineUsers(db, user.uid);
                    setupEventListeners(db, user.uid);
                }
            });

            let chatMessagesArray = [];
            let latestTimeLoaded = 0;

            const chatBody = document.getElementById('chat-body');
            const currentUser = auth.currentUser;

            // Step 1: Load initial messages
            db.ref("slitherChat").orderByChild("time").limitToLast(config.chatMaxMessages).once("value", async (snapshot) => {
                if (!snapshot.exists()) return;
                snapshot.forEach(child => { chatMessagesArray.push({ key: child.key, ...child.val() }); });
                chatMessagesArray.sort((a, b) => a.time - b.time);
                if (chatMessagesArray.length > 0) { latestTimeLoaded = chatMessagesArray[chatMessagesArray.length - 1].time; }
                if (chatBody) {
                    chatBody.innerHTML = '';
                    for (const msg of chatMessagesArray) { await renderChatMessage(msg, chatBody, currentUser?.uid); }
                    chatBody.scrollTop = chatBody.scrollHeight;
                }
                // Step 2: Listen for ONLY new messages from now on
                db.ref("slitherChat").orderByChild("time").startAt(latestTimeLoaded + 1).on("child_added", async (newSnapshot) => {
                    const newMsg = { key: newSnapshot.key, ...newSnapshot.val() };
                    if (chatMessagesArray.some(m => m.key === newMsg.key)) return;
                    chatMessagesArray.push(newMsg);
                    if (chatMessagesArray.length > config.chatMaxMessages) chatMessagesArray.shift();
                    if (chatBody) {
                        while (chatBody.children.length >= config.chatMaxMessages) { chatBody.removeChild(chatBody.firstChild); }
                        await renderChatMessage(newMsg, chatBody, currentUser?.uid, true);
                    }
                });
            });
        }

        
        function renderChatMessage(msg, chatBodyElement, currentUid, shouldScroll = false) {
            // Check if user is ignored
            if (window.standaloneState && window.standaloneState.ignoredUsers && window.standaloneState.ignoredUsers[msg.uid]) return;
            if (!msg || !msg.uid) return;

            // --- 1. SETUP & VALIDATION ---
            const isValidHexColor = (color) => /^#([0-9a-fA-F]{3}){1,2}$/.test(color);
            let userColor = (msg.chatNameColor && isValidHexColor(msg.chatNameColor)) ? msg.chatNameColor : '#FFD700';

            const isSystemMessage = systemAccounts.includes(msg.uid);
            const isDiscordBot = msg.uid === 'discord_bot';
            const displayName = escapeHTML(msg.name || 'Anon');

            let nameHtml;
            let roleTagHTML = '';

            // --- 2. BUILD THE NAME HTML BASED ON USER TYPE ---

            if (isDiscordBot) {
                // Discord names are not clickable as they don't have in-game profiles
                nameHtml = `<span style="color:${userColor};font-weight:bold;">${displayName}</span>`;
                roleTagHTML = ` <span style="background: #7289DA; color: #fff; padding: 2px 7px; border-radius: 4px; font-size: 0.8em; font-weight: 700; vertical-align:middle;">DISCORD</span>`;
            } else if (isDev(msg.uid)) {
                // Dev names are clickable
                nameHtml = `<span class="chat-username" data-uid="${msg.uid}" style="cursor:pointer;">${rainbowTextStyle(displayName)}</span>`;
                roleTagHTML = ` <span style="background: #E91E63; color: #fff; padding: 2px 7px; border-radius: 4px; font-size: 0.8em; font-weight: 700; vertical-align:middle;">DEV</span>`;
            } else if (isVip(msg.uid, msg.name)) {
                // VIP names are clickable
                nameHtml = `<span class="chat-username" data-uid="${msg.uid}" style="cursor:pointer;">${vipGlowStyle(displayName, userColor)}</span>`;
                roleTagHTML = ` <span style="background: #9C27B0; color: #fff; padding: 2px 7px; border-radius: 4px; font-size: 0.8em; font-weight: 700; vertical-align:middle;">VIP</span>`;
            } else if (isSystemMessage) {
                // System names are not clickable
                nameHtml = `<span style="color:${userColor};font-weight:bold;">System</span>`;
                roleTagHTML = ` <span style="background: #e74c3c; color: #fff; padding: 2px 7px; border-radius: 4px; font-size: 0.8em; font-weight: 700; vertical-align:middle;">SYSTEM</span>`;
            } else {
                // *** THIS IS THE KEY CHANGE FOR REGULAR USERS ***
                // We add the class and data-uid to make them clickable too.
                nameHtml = `<span class="chat-username" data-uid="${msg.uid}" style="color:${userColor};font-weight:bold;cursor:pointer;">${displayName}</span>`;
            }

            // --- 3. HANDLE MESSAGE CONTENT (IMAGES, HTML, ETC.) ---
            let finalMessage = '';
            const imageRegex = /(https?:\/\/[^\s]+\.(?:png|jpg|jpeg|gif|webp))/i;
            const imageMatch = msg.text.match(imageRegex);

            if (imageMatch) {
                // If there's an image, separate it from the text
                const textPart = escapeHTML(msg.text.replace(imageRegex, '').trim());
                finalMessage = `${textPart}<br><img src="${imageMatch[0]}" style="max-width:90%; border-radius:6px; margin-top:5px; cursor:pointer;" onclick="window.open('${imageMatch[0]}', '_blank')">`;
            } else if (isSystemMessage || isDev(msg.uid)) {
                // Allow HTML for Devs and System messages
                finalMessage = msg.text;
            } else {
                // Escape HTML for everyone else to prevent injection
                finalMessage = escapeHTML(msg.text);
            }

            // --- 4. ASSEMBLE AND RENDER THE FINAL ELEMENT ---
            const el = document.createElement('div');
            const borderColor = (msg.uid === currentUid) ? '#4CAF50' : '#444';
            const bgColor = (msg.uid === currentUid) ? 'rgba(76, 175, 80, 0.12)' : 'rgba(255,255,255,0.04)';

            el.style.cssText = `margin-bottom: 8px; word-break: break-word; background: ${bgColor}; padding: 8px 12px; border-radius: 6px; color: #ddd; font-family: inherit; font-size: 14px; line-height: 1.5; border-left: 3px solid ${borderColor};`;

            const timestamp = new Date(msg.time).toLocaleTimeString([], { hour12: true, hour: '2-digit', minute: '2-digit' });
            el.innerHTML = `<span style="color:#888; font-size:0.9em; margin-right:5px;">${timestamp}</span> <b>${nameHtml}${roleTagHTML}:</b> ${finalMessage}`;

            chatBodyElement.appendChild(el);

            // Auto-scroll logic
            if (shouldScroll || chatBodyElement.scrollTop >= chatBodyElement.scrollHeight - chatBodyElement.clientHeight - 150) {
                chatBodyElement.scrollTop = chatBodyElement.scrollHeight;
            }
        }

        function listenForOnlineUsers(db, currentUid) {
            const onlineUsersRef = db.ref("onlineUsers");
            const onlineUsersDiv = document.getElementById('online-users');
            const onlineUsersTab = document.getElementById('chat-tab-users');
            const tenMinutesAgo = Date.now() - (10 * 60 * 1000);

            onlineUsersRef.orderByChild('lastActive').startAt(tenMinutesAgo).on('value', (snapshot) => {
                if (!onlineUsersDiv) return;
                onlineUsersDiv.innerHTML = '';
                let userCount = 0;
                if (snapshot.exists()) {
                    snapshot.forEach(childSnapshot => {
                        const user = childSnapshot.val();
                        if (!user || !user.name) return;
                        userCount++;

                        const userEl = document.createElement('div');
                        userEl.style.cssText = `padding: 5px; cursor: pointer; border-radius: 4px; display:flex; align-items:center;`;
                        userEl.className = 'chat-username'; // Make it clickable
                        userEl.dataset.uid = user.uid; // Store UID for click event

                        const userColor = (user.chatNameColor && /^#([0-9a-fA-F]{3}){1,2}$/.test(user.chatNameColor)) ? user.chatNameColor : '#FFFFFF';

                        let nameHtml;
                        if (isDev(user.uid)) {
                            nameHtml = rainbowTextStyle(escapeHTML(user.name));
                        } else if (isVip(user.uid, user.name)) {
                            nameHtml = vipGlowStyle(escapeHTML(user.name), userColor);
                        } else {
                            nameHtml = `<span style="color:${userColor};">${escapeHTML(user.name)}</span>`;
                        }

                        userEl.innerHTML = nameHtml;
                        onlineUsersDiv.appendChild(userEl);
                    });
                }
                onlineUsersTab.textContent = `Online Users (${userCount})`;
            });
        }


                async function showUserProfile(db, uid) {
            // Prevent opening multiple popups
            if (document.querySelector('.profile-popup')) return;

            try {
                const userSnapshot = await db.ref(`onlineUsers/${uid}`).once('value');
                if (!userSnapshot.exists()) {
                    console.log("User not found or offline.");
                    return;
                }
                const userData = userSnapshot.val();

                const popup = document.createElement('div');
                popup.className = 'profile-popup';

                const defaultAvatar = 'https://i.imgur.com/M6NYjjO.jpeg';
                const avatarUrl = (userData.profileAvatar || '').trim() || defaultAvatar;

                popup.innerHTML = `
                    <button class="close-btn">×</button>
                    <img src="${escapeHTML(avatarUrl)}" class="avatar" onerror="this.src='${defaultAvatar}'">
                    <h3 style="margin: 0; color: ${escapeHTML(userData.chatNameColor || '#fff')};">${escapeHTML(userData.name || 'Anonymous')}</h3>
                    <p style="color: #bbb; font-style: italic; margin: 5px 0 15px 0;">"${escapeHTML(userData.profileMotto || 'No motto.')}"</p>
                    <div class="profile-actions">
                        <!-- Add buttons for ignore, etc. here if needed -->
                    </div>
                `;

                document.body.appendChild(popup);

                // Add event listener to the close button
                popup.querySelector('.close-btn').addEventListener('click', () => {
                    popup.remove();
                });

            } catch (error) {
                console.error("Error fetching user profile:", error);
            }
        }

        // --- EVENT LISTENERS ---
        function setupEventListeners(db, uid) {
            document.getElementById('chat-tab-main').addEventListener('click', () => { document.getElementById('chat-body').style.display = 'flex'; document.getElementById('online-users').style.display = 'none'; document.getElementById('chat-tab-main').style.background='rgba(76, 175, 80, 0.25)'; document.getElementById('chat-tab-users').style.background='transparent';});
            document.getElementById('chat-tab-users').addEventListener('click', () => { document.getElementById('chat-body').style.display = 'none'; document.getElementById('online-users').style.display = 'flex'; document.getElementById('chat-tab-users').style.background='rgba(76, 175, 80, 0.25)'; document.getElementById('chat-tab-main').style.background='transparent';});
            document.getElementById('chat-input').addEventListener('keydown', (e) => { if (e.key === 'Enter') { const text = e.target.value.trim(); if (text) { const name = localStorage.getItem("slitherChatNickname"); const color = localStorage.getItem("slitherChatNameColor") || "#FFD700"; db.ref("slitherChat").push({ uid, name, text, time: firebase.database.ServerValue.TIMESTAMP, chatNameColor: color }); db.ref("discordBridge").push({ uid, name, text, time: firebase.database.ServerValue.TIMESTAMP }); e.target.value = ''; } } });
            const settingsOverlay = document.getElementById('settings-modal-overlay');
            document.getElementById('chat-settings-btn').addEventListener('click', () => {
                document.getElementById('settings-nickname').value = localStorage.getItem("slitherChatNickname") || '';
                // CORRECTED: Only set the value for the existing color picker
                document.getElementById('settings-name-color').value = localStorage.getItem("slitherChatNameColor") || '#FFD700';
                document.getElementById('settings-avatar').value = localStorage.getItem("slitherChatAvatar") || '';
                document.getElementById('settings-motto').value = localStorage.getItem("slitherChatMotto") || '';
                settingsOverlay.style.display = 'flex';
            });
            document.getElementById('settings-cancel-btn').addEventListener('click', () => settingsOverlay.style.display = 'none');
            const cPicker = document.getElementById('settings-name-color');
            document.getElementById('settings-save-btn').addEventListener('click', () => { const newData = { name: document.getElementById('settings-nickname').value.trim().slice(0, 20) || 'Anon', chatNameColor: cPicker.value, profileAvatar: document.getElementById('settings-avatar').value.trim(), profileMotto: document.getElementById('settings-motto').value.trim() }; localStorage.setItem("slitherChatNickname", newData.name); localStorage.setItem("slitherChatNameColor", newData.chatNameColor); localStorage.setItem("slitherChatAvatar", newData.profileAvatar); localStorage.setItem("slitherChatMotto", newData.profileMotto); db.ref("onlineUsers/" + uid).update(newData); settingsOverlay.style.display = 'none'; });
            document.getElementById('chat-hide-btn').addEventListener('click', () => document.getElementById('chat-container').style.display = 'none');
            document.body.addEventListener('click', e => { if (e.target.closest('.chat-username')) showUserProfile(db, e.target.closest('.chat-username').dataset.uid); });
        }

        // --- UTILITY ---
        function makeDraggable(el, handle) { let p1=0, p2=0, p3=0, p4=0; handle.onmousedown = (e) => { e.preventDefault(); p3 = e.clientX; p4 = e.clientY; document.onmouseup = () => {document.onmouseup=null; document.onmousemove=null;}; document.onmousemove = (e) => { e.preventDefault(); p1=p3-e.clientX; p2=p4-e.clientY; p3=e.clientX; p4=e.clientY; el.style.top=(el.offsetTop-p2)+"px"; el.style.left=(el.offsetLeft-p1)+"px"; }; }; }

        // --- RUN ---
        loadFirebaseAndInit();
    }
})();