OpenFrontIO Auto-Join Lobby

Auto-join lobbies based on game mode preferences (FFA, Team with all team configurations, player filters). Tested and 100% functional against OpenFront v0.26.16

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

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

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         OpenFrontIO Auto-Join Lobby
// @namespace    http://tampermonkey.net/
// @version      1.4.7
// @description  Auto-join lobbies based on game mode preferences (FFA, Team with all team configurations, player filters). Tested and 100% functional against OpenFront v0.26.16
// @author       DeLoVaN
// @homepageURL  https://github.com/DeLoWaN/openfront-autojoin-lobby
// @match        https://openfront.io/*
// @match        https://*.openfront.io/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// ==/UserScript==

(function() {
    'use strict';

    // Tested and 100% functional against OpenFront v0.26.16

    // Configuration
    const CONFIG = {
        pollInterval: 1000, // ms - how often to check for matching lobbies
        rootURL: 'https://openfront.io/' // Root URL for lobby detection
    };

    // State
    let autoJoinEnabled = true; // Always start with ON (search active by default)
    let criteriaList = []; // List of criteria (can contain multiple modes)
    let monitoringInterval = null;
    let timerInterval = null;
    let gameInfoInterval = null; // Interval for updating current game info display
    let joinedLobbies = new Set(); // To avoid double joins
    let searchStartTime = null; // Timestamp when auto-join search started
    let gameFoundTime = null; // Timestamp when game was found (for fixed timer display)
    let isJoining = false; // Prevent concurrent join attempts
    let soundEnabled = true; // Sound notification enabled by default
    let recentlyLeftLobbyID = null; // Track lobby ID that was just left to prevent auto-rejoin
    let joinMode = 'notify'; // 'autojoin' or 'notify' - default to 'notify'
    let notifiedLobbies = new Set(); // Track lobbies we've notified about
    let lastNotifiedGameID = null; // Track the last game we notified about (for timer reset)
    let notificationTimeout = null; // Timeout for auto-dismissing notification
    let gameFoundAudio = null; // Preloaded audio for game found notification
    let gameStartAudio = null; // Preloaded audio for game start bell

    // Load settings from storage
    function loadSettings() {
        const saved = GM_getValue('autoJoinSettings', null);
        if (saved) {
            // Always keep search enabled
            autoJoinEnabled = true;
            criteriaList = saved.criteria || [];
            soundEnabled = saved.soundEnabled !== undefined ? saved.soundEnabled : true;
            joinMode = saved.joinMode || 'notify'; // Default to 'notify'
        }
    }

    // Save settings to storage
    function saveSettings() {
        GM_setValue('autoJoinSettings', {
            enabled: autoJoinEnabled,
            criteria: criteriaList,
            soundEnabled: soundEnabled,
            joinMode: joinMode
        });
    }

    // Update search timer display
    function updateSearchTimer() {
        const timerElement = document.getElementById('search-timer');
        if (!timerElement) return;

        if (!autoJoinEnabled || searchStartTime === null) {
            timerElement.style.display = 'none';
            gameFoundTime = null;
            return;
        }

        // If game was found, show fixed time
        if (gameFoundTime !== null) {
            const elapsed = Math.floor((gameFoundTime - searchStartTime) / 1000); // seconds
            const minutes = Math.floor(elapsed / 60);
            const seconds = elapsed % 60;
            timerElement.textContent = `Game found! (${minutes}m ${seconds}s)`;
            timerElement.style.display = 'inline';
            return;
        }

        // Otherwise show live timer
        const elapsed = Math.floor((Date.now() - searchStartTime) / 1000); // seconds
        const minutes = Math.floor(elapsed / 60);
        const seconds = elapsed % 60;

        timerElement.textContent = `Searching: ${minutes}m ${seconds}s`;
        timerElement.style.display = 'inline';
    }

    // Update current game info display (players per team for team games)
    function updateCurrentGameInfo() {
        const gameInfoElement = document.getElementById('current-game-info');
        if (!gameInfoElement) return;

        // Only show on lobby page
        if (!isOnLobbyPage()) {
            gameInfoElement.style.display = 'none';
            return;
        }

        // Always show the element to avoid resizing, but update content based on game type
        gameInfoElement.style.display = 'block';

        // Get current displayed lobby
        const publicLobby = document.querySelector('public-lobby');
        if (!publicLobby || !publicLobby.lobbies || !Array.isArray(publicLobby.lobbies) || publicLobby.lobbies.length === 0) {
            gameInfoElement.textContent = 'Current game: No game';
            gameInfoElement.classList.add('not-applicable');
            return;
        }

        const currentLobby = publicLobby.lobbies[0];
        if (!currentLobby || !currentLobby.gameConfig) {
            gameInfoElement.textContent = 'Current game: No game';
            gameInfoElement.classList.add('not-applicable');
            return;
        }

        const config = currentLobby.gameConfig;
        
        // Check if it's a team game
        if (config.gameMode !== 'Team') {
            gameInfoElement.textContent = 'Current game: Not a team game';
            gameInfoElement.classList.add('not-applicable');
            return;
        }

        // Get game capacity
        const gameCapacity = currentLobby.maxClients || config.maxClients || config.maxPlayers || null;
        if (gameCapacity === null) {
            gameInfoElement.textContent = 'Current game: Capacity unknown';
            gameInfoElement.classList.add('not-applicable');
            return;
        }

        // Get team configuration
        const playerTeams = config.playerTeams;
        
        // Display the info for team games
        // For Duos/Trios/Quads, show the mode name directly
        if (playerTeams === 'Duos') {
            gameInfoElement.textContent = 'Current game: Duos';
            gameInfoElement.classList.remove('not-applicable');
        } else if (playerTeams === 'Trios') {
            gameInfoElement.textContent = 'Current game: Trios';
            gameInfoElement.classList.remove('not-applicable');
        } else if (playerTeams === 'Quads') {
            gameInfoElement.textContent = 'Current game: Quads';
            gameInfoElement.classList.remove('not-applicable');
        } else if (typeof playerTeams === 'number') {
            // For modes with number of teams, show players per team
            const playersPerTeam = getPlayersPerTeam(playerTeams, gameCapacity);
            if (playersPerTeam === null) {
                gameInfoElement.textContent = 'Current game: Team configuration unknown';
                gameInfoElement.classList.add('not-applicable');
                return;
            }
            gameInfoElement.textContent = `Current game: ${playersPerTeam} players per team (${playerTeams} teams)`;
            gameInfoElement.classList.remove('not-applicable');
        } else {
            gameInfoElement.textContent = 'Current game: Team configuration unknown';
            gameInfoElement.classList.add('not-applicable');
        }
    }

    // Helper function to get players per team from playerTeams
    // When playerTeams is a string like 'Quads', 'Trios', 'Duos', it represents players per team directly
    // When playerTeams is a number, it represents the number of teams, so we calculate players per team
    function getPlayersPerTeam(playerTeams, gameCapacity) {
        // String values represent players per team directly
        if (playerTeams === 'Duos') return 2;
        if (playerTeams === 'Trios') return 3;
        if (playerTeams === 'Quads') return 4;
        // Number values represent the number of teams
        if (typeof playerTeams === 'number' && playerTeams > 0) {
            return Math.floor(gameCapacity / playerTeams);
        }
        return null;
    }

    // Check if lobby matches criteria
    function matchesCriteria(lobby, criteriaList) {
        if (!lobby || !lobby.gameConfig || !criteriaList || criteriaList.length === 0) {
            return false;
        }

        const config = lobby.gameConfig;
        // Get game capacity (max players the game can hold)
        // Try common property names for capacity
        const gameCapacity = lobby.maxClients || config.maxClients || config.maxPlayers || null;

        // Check if lobby matches at least one criterion
        for (const criteria of criteriaList) {
            let matches = false;

            if (criteria.gameMode === 'FFA') {
                if (config.gameMode !== 'Free For All') {
                    continue; // Move to next criterion
                }
                matches = true;
            } else if (criteria.gameMode === 'Team') {
                if (config.gameMode !== 'Team') {
                    continue;
                }

                // If a team count is specified, check it
                if (criteria.teamCount !== null && criteria.teamCount !== undefined) {
                    const playerTeams = config.playerTeams;

                    // Handle special modes
                    // Note: Duos/Trios/Quads are game modes (players per team), NOT team counts
                    // They should only match their exact string values, not numeric team counts
                    if (criteria.teamCount === 'Duos') {
                        if (playerTeams !== 'Duos') {
                            continue;
                        }
                    } else if (criteria.teamCount === 'Trios') {
                        if (playerTeams !== 'Trios') {
                            continue;
                        }
                    } else if (criteria.teamCount === 'Quads') {
                        if (playerTeams !== 'Quads') {
                            continue;
                        }
                    } else if (typeof criteria.teamCount === 'number') {
                        if (playerTeams !== criteria.teamCount) {
                            continue;
                        }
                    }
                }

                matches = true;
            }

            // If mode matches, check player filters
            if (matches) {
                if (criteria.gameMode === 'FFA') {
                    // For FFA, check based on game capacity (total players)
                    if (gameCapacity === null) {
                        // No capacity info available, skip capacity filtering
                        return true;
                    }

                    // Check minPlayers (minimum game capacity)
                    if (criteria.minPlayers !== null && criteria.minPlayers !== undefined) {
                        if (gameCapacity < criteria.minPlayers) {
                            continue; // Game capacity is less than minimum required
                        }
                    }

                    // Check maxPlayers (maximum game capacity)
                    if (criteria.maxPlayers !== null && criteria.maxPlayers !== undefined) {
                        if (gameCapacity > criteria.maxPlayers) {
                            continue; // Game capacity exceeds maximum allowed
                        }
                    }
                } else if (criteria.gameMode === 'Team') {
                    // For Team games, check based on players per team
                    // BUT: Duos/Trios/Quads always have 2/3/4 players per team by definition,
                    // so we skip the players per team filter for these modes
                    const playerTeams = config.playerTeams;
                    const isFixedPlayersPerTeam = playerTeams === 'Duos' || playerTeams === 'Trios' || playerTeams === 'Quads';
                    
                    // Only apply players per team filter for modes with number of teams (2/3/4/5/6/7 teams)
                    if (!isFixedPlayersPerTeam) {
                        if (gameCapacity === null) {
                            // No capacity info available, skip capacity filtering
                            return true;
                        }

                        // Get players per team
                        const playersPerTeam = getPlayersPerTeam(playerTeams, gameCapacity);
                        
                        if (playersPerTeam === null) {
                            // Cannot determine players per team, skip capacity filtering
                            return true;
                        }

                        // Check minPlayers (minimum players per team)
                        if (criteria.minPlayers !== null && criteria.minPlayers !== undefined) {
                            if (playersPerTeam < criteria.minPlayers) {
                                continue; // Players per team is less than minimum required
                            }
                        }

                        // Check maxPlayers (maximum players per team)
                        if (criteria.maxPlayers !== null && criteria.maxPlayers !== undefined) {
                            if (playersPerTeam > criteria.maxPlayers) {
                                continue; // Players per team exceeds maximum allowed
                            }
                        }
                    }
                    // For Duos/Trios/Quads, skip players per team filtering (always 2/3/4 by definition)
                }

                // All criteria satisfied
                return true;
            }
        }

        // No criterion matches
        return false;
    }

    // Helper function to check if we're on the lobby page (allowing hash/path variations)
    function isOnLobbyPage() {
        try {
            // Parse the configured root URL
            const configUrl = new URL(CONFIG.rootURL);
            const configOrigin = configUrl.origin;
            const configHostname = configUrl.hostname;

            // Get current location base
            const currentOrigin = window.location.origin;
            const currentHostname = window.location.hostname;

            // Check if origins/hostnames match
            if (currentOrigin !== configOrigin && currentHostname !== configHostname) {
                return false;
            }

            // Get current pathname and hash (normalize)
            const currentPath = window.location.pathname;
            const currentHash = window.location.hash;

            // Normalize path: remove trailing slashes and check if it's root or lobby-related
            const normalizedPath = currentPath.replace(/\/+$/, '') || '/';
            const isRootPath = normalizedPath === '/';

            // Check if path is a lobby-related path suffix (e.g., /public-lobby)
            const isLobbyPath = normalizedPath === '/public-lobby' || normalizedPath.startsWith('/public-lobby/');

            // Allow hash fragments like #/public-lobby or empty hash
            const isLobbyHash = !currentHash || currentHash === '#' || currentHash === '#/public-lobby' || currentHash.startsWith('#/public-lobby/');

            // Consider it the lobby page if:
            // 1. Origin/hostname matches AND
            // 2. Either:
            //    - Path is root/empty AND hash is empty or lobby-related, OR
            //    - Path is a lobby path (path-based routing, hash doesn't matter)
            return (isRootPath && isLobbyHash) || isLobbyPath;
        } catch (error) {
            // Fallback to strict comparison if URL parsing fails
            console.warn('[Auto-Join] Error checking lobby page, using fallback:', error);
            return location.href === CONFIG.rootURL;
        }
    }

    // Check lobbies and auto-join if match found
    async function checkLobbies() {
        try {
            // Always update current game info display
            updateCurrentGameInfo();

            // Prevent concurrent join attempts
            if (isJoining) {
                return;
            }

            // Check if we have criteria
            if (!criteriaList || criteriaList.length === 0) {
                console.warn('[Auto-Join] No criteria configured. Please select at least one game mode.');
                return;
            }

            // Check we're not already in a game
            if (!isOnLobbyPage()) {
                return;
            }

            // Get lobbies from the public-lobby component
            const publicLobby = document.querySelector('public-lobby');
            if (!publicLobby || !publicLobby.lobbies || !Array.isArray(publicLobby.lobbies)) {
                return; // Component not ready yet
            }
            const lobbies = publicLobby.lobbies;

            // In notify mode, check if we need to reset the timer when a new game appears
            if (joinMode === 'notify' && gameFoundTime !== null && lastNotifiedGameID !== null) {
                // Check if the currently displayed lobby is different from the one we notified about
                const currentLobby = lobbies.length > 0 ? lobbies[0] : null;
                const currentLobbyID = currentLobby ? currentLobby.gameID : null;
                
                // If current lobby is different from the one we notified about, reset timer and continue searching
                if (currentLobbyID !== lastNotifiedGameID) {
                    gameFoundTime = null;
                    lastNotifiedGameID = null;
                    // Restart timer if it was stopped
                    if (!timerInterval) {
                        timerInterval = setInterval(() => {
                            updateSearchTimer();
                        }, 1000);
                    }
                    updateSearchTimer();
                    updateUI({ status: null });
                }
            }

            // Search for a lobby matching criteria
            for (const lobby of lobbies) {

                if (matchesCriteria(lobby, criteriaList)) {
                    // Skip if this is the lobby we just left (permanently block auto-rejoin)
                    if (recentlyLeftLobbyID === lobby.gameID) {
                        continue;
                    }
                    
                    if (joinMode === 'notify') {
                        // Notify mode: show notification instead of joining
                        if (!notifiedLobbies.has(lobby.gameID)) {
                            showGameFoundNotification(lobby);
                            playGameFoundSound(); // Chime when match is found
                            notifiedLobbies.add(lobby.gameID);
                            // Mark that game was found and stop timer updates
                            gameFoundTime = Date.now();
                            lastNotifiedGameID = lobby.gameID;
                            stopTimer();
                            updateSearchTimer();
                            // Update UI status
                            updateUI({ status: 'found', gameID: lobby.gameID });
                            return; // Only notify about one lobby at a time
                        }
                    } else {
                        // Auto-join mode: existing behavior
                        // Check we haven't already joined this lobby
                        if (!joinedLobbies.has(lobby.gameID)) {
                            joinLobby(lobby);
                            joinedLobbies.add(lobby.gameID);
                            // Clear recently left lobby ID since we're joining a different one
                            recentlyLeftLobbyID = null;
                            return; // Only join one lobby at a time
                        }
                    }
                }
            }
        } catch (error) {
            console.error('[Auto-Join] Error checking lobbies:', error);
        }
    }


    // Preload audio files for better performance
    function preloadSounds() {
        try {
            gameFoundAudio = new Audio('https://github.com/DeLoWaN/openfront-autojoin-lobby/raw/refs/heads/main/notification_sounds/new-notification-014-363678.mp3');
            gameFoundAudio.volume = 0.5;
            gameFoundAudio.preload = 'auto';
            
            gameStartAudio = new Audio('https://github.com/DeLoWaN/openfront-autojoin-lobby/raw/refs/heads/main/notification_sounds/opening-bell-421471.mp3');
            gameStartAudio.volume = 0.5;
            gameStartAudio.preload = 'auto';
            
        } catch (error) {
            console.warn('[Auto-Join] Could not preload audio files:', error);
        }
    }

    // Play sound notification (chime) when a game is found
    function playGameFoundSound() {
        if (!soundEnabled || !gameFoundAudio) return;

        try {
            gameFoundAudio.currentTime = 0; // Reset to start
            gameFoundAudio.play().catch(error => {
                console.warn('[Auto-Join] Could not play game found sound:', error);
            });
        } catch (error) {
            console.warn('[Auto-Join] Could not play game found sound:', error);
        }
    }

    // Play boxing ring bell sound when game starts/joins
    function playGameStartSound() {
        if (!soundEnabled || !gameStartAudio) return;

        try {
            gameStartAudio.currentTime = 0; // Reset to start
            gameStartAudio.play().catch(error => {
                console.warn('[Auto-Join] Could not play game start sound:', error);
            });
        } catch (error) {
            console.warn('[Auto-Join] Could not play game start sound:', error);
        }
    }

    // Get formatted game details text for notification
    function getGameDetailsText(lobby) {
        if (!lobby || !lobby.gameConfig) {
            return 'Game Found!';
        }

        const config = lobby.gameConfig;
        const gameCapacity = lobby.maxClients || config.maxClients || config.maxPlayers || null;

        if (config.gameMode === 'Free For All') {
            if (gameCapacity !== null) {
                return `Game Found! FFA - ${gameCapacity} players`;
            }
            return 'Game Found! FFA';
        } else if (config.gameMode === 'Team') {
            const playerTeams = config.playerTeams;
            
            let teamCountText = '';
            if (playerTeams === 'Duos') {
                teamCountText = 'Duos';
            } else if (playerTeams === 'Trios') {
                teamCountText = 'Trios';
            } else if (playerTeams === 'Quads') {
                teamCountText = 'Quads';
            } else if (typeof playerTeams === 'number') {
                teamCountText = `${playerTeams} teams`;
            } else {
                teamCountText = 'Team';
            }

            // For Duos/Trios/Quads, just show the mode name (always 2/3/4 players per team)
            if (playerTeams === 'Duos' || playerTeams === 'Trios' || playerTeams === 'Quads') {
                return `Game Found! Team (${teamCountText})`;
            }
            
            // For modes with number of teams, show players per team
            if (typeof playerTeams === 'number' && gameCapacity !== null) {
                const playersPerTeam = getPlayersPerTeam(playerTeams, gameCapacity);
                if (playersPerTeam !== null) {
                    return `Game Found! Team (${teamCountText}) - ${playersPerTeam} players per team`;
                }
            }
            
            return `Game Found! Team (${teamCountText})`;
        }

        return 'Game Found!';
    }

    // Dismiss notification
    function dismissNotification(targetElement = null) {
        const elements = targetElement
            ? [targetElement]
            : Array.from(document.querySelectorAll('.game-found-notification'));

        if (elements.length === 0) {
            if (notificationTimeout) {
                clearTimeout(notificationTimeout);
                notificationTimeout = null;
            }
            return;
        }

        elements.forEach(element => {
            if (!element.classList.contains('notification-dismissing')) {
                element.classList.add('notification-dismissing');
            }
            setTimeout(() => element.remove(), 300);
        });

        if (notificationTimeout) {
            clearTimeout(notificationTimeout);
            notificationTimeout = null;
        }
    }

    // Show game found notification
    function showGameFoundNotification(lobby) {
        // If notification already exists, dismiss it first (show only latest)
        const existingNotification = document.getElementById('game-found-notification');
        if (existingNotification) {
            dismissNotification(existingNotification);
            // Wait a bit for the dismiss animation
            setTimeout(() => {
                createNewNotification(lobby);
            }, 50);
        } else {
            createNewNotification(lobby);
        }
    }

    // Create new notification element
    function createNewNotification(lobby) {
        const notification = document.createElement('div');
        notification.id = 'game-found-notification';
        notification.className = 'game-found-notification';
        
        const message = getGameDetailsText(lobby);
        notification.textContent = message;
        
        // Click to dismiss
        notification.addEventListener('click', () => dismissNotification(notification));
        
        // Add to body
        document.body.appendChild(notification);
        
        // Trigger animation
        setTimeout(() => {
            notification.classList.add('notification-visible');
        }, 10);
        
        // Auto-dismiss after 10 seconds
        notificationTimeout = setTimeout(() => {
            dismissNotification(notification);
        }, 10000);
    }

    // Join a lobby - only join if we can click the button (for visual feedback)
    function joinLobby(lobby) {
        // Prevent concurrent joins
        if (isJoining) {
            return;
        }

        // Try to get the public lobby component and check if the lobby matches
        const publicLobby = document.querySelector('public-lobby');
        const lobbyButton = publicLobby?.querySelector('button');

        // Only join if we can click the button (lobby must be displayed and button enabled)
        if (!publicLobby || !lobbyButton || lobbyButton.disabled) {
            // Remove from joinedLobbies so we can retry later
            joinedLobbies.delete(lobby.gameID);
            return;
        }

        // Get current displayed lobby (public-lobby shows lobbies[0])
        const currentLobby = publicLobby.lobbies?.[0];
        if (!currentLobby || currentLobby.gameID !== lobby.gameID) {
            // Remove from joinedLobbies so we can retry when it becomes visible
            joinedLobbies.delete(lobby.gameID);
            return;
        }

        // All conditions met - proceed with join
        isJoining = true;

        // Mark that game was found and stop timer updates
        gameFoundTime = Date.now();
        stopTimer();
        updateSearchTimer();

        // Play sound notification (chime when match is found)
        playGameFoundSound();

        // Click the button - component will handle join with visual feedback (green highlight)
        lobbyButton.click();

        // Update UI to indicate we joined
        updateUI({ status: 'joined', gameID: lobby.gameID });

        // Reset joining flag after a delay (allows time for join to process)
        setTimeout(() => {
            isJoining = false;
        }, 2000);
    }

    // Try to auto-start search if conditions are met
    function tryAutoStartSearch() {
        // Only auto-start if:
        // 1. We're in lobby
        // 2. Search is enabled
        // 3. We have valid criteria
        // 4. We're not already monitoring
        if (!isOnLobbyPage()) {
            return;
        }

        if (!autoJoinEnabled) {
            return;
        }

        // Build criteria from UI
        criteriaList = buildCriteriaFromUI();

        if (!criteriaList || criteriaList.length === 0) {
            return;
        }

        // Start monitoring if not already running
        if (!monitoringInterval) {
            startMonitoring();
            saveSettings();
            updateUI();
        }
    }

    // Start monitoring lobbies
    function startMonitoring() {
        if (monitoringInterval) return; // Already monitoring

        searchStartTime = Date.now();
        gameFoundTime = null; // Reset game found time
        lastNotifiedGameID = null; // Reset last notified game ID
        // Clear notified lobbies when starting new search
        notifiedLobbies.clear();
        updateSearchTimer();

        // Update timer display every second
        timerInterval = setInterval(() => {
            updateSearchTimer();
        }, 1000);

        // Start checking lobbies
        monitoringInterval = setInterval(checkLobbies, CONFIG.pollInterval);
    }

    // Stop monitoring lobbies
    function stopMonitoring() {
        if (monitoringInterval) {
            clearInterval(monitoringInterval);
            monitoringInterval = null;
        }
        stopTimer();
        searchStartTime = null;
        gameFoundTime = null;
        lastNotifiedGameID = null;
        updateSearchTimer();
        // Dismiss any active notification
        dismissNotification();
        // Clear notified lobbies
        notifiedLobbies.clear();
        // Reset status to inactive when stopping
        updateUI({ status: null });
    }

    // Stop timer interval
    function stopTimer() {
        if (timerInterval) {
            clearInterval(timerInterval);
            timerInterval = null;
        }
    }

    // Start game info update interval
    function startGameInfoUpdates() {
        if (gameInfoInterval) return; // Already running
        
        // Update immediately
        updateCurrentGameInfo();
        
        // Update every second
        gameInfoInterval = setInterval(() => {
            updateCurrentGameInfo();
        }, 1000);
    }

    // Stop game info update interval
    function stopGameInfoUpdates() {
        if (gameInfoInterval) {
            clearInterval(gameInfoInterval);
            gameInfoInterval = null;
        }
        // Hide the display
        const gameInfoElement = document.getElementById('current-game-info');
        if (gameInfoElement) {
            gameInfoElement.style.display = 'none';
        }
    }

    // Update slider visual fill and sync with hidden inputs
    function updateSliderRange(minSliderId, maxSliderId, minInputId, maxInputId, fillId, minValueId, maxValueId) {
        const minSlider = document.getElementById(minSliderId);
        const maxSlider = document.getElementById(maxSliderId);
        const minInput = document.getElementById(minInputId);
        const maxInput = document.getElementById(maxInputId);
        const fill = document.getElementById(fillId);
        const minValueDisplay = document.getElementById(minValueId);
        const maxValueDisplay = document.getElementById(maxValueId);

        if (!minSlider || !maxSlider || !fill) return;

        const minVal = parseInt(minSlider.value, 10);
        const maxVal = parseInt(maxSlider.value, 10);
        const min = parseInt(minSlider.min, 10);
        const max = parseInt(minSlider.max, 10);

        // Ensure min <= max
        if (minVal > maxVal) {
            if (document.activeElement === minSlider) {
                maxSlider.value = minVal;
                if (maxInput) maxInput.value = minVal === max ? '' : minVal;
            } else {
                minSlider.value = maxVal;
                if (minInput) minInput.value = maxVal === min ? '' : maxVal;
            }
            return updateSliderRange(minSliderId, maxSliderId, minInputId, maxInputId, fillId, minValueId, maxValueId);
        }

        // Calculate fill position and width
        const minPercent = ((minVal - min) / (max - min)) * 100;
        const maxPercent = ((maxVal - min) / (max - min)) * 100;

        fill.style.left = minPercent + '%';
        fill.style.width = (maxPercent - minPercent) + '%';

        // Update hidden inputs
        if (minInput) {
            minInput.value = minVal === min ? '' : minVal;
        }
        if (maxInput) {
            maxInput.value = maxVal === max ? '' : maxVal;
        }

        // Update displayed values
        if (minValueDisplay) {
            minValueDisplay.textContent = minVal === min ? 'Any' : minVal;
        }
        if (maxValueDisplay) {
            maxValueDisplay.textContent = maxVal === max ? 'Any' : maxVal;
        }
    }

    // Initialize slider from hidden input values
    function initializeSlider(minSliderId, maxSliderId, minInputId, maxInputId, fillId, minValueId, maxValueId) {
        const minSlider = document.getElementById(minSliderId);
        const maxSlider = document.getElementById(maxSliderId);
        const minInput = document.getElementById(minInputId);
        const maxInput = document.getElementById(maxInputId);

        if (!minSlider || !maxSlider) return;

        const min = parseInt(minSlider.min, 10);
        const max = parseInt(minSlider.max, 10);

        // Get values from hidden inputs or use defaults
        let minVal = min;
        let maxVal = max;

        if (minInput && minInput.value) {
            minVal = Math.max(min, Math.min(max, parseInt(minInput.value, 10)));
        }
        if (maxInput && maxInput.value) {
            maxVal = Math.max(min, Math.min(max, parseInt(maxInput.value, 10)));
        }

        // Ensure min <= max
        if (minVal > maxVal) {
            minVal = maxVal;
        }

        minSlider.value = minVal;
        maxSlider.value = maxVal;

        updateSliderRange(minSliderId, maxSliderId, minInputId, maxInputId, fillId, minValueId, maxValueId);
    }

    // Get number value from input
    function getNumberValue(id) {
        const input = document.getElementById(id);
        if (!input || !input.value) return null;
        const value = parseInt(input.value, 10);
        return isNaN(value) ? null : value;
    }

    // Helper function to update mode label styles
    function updateModeLabels(isAutoJoin) {
        const labelLeft = document.querySelector('.mode-label-left');
        const labelRight = document.querySelector('.mode-label-right');
        if (labelLeft && labelRight) {
            if (isAutoJoin) {
                labelLeft.style.opacity = '0.6';
                labelLeft.style.color = 'rgba(255, 255, 255, 0.7)';
                labelRight.style.opacity = '1';
                labelRight.style.color = 'rgba(255, 255, 255, 0.95)';
            } else {
                labelLeft.style.opacity = '1';
                labelLeft.style.color = 'rgba(255, 255, 255, 0.95)';
                labelRight.style.opacity = '0.6';
                labelRight.style.color = 'rgba(255, 255, 255, 0.7)';
            }
        }
    }

    // Get all selected team counts from UI (returns array)
    function getAllTeamCountValues() {
        const selectedCounts = [];
        const teamCountIds = [
            { id: 'autojoin-team-duos', value: 'Duos' },
            { id: 'autojoin-team-trios', value: 'Trios' },
            { id: 'autojoin-team-quads', value: 'Quads' },
            { id: 'autojoin-team-2', value: 2 },
            { id: 'autojoin-team-3', value: 3 },
            { id: 'autojoin-team-4', value: 4 },
            { id: 'autojoin-team-5', value: 5 },
            { id: 'autojoin-team-6', value: 6 },
            { id: 'autojoin-team-7', value: 7 }
        ];

        teamCountIds.forEach(({ id, value }) => {
            const checkbox = document.getElementById(id);
            if (checkbox && checkbox.checked) {
                selectedCounts.push(value);
            }
        });

        return selectedCounts.length > 0 ? selectedCounts : null;
    }

    // Helper function for select/deselect all team counts
    function setAllTeamCounts(checked) {
        const checkboxes = [
            'autojoin-team-2', 'autojoin-team-3', 'autojoin-team-4', 'autojoin-team-5',
            'autojoin-team-6', 'autojoin-team-7', 'autojoin-team-duos', 'autojoin-team-trios',
            'autojoin-team-quads'
        ];
        checkboxes.forEach(id => {
            const checkbox = document.getElementById(id);
            if (checkbox) checkbox.checked = checked;
        });
    }

    // Build criteria list from UI
    function buildCriteriaFromUI() {
        const criteriaList = [];

        // Check FFA
        const ffaChecked = document.getElementById('autojoin-ffa').checked;
        if (ffaChecked) {
            const ffaCriteria = {
                gameMode: 'FFA',
                minPlayers: getNumberValue('autojoin-ffa-min') || null,
                maxPlayers: getNumberValue('autojoin-ffa-max') || null
            };
            criteriaList.push(ffaCriteria);
        }

        // Check Team
        const teamChecked = document.getElementById('autojoin-team').checked;
        if (teamChecked) {
            const selectedTeamCounts = getAllTeamCountValues();
            const minPlayers = getNumberValue('autojoin-team-min') || null;
            const maxPlayers = getNumberValue('autojoin-team-max') || null;

            if (selectedTeamCounts === null) {
                // No specific team counts selected, create one criteria that accepts all Team modes
                const teamCriteria = {
                    gameMode: 'Team',
                    teamCount: null,
                    minPlayers: minPlayers,
                    maxPlayers: maxPlayers
                };
                criteriaList.push(teamCriteria);
            } else {
                // Create a separate criteria for each selected team count
                for (const teamCount of selectedTeamCounts) {
                    // Duos/Trios/Quads are game modes with fixed players per team (2/3/4)
                    // Player per team filters do NOT apply to these modes
                    const isFixedPlayersPerTeam = teamCount === 'Duos' || teamCount === 'Trios' || teamCount === 'Quads';
                    
                    const teamCriteria = {
                        gameMode: 'Team',
                        teamCount: teamCount,
                        // Only include player filters for modes with variable team counts (2/3/4/5/6/7 teams)
                        minPlayers: isFixedPlayersPerTeam ? null : minPlayers,
                        maxPlayers: isFixedPlayersPerTeam ? null : maxPlayers
                    };
                    criteriaList.push(teamCriteria);
                }
            }
        }

        return criteriaList;
    }

    // Update UI based on state
    function updateUI(options = {}) {
        const statusIndicator = document.querySelector('#autojoin-status .status-indicator');
        const statusText = document.querySelector('#autojoin-status .status-text');

        if (statusIndicator) {
            // Always show as active (search is always on)
            statusIndicator.classList.add('active');
        }

        if (statusText) {
            // Show "Joined" if explicitly set
            if (options.status === 'joined') {
                statusText.textContent = 'Joined';
            } else {
                // In notify mode, always show "Searching" (timer will show "Game found!" when applicable)
                statusText.textContent = joinMode === 'notify' ? 'Searching' : 'Active';
            }
        }

        updateSearchTimer();
    }

    // Create UI
    function createUI() {
        // Check if UI already exists
        if (document.getElementById('openfront-autojoin-panel')) {
            return;
        }

        const panel = document.createElement('div');
        panel.id = 'openfront-autojoin-panel';
        panel.className = 'autojoin-panel';
        panel.innerHTML = `
            <div class="autojoin-header" id="autojoin-header-drag">
                <h3>Auto-Join Lobby</h3>
            </div>

            <div class="autojoin-content">
                <!-- Mode Selector -->
                <div class="join-mode-selector">
                    <span class="mode-label-left">Notify Only</span>
                    <label class="mode-toggle-switch">
                        <input type="checkbox" id="join-mode-toggle">
                        <span class="mode-toggle-slider"></span>
                    </label>
                    <span class="mode-label-right">Auto-Join</span>
                </div>

                <!-- FFA Section -->
                <div class="autojoin-mode-section">
                    <label class="mode-checkbox-label">
                        <input type="checkbox" id="autojoin-ffa" name="gameMode" value="FFA">
                        <span>FFA</span>
                    </label>

                    <div class="autojoin-mode-config" id="ffa-config" style="display: none;">
                        <div class="player-filter-info">
                            <small>Filter by max players:</small>
                        </div>
                        <div class="capacity-range-wrapper">
                            <div class="capacity-range-visual">
                                <div class="capacity-track">
                                    <div class="capacity-range-fill" id="ffa-range-fill"></div>
                                    <input type="range" id="autojoin-ffa-min-slider" min="1" max="100" value="1" class="capacity-slider capacity-slider-min">
                                    <input type="range" id="autojoin-ffa-max-slider" min="1" max="100" value="100" class="capacity-slider capacity-slider-max">
                                </div>
                                <div class="capacity-labels">
                                    <div class="capacity-label-group">
                                        <label for="autojoin-ffa-min-slider">Min:</label>
                                        <span class="capacity-value" id="ffa-min-value">Any</span>
                                    </div>
                                    <div class="capacity-label-group">
                                        <label for="autojoin-ffa-max-slider">Max:</label>
                                        <span class="capacity-value" id="ffa-max-value">Any</span>
                                    </div>
                                </div>
                            </div>
                            <div class="capacity-inputs-hidden">
                                <input type="number" id="autojoin-ffa-min" min="1" max="100" style="display: none;">
                                <input type="number" id="autojoin-ffa-max" min="1" max="100" style="display: none;">
                            </div>
                        </div>
                    </div>
                </div>

                <!-- Team Section -->
                <div class="autojoin-mode-section">
                    <label class="mode-checkbox-label">
                        <input type="checkbox" id="autojoin-team" name="gameMode" value="Team">
                        <span>Team</span>
                    </label>

                    <div class="autojoin-mode-config" id="team-config" style="display: none;">
                        <!-- Fixed Team Modes (Duos/Trios/Quads) - No player filters -->
                        <div class="team-mode-panel fixed-modes-panel">
                            <div class="panel-header">
                                <label style="display: block; margin-bottom: 6px; font-size: 0.9em; font-weight: 600;">Fixed Team Modes (Duos/Trios/Quads):</label>
                                <div style="display: flex; gap: 4px; margin-bottom: 6px;">
                                    <button type="button" id="autojoin-fixed-select-all" class="select-all-btn">Select All</button>
                                    <button type="button" id="autojoin-fixed-deselect-all" class="select-all-btn">Deselect All</button>
                                </div>
                            </div>
                            <div class="team-count-options">
                                <label><input type="checkbox" id="autojoin-team-duos" value="Duos"> Duos</label>
                                <label><input type="checkbox" id="autojoin-team-trios" value="Trios"> Trios</label>
                                <label><input type="checkbox" id="autojoin-team-quads" value="Quads"> Quads</label>
                            </div>
                        </div>

                        <!-- Variable Team Counts (2-7 teams) - With player filters -->
                        <div class="team-mode-panel variable-modes-panel">
                            <div class="panel-header">
                                <label style="display: block; margin-bottom: 6px; font-size: 0.9em; font-weight: 600;">Variable Team Counts (2-7 teams):</label>
                                <div style="display: flex; gap: 4px; margin-bottom: 6px;">
                                    <button type="button" id="autojoin-variable-select-all" class="select-all-btn">Select All</button>
                                    <button type="button" id="autojoin-variable-deselect-all" class="select-all-btn">Deselect All</button>
                                </div>
                            </div>
                            <div class="team-count-options">
                                <label><input type="checkbox" id="autojoin-team-2" value="2"> 2 teams</label>
                                <label><input type="checkbox" id="autojoin-team-3" value="3"> 3 teams</label>
                                <label><input type="checkbox" id="autojoin-team-4" value="4"> 4 teams</label>
                                <label><input type="checkbox" id="autojoin-team-5" value="5"> 5 teams</label>
                                <label><input type="checkbox" id="autojoin-team-6" value="6"> 6 teams</label>
                                <label><input type="checkbox" id="autojoin-team-7" value="7"> 7 teams</label>
                            </div>
                            <div class="player-filter-info">
                                <small>Filter by players per team:</small>
                            </div>
                            <div class="capacity-range-wrapper">
                                <div class="capacity-range-visual">
                                    <div class="capacity-track">
                                        <div class="capacity-range-fill" id="team-range-fill"></div>
                                        <input type="range" id="autojoin-team-min-slider" min="0" max="50" value="0" class="capacity-slider capacity-slider-min">
                                        <input type="range" id="autojoin-team-max-slider" min="0" max="50" value="50" class="capacity-slider capacity-slider-max">
                                    </div>
                                    <div class="capacity-labels">
                                        <div class="capacity-label-group">
                                            <label for="autojoin-team-min-slider">Min:</label>
                                            <span class="capacity-value" id="team-min-value">Any</span>
                                        </div>
                                        <div class="capacity-label-group">
                                            <label for="autojoin-team-max-slider">Max:</label>
                                            <span class="capacity-value" id="team-max-value">Any</span>
                                        </div>
                                    </div>
                                </div>
                                <div class="capacity-inputs-hidden">
                                    <input type="number" id="autojoin-team-min" min="0" max="50" style="display: none;">
                                    <input type="number" id="autojoin-team-max" min="0" max="50" style="display: none;">
                                </div>
                            </div>
                        </div>
                        <div class="current-game-info" id="current-game-info" style="display: none;"></div>
                    </div>
                </div>

                <div class="autojoin-status" id="autojoin-status">
                    <span class="status-label">Status:</span>
                    <span class="status-indicator"></span>
                    <span class="status-text">Searching</span>
                    <span class="search-timer" id="search-timer" style="display: none;"></span>
                </div>

                <div class="autojoin-settings">
                    <label class="sound-toggle-label">
                        <input type="checkbox" id="autojoin-sound-toggle">
                        <span>🔔 Sound</span>
                    </label>
                </div>
            </div>
        `;

        document.body.appendChild(panel);

        // Make panel draggable
        makePanelDraggable(panel);

        // Setup event listeners
        setupEventListeners();

        // Load saved settings into UI
        loadUIFromSettings();

        // Initialize sliders (in case no saved settings)
        initializeSlider('autojoin-ffa-min-slider', 'autojoin-ffa-max-slider', 'autojoin-ffa-min', 'autojoin-ffa-max', 'ffa-range-fill', 'ffa-min-value', 'ffa-max-value');
        initializeSlider('autojoin-team-min-slider', 'autojoin-team-max-slider', 'autojoin-team-min', 'autojoin-team-max', 'team-range-fill', 'team-min-value', 'team-max-value');

        // Update UI
        updateUI();

        // Auto-start search if criteria exist and we're in lobby
        setTimeout(() => {
            tryAutoStartSearch();
        }, 500); // Small delay to ensure page is ready
    }

    // Make panel draggable
    function makePanelDraggable(panel) {
        const header = document.getElementById('autojoin-header-drag');
        if (!header) return;

        let isDragging = false;
        let currentX;
        let currentY;
        let initialX;
        let initialY;
        let xOffset = 0;
        let yOffset = 0;

        // Load saved position
        const savedPos = GM_getValue('autoJoinPanelPosition', null);
        if (savedPos) {
            panel.style.left = savedPos.x + 'px';
            panel.style.top = savedPos.y + 'px';
            xOffset = savedPos.x;
            yOffset = savedPos.y;
        }

        header.addEventListener('mousedown', dragStart);
        document.addEventListener('mousemove', drag);
        document.addEventListener('mouseup', dragEnd);

        function dragStart(e) {

            initialX = e.clientX - xOffset;
            initialY = e.clientY - yOffset;

            if (e.target === header || header.contains(e.target)) {
                isDragging = true;
                header.style.cursor = 'grabbing';
            }
        }

        function drag(e) {
            if (isDragging) {
                e.preventDefault();
                currentX = e.clientX - initialX;
                currentY = e.clientY - initialY;

                xOffset = currentX;
                yOffset = currentY;

                panel.style.left = currentX + 'px';
                panel.style.top = currentY + 'px';
                panel.style.right = 'auto';
            }
        }

        function dragEnd(e) {
            initialX = currentX;
            initialY = currentY;
            isDragging = false;
            header.style.cursor = 'grab';

            // Save position
            GM_setValue('autoJoinPanelPosition', { x: xOffset, y: yOffset });
        }
    }

    // Setup event listeners
    function setupEventListeners() {

        // FFA checkbox - show/hide config
        const ffaCheckbox = document.getElementById('autojoin-ffa');
        const ffaConfig = document.getElementById('ffa-config');
        if (ffaCheckbox && ffaConfig) {
            ffaCheckbox.addEventListener('change', () => {
                ffaConfig.style.display = ffaCheckbox.checked ? 'block' : 'none';
                criteriaList = buildCriteriaFromUI();
                saveSettings();
                // Auto-start search if criteria are valid and we're in lobby
                tryAutoStartSearch();
            });
        }

        // Team checkbox - show/hide config
        const teamCheckbox = document.getElementById('autojoin-team');
        const teamConfig = document.getElementById('team-config');
        if (teamCheckbox && teamConfig) {
            teamCheckbox.addEventListener('change', () => {
                teamConfig.style.display = teamCheckbox.checked ? 'block' : 'none';
                criteriaList = buildCriteriaFromUI();
                saveSettings();
                // Auto-start search if criteria are valid and we're in lobby
                tryAutoStartSearch();
            });
        }

        // Select All / Deselect All buttons for fixed modes (Duos/Trios/Quads)
        const fixedSelectAllBtn = document.getElementById('autojoin-fixed-select-all');
        const fixedDeselectAllBtn = document.getElementById('autojoin-fixed-deselect-all');
        if (fixedSelectAllBtn) {
            fixedSelectAllBtn.addEventListener('click', () => {
                const checkboxes = ['autojoin-team-duos', 'autojoin-team-trios', 'autojoin-team-quads'];
                checkboxes.forEach(id => {
                    const checkbox = document.getElementById(id);
                    if (checkbox) checkbox.checked = true;
                });
                criteriaList = buildCriteriaFromUI();
                saveSettings();
                tryAutoStartSearch();
            });
        }
        if (fixedDeselectAllBtn) {
            fixedDeselectAllBtn.addEventListener('click', () => {
                const checkboxes = ['autojoin-team-duos', 'autojoin-team-trios', 'autojoin-team-quads'];
                checkboxes.forEach(id => {
                    const checkbox = document.getElementById(id);
                    if (checkbox) checkbox.checked = false;
                });
                criteriaList = buildCriteriaFromUI();
                saveSettings();
                tryAutoStartSearch();
            });
        }

        // Select All / Deselect All buttons for variable modes (2-7 teams)
        const variableSelectAllBtn = document.getElementById('autojoin-variable-select-all');
        const variableDeselectAllBtn = document.getElementById('autojoin-variable-deselect-all');
        if (variableSelectAllBtn) {
            variableSelectAllBtn.addEventListener('click', () => {
                const checkboxes = ['autojoin-team-2', 'autojoin-team-3', 'autojoin-team-4', 'autojoin-team-5', 'autojoin-team-6', 'autojoin-team-7'];
                checkboxes.forEach(id => {
                    const checkbox = document.getElementById(id);
                    if (checkbox) checkbox.checked = true;
                });
                criteriaList = buildCriteriaFromUI();
                saveSettings();
                tryAutoStartSearch();
            });
        }
        if (variableDeselectAllBtn) {
            variableDeselectAllBtn.addEventListener('click', () => {
                const checkboxes = ['autojoin-team-2', 'autojoin-team-3', 'autojoin-team-4', 'autojoin-team-5', 'autojoin-team-6', 'autojoin-team-7'];
                checkboxes.forEach(id => {
                    const checkbox = document.getElementById(id);
                    if (checkbox) checkbox.checked = false;
                });
                criteriaList = buildCriteriaFromUI();
                saveSettings();
                tryAutoStartSearch();
            });
        }

        // Listen to all team count checkbox changes
        const teamCountCheckboxes = [
            'autojoin-team-2', 'autojoin-team-3', 'autojoin-team-4', 'autojoin-team-5',
            'autojoin-team-6', 'autojoin-team-7', 'autojoin-team-duos', 'autojoin-team-trios',
            'autojoin-team-quads'
        ];
        teamCountCheckboxes.forEach(id => {
            const checkbox = document.getElementById(id);
            if (checkbox) {
                checkbox.addEventListener('change', () => {
                    criteriaList = buildCriteriaFromUI();
                    saveSettings();
                    tryAutoStartSearch();
                });
            }
        });

        // Setup slider event listeners
        const sliderPairs = [
            {
                minSlider: 'autojoin-ffa-min-slider',
                maxSlider: 'autojoin-ffa-max-slider',
                minInput: 'autojoin-ffa-min',
                maxInput: 'autojoin-ffa-max',
                fill: 'ffa-range-fill',
                minValue: 'ffa-min-value',
                maxValue: 'ffa-max-value'
            },
            {
                minSlider: 'autojoin-team-min-slider',
                maxSlider: 'autojoin-team-max-slider',
                minInput: 'autojoin-team-min',
                maxInput: 'autojoin-team-max',
                fill: 'team-range-fill',
                minValue: 'team-min-value',
                maxValue: 'team-max-value'
            }
        ];

        sliderPairs.forEach(pair => {
            const minSlider = document.getElementById(pair.minSlider);
            const maxSlider = document.getElementById(pair.maxSlider);

            if (minSlider) {
                minSlider.addEventListener('input', () => {
                    updateSliderRange(pair.minSlider, pair.maxSlider, pair.minInput, pair.maxInput, pair.fill, pair.minValue, pair.maxValue);
                    criteriaList = buildCriteriaFromUI();
                    saveSettings();
                    tryAutoStartSearch();
                });
            }

            if (maxSlider) {
                maxSlider.addEventListener('input', () => {
                    updateSliderRange(pair.minSlider, pair.maxSlider, pair.minInput, pair.maxInput, pair.fill, pair.minValue, pair.maxValue);
                    criteriaList = buildCriteriaFromUI();
                    saveSettings();
                    tryAutoStartSearch();
                });
            }
        });

        // Sound toggle
        const soundToggle = document.getElementById('autojoin-sound-toggle');
        if (soundToggle) {
            soundToggle.checked = soundEnabled;
            soundToggle.addEventListener('change', () => {
                soundEnabled = soundToggle.checked;
                saveSettings();
            });
        }

        // Mode selector toggle
        const modeToggle = document.getElementById('join-mode-toggle');
        if (modeToggle) {
            modeToggle.addEventListener('change', (e) => {
                joinMode = e.target.checked ? 'autojoin' : 'notify';
                updateModeLabels(e.target.checked);
                saveSettings();
                // Restart search if already running to apply mode change
                if (autoJoinEnabled && monitoringInterval) {
                    stopMonitoring();
                    startMonitoring();
                }
                // Update UI to reflect mode change
                updateUI();
            });
        }
        

        // Listen for leave-lobby event - restart search when user manually leaves
        document.addEventListener('leave-lobby', () => {
            if (autoJoinEnabled) {
                // Get the lobby ID that was just left
                // Try to get it from the current lobby display first
                let leftLobbyID = null;
                const publicLobby = document.querySelector('public-lobby');
                if (publicLobby && publicLobby.lobbies && publicLobby.lobbies.length > 0) {
                    const currentLobby = publicLobby.lobbies[0];
                    if (currentLobby && currentLobby.gameID) {
                        leftLobbyID = currentLobby.gameID;
                    }
                }
                // If not found in display, check joinedLobbies before clearing
                if (!leftLobbyID && joinedLobbies.size > 0) {
                    // Get the first (and likely only) lobby ID from the set
                    leftLobbyID = Array.from(joinedLobbies)[0];
                }
                if (leftLobbyID) {
                    recentlyLeftLobbyID = leftLobbyID;
                }
                // Clear joined lobbies to allow rejoining other lobbies
                joinedLobbies.clear();
                // Clear notified lobbies
                notifiedLobbies.clear();
                // Reset game found time
                gameFoundTime = null;
                lastNotifiedGameID = null;
                // Restart monitoring (this will reset the search timer)
                stopMonitoring();
                startMonitoring();
                saveSettings();
                updateUI();
            }
        });
    }

    // Load settings into UI
    function loadUIFromSettings() {
        // Load join mode
        const modeToggle = document.getElementById('join-mode-toggle');
        if (modeToggle) {
            modeToggle.checked = joinMode === 'autojoin';
            updateModeLabels(joinMode === 'autojoin');
        }

        // Load criteria into UI
        let teamCheckboxChecked = false;
        let teamMinPlayers = null;
        let teamMaxPlayers = null;

        for (const criteria of criteriaList) {
            if (criteria.gameMode === 'FFA') {
                const ffaCheckbox = document.getElementById('autojoin-ffa');
                if (ffaCheckbox) {
                    ffaCheckbox.checked = true;
                    const ffaConfig = document.getElementById('ffa-config');
                    if (ffaConfig) ffaConfig.style.display = 'block';

                    if (criteria.minPlayers) {
                        const minInput = document.getElementById('autojoin-ffa-min');
                        if (minInput) minInput.value = criteria.minPlayers;
                    }
                    if (criteria.maxPlayers) {
                        const maxInput = document.getElementById('autojoin-ffa-max');
                        if (maxInput) maxInput.value = criteria.maxPlayers;
                    }
                    // Initialize FFA slider from loaded values
                    initializeSlider('autojoin-ffa-min-slider', 'autojoin-ffa-max-slider', 'autojoin-ffa-min', 'autojoin-ffa-max', 'ffa-range-fill', 'ffa-min-value', 'ffa-max-value');
                }
            } else if (criteria.gameMode === 'Team') {
                // Mark team checkbox as checked (only once)
                if (!teamCheckboxChecked) {
                    const teamCheckbox = document.getElementById('autojoin-team');
                    if (teamCheckbox) {
                        teamCheckbox.checked = true;
                        teamCheckboxChecked = true;
                        const teamConfig = document.getElementById('team-config');
                        if (teamConfig) teamConfig.style.display = 'block';
                    }
                }

                // Store min/max players (they should be the same for all Team criteria)
                if (criteria.minPlayers !== null && criteria.minPlayers !== undefined) {
                    teamMinPlayers = criteria.minPlayers;
                }
                if (criteria.maxPlayers !== null && criteria.maxPlayers !== undefined) {
                    teamMaxPlayers = criteria.maxPlayers;
                }

                // Set team count checkbox (can have multiple)
                if (criteria.teamCount !== null && criteria.teamCount !== undefined) {
                    if (criteria.teamCount === 'Duos') {
                        const checkbox = document.getElementById('autojoin-team-duos');
                        if (checkbox) checkbox.checked = true;
                    } else if (criteria.teamCount === 'Trios') {
                        const checkbox = document.getElementById('autojoin-team-trios');
                        if (checkbox) checkbox.checked = true;
                    } else if (criteria.teamCount === 'Quads') {
                        const checkbox = document.getElementById('autojoin-team-quads');
                        if (checkbox) checkbox.checked = true;
                    } else if (typeof criteria.teamCount === 'number') {
                        const checkbox = document.getElementById(`autojoin-team-${criteria.teamCount}`);
                        if (checkbox) checkbox.checked = true;
                    }
                }
            }
        }

        // Set min/max players for Team (use the last values found, they should be consistent)
        if (teamCheckboxChecked) {
            if (teamMinPlayers !== null) {
                const minInput = document.getElementById('autojoin-team-min');
                if (minInput) minInput.value = teamMinPlayers;
            }
            if (teamMaxPlayers !== null) {
                const maxInput = document.getElementById('autojoin-team-max');
                if (maxInput) maxInput.value = teamMaxPlayers;
            }
            // Initialize Team slider from loaded values
            initializeSlider('autojoin-team-min-slider', 'autojoin-team-max-slider', 'autojoin-team-min', 'autojoin-team-max', 'team-range-fill', 'team-min-value', 'team-max-value');
        }
    }

    // Add CSS styles
    GM_addStyle(`
        .autojoin-panel {
            position: fixed;
            top: 20px;
            right: 20px;
            width: 460px;
            max-height: 90vh;
            overflow-y: auto;
            background: rgba(25, 25, 30, 0.85);
            border: 1px solid rgba(59, 130, 246, 0.4);
            border-radius: 8px;
            padding: 0;
            z-index: 10000;
            color: rgba(255, 255, 255, 0.9);
            font-family: sans-serif;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
            transition: opacity 0.3s ease, transform 0.3s ease;
        }

        .autojoin-panel.hidden {
            display: none;
        }

        .autojoin-header {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-bottom: 8px;
            border-bottom: 1px solid rgba(59, 130, 246, 0.3);
            padding: 8px 12px 6px 12px;
            cursor: grab;
            user-select: none;
        }

        .autojoin-header:active {
            cursor: grabbing;
        }

        .autojoin-content {
            padding: 0 12px 10px 12px;
        }

        .autojoin-header h3 {
            margin: 0;
            font-size: 1.05em;
            color: rgba(255, 255, 255, 0.95);
        }


        .autojoin-mode-section {
            margin-bottom: 8px;
            padding: 6px;
            background: rgba(59, 130, 246, 0.08);
            border-radius: 4px;
        }

        .mode-checkbox-label {
            display: flex;
            align-items: center;
            gap: 6px;
            font-weight: bold;
            cursor: pointer;
            margin-bottom: 6px;
            font-size: 0.95em;
            color: rgba(255, 255, 255, 0.95);
        }

        .mode-checkbox-label input[type="checkbox"] {
            width: 18px;
            height: 18px;
            cursor: pointer;
        }

        .autojoin-mode-config {
            margin-left: 26px;
            margin-top: 6px;
            padding: 6px;
            background: rgba(0, 0, 0, 0.2);
            border-radius: 4px;
        }


        .player-filter-info {
            margin-bottom: 6px;
            padding: 3px 0;
        }

        .player-filter-info small {
            color: rgba(255, 255, 255, 0.75);
            font-size: 0.9em;
        }

        .capacity-range-wrapper {
            margin-top: 6px;
        }

        .capacity-range-visual {
            position: relative;
            padding: 12px 0 6px 0;
        }

        .capacity-track {
            position: relative;
            height: 6px;
            background: rgba(59, 130, 246, 0.2);
            border-radius: 3px;
            margin-bottom: 8px;
        }

        .capacity-range-fill {
            position: absolute;
            height: 100%;
            background: rgba(59, 130, 246, 0.5);
            border-radius: 3px;
            pointer-events: none;
            opacity: 0.7;
            transition: left 0.1s ease, width 0.1s ease;
        }

        .capacity-slider {
            position: absolute;
            width: 100%;
            height: 6px;
            top: 0;
            left: 0;
            background: transparent;
            outline: none;
            -webkit-appearance: none;
            pointer-events: none;
            margin: 0;
        }

        .capacity-slider::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 18px;
            height: 18px;
            border-radius: 50%;
            background: rgba(59, 130, 246, 0.8);
            cursor: pointer;
            pointer-events: all;
            border: 2px solid rgba(255, 255, 255, 0.9);
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
            transition: transform 0.1s ease;
        }

        .capacity-slider::-webkit-slider-thumb:hover {
            transform: scale(1.1);
            background: rgba(96, 165, 250, 0.9);
        }

        .capacity-slider::-moz-range-thumb {
            width: 18px;
            height: 18px;
            border-radius: 50%;
            background: rgba(59, 130, 246, 0.8);
            cursor: pointer;
            pointer-events: all;
            border: 2px solid rgba(255, 255, 255, 0.9);
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
            transition: transform 0.1s ease;
        }

        .capacity-slider::-moz-range-thumb:hover {
            transform: scale(1.1);
            background: rgba(96, 165, 250, 0.9);
        }

        .capacity-slider-min {
            z-index: 2;
        }

        .capacity-slider-max {
            z-index: 1;
        }

        .capacity-labels {
            display: flex;
            justify-content: space-between;
            align-items: center;
            margin-top: 6px;
        }

        .capacity-label-group {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 3px;
        }

        .capacity-label-group label {
            font-size: 0.9em;
            color: rgba(255, 255, 255, 0.8);
            font-weight: normal;
            margin: 0;
        }

        .capacity-value {
            font-size: 0.9em;
            color: rgba(255, 255, 255, 0.95);
            font-weight: 500;
            min-width: 40px;
            text-align: center;
        }

        .capacity-inputs-hidden {
            display: none;
        }


        .team-mode-panel {
            margin-bottom: 12px;
            padding: 8px;
            background: rgba(0, 0, 0, 0.15);
            border-radius: 4px;
            border: 1px solid rgba(59, 130, 246, 0.2);
        }

        .team-mode-panel:last-child {
            margin-bottom: 0;
        }

        .fixed-modes-panel {
            border-color: rgba(147, 197, 253, 0.3);
        }

        .variable-modes-panel {
            border-color: rgba(16, 185, 129, 0.3);
        }

        .panel-header {
            margin-bottom: 6px;
        }

        .team-count-options {
            display: flex;
            flex-direction: column;
            gap: 3px;
            margin-top: 3px;
        }

        .team-count-options label {
            display: flex;
            align-items: center;
            gap: 4px;
            cursor: pointer;
            font-size: 0.9em;
            color: rgba(255, 255, 255, 0.9);
        }

        .autojoin-status {
            margin: 8px 0;
            padding: 6px;
            background: rgba(59, 130, 246, 0.15);
            border-radius: 4px;
            display: flex;
            align-items: center;
            gap: 8px;
            flex-wrap: wrap;
        }

        .status-label {
            font-weight: 600;
            color: rgba(255, 255, 255, 0.9);
            font-size: 0.9em;
        }

        .status-indicator {
            width: 10px;
            height: 10px;
            border-radius: 50%;
            background: rgba(239, 68, 68, 0.8);
        }

        .status-indicator.active {
            background: rgba(16, 185, 129, 0.8);
            animation: pulse 1s infinite;
        }


        @keyframes pulse {
            0%, 100% { opacity: 1; }
            50% { opacity: 0.5; }
        }

        .search-timer {
            margin-left: auto;
            font-size: 0.9em;
            color: rgba(147, 197, 253, 0.9);
            font-weight: 500;
        }

        .select-all-btn {
            background: rgba(59, 130, 246, 0.2);
            color: rgba(255, 255, 255, 0.9);
            border: 1px solid rgba(59, 130, 246, 0.4);
            border-radius: 4px;
            cursor: pointer;
            font-size: 0.85em;
            padding: 3px 8px;
            flex: 1;
        }

        .select-all-btn:hover {
            background: rgba(59, 130, 246, 0.35);
        }

        .autojoin-settings {
            margin: 8px 0 0 0;
            padding: 6px;
            background: rgba(59, 130, 246, 0.08);
            border-radius: 4px;
        }

        .sound-toggle-label {
            display: flex;
            align-items: center;
            gap: 6px;
            cursor: pointer;
            font-size: 0.9em;
            color: rgba(255, 255, 255, 0.9);
        }

        .sound-toggle-label input[type="checkbox"] {
            width: 18px;
            height: 18px;
            cursor: pointer;
        }

        /* Remove any extra spacing/margins that might cause gray bar */
        .autojoin-status {
            margin-bottom: 8px;
        }

        .current-game-info {
            margin: 6px 0;
            padding: 5px 8px;
            background: rgba(59, 130, 246, 0.12);
            border-radius: 4px;
            font-size: 0.9em;
            color: rgba(147, 197, 253, 0.9);
            text-align: center;
            border: 1px solid rgba(59, 130, 246, 0.25);
        }

        .current-game-info.not-applicable {
            background: rgba(100, 100, 100, 0.1);
            color: rgba(255, 255, 255, 0.5);
            border-color: rgba(100, 100, 100, 0.2);
            font-style: italic;
        }

        .join-mode-selector {
            display: flex;
            align-items: center;
            gap: 10px;
            margin-bottom: 8px;
            padding: 6px;
            background: rgba(59, 130, 246, 0.08);
            border-radius: 4px;
            justify-content: center;
        }

        .mode-label-left,
        .mode-label-right {
            font-size: 0.9em;
            color: rgba(255, 255, 255, 0.9);
            font-weight: 500;
            user-select: none;
            transition: opacity 0.3s, color 0.3s;
        }

        .mode-label-left {
            opacity: 1;
            color: rgba(255, 255, 255, 0.95);
        }

        .mode-label-right {
            opacity: 0.6;
            color: rgba(255, 255, 255, 0.7);
        }

        .mode-toggle-switch {
            position: relative;
            display: inline-block;
            width: 50px;
            height: 26px;
            cursor: pointer;
        }

        .mode-toggle-switch input {
            opacity: 0;
            width: 0;
            height: 0;
        }

        .mode-toggle-slider {
            position: absolute;
            cursor: pointer;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background-color: rgba(59, 130, 246, 0.3);
            transition: 0.3s;
            border-radius: 26px;
        }

        .mode-toggle-slider:before {
            position: absolute;
            content: "";
            height: 20px;
            width: 20px;
            left: 3px;
            bottom: 3px;
            background-color: white;
            transition: 0.3s;
            border-radius: 50%;
        }

        .mode-toggle-switch input:checked + .mode-toggle-slider {
            background-color: rgba(16, 185, 129, 0.6);
        }

        .mode-toggle-switch input:checked + .mode-toggle-slider:before {
            transform: translateX(24px);
        }

        .mode-toggle-switch:hover .mode-toggle-slider {
            box-shadow: 0 0 8px rgba(59, 130, 246, 0.5);
        }

        .game-found-notification {
            position: fixed;
            top: 20px;
            left: 50%;
            transform: translateX(-50%) translateY(-100px);
            background: linear-gradient(135deg, #3b82f6 0%, #2563eb 100%);
            border: 3px solid #60a5fa;
            border-radius: 8px;
            padding: 20px 30px;
            z-index: 20000;
            color: white;
            font-family: sans-serif;
            font-size: 1.1em;
            font-weight: 600;
            text-align: center;
            cursor: pointer;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5), 0 0 20px rgba(59, 130, 246, 0.5);
            transition: transform 0.3s ease, opacity 0.3s ease;
            opacity: 0;
            min-width: 300px;
            max-width: 500px;
            animation: notificationPulse 2s infinite;
        }

        .game-found-notification.notification-visible {
            transform: translateX(-50%) translateY(0);
            opacity: 1;
        }

        .game-found-notification.notification-dismissing {
            transform: translateX(-50%) translateY(-100px);
            opacity: 0;
        }

        @keyframes notificationPulse {
            0%, 100% {
                box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5), 0 0 0 0 rgba(59, 130, 246, 0.7);
            }
            50% {
                box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5), 0 0 0 8px rgba(59, 130, 246, 0);
            }
        }

        .game-found-notification:hover {
            background: linear-gradient(135deg, #2563eb 0%, #1d4ed8 100%);
            border-color: #93c5fd;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.5), 0 0 25px rgba(59, 130, 246, 0.7);
        }
    `);

    // Update panel visibility based on game state (URL-based detection)
    function updatePanelVisibility() {
        const panel = document.getElementById('openfront-autojoin-panel');
        if (!panel) return;

        const isLobby = isOnLobbyPage();
        const wasInGame = panel.dataset.wasInGame === 'true';

        if (!isLobby) {
            // In game → hide panel
            panel.classList.add('hidden');
            stopGameInfoUpdates(); // Stop updating game info when not in lobby
            // Dismiss notification when game starts
            dismissNotification();
            // If we just entered a game, disable auto-join only if we joined via auto-join mode
            if (!wasInGame) {
                // Play boxing ring bell sound when game starts
                playGameStartSound();
                // Only disable search if we joined via auto-join mode
                if (autoJoinEnabled && joinMode === 'autojoin') {
                    autoJoinEnabled = false;
                    stopMonitoring();
                    saveSettings();
                    updateUI();
                } else if (autoJoinEnabled && joinMode === 'notify') {
                    // In notify mode, keep search active (user manually joined)
                }
            }
            panel.dataset.wasInGame = 'true';
        } else {
            // In lobby → show panel and clear joined lobbies
            panel.classList.remove('hidden');
            startGameInfoUpdates(); // Start updating game info when in lobby
            joinedLobbies.clear(); // Clear to allow rejoining same lobby if needed

            // If we just returned to lobby, auto-start search in notify mode
            if (wasInGame) {
                // Always enable search when returning to lobby
                autoJoinEnabled = true;
                // Set to notify mode by default when returning to lobby
                joinMode = 'notify';
                // Update toggle and labels
                const modeToggle = document.getElementById('join-mode-toggle');
                if (modeToggle) {
                    modeToggle.checked = false;
                    updateModeLabels(false);
                }
                // Save settings and auto-start search if criteria exist
                saveSettings();
                updateUI();
                tryAutoStartSearch();
            }
            // Clear notified lobbies when returning to lobby
            notifiedLobbies.clear();
            lastNotifiedGameID = null;
            gameFoundTime = null;
            panel.dataset.wasInGame = 'false';
        }
    }

    // Observe URL changes to update panel visibility (event-based approach)
    function observeURL() {
        // Use popstate for browser navigation (back/forward buttons)
        window.addEventListener('popstate', updatePanelVisibility);

        // Use hashchange for hash changes
        window.addEventListener('hashchange', updatePanelVisibility);

        // Use pushstate/replacestate interception for programmatic navigation
        const originalPushState = history.pushState;
        const originalReplaceState = history.replaceState;

        history.pushState = function(...args) {
            originalPushState.apply(history, args);
            setTimeout(updatePanelVisibility, 0);
        };

        history.replaceState = function(...args) {
            originalReplaceState.apply(history, args);
            setTimeout(updatePanelVisibility, 0);
        };

        // Initial check
        updatePanelVisibility();
    }

    // Initialize
    function init() {
        loadSettings();
        createUI();

        // Preload audio files
        preloadSounds();

        // Monitor URL changes to show/hide panel (event-based, more efficient)
        observeURL();
    }

    // Start when DOM is ready (immediate check like lobby_player_list.js)
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        // Initialize immediately (no delay needed with URL-based detection)
        init();
    }

})();