Slither.io Mobile Mod

iPhone/iPad touch controls for slither.io with visual pointer and customization

Устаревшая версия за 20.10.2025. Перейдите к последней версии.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey, Greasemonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Violentmonkey.

Чтобы установить этот скрипт, вы сначала должны установить расширение браузера, например Tampermonkey или Userscripts.

Чтобы установить этот скрипт, сначала вы должны установить расширение браузера, например Tampermonkey.

Чтобы установить этот скрипт, вы должны установить расширение — менеджер скриптов.

(у меня уже есть менеджер скриптов, дайте мне установить скрипт!)

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение браузера, например Stylus.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

Чтобы установить этот стиль, сначала вы должны установить расширение — менеджер стилей.

(у меня уже есть менеджер стилей, дайте мне установить скрипт!)

// ==UserScript==
// @name         Slither.io Mobile Mod
// @namespace    slither_mobile_mod
// @version      1.0
// @description  iPhone/iPad touch controls for slither.io with visual pointer and customization
// @author       XBACT
// @match        *://slither.com/*
// @match        *://slither.io/*
// @grant        none
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // Touch control state
    let touchActive = false;
    let touchStartX = 0;
    let touchStartY = 0;
    let touchCurrentX = 0;
    let touchCurrentY = 0;
    let pointerX = 0;
    let pointerY = 0;
    let pointerElement = null;
    let boostButton = null;
    let zoomInButton = null;
    let zoomOutButton = null;
    let settingsButton = null;
    let settingsPanel = null;
    let isAccelerating = false;
    let lastKnownAngle = 0;
    let currentZoom = 1.0;
    let gameStartZoom = 1.0;
    let editMode = false;
    let draggingButton = null;
    let dragOffsetX = 0;
    let dragOffsetY = 0;
    let editingButton = null;
    let wasInGame = false;

    const POINTER_SPEED_MULTIPLIER = 2.5;
    const INITIAL_POINTER_DISTANCE = 120;
    const MIN_POINTER_DISTANCE = 10;
    const MIN_ZOOM = 0.3;
    const MAX_ZOOM = 3.0;

    // Default button settings
    let buttonSettings = {
        boost: {
            x: window.innerWidth - 110,
            y: window.innerHeight - 110,
            width: 80,
            height: 80,
            color: '#ff3232',
            opacity: 0.7,
            borderRadius: 50
        },
        zoomIn: {
            x: window.innerWidth - 80,
            y: window.innerHeight - 210,
            width: 50,
            height: 50,
            color: '#6496ff',
            opacity: 0.7,
            borderRadius: 10,
            value: 0.05
        },
        zoomOut: {
            x: window.innerWidth - 80,
            y: window.innerHeight - 270,
            width: 50,
            height: 50,
            color: '#6496ff',
            opacity: 0.7,
            borderRadius: 10,
            value: 0.05
        }
    };

    // Load saved settings
    function loadSettings() {
        const saved = localStorage.getItem('slitherMobileSettings');
        if (saved) {
            try {
                const parsed = JSON.parse(saved);
                buttonSettings = { ...buttonSettings, ...parsed };
            } catch (e) {
                console.error('Failed to load settings:', e);
            }
        }
    }

    // Save settings
    function saveSettings() {
        localStorage.setItem('slitherMobileSettings', JSON.stringify(buttonSettings));
    }

    // Check if currently in game
    function isInGame() {
        if (window.snake && window.snake.id !== undefined) return true;
        if (window.playing === true) return true;
        const loginDiv = document.getElementById('login');
        if (loginDiv && loginDiv.style.display === 'none') return true;
        const playBtn = document.getElementById('playh');
        if (playBtn && playBtn.style.display === 'none') return true;
        return false;
    }

    // Monitor game state changes
    function checkGameStateChange() {
        const currentlyInGame = isInGame();
        
        // Detect game start
        if (currentlyInGame && !wasInGame) {
            console.log('Game started - resetting zoom to:', gameStartZoom);
            currentZoom = gameStartZoom;
            if (window.gsc !== undefined) {
                window.gsc = currentZoom;
            }
        }
        
        // Detect game end
        if (!currentlyInGame && wasInGame) {
            console.log('Game ended - saving zoom:', currentZoom);
            gameStartZoom = currentZoom;
        }
        
        wasInGame = currentlyInGame;
    }

    // Run game state check regularly
    setInterval(checkGameStateChange, 100);

    // Create visual pointer element
    function createPointer() {
        pointerElement = document.createElement('div');
        pointerElement.style.cssText = `
            position: fixed;
            width: 40px;
            height: 40px;
            pointer-events: none;
            z-index: 10000;
            display: none;
            transform: translate(-50%, -50%);
        `;
        
        pointerElement.innerHTML = `
            <svg width="40" height="40" viewBox="0 0 40 40" xmlns="http://www.w3.org/2000/svg">
                <defs>
                    <filter id="glow">
                        <feGaussianBlur stdDeviation="2" result="coloredBlur"/>
                        <feMerge>
                            <feMergeNode in="coloredBlur"/>
                            <feMergeNode in="SourceGraphic"/>
                        </feMerge>
                    </filter>
                </defs>
                <path d="M20 38 L10 25 L16 25 L16 2 L24 2 L24 25 L30 25 Z" 
                      fill="rgba(100, 200, 255, 0.9)" 
                      stroke="white" 
                      stroke-width="2"
                      filter="url(#glow)"/>
                <circle cx="20" cy="20" r="3" fill="white" opacity="0.8"/>
            </svg>
        `;
        
        document.body.appendChild(pointerElement);
    }

    // Apply button style
    function applyButtonStyle(button, settings) {
        const s = buttonSettings[settings];
        button.style.left = s.x + 'px';
        button.style.top = s.y + 'px';
        button.style.width = s.width + 'px';
        button.style.height = s.height + 'px';
        button.style.backgroundColor = `${s.color}${Math.round(s.opacity * 255).toString(16).padStart(2, '0')}`;
        button.style.borderRadius = s.borderRadius + '%';
    }

    // Create boost button
    function createBoostButton() {
        boostButton = document.createElement('button');
        boostButton.textContent = 'BOOST';
        boostButton.className = 'control-button';
        boostButton.dataset.buttonType = 'boost';
        boostButton.style.cssText = `
            position: fixed;
            border: 3px solid white;
            color: white;
            font-weight: bold;
            font-size: 14px;
            z-index: 10001;
            touch-action: none;
            user-select: none;
            box-shadow: 0 4px 10px rgba(0,0,0,0.3);
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
        `;
        
        applyButtonStyle(boostButton, 'boost');
        
        boostButton.addEventListener('touchstart', handleBoostStart, { passive: false });
        boostButton.addEventListener('touchend', handleBoostEnd, { passive: false });
        
        document.body.appendChild(boostButton);
    }

    function handleBoostStart(e) {
        if (editMode) {
            startDragging(e, boostButton);
            return;
        }
        e.preventDefault();
        e.stopPropagation();
        if (!isInGame()) return;
        isAccelerating = true;
        boostButton.style.filter = 'brightness(1.3)';
        simulateMouseDown();
    }

    function handleBoostEnd(e) {
        if (editMode) return;
        e.preventDefault();
        e.stopPropagation();
        isAccelerating = false;
        boostButton.style.filter = 'brightness(1)';
        simulateMouseUp();
        // Don't stop mouse tracking here - keep it running if touch is still active
    }

    // Create zoom buttons
    function createZoomButtons() {
        zoomInButton = document.createElement('button');
        zoomInButton.textContent = '+';
        zoomInButton.className = 'control-button';
        zoomInButton.dataset.buttonType = 'zoomIn';
        zoomInButton.style.cssText = `
            position: fixed;
            border: 2px solid white;
            color: white;
            font-weight: bold;
            font-size: 24px;
            z-index: 10001;
            touch-action: none;
            user-select: none;
            box-shadow: 0 4px 10px rgba(0,0,0,0.3);
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
        `;
        
        applyButtonStyle(zoomInButton, 'zoomIn');
        
        zoomInButton.addEventListener('touchstart', (e) => {
            if (editMode) {
                startDragging(e, zoomInButton);
                return;
            }
            e.preventDefault();
            e.stopPropagation();
            zoomInButton.style.filter = 'brightness(1.3)';
            adjustZoom(buttonSettings.zoomIn.value);
        }, { passive: false });
        
        zoomInButton.addEventListener('touchend', (e) => {
            if (editMode) return;
            e.preventDefault();
            e.stopPropagation();
            zoomInButton.style.filter = 'brightness(1)';
        }, { passive: false });
        
        document.body.appendChild(zoomInButton);

        zoomOutButton = document.createElement('button');
        zoomOutButton.textContent = '−';
        zoomOutButton.className = 'control-button';
        zoomOutButton.dataset.buttonType = 'zoomOut';
        zoomOutButton.style.cssText = `
            position: fixed;
            border: 2px solid white;
            color: white;
            font-weight: bold;
            font-size: 24px;
            z-index: 10001;
            touch-action: none;
            user-select: none;
            box-shadow: 0 4px 10px rgba(0,0,0,0.3);
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
        `;
        
        applyButtonStyle(zoomOutButton, 'zoomOut');
        
        zoomOutButton.addEventListener('touchstart', (e) => {
            if (editMode) {
                startDragging(e, zoomOutButton);
                return;
            }
            e.preventDefault();
            e.stopPropagation();
            zoomOutButton.style.filter = 'brightness(1.3)';
            adjustZoom(-buttonSettings.zoomOut.value);
        }, { passive: false });
        
        zoomOutButton.addEventListener('touchend', (e) => {
            if (editMode) return;
            e.preventDefault();
            e.stopPropagation();
            zoomOutButton.style.filter = 'brightness(1)';
        }, { passive: false });
        
        document.body.appendChild(zoomOutButton);
    }

    // Create settings button
    function createSettingsButton() {
        settingsButton = document.createElement('button');
        settingsButton.innerHTML = '⚙';
        settingsButton.style.cssText = `
            position: fixed;
            top: 10px;
            right: 10px;
            width: 40px;
            height: 40px;
            border-radius: 50%;
            background: rgba(100, 100, 100, 0.7);
            border: 2px solid white;
            color: white;
            font-size: 20px;
            z-index: 10002;
            cursor: pointer;
            display: flex;
            align-items: center;
            justify-content: center;
            box-shadow: 0 2px 8px rgba(0,0,0,0.3);
            touch-action: none;
        `;
        
        settingsButton.addEventListener('click', (e) => {
            e.stopPropagation();
            toggleSettingsPanel();
        });
        
        settingsButton.addEventListener('touchend', (e) => {
            e.preventDefault();
            e.stopPropagation();
        }, { passive: false });
        
        document.body.appendChild(settingsButton);
    }

    // Create settings panel
    function createSettingsPanel() {
        settingsPanel = document.createElement('div');
        settingsPanel.id = 'settings-panel';
        settingsPanel.style.cssText = `
            position: fixed;
            top: 60px;
            right: 10px;
            width: 320px;
            max-height: 80vh;
            background: rgba(30, 30, 35, 0.98);
            border: 2px solid white;
            border-radius: 10px;
            padding: 15px;
            z-index: 10003;
            display: none;
            overflow-y: auto;
            color: white;
            font-family: Arial, sans-serif;
            box-shadow: 0 4px 20px rgba(0,0,0,0.5);
        `;
        
        settingsPanel.innerHTML = `
            <h3 style="margin: 0 0 15px 0; text-align: center;">コントロール設定</h3>
            <button id="toggleEditMode" style="width: 100%; padding: 10px; margin-bottom: 15px; background: #4CAF50; color: white; border: none; border-radius: 5px; cursor: pointer; font-weight: bold; font-size: 14px;">
                移動モード: OFF
            </button>
            <div id="buttonSettingsContainer"></div>
            <button id="resetSettings" style="width: 100%; padding: 10px; margin-top: 15px; background: #f44336; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 14px;">
                設定をリセット
            </button>
        `;
        
        document.body.appendChild(settingsPanel);
        
        document.getElementById('toggleEditMode').addEventListener('click', toggleEditMode);
        document.getElementById('resetSettings').addEventListener('click', resetSettings);
        
        updateSettingsPanel();
    }

    function toggleSettingsPanel() {
        const isVisible = settingsPanel.style.display !== 'none';
        settingsPanel.style.display = isVisible ? 'none' : 'block';
        
        if (!isVisible) {
            updateSettingsPanel();
        }
    }

    function toggleEditMode() {
        editMode = !editMode;
        const btn = document.getElementById('toggleEditMode');
        btn.textContent = `移動モード: ${editMode ? 'ON' : 'OFF'}`;
        btn.style.background = editMode ? '#ff9800' : '#4CAF50';
        
        document.querySelectorAll('.control-button').forEach(button => {
            button.style.border = editMode ? '3px dashed yellow' : button.dataset.buttonType === 'boost' ? '3px solid white' : '2px solid white';
        });
    }

    function updateSettingsPanel() {
        const container = document.getElementById('buttonSettingsContainer');
        container.innerHTML = '';
        
        Object.keys(buttonSettings).forEach(key => {
            const s = buttonSettings[key];
            const section = document.createElement('div');
            section.style.cssText = 'margin-bottom: 15px; padding: 12px; background: rgba(255,255,255,0.1); border-radius: 8px;';
            
            const title = key === 'boost' ? 'ブースト' : key === 'zoomIn' ? 'ズームイン' : 'ズームアウト';
            
            let html = `
                <div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
                    <h4 style="margin: 0;">${title}</h4>
                    <button class="edit-button" data-button="${key}" style="padding: 5px 12px; background: #2196F3; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 12px;">
                        ${editingButton === key ? '閉じる' : '編集'}
                    </button>
                </div>
            `;
            
            if (editingButton === key) {
                html += `<div style="padding-top: 10px; border-top: 1px solid rgba(255,255,255,0.2);">`;
                html += createSlider('幅', `${key}-width`, s.width, 30, 200, 1);
                html += createSlider('高さ', `${key}-height`, s.height, 30, 200, 1);
                html += createSlider('角丸', `${key}-radius`, s.borderRadius, 0, 50, 1);
                html += createSlider('透明度', `${key}-opacity`, s.opacity, 0.1, 1, 0.1);
                html += createColorPicker('色', `${key}-color`, s.color);
                
                if (key !== 'boost') {
                    html += createSlider('変更値', `${key}-value`, s.value, 0.01, 1.0, 0.01);
                }
                html += `</div>`;
            }
            
            section.innerHTML = html;
            container.appendChild(section);
            
            // Add event listener for edit button
            section.querySelector('.edit-button').addEventListener('click', () => {
                editingButton = editingButton === key ? null : key;
                updateSettingsPanel();
            });
            
            // Add event listeners for controls if this button is being edited
            if (editingButton === key) {
                document.getElementById(`${key}-width`).addEventListener('input', (e) => updateSetting(key, 'width', parseFloat(e.target.value)));
                document.getElementById(`${key}-height`).addEventListener('input', (e) => updateSetting(key, 'height', parseFloat(e.target.value)));
                document.getElementById(`${key}-radius`).addEventListener('input', (e) => updateSetting(key, 'borderRadius', parseFloat(e.target.value)));
                document.getElementById(`${key}-opacity`).addEventListener('input', (e) => updateSetting(key, 'opacity', parseFloat(e.target.value)));
                document.getElementById(`${key}-color`).addEventListener('input', (e) => updateSetting(key, 'color', e.target.value));
                
                if (key !== 'boost') {
                    document.getElementById(`${key}-value`).addEventListener('input', (e) => updateSetting(key, 'value', parseFloat(e.target.value)));
                }
            }
        });
    }

    function createSlider(label, id, value, min, max, step) {
        return `
            <div style="margin: 8px 0;">
                <label style="display: block; margin-bottom: 3px; font-size: 12px;">${label}: <span id="${id}-val">${value.toFixed(step < 0.1 ? 2 : 1)}</span></label>
                <input type="range" id="${id}" min="${min}" max="${max}" step="${step}" value="${value}" style="width: 100%;">
            </div>
        `;
    }

    function createColorPicker(label, id, value) {
        return `
            <div style="margin: 8px 0;">
                <label style="display: block; margin-bottom: 3px; font-size: 12px;">${label}</label>
                <input type="color" id="${id}" value="${value}" style="width: 100%; height: 30px; cursor: pointer; border-radius: 4px;">
            </div>
        `;
    }

    function updateSetting(buttonType, property, value) {
        buttonSettings[buttonType][property] = value;
        
        // Update value display
        const valSpan = document.getElementById(`${buttonType}-${property}-val`);
        if (valSpan) {
            if (property === 'value') {
                valSpan.textContent = value.toFixed(2);
            } else {
                valSpan.textContent = value.toFixed(1);
            }
        }
        
        // Apply to button
        const button = buttonType === 'boost' ? boostButton : buttonType === 'zoomIn' ? zoomInButton : zoomOutButton;
        applyButtonStyle(button, buttonType);
        
        saveSettings();
    }

    function resetSettings() {
        if (confirm('設定をリセットしますか?')) {
            localStorage.removeItem('slitherMobileSettings');
            location.reload();
        }
    }

    function startDragging(e, button) {
        e.preventDefault();
        e.stopPropagation();
        draggingButton = button;
        const rect = button.getBoundingClientRect();
        dragOffsetX = e.touches[0].clientX - rect.left;
        dragOffsetY = e.touches[0].clientY - rect.top;
    }

    // Adjust zoom level
    function adjustZoom(delta) {
        currentZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, currentZoom + delta));
        gameStartZoom = currentZoom; // Update the base zoom level
        if (window.gsc !== undefined) {
            window.gsc = currentZoom;
        }
        console.log('Zoom adjusted to:', currentZoom.toFixed(2));
    }

    // Get snake angle
    function getSnakeAngle() {
        if (window.snake) {
            if (typeof window.snake.ang !== 'undefined') {
                lastKnownAngle = window.snake.ang;
                return window.snake.ang;
            }
            if (typeof window.snake.eang !== 'undefined') {
                lastKnownAngle = window.snake.eang;
                return window.snake.eang;
            }
            if (typeof window.snake.wang !== 'undefined') {
                lastKnownAngle = window.snake.wang;
                return window.snake.wang;
            }
        }
        return lastKnownAngle;
    }

    // Initialize pointer position
    function initializePointerPosition() {
        const centerX = window.innerWidth / 2;
        const centerY = window.innerHeight / 2;
        const snakeAngle = getSnakeAngle();
        pointerX = centerX + Math.cos(snakeAngle) * INITIAL_POINTER_DISTANCE;
        pointerY = centerY + Math.sin(snakeAngle) * INITIAL_POINTER_DISTANCE;
    }

    // Update pointer position
    function updatePointerPosition() {
        if (!touchActive || !pointerElement || !isInGame()) {
            if (pointerElement) pointerElement.style.display = 'none';
            return;
        }

        const fingerDeltaX = touchCurrentX - touchStartX;
        const fingerDeltaY = touchCurrentY - touchStartY;
        const centerX = window.innerWidth / 2;
        const centerY = window.innerHeight / 2;
        const snakeAngle = getSnakeAngle();
        const initialX = centerX + Math.cos(snakeAngle) * INITIAL_POINTER_DISTANCE;
        const initialY = centerY + Math.sin(snakeAngle) * INITIAL_POINTER_DISTANCE;

        let newPointerX = initialX + (fingerDeltaX * POINTER_SPEED_MULTIPLIER);
        let newPointerY = initialY + (fingerDeltaY * POINTER_SPEED_MULTIPLIER);

        const distanceFromCenter = Math.sqrt(Math.pow(newPointerX - centerX, 2) + Math.pow(newPointerY - centerY, 2));
        if (distanceFromCenter < MIN_POINTER_DISTANCE) {
            const angle = Math.atan2(newPointerY - centerY, newPointerX - centerX);
            newPointerX = centerX + Math.cos(angle) * MIN_POINTER_DISTANCE;
            newPointerY = centerY + Math.sin(angle) * MIN_POINTER_DISTANCE;
        }

        pointerX = newPointerX;
        pointerY = newPointerY;
        pointerElement.style.left = pointerX + 'px';
        pointerElement.style.top = pointerY + 'px';
        pointerElement.style.display = 'block';

        const angleToCenter = Math.atan2(centerY - pointerY, centerX - pointerX);
        const rotationDegrees = (angleToCenter * 180 / Math.PI) + 90;
        pointerElement.style.transform = `translate(-50%, -50%) rotate(${rotationDegrees}deg)`;
    }

    // Simulate mouse events
    let mouseUpdateInterval = null;

    function startMouseTracking() {
        if (mouseUpdateInterval) return;
        mouseUpdateInterval = setInterval(() => {
            if (touchActive && isInGame()) {
                simulateMouseMove(pointerX, pointerY);
            }
        }, 16);
    }

    function stopMouseTracking() {
        if (mouseUpdateInterval) {
            clearInterval(mouseUpdateInterval);
            mouseUpdateInterval = null;
        }
    }

    function simulateMouseMove(x, y) {
        if (!isInGame()) return;
        
        const canvas = document.querySelector('canvas');
        if (!canvas) return;

        const event = new MouseEvent('mousemove', {
            clientX: x,
            clientY: y,
            bubbles: true,
            cancelable: true,
            view: window
        });
        canvas.dispatchEvent(event);
    }

    function simulateMouseDown() {
        if (!isInGame()) return;
        const canvas = document.querySelector('canvas');
        if (!canvas) return;

        const event = new MouseEvent('mousedown', {
            button: 0,
            buttons: 1,
            bubbles: true,
            cancelable: true,
            view: window
        });
        canvas.dispatchEvent(event);
    }

    function simulateMouseUp() {
        const canvas = document.querySelector('canvas');
        if (!canvas) return;

        const event = new MouseEvent('mouseup', {
            button: 0,
            buttons: 0,
            bubbles: true,
            cancelable: true,
            view: window
        });
        canvas.dispatchEvent(event);
    }

    // Touch event handlers
    document.addEventListener('touchstart', (e) => {
        // Allow settings panel interaction
        if (e.target.closest('#settings-panel')) {
            return;
        }

        // Allow settings button
        if (e.target === settingsButton) {
            return;
        }

        // Allow control buttons
        if (e.target.closest('.control-button')) {
            return;
        }

        // For menu screen, only prevent default but don't start pointer tracking
        if (!isInGame()) {
            // Allow normal touch interaction on menu
            return;
        }

        // In game: block and handle with pointer
        e.preventDefault();
        e.stopPropagation();

        touchActive = true;
        touchStartX = e.touches[0].clientX;
        touchStartY = e.touches[0].clientY;
        touchCurrentX = touchStartX;
        touchCurrentY = touchStartY;
        
        initializePointerPosition();
        updatePointerPosition();
        startMouseTracking();
    }, { passive: false, capture: true });

    document.addEventListener('touchmove', (e) => {
        if (draggingButton) {
            e.preventDefault();
            e.stopPropagation();
            const newX = e.touches[0].clientX - dragOffsetX;
            const newY = e.touches[0].clientY - dragOffsetY;
            const buttonType = draggingButton.dataset.buttonType;
            buttonSettings[buttonType].x = newX;
            buttonSettings[buttonType].y = newY;
            draggingButton.style.left = newX + 'px';
            draggingButton.style.top = newY + 'px';
            saveSettings();
            return;
        }

        if (!touchActive) return;
        
        // Block native touch events only in game
        if (isInGame() && !e.target.closest('#settings-panel')) {
            e.preventDefault();
            e.stopPropagation();
        }

        touchCurrentX = e.touches[0].clientX;
        touchCurrentY = e.touches[0].clientY;
        
        if (isInGame()) {
            updatePointerPosition();
        }
    }, { passive: false, capture: true });

    document.addEventListener('touchend', (e) => {
        if (draggingButton) {
            draggingButton = null;
            e.preventDefault();
            e.stopPropagation();
            return;
        }

        if (!e.target.closest('.control-button') && e.target !== settingsButton && !e.target.closest('#settings-panel')) {
            if (isInGame()) {
                e.preventDefault();
                e.stopPropagation();
            }
        }

        touchActive = false;
        stopMouseTracking();
        if (pointerElement) {
            pointerElement.style.display = 'none';
        }
    }, { passive: false, capture: true });

    // Initialize
    function init() {
        loadSettings();
        createPointer();
        createBoostButton();
        createZoomButtons();
        createSettingsButton();
        createSettingsPanel();
        
        const canvas = document.querySelector('canvas');
        if (canvas) {
            canvas.style.touchAction = 'none';
        }

        if (window.gsc !== undefined) {
            currentZoom = window.gsc;
            gameStartZoom = currentZoom;
        }

        wasInGame = isInGame();

        console.log('Slither.io mobile controls initialized');
        console.log('Initial zoom:', currentZoom.toFixed(2));
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }

})();