Gartic Enhanced UI Pro

Enhanced UI for Gartic.io with Advanced Features

// ==UserScript==
// @name         Gartic Enhanced UI Pro
// @namespace    http://tampermonkey.net/
// @version      2024.1.2
// @description  Enhanced UI for Gartic.io with Advanced Features
// @author       Akira
// @match        https://gartic.io/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=gartic.io
// @grant        none
// ==/UserScript==

if(window.location.href.includes("?ww")) {
    document.head.innerHTML = `
        <style>
            @import url('https://fonts.googleapis.com/css2?family=Poppins:wght@300;400;500;600&display=swap');
            
            * {
                margin: 0;
                padding: 0;
                box-sizing: border-box;
            }

            body {
                margin: 0;
                background: #0f0f1a;
                font-family: 'Poppins', sans-serif;
                color: #fff;
                overflow-x: hidden;
                width: 100vw;
                height: 100vh;
            }

            #app-container {
                width: 100vw;
                height: 100vh;
                overflow-y: auto;
                background: linear-gradient(135deg, #0f0f1a, #1a1a2e);
                padding: 20px;
            }

            .top-bar {
                position: sticky;
                top: 0;
                background: rgba(15, 15, 26, 0.95);
                padding: 15px;
                backdrop-filter: blur(10px);
                border-bottom: 1px solid rgba(108, 92, 231, 0.2);
                z-index: 1000;
                margin: -20px -20px 20px -20px;
            }

            .search-box {
                max-width: 600px;
                margin: 15px auto;
                position: relative;
            }

            .search-input {
                width: 100%;
                padding: 12px 20px;
                border-radius: 12px;
                border: 2px solid #6c5ce7;
                background: rgba(26, 26, 46, 0.8);
                color: #fff;
                font-size: 16px;
                transition: all 0.3s ease;
            }

            .search-input:focus {
                outline: none;
                border-color: #a55eea;
                box-shadow: 0 0 15px rgba(108, 92, 231, 0.3);
            }

            .filters-panel {
                background: rgba(26, 26, 46, 0.9);
                padding: 15px;
                border-radius: 12px;
                margin: 10px 0;
                display: flex;
                gap: 20px;
                flex-wrap: wrap;
                align-items: center;
            }

            .filter-group {
                display: flex;
                align-items: center;
                gap: 10px;
            }

            .filter-checkbox {
                appearance: none;
                width: 20px;
                height: 20px;
                border: 2px solid #6c5ce7;
                border-radius: 6px;
                cursor: pointer;
                position: relative;
                transition: all 0.3s ease;
            }

            .filter-checkbox:checked {
                background: #6c5ce7;
            }

            .filter-checkbox:checked::after {
                content: "✓";
                position: absolute;
                color: white;
                font-size: 14px;
                top: 50%;
                left: 50%;
                transform: translate(-50%, -50%);
            }

            .stats-panel {
                background: rgba(26, 26, 46, 0.9);
                padding: 20px;
                border-radius: 12px;
                margin: 10px 0;
                display: grid;
                grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
                gap: 20px;
            }

            .stat-item {
                text-align: center;
            }

            .stat-value {
                font-size: 24px;
                font-weight: 600;
                color: #6c5ce7;
                margin-bottom: 5px;
            }

            .stat-label {
                font-size: 14px;
                color: rgba(255,255,255,0.7);
            }

            .sort-options {
                display: flex;
                gap: 10px;
                align-items: center;
            }

            .sort-select {
                background: rgba(26, 26, 46, 0.8);
                border: 1px solid #6c5ce7;
                color: white;
                padding: 8px;
                border-radius: 8px;
                cursor: pointer;
            }

            .sort-select option {
                background: #1a1a2e;
            }

            .rooms-container {
                display: flex;
                flex-direction: column;
                gap: 20px;
            }

            .room-block {
                background: rgba(26, 26, 46, 0.8);
                border-radius: 15px;
                padding: 20px;
                border: 1px solid rgba(108, 92, 231, 0.2);
                transition: all 0.3s ease;
            }

            .room-header {
                display: flex;
                justify-content: space-between;
                align-items: center;
                margin-bottom: 15px;
                padding-bottom: 10px;
                border-bottom: 1px solid rgba(108, 92, 231, 0.2);
            }

            .room-title {
                font-size: 1.2em;
                font-weight: 600;
                color: #6c5ce7;
            }

            .room-stats {
                display: flex;
                gap: 15px;
                font-size: 0.9em;
            }

            .players-grid {
                display: grid;
                grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
                gap: 15px;
            }

            .player-card {
                background: rgba(15, 15, 26, 0.8);
                border-radius: 12px;
                padding: 15px;
                text-align: center;
                transition: all 0.3s ease;
                border: 1px solid rgba(108, 92, 231, 0.1);
            }

            .player-card:hover {
                transform: translateY(-5px);
                border-color: #6c5ce7;
                box-shadow: 0 5px 15px rgba(108, 92, 231, 0.2);
            }

            .player-avatar {
                width: 80px;
                height: 80px;
                border-radius: 50%;
                margin: 0 auto 10px auto;
                border: 3px solid #6c5ce7;
                transition: all 0.3s ease;
            }

            .player-card:hover .player-avatar {
                border-color: #a55eea;
                transform: scale(1.05);
            }

            .player-name {
                font-weight: 600;
                margin: 5px 0;
                color: #fff;
            }

            .player-points {
                background: rgba(108, 92, 231, 0.2);
                padding: 3px 10px;
                border-radius: 12px;
                font-size: 0.9em;
                color: #6c5ce7;
                display: inline-block;
                margin: 5px 0;
            }

            .player-links {
                display: flex;
                flex-direction: column;
                gap: 8px;
                margin-top: 10px;
            }

            .room-link, .viewer-link {
                padding: 8px 15px;
                border-radius: 8px;
                text-decoration: none;
                transition: all 0.3s ease;
                font-size: 0.9em;
            }

            .room-link {
                background: linear-gradient(45deg, #6c5ce7, #a55eea);
                color: #fff;
            }

            .viewer-link {
                background: rgba(108, 92, 231, 0.1);
                color: #6c5ce7;
            }

            .room-link:hover, .viewer-link:hover {
                transform: translateY(-2px);
                filter: brightness(1.1);
            }

            .refresh-button {
                background: linear-gradient(45deg, #6c5ce7, #a55eea);
                border: none;
                padding: 8px 20px;
                border-radius: 8px;
                color: #fff;
                cursor: pointer;
                transition: all 0.3s ease;
                font-size: 0.9em;
            }

            .refresh-button:hover {
                transform: scale(1.05);
                box-shadow: 0 5px 15px rgba(108, 92, 231, 0.3);
            }

            .creator-tag {
                position: fixed;
                bottom: 10px;
                right: 10px;
                font-size: 12px;
                color: rgba(255,255,255,0.3);
                z-index: 1000;
            }

            @keyframes fadeIn {
                from { opacity: 0; transform: translateY(20px); }
                to { opacity: 1; transform: translateY(0); }
            }

            .player-card {
                animation: fadeIn 0.5s ease-out forwards;
            }
        </style>
    `;

    document.body.innerHTML = `
        <div id="app-container">
            <div class="top-bar">
                <div class="search-box">
                    <input type="text" class="search-input" placeholder="Search players..." onkeyup="filterPlayers(this.value)">
                </div>
                <div class="filters-panel">
                    <div class="filter-group">
                        <input type="checkbox" id="avatarOnly" class="filter-checkbox" onchange="applyFilters()">
                        <label for="avatarOnly">Show Avatar Users Only</label>
                    </div>
                    <div class="filter-group">
                        <input type="checkbox" id="customProfileOnly" class="filter-checkbox" onchange="applyFilters()">
                        <label for="customProfileOnly">Show Custom Profile Users Only</label>
                    </div>
                    <div class="sort-options">
                        <label>Sort by:</label>
                        <select class="sort-select" onchange="sortPlayers(this.value)">
                            <option value="points">Points (High to Low)</option>
                            <option value="name">Name (A-Z)</option>
                            <option value="room">Room</option>
                        </select>
                    </div>
                </div>
                <div class="stats-panel">
                    <div class="stat-item">
                        <div class="stat-value" id="totalPlayers">0</div>
                        <div class="stat-label">Total Players</div>
                    </div>
                    <div class="stat-item">
                        <div class="stat-value" id="totalRooms">0</div>
                        <div class="stat-label">Active Rooms</div>
                    </div>
                    <div class="stat-item">
                        <div class="stat-value" id="avatarUsers">0</div>
                        <div class="stat-label">Avatar Users</div>
                    </div>
                    <div class="stat-item">
                        <div class="stat-value" id="customProfileUsers">0</div>
                        <div class="stat-label">Custom Profile Users</div>
                    </div>
                </div>
            </div>
            <div class="rooms-container"></div>
            <div class="creator-tag">Made By Akira</div>
        </div>
    `;

    let roomData = new Map();
    let updateTimers = new Map();

    function updateUI() {
        const container = document.querySelector('.rooms-container');
        container.innerHTML = '';

        roomData.forEach((players, roomCode) => {
            const roomBlock = document.createElement('div');
            roomBlock.className = 'room-block';
            roomBlock.innerHTML = `
                <div class="room-header">
                    <div class="room-title">Room: ${roomCode}</div>
                    <div class="room-stats">
                        <span>${players.length} Players</span>
                        <button class="refresh-button" onclick="refreshRoom('${roomCode}')">
                            Refresh
                        </button>
                    </div>
                </div>
                <div class="players-grid" id="room-${roomCode}">
                    ${players.map(user => createPlayerCard(user)).join('')}
                </div>
            `;
            container.appendChild(roomBlock);
        });
        updateStats();
    }

    function createPlayerCard(user) {
        const avatarSrc = user.foto || `https://gartic.io/static/images/avatar/svg/${user.avatar}.svg`;
        return `
            <div class="player-card" data-player-name="${user.nick.toLowerCase()}">
                <img class="player-avatar" src="${avatarSrc}" alt="${user.nick}" title="${user.id}">
                <div class="player-name">${user.nick}</div>
                <div class="player-points">${user.points}p</div>
                <div class="player-links">
                    <a href="${user.room}" target="_blank" class="room-link">Join Room</a>
                    <a href="${user.room}/viewer" target="_blank" class="viewer-link">View Only</a>
                </div>
            </div>
        `;
    }

    window.filterPlayers = function(searchTerm) {
        searchTerm = searchTerm.toLowerCase();
        document.querySelectorAll('.player-card').forEach(card => {
            const playerName = card.dataset.playerName;
            card.style.display = playerName.includes(searchTerm) ? 'block' : 'none';
        });
    };

    window.applyFilters = function() {
        const avatarOnly = document.getElementById('avatarOnly').checked;
        const customProfileOnly = document.getElementById('customProfileOnly').checked;
        
        document.querySelectorAll('.player-card').forEach(card => {
            const avatarSrc = card.querySelector('.player-avatar').src;
            const isAvatarUser = avatarSrc.includes('/static/images/avatar/svg/');
            
            let show = true;
            if (avatarOnly && !isAvatarUser) show = false;
            if (customProfileOnly && isAvatarUser) show = false;
            
            card.style.display = show ? 'block' : 'none';
        });
        
        updateStats();
    };

    window.sortPlayers = function(criteria) {
        roomData.forEach((players, roomCode) => {
            const sortedPlayers = [...players].sort((a, b) => {
                switch(criteria) {
                    case 'points':
                        return b.points - a.points;
                    case 'name':
                        return a.nick.localeCompare(b.nick);
                    case 'room':
                        return a.room.localeCompare(b.room);
                    default:
                        return 0;
                }
            });
            roomData.set(roomCode, sortedPlayers);
        });
        updateUI();
    };

    window.refreshRoom = function(roomCode) {
        getData(roomCode);
    };

    function updateStats() {
        const totalPlayers = Array.from(roomData.values()).reduce((acc, curr) => acc + curr.length, 0);
        const totalRooms = roomData.size;
        
        let avatarUsers = 0;
        let customProfileUsers = 0;
        
        roomData.forEach(players => {
            players.forEach(player => {
                if (player.foto) {
                    customProfileUsers++;
                } else {
                    avatarUsers++;
                }
            });
        });
        
        document.getElementById('totalPlayers').textContent = totalPlayers;
        document.getElementById('totalRooms').textContent = totalRooms;
        document.getElementById('avatarUsers').textContent = avatarUsers;
        document.getElementById('customProfileUsers').textContent = customProfileUsers;
    }

    function getData(roomCode) {
        if (updateTimers.has(roomCode)) {
            clearTimeout(updateTimers.get(roomCode));
        }

        const players = [];
        ['01', '02', '03', '04', '05', '06'].forEach(server => {
            const ws = new WebSocket(`wss://server${server}.gartic.io/socket.io/?EIO=3&transport=websocket`);
            
            ws.onopen = () => {
                ws.send(`42[12,{"v":20000,"platform":0,"sala":"${roomCode.slice(-4)}"}]`);
            };
            
            ws.onmessage = (msg) => {
                if(msg.data[4] == "5") {
                    const data = JSON.parse(msg.data.slice(2));
                    if(data[0] == 5) {
                        data[5].forEach(user => {
                            players.push({
                                points: user.pontos,
                                victory: user.vitorias,
                                id: user.id,
                                avatar: user.avatar,
                                room: `https://gartic.io/${roomCode}`,
                                nick: user.nick,
                                foto: user.foto
                            });
                        });
                        roomData.set(roomCode, players);
                        updateUI();
                        ws.close();
                    }
                }
            };
        });

        updateTimers.set(roomCode, setTimeout(() => getData(roomCode), 5000));
    }

    function fetchRooms() {
        fetch("https://gartic.io/req/list?search=&language[]=8")
            .then(x => x.json())
            .then(x => {
                x.forEach(room => {
                    if(room.quant > 0) {
                        getData(room.code);
                    }
                });
            });
        setTimeout(fetchRooms, 5000);
    }

    fetchRooms();
}