/*
gjhghjhgjghj
- Проблема с игроками они множатся
- Проблема с перемещением между городами (исчезновение и появление игроков)
- Проблема с Null полусферами
*/
import PF from 'pathfinding';
import React, { useEffect, useRef, useState } from 'react';
import { io } from 'socket.io-client';
import * as THREE from 'three';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
import { useDialogManager } from './components/DialogSystem/DialogManager';
import { DialogWindow } from './components/DialogSystem/DialogWindow';
import Inventory from './components/Inventory';
import OrgControlPanel from './components/OrgControlPanel';
import DoubleTapWrapper from './pages/DoubleTapWrapper';
import WaveformPlayer from './pages/WaveformPlayer';
import { getUsersStatus, loadUserInfo } from './api/auth.js';
import QuestSystem from './pages/QuestSystem ';

function Game({ avatarUrl, gender }) {
    const [showQuests, setShowQuests] = useState(false);
    // 1) реф для хранилища сцены
    const sceneRef = useRef(new THREE.Scene());

    // 2) реф для группы «города»
    const cityGroupRef = useRef(null);

    // 3) реф для группы «интерьера»
    const interiorGroupRef = useRef(null);
    const interiorCollidersRef = useRef([]);
    const interiorColliderBoxesRef = useRef([]);
    const jsonCollidersRef = useRef([]);
    const visualCollidersRef = useRef([]);
    const interiorExitPosRef = useRef(null);
    const fpHiddenNodesRef = useRef([]);
    const interiorDebugEnabledRef = useRef(false);
    const interiorDebugHelpersRef = useRef([]);
    const cleanupTimerRef = useRef(null);
    // Глобальный менеджер прогресса загрузки (используем в GLTFLoader))
    const loadingManagerRef = useRef(null);
    const overlayTimeoutRef = useRef(null);
    // Кликабельные объекты внутри интерьера
    const interiorInteractablesRef = useRef([]);
    const npcMeshesRef = useRef([]);

    // камеры
    const orthoCamRef = useRef(null);
    const fpCamRef = useRef(null);
    const cameraRef = useRef(null);
    const rendererRef = useRef(null);
    const moveInputRef = useRef({ forward: false, backward: false, left: false, right: false, strafeLeft: false, strafeRight: false });
    const fpPitchRef = useRef(0);
    const isTouchDevice = typeof window !== 'undefined' && ('ontouchstart' in window || navigator.maxTouchPoints > 0);
    const isInInteriorRef = useRef(false);
    const altHeldRef = useRef(false);
    const LOAD_RADIUS = 120;
    
    // Конфигурация коллайдеров
    const COLLIDER_CONFIG = {
        sizeMultiplier: 2.0, // Коэффициент увеличения размеров для полного покрытия объекта
        debugMode: false, // Режим отладки для визуализации коллайдеров
        minSize: 0.5, // Минимальный размер коллайдера
        maxSize: 50.0, // Максимальный размер коллайдера
        adaptiveScaling: true // Адаптивное масштабирование на основе размеров объекта
    };

    const [activeApp, setActiveApp] = useState(null);

    const [selectedHouse, setSelectedHouse] = useState(null);
    const [isInInterior, setIsInInterior] = useState(false);
    const mountRef = useRef(null);
    const socketRef = useRef(null);

    useEffect(() => {
        console.log('useEffect isInInterior изменился:', isInInterior);
        isInInteriorRef.current = isInInterior;
        console.log('isInInteriorRef.current установлен в:', isInInteriorRef.current);
    }, [isInInterior]);
    const [selectedPlayer, setSelectedPlayer] = useState(null);
    const [playerStats, setPlayerStats] = useState(null);
    const [micEnabled, setMicEnabled] = useState(false);
    const [orgMenu, setOrgMenu] = useState(null);
    const [orgPanelId, setOrgPanelId] = useState(null);
    const [satiety, setSatiety] = useState(() => {
        const p = JSON.parse(sessionStorage.getItem('user_profile') || '{}');
        return p.satiety ?? 100;
    });
    const [thirst, setThirst] = useState(() => {
        const p = JSON.parse(sessionStorage.getItem('user_profile') || '{}');
        return p.thirst ?? 100;
    });
    const [inventory, setInventory] = useState([]);
    const [showInventory, setShowInventory] = useState(false);
    const [gameTime, setGameTime] = useState(null);
    // Сеть
    const [connectionLost, setConnectionLost] = useState(false);
    const [latencyMs, setLatencyMs] = useState(null);
    const connectionLostRef = useRef(false);
    useEffect(() => { connectionLostRef.current = connectionLost; }, [connectionLost]);
    const [balance, setBalance] = useState(() => {
        const p = JSON.parse(sessionStorage.getItem('user_profile') || '{}');
        return p.balance ?? 0;
    });
    const [playerCoords, setPlayerCoords] = useState({ x: 0, y: 0, z: 0 });
    const [programmingLanguages, setProgrammingLanguages] = useState([]);
    const [passwordCorrect, setPasswordCorrect] = useState(false);
    const [showMiniGame, setShowMiniGame] = useState(false);
    const [questsProgress, setQuestsProgress] = useState([]);
    const statsRef = useRef(null);
    const voiceConnections = useRef({});
    const localStream = useRef(null);
    const voiceIcons = useRef({});
    const [isPlaying, setIsPlaying] = useState(true);
    //Телефон\
    const [audioUrl, setAudioUrl] = useState("/audio/firs.ogg");
    // for Mini-game_2
    const [showCleanupGame, setShowCleanupGame] = useState(false);
    const [cleanupGameData, setCleanupGameData] = useState(null);
    const [selectedTransaction, setSelectedTransaction] = useState(null);
    const [markedTransactions, setMarkedTransactions] = useState([]);
    const [decryptAttempts, setDecryptAttempts] = useState(3);
    const [timeLeft, setTimeLeft] = useState(180); // 3 минуты
    const [suspiciousFound, setSuspiciousFound] = useState(0);
    const [gameResult, setGameResult] = useState(null);
    const [personalArchive, setPersonalArchive] = useState([]);
    const [currentLevel, setCurrentLevel] = useState(1);
    const [gameCompleted, setGameCompleted] = useState(false);

    const [activeChat, setActiveChat] = useState(null);
    // Добавьте этот код в начало компонента Game, рядом с другими состояниями
    const [telegramContacts, setTelegramContacts] = useState([]);
    const [tgLoading, setTgLoading] = useState(false);
    const [tgError, setTgError] = useState(null);
    const [sysTime, setSysTime] = useState(new Date());
    const isPhoneNarrow = true; // экран виртуального телефона — всегда узкий

    const [isIframeOpen, setIsIframeOpen] = useState(false);
    const [iframeUrl, setIframeUrl] = useState('');

    const [appsHidden, setAppsHidden] = useState(false);
    const [isPhoneVisible, setIsPhoneVisible] = useState(true);
    const [isChatVisible, setIsChatVisible] = useState(true);

    const [seregaComments, setSeregaComments] = useState([]);
    const [currentExit, setCurrentExit] = useState(null);
    const currentExitRef = useRef(null);
    useEffect(() => { currentExitRef.current = currentExit; }, [currentExit]);

    useEffect(() => {
        const decay = setInterval(() => {
            setSatiety(s => Math.max(0, s - 0.05));
            setThirst(t => Math.max(0, t - 0.07));
        }, 10000);
        return () => clearInterval(decay);
    }, []);

    useEffect(() => {
        const profile = JSON.parse(sessionStorage.getItem('user_profile') || '{}');
        profile.satiety = satiety;
        profile.thirst = thirst;
        sessionStorage.setItem('user_profile', JSON.stringify(profile));
        socketRef.current?.emit('economy:updateStats', { satiety, thirst });
    }, [satiety, thirst]);


    //const [currentDialog, setCurrentDialog] = useState(null);
    //const [dialogIndex, setDialogIndex] = useState(0);
    //const [showDialog, setShowDialog] = useState(false);
    //const [formData, setFormData] = useState({});
    //const [currentForm, setCurrentForm] = useState(null);

    //Телефон
    let scene, renderer;
    const playerRef = useRef(null);
    const cityMeshesRef = useRef([]);
    const cityObjectsDataRef = useRef([]);
    const loadedCityObjectsRef = useRef({});
    const loadedInteriorMeshesRef = useRef({});
    const interiorsDataRef = useRef([]);
    const groundRef = useRef(null);
    const cityGroup = new THREE.Group();
    cityGroupRef.current = cityGroup;
    // группа интерьера создаётся при входе в здание
    const savedPositionRef = useRef(new THREE.Vector3());
    const remotePlayersRef = useRef({});


    const {
        currentDialog,
        dialogIndex,
        showDialog,
        formData,
        currentForm,
        loadDialog,
        handleAnswerSelect,
        handleFormSubmit,
        handleFormChange,
        setShowDialog
    } = useDialogManager();

    useEffect(() => {
        const id = setInterval(() => {
            if (playerRef.current) {
                const p = playerRef.current.position;
                setPlayerCoords({
                    x: p.x.toFixed(1),
                    y: p.y.toFixed(1),
                    z: p.z.toFixed(1)
                });
            }
        }, 100);
        return () => clearInterval(id);
    }, []);
    const handleAppClick = (appName) => {
        setAppsHidden(true);
        setActiveApp(appName);
        if (appName === "Telegram") {
            setTgError(null);
            setTgLoading(true);
            loadTelegramContacts().finally(() => setTgLoading(false));
        }
        //if (appName === "Chrome") {
        //    loadQuestsProgress();
        //}
        if (appName === "Settings") {
            setShowMiniGame(true);
        }
    };

    const handlePasswordInput = (e) => {
        if (e.key === 'Enter') {
            const input = e.target.value.trim();
            e.target.value = "";

            const negativeComments = [
                "Ты чё, братан, спишь?!",
                "Мимо кассы, как всегда!",
                "Это даже я знаю, что не так!",
                "Ну и лажа...",
                "Ты вообще в теме или как?",
                "Не-а, попробуй ещё раз!"
            ];

            const positiveComments = [
                "О, да ты в ударе сегодня!",
                "В точку, братишка!",
                "Ну наконец-то угадал!",
                "Так держать, хакер!",
                "Бинго! Правильный ответ!",
                "Ты меня удивляешь!"
            ];

            if (input === "mN8 2kP 5zX") {
                setTimeout(() => {
                    addSeregaComment(positiveComments[Math.floor(Math.random() * positiveComments.length)]);
                    setPasswordCorrect(true);
                    setProgrammingLanguages(["TR4 FG8 HJ2", "Z9 xC3 vB1", "mN8 2kP 5zX", "kL5 mN7 qW0"]);
                    setAudioUrl("/audio/TR4-FG8-Hj2.ogg");
                }, 800);
            }
            else if (input === "TR4 FG8 HJ2") {
                setTimeout(() => {
                    addSeregaComment(positiveComments[Math.floor(Math.random() * positiveComments.length)]);
                    setPasswordCorrect(true);
                    setProgrammingLanguages(["X b7kG z3Lp", "vn4 Zq J8mr", "sW 1Rt yK 90", "q9 Xgd2 BwF"]);
                    setAudioUrl("/audio/X-b7kG-z3Lp.ogg");
                }, 800);
            }
            else if (input === "X b7kG z3Lp") {
                setTimeout(() => {
                    addSeregaComment(positiveComments[Math.floor(Math.random() * positiveComments.length)]);
                    setPasswordCorrect(true);
                    setShowMiniGame(false);
                    loadCleanupGame();
                }, 800);
            }
            else {
                // Добавляем обработку неправильного ввода
                setTimeout(() => {
                    addSeregaComment(negativeComments[Math.floor(Math.random() * negativeComments.length)]);
                }, 800);
            }
        }
    };
    function addSeregaComment(text) {
        setSeregaComments(prev => [...prev, { text, id: Date.now() }]);
    }
    async function loadCleanupGame() {

        if (cleanupTimerRef.current) {
            clearInterval(cleanupTimerRef.current);
        }
        try {
            const token = localStorage.getItem('token');
            if (!token) {
                console.error('No token found');
                return;
            }
            if (gameCompleted) return;
            const res = await fetch(`/api/cleanup-game/data?level=${currentLevel}`, {
                headers: {
                    'Authorization': `Bearer ${token}`,
                    'Content-Type': 'application/json'
                }
            });
            cleanupTimerRef.current = setInterval(() => {
                setTimeLeft(prev => {
                    if (prev <= 0) {
                        clearInterval(cleanupTimerRef.current);
                        handleGameFinish(false);
                        return 0;
                    }
                    return prev - 1;
                });
            }, 1000);
            // Добавьте проверку типа контента
            const contentType = res.headers.get('content-type');
            if (!contentType || !contentType.includes('application/json')) {
                const text = await res.text();
                throw new Error(`Ожидался JSON, получено: ${text.substring(0, 100)}...`);
            }

            const data = await res.json();

            if (!data.success) {
                throw new Error(data.error || 'Неизвестная ошибка сервера');
            }

            setCleanupGameData(data.transactions);

            if (!res.ok) {
                throw new Error(`Server error: ${res.status}`);
            }

            setCleanupGameData(data.transactions);
            setShowCleanupGame(true);
            setTimeLeft(180);
            setDecryptAttempts(3);
            setMarkedTransactions([]);
            setSuspiciousFound(0);
            setGameResult(null);
            setSeregaComments([]);
            setSelectedTransaction(null);


            return () => clearInterval(timer);
        } catch (err) {
            console.error('Ошибка загрузки игры:', err);
            if (cleanupTimerRef.current) {
                clearInterval(cleanupTimerRef.current);
            }
        }
    }
    useEffect(() => {
        return () => {
            if (cleanupTimerRef.current) {
                clearInterval(cleanupTimerRef.current);
            }
        };
    }, []);

    function addSeregaComment(text) {
        setSeregaComments(prev => [...prev, { text, id: Date.now() }]);
    }

    function handleMarkTransaction(id) {
        setMarkedTransactions(prev => {
            const transaction = cleanupGameData.find(tx => tx.id === id);
            const isCurrentlyMarked = prev.includes(id);
            let newMarkedTransactions;
            let newSuspiciousFound = suspiciousFound;

            if (isCurrentlyMarked) {
                newMarkedTransactions = prev.filter(t => t !== id);
                if (transaction._isSuspicious) {
                    newSuspiciousFound = Math.max(0, suspiciousFound - 1);
                    addSeregaComment("Снята отметка с подозрительной транзакции.");
                } else {
                    addSeregaComment("Снята отметка с транзакции.");
                }
            } else {
                newMarkedTransactions = [...prev, id];
                if (transaction._isSuspicious) {
                    newSuspiciousFound = suspiciousFound + 1;
                    addSeregaComment("Верно! Это явно что-то нечистое.");
                } else {
                    addSeregaComment("Эээ... Ты уверен? Это выглядит нормально.");
                }
            }

            // Обновляем suspiciousFound синхронно с markedTransactions
            setSuspiciousFound(newSuspiciousFound);

            // Проверяем завершение игры с новым значением
            if (transaction._isSuspicious && !isCurrentlyMarked && newSuspiciousFound >= 3) {
                handleGameFinish(true);
            }

            return newMarkedTransactions;
        });
    }

    function handleDecryptField(transactionId, field) {
        if (decryptAttempts <= 0) return;

        setDecryptAttempts(prev => prev);

        setCleanupGameData(prev => {
            return prev.map(tx => {
                if (tx.id === transactionId) {
                    return {
                        ...tx,
                        [field]: field === 'ip' ? tx._realIp : tx._realDevice
                    };
                }
                return tx;
            });
        });

        // Добавляем комментарий от Серёги
        addSeregaComment(field === 'ip'
            ? "Хм... Это VPN или прокси. Подозрительно!"
            : "Старое устройство или эмулятор. Нечисто!");
    }

    function handleAddToArchive(id) {
        if (personalArchive.includes(id)) return;

        setPersonalArchive(prev => [...prev, id]);
        addSeregaComment("Опасно... но может пригодиться.");
    }

    function handleGameFinish(success) {
        if (success) {
            const correctMarks = cleanupGameData.filter(tx =>
                markedTransactions.includes(tx.id) && tx._isSuspicious
            ).length;

            const score = Math.min(3, correctMarks);

            setGameResult('success');
            addSeregaComment(`Уровень ${currentLevel} пройден! Найдено ${score} из 3 аномалий.`);

            // Отправка результата на сервер
            const token = localStorage.getItem('token');
            fetch('/api/cleanup-game/finish', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    Authorization: `Bearer ${token}`
                },
                body: JSON.stringify({
                    success,
                    score,
                    markedTransactions,
                    personalArchive,
                    level: currentLevel
                })
            });

            // Если это 5 уровень - завершаем игру
            if (currentLevel >= 5) {
                setTimeout(() => {
                    setGameResult('complete');
                    setShowCleanupGame(false);
                }, 3000);
            } else {
                // Иначе загружаем следующий уровень
                setTimeout(() => {
                    setCurrentLevel(prev => prev + 1);
                    loadCleanupGame();
                }, 3000);
            }
        } else {
            setGameResult('fail');
            addSeregaComment('Время вышло! Попробуй еще раз.');

            // Добавляем таймер для автоматического перезапуска через 3 секунды
            setTimeout(() => {
                setGameResult(null);
                loadCleanupGame(); // Перезапускаем игру
            }, 3000);
        }
    }


    // Добавляем кнопку для запуска игры в интерфейс
    const cleanupGameButton = (
        <button
            style={{
                position: 'absolute',
                top: 20,
                right: 180,
                zIndex: 1000,
                padding: '10px 18px',
                background: '#d35400',
                color: 'white',
                border: 'none',
                borderRadius: '8px',
                fontSize: '18px',
                cursor: 'pointer',
                boxShadow: '0 2px 8px rgba(0,0,0,0.15)'
            }}
            onClick={loadCleanupGame}
        >
            Чистка или компромат
        </button>
    );


    const buttonStyle = {
        padding: '10px 20px',
        background: '#444',
        color: 'white',
        border: 'none',
        borderRadius: '6px',
        cursor: 'pointer'
    };

    /*const loadDialog = async (npcId) => {
        try {
            const response = await fetch(`/dialogs/${npcId}.json`);
            const data = await response.json();
            setCurrentDialog(data);
            setDialogIndex(0);
            setShowDialog(true);
        } catch (error) {
            console.error('Ошибка загрузки диалога:', error);
        }
    };*/
    const loader = new GLTFLoader();
    // базовая геометрия для объектов типа "chair"
    const baseChairMesh = new THREE.Mesh(
        new THREE.BoxGeometry(1, 1, 1),
        new THREE.MeshBasicMaterial({ visible: false })
    );

    async function loadGLTF(url) {
        return new Promise((resolve, reject) => {
            loader.load(url, gltf => resolve(gltf), undefined, err => reject(err));
        });
    }

    async function enterInteriorMode(interiorId) {
        console.log('enterInteriorMode вызвана для интерьера:', interiorId);

        // Сохраняем текущую позицию игрока
        if (playerRef.current) {
            savedPositionRef.current.copy(playerRef.current.position);
        }

        // Загружаем модель интерьера
        console.log('Загружаем модель интерьера для ID:', interiorId);
        try {
        await loadInteriorModel(interiorId);
            console.log('loadInteriorModel завершена успешно');
        } catch (error) {
            console.error('Ошибка в loadInteriorModel:', error);
        }

        // Загружаем коллизионные данные из JSON
        console.log('Загружаем коллизионные данные из JSON...');
        try {
            const jsonColliders = await loadCollidersFromJSON(1); // Пока используем город 1
            console.log('🔍 Результат loadCollidersFromJSON:', jsonColliders);
            jsonCollidersRef.current = jsonColliders;
            console.log('🔍 jsonCollidersRef.current установлен:', jsonCollidersRef.current?.length || 0, 'объектов');
            console.log('Коллизионные данные загружены:', jsonColliders.length, 'объектов');
            
            // Автоматически применяем цвета и прозрачность из JSON к объектам
            console.log('🎨 Автоматически применяем цвета из JSON к объектам...');
            setTimeout(() => {
                if (window.applyJsonColorsToObjects) {
                    window.applyJsonColorsToObjects();
                }
            }, 100); // Небольшая задержка для завершения загрузки объектов
            
            // Добавляем визуальные коллайдеры в сцену
            const visualColliders = jsonColliders.map(collider => collider.visual);
            visualCollidersRef.current = visualColliders;
            
            visualColliders.forEach(collider => {
                if (sceneRef.current) {
                    sceneRef.current.add(collider);
                    console.log('Добавлен визуальный коллайдер в сцену');
                }
            });
            
            // Добавляем функции для настройки коллайдеров в глобальную область
            window.colliderConfig = COLLIDER_CONFIG;
            window.updateColliderSize = (multiplier) => {
                COLLIDER_CONFIG.sizeMultiplier = multiplier;
                console.log('🔧 Обновлен коэффициент размера коллайдеров:', multiplier);
                reloadColliders();
            };
            
            window.toggleAdaptiveScaling = () => {
                COLLIDER_CONFIG.adaptiveScaling = !COLLIDER_CONFIG.adaptiveScaling;
                console.log('🔧 Адаптивное масштабирование:', COLLIDER_CONFIG.adaptiveScaling ? 'включено' : 'выключено');
                reloadColliders();
            };
            
            window.setColliderLimits = (minSize, maxSize) => {
                COLLIDER_CONFIG.minSize = minSize;
                COLLIDER_CONFIG.maxSize = maxSize;
                console.log('🔧 Установлены ограничения размеров:', { minSize, maxSize });
                reloadColliders();
            };
            
            window.toggleColliderDebug = () => {
                COLLIDER_CONFIG.debugMode = !COLLIDER_CONFIG.debugMode;
                console.log('🔧 Режим отладки коллайдеров:', COLLIDER_CONFIG.debugMode ? 'включен' : 'выключен');
                // Обновляем видимость визуальных коллайдеров
                visualCollidersRef.current.forEach(collider => {
                    collider.visible = COLLIDER_CONFIG.debugMode;
                });
            };
            
            // Функция для принудительной перезагрузки всех коллайдеров из базы данных
            window.reloadAllColliders = async () => {
                console.log('🔄 Принудительная перезагрузка всех коллайдеров...');
                try {
                    // Перезагружаем коллизионные коллайдеры
                    await loadCollidersFromJSON(1);
                    console.log('✅ Коллизионные коллайдеры перезагружены');
                    
                    // Перезагружаем визуальные коллайдеры
                    await loadCustomCollidersForCity(1);
                    console.log('✅ Визуальные коллайдеры перезагружены');
                    
                    console.log('🎉 Все коллайдеры успешно перезагружены из базы данных');
                } catch (error) {
                    console.error('❌ Ошибка при перезагрузке коллайдеров:', error);
                }
            };
            
            // Функция для проверки состояния коллайдеров в базе данных
            window.checkCollidersInDB = async () => {
                console.log('🔍 Проверяем коллайдеры в базе данных...');
                try {
                    const token = localStorage.getItem('token');
                    const response = await fetch('/api/colliders/city/1', {
                        headers: { Authorization: `Bearer ${token}` }
                    });
                    
                    if (response.ok) {
                        const data = await response.json();
                        console.log('📊 Коллайдеры в БД:', data.colliders?.length || 0, 'штук');
                        console.log('🔍 Данные из БД:', data);
                        
                        // Сравниваем с текущими коллайдерами в игре
                        console.log('📊 Коллизионные коллайдеры в игре:', jsonCollidersRef.current?.length || 0, 'штук');
                        console.log('📊 Визуальные коллайдеры в игре:', visualCollidersRef.current?.length || 0, 'штук');
                        
                        return data;
                    } else {
                        console.error('❌ Ошибка загрузки коллайдеров из БД:', response.status);
                    }
                } catch (error) {
                    console.error('❌ Ошибка при проверке коллайдеров в БД:', error);
                }
            };
            
            // Функция для обновления прозрачности всех коллизионных объектов
            window.updateColliderOpacity = (opacity) => {
                console.log('👁️ Обновляем прозрачность всех коллизионных объектов:', opacity);
                obstacles.forEach(obstacle => {
                    if (obstacle.mesh && obstacle.mesh.userData.isCustomCollider) {
                        obstacle.mesh.material.opacity = opacity;
                        if (opacity === 0) {
                            obstacle.mesh.material.visible = false;
                            obstacle.mesh.material.alphaTest = 0;
                        } else {
                            obstacle.mesh.material.visible = true;
                            obstacle.mesh.material.alphaTest = 0.1;
                        }
                    }
                });
                console.log('✅ Прозрачность обновлена для', obstacles.length, 'коллизионных объектов');
            };
            
            // Функция для включения/выключения отображения коллизионных объектов
            window.toggleColliderVisibility = (visible) => {
                console.log('👁️ Переключаем видимость коллизионных объектов:', visible);
                obstacles.forEach(obstacle => {
                    if (obstacle.mesh && obstacle.mesh.userData.isCustomCollider) {
                        obstacle.mesh.visible = visible;
                    }
                });
                console.log('✅ Видимость коллизионных объектов:', visible ? 'включена' : 'выключена');
            };
            
            window.setColliderColor = (r, g, b) => {
                console.log('🎨 Устанавливаем цвет коллайдеров:', { r, g, b });
                visualCollidersRef.current.forEach(collider => {
                    if (collider.material) {
                        const color = (Math.floor(r * 255) << 16) | (Math.floor(g * 255) << 8) | Math.floor(b * 255);
                        collider.material.color.setHex(color);
                    }
                });
            };
            
            window.setColliderOpacity = (opacity) => {
                const clampedOpacity = Math.max(0, Math.min(1, opacity));
                console.log('👁️ Устанавливаем прозрачность коллайдеров:', clampedOpacity);
                visualCollidersRef.current.forEach(collider => {
                    if (collider.material) {
                        collider.material.opacity = clampedOpacity;
                    }
                });
            };
            
            window.randomizeColliderColors = () => {
                console.log('🌈 Случайные цвета для коллайдеров');
                visualCollidersRef.current.forEach(collider => {
                    if (collider.material) {
                        const r = Math.random();
                        const g = Math.random();
                        const b = Math.random();
                        const color = (Math.floor(r * 255) << 16) | (Math.floor(g * 255) << 8) | Math.floor(b * 255);
                        collider.material.color.setHex(color);
                    }
                });
            };
            
            window.setInteriorObjectColor = (r, g, b) => {
                console.log('🎨 Устанавливаем цвет объектов интерьера:', { r, g, b });
                const color = (Math.floor(r * 255) << 16) | (Math.floor(g * 255) << 8) | Math.floor(b * 255);
                
                console.log('🔍 Проверяем interiorGroupRef.current:', interiorGroupRef.current);
                console.log('🔍 Проверяем sceneRef.current:', sceneRef.current);
                
                // Ищем группу интерьера в сцене
                let interiorGroup = interiorGroupRef.current;
                if (!interiorGroup && sceneRef.current) {
                    interiorGroup = sceneRef.current.getObjectByName('interiorGroup');
                    console.log('🔍 Найдена группа интерьера по имени:', interiorGroup);
                }
                
                if (!interiorGroup) {
                    console.warn('⚠️ Группа интерьера не найдена!');
                    return;
                }
                
                let meshCount = 0;
                let materialCount = 0;
                
                // Применяем цвет ко всем объектам интерьера
                interiorGroup.traverse((child) => {
                    if (child.isMesh && child.material) {
                        meshCount++;
                        console.log('🔍 Обрабатываем меш:', child.name || 'unnamed', 'материал:', child.material);
                        
                        if (Array.isArray(child.material)) {
                            child.material.forEach(mat => {
                                if (mat) {
                                    materialCount++;
                                    console.log('🔍 Изменяем материал массива:', mat);
                                    mat.color.setHex(color);
                                    mat.needsUpdate = true;
                                }
                            });
                        } else {
                            materialCount++;
                            console.log('🔍 Изменяем материал:', child.material);
                            child.material.color.setHex(color);
                            child.material.needsUpdate = true;
                        }
                    }
                });
                
                console.log(`✅ Обработано мешей: ${meshCount}, материалов: ${materialCount}`);
            };
            
            window.debugInteriorObjects = () => {
                console.log('🔍 Диагностика объектов интерьера:');
                console.log('interiorGroupRef.current:', interiorGroupRef.current);
                console.log('sceneRef.current:', sceneRef.current);
                
                if (sceneRef.current) {
                    console.log('Все объекты в сцене:');
                    sceneRef.current.traverse((child) => {
                        if (child.isMesh) {
                            console.log('Меш:', child.name || 'unnamed', 'позиция:', child.position, 'материал:', child.material);
                        } else if (child.isGroup) {
                            console.log('Группа:', child.name || 'unnamed', 'дети:', child.children.length);
                        }
                    });
                }
                
                // Ищем группу интерьера
                if (sceneRef.current) {
                    const interiorGroup = sceneRef.current.getObjectByName('interiorGroup');
                    console.log('Группа интерьера найдена:', interiorGroup);
                    
                    if (interiorGroup) {
                        console.log('Объекты в группе интерьера:');
                        interiorGroup.traverse((child) => {
                            if (child.isMesh) {
                                console.log('Меш в интерьере:', child.name || 'unnamed', 'материал:', child.material);
                            }
                        });
                    }
                }
            };
            
            window.setColliderObjectsColor = (r, g, b) => {
                console.log('🎨 Устанавливаем цвет только объектов из JSON коллайдеров:', { r, g, b });
                const color = (Math.floor(r * 255) << 16) | (Math.floor(g * 255) << 8) | Math.floor(b * 255);
                
                if (!jsonCollidersRef.current || jsonCollidersRef.current.length === 0) {
                    console.warn('⚠️ JSON коллайдеры не найдены!');
                    return;
                }
                
                if (!sceneRef.current) {
                    console.warn('⚠️ Сцена не найдена!');
                    return;
                }
                
                let processedCount = 0;
                
                // Проходим по всем коллайдерам из JSON
                jsonCollidersRef.current.forEach((colliderData, index) => {
                    const colliderPos = colliderData.data.position;
                    console.log(`🔍 Ищем объект для коллайдера ${index} в позиции:`, colliderPos);
                    
                    // Ищем объекты в сцене, которые находятся рядом с позицией коллайдера
                    sceneRef.current.traverse((child) => {
                        if (child.isMesh && child.material) {
                            const distance = Math.sqrt(
                                Math.pow(child.position.x - colliderPos.x, 2) + 
                                Math.pow(child.position.y - colliderPos.y, 2) + 
                                Math.pow(child.position.z - colliderPos.z, 2)
                            );
                            
                            // Если объект находится в радиусе 2 единиц от коллайдера
                            if (distance < 2.0) {
                                console.log(`🎯 Найден объект для коллайдера ${index}:`, child.name || 'unnamed', 'расстояние:', distance);
                                
                                if (Array.isArray(child.material)) {
                                    child.material.forEach(mat => {
                                        if (mat) {
                                            mat.color.setHex(color);
                                            mat.needsUpdate = true;
                                        }
                                    });
                                } else {
                                    child.material.color.setHex(color);
                                    child.material.needsUpdate = true;
                                }
                                
                                processedCount++;
                            }
                        }
                    });
                });
                
                console.log(`✅ Обработано объектов: ${processedCount}`);
            };
            
            window.applyJsonColorsToObjects = () => {
                console.log('🎨 Применяем цвета и прозрачность из JSON к объектам в сцене');
                
                if (!jsonCollidersRef.current || jsonCollidersRef.current.length === 0) {
                    console.warn('⚠️ JSON коллайдеры не найдены!');
                    return;
                }
                
                if (!sceneRef.current) {
                    console.warn('⚠️ Сцена не найдена!');
                    return;
                }
                
                let processedCount = 0;
                
                // Проходим по всем коллайдерам из JSON
                jsonCollidersRef.current.forEach((colliderData, index) => {
                    const colliderPos = colliderData.data.position;
                    const colliderData_obj = colliderData.data;
                    
                    console.log(`🔍 Применяем настройки коллайдера ${index}:`, colliderData_obj);
                    
                    // Определяем цвет и прозрачность из JSON данных
                    let color = 0xffffff; // Белый по умолчанию
                    let opacity = 1.0; // Полная непрозрачность по умолчанию
                    
                    if (colliderData_obj.color) {
                        const r = Math.floor((colliderData_obj.color.r || 1.0) * 255);
                        const g = Math.floor((colliderData_obj.color.g || 1.0) * 255);
                        const b = Math.floor((colliderData_obj.color.b || 1.0) * 255);
                        color = (r << 16) | (g << 8) | b;
                    }
                    
                    if (colliderData_obj.opacity !== undefined) {
                        opacity = Math.max(0, Math.min(1, colliderData_obj.opacity));
                    }
                    
                    console.log(`🎨 Применяем цвет ${color.toString(16)} и прозрачность ${opacity} для коллайдера ${index}`);
                    
                    // Ищем объекты в сцене, которые находятся рядом с позицией коллайдера
                    sceneRef.current.traverse((child) => {
                        if (child.isMesh && child.material) {
                            const distance = Math.sqrt(
                                Math.pow(child.position.x - colliderPos.x, 2) + 
                                Math.pow(child.position.y - colliderPos.y, 2) + 
                                Math.pow(child.position.z - colliderPos.z, 2)
                            );
                            
                            // Если объект находится в радиусе 2 единиц от коллайдера
                            if (distance < 2.0) {
                                console.log(`🎯 Найден объект для коллайдера ${index}:`, child.name || 'unnamed', 'расстояние:', distance);
                                
                                if (Array.isArray(child.material)) {
                                    child.material.forEach(mat => {
                                        if (mat) {
                                            mat.color.setHex(color);
                                            mat.transparent = opacity < 1.0;
                                            mat.opacity = opacity;
                                            mat.needsUpdate = true;
                                        }
                                    });
                                } else {
                                    child.material.color.setHex(color);
                                    child.material.transparent = opacity < 1.0;
                                    child.material.opacity = opacity;
                                    child.material.needsUpdate = true;
                                }
                                
                                processedCount++;
                            }
                        }
                    });
                });
                
                console.log(`✅ Применены настройки к ${processedCount} объектам`);
            };
            
            window.setAllObjectsColor = (r, g, b) => {
                console.log('🎨 Устанавливаем цвет ВСЕХ объектов в сцене:', { r, g, b });
                const color = (Math.floor(r * 255) << 16) | (Math.floor(g * 255) << 8) | Math.floor(b * 255);
                
                if (!sceneRef.current) {
                    console.warn('⚠️ Сцена не найдена!');
                    return;
                }
                
                let meshCount = 0;
                let materialCount = 0;
                
                // Применяем цвет ко всем объектам в сцене
                sceneRef.current.traverse((child) => {
                    if (child.isMesh && child.material) {
                        meshCount++;
                        console.log('🔍 Обрабатываем меш:', child.name || 'unnamed');
                        
                        if (Array.isArray(child.material)) {
                            child.material.forEach(mat => {
                                if (mat) {
                                    materialCount++;
                                    mat.color.setHex(color);
                                    mat.needsUpdate = true;
                                }
                            });
                        } else {
                            materialCount++;
                            child.material.color.setHex(color);
                            child.material.needsUpdate = true;
                        }
                    }
                });
                
                console.log(`✅ Обработано мешей: ${meshCount}, материалов: ${materialCount}`);
            };
            
            window.setInteriorObjectOpacity = (opacity) => {
                const clampedOpacity = Math.max(0, Math.min(1, opacity));
                console.log('👁️ Устанавливаем прозрачность объектов интерьера:', clampedOpacity);
                
                // Применяем прозрачность ко всем объектам интерьера
                if (interiorGroupRef.current) {
                    interiorGroupRef.current.traverse((child) => {
                        if (child.isMesh && child.material) {
                            if (Array.isArray(child.material)) {
                                child.material.forEach(mat => {
                                    if (mat) {
                                        mat.transparent = clampedOpacity < 1.0;
                                        mat.opacity = clampedOpacity;
                                        mat.needsUpdate = true;
                                    }
                                });
                            } else {
                                child.material.transparent = clampedOpacity < 1.0;
                                child.material.opacity = clampedOpacity;
                                child.material.needsUpdate = true;
                            }
                        }
                    });
                }
            };
            
            const reloadColliders = () => {
                loadCollidersFromJSON(1).then(newColliders => {
                    // Удаляем старые визуальные коллайдеры
                    visualCollidersRef.current.forEach(collider => {
                        if (sceneRef.current) {
                            sceneRef.current.remove(collider);
                        }
                    });
                    
                    // Обновляем данные
                    jsonCollidersRef.current = newColliders;
                    visualCollidersRef.current = newColliders.map(collider => collider.visual);
                    
                    // Добавляем новые визуальные коллайдеры
                    visualCollidersRef.current.forEach(collider => {
                        if (sceneRef.current) {
                            sceneRef.current.add(collider);
                            collider.visible = COLLIDER_CONFIG.debugMode;
                        }
                    });
                    
                    console.log('✅ Коллайдеры перезагружены с новыми настройками');
                });
            };
            
            window.testCollisions = () => {
                console.log('🧪 Тестируем коллизии:');
                console.log('JSON коллайдеров в ref:', jsonCollidersRef.current?.length || 0);
                console.log('JSON коллайдеров в переменной:', jsonColliders.length);
                console.log('Позиция игрока:', playerRef.current?.position);
                console.log('jsonCollidersRef.current:', jsonCollidersRef.current);
                
                if (jsonCollidersRef.current && jsonCollidersRef.current.length > 0) {
                    const testPos = playerRef.current?.position || new THREE.Vector3(-13.2, -100, -69.3);
                    const playerBox = new THREE.Box3();
                    const playerRadius = 0.4;
                    const playerHeight = 1.6;
                    
                    playerBox.setFromPoints([
                        new THREE.Vector3(testPos.x - playerRadius, testPos.y, testPos.z - playerRadius),
                        new THREE.Vector3(testPos.x + playerRadius, testPos.y + playerHeight, testPos.z + playerRadius)
                    ]);
                    
                    console.log('Player box:', playerBox.min, '->', playerBox.max);
                    
                    jsonCollidersRef.current.forEach((collider, i) => {
                        console.log(`Коллайдер ${i}:`, collider.box.min, '->', collider.box.max);
                        const intersects = playerBox.intersectsBox(collider.box);
                        console.log(`Пересекается: ${intersects}`);
                    });
                }
            };
            
        } catch (error) {
            console.error('Ошибка загрузки коллизионных данных:', error);
        }

        // Переключаемся на камеру от первого лица
        console.log('Переключаемся на камеру от первого лица');
        switchToFirstPersonCamera();

        // Включаем управление мышью для интерьера
        // Курсор оставляем активным (без pointer lock)
        document.body.style.cursor = 'default';

        // Устанавливаем состояние "в интерьере"
        console.log('Устанавливаем setIsInInterior(true)');
        setIsInInterior(true);
        isInInteriorRef.current = true; // Важно! Устанавливаем ref для системы коллизий
        setSelectedHouse(null);

        console.log('isInInterior установлен в true');
        // Сброс кликово-путевого движения и визуальных маркеров
        if (typeof currentPath !== 'undefined') currentPath = [];
        if (typeof pathIndex !== 'undefined') pathIndex = 0;
        if (typeof destination !== 'undefined') destination = null;
        if (typeof blockedTime !== 'undefined') blockedTime = 0;
        if (typeof destinationMarker !== 'undefined' && destinationMarker) destinationMarker.visible = false;
        // Сброс нажатых направлений
        if (moveInputRef.current) {
            Object.keys(moveInputRef.current).forEach(k => { moveInputRef.current[k] = false; });
        }

        // Телепортируем игрока в интерьер (если нужно)
        console.log('Вызываем teleportPlayerToInterior для интерьера:', interiorId);
        await teleportPlayerToInterior(interiorId);
        // Отправляем мгновенное обновление позиции перед уведомлением об интерьере
        if (socketRef.current && playerRef.current) {
            socketRef.current.emit('playerMovement', { x: playerRef.current.position.x, y: playerRef.current.position.y, z: playerRef.current.position.z });
        }
        // Сообщаем серверу о смене интерьера, чтобы видимость игроков фильтровалась по interiorId
        socketRef.current?.emit('interiorChange', { interiorId });
        console.log('teleportPlayerToInterior завершена');
    }
    const teleportPlayerToInterior = async (interiorId) => {
        console.log('teleportPlayerToInterior вызвана для интерьера:', interiorId);
        console.log('playerRef.current:', playerRef.current);
        const token = localStorage.getItem('token');
        if (!token) {
            alert('Пожалуйста, войдите в систему, чтобы войти в здание');
            return;
        }
        try {
            const res = await fetch(`/api/interiors/${interiorId}/enter`, {
                method: 'POST',
                headers: { Authorization: `Bearer ${token}` },
                credentials: 'include',
                cache: 'no-cache'
            });
            if (!res.ok) {
                const errText = await res.text();
                console.error(`Ошибка ${res.status} при получении spawn-координат: ${errText}`);
                alert(`Не удалось получить координаты интерьера: ${errText}`);
                return;
            }
            const { spawn, exit, exitInt } = await res.json();
            if (!spawn) {
                alert('Для этого интерьера не заданы координаты входа');
                return;
            }
            // Нормализуем типы в числа (pg для NUMERIC отдает строки)
            const nSpawn = {
                x: Number(spawn.x),
                y: Number(spawn.y),
                z: Number(spawn.z),
                rot: Number(spawn.rot) || 0
            };
            const nExit = exit && typeof exit === 'object' ? {
                x: Number(exit.x),
                y: Number(exit.y),
                z: Number(exit.z),
                rot: Number(exit.rot) || 0
            } : null;
            const nExitInt = exitInt && typeof exitInt === 'object' ? {
                x: Number(exitInt.x),
                y: Number(exitInt.y),
                z: Number(exitInt.z)
            } : null;
            // Телепортируем игрока в интерьер
            if (playerRef.current) {
                console.log('[ENTER INTERIOR] spawn from server:', nSpawn);
                playerRef.current.position.set(nSpawn.x, nSpawn.y, nSpawn.z);
                playerRef.current.rotation.set(0, nSpawn.rot || 0, 0);
                // Полный сброс движения/целей при входе
                if (typeof currentPath !== 'undefined') currentPath = [];
                if (typeof pathIndex !== 'undefined') pathIndex = 0;
                if (typeof destination !== 'undefined') destination = null;
                if (typeof blockedTime !== 'undefined') blockedTime = 0;
                if (typeof destinationMarker !== 'undefined' && destinationMarker) destinationMarker.visible = false;
                if (moveInputRef.current) {
                    Object.keys(moveInputRef.current).forEach(k => { moveInputRef.current[k] = false; });
                }
            }
            console.log('[ENTER INTERIOR] exit from server:', nExit);
            setCurrentExit(nExit || null);
            // Визуализируем маркер выхода внутри интерьера, чтобы по клику можно было выйти
            if (nExit && typeof nExit.x === 'number' && typeof nExit.z === 'number') {
                try { addExitMarker(nExit); } catch (e) { console.warn('[ENTER INTERIOR] addExitMarker failed', e); }
            }
            // Запоминаем позицию внутреннего триггера выхода, если пришла
            if (nExitInt && typeof nExitInt.x === 'number') {
                console.log('[ENTER INTERIOR] exitInt (internal exit trigger):', nExitInt);
                interiorExitPosRef.current = new THREE.Vector3(nExitInt.x, nExitInt.y || 0, nExitInt.z);
            }
            console.log('teleportPlayerToInterior завершена успешно');
        } catch (e) {
            console.error('Failed to enter interior:', e);
        }
    };

    // Функция для создания визуального коллайдера
    const createVisualCollider = (colliderData, index) => {
        const geometry = new THREE.BoxGeometry(
            colliderData.scale.x,
            colliderData.scale.y,
            colliderData.scale.z
        );
        
        const material = new THREE.MeshBasicMaterial({
            color: 0xff0000, // Красный цвет
            transparent: true,
            opacity: 0.3, // Полупрозрачность
            wireframe: false
        });
        
        const mesh = new THREE.Mesh(geometry, material);
        mesh.position.set(
            colliderData.position.x,
            colliderData.position.y,
            colliderData.position.z
        );
        mesh.rotation.set(
            colliderData.rotation.x,
            colliderData.rotation.y,
            colliderData.rotation.z
        );
        
        // Добавляем метку для отладки
        mesh.userData = {
            isCollider: true,
            colliderIndex: index,
            originalData: colliderData
        };
        
        return mesh;
    };

    // Создаем визуальный коллайдер на основе реальных размеров мешей из модели
    const createVisualColliderFromModel = (colliderData, index) => {
        // Находим соответствующий меш в модели интерьера
        let targetMesh = null;
        if (interiorCollidersRef.current && interiorCollidersRef.current.length > 0) {
            // Ищем меш, который ближе всего к позиции коллайдера
            const targetPos = new THREE.Vector3(
                colliderData.position.x,
                colliderData.position.y,
                colliderData.position.z
            );
            
            let minDistance = Infinity;
            for (const mesh of interiorCollidersRef.current) {
                if (!mesh.geometry) continue;
                
                // Получаем реальные размеры меша
                const box = new THREE.Box3().setFromObject(mesh);
                const center = new THREE.Vector3();
                box.getCenter(center);
                
                const distance = targetPos.distanceTo(center);
                if (distance < minDistance) {
                    minDistance = distance;
                    targetMesh = mesh;
                }
            }
        }
        
        let geometry, position, rotation, scale;
        
        if (targetMesh) {
            // Используем реальные размеры меша
            const box = new THREE.Box3().setFromObject(targetMesh);
            const size = new THREE.Vector3();
            const center = new THREE.Vector3();
            box.getSize(size);
            box.getCenter(center);
            
            geometry = new THREE.BoxGeometry(size.x, size.y, size.z);
            position = center;
            rotation = targetMesh.rotation;
            scale = targetMesh.scale;
            
            console.log('🎯 Используем реальные размеры меша:', {
                size: size,
                center: center,
                rotation: rotation,
                scale: scale
            });
        } else {
            // Используем JSON данные с умным масштабированием
            let adjustedSize = new THREE.Vector3(
                colliderData.scale.x,
                colliderData.scale.y,
                colliderData.scale.z
            );
            
            if (COLLIDER_CONFIG.adaptiveScaling) {
                // Адаптивное масштабирование на основе размеров объекта
                const avgSize = (adjustedSize.x + adjustedSize.y + adjustedSize.z) / 3;
                
                if (avgSize < 1.0) {
                    // Для маленьких объектов используем больший коэффициент
                    const adaptiveMultiplier = Math.max(COLLIDER_CONFIG.sizeMultiplier, 3.0);
                    adjustedSize.multiplyScalar(adaptiveMultiplier);
                } else if (avgSize < 5.0) {
                    // Для средних объектов используем стандартный коэффициент
                    adjustedSize.multiplyScalar(COLLIDER_CONFIG.sizeMultiplier);
                } else {
                    // Для больших объектов используем меньший коэффициент
                    adjustedSize.multiplyScalar(Math.max(COLLIDER_CONFIG.sizeMultiplier * 0.8, 1.5));
                }
                
                // Применяем минимальные и максимальные ограничения
                adjustedSize.x = Math.max(Math.min(adjustedSize.x, COLLIDER_CONFIG.maxSize), COLLIDER_CONFIG.minSize);
                adjustedSize.y = Math.max(Math.min(adjustedSize.y, COLLIDER_CONFIG.maxSize), COLLIDER_CONFIG.minSize);
                adjustedSize.z = Math.max(Math.min(adjustedSize.z, COLLIDER_CONFIG.maxSize), COLLIDER_CONFIG.minSize);
            } else {
                // Простое масштабирование
                adjustedSize.multiplyScalar(COLLIDER_CONFIG.sizeMultiplier);
            }
            
            geometry = new THREE.BoxGeometry(adjustedSize.x, adjustedSize.y, adjustedSize.z);
            position = new THREE.Vector3(
                colliderData.position.x,
                colliderData.position.y,
                colliderData.position.z
            );
            rotation = new THREE.Euler(
                colliderData.rotation.x,
                colliderData.rotation.y,
                colliderData.rotation.z
            );
            scale = new THREE.Vector3(1, 1, 1);
            
            console.log('⚠️ Используем JSON данные с адаптивными размерами для коллайдера', index, {
                originalSize: colliderData.scale,
                adjustedSize: adjustedSize,
                avgOriginalSize: (colliderData.scale.x + colliderData.scale.y + colliderData.scale.z) / 3
            });
        }
        
        // Определяем цвет и прозрачность из JSON данных или используем значения по умолчанию
        let color = 0xff0000; // Красный по умолчанию
        let opacity = 0.3; // Прозрачность по умолчанию
        
        if (colliderData.color) {
            // Конвертируем RGB значения (0-1) в hex цвет
            const r = Math.floor((colliderData.color.r || 1.0) * 255);
            const g = Math.floor((colliderData.color.g || 0.0) * 255);
            const b = Math.floor((colliderData.color.b || 0.0) * 255);
            color = (r << 16) | (g << 8) | b;
        }
        
        if (colliderData.opacity !== undefined) {
            opacity = Math.max(0, Math.min(1, colliderData.opacity)); // Ограничиваем от 0 до 1
        }
        
        const material = new THREE.MeshBasicMaterial({
            color: color,
            transparent: true,
            opacity: opacity,
            wireframe: false
        });
        
        const mesh = new THREE.Mesh(geometry, material);
        mesh.position.copy(position);
        mesh.rotation.copy(rotation);
        mesh.scale.copy(scale);
        
        // Добавляем метку для отладки
        mesh.userData = {
            isCollider: true,
            colliderIndex: index,
            originalData: colliderData,
            isFromModel: !!targetMesh
        };
        
        return mesh;
    };

    // Функция для применения цвета и прозрачности к объектам интерьера
    const applyColliderColorAndOpacity = (scene, objectData) => {
        // Ищем соответствующий коллайдер в JSON данных
        const matchingCollider = jsonCollidersRef.current?.find(collider => {
            const pos = collider.data.position;
            const objPos = { x: objectData.x || 0, y: objectData.y || 0, z: objectData.z || 0 };
            
            // Проверяем близость позиций (с небольшой погрешностью)
            const distance = Math.sqrt(
                Math.pow(pos.x - objPos.x, 2) + 
                Math.pow(pos.y - objPos.y, 2) + 
                Math.pow(pos.z - objPos.z, 2)
            );
            
            return distance < 2.0; // Если объекты находятся в радиусе 2 единиц
        });
        
        if (matchingCollider && matchingCollider.data) {
            const colliderData = matchingCollider.data;
            
            // Определяем цвет и прозрачность
            let color = 0xffffff; // Белый по умолчанию
            let opacity = 1.0; // Полная непрозрачность по умолчанию
            
            if (colliderData.color) {
                const r = Math.floor((colliderData.color.r || 1.0) * 255);
                const g = Math.floor((colliderData.color.g || 1.0) * 255);
                const b = Math.floor((colliderData.color.b || 1.0) * 255);
                color = (r << 16) | (g << 8) | b;
            }
            
            if (colliderData.opacity !== undefined) {
                opacity = Math.max(0, Math.min(1, colliderData.opacity));
            }
            
            // Применяем цвет и прозрачность ко всем мешам в сцене
            scene.traverse((child) => {
                if (child.isMesh && child.material) {
                    if (Array.isArray(child.material)) {
                        child.material = child.material.map(mat => {
                            if (!mat) return mat;
                            const m = mat.clone();
                            m.color.setHex(color);
                            m.transparent = opacity < 1.0;
                            m.opacity = opacity;
                            m.needsUpdate = true;
                            return m;
                        });
                    } else {
                        child.material = child.material.clone();
                        child.material.color.setHex(color);
                        child.material.transparent = opacity < 1.0;
                        child.material.opacity = opacity;
                        child.material.needsUpdate = true;
                    }
                }
            });
            
            console.log('🎨 Применен цвет и прозрачность к объекту:', {
                position: { x: objectData.x, y: objectData.y, z: objectData.z },
                color: color,
                opacity: opacity,
                colliderData: colliderData
            });
        }
    };

    // Функция для загрузки коллизионных данных из базы данных с fallback на JSON
    const loadCollidersFromJSON = async (cityId = 1) => {
        console.log('🔍 loadCollidersFromJSON вызвана для города:', cityId);
        try {
            // Сначала пробуем загрузить из базы данных
            const token = localStorage.getItem('token');
            let response = await fetch(`/api/colliders/city/${cityId}`, {
                headers: { Authorization: `Bearer ${token}` }
            });
            
            // Если новый API недоступен (500 ошибка), пробуем старый JSON API
            if (!response.ok && response.status === 500) {
                console.log('🔄 Новый API недоступен, пробуем старый JSON API...');
                const url = `/colliders_city_${cityId}.json`;
                console.log('🔍 Загружаем URL:', url);
                response = await fetch(url);
            }
            
            console.log('🔍 Ответ сервера:', response.status, response.ok);
            
            if (!response.ok) {
                console.warn('Не удалось загрузить коллизионные данные для города:', cityId);
                return [];
            }
            
            const data = await response.json();
            console.log('🔍 Загруженные данные:', data);
            
            // Обрабатываем данные в зависимости от источника
            let collidersData;
            if (data.colliders) {
                // Данные из базы данных (уже в правильном формате)
                collidersData = data.colliders;
                console.log('📊 Загружены коллайдеры из базы данных:', collidersData.length, 'объектов');
                console.log('🔍 Пример коллайдера из БД:', collidersData[0]);
            } else if (Array.isArray(data)) {
                // Данные из JSON файла (прямой массив)
                collidersData = data;
                console.log('📄 Загружены коллайдеры из JSON файла:', collidersData.length, 'объектов');
                console.log('🔍 Пример коллайдера из JSON:', collidersData[0]);
            } else {
                console.warn('Неизвестный формат данных коллайдеров:', data);
                return [];
            }
            
            // Преобразуем данные в Box3 объекты
            const colliderBoxes = collidersData.map((colliderData, index) => {
                const box = new THREE.Box3();
                
                // Создаем центр бокса
                const center = new THREE.Vector3(
                    colliderData.position.x,
                    colliderData.position.y,
                    colliderData.position.z
                );
                
                // Увеличиваем размеры для полного покрытия объекта с адаптивным масштабированием
                let size = new THREE.Vector3(
                    colliderData.scale.x,
                    colliderData.scale.y,
                    colliderData.scale.z
                );
                
                if (COLLIDER_CONFIG.adaptiveScaling) {
                    // Адаптивное масштабирование на основе размеров объекта
                    const avgSize = (size.x + size.y + size.z) / 3;
                    
                    if (avgSize < 1.0) {
                        // Для маленьких объектов используем больший коэффициент
                        const adaptiveMultiplier = Math.max(COLLIDER_CONFIG.sizeMultiplier, 3.0);
                        size.multiplyScalar(adaptiveMultiplier);
                    } else if (avgSize < 5.0) {
                        // Для средних объектов используем стандартный коэффициент
                        size.multiplyScalar(COLLIDER_CONFIG.sizeMultiplier);
                    } else {
                        // Для больших объектов используем меньший коэффициент
                        size.multiplyScalar(Math.max(COLLIDER_CONFIG.sizeMultiplier * 0.8, 1.5));
                    }
                    
                    // Применяем минимальные и максимальные ограничения
                    size.x = Math.max(Math.min(size.x, COLLIDER_CONFIG.maxSize), COLLIDER_CONFIG.minSize);
                    size.y = Math.max(Math.min(size.y, COLLIDER_CONFIG.maxSize), COLLIDER_CONFIG.minSize);
                    size.z = Math.max(Math.min(size.z, COLLIDER_CONFIG.maxSize), COLLIDER_CONFIG.minSize);
                } else {
                    // Простое масштабирование
                    size.multiplyScalar(COLLIDER_CONFIG.sizeMultiplier);
                }
                
                // Устанавливаем min и max точки с увеличенными размерами
                const min = center.clone().sub(size.clone().multiplyScalar(0.5));
                const max = center.clone().add(size.clone().multiplyScalar(0.5));
                
                box.setFromPoints([min, max]);
                
                // Создаем визуальный коллайдер на основе реальных размеров мешей из модели
                const visualCollider = createVisualColliderFromModel(colliderData, index);
                
                console.log('Создан коллайдер с увеличенными размерами:', {
                    center: center,
                    originalSize: colliderData.scale,
                    adjustedSize: size,
                    min: min,
                    max: max,
                    visual: visualCollider
                });
                
                return {
                    box: box,
                    data: colliderData,
                    visual: visualCollider
                };
            });
            
            console.log('🔍 Возвращаем colliderBoxes:', colliderBoxes.length, 'объектов');
            console.log('🔍 Первый коллайдер:', colliderBoxes[0]);
            
            // Автоматически применяем цвета к объектам интерьера, если они уже загружены
            setTimeout(() => {
                if (interiorGroupRef.current) {
                    console.log('🎨 JSON коллайдеры загружены, применяем цвета к объектам интерьера');
                    colliderBoxes.forEach((colliderData, index) => {
                        const colliderPos = colliderData.data.position;
                        const colliderData_obj = colliderData.data;
                        
                        // Определяем цвет и прозрачность из JSON данных
                        let color = 0xffffff; // Белый по умолчанию
                        let opacity = 1.0; // Полная непрозрачность по умолчанию
                        
                        if (colliderData_obj.color) {
                            const r = Math.floor((colliderData_obj.color.r || 1.0) * 255);
                            const g = Math.floor((colliderData_obj.color.g || 1.0) * 255);
                            const b = Math.floor((colliderData_obj.color.b || 1.0) * 255);
                            color = (r << 16) | (g << 8) | b;
                        }
                        
                        if (colliderData_obj.opacity !== undefined) {
                            opacity = Math.max(0, Math.min(1, colliderData_obj.opacity));
                        }
                        
                        console.log(`🎨 Применяем цвет ${color.toString(16)} и прозрачность ${opacity} к объектам интерьера`);
                        
                        // Применяем цвет к объектам интерьера
                        interiorGroupRef.current.traverse((child) => {
                            if (child.isMesh && child.material) {
                                const distance = Math.sqrt(
                                    Math.pow(child.position.x - colliderPos.x, 2) + 
                                    Math.pow(child.position.y - colliderPos.y, 2) + 
                                    Math.pow(child.position.z - colliderPos.z, 2)
                                );
                                
                                if (distance < 2.0) {
                                    console.log(`🎯 Применяем цвет к объекту интерьера:`, child.name || 'unnamed');
                                    
                                    if (Array.isArray(child.material)) {
                                        child.material.forEach(mat => {
                                            if (mat) {
                                                mat.color.setHex(color);
                                                mat.transparent = opacity < 1.0;
                                                mat.opacity = opacity;
                                                mat.needsUpdate = true;
                                            }
                                        });
                                    } else {
                                        child.material.color.setHex(color);
                                        child.material.transparent = opacity < 1.0;
                                        child.material.opacity = opacity;
                                        child.material.needsUpdate = true;
                                    }
                                }
                            }
                        });
                    });
                }
            }, 100);
            
            return colliderBoxes;
        } catch (error) {
            console.error('Ошибка загрузки коллизионных данных:', error);
            return [];
        }
    };

    async function loadInteriorModel(interiorId) {
        console.log('loadInteriorModel вызвана для интерьера:', interiorId);
        const token = localStorage.getItem('token');
        console.log('Токен найден:', !!token);

        try {
            console.log('Запрашиваем определение интерьера с сервера...');
            const defRes = await fetch(`/api/interiors/${interiorId}/definition`, {
                headers: { Authorization: `Bearer ${token}` },
                credentials: 'include',
                cache: 'no-cache'
            });

            console.log('Ответ сервера:', defRes.status, defRes.ok);

            if (!defRes.ok) {
                const errText = await defRes.text();
                console.error(`Ошибка ${defRes.status} при загрузке определения интерьера: ${errText}`);
                return;
            }

            const { glb, objects } = await defRes.json();
            const baseUrl = window.location.origin;
            const glbUrl = baseUrl + glb;
            console.log('Loading interior GLB from', glbUrl);

            // Проверяем доступность GLB файла
            const headResp = await fetch(glbUrl, { method: 'HEAD', cache: 'no-cache' });
            if (!headResp.ok) {
                console.error(`GLB not reachable: HTTP ${headResp.status}`);
                return;
            }

            const gltf = await loadGLTF(glbUrl);
            const scene = sceneRef.current;

            // Создаем группу для интерьера
            const intGroup = new THREE.Group();
            intGroup.name = 'interiorGroup';
            intGroup.add(gltf.scene);

            // Декуплируем и гарантируем непрозрачность материалов интерьера
            gltf.scene.traverse((child) => {
                if (child.isMesh && child.material) {
                    if (Array.isArray(child.material)) {
                        child.material = child.material.map(mat => {
                            if (!mat) return mat;
                            const m = mat.clone();
                            m.transparent = false;
                            m.opacity = 1;
                            m.depthWrite = true;
                            m.needsUpdate = true;
                            return m;
                        });
                    } else {
                        child.material = child.material.clone();
                        child.material.transparent = false;
                        child.material.opacity = 1;
                        child.material.depthWrite = true;
                        child.material.needsUpdate = true;
                    }
                }
            });

            // Построение коллайдеров интерьера (простые коробки по мешам)
            const colliders = [];
            gltf.scene.traverse((child) => {
                if (child.isMesh && child.geometry) {
                    // Пропускаем интерактивные объекты и сферы
                    if (child.userData && (child.userData.interactable || child.userData.payload)) return;
                    if (child.geometry.type === 'SphereGeometry') return;
                    colliders.push(child);
                }
            });
            interiorCollidersRef.current = colliders;
            console.log('Инициализировано коллайдеров интерьера:', colliders.length);
            try {
                const boxes = [];
                for (const m of colliders) {
                    if (!m) continue;
                    const b = new THREE.Box3().setFromObject(m).expandByScalar(0.03);
                    const h = b.max.y - b.min.y;
                    if (h < 0.15) continue; // игнорируем пол/ковёр
                    boxes.push(b);
                }
                interiorColliderBoxesRef.current = boxes;
                console.log('[INTERIOR] colliders boxes:', boxes.length);

                // Визуализация (вкл/выкл через interiorDebugEnabledRef)
                if (interiorDebugEnabledRef.current && sceneRef.current) {
                    // Очистим старые
                    if (Array.isArray(interiorDebugHelpersRef.current)) {
                        for (const h of interiorDebugHelpersRef.current) {
                            try { sceneRef.current.remove(h); } catch (_) {}
                        }
                    }
                    interiorDebugHelpersRef.current = [];
                    const mat = new THREE.LineBasicMaterial({ color: 0xff00ff });
                    for (const box of boxes) {
                        const size = new THREE.Vector3();
                        const center = new THREE.Vector3();
                        box.getSize(size);
                        box.getCenter(center);
                        const geom = new THREE.BoxGeometry(size.x, size.y, size.z);
                        const edges = new THREE.EdgesGeometry(geom);
                        const helper = new THREE.LineSegments(edges, mat);
                        helper.position.copy(center);
                        sceneRef.current.add(helper);
                        interiorDebugHelpersRef.current.push(helper);
                    }
                }
            } catch (_) {}

            // Добавляем объекты интерьера
            interiorInteractablesRef.current = []; // сбрасываем реестр интерактива

            // Хелпер для определения ID NPC по пути к модели
            const getNpcIdFromModel = (url) => {
                if (!url || typeof url !== 'string') return null;
                const lower = url.toLowerCase();
                if (lower.includes('/models/npc/galina.glb')) return 'Adventurer';
                if (lower.includes('/models/npc/oxranik.glb')) return 'Oxranik';
                if (lower.includes('/models/npc/guard.glb')) return 'guard';
                if (lower.includes('/models/npc/beachcharacter.glb')) return 'BeachCharacter';
                if (lower.includes('/models/npc/bartender.glb')) return 'bartender';
                if (lower.includes('/models/npc/computer.glb')) return 'Computer';
                return null;
            };

            for (const o of objects) {
                if (o.model_url) {
                    try {
                        const objGltf = await loadGLTF(baseUrl + o.model_url);
                        objGltf.scene.position.set(o.x, o.y, o.z);
                        objGltf.scene.rotation.set(o.rot_x, o.rot_y, o.rot_z);
                        objGltf.scene.scale.set(o.scale, o.scale, o.scale);
                        
                        // Применяем цвет и прозрачность из JSON данных коллайдеров
                        applyColliderColorAndOpacity(objGltf.scene, o);
                        
                        intGroup.add(objGltf.scene);

                        // Добавляем меши объекта как коллайдеры интерьера
                        objGltf.scene.traverse((child) => {
                            if (child.isMesh && child.geometry) {
                                colliders.push(child);
                            }
                        });

                        // Если это NPC внутри интерьера — добавим кликабельную хит‑зону
                        const isNpc = (o.type === 'npc') || (typeof o.model_url === 'string' && o.model_url.includes('/models/npc/'));
                        if (isNpc) {
                            const npcId = o.id || getNpcIdFromModel(o.model_url);
                            console.log('[INTERIOR NPC] detected npc, id:', npcId, 'at', { x: o.x, y: o.y, z: o.z });
                            const hit = new THREE.Mesh(
                                new THREE.SphereGeometry(1.2),
                                new THREE.MeshBasicMaterial({ color: 0x00ff00, transparent: true, opacity: 0.0001, depthWrite: false })
                            );
                            hit.position.set(o.x, (o.y ?? 0) + 1.0, o.z);
                            hit.userData.interactable = true;
                            hit.userData.payload = { type: 'npc', id: npcId };
                            hit.visible = true;
                            intGroup.add(hit);
                            interiorInteractablesRef.current.push(hit);

                            // Также помечаем сам корень модели как кликабельный NPC
                            try {
                                objGltf.scene.userData = objGltf.scene.userData || {};
                                objGltf.scene.userData.interactable = true;
                                objGltf.scene.userData.payload = { type: 'npc', id: npcId };
                                interiorInteractablesRef.current.push(objGltf.scene);
                                // и помечаем как isNpc/npcId для fallback
                                objGltf.scene.userData.isNpc = true;
                                objGltf.scene.userData.npcId = npcId;
                            } catch (_) { }
                        }
                    } catch (e) {
                        console.warn('Не удалось загрузить объект интерьера', o.model_url, e);
                    }
                } else {
                    const mesh = baseChairMesh.clone();
                    mesh.position.set(o.x, o.y, o.z);
                    mesh.rotation.set(o.rot_x, o.rot_y, o.rot_z);
                    mesh.scale.set(o.scale, o.scale, o.scale);
                    if (mesh.material) {
                        if (Array.isArray(mesh.material)) {
                            mesh.material = mesh.material.map(mat => {
                                if (!mat) return mat;
                                const m = mat.clone();
                                m.transparent = false;
                                m.opacity = 1;
                                m.depthWrite = true;
                                m.needsUpdate = true;
                                return m;
                            });
                        } else {
                            mesh.material = mesh.material.clone();
                            mesh.material.transparent = false;
                            mesh.material.opacity = 1;
                            mesh.material.depthWrite = true;
                            mesh.material.needsUpdate = true;
                        }
                    }
                    intGroup.add(mesh);
                    // Плейсхолдер не рендерим, но используем как коллайдер
                    try { mesh.visible = false; } catch (_) { }
                    // Плейсхолдер без GLTF тоже участвует в коллизиях
                    colliders.push(mesh);
                }

                // Если сервер пометил объект как «интерактивный/маркер» — кликабельная зона
                if (o.interactable || o.marker) {
                    const hit = new THREE.Mesh(
                        new THREE.SphereGeometry(0.6),
                        new THREE.MeshBasicMaterial({ color: 0x00ff00, transparent: true, opacity: 0.0001, depthWrite: false })
                    );
                    hit.position.set(o.x, o.y + 1.0, o.z);
                    hit.userData.interactable = true;
                    hit.userData.payload = { type: o.type || 'marker', id: o.id || null, label: o.label || 'Интерактив' };
                    hit.visible = true; // кликабелен
                    try { if (hit.material) hit.material.visible = false; } catch (_) { }
                    intGroup.add(hit);
                    interiorInteractablesRef.current.push(hit);
                }
                // Сохраним позицию внутреннего выхода, если есть
                if (typeof o.exit_int_x === 'number' && typeof o.exit_int_y === 'number' && typeof o.exit_int_z === 'number') {
                    interiorExitPosRef.current = new THREE.Vector3(o.exit_int_x, o.exit_int_y, o.exit_int_z);
                }
            }

            // Добавляем освещение для интерьера
            const light = new THREE.AmbientLight(0xffffff, 1);
            intGroup.add(light);

            // Добавляем группу в сцену
            scene.add(intGroup);
            interiorGroupRef.current = intGroup;

            console.log('Модель интерьера загружена успешно');
            
            // Автоматически применяем цвета из JSON к объектам интерьера
            console.log('🎨 Применяем цвета из JSON к объектам интерьера...');
            setTimeout(() => {
                // Применяем цвета к объектам интерьера, если JSON коллайдеры уже загружены
                if (jsonCollidersRef.current && jsonCollidersRef.current.length > 0) {
                    console.log('🔍 JSON коллайдеры найдены, применяем цвета к объектам интерьера');
                    jsonCollidersRef.current.forEach((colliderData, index) => {
                        const colliderPos = colliderData.data.position;
                        const colliderData_obj = colliderData.data;
                        
                        // Определяем цвет и прозрачность из JSON данных
                        let color = 0xffffff; // Белый по умолчанию
                        let opacity = 1.0; // Полная непрозрачность по умолчанию
                        
                        if (colliderData_obj.color) {
                            const r = Math.floor((colliderData_obj.color.r || 1.0) * 255);
                            const g = Math.floor((colliderData_obj.color.g || 1.0) * 255);
                            const b = Math.floor((colliderData_obj.color.b || 1.0) * 255);
                            color = (r << 16) | (g << 8) | b;
                        }
                        
                        if (colliderData_obj.opacity !== undefined) {
                            opacity = Math.max(0, Math.min(1, colliderData_obj.opacity));
                        }
                        
                        console.log(`🎨 Применяем цвет ${color.toString(16)} и прозрачность ${opacity} к объектам интерьера`);
                        
                        // Применяем цвет к объектам интерьера
                        intGroup.traverse((child) => {
                            if (child.isMesh && child.material) {
                                const distance = Math.sqrt(
                                    Math.pow(child.position.x - colliderPos.x, 2) + 
                                    Math.pow(child.position.y - colliderPos.y, 2) + 
                                    Math.pow(child.position.z - colliderPos.z, 2)
                                );
                                
                                if (distance < 2.0) {
                                    console.log(`🎯 Применяем цвет к объекту интерьера:`, child.name || 'unnamed');
                                    
                                    if (Array.isArray(child.material)) {
                                        child.material.forEach(mat => {
                                            if (mat) {
                                                mat.color.setHex(color);
                                                mat.transparent = opacity < 1.0;
                                                mat.opacity = opacity;
                                                mat.needsUpdate = true;
                                            }
                                        });
                                    } else {
                                        child.material.color.setHex(color);
                                        child.material.transparent = opacity < 1.0;
                                        child.material.opacity = opacity;
                                        child.material.needsUpdate = true;
                                    }
                                }
                            }
                        });
                    });
                } else {
                    console.log('⚠️ JSON коллайдеры еще не загружены, цвета будут применены позже');
                }
            }, 200); // Задержка для завершения загрузки объектов
        } catch (e) {
            console.error('Ошибка загрузки модели интерьера:', e);
        }
    }

    // Кэш для загруженных текстурпаков
    const texturePackCache = new Map();



    function loadTexturePackForMesh(texturePackUrl, mesh, forceReplace = false) {
        console.log('loadTexturePackForMesh вызвана:', { texturePackUrl, mesh });

        // Проверяем, есть ли уже загруженный текстурпак в кэше
        if (texturePackCache.has(texturePackUrl)) {
            console.log('Используем кэшированный текстурпак:', texturePackUrl);
            const cachedTextures = texturePackCache.get(texturePackUrl);
            applyTexturesToMesh(mesh, cachedTextures, forceReplace, texturePackUrl);
            return;
        }

        console.log('Загружаем текстурпак для меша:', texturePackUrl);

        // Загружаем текстурпак асинхронно
        const baseUrl = window.location.origin;
        const fullUrl = texturePackUrl.startsWith('http') ? texturePackUrl : baseUrl + texturePackUrl;
        console.log('Полный URL для загрузки:', fullUrl);

        fetch(fullUrl)
            .then(response => {
                console.log('Ответ сервера для текстурпака:', response.status, response.statusText);
                if (!response.ok) {
                    throw new Error(`HTTP ${response.status}: ${response.statusText}`);
                }
                console.log('Начинаем парсинг JSON...');
                return response.json();
            })
            .then(texturePack => {
                console.log('Загруженный текстурпак:', texturePack);

                // Кэшируем загруженный текстурпак
                texturePackCache.set(texturePackUrl, texturePack);

                // Проверяем, что меш все еще существует и валиден
                if (mesh && mesh.isMesh && mesh.material) {
                    // Применяем текстуры к мешу (функция сама проверит типы материалов/массивы)
                    applyTexturesToMesh(mesh, texturePack, forceReplace, texturePackUrl);
                } else {
                    console.warn('Меш не подходит для применения текстурпака:', {
                        hasMesh: !!mesh,
                        isMesh: mesh?.isMesh,
                        hasMaterial: !!mesh?.material
                    });
                }
            })
            .catch(error => {
                console.error('Ошибка загрузки текстурпака:', texturePackUrl, error);
                // В случае ошибки оставляем оригинальные материалы
                if (mesh.material) {
                    mesh.material.needsUpdate = true;
                }
            });
    }

    // Предсоздаём материал в стиле MapEditor для citypack.json
    const cityPackMaterialCache = new Map(); // url -> material

    function getCityPackMaterial(texturePackUrl, texturePack) {
        if (cityPackMaterialCache.has(texturePackUrl)) return cityPackMaterialCache.get(texturePackUrl);
        const mat = new THREE.MeshStandardMaterial();
        if (typeof texturePack.baseColor === 'string') {
            const loader = new THREE.TextureLoader();
            const tex = loader.load(texturePack.baseColor);
            if (THREE.SRGBColorSpace) tex.colorSpace = THREE.SRGBColorSpace;
            mat.map = tex;
        }
        mat.roughness = typeof texturePack.roughness === 'number' ? texturePack.roughness : 0.5;
        mat.metalness = typeof texturePack.metalness === 'number' ? texturePack.metalness : 0.1;
        cityPackMaterialCache.set(texturePackUrl, mat);
        return mat;
    }

    function applyTexturesToMesh(mesh, texturePack, forceReplace = false, texturePackUrl) {
        console.log('applyTexturesToMesh вызвана:', { mesh, texturePack });

        if (!mesh || !texturePack) {
            console.warn('applyTexturesToMesh: отсутствует меш или текстурпак', {
                hasMesh: !!mesh,
                hasTexturePack: !!texturePack
            });
            return;
        }

        if (!mesh.material) {
            console.warn('У меша нет материала');
            return;
        }

        const materials = Array.isArray(mesh.material) ? mesh.material : [mesh.material];
        const targetMaterials = materials.filter(m => m && m.isMaterial && (m.type === 'MeshStandardMaterial' || m.type === 'MeshPhysicalMaterial' || m.type === 'MeshPhongMaterial'));
        if (targetMaterials.length === 0) {
            console.warn('Нет подходящих материалов для применения текстур:', mesh.material);
            return;
        }

        // Особый режим: если это citypack.json — ведём себя как MapEditor: заменяем материал на единый стандартный
        if (texturePackUrl === '/packs/citypack.json') {
            const mat = getCityPackMaterial(texturePackUrl, texturePack).clone();
            if (Array.isArray(mesh.material)) {
                mesh.material = mesh.material.map(() => mat.clone());
            } else {
                mesh.material = mat.clone();
            }
            mesh.traverse?.((child) => {
                if (child.isMesh) {
                    child.material = Array.isArray(child.material) ? child.material.map(() => mat.clone()) : mat.clone();
                }
            });
            return;
        }

        // baseColor map — по умолчанию не перетираем; при forceReplace перезаписываем
        if (typeof texturePack.baseColor === 'string') {
            console.log('Загружаем baseColor текстуру:', texturePack.baseColor);
            const textureLoader = new THREE.TextureLoader();
            textureLoader.load(texturePack.baseColor, (texture) => {
                if (THREE.SRGBColorSpace) {
                    texture.colorSpace = THREE.SRGBColorSpace;
                }
                targetMaterials.forEach(mat => {
                    if (mat.type === 'MeshStandardMaterial' || mat.type === 'MeshPhysicalMaterial') {
                        if (forceReplace || !mat.map) {
                            mat.map = texture;
                            if (mat.color && mat.color.set) mat.color.set(0xffffff);
                            mat.needsUpdate = true;
                        }
                    }
                });
            }, undefined, (error) => {
                console.error('Ошибка загрузки baseColor текстуры:', error);
            });
        }

        // normal map
        if (typeof texturePack.normal === 'string') {
            console.log('Загружаем normal текстуру:', texturePack.normal);
            const textureLoader = new THREE.TextureLoader();
            textureLoader.load(texturePack.normal, (texture) => {
                targetMaterials.forEach(mat => {
                    if (mat.type === 'MeshStandardMaterial' || mat.type === 'MeshPhysicalMaterial') {
                        if (forceReplace || !mat.normalMap) {
                            mat.normalMap = texture;
                            mat.needsUpdate = true;
                        }
                    }
                });
            }, undefined, (error) => {
                console.error('Ошибка загрузки normal текстуры:', error);
            });
        }

        // roughness map or value
        if (typeof texturePack.roughness === 'string') {
            const textureLoader = new THREE.TextureLoader();
            textureLoader.load(texturePack.roughness, (texture) => {
                targetMaterials.forEach(mat => {
                    if (mat.type === 'MeshStandardMaterial' || mat.type === 'MeshPhysicalMaterial') {
                        if (forceReplace || !mat.roughnessMap) {
                            mat.roughnessMap = texture;
                            mat.needsUpdate = true;
                        }
                    }
                });
            }, undefined, (error) => {
                console.error('Ошибка загрузки roughness текстуры:', error);
            });
        } else if (typeof texturePack.roughness === 'number') {
            targetMaterials.forEach(mat => {
                if (mat.type === 'MeshStandardMaterial' || mat.type === 'MeshPhysicalMaterial') {
                    if (forceReplace || mat.roughnessMap == null) {
                        mat.roughness = texturePack.roughness;
                        mat.needsUpdate = true;
                    }
                }
            });
        }

        // metalness map or value (key metallic for map, metalness for value)
        if (typeof texturePack.metallic === 'string') {
            const textureLoader = new THREE.TextureLoader();
            textureLoader.load(texturePack.metallic, (texture) => {
                targetMaterials.forEach(mat => {
                    if (mat.type === 'MeshStandardMaterial' || mat.type === 'MeshPhysicalMaterial') {
                        if (forceReplace || !mat.metalnessMap) {
                            mat.metalnessMap = texture;
                            mat.needsUpdate = true;
                        }
                    }
                });
            }, undefined, (error) => {
                console.error('Ошибка загрузки metallic текстуры:', error);
            });
        }
        if (typeof texturePack.metalness === 'number') {
            targetMaterials.forEach(mat => {
                if (mat.type === 'MeshStandardMaterial' || mat.type === 'MeshPhysicalMaterial') {
                    if (forceReplace || mat.metalnessMap == null) {
                        mat.metalness = texturePack.metalness;
                        mat.needsUpdate = true;
                    }
                }
            });
        }

        // ambient occlusion map
        if (typeof texturePack.ao === 'string') {
            const textureLoader = new THREE.TextureLoader();
            textureLoader.load(texturePack.ao, (texture) => {
                targetMaterials.forEach(mat => {
                    if (mat.type === 'MeshStandardMaterial' || mat.type === 'MeshPhysicalMaterial') {
                        if (forceReplace || !mat.aoMap) {
                            mat.aoMap = texture;
                            mat.needsUpdate = true;
                        }
                    }
                });
            }, undefined, (error) => {
                console.error('Ошибка загрузки ao текстуры:', error);
            });
        }

        // specular only for Phong
        if (typeof texturePack.specular === 'string') {
            const textureLoader = new THREE.TextureLoader();
            textureLoader.load(texturePack.specular, (texture) => {
                targetMaterials.forEach(mat => {
                    if (mat.type === 'MeshPhongMaterial') {
                        mat.specularMap = texture;
                        mat.needsUpdate = true;
                    }
                });
            }, undefined, (error) => {
                console.error('Ошибка загрузки specular текстуры:', error);
            });
        }
    }

    function addExitMarker(exit) {
        // Удаляем старый маркер, если был
        if (window.exitMarkerMesh && sceneRef.current) {
            sceneRef.current.remove(window.exitMarkerMesh);
            window.exitMarkerMesh = null;
        }
        // Создаём маркер выхода
        const marker = new THREE.Mesh(
            new THREE.SphereGeometry(0.5),
            new THREE.MeshBasicMaterial({ color: 0xff0000, transparent: true, opacity: 0.5 })
        );
        marker.position.set(exit.x, exit.y, exit.z);
        marker.userData.isExitMarker = true;
        if (sceneRef.current) sceneRef.current.add(marker);
        window.exitMarkerMesh = marker;
    }

    const exitInterior = () => {
        console.log('exitInterior вызвана');

        // Телепортируем на координаты выхода из интерьера, если заданы; иначе возвращаем на сохранённую позицию
        if (playerRef.current) {
            const cx = currentExitRef.current;
            console.log('[EXIT] currentExit before teleport:', cx);
            if (cx && typeof cx.x === 'number') {
                playerRef.current.position.set(
                    cx.x,
                    typeof cx.y === 'number' ? cx.y : playerRef.current.position.y,
                    cx.z
                );
                playerRef.current.rotation.set(0, cx.rot || 0, 0);
                console.log('[EXIT] Teleported to exit coords');
                // Гарантируем выход из интерьера на сервере
                socketRef.current?.emit('interiorChange', { interiorId: null });
                // Включаем мир (закрытие могло скрыть город)
                try { toggleWorldVisibility(true); } catch (_) { }
            } else if (savedPositionRef.current) {
                console.log('[EXIT] No exit coords, using savedPositionRef');
                playerRef.current.position.copy(savedPositionRef.current);
            }
            // Сразу шлём позицию наружу
            socketRef.current?.emit('playerMovement', {
                x: playerRef.current.position.x,
                y: playerRef.current.position.y,
                z: playerRef.current.position.z
            });
        }

        // Удаляем маркер выхода, если был
        if (window.exitMarkerMesh && sceneRef.current) {
            sceneRef.current.remove(window.exitMarkerMesh);
            window.exitMarkerMesh = null;
        }

        // Удаляем группу интерьера, если она есть
        if (interiorGroupRef.current && sceneRef.current) {
            sceneRef.current.remove(interiorGroupRef.current);
            interiorGroupRef.current = null;
            console.log('Группа интерьера удалена');
        }

                    // Очищаем коллайдеры интерьера
                    interiorCollidersRef.current = [];
                    interiorColliderBoxesRef.current = [];
                    jsonCollidersRef.current = [];
                    
                    // Удаляем визуальные коллайдеры из сцены
                    visualCollidersRef.current.forEach(collider => {
                        if (sceneRef.current) {
                            sceneRef.current.remove(collider);
                            console.log('Удален визуальный коллайдер из сцены');
                        }
                    });
                    visualCollidersRef.current = [];
                    
                    console.log('Коллайдеры интерьера очищены');
                    
                    // Сбрасываем флаги отладки
                    window.colliderDebugShown = false;
                    window.collisionDebugShown = false;

        // Возвращаем третье лицо/камеру и актуализировать видимость объектов города
        switchToThirdPersonCamera?.();
        // Безопасный вызов без ReferenceError, даже если функция ещё не определена
        if (typeof updateCityObjectVisibility === 'function') {
            updateCityObjectVisibility();
        }
        // Повторно закрепляем телепорт на выход уже после очистки интерьера (на случай перезаписи позы)
        if (playerRef.current) {
            const cx2 = currentExitRef.current;
            console.log('[EXIT AFTER CLEANUP] currentExit:', cx2);
            if (cx2 && typeof cx2.x === 'number') {
                playerRef.current.position.set(
                    cx2.x,
                    typeof cx2.y === 'number' ? cx2.y : playerRef.current.position.y,
                    cx2.z
                );
                playerRef.current.rotation.set(0, cx2.rot || 0, 0);
                console.log('[EXIT AFTER CLEANUP] Position applied');
            }
            if (typeof lastPlayerPosition !== 'undefined') {
                try { lastPlayerPosition = playerRef.current.position.clone(); } catch (_) { }
            }
            socketRef.current?.emit('playerMovement', {
                x: playerRef.current.position.x,
                y: playerRef.current.position.y,
                z: playerRef.current.position.z
            });
        }
        // Полный сброс путевого движения и ввода
        if (typeof currentPath !== 'undefined') currentPath = [];
        if (typeof pathIndex !== 'undefined') pathIndex = 0;
        if (typeof destination !== 'undefined') destination = null;
        if (typeof blockedTime !== 'undefined') blockedTime = 0;
        if (typeof destinationMarker !== 'undefined' && destinationMarker) destinationMarker.visible = false;
        if (moveInputRef.current) {
            Object.keys(moveInputRef.current).forEach(k => { moveInputRef.current[k] = false; });
        }
        // Сообщаем серверу, что покинули интерьер
        socketRef.current?.emit('interiorChange', { interiorId: null });

        // Возвращаем курсор и отключаем pointer lock
        document.body.style.cursor = 'default';
        document.exitPointerLock();

        setIsInInterior(false);
        isInInteriorRef.current = false; // Важно! Сбрасываем ref для системы коллизий
        setCurrentExit(null);
        interiorExitPosRef.current = null;
    };


    // В useEffect для кликов по сцене:
    useEffect(() => {
        function onDocumentClick(event) {
            if (!rendererRef.current || !cameraRef.current) return;
            const rect = rendererRef.current.domElement.getBoundingClientRect();
            const mouse = new THREE.Vector2(
                ((event.clientX - rect.left) / rect.width) * 2 - 1,
                -((event.clientY - rect.top) / rect.height) * 2 + 1
            );
            const raycaster = new THREE.Raycaster();
            raycaster.setFromCamera(mouse, cameraRef.current);
            const intersects = raycaster.intersectObjects(sceneRef.current.children, true);
            for (let i = 0; i < intersects.length; i++) {
                const obj = intersects[i].object;
                if (obj.userData.isExitMarker) {
                    exitInterior();
                    break;
                }
            }
        }
        window.addEventListener('mousedown', onDocumentClick);
        return () => window.removeEventListener('mousedown', onDocumentClick);
    }, [currentExit]);

    /*const handleAnswerSelect = (answer) => {
        if (answer.end) {
            setShowDialog(false);
        } else if (answer.next !== undefined) {
            // Если следующий узел - форма
            if (typeof answer.next === 'string' && answer.next.startsWith('form_')) {
                const nextNode = currentDialog.dialog.find(node => node.id === answer.next);
                if (nextNode && nextNode.type === 'form') {
                    setCurrentForm(nextNode);
                    return;
                }
            }

            const nextIndex = currentDialog.dialog.findIndex(node => node.id === answer.next);
            if (nextIndex !== -1) {
                setDialogIndex(nextIndex);
            } else {
                console.error('Диалоговый узел не найден:', answer.next);
                setShowDialog(false);
            }
        } else {
            setShowDialog(false);
        }
    };
    // Добавьте эту функцию для обработки отправки формы
    const handleFormSubmit = (e) => {
        e.preventDefault();
        if (currentForm.next) {
            const nextIndex = currentDialog.dialog.findIndex(node => node.id === currentForm.next);
            if (nextIndex !== -1) {
                setDialogIndex(nextIndex);
                setCurrentForm(null);

                // Здесь можно отправить данные формы на сервер
                console.log('Отправленные данные:', formData);
                // Например: socketRef.current?.emit('dialogFormSubmit', formData);
            }
        }
    };

    // Добавьте эту функцию для обработки изменения полей формы
    const handleFormChange = (e) => {
        const { name, value } = e.target;
        setFormData(prev => ({
            ...prev,
            [name]: value
        }));
    };*/

    // Добавить функцию загрузки прогресса квестов:
    async function loadQuestsProgress() {
        const token = localStorage.getItem('token');
        try {
            console.log("Попытка загрузить");
            const res = await fetch('/api/quests/progress', {
                headers: { Authorization: `Bearer ${token}` }
            });
            if (res.ok) {
                const data = await res.json();
                setQuestsProgress(data);
            } else {
                console.error('Ошибка загрузки прогресса квестов');
            }
        } catch (err) {
            console.error('Ошибка сети:', err);
        }
    }


    const closeApp = () => {
        setAppsHidden(false);
        setActiveApp(null);
    };

    const bodyStyle = {
        margin: 0,
        fontFamily: "'Arial', sans-serif",
        background: '#f1f1f1',
        color: '#333',
        minHeight: '100vh'
    };

    const headerStyle = {
        backgroundColor: '#0047ab',
        color: 'white',
        padding: '1em',
        textAlign: 'center'
    };

    const mainStyle = {
        padding: '1em'
    };

    const listingStyle = {
        background: 'white',
        borderRadius: '10px',
        padding: '1em',
        marginBottom: '1em',
        boxShadow: '0 2px 8px rgba(0,0,0,0.1)'
    };

    const imageStyle = {
        width: '100%',
        borderRadius: '10px'
    };

    const listingTitleStyle = {
        marginTop: '0.5em',
        marginBottom: '0.3em'
    };

    const openIframe = (url) => {
        setIframeUrl(url);
        setIsIframeOpen(true);
    };

    const closeIframe = () => {
        setIsIframeOpen(false);
        setIframeUrl('');
    };

    async function loadTelegramContacts() {
        const token = localStorage.getItem('token');
        try {
            setTgError(null);
            const res = await fetch('/api/users/status', {
                headers: { Authorization: `Bearer ${token}` },
                credentials: 'include',
                cache: 'no-cache'
            });
            if (res.ok) {
                const data = await res.json();
                // Добавляем счетчик непрочитанных сообщений для каждого пользователя
                const dataWithUnread = data.map(user => ({
                    ...user,
                    unreadCount: 0
                }));
                setTelegramContacts(dataWithUnread);
            } else {
                const txt = await res.text().catch(() => '');
                console.error('Ошибка загрузки контактов Telegram', res.status, txt);
                setTgError('Не удалось загрузить контакты');
            }
        } catch (err) {
            console.error('Ошибка сети:', err);
            setTgError('Проблема сети');
        }
    }

    // Дополняем состояния
    const [newMessage, setNewMessage] = useState("");
    const [messageInterval, setMessageInterval] = useState(null);
    const [messages, setMessages] = useState([]);
    //const [readmes, setReadmes] = useState('false');
    const [userProfile, setUserProfile] = useState(null);

    // Функция показа уведомлений о сообщениях
    const showMessageNotification = async (senderId, messageText) => {
        try {
            // Сначала пытаемся найти отправителя в контактах
            let senderName = 'Неизвестный';
            const contact = telegramContacts.find(c => c.id === senderId);
            
            if (contact) {
                senderName = contact.firstName || contact.lastName || 'Неизвестный';
            } else {
                // Если не найден в контактах, загружаем информацию о пользователе
                try {
                    const userInfo = await loadUserInfo(senderId, localStorage.getItem('token'));
                    senderName = userInfo.firstName || userInfo.lastName || 'Неизвестный';
                } catch (error) {
                    console.error('Ошибка загрузки информации о пользователе:', error);
                    senderName = 'Неизвестный';
                }
            }
            
            // Создаем уведомление
            const notification = document.createElement('div');
            notification.style.cssText = `
                position: fixed;
                top: 20px;
                right: 20px;
                background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
                color: white;
                padding: 15px 20px;
                border-radius: 10px;
                box-shadow: 0 8px 32px rgba(0,0,0,0.3);
                z-index: 10000;
                font-family: 'Arial', sans-serif;
                font-size: 14px;
                max-width: 300px;
                transform: translateX(400px);
                transition: transform 0.3s ease-out;
                backdrop-filter: blur(10px);
                border: 1px solid rgba(255,255,255,0.2);
            `;
            
            notification.innerHTML = `
                <div style="font-weight: bold; margin-bottom: 5px; color: #ffd700;">${senderName}</div>
                <div style="opacity: 0.9;">${messageText.length > 50 ? messageText.substring(0, 50) + '...' : messageText}</div>
            `;
            
            document.body.appendChild(notification);
            
            // Анимация появления
            setTimeout(() => {
                notification.style.transform = 'translateX(0)';
            }, 100);
            
            // Автоматическое скрытие через 5 секунд
            setTimeout(() => {
                notification.style.transform = 'translateX(400px)';
                setTimeout(() => {
                    if (notification.parentNode) {
                        notification.parentNode.removeChild(notification);
                    }
                }, 300);
            }, 5000);
            
        } catch (error) {
            console.error('Ошибка показа уведомления:', error);
        }
    };

    // Функция для обновления счетчика непрочитанных сообщений
    const updateUnreadCount = async (senderId) => {
        try {
            const token = localStorage.getItem('token');
            if (!token) return;
            
            // Получаем количество непрочитанных сообщений
            const response = await fetch(`/api/messages-read/${senderId}`, {
                headers: {
                    'Authorization': `Bearer ${token}`,
                    'Content-Type': 'application/json'
                }
            });
            
            if (response.ok) {
                const data = await response.json();
                const unreadCount = data.unreadCount || 0;
                
                // Обновляем счетчик в контактах
                setTelegramContacts(prev => 
                    prev.map(contact => 
                        contact.id === senderId 
                            ? { ...contact, unreadCount: unreadCount }
                            : contact
                    )
                );
            }
        } catch (error) {
            console.error('Ошибка обновления счетчика непрочитанных сообщений:', error);
        }
    };

    // Функция показа подсказки об управлении камерой
    function showCameraControlsHint() {
        const hint = document.createElement('div');
        hint.style.cssText = `
            position: fixed;
            bottom: 20px;
            left: 20px;
            background: rgba(0, 0, 0, 0.8);
            color: white;
            padding: 15px 20px;
            border-radius: 10px;
            font-family: system-ui, Arial, sans-serif;
            font-size: 14px;
            z-index: 9999;
            max-width: 300px;
            animation: fadeIn 0.5s ease-in;
        `;
        
        hint.innerHTML = `
            <div style="font-weight: 600; margin-bottom: 8px;">🎮 Управление камерой:</div>
            <div style="margin-bottom: 5px;">• <strong>Ctrl + колесо</strong> = вертикальный поворот</div>
            <div style="margin-bottom: 5px;">• <strong>Shift + Ctrl + колесо</strong> = горизонтальный поворот</div>
            <div style="font-size: 12px; opacity: 0.8;">Подсказка исчезнет через 10 секунд</div>
        `;
        
        // Добавляем CSS анимацию
        const style = document.createElement('style');
        style.textContent = `
            @keyframes fadeIn {
                from { opacity: 0; transform: translateY(20px); }
                to { opacity: 1; transform: translateY(0); }
            }
        `;
        document.head.appendChild(style);
        
        document.body.appendChild(hint);
        
        // Автоматически скрываем через 10 секунд
        setTimeout(() => {
            hint.style.animation = 'fadeOut 0.5s ease-out';
            hint.style.opacity = '0';
            setTimeout(() => hint.remove(), 500);
        }, 10000);
        
        // Добавляем CSS для fadeOut
        if (!document.querySelector('#hint-styles')) {
            const fadeOutStyle = document.createElement('style');
            fadeOutStyle.id = 'hint-styles';
            fadeOutStyle.textContent = `
                @keyframes fadeOut {
                    from { opacity: 1; transform: translateY(0); }
                    to { opacity: 0; transform: translateY(20px); }
                }
            `;
            document.head.appendChild(fadeOutStyle);
        }
    }
    
    // Функция показа уведомления о перезагрузке сервера
    function showServerRestartNotification(message, restartIn) {
        const notification = document.createElement('div');
        notification.style.cssText = `
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: #dc2626;
            color: white;
            padding: 20px 30px;
            border-radius: 15px;
            box-shadow: 0 8px 32px rgba(0,0,0,0.5);
            z-index: 10001;
            max-width: 400px;
            font-family: system-ui, Arial, sans-serif;
            text-align: center;
            animation: serverRestartPulse 2s infinite;
        `;
        
        notification.innerHTML = `
            <div style="font-size: 18px; font-weight: 600; margin-bottom: 10px;">⚠️ Перезагрузка сервера</div>
            <div style="font-size: 14px; margin-bottom: 15px;">${message}</div>
            <div style="font-size: 12px; opacity: 0.8;">Перезагрузка через: <span id="restart-countdown">${Math.ceil(restartIn/1000)}</span> сек</div>
        `;
        
        // Добавляем CSS анимацию
        const style = document.createElement('style');
        style.textContent = `
            @keyframes serverRestartPulse {
                0%, 100% { transform: translate(-50%, -50%) scale(1); }
                50% { transform: translate(-50%, -50%) scale(1.05); }
            }
        `;
        document.head.appendChild(style);
        
        document.body.appendChild(notification);
        
        // Обновляем счетчик
        const countdownEl = notification.querySelector('#restart-countdown');
        const startTime = Date.now();
        const countdownInterval = setInterval(() => {
            const remaining = Math.max(0, restartIn - (Date.now() - startTime));
            if (countdownEl) {
                countdownEl.textContent = Math.ceil(remaining/1000);
            }
            if (remaining <= 0) {
                clearInterval(countdownInterval);
                notification.remove();
            }
        }, 100);
        
        // Автоматически скрываем через время перезагрузки
        setTimeout(() => {
            clearInterval(countdownInterval);
            notification.remove();
        }, restartIn);
    }

    // Функция загрузки сообщений
    async function loadMessages(contactId) {
        if (!contactId) return;

        const token = localStorage.getItem('token');
        async function markMessagesAsRead(contactId, token) {
            try {
                const res = await fetch('/api/messages-read-true-false', {
                    method: 'POST',
                    headers: {
                        'Authorization': `Bearer ${token}`,
                        'Content-Type': 'application/json'
                    },
                    body: JSON.stringify({
                        contactId: contactId
                    })
                });

                if (!res.ok) {
                    console.error('Ошибка отметки сообщений как прочитанных');
                }
            } catch (error) {
                console.error('Error marking as read:', error);
            }
        }

        try {
            // 1. Загружаем сообщения
            const res = await fetch(`/api/messages/${contactId}`, {
                headers: { Authorization: `Bearer ${token}` }
            });

            if (res.ok) {
                const data = await res.json();
                setMessages(data);
                console.log('Сообщения загружены');

                // 2. Отмечаем сообщения как прочитанные
                await markMessagesAsRead(contactId, token);

                // Прокручиваем чат вниз
                setTimeout(() => {
                    const chatContainer = document.getElementById('chatContainer');
                    if (chatContainer) {
                        chatContainer.scrollTop = chatContainer.scrollHeight;
                    }
                }, 1000);
            } else {
                console.error('Ошибка загрузки сообщений');
            }
        } catch (err) {
            console.error('Ошибка:', err);
        }
    }
   /* async function readmessages(contactId) {
        if (!contactId) return;

        const token = localStorage.getItem('token');
        try {
            const res = await fetch(`/api/messages-read/${contactId}`, {
                headers: { Authorization: `Bearer ${token}` }
            });

            if (res.ok) {
                const data = await res.text();
                if (data == "true") {
                    readmes('true'); // Есть непрочитанные
                } else {
                    readmes('false'); // Нет непрочитанных
                }
                console.log('Статус прочитанности проверен:', data);
            } else {
                console.error('Ошибка проверки сообщений');
                readmes('false');
            }
        } catch (err) {
            console.error('Ошибка:', err);
            readmes('false');
        }
    }*/

    // Функция отправки сообщения
    async function sendMessage() {
        if (!activeChat || !newMessage.trim()) return;

        const token = localStorage.getItem('token');
        try {
            const res = await fetch('/api/messages/send', {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                    Authorization: `Bearer ${token}`
                },
                body: JSON.stringify({
                    receiverId: activeChat.id,
                    message: newMessage
                })
            });

            if (res.ok) {
                setNewMessage("");
                console.log("Сообщение ушло");
                // После отправки сразу обновляем сообщения
                loadMessages(activeChat.id);
            } else {
                console.error('Ошибка отправки сообщения');
            }
        } catch (err) {
            console.error('Ошибка сети:', err);
        }
    }

    // Запускаем интервал при открытии чата
    useEffect(() => {
        if (activeChat) {
            // Первоначальная загрузка сообщений
            loadMessages(activeChat.id);
            //readmessages(activeChat.id)
            // Запускаем интервал для проверки новых сообщений
            const interval = setInterval(() => {
                loadMessages(activeChat.id);
                //readmessages(activeChat.id);
            }, 1000); // Проверка каждую секунду

            setMessageInterval(interval);

            // Очищаем интервал при закрытии чата
            return () => {
                if (interval) clearInterval(interval);
            };
        } else {
            // Останавливаем интервал, если чат закрыт
            if (messageInterval) {
                clearInterval(messageInterval);
                setMessageInterval(null);
            }
            setMessages([]);
        }
    }, [activeChat]);

    // Очищаем интервал при размонтировании компонента
    useEffect(() => {
        return () => {
            if (messageInterval) {
                clearInterval(messageInterval);
            }
        };
    }, []);

    // Загружаем профиль при монтировании
    useEffect(() => {
        const profile = JSON.parse(sessionStorage.getItem('user_profile') || {});
        setUserProfile(profile);
    }, []);

    //Телефон конец

    async function viewStats() {
        if (!selectedPlayer) return;
        const token = localStorage.getItem('token');
        const res = await fetch(`/api/players/${selectedPlayer.socketId}`, {
            headers: { Authorization: `Bearer ${token}` }
        });
        if (!res.ok) {
            console.error('Ошибка при загрузке статистики');
            return;
        }
        const data = await res.json();
        setPlayerStats(data);
    }

    async function toggleMicrophone() {
        try {
            if (!micEnabled) {
                localStream.current = await navigator.mediaDevices.getUserMedia({ audio: true });
                setMicEnabled(true);
                socketRef.current?.emit('voiceChatToggle', { enabled: true });

                const track = localStream.current.getAudioTracks()[0];
                Object.values(voiceConnections.current).forEach(conn => {
                    if (conn.audioSender && track) {
                        conn.audioSender.replaceTrack(track);
                    }
                });
            } else {
                if (localStream.current) {
                    localStream.current.getTracks().forEach(track => track.stop());
                }
                Object.values(voiceConnections.current).forEach(conn => {
                    if (conn.audioSender) {
                        conn.audioSender.replaceTrack(null);
                    }
                });
                localStream.current = null;
                setMicEnabled(false);
                socketRef.current?.emit('voiceChatToggle', { enabled: false });
            }
        } catch (err) {
            console.error('Ошибка доступа к микрофону:', err);
        }
    }

    async function onObjectClick(mesh) {
        const objectId = mesh.userData.id;        // <-- USER DATA ID из city_objects
        const token = localStorage.getItem('token');

        try {
            const resp = await fetch(
                `/api/city_objects/${objectId}/interior`,  // <-- обязательно "/interior"
                {
                    headers: { Authorization: `Bearer ${token}` },
                    credentials: 'include',
                    cache: 'no-cache'
                }
            );
            if (!resp.ok) {
                console.warn(`Для объекта ${objectId} не задан interior_id (status ${resp.status})`);
                return;
            }
            const { interiorId } = await resp.json();
            if (!interiorId) return;

            console.log(`Переходим в интерьер ${interiorId} из объекта ${objectId}`);
            movePlayerToInterior(interiorId);
        } catch (err) {
            console.error(`Ошибка при запросе interior_id для объекта ${objectId}:`, err);
        }
    }


    async function openOrganizationMenu(orgId) {
        const token = localStorage.getItem('token');
        try {
            const orgRes = await fetch(`/api/organizations/${orgId}`, {
                headers: { Authorization: `Bearer ${token}` }
            });

            let name = 'Организация';
            if (orgRes.ok) {
                const org = await orgRes.json();
                name = org.name;
            }

            const setRes = await fetch(`/api/organizations/${orgId}/settings`, {
                headers: { Authorization: `Bearer ${token}` }
            });
            const settings = setRes.ok ? await setRes.json() : { menu: [] };

            // сервер уже отдаёт menu как массив
            const menuArray = Array.isArray(settings.menu) ? settings.menu : [];

            setOrgMenu({ id: orgId, name, menu: menuArray });
            setSelectedHouse(null);
        } catch (e) {
            console.error('Не удалось загрузить меню организации', orgId, e);
            alert('Ошибка загрузки меню организации');
        }
    }


    function openOrganizationPanel(orgId) {
        setOrgPanelId(orgId);
        setOrgMenu(null);
        setSelectedHouse(null);
    }


    async function movePlayerToInterior(interiorId) {
        await enterInteriorMode(interiorId);
    }

    function switchToFirstPersonCamera() {
        console.log('switchToFirstPersonCamera вызвана');
        console.log('isInInteriorRef.current:', isInInteriorRef.current);

        if (fpCamRef.current) {
            cameraRef.current = fpCamRef.current;
            console.log('Камера переключена на fpCamRef');
        }
        if (playerRef.current) {
            // Скрываем полностью собственную модель в режиме FPV
            playerRef.current.visible = false;
            // На всякий случай также скрываем голову/шею (если модель будет вновь показана без выхода из режима)
            const hidden = [];
            playerRef.current.traverse((child) => {
                if (!child.isMesh) return;
                const name = (child.name || '').toLowerCase();
                if (name.includes('head') || name.includes('neck') || name.includes('helmet') || name.includes('hair')) {
                    child.visible = false;
                    hidden.push(child);
                }
            });
            fpHiddenNodesRef.current = hidden;
            console.log('Скрыты узлы для FPV:', hidden.map(n => n.name));
        }
        fpPitchRef.current = 0;

        // Настраиваем камеру от первого лица для интерьера
        if (isInInteriorRef.current) {
            console.log('Настраиваем камеру для интерьера');
            // Устанавливаем позицию камеры на уровне глаз игрока
            const headHeight = 1.6;
            fpCamRef.current.position.set(
                playerRef.current.position.x,
                playerRef.current.position.y + headHeight,
                playerRef.current.position.z
            );
            // Не большой сдвиг камеры вперёд, чтобы не упираться в скрытую голову
            const forward = new THREE.Vector3(0, 0, -0.08).applyEuler(new THREE.Euler(0, playerRef.current.rotation.y, 0));
            fpCamRef.current.position.add(forward);

            // Направляем камеру в том же направлении, что и игрок
            const direction = new THREE.Vector3(0, 0, -1);
            direction.applyEuler(new THREE.Euler(0, playerRef.current.rotation.y, 0));
            fpCamRef.current.lookAt(
                fpCamRef.current.position.clone().add(direction)
            );
            console.log('Камера настроена для интерьера');
        }
    }

    function switchToThirdPersonCamera() {
        console.log('switchToThirdPersonCamera вызвана');
        if (orthoCamRef.current) {
            cameraRef.current = orthoCamRef.current;
            console.log('Камера переключена на orthoCamRef');
        }
        if (playerRef.current) {
            playerRef.current.visible = true;
            // Вернуть видимость скрытых для FPV узлов
            if (Array.isArray(fpHiddenNodesRef.current)) {
                fpHiddenNodesRef.current.forEach(n => { n.visible = true; });
                fpHiddenNodesRef.current = [];
            }
            console.log('Игрок показан');
        }
        fpPitchRef.current = 0;
    }

    function startMove(dir) {
        console.log('startMove вызвана для направления:', dir);
        moveInputRef.current[dir] = true;
        console.log('moveInputRef.current после startMove:', moveInputRef.current);
    }

    function stopMove(dir) {
        console.log('stopMove вызвана для направления:', dir);
        moveInputRef.current[dir] = false;
        console.log('moveInputRef.current после stopMove:', moveInputRef.current);
    }


    // ─────────────────────────────────────────────────────
    // КЛИКИ ВНУТРИ ИНТЕРЬЕРА (интерактивные маркеры/NPC)
    // ─────────────────────────────────────────────────────
    useEffect(() => {
        const onClick = (e) => {
            console.log('[INTERIOR CLICK] handler start; isInInterior:', isInInteriorRef.current);
            if (!isInInteriorRef.current) return;
            const mount = mountRef.current;
            if (!mount || !cameraRef.current) return;

            // координаты мыши в NDC
            // Пытаемся получить координаты из элемента рендера (FP вид)
            const canvas = rendererRef.current && rendererRef.current.domElement;
            const rect = (canvas || mount).getBoundingClientRect();
            const mouse = new THREE.Vector2(
                ((e.clientX - rect.left) / rect.width) * 2 - 1,
                -((e.clientY - rect.top) / rect.height) * 2 + 1
            );
            const raycaster = new THREE.Raycaster();
            raycaster.setFromCamera(mouse, cameraRef.current);
            // Ищем пересечения по интерактивам (включая NPC)
            const objects = interiorInteractablesRef.current.filter(obj => obj?.isObject3D);
            // Добавим в список интерактивов саму группу интерьера, чтобы traverse детектил payload у вложенных узлов
            const extraTargets = [];
            if (interiorGroupRef.current) extraTargets.push(interiorGroupRef.current);
            const rayHits = raycaster.intersectObjects(objects.concat(extraTargets), true);
            console.log('[INTERIOR CLICK] rayHits count:', rayHits.length);
            const hits = rayHits.filter(h => {
                const obj = h.object;
                // учитываем payload на мешах и на родителях
                if (obj && obj.userData && (obj.userData.interactable || obj.userData.payload || obj.userData.isNpc)) return true;
                let p = obj;
                while (p && p.parent) {
                    p = p.parent;
                    if (p.userData && (p.userData.interactable || p.userData.payload || p.userData.isNpc)) return true;
                }
                return false;
            });
            console.log('[INTERIOR CLICK] interactable hits count:', hits.length);
            if (hits.length) {
                const top = hits[0].object;
                // поднимаем до узла, где лежит payload
                let node = top;
                while (node && !node.userData?.payload && node.parent) node = node.parent;
                let payload = (node && node.userData && node.userData.payload) || (top.userData.payload) || {};
                // Если у попавшего меша нет payload, но это часть NPC, поднимемся до isNpc
                if ((!payload || !payload.type) && node) {
                    let p = node;
                    while (p && !p.userData?.isNpc && p.parent) p = p.parent;
                    if (p && p.userData?.npcId) {
                        payload = { type: 'npc', id: p.userData.npcId };
                    }
                }
                console.log('[INTERIOR CLICK] payload:', payload);
                if (payload.type === 'marker') {
                    console.log('Нажат маркер:', payload);
                } else if (payload.type === 'npc') {
                    console.log('Нажат NPC:', payload);
                    try { if (payload.id) { loadDialog(payload.id); } } catch (_) { }
                } else {
                    console.log('Интерактив:', payload);
                }
                return;
            }

            // Если своих интерактивов не нашли, пробуем поймать NPC из общего массива npcMeshes
            try {
                const npcHit = raycaster.intersectObjects(npcMeshesRef.current || [], true);
                console.log('[INTERIOR CLICK] npcMeshes hits:', npcHit.length);
                if (npcHit.length) {
                    let root = npcHit[0].object;
                    while (root.parent && !root.userData?.isNpc) root = root.parent;
                    if (root.userData && root.userData.npcId) {
                        console.log('[INTERIOR CLICK] NPC root found:', root.userData.npcId);
                        if (root.userData.npcId === 'Computer') {
                            setShowMiniGame(true);
                            setPasswordCorrect(false);
                            setAudioUrl('/audio/firs.ogg');
                            addSeregaComment('Ну чё, хакер, разберёшься?');
                        } else {
                            loadDialog(root.userData.npcId);
                        }
                        return;
                    }
                }
            } catch (e) {
                console.warn('[INTERIOR CLICK] npcMeshes raycast failed:', e);
            }
        };

        const target = rendererRef.current ? rendererRef.current.domElement : window;
        target.addEventListener('click', onClick);
        target.addEventListener('pointerdown', onClick);
        return () => { target.removeEventListener('click', onClick); target.removeEventListener('pointerdown', onClick); };
    }, []);

    async function buyItem(key) {
        if (!orgMenu) return;
        const token = localStorage.getItem('token');
        const res = await fetch(`/api/organizations/${orgMenu.id}/purchase`, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
            body: JSON.stringify({ itemKey: key })
        });
        if (res.ok) {
            const data = await res.json();
            setSatiety(data.satiety);
            setThirst(data.thirst);
            setBalance(data.balance);
            const profile = JSON.parse(sessionStorage.getItem('user_profile') || '{}');
            profile.satiety = data.satiety;
            profile.thirst = data.thirst;
            profile.balance = data.balance;
            sessionStorage.setItem('user_profile', JSON.stringify(profile));
            socketRef.current.emit('economy:getInventory', { userId: profile.id });
        }
    }

    function handleItemAction(item) {
        const act = window.prompt('1 - использовать, 2 - выкинуть');
        const prof = JSON.parse(sessionStorage.getItem('user_profile') || '{}');
        if (act === '1') {
            if (item.name.toLowerCase().includes('вода')) {
                setThirst(t => Math.min(100, t + 20));
            } else {
                setSatiety(s => Math.min(100, s + 20));
            }
            socketRef.current.emit('economy:removeItem', { userId: prof.id, itemId: item.item_id, quantity: 1 });
        } else if (act === '2') {
            socketRef.current.emit('economy:removeItem', { userId: prof.id, itemId: item.item_id, quantity: 1 });
        }
        socketRef.current.emit('economy:getInventory', { userId: prof.id });
    }
    function toggleWorldVisibility(visible) {
        groundRef.current && (groundRef.current.visible = visible);
        cityMeshesRef.current.forEach(m => m.visible = visible);
    }

    function createInterior() {
        const group = new THREE.Group();
        const floorMat = new THREE.MeshStandardMaterial({ color: 0x808080 });
        const floor = new THREE.Mesh(new THREE.PlaneGeometry(20, 20), floorMat);
        floor.rotation.x = -Math.PI / 2;
        group.add(floor);

        const wallMat = new THREE.MeshStandardMaterial({ color: 0x999999 });
        const wallGeo = new THREE.PlaneGeometry(20, 10);
        const back = new THREE.Mesh(wallGeo, wallMat);
        back.position.set(0, 5, -10);
        group.add(back);
        const front = back.clone();
        front.position.set(0, 5, 10);
        front.rotation.y = Math.PI;
        group.add(front);
        const left = back.clone();
        left.position.set(-10, 5, 0);
        left.rotation.y = Math.PI / 2;
        group.add(left);
        const right = back.clone();
        right.position.set(10, 5, 0);
        right.rotation.y = -Math.PI / 2;
        group.add(right);

        const light = new THREE.PointLight(0xffffff, 1);
        light.position.set(0, 5, 0);
        group.add(light);

        return group;
    }

    function enterHouse(house) {
        if (!house || !sceneRef.current || !playerRef.current) return;
        const id = parseInt(house.id, 10);
        if (id === 9) {
            savedPositionRef.current.copy(playerRef.current.position);
            toggleWorldVisibility(false);
            interiorGroupRef.current = createInterior();
            sceneRef.current.add(interiorGroupRef.current);
            playerRef.current.position.set(0, 0, 0);
            playerRef.current.quaternion.identity();
            setSelectedHouse(null);
            switchToFirstPersonCamera();
            setIsInInterior(true);
        }
    }

    useEffect(() => {
        console.log('[DEBUG] useEffect вызван');
        const mount = mountRef.current;
        if (!mount) {
            console.log('[DEBUG] mountRef.current не определён!');
            return;
        }

        // ─────────────────────────────────────────────
        // Улучшенный загрузочный оверлей + LoadingManager
        // ─────────────────────────────────────────────
        let overlayEl = null, barEl = null, textEl = null;
        let isInitialLoad = true; // Флаг для определения начальной загрузки
        
        function createLoadingOverlay() {
            if (overlayEl) return;
            // Дополнительная проверка - не показываем overlay для очень маленьких загрузок
            if (!isInitialLoad && loadingManagerRef.current && loadingManagerRef.current.itemStart) {
                const currentTotal = loadingManagerRef.current.itemStart.length || 0;
                if (currentTotal <= 3) return; // Не показываем для загрузки 3 или меньше ресурсов
            }
            overlayEl = document.createElement('div');
            Object.assign(overlayEl.style, {
                position: 'fixed', inset: '0', zIndex: 2000,
                display: 'flex', flexDirection: 'column',
                alignItems: 'center', justifyContent: 'center',
                background: 'linear-gradient(135deg,#0f172a,#1e293b)',
                color: '#fff', fontFamily: 'system-ui, Arial, sans-serif'
            });
            textEl = document.createElement('div');
            Object.assign(textEl.style, {
                fontSize: '24px', fontWeight: 700, opacity: 0.9, marginBottom: '16px'
            });
            textEl.textContent = 'Загрузка ресурсов...';
            overlayEl.appendChild(textEl);
            const barWrap = document.createElement('div');
            Object.assign(barWrap.style, {
                width: '320px', height: '10px',
                background: 'rgba(255,255,255,0.15)',
                borderRadius: '999px', overflow: 'hidden',
                boxShadow: '0 6px 20px rgba(0,0,0,0.35)'
            });
            barEl = document.createElement('div');
            Object.assign(barEl.style, {
                width: '0%', height: '100%',
                transition: 'width .15s ease',
                background: 'linear-gradient(90deg,#22d3ee,#38bdf8,#60a5fa)'
            });
            barWrap.appendChild(barEl);
            overlayEl.appendChild(barWrap);
            const pct = document.createElement('div');
            Object.assign(pct.style, { marginTop: '12px', fontSize: '14px', opacity: 0.8 });
            pct.id = 'loadingPct';
            pct.textContent = '0%';
            overlayEl.appendChild(pct);
            document.body.appendChild(overlayEl);
        }
        
        function updateLoadingOverlay(percent, text) {
            if (!overlayEl) return;
            const p = Math.max(0, Math.min(100, Math.round(percent || 0)));
            if (barEl) barEl.style.width = p + '%';
            const pct = overlayEl.querySelector('#loadingPct');
            if (pct) pct.textContent = p + '%';
            if (text && textEl) textEl.textContent = text;
        }
        
        function removeLoadingOverlay() {
            if (!overlayEl) return;
            
            // Очищаем все таймеры overlay
            if (overlayTimeoutRef.current) {
                clearTimeout(overlayTimeoutRef.current);
                overlayTimeoutRef.current = null;
            }
            
            overlayEl.style.transition = 'opacity .2s ease';
            overlayEl.style.opacity = '0';
            setTimeout(() => {
                overlayEl && overlayEl.remove();
                overlayEl = barEl = textEl = null;
            }, 220);
        }
        
        // Общий менеджер загрузки (для GLTF/Texture и т.п.)
        const loadingManager = new THREE.LoadingManager();
        loadingManagerRef.current = loadingManager;
        
        loadingManager.onStart = (_url, loaded, total) => {
            console.log(`LoadingManager.onStart: isInitialLoad=${isInitialLoad}, total=${total}, url=${_url}`);
            // Показываем оверлей только при начальной загрузке или при загрузке большого количества ресурсов
            if (isInitialLoad || total > 10) {
                console.log('Показываем overlay для загрузки');
                createLoadingOverlay();
                updateLoadingOverlay(total ? (loaded / total) * 100 : 5, 'Загрузка ресурсов...');
            } else {
                console.log('Не показываем overlay - небольшая загрузка');
            }
        };
        
        loadingManager.onProgress = (_url, loaded, total) => {
            if (overlayEl && (isInitialLoad || total > 10)) {
                updateLoadingOverlay(total ? (loaded / total) * 100 : 50);
            }
        };
        
        loadingManager.onLoad = () => {
            console.log(`LoadingManager.onLoad: isInitialLoad=${isInitialLoad}, overlayEl=${!!overlayEl}`);
            if (overlayEl) {
                // Показываем "Инициализация сцены" только для начальной загрузки
                if (isInitialLoad) {
                    console.log('Показываем "Инициализация сцены" для начальной загрузки');
                    updateLoadingOverlay(100, 'Инициализация сцены...');
                    setTimeout(removeLoadingOverlay, 150);
                } else {
                    // Для небольших загрузок просто скрываем overlay
                    console.log('Скрываем overlay для небольшой загрузки');
                    removeLoadingOverlay();
                }
            }
            isInitialLoad = false; // После первой загрузки сбрасываем флаг
            
                                // Дополнительная защита - принудительно скрываем overlay через 3 секунды
            if (overlayEl) {
                overlayTimeoutRef.current = setTimeout(() => {
                    if (overlayEl) {
                        console.log('Принудительно скрываем overlay по таймауту');
                        removeLoadingOverlay();
                    }
                }, 3000);
            }
            
            // Глобальная защита - принудительно скрываем overlay через 5 секунд после начала игры
            overlayTimeoutRef.current = setTimeout(() => {
                if (overlayEl && !isInitialLoad) {
                    console.log('Глобальная защита: принудительно скрываем overlay');
                    removeLoadingOverlay();
                }
            }, 5000);
        };


        console.log('–– useEffect начало');

        const baseOffset = new THREE.Vector3(-200, 150, -200);
        const planarDist = Math.hypot(baseOffset.x, baseOffset.z);
        const radius = Math.hypot(planarDist, baseOffset.y);
        let baseAzimuth = Math.atan2(baseOffset.z, baseOffset.x);
        const baseAzimuth0 = baseAzimuth;
        let horizontalYaw = 0; // относительный поворот (±90°) от исходного
        const basePolar = Math.atan2(baseOffset.y, planarDist);

        let cameraPitchOffset = 0;
        const maxPitch = THREE.MathUtils.degToRad(10);

        let zoom = 10;
        const minZoom = zoom * 0.1;
        const maxZoom = zoom * 3.5;

        let orthoCamera, fpCamera;
        let player, mixer;
        let idleAction, walkAction, currentAction;
        let remotePlayers = remotePlayersRef.current;
        let obstacles = [];
        let destination = null;
        let blockedTime = 0;
        const moveSpeed = 2.5;
        const WALK_ANIM_SPEED_MPS = 2;
        let clock;
        try {
            clock = new THREE.Clock();
        } catch (error) {
            console.error('Ошибка создания THREE.Clock:', error);
            return;
        }
        const keys = {};
        let npcMeshes = [];
        const territorySize = 500;
        const boundary = territorySize / 2;
        const gridSize = 300;
        const nodeSize = territorySize / gridSize;

        let pathfinderGrid;
        let currentPath = [];
        let pathIndex = 0;
        let groundPlane;
        let destinationMarker;
        let customMaterial;

        const token = localStorage.getItem('token');
        // Подключаемся к локальному серверу
        const serverUrl = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'
            ? 'http://localhost:4000'
            : window.location.origin;

        socketRef.current = io(serverUrl, {
            transports: ['websocket', 'polling'],
            auth: { token },
            timeout: 20000 // Увеличиваем timeout до 20 секунд
        });
        const socket = socketRef.current;

        async function loadCustomCollidersForCity(cityIdParam) {
            try {
                const cityIdNum = Number(cityIdParam) || 1;
                console.log('🔍 loadCustomCollidersForCity для города:', cityIdNum);
                
                // Сначала пробуем новый API с базой данных
                let res = await fetch(`/api/colliders/city/${cityIdNum}`, { 
                    cache: 'no-store', 
                    headers: { Authorization: `Bearer ${token}` } 
                });
                
                // Если новый API недоступен (500 ошибка), пробуем старый JSON API
                if (!res.ok && res.status === 500) {
                    console.log('🔄 Новый API недоступен в loadCustomCollidersForCity, пробуем старый JSON API...');
                    const query = cityIdNum ? `?cityId=${encodeURIComponent(cityIdNum)}` : '';
                    res = await fetch(`/api/colliders${query}`, { cache: 'no-store', headers: { Authorization: `Bearer ${token}` } });
                }
                
                if (!res.ok) {
                    console.warn('Не удалось загрузить кастомные коллайдеры для города:', cityIdNum);
                    return;
                }
                
                const data = await res.json();
                console.log('🔍 Загруженные данные кастомных коллайдеров:', data);
                
                // Обрабатываем данные в зависимости от источника
                let list;
                if (data.colliders) {
                    // Данные из базы данных
                    list = data.colliders;
                    console.log('📊 Загружены кастомные коллайдеры из базы данных:', list.length, 'объектов');
                } else if (Array.isArray(data)) {
                    // Данные из JSON файла
                    list = data;
                    console.log('📄 Загружены кастомные коллайдеры из JSON файла:', list.length, 'объектов');
                } else {
                    console.warn('Неизвестный формат данных кастомных коллайдеров:', data);
                    return;
                }
                // Удаляем старые кастомные коллайдеры
                obstacles = obstacles.filter(o => {
                    const keep = !o?.mesh?.userData?.isCustomCollider;
                    if (!keep && o.mesh) {
                        scene.remove(o.mesh);
                    }
                    return keep;
                });
                // Добавляем новые
                list.forEach(c => {
                    let geometry;
                    if (c.type === 'circle') geometry = new THREE.CylinderGeometry(1.5, 1.5, 2, 24);
                    else if (c.type === 'capsule') geometry = new THREE.CapsuleGeometry(1, 2, 4, 12);
                    else geometry = new THREE.BoxGeometry(2, 2, 2);
                    
                    // Используем прозрачность из базы данных
                    const opacity = c.opacity !== undefined ? c.opacity : 0.001;
                    const material = new THREE.MeshBasicMaterial({ 
                        color: 0x000000, 
                        transparent: true, 
                        opacity: opacity, 
                        depthWrite: false 
                    });
                    
                    // Если прозрачность 0, делаем материал невидимым
                    if (opacity === 0) {
                        material.visible = false;
                        material.alphaTest = 0;
                    } else {
                        material.visible = true;
                        material.alphaTest = 0.1;
                    }
                    
                    const mesh = new THREE.Mesh(geometry, material);
                    const p = c.position || {}; const r = c.rotation || {}; const s = c.scale || {};
                    mesh.position.set(p.x || 0, p.y || 0, p.z || 0);
                    mesh.rotation.set(r.x || 0, r.y || 0, r.z || 0);
                    mesh.scale.set(s.x || 1, s.y || 1, s.z || 1);
                    mesh.userData.isCustomCollider = true;
                    scene.add(mesh);
                    obstacles.push({ mesh });
                });
                buildPathfindingGrid?.();
            } catch (e) {
                console.warn('Не удалось загрузить кастомные коллайдеры', e);
            }
        }

        console.log('socket инстанс:', socket);
        console.log('Подключение к серверу:', serverUrl);

        socket.on('connect', () => {
            console.log('✔ Socket connected, id=', socket.id);
            console.log('Подключение успешно установлено');
            setConnectionLost(false);
            // Подписка на ping/pong менеджера Socket.IO для измерения задержки
            try {
                const mgr = socket.io;
                if (mgr && typeof mgr.on === 'function') {
                    mgr.off?.('pong');
                    mgr.on('pong', (latency) => {
                        if (typeof latency === 'number' && isFinite(latency)) {
                            setLatencyMs(Math.round(latency));
                        }
                    });
                }
            } catch (e) { /* noop */ }
        });

        // Загрузка пользовательских коллайдеров при старте (по текущему городу)
        try {
            const profile = JSON.parse(sessionStorage.getItem('user_profile') || '{}');
            const initialCityId = profile.last_city_id || 1;
            loadCustomCollidersForCity(initialCityId);
        } catch {}

        socket.on('connect_error', err => {
            console.error('Socket connect_error:', err);
            console.error('Ошибка подключения к серверу:', serverUrl);
            console.error('Проверьте, что сервер запущен на порту 4000');
            setConnectionLost(true);
        });

        socket.on('disconnect', reason => {
            console.warn('Socket disconnected:', reason);
            console.warn('Соединение разорвано, причина:', reason);
            setConnectionLost(true);
        });

        // Небольшой таймер для обновления latency при отсутствии событий
        const pingTimer = setInterval(() => {
            const s = socketRef.current;
            if (!s || s.disconnected) return;
            // менеджер сам шлёт ping с интервалом, мы лишь не даём UI "застывать"
            // если давно не было pong — считаем соединение деградировало
            setLatencyMs((prev) => (prev == null ? prev : Math.min(prev + 1, 999)));
        }, 1000);
        const profile = JSON.parse(sessionStorage.getItem('user_profile') || '{}');
        if (profile?.id) {
            socket.emit('economy:getBalance', { userId: profile.id });
        }
        const balanceInterval = setInterval(() => {
            const p = JSON.parse(sessionStorage.getItem('user_profile') || '{}');
            if (p?.id) socket.emit('economy:getBalance', { userId: p.id });
        }, 3000);
        
        // Периодическое обновление статуса пользователей для Telegram
        const statusInterval = setInterval(() => {
            if (activeApp === "Telegram" && telegramContacts.length > 0) {
                loadTelegramContacts();
                // Обновляем счетчики непрочитанных сообщений для всех контактов
                telegramContacts.forEach(contact => {
                    if (contact.id !== profile.id) {
                        updateUnreadCount(contact.id);
                    }
                });
            }
        }, 30000); // Обновляем каждые 30 секунд
        socket.on('economy:balanceChanged', ({ userId, newBalance }) => {
            if (userId === profile.id) {
                setBalance(newBalance);
                const upd = { ...(profile || {}), balance: newBalance };
                sessionStorage.setItem('user_profile', JSON.stringify(upd));
            }
        });
        socket.emit('economy:getInventory', { userId: profile.id });
        socket.on('economy:inventory', setInventory);
        socket.on('gameTime:update', ({ time }) => setGameTime(time));
        
            // Обработчик изменения статуса пользователей для Telegram
    socket.on('userStatusChanged', ({ userId, isOnline }) => {
        console.log('Статус пользователя изменился:', { userId, isOnline });
        setTelegramContacts(prev => prev.map(user => 
            user.id === userId ? { ...user, isOnline } : user
        ));
    });
    
            // Обработчик новых сообщений для уведомлений
        socket.on('newMessage', ({ id, text, senderId, timestamp, isRead }) => {
            console.log('Новое сообщение:', { id, text, senderId, timestamp, isRead });
            
            // Показываем уведомление только если Telegram не открыт
            if (activeApp !== "Telegram") {
                showMessageNotification(senderId, text);
            }
            
            // Обновляем счетчик непрочитанных сообщений
            updateUnreadCount(senderId);
            
            // Обновляем список сообщений если открыт чат с этим пользователем
            if (activeChat && activeChat.id === senderId) {
                loadMessages(senderId);
            }
        });
        
        // Обработчик перезагрузки сервера
        socket.on('serverRestart', ({ message, restartIn }) => {
            console.log('Сервер будет перезагружен:', { message, restartIn });
            showServerRestartNotification(message, restartIn);
        });
        // Лоадеры, учитывающиеся в прогрессе через loadingManagerRef
        const gltfLoader = new GLTFLoader(loadingManagerRef.current || undefined);
        const animLoader = new GLTFLoader(loadingManagerRef.current || undefined);

        async function loadPlayerModel(avatarUrl) {
            return new Promise((resolve, reject) => {
                console.log('GLTFLoader загружает:', avatarUrl);

                // Проверяем, что URL начинается с правильного пути
                if (!avatarUrl.startsWith('/') && !avatarUrl.startsWith('http')) {
                    console.error('Неправильный формат URL:', avatarUrl);
                    reject(new Error('Неправильный формат URL'));
                    return;
                }

                gltfLoader.load(
                    avatarUrl,
                    (gltf) => {
                        console.log('GLTF загружен успешно:', gltf);
                        if (!gltf.scene) {
                            console.error('GLTF.scene отсутствует в загруженном файле');
                            return reject('GLTF.scene отсутствует');
                        }
                        resolve(gltf);
                    },
                    (progress) => {
                        console.log('Прогресс загрузки:', progress);
                    },
                    (err) => {
                        console.error('Ошибка загрузки GLTF:', err);
                        reject(err);
                    }
                );
            });
        }

        async function addOtherPlayer(id, x, z, avatarURL, genderRemote = 'male', firstName = '', lastName = '', y = 0) {
            if (remotePlayers[id]) {
                // Уже есть — не пересоздаём
                return;
            }
            let model;
            try {
                if (!avatarURL) throw new Error('no avatarURL');
                const gltf = await loadPlayerModel(avatarURL);
                model = gltf.scene;

                // Проверяем и исправляем материалы модели
                model.traverse((child) => {
                    if (child.isMesh && child.material) {
                        if (Array.isArray(child.material)) {
                            child.material.forEach(mat => {
                                if (!mat || !mat.isMaterial) {
                                    console.warn(`Неправильный материал в аватаре ${id}, заменяем на стандартный`);
                                    if (THREE.MeshStandardMaterial) {
                                        child.material = new THREE.MeshStandardMaterial({ color: 0x808080 });
                                    } else {
                                        console.error('THREE.MeshStandardMaterial не доступен для замены материала');
                                    }
                                }
                            });
                        } else if (!child.material.isMaterial) {
                            console.warn(`Неправильный материал в аватаре ${id}, заменяем на стандартный`);
                            child.material = new THREE.MeshStandardMaterial({ color: 0x808080 });
                        }
                    }
                });
            } catch (e) {
                console.warn(`Не удалось загрузить аватар ${id}, рисуем сферу`, e);
                model = new THREE.Mesh(
                    new THREE.SphereGeometry(1),
                    new THREE.MeshBasicMaterial({ color: 0x888888 })
                );
            }
            model.scale.set(1, 1, 1);
            model.position.set(x, y || 0, z);
            scene.add(model);

            const fullname = `${firstName} ${lastName}`.trim();
            if (fullname) {
                const label = createPlayerLabel(fullname);
                label.position.set(0, 2.2, 0);
                model.add(label);
            }

            // Add voice chat icon (initially hidden)
            const voiceIcon = createVoiceIcon();
            voiceIcon.position.set(0, 2.7, 0);
            voiceIcon.visible = false;
            model.add(voiceIcon);
            voiceIcons.current[id] = voiceIcon;

            const mixerRemote = new THREE.AnimationMixer(model);

            const isFemale = genderRemote === 'female';
            const animGender = isFemale ? 'feminine' : 'masculine';

            const idleFile = isFemale ? 'F_Standing_Idle_001.glb' : 'M_Standing_Idle_001.glb';
            const walkFile = isFemale ? 'F_Walk_002.glb' : 'M_Walk_001.glb';

            const idlePath = `/animations/${animGender}/glb/idle/${idleFile}`;
            const walkPath = `/animations/${animGender}/glb/locomotion/${walkFile}`;

            const [idleGltf, walkGltf] = await Promise.all([
                animLoader.loadAsync(idlePath),
                animLoader.loadAsync(walkPath)
            ]);

            idleGltf.animations.forEach(stripPositionTracks);
            walkGltf.animations.forEach(stripPositionTracks);

            const remoteIdleAction = mixerRemote.clipAction(idleGltf.animations[0], model);
            const remoteWalkAction = mixerRemote.clipAction(walkGltf.animations[0], model);

            remoteIdleAction.play();

            remotePlayers[id] = {
                model,
                mixer: mixerRemote,
                idleAction: remoteIdleAction,
                walkAction: remoteWalkAction,
                currentAction: remoteIdleAction,
                firstName,
                lastName,
                gender: genderRemote,
                avatarURL,
                _idleTimeout: null
            };

            // Синхронизируем анимацию ходьбы с скоростью перемещения
            remotePlayers[id].walkAction.setEffectiveTimeScale(moveSpeed / WALK_ANIM_SPEED_MPS);
        }

        function createVoiceIcon() {
            const canvas = document.createElement('canvas');
            canvas.width = 64;
            canvas.height = 64;
            const ctx = canvas.getContext('2d');

            ctx.fillStyle = '#00ff00';
            ctx.beginPath();
            ctx.arc(32, 32, 20, 0, 2 * Math.PI);
            ctx.fill();
            ctx.fillStyle = '#000';
            ctx.font = '24px Arial';
            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';
            ctx.fillText('🎤', 32, 32);

            const texture = new THREE.CanvasTexture(canvas);

            texture.generateMipmaps = false;
            texture.minFilter = THREE.LinearFilter;
            texture.magFilter = THREE.LinearFilter;
            texture.anisotropy = 1;

            texture.needsUpdate = true;

            const spriteMaterial = new THREE.SpriteMaterial({
                map: texture,
                transparent: true,
                depthTest: false,     // рисуем поверх геометрии
                depthWrite: false,
                toneMapped: false,    // чтобы белый не «теплился» тон-меппингом
                sizeAttenuation: false
            });
            const sprite = new THREE.Sprite(spriteMaterial);
            sprite.scale.set(0.5, 0.5, 1);

            // ↓↓↓ добавь это ↓↓↓
            sprite.raycast = () => { };
            sprite.userData.isUiSprite = true;

            return sprite;
        }

        async function initiateVoiceChat(peerId) {
            if (voiceConnections.current[peerId]) return;

            const peerConnection = new RTCPeerConnection({
                iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
            });


            voiceConnections.current[peerId] = {
                peerConnection,
                audioElement: document.createElement('audio'),
                pendingCandidates: [],
                audioSender: null
            };

            voiceConnections.current[peerId].audioElement.autoplay = true;
            document.body.appendChild(voiceConnections.current[peerId].audioElement);

            peerConnection.ontrack = (event) => {
                voiceConnections.current[peerId].audioElement.srcObject = event.streams[0];
            };

            // В функции initiateVoiceChat, перед peerConnection.onicecandidate, добавьте (18.05.2025):
            voiceConnections.current[peerId].pendingCandidates = [];

            peerConnection.onicecandidate = (event) => {
                if (event.candidate) {
                    socket.emit('voiceChatIceCandidate', {
                        to: peerId,
                        candidate: event.candidate
                    });
                }
            };

            peerConnection.onconnectionstatechange = () => {
                if (peerConnection.connectionState === 'disconnected' || peerConnection.connectionState === 'failed') {
                    cleanupVoiceConnection(peerId);
                }
            };

            try {
                const offer = await peerConnection.createOffer();
                await peerConnection.setLocalDescription(offer);
                socket.emit('voiceChatOffer', { to: peerId, offer });
            } catch (err) {
                console.error('Ошибка создания WebRTC предложения:', err);
            }
        }

        function cleanupVoiceConnection(peerId) {
            if (voiceConnections.current[peerId]) {
                const conn = voiceConnections.current[peerId];
                try {
                    conn.audioSender?.replaceTrack(null);
                } catch { }
                conn.peerConnection.close();
                conn.audioElement.remove();
                delete voiceConnections.current[peerId];
            }
        }

        socket.on('voiceChatNearby', ({ playerId }) => {
            if (remotePlayers[playerId] && !voiceConnections.current[playerId]) {
                if (socket.id < playerId) {
                    initiateVoiceChat(playerId);
                }
            }
        });

        socket.on('voiceChatOffer', async ({ from, offer }) => {
            if (!voiceConnections.current[from]) {
                const peerConnection = new RTCPeerConnection({
                    iceServers: [{ urls: 'stun:stun.l.google.com:19302' }]
                });

                voiceConnections.current[from] = {
                    peerConnection,
                    audioElement: document.createElement('audio'),
                    pendingCandidates: [],
                    audioSender: null
                };

                voiceConnections.current[from].audioElement.autoplay = true;
                document.body.appendChild(voiceConnections.current[from].audioElement);

                peerConnection.ontrack = (event) => {
                    voiceConnections.current[from].audioElement.srcObject = event.streams[0];
                };

                peerConnection.onicecandidate = (event) => {
                    if (event.candidate) {
                        socket.emit('voiceChatIceCandidate', {
                            to: from,
                            candidate: event.candidate
                        });
                    }
                };

                peerConnection.onconnectionstatechange = () => {
                    if (peerConnection.connectionState === 'disconnected' || peerConnection.connectionState === 'failed') {
                        cleanupVoiceConnection(from);
                    }
                };

                try {
                    await peerConnection.setRemoteDescription(new RTCSessionDescription(offer));
                    const remoteTransceiver = peerConnection.getTransceivers().find(
                        t => t.receiver && t.receiver.track && t.receiver.track.kind === 'audio'
                    );
                    if (remoteTransceiver) {
                        remoteTransceiver.direction = 'sendrecv';
                        voiceConnections.current[from].audioSender = remoteTransceiver.sender;
                        if (localStream.current) {
                            const track = localStream.current.getAudioTracks()[0];
                            if (track) {
                                await remoteTransceiver.sender.replaceTrack(track);
                            }
                        }
                    }
                    // В обработчике voiceChatOffer, после await peerConnection.setRemoteDescription, добавьте (18.05.2025):
                    const pendingCandidates = voiceConnections.current[from].pendingCandidates || [];
                    for (const candidate of pendingCandidates) {
                        try {
                            await voiceConnections.current[from].peerConnection.addIceCandidate(
                                new RTCIceCandidate(candidate)
                            );
                        } catch (err) {
                            console.error('Ошибка добавления буферизованного ICE кандидата:', err);
                        }
                    }
                    voiceConnections.current[from].pendingCandidates = [];
                    const answer = await peerConnection.createAnswer();
                    await peerConnection.setLocalDescription(answer);
                    socket.emit('voiceChatAnswer', { to: from, answer });
                } catch (err) {
                    console.error('Ошибка обработки WebRTC предложения:', err);
                }
            }
        });

        socket.on('voiceChatAnswer', async ({ from, answer }) => {
            if (voiceConnections.current[from]) {
                try {
                    await voiceConnections.current[from].peerConnection.setRemoteDescription(
                        new RTCSessionDescription(answer)
                    );
                    const pending = voiceConnections.current[from].pendingCandidates || [];
                    for (const candidate of pending) {
                        try {
                            await voiceConnections.current[from].peerConnection.addIceCandidate(
                                new RTCIceCandidate(candidate)
                            );
                        } catch (err) {
                            console.error('Ошибка добавления буферизованного ICE кандидата:', err);
                        }
                    }
                    voiceConnections.current[from].pendingCandidates = [];
                } catch (err) {
                    console.error('Ошибка установки WebRTC ответа:', err);
                }
            }
        });


        // Замените обработчик voiceChatIceCandidate на (18.05.2025):
        socket.on('voiceChatIceCandidate', async ({ from, candidate }) => {
            if (!voiceConnections.current[from]) {
                console.warn('Соединение для', from, 'не существует, пропущен ICE кандидат');
                return;
            }

            const peerConnection = voiceConnections.current[from].peerConnection;

            if (peerConnection.remoteDescription) {
                try {
                    await peerConnection.addIceCandidate(new RTCIceCandidate(candidate));
                } catch (err) {
                    console.error('Ошибка добавления ICE кандидата:', err);
                }
            } else {
                console.log('Буферизация ICE кандидата для', from);
                voiceConnections.current[from].pendingCandidates.push(candidate);
            }
        });

        socket.on('voiceChatStatus', ({ playerId, enabled }) => {
            if (voiceIcons.current[playerId]) {
                voiceIcons.current[playerId].visible = enabled;
            }
        });

        socket.on('connect', () => console.log('Socket connected, id=', socket.id));
        socket.on('currentPlayers', (players) => {
            console.log('currentPlayers', players);
            // Получаем профиль (только для ФИО/аватара)
            const myProfile = JSON.parse(sessionStorage.getItem('user_profile') || '{}');
            // Добавляем/обновляем игроков из пришедшего списка
            Object.keys(players).forEach(id => {
                if (id === socket.id) return;
                const { x, y, z, avatarURL, gender, firstName, lastName } = players[id];
                if (!remotePlayers[id]) {
                    addOtherPlayer(id, x, z, avatarURL, gender, firstName, lastName, y);
                }
            });
            // Удаляем тех, кого нет в актуальном списке (после входа/выхода из интерьера и т.п.)
            const validIds = new Set(Object.keys(players));
            Object.keys(remotePlayers).forEach((rid) => {
                if (rid === socket.id) return;
                if (!validIds.has(rid)) {
                    if (remotePlayers[rid] && remotePlayers[rid].model) {
                        scene.remove(remotePlayers[rid].model);
                    }
                    delete remotePlayers[rid];
                    if (voiceIcons.current[rid]) delete voiceIcons.current[rid];
                    cleanupVoiceConnection(rid);
                }
            });

            // После получения списка игроков, отправляем newPlayer о себе ТОЛЬКО когда мы не в интерьере
            // Отправляем себя только если это первый коннект и ещё не отправляли
            if (!window.__newPlayerSentOnce) {
                const profile = myProfile;
                socket.emit('newPlayer', {
                    x: player?.position?.x || 0,
                    y: player?.position?.y || 0,
                    z: player?.position?.z || 0,
                    avatarURL: avatarUrl,
                    firstName: profile.firstName,
                    lastName: profile.lastName,
                    userId: profile.id
                });
                window.__newPlayerSentOnce = true;
            }
        });

        socket.on('chatMessage', ({ playerId, name, message, position }) => {
            console.log('← chatMessage получил:', message);
            if (!player || !cameraRef.current || !scene || !obstacles) return;

            const origin = cameraRef.current.position.clone();
            const targetPos = new THREE.Vector3(position.x, player.position.y, position.z);
            const direction = new THREE.Vector3().subVectors(targetPos, origin).normalize();

            const raycaster = new THREE.Raycaster(origin, direction);
            raycaster.camera = cameraRef.current; // ← ВАЖНО для спрайтов

            const obstacleMeshes = obstacles.map(o => o.mesh).filter(Boolean); // ← фильтр от null
            const intersects = raycaster.intersectObjects(obstacleMeshes, true);

            const distanceToTarget = origin.distanceTo(targetPos);

            if (intersects.length > 0 && intersects[0].distance < distanceToTarget) {
                console.log(`🔕 ${name} за препятствием — сообщение скрыто`);
                return;
            }

            const div = document.getElementById('chatMessages');
            if (!div) return;

            const p = document.createElement('p');
            p.textContent = `${name || 'Игрок'}: ${message}`;
            p.style.color = 'white';
            p.style.padding = '5px';
            p.style.margin = '2px 0';
            p.style.fontSize = '14px';
            p.style.borderRadius = '10px';
            div.appendChild(p);
            div.scrollTop = div.scrollHeight;
        });

        socket.on('playerMoved', (data) => {
            const remote = remotePlayers[data.playerId];
            if (!remote) return;

            const newPos = new THREE.Vector3(data.x, typeof data.y === 'number' ? data.y : remote.model.position.y, data.z);
            const dir = new THREE.Vector3().subVectors(newPos, remote.model.position);
            if (dir.lengthSq() > 1e-4) {
                const angle = Math.atan2(dir.x, dir.z);
                const targetQuat = new THREE.Quaternion().setFromEuler(
                    new THREE.Euler(0, angle, 0)
                );
                remote.model.quaternion.slerp(targetQuat, 0.2);
            }

            remote.targetPosition = newPos.clone();

            if (remote.currentAction !== remote.walkAction) {
                // Более плавный переход к анимации ходьбы
                const fadeTime = 0.3;
                remote.currentAction.fadeOut(fadeTime);
                remote.walkAction.reset().fadeIn(fadeTime).play();
                remote.currentAction = remote.walkAction;

                // Синхронизируем время анимации
                remote.walkAction.time = 0;
            }

            clearTimeout(remote._idleTimeout);
            remote._idleTimeout = setTimeout(() => {
                if (remote.currentAction !== remote.idleAction) {
                    // Более плавный переход к idle анимации
                    const fadeTime = 0.3;
                    remote.currentAction.fadeOut(fadeTime);
                    remote.idleAction.reset().fadeIn(fadeTime).play();
                    remote.currentAction = remote.idleAction;
                }
            }, 500);

            // Update voice chat volume based on distance
            if (voiceConnections.current[data.playerId]) {
                const dist = player.position.distanceTo(newPos);
                const maxDist = 50;
                const volume = Math.max(0, 1 - dist / maxDist);
                voiceConnections.current[data.playerId].audioElement.volume = volume;
            }
        });

        socket.on('newPlayer', (data) => {
            console.log('newPlayer', data);
            const { playerId, x, z, avatarURL, gender, firstName, lastName } = data;

            // Проверяем, не существует ли уже игрок с таким ID
            if (remotePlayers[playerId]) {
                console.log(`Игрок ${playerId} уже существует, обновляем позицию`);
                // Обновляем позицию существующего игрока
                remotePlayers[playerId].model.position.set(x, 0, z);
                return;
            }

            // Если мы сейчас внутри интерьера, показывать новых игроков следует только когда они тоже будут в нашем списке currentPlayers,
            // который уже фильтруется сервером по interiorId. Здесь просто добавляем как обычно.
            addOtherPlayer(playerId, x, z, avatarURL, gender, firstName, lastName);
        });

        socket.on('playerDisconnected', (id) => {
            if (remotePlayers[id]) {
                scene.remove(remotePlayers[id].model);
                delete remotePlayers[id];
            }
            if (voiceIcons.current[id]) {
                delete voiceIcons.current[id];
            }
            cleanupVoiceConnection(id);
        });




        // Throttling для колеса мыши
        let wheelTimeout = null;

        function onMouseWheel(e) {
            e.preventDefault();

            // Throttling - обрабатываем только каждые 16ms (60fps)
            if (wheelTimeout) return;

            wheelTimeout = setTimeout(() => {
                wheelTimeout = null;
            }, 16);

            const delta = -e.deltaY * 0.001;

            if (e.ctrlKey) {
                // При нажатом Ctrl управляем и вертикальным, и горизонтальным углом камеры
                if (e.shiftKey) {
                    // Shift + Ctrl + колесо = горизонтальный поворот (влево-вправо) относительно исходного азимута
                    const horizontalDelta = delta * 2; // Увеличиваем чувствительность
                    horizontalYaw = THREE.MathUtils.clamp(
                        horizontalYaw + horizontalDelta,
                        -Math.PI / 2,
                        Math.PI / 2
                    );
                } else {
                    // Ctrl + колесо = вертикальный поворот (вверх-вниз)
                cameraPitchOffset = THREE.MathUtils.clamp(
                    cameraPitchOffset + delta,
                    -maxPitch,
                    maxPitch
                );
                }
            } else {
                if (cameraRef.current === orthoCamRef.current) {
                    zoom = THREE.MathUtils.clamp(zoom * (1 + delta), minZoom, maxZoom);
                    orthoCamRef.current.zoom = zoom;
                    orthoCamRef.current.updateProjectionMatrix();
                }
            }
        }

        // Throttling для движения мыши
        let mouseMoveTimeout = null;

        function onMouseLookMove(e) {
            if (!isInInteriorRef.current || cameraRef.current !== fpCamRef.current || !playerRef.current) return;
            if (altHeldRef.current) return; // при зажатом Alt не вращаем камеру

            // Throttling - обрабатываем только каждые 8ms (120fps для более плавного движения)
            if (mouseMoveTimeout) return;

            mouseMoveTimeout = setTimeout(() => {
                mouseMoveTimeout = null;
            }, 8);

            const movementX = e.movementX || e.mozMovementX || e.webkitMovementX || 0;
            const movementY = e.movementY || e.mozMovementY || e.webkitMovementY || 0;

            // Уменьшаем чувствительность для более плавного движения
            const sensitivity = 0.0015;

            // В интерьере поворачиваем только камеру, не игрока
            if (isInInteriorRef.current) {
                // Поворачиваем камеру по горизонтали (влево-вправо)
                const yawDelta = -movementX * sensitivity;
                const currentYaw = playerRef.current.rotation.y;
                playerRef.current.rotation.y = currentYaw + yawDelta;

                // Поворачиваем камеру по вертикали (вверх-вниз)
                const pitchDelta = -movementY * sensitivity;
                fpPitchRef.current = THREE.MathUtils.clamp(
                    fpPitchRef.current + pitchDelta,
                    -Math.PI / 2 + 0.1,
                    Math.PI / 2 - 0.1
                );
            } else {
                // В обычном режиме поворачиваем игрока
                playerRef.current.rotation.y -= movementX * sensitivity;
                fpPitchRef.current = THREE.MathUtils.clamp(
                    fpPitchRef.current - movementY * sensitivity,
                    -Math.PI / 2 + 0.1,
                    Math.PI / 2 - 0.1
                );
            }
        }

        async function init() {
            console.log('[DEBUG] init вызван');

            // Проверяем, что THREE загружен
            if (!THREE) {
                console.error('THREE.js не загружен');
                return;
            }

            // Проверяем, что THREE.Clock доступен
            if (!THREE.Clock) {
                console.error('THREE.Clock не доступен');
                return;
            }

            // Проверяем, что THREE.Scene доступен
            if (!THREE.Scene) {
                console.error('THREE.Scene не доступен');
                return;
            }

            scene = new THREE.Scene();
            //scene.fog = new THREE.FogExp2(0xcce0ff, 0.002);
            sceneRef.current = scene;
            const aspect = window.innerWidth / window.innerHeight;
            const d = 200;

            // Проверяем, что THREE.OrthographicCamera доступен
            if (!THREE.OrthographicCamera) {
                console.error('THREE.OrthographicCamera не доступен');
                return;
            }

            // Проверяем, что THREE.PerspectiveCamera доступен
            if (!THREE.PerspectiveCamera) {
                console.error('THREE.PerspectiveCamera не доступен');
                return;
            }

            orthoCamera = new THREE.OrthographicCamera(-d * aspect, d * aspect, d, -d, 1, 1000);
            orthoCamera.position.set(200, 200, 200);
            orthoCamera.zoom = zoom;
            orthoCamera.updateProjectionMatrix();
            orthoCamera.lookAt(scene.position);

            fpCamera = new THREE.PerspectiveCamera(75, aspect, 0.1, 1000);

            cameraRef.current = orthoCamera;
            orthoCamRef.current = orthoCamera;
            fpCamRef.current = fpCamera;

            // Проверяем поддержку WebGL
            if (!window.WebGLRenderingContext) {
                console.error('WebGL не поддерживается в этом браузере');
                return;
            }

            // Проверяем, что THREE.WebGLRenderer доступен
            if (!THREE.WebGLRenderer) {
                console.error('THREE.WebGLRenderer не доступен');
                return;
            }

            try {
                renderer = new THREE.WebGLRenderer({
                    antialias: true,
                    alpha: true,
                    preserveDrawingBuffer: false
                });
            } catch (error) {
                console.error('Ошибка создания WebGL renderer:', error);
                // Попытка создать renderer без antialias
                try {
                    renderer = new THREE.WebGLRenderer({
                        antialias: false,
                        alpha: true,
                        preserveDrawingBuffer: false
                    });
                } catch (secondError) {
                    console.error('Не удалось создать WebGL renderer даже без antialias:', secondError);
                    return;
                }
            }
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setClearColor(0x87CEEB, 1); // Голубое небо
            renderer.shadowMap.enabled = true;
            renderer.shadowMap.type = THREE.PCFSoftShadowMap;
            renderer.outputColorSpace = THREE.SRGBColorSpace;
            renderer.toneMapping = THREE.ACESFilmicToneMapping;
            renderer.toneMappingExposure = 1.0;
            rendererRef.current = renderer;

            if (mountRef.current) {
                mountRef.current.appendChild(renderer.domElement);
            } else {
                console.error('mountRef.current не найден');
                return;
            }

            if (renderer && renderer.domElement) {
                renderer.domElement.addEventListener('wheel', onMouseWheel, { passive: false });
                renderer.domElement.addEventListener('mousemove', onMouseLookMove);
            } else {
                console.error('renderer или renderer.domElement не найден');
                return;
            }

            // Pointer lock больше не используется в интерьере — курсор всегда активен

            // Проверяем, что THREE.PlaneGeometry доступен
            if (!THREE.PlaneGeometry) {
                console.error('THREE.PlaneGeometry не доступен');
                return;
            }

            // Проверяем, что THREE.MeshBasicMaterial доступен
            if (!THREE.MeshBasicMaterial) {
                console.error('THREE.MeshBasicMaterial не доступен');
                return;
            }

            const planeGeometry = new THREE.PlaneGeometry(territorySize, territorySize);
            const planeMaterial = new THREE.MeshBasicMaterial({
                color: 0x00aa00,
                transparent: true,
                opacity: 0,        // невидим
                depthWrite: false  // не трогает Z-буфер
            });

            // Проверяем, что THREE.Mesh доступен
            if (!THREE.Mesh) {
                console.error('THREE.Mesh не доступен');
                return;
            }

            groundPlane = new THREE.Mesh(planeGeometry, planeMaterial);
            groundPlane.rotation.x = -Math.PI / 2;
            scene.add(groundPlane);
            groundRef.current = groundPlane;

            // Проверяем, что THREE.AmbientLight доступен
            if (!THREE.AmbientLight) {
                console.error('THREE.AmbientLight не доступен');
                return;
            }

            // Проверяем, что THREE.DirectionalLight доступен
            if (!THREE.DirectionalLight) {
                console.error('THREE.DirectionalLight не доступен');
                return;
            }

            const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
            scene.add(ambientLight);
            const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
            directionalLight.position.set(50, 100, 50);
            scene.add(directionalLight);

            // Проверяем, что THREE.SphereGeometry доступен
            if (!THREE.SphereGeometry) {
                console.error('THREE.SphereGeometry не доступен');
                return;
            }

            const markerGeometry = new THREE.SphereGeometry(0.5, 16, 16);
            const markerMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
            destinationMarker = new THREE.Mesh(markerGeometry, markerMaterial);
            destinationMarker.visible = false;
            scene.add(destinationMarker);

            // Проверяем, что THREE.LoadingManager доступен
            if (!THREE.LoadingManager) {
                console.error('THREE.LoadingManager не доступен');
                return;
            }

            // Проверяем, что THREE.TextureLoader доступен
            if (!THREE.TextureLoader) {
                console.error('THREE.TextureLoader не доступен');
                return;
            }

            const loadingManager = new THREE.LoadingManager(() => {
                console.log("Все текстуры загружены");
            });
            const textureLoader = new THREE.TextureLoader(loadingManager);
            const baseTexture = textureLoader.load('textures/base.png',
                // onLoad callback
                (texture) => {
                    console.log('Текстура base.png загружена успешно');
                    if (THREE.SRGBColorSpace) {
                        texture.colorSpace = THREE.SRGBColorSpace;
                    }
                },
                // onProgress callback
                (progress) => {
                    console.log('Прогресс загрузки текстуры:', progress);
                },
                // onError callback
                (error) => {
                    console.error('Ошибка загрузки текстуры base.png:', error);
                    // Создаем материал без текстуры в случае ошибки
                    if (THREE.MeshStandardMaterial) {
                        customMaterial = new THREE.MeshStandardMaterial({
                            color: 0x808080
                        });
                    } else {
                        console.error('THREE.MeshStandardMaterial не доступен');
                    }
                }
            );

            // Проверяем, что THREE.MeshStandardMaterial доступен
            if (!THREE.MeshStandardMaterial) {
                console.error('THREE.MeshStandardMaterial не доступен');
                return;
            }

            customMaterial = new THREE.MeshStandardMaterial({
                map: baseTexture,
                roughness: 0.5,
                metalness: 0.1
            });


            const npcMixersArray = [];
            // Добавление персонажей 
            const npcData = [
                { id: 'bartender', model: '/models/npc/bartender.glb', position: [0, 0, 10] },
                { id: 'guard', model: '/models/npc/guard.glb', position: [0, 0, 5] },
                { id: 'Adventurer', model: '/models/npc/galina.glb', position: [-16.5, -100, -68.8] },
                { id: 'BeachCharacter', model: '/models/npc/BeachCharacter.glb', position: [0, 0, 3] },
                { id: 'Oxranik', model: '/models/npc/Oxranik.glb', position: [0, 0, -3] },
                { id: 'Computer', model: '/models/npc/Computer.glb', position: [0.1, 0.1, 2.1] }
            ];
            for (const npc of npcData) {
                try {
                    const gltf = await gltfLoader.loadAsync(npc.model);
                    const model = gltf.scene;

                    // Проверяем и исправляем материалы модели
                    model.traverse((child) => {
                        if (child.isMesh && child.material) {
                            if (Array.isArray(child.material)) {
                                child.material.forEach(mat => {
                                    if (!mat || !mat.isMaterial) {
                                        console.warn(`Неправильный материал в ${npc.id}, заменяем на стандартный`);
                                        if (THREE.MeshStandardMaterial) {
                                            child.material = new THREE.MeshStandardMaterial({ color: 0x808080 });
                                        } else {
                                            console.error('THREE.MeshStandardMaterial не доступен для замены материала');
                                        }
                                    }
                                });
                            } else if (!child.material.isMaterial) {
                                console.warn(`Неправильный материал в ${npc.id}, заменяем на стандартный`);
                                if (THREE.MeshStandardMaterial) {
                                    child.material = new THREE.MeshStandardMaterial({ color: 0x808080 });
                                } else {
                                    console.error('THREE.MeshStandardMaterial не доступен для замены материала');
                                }
                            }
                        }
                    });

                    model.position.set(...npc.position);
                    model.userData.npcId = npc.id;
                    model.userData.isNpc = true;

                    // Добавляем метку с именем
                    let label;
                    if (npc.id == 'bartender') {
                        label = createPlayerLabel('Серега Пират');
                    }
                    else if (npc.id == 'guard') {
                        label = createPlayerLabel('Саша Белый');
                    }
                    else if (npc.id == 'Adventurer') {
                        label = createPlayerLabel('Галина');
                    }
                    else if (npc.id == 'BeachCharacter') {
                        label = createPlayerLabel('Костя Ключник');
                    }
                    else if (npc.id == 'Oxranik') {
                        label = createPlayerLabel('Охранник');
                    }

                    if (label) {
                        label.position.set(0, 2.2, 0);
                        model.add(label);
                    }

                    model.rotateY(Math.PI); // Развернуть персонажа 
                    scene.add(model);
                    npcMeshes.push(model); // Правильное добавление в массив
                    npcMeshesRef.current.push(model);
                    cityMeshesRef.current.push(model);

                    if (npc.id == 'Computer') {
                        model.scale.set(0.001, 0.001, 0.001);
                    }

                    if (npc.id == 'Oxranik') {
                        model.scale.set(0.2, 0.2, 0.2);
                    }

                } catch (error) {
                    console.error(`Ошибка загрузки NPC ${npc.id}:`, error);
                }
            }
            // Загрузка объектов города из базы данных
            let cityObjects = [];
            try {
                const profile = JSON.parse(sessionStorage.getItem('user_profile') || '{}');
                const cityId = profile.last_city_id || 1;
                const token = localStorage.getItem('token');
                const res = await fetch(`/api/cities/${cityId}/objects`, {
                    headers: { Authorization: `Bearer ${token}` }
                });
                cityObjects = await res.json();
            } catch (e) {
                console.error('[DEBUG] Ошибка загрузки объектов города:', e);
                cityObjects = [];
            }

            cityObjectsDataRef.current = cityObjects;
            let interiors = [];
            try {
                const token = localStorage.getItem('token');
                const resInt = await fetch('/api/interiors', { headers: { Authorization: `Bearer ${token}` } });
                interiors = await resInt.json();
            } catch (e) {
                console.error('Ошибка загрузки списка интерьеров', e);
            }
            interiorsDataRef.current = interiors;
            updateCityObjectVisibility();

            window.addEventListener('keydown', onKeyDown);
            window.addEventListener('keyup', onKeyUp);
            renderer.domElement.addEventListener('pointerdown', onDocumentMouseDown);
            renderer.domElement.addEventListener('mousemove', onMouseLookMove);

            try {
                // Проверяем, что avatarUrl существует и валиден
                let modelUrl = avatarUrl;
                if (!avatarUrl || avatarUrl === 'undefined' || avatarUrl === 'null') {
                    console.warn('avatarUrl не определен, используем fallback модель');
                    modelUrl = '/models/character.glb';
                }

                console.log('Загружаем модель игрока:', modelUrl);
                const gltf = await loadPlayerModel(modelUrl);
                player = gltf.scene;
                scene.add(player);
                playerRef.current = player;
                player.scale.set(1, 1, 1);
                player.position.set(0, 0, 0);

                const profile = JSON.parse(sessionStorage.getItem('user_profile') || '{}');
                const myName = `${profile.firstName || ''} ${profile.lastName || ''}`.trim();

                // Устанавливаем имя игрока в mountRef для отладки
                if (mountRef.current) {
                    mountRef.current.setAttribute('data-player-name', myName);
                }

                const nameLabel = createPlayerLabel(myName);
                nameLabel.position.set(0, 2.2, 0);
                player.add(nameLabel);

                mixer = new THREE.AnimationMixer(player);

                const isFemale = gender === 'female';
                const animGender = isFemale ? 'feminine' : 'masculine';

                const idlePath = `/animations/${animGender}/glb/idle/${isFemale ? 'F_Standing_Idle_001.glb' : 'M_Standing_Idle_001.glb'
                    }`;
                const walkPath = `/animations/${animGender}/glb/locomotion/${isFemale ? 'F_Walk_002.glb' : 'M_Walk_001.glb'
                    }`;

                console.log('Загружаем анимации:', { idlePath, walkPath });

                const [idleGltf, walkGltf] = await Promise.all([
                    animLoader.loadAsync(idlePath).catch(err => {
                        console.error('Ошибка загрузки idle анимации:', err);
                        throw err;
                    }),
                    animLoader.loadAsync(walkPath).catch(err => {
                        console.error('Ошибка загрузки walk анимации:', err);
                        throw err;
                    })
                ]);

                idleGltf.animations.forEach(stripPositionTracks);
                walkGltf.animations.forEach(stripPositionTracks);

                console.log('Idle GLB анимации:', idleGltf.animations);
                console.log('Walk GLB анимации:', walkGltf.animations);

                // Проверяем, что анимации загружены
                if (idleGltf.animations.length === 0) {
                    console.warn('Idle анимации не найдены, создаем пустую анимацию');
                    const emptyClip = new THREE.AnimationClip('idle', 1, []);
                    idleGltf.animations.push(emptyClip);
                }

                if (walkGltf.animations.length === 0) {
                    console.warn('Walk анимации не найдены, создаем пустую анимацию');
                    const emptyClip = new THREE.AnimationClip('walk', 1, []);
                    walkGltf.animations.push(emptyClip);
                }

                idleAction = mixer.clipAction(idleGltf.animations[0], player);
                walkAction = mixer.clipAction(walkGltf.animations[0], player);

                // синхронизация темпа шага с линейной скоростью
                walkAction.setEffectiveTimeScale(moveSpeed / WALK_ANIM_SPEED_MPS);

                idleAction.play();
                currentAction = idleAction;

                updateCameraFollow();

                // Не отправляем здесь newPlayer — делаем это централизованно после currentPlayers
            } catch (err) {
                console.error("Ошибка загрузки модели игрока:", err);
                console.error("Детали ошибки:", {
                    avatarUrl,
                    gender,
                    error: err.message,
                    stack: err.stack
                });

                // Создаем простую модель-заглушку в случае ошибки
                console.log("Создаем fallback модель для игрока");

                // Пробуем загрузить локальную модель
                try {
                    const fallbackGltf = await loadPlayerModel('/models/character.glb');
                    player = fallbackGltf.scene;
                    console.log("Fallback модель загружена успешно");
                } catch (fallbackErr) {
                    console.error("Ошибка загрузки fallback модели:", fallbackErr);

                    // Создаем простую геометрию
                    const fallbackGeometry = new THREE.BoxGeometry(1, 2, 1);
                    const fallbackMaterial = new THREE.MeshBasicMaterial({ color: 0xff0000 });
                    player = new THREE.Mesh(fallbackGeometry, fallbackMaterial);
                    console.log("Создана простая модель-заглушка");
                }

                scene.add(player);
                playerRef.current = player;
                player.scale.set(1, 1, 1);
                player.position.set(0, 0, 0);

                // Создаем простые анимации для fallback
                mixer = new THREE.AnimationMixer(player);
                const emptyIdleClip = new THREE.AnimationClip('idle', 1, []);
                const emptyWalkClip = new THREE.AnimationClip('walk', 1, []);

                idleAction = mixer.clipAction(emptyIdleClip, player);
                walkAction = mixer.clipAction(emptyWalkClip, player);

                idleAction.play();
                currentAction = idleAction;

                updateCameraFollow();

                // Отправляем данные о новом игроке
                const profile = JSON.parse(sessionStorage.getItem('user_profile') || '{}');
                socketRef.current?.emit('newPlayer', {
                    x: player.position.x,
                    z: player.position.z,
                    avatarURL: avatarUrl || '/models/character.glb',
                    firstName: profile.firstName,
                    lastName: profile.lastName,
                    userId: profile.id
                });
            }
        }

        function stripPositionTracks(clip) {
            clip.tracks = clip.tracks.filter(track => !track.name.endsWith('.position'));
            return clip;
        }

        function computePath(fromVec3, toVec3) {
            const startX = Math.floor((fromVec3.x + boundary) / nodeSize);
            const startZ = Math.floor((fromVec3.z + boundary) / nodeSize);
            const endX = Math.floor((toVec3.x + boundary) / nodeSize);
            const endZ = Math.floor((toVec3.z + boundary) / nodeSize);

            const finder = new PF.AStarFinder({
                allowDiagonal: true,
                dontCrossCorners: true,
                diagonalMovement: PF.DiagonalMovement.OnlyWhenNoObstacles
            });
            if (!pathfinderGrid) {
                console.warn('Pathfinder grid not ready');
                return [];
            }
            const gridClone = pathfinderGrid.clone();

            if (!gridClone.isWalkableAt(startX, startZ)) {
                gridClone.setWalkableAt(startX, startZ, true);
            }

            if (!gridClone.isWalkableAt(endX, endZ)) {
                gridClone.setWalkableAt(endX, endZ, true);
            }

            const rawPath = finder.findPath(startX, startZ, endX, endZ, gridClone);
            if (!rawPath.length) return [];

            const smooth = PF.Util.smoothenPath(gridClone, rawPath);
            return smooth.map(([x, z]) => new THREE.Vector3(
                x * nodeSize - boundary + nodeSize / 2,
                fromVec3.y,
                z * nodeSize - boundary + nodeSize / 2
            ));
        }

        function buildPathfindingGrid() {
            pathfinderGrid = new PF.Grid(gridSize, gridSize);

            obstacles.forEach(o => {
                const box = new THREE.Box3().setFromObject(o.mesh);

                let minX = Math.floor((box.min.x + boundary) / nodeSize);
                let maxX = Math.floor((box.max.x + boundary) / nodeSize);
                let minZ = Math.floor((box.min.z + boundary) / nodeSize);
                let maxZ = Math.floor((box.max.z + boundary) / nodeSize);

                minX = Math.max(0, Math.min(gridSize - 1, minX));
                maxX = Math.max(0, Math.min(gridSize - 1, maxX));
                minZ = Math.max(0, Math.min(gridSize - 1, minZ));
                maxZ = Math.max(0, Math.min(gridSize - 1, maxZ));

                for (let x = minX; x <= maxX; x++) {
                    for (let z = minZ; z <= maxZ; z++) {
                        pathfinderGrid.setWalkableAt(x, z, false);
                    }
                }
            });
        }

        function loadCityObject(obj) {
            console.log('loadCityObject вызвана для объекта:', {
                id: obj.id,
                name: obj.name,
                textures: obj.textures,
                model_url: obj.model_url
            });

            gltfLoader.load(
                obj.model_url,
                (gltf) => {
                    const model = gltf.scene;

                    // Проверяем и исправляем материалы модели
                    model.traverse((child) => {
                        if (child.isMesh && child.material) {
                            if (Array.isArray(child.material)) {
                                child.material.forEach(mat => {
                                    if (!mat || !mat.isMaterial) {
                                        console.warn(`Неправильный материал в объекте ${obj.name}, заменяем на стандартный`);
                                        child.material = new THREE.MeshStandardMaterial({ color: 0x808080 });
                                    }
                                });
                            } else if (!child.material.isMaterial) {
                                console.warn(`Неправильный материал в объекте ${obj.name}, заменяем на стандартный`);
                                child.material = new THREE.MeshStandardMaterial({ color: 0x808080 });
                            }
                        }
                    });

                    model.userData = {
                        id: obj.id,
                        type: obj.name,
                        organizationId: obj.organization_id,
                        rent: obj.rent,
                        tax: obj.tax
                    };
                    // Применяем масштаб из БД, если есть
                    const sx = (obj.scale_x ?? 1) || 1;
                    const sy = (obj.scale_y ?? 1) || 1;
                    const sz = (obj.scale_z ?? 1) || 1;
                    model.scale.set(sx, sy, sz);
                    model.position.set(obj.pos_x, obj.pos_y, obj.pos_z);
                    model.rotation.set(obj.rot_x, obj.rot_y, obj.rot_z);

                    console.log('Обрабатываем материалы для объекта:', obj.name);

                    // Обрабатываем материалы в зависимости от поля textures
                    model.traverse(child => {
                        if (child.isMesh) {
                            console.log('Найден меш в объекте:', obj.name, {
                                hasMaterial: !!child.material,
                                materialType: child.material ? child.material.type : 'none'
                            });

                            // Сохраняем оригинальные материалы для интерьеров
                            if (obj.name && obj.name.toLowerCase().includes('interior')) {
                                console.log('Объект интерьера - оставляем оригинальные материалы');
                                // Для интерьеров оставляем оригинальные материалы
                                if (child.material) {
                                    child.material.needsUpdate = true;
                                }
                            } else {
                                // Проверяем поле textures
                                if (obj.textures && obj.textures !== '-') {
                                    console.log('Загружаем текстурпак для объекта:', obj.name, 'текстурпак:', obj.textures);

                                    // Для citypack.json используем тот же принцип, что в MapEditor: единый стандартный материал с baseColor
                                    if (obj.textures === '/packs/citypack.json') {
                                        // Присваиваем клон стандартного материала с базовой текстурой из пака
                                        const forceReplace = true;
                                        loadTexturePackForMesh(obj.textures, child, forceReplace);
                                    } else {
                                        loadTexturePackForMesh(obj.textures, child);
                                    }
                                } else {
                                    console.log('Оставляем встроенные текстуры для объекта:', obj.name);
                                    // Если textures = '-' или не указано, оставляем встроенные текстуры
                                    if (child.material) {
                                        child.material.needsUpdate = true;
                                    }
                                }
                            }
                        }
                    });

                    scene.add(model);
                    cityMeshesRef.current.push(model);
                    const boundingBox = new THREE.Box3().setFromObject(model);
                    const isCollidable = obj.collidable !== false && !/road/i.test(obj.name);
                    if (isCollidable) {
                        obstacles.push({ mesh: model, box: boundingBox });
                    }
                    loadedCityObjectsRef.current[obj.id] = { mesh: model, data: obj };
                    buildPathfindingGrid();
                },
                undefined,
                (error) => console.error('Ошибка загрузки объекта', obj.name, error)
            );
        }

        function unloadCityObject(id) {
            const entry = loadedCityObjectsRef.current[id];
            if (!entry) return;
            const { mesh } = entry;
            scene.remove(mesh);
            cityMeshesRef.current = cityMeshesRef.current.filter(m => m !== mesh);
            obstacles = obstacles.filter(o => o.mesh !== mesh);
            delete loadedCityObjectsRef.current[id];
            buildPathfindingGrid();
        }

        // Кэш для оптимизации вычислений расстояний
        let lastPlayerPosition = null;
        let lastVisibilityUpdate = 0;

        function updateCityObjectVisibility() {
            if (!player) return;

            const p = player.position;
            const now = Date.now();

            // Проверяем, изменилась ли позиция игрока значительно
            if (lastPlayerPosition &&
                Math.abs(lastPlayerPosition.x - p.x) < 5 &&
                Math.abs(lastPlayerPosition.z - p.z) < 5 &&
                now - lastVisibilityUpdate < 1000) {
                return; // Пропускаем обновление, если игрок не двигался значительно
            }

            lastPlayerPosition = p.clone();
            lastVisibilityUpdate = now;

            // Оптимизированные вычисления расстояний
            const loadRadiusSq = LOAD_RADIUS * LOAD_RADIUS;

            cityObjectsDataRef.current.forEach(obj => {
                const dx = obj.pos_x - p.x;
                const dz = obj.pos_z - p.z;
                const distSq = dx * dx + dz * dz; // Используем квадрат расстояния для избежания sqrt

                if (distSq <= loadRadiusSq) {
                    if (!loadedCityObjectsRef.current[obj.id]) {
                        console.log('Загружаем объект:', { id: obj.id, name: obj.name, textures: obj.textures });
                        loadCityObject(obj);
                    }
                } else {
                    if (loadedCityObjectsRef.current[obj.id]) unloadCityObject(obj.id);
                }
            });

            interiorsDataRef.current.forEach(int => {
                const dx = int.pos_x - p.x;
                const dz = int.pos_z - p.z;
                const distSq = dx * dx + dz * dz;

                if (distSq <= loadRadiusSq) {
                    if (!loadedInteriorMeshesRef.current[int.id]) loadInteriorPlaceholder(int);
                } else if (loadedInteriorMeshesRef.current[int.id]) {
                    unloadInteriorPlaceholder(int.id);
                }
            });
        }

        function loadInteriorPlaceholder(int) {
            // Упрощённый невидимый placeholder с кликабельной зоной
            const mesh = new THREE.Mesh(
                new THREE.BoxGeometry(2, 2, 2),
                new THREE.MeshBasicMaterial({ visible: false })
            );
            mesh.position.set(int.pos_x, int.pos_y, int.pos_z);
            mesh.userData.interiorId = int.id;
            scene.add(mesh);
            cityMeshesRef.current.push(mesh);
            loadedInteriorMeshesRef.current[int.id] = mesh;
        }

        function unloadInteriorPlaceholder(id) {
            const mesh = loadedInteriorMeshesRef.current[id];
            if (!mesh) return;
            scene.remove(mesh);
            cityMeshesRef.current = cityMeshesRef.current.filter(m => m !== mesh);
            delete loadedInteriorMeshesRef.current[id];
        }

        // В функции onDocumentMouseDown заменяем существующий код на:
        async function onDocumentMouseDown(event) {
            if (!player) return;
            if (isInInteriorRef.current) return; // disable clicks when inside
            event.preventDefault();

            const rect = renderer.domElement.getBoundingClientRect();
            const mouse = new THREE.Vector2(
                ((event.clientX - rect.left) / rect.width) * 2 - 1,
                -((event.clientY - rect.top) / rect.height) * 2 + 1
            );
            const raycaster = new THREE.Raycaster();
            raycaster.setFromCamera(mouse, cameraRef.current);

            // NPC
            const npcHit = raycaster.intersectObjects(npcMeshes, true);
            if (npcHit.length) {
                let root = npcHit[0].object;
                while (root.parent && !root.userData.isNpc) root = root.parent;
                if (root.userData.npcId) {
                    if (root.userData.npcId === 'Computer') {
                        setShowMiniGame(true);
                        setPasswordCorrect(false);
                        setAudioUrl("/audio/firs.ogg");
                        addSeregaComment("Ну чё, хакер, разберёшься?");
                    } else {
                        loadDialog(root.userData.npcId);
                    }
                    return;
                }
            }

            // Здания/объекты
            const houseHit = raycaster.intersectObjects(obstacles.map(o => o.mesh).filter(Boolean), true);
            if (houseHit.length) {
                let obj = houseHit[0].object;
                while (obj && !obj.userData.id && !obj.userData.interiorId) obj = obj.parent;
                if (obj && obj.userData.id) {
                    setSelectedHouse(obj.userData);
                    return;
                }
                if (obj && obj.userData.interiorId) {
                    console.log('Клик по интерьеру:', obj.userData.interiorId);
                    await enterInteriorMode(obj.userData.interiorId);
                    return;
                }
            }

            // 3. Проверка игроков
            const remoteModels = Object.values(remotePlayers).map(r => r.model);
            const playerIntersects = raycaster.intersectObjects(remoteModels, true);
            if (playerIntersects.length) {
                let mesh = playerIntersects[0].object;
                while (mesh && !remoteModels.includes(mesh)) mesh = mesh.parent;
                const entry = Object.entries(remotePlayers).find(([, r]) => r.model === mesh);
                if (entry) {
                    const [id, r] = entry;
                    setSelectedPlayer({ socketId: id, firstName: r.firstName, lastName: r.lastName });
                    setPlayerStats(null);
                    return;
                }
            }

            // Сброс выделений
            setSelectedHouse(null);
            setOrgMenu(null);
            setSelectedPlayer(null);

            // 4. Проверка земли
            if (!groundPlane) {
                console.warn('groundPlane ещё не готов');
                return;
            }

            const groundIntersects = raycaster.intersectObject(groundPlane);
            if (groundIntersects.length === 0) {
                console.log("Клик не попал по плоскости");
                return;
            }

            destination = groundIntersects[0].point.clone();
            destination.y = player.position.y;

            const newPath = computePath(player.position, destination);
            if (newPath.length === 0) {
                console.warn("Путь не найден");
                return;
            }
            currentPath = newPath;
            pathIndex = 0;

            if (destinationMarker) {
                destinationMarker.position.copy(destination);
                destinationMarker.visible = true;
            }
        }

        function onKeyDown(event) {
            keys[event.key] = true;
            if (event.key === 'Alt') altHeldRef.current = true;

            console.log('onKeyDown:', event.key, 'isInInteriorRef.current:', isInInteriorRef.current);

            // ESC больше не выходит из интерьера

            if (isInInteriorRef.current) {
                console.log('Обрабатываем клавишу в интерьере:', event.key);
                const k = event.key.toLowerCase();
                if (k === 'arrowup' || k === 'w') startMove('forward');
                if (k === 'arrowdown' || k === 's') startMove('backward');
                if (k === 'arrowleft' || k === 'a') startMove('left');
                if (k === 'arrowright' || k === 'd') startMove('right');
                if (k === 'q') startMove('strafeLeft');
                if (k === 'e') startMove('strafeRight');
            }

            if (event.key.toLowerCase() === 'i') {
                const prof = JSON.parse(sessionStorage.getItem('user_profile') || '{}');
                socket.emit('economy:getInventory', { userId: prof.id });
                setShowInventory(v => !v);
            }

            // Ctrl + Arrow keys for camera control
            if (event.ctrlKey) {
                const key = event.key.toLowerCase();
                if (key === 'arrowleft') {
                    horizontalYaw = THREE.MathUtils.clamp(horizontalYaw - 0.1, -Math.PI / 2, Math.PI / 2);
                } else if (key === 'arrowright') {
                    horizontalYaw = THREE.MathUtils.clamp(horizontalYaw + 0.1, -Math.PI / 2, Math.PI / 2);
                }
            }

            // Сбрасываем назначение только если не в интерьере
            if (!isInInteriorRef.current) {
                destination = null;
                destinationMarker.visible = false;
            }
        }

        function onKeyUp(event) {
            keys[event.key] = false;
            if (event.key === 'Alt') altHeldRef.current = false;
            if (isInInteriorRef.current) {
                const k = event.key.toLowerCase();
                if (k === 'arrowup' || k === 'w') stopMove('forward');
                if (k === 'arrowdown' || k === 's') stopMove('backward');
                if (k === 'arrowleft' || k === 'a') stopMove('left');
                if (k === 'arrowright' || k === 'd') stopMove('right');
                if (k === 'q') stopMove('strafeLeft');
                if (k === 'e') stopMove('strafeRight');
            }
        }

        function createPlayerLabel(text) {
            const canvas = document.createElement('canvas');
            canvas.width = 1024; // Увеличиваем размер canvas
            canvas.height = 256;
            const ctx = canvas.getContext('2d');

            // Добавляем фон для лучшей видимости
            ctx.fillStyle = 'rgba(0, 0, 0, 0.7)';
            ctx.fillRect(0, 0, canvas.width, canvas.height);

            const fontSize = 72; // Увеличиваем размер шрифта
            ctx.fillStyle = 'white';
            ctx.font = `bold ${fontSize}px Arial`;

            ctx.textAlign = 'center';
            ctx.textBaseline = 'middle';

            // Добавляем обводку для лучшей видимости
            ctx.strokeStyle = 'black';
            ctx.lineWidth = 2;
            ctx.strokeText(text, canvas.width / 2, canvas.height / 2);
            ctx.fillText(text, canvas.width / 2, canvas.height / 2);

            const texture = new THREE.CanvasTexture(canvas);
            texture.needsUpdate = true;

            const spriteMaterial = new THREE.SpriteMaterial({
                map: texture,
                transparent: true,
                depthTest: false, // Рисуем поверх всего
                depthWrite: false
            });
            const sprite = new THREE.Sprite(spriteMaterial);
            sprite.scale.set(2.2, 0.55, 1); // Увеличиваем размер спрайта

            // ↓↓↓ добавь это ↓↓↓
            sprite.raycast = () => { };
            sprite.userData.isUiSprite = true;

            return sprite;
        }

        function switchAnimation(newAction) {
            if (!newAction || !currentAction || newAction === currentAction) return;

            // Увеличиваем время перехода для более плавной анимации
            const fadeTime = 0.3;

            // Плавно убираем текущую анимацию
            currentAction.fadeOut(fadeTime);

            // Плавно включаем новую анимацию
            newAction.reset().fadeIn(fadeTime).play();

            // Обновляем текущую анимацию
            currentAction = newAction;

            // Синхронизируем время для избежания подлагов
            if (newAction === walkAction) {
                newAction.time = 0;
            }
        }

        function canMove(newPosition) {
            const halfSize = 1;
            const playerMin = new THREE.Vector2(newPosition.x - halfSize, newPosition.z - halfSize);
            const playerMax = new THREE.Vector2(newPosition.x + halfSize, newPosition.z + halfSize);

            for (let i = 0; i < obstacles.length; i++) {
                obstacles[i].mesh.updateMatrixWorld();
                const box = new THREE.Box3().setFromObject(obstacles[i].mesh);
                const obstacleMin = new THREE.Vector2(box.min.x, box.min.z);
                const obstacleMax = new THREE.Vector2(box.max.x, box.max.z);
                if ((playerMin.x <= obstacleMax.x && playerMax.x >= obstacleMin.x) &&
                    (playerMin.y <= obstacleMax.y && playerMax.y >= obstacleMin.y)) {
                    return false;
                }
            }
            return true;
        }

        // Подсчёт количества пересечений с препятствиями для позиции (для "саморазблокировки")
        function countIntersectionsAtPosition(pos, halfSize = 1) {
            const playerMin = new THREE.Vector2(pos.x - halfSize, pos.z - halfSize);
            const playerMax = new THREE.Vector2(pos.x + halfSize, pos.z + halfSize);

            let count = 0;
            for (let i = 0; i < obstacles.length; i++) {
                const mesh = obstacles[i]?.mesh;
                if (!mesh) continue;
                mesh.updateMatrixWorld();
                const box = new THREE.Box3().setFromObject(mesh);
                const obstacleMin = new THREE.Vector2(box.min.x, box.min.z);
                const obstacleMax = new THREE.Vector2(box.max.x, box.max.z);
                if ((playerMin.x <= obstacleMax.x && playerMax.x >= obstacleMin.x) &&
                    (playerMin.y <= obstacleMax.y && playerMax.y >= obstacleMin.y)) {
                    count++;
                }
            }
            return count;
        }

        function updateDestinationMovement(delta) {
            if (!player || currentPath.length === 0 || pathIndex >= currentPath.length) return;

            const target = currentPath[pathIndex];
            const dir = new THREE.Vector3().subVectors(target, player.position);
            dir.y = 0;
            const dist = dir.length();

            const stepDistance = moveSpeed * delta;
            if (dist < stepDistance) {
                // Двигаем к точке и аккуратно выравниваем по верхней поверхности
                player.position.copy(target);
                // Жёсткое выравнивание по топ-поверхности, чтобы исключить спад до y=0 на остановке
                (function alignGroundFinal(p) {
                    const downRay = new THREE.Raycaster(
                        new THREE.Vector3(p.x, 100, p.z),
                        new THREE.Vector3(0, -1, 0),
                        0,
                        300
                    );
                    downRay.camera = cameraRef.current;
                    const walkables = [
                        ...(cityGroupRef.current ? [cityGroupRef.current] : []),
                        groundPlane,
                        ...(cityMeshesRef.current || [])
                    ].filter(Boolean);
                    const raw = downRay.intersectObjects(walkables, true);
                    const isDescendantOf = (obj, ancestor) => { let c=obj; while(c){ if(c===ancestor) return true; c=c.parent;} return false; };
                    const hits = raw
                        .filter(h => !h.object.isSprite && h.face && h.face.normal && h.face.normal.y > 0.6)
                        .filter(h => !isDescendantOf(h.object, player));
                    if (hits.length) p.y = hits[0].point.y + 0.02;
                })(player.position);
                pathIndex++;
                blockedTime = 0;
                if (pathIndex >= currentPath.length) {
                    currentPath = [];
                    destination = null;
                    if (currentAction !== idleAction) {
                        currentAction.fadeOut(0.2);
                        idleAction.reset().fadeIn(0.2).play();
                        currentAction = idleAction;
                    }
                }
                return;
            }

            dir.normalize();
            const step = dir.clone().multiplyScalar(stepDistance);

            // 1) Поворот всегда догоняет, движение начинается сразу — естественное скольжение в сторону цели
            const desiredYaw = Math.atan2(dir.x, dir.z);
            const currentYaw = new THREE.Euler().setFromQuaternion(player.quaternion, 'YXZ').y;
            let yawDiff = desiredYaw - currentYaw;
            yawDiff = ((yawDiff + Math.PI) % (2 * Math.PI)) - Math.PI; // нормализация [-PI, PI]
            const maxTurnRate = 3.0; // рад/сек — ограничиваем скорость поворота
            const stepAngle = THREE.MathUtils.clamp(yawDiff, -maxTurnRate * delta, maxTurnRate * delta);
            const newYawFollow = currentYaw + stepAngle;
            player.quaternion.setFromEuler(new THREE.Euler(0, newYawFollow, 0));

            // Кандидаты перемещения: прямо, слайд по X, слайд по Z
            const tryMoves = [
                player.position.clone().add(step),
                player.position.clone().add(new THREE.Vector3(step.x, 0, 0)),
                player.position.clone().add(new THREE.Vector3(0, 0, step.z))
            ];

            // Помощник: «привязка» к верхней поверхности (учитываем всю геометрию города)
            const stickToTopSurface = (pos) => {
                const downRay = new THREE.Raycaster(
                    new THREE.Vector3(pos.x, 100, pos.z),
                    new THREE.Vector3(0, -1, 0),
                    0,
                    300
                );
                downRay.camera = cameraRef.current; // важное дополнение для спрайтов

                // фильтруем null/undefined и целимся в корневую группу города + groundPlane
                const walkables = [
                    ...(cityGroupRef.current ? [cityGroupRef.current] : []),
                    groundPlane,
                    ...(cityMeshesRef.current || [])
                ].filter(Boolean);

                const raw = downRay.intersectObjects(walkables, true);
                const isDescendantOf = (obj, ancestor) => {
                    let cur = obj; while (cur) { if (cur === ancestor) return true; cur = cur.parent; }
                    return false;
                };
                const hits = raw
                    .filter(h => !h.object.isSprite && h.face && h.face.normal && h.face.normal.y > 0.6)
                    .filter(h => !isDescendantOf(h.object, player));

                if (hits.length) {
                    pos.y = hits[0].point.y + 0.02; // лёгкий "антизалип"
                }
            };


            let moved = false;
            for (const candidate of tryMoves) {
                if (canMove(candidate)) {
                    stickToTopSurface(candidate);
                    player.position.copy(candidate);
                    moved = true;
                    blockedTime = 0;
                    break;
                }
            }

            // Саморазблокировка: если не удалось пройти обычной проверкой, но текущая клетка непроходима,
            // ищем ближайшее направление с уменьшением количества пересечений и прогрессом к цели
            if (!moved) {
                const currentIntersections = countIntersectionsAtPosition(player.position, 1);
                if (currentIntersections > 0) {
                    const radii = [stepDistance * 0.6, stepDistance * 1.0, stepDistance * 1.6];
                    const angles = 24; // 15° шаг
                    let bestPos = null;
                    let bestScore = currentIntersections;
                    let bestDist = Infinity;
                    const escapeHalf = 0.6; // слегка ужимаем хитбокс при выходе
                    for (const r of radii) {
                        for (let i = 0; i < angles; i++) {
                            const a = (i / angles) * Math.PI * 2;
                            const dir2 = new THREE.Vector3(Math.sin(a), 0, Math.cos(a));
                            const cand = player.position.clone().addScaledVector(dir2, r);
                            const score = countIntersectionsAtPosition(cand, escapeHalf);
                            const distToTarget = cand.distanceTo(target);
                            if (
                                score < bestScore ||
                                (score === bestScore && distToTarget < bestDist)
                            ) {
                                bestScore = score;
                                bestDist = distToTarget;
                                bestPos = cand;
                                if (bestScore === 0) break;
                            }
                        }
                        if (bestScore === 0) break;
                    }
                    if (bestPos) {
                        stickToTopSurface(bestPos);
                        player.position.copy(bestPos);
                        moved = true;
                        blockedTime = 0;
                    } else {
                        // Последняя попытка: небольшая "встряска" вверх и повторное прилипание к поверхности
                        const nudged = player.position.clone();
                        nudged.y += 0.05;
                        stickToTopSurface(nudged);
                        player.position.copy(nudged);
                    }
                }
            }

            if (moved) {
                // Плавный доворот в сторону движения, но движение идёт сразу
                const curYaw = new THREE.Euler().setFromQuaternion(player.quaternion, 'YXZ').y;
                let d = desiredYaw - curYaw;
                d = ((d + Math.PI) % (2 * Math.PI)) - Math.PI;
                const rotStep = THREE.MathUtils.clamp(d, -maxTurnRate * delta, maxTurnRate * delta);
                const newYaw = curYaw + rotStep;
                player.quaternion.setFromEuler(new THREE.Euler(0, newYaw, 0));
                socketRef.current?.emit('playerMovement', { x: player.position.x, y: player.position.y, z: player.position.z });

                if (currentAction !== walkAction) {
                    currentAction.fadeOut(0.2);
                    walkAction.reset().fadeIn(0.2).play();
                    currentAction = walkAction;
                }
            } else {
                // полностью заблокированы
                blockedTime += delta;

                // Пробуем перепроложить путь к текущей цели,
                // либо через 0.35с сдаёмся и ставим idle
                if (destination && blockedTime > 0.1) {
                    const newPath = computePath(player.position, destination);
                    if (newPath.length > 0) {
                        currentPath = newPath;
                        pathIndex = 0;
                        // оставляем walk
                        if (currentAction !== walkAction) {
                            currentAction.fadeOut(0.2);
                            walkAction.reset().fadeIn(0.2).play();
                            currentAction = walkAction;
                        }
                        return;
                    }
                }
                if (blockedTime > 0.35) {
                    currentPath = [];
                    destination = null;
                    if (currentAction !== idleAction) {
                        currentAction.fadeOut(0.2);
                        idleAction.reset().fadeIn(0.2).play();
                        currentAction = idleAction;
                    }
                    // Жёсткое выравнивание по топ-поверхности при переходе в idle
                    (function alignGroundFinal(p) {
                        const downRay = new THREE.Raycaster(
                            new THREE.Vector3(p.x, 100, p.z),
                            new THREE.Vector3(0, -1, 0),
                            0,
                            300
                        );
                        downRay.camera = cameraRef.current;
                        const walkables = [
                            ...(cityGroupRef.current ? [cityGroupRef.current] : []),
                            groundPlane,
                            ...(cityMeshesRef.current || [])
                        ].filter(Boolean);
                        const raw = downRay.intersectObjects(walkables, true);
                        const isDescendantOf = (obj, ancestor) => { let c=obj; while(c){ if(c===ancestor) return true; c=c.parent;} return false; };
                        const hits = raw
                            .filter(h => !h.object.isSprite && h.face && h.face.normal && h.face.normal.y > 0.6)
                            .filter(h => !isDescendantOf(h.object, player));
                        if (hits.length) p.y = hits[0].point.y + 0.02;
                    })(player.position);
                }
            }

            // Всегда подравниваем Y к верхней поверхности, чтобы исключить проваливания на остановке
            stickToTopSurface(player.position);
        }


        function updateTransparency() {
            if (!player) return;

            // Если мы в интерьере, не применяем прозрачность
            if (isInInteriorRef.current) return;

            obstacles.forEach(obstacle => {
                obstacle.mesh.traverse(child => {
                    if (child.isMesh && child.material) {
                        if (Array.isArray(child.material)) {
                            child.material.forEach(mat => {
                                if (!mat) return;
                                mat.transparent = false;
                                mat.opacity = 1.0;
                                mat.depthWrite = true;
                                mat.needsUpdate = true;
                            });
                        } else {
                            child.material.transparent = false;
                            child.material.opacity = 1.0;
                            child.material.depthWrite = true;
                            child.material.needsUpdate = true;
                        }
                    }
                });
            });
            const direction = new THREE.Vector3()
                .subVectors(player.position, cameraRef.current.position)
                .normalize();

            const raycaster = new THREE.Raycaster(cameraRef.current.position, direction);
            raycaster.camera = cameraRef.current; // ← ВАЖНО для спрайтов

            const camToPlayerDist = cameraRef.current.position.distanceTo(player.position);

            const obstacleMeshes = obstacles.map(ob => ob.mesh).filter(Boolean); // ← фильтр от null
            if (obstacleMeshes.length === 0) return;

            const intersects = raycaster.intersectObjects(obstacleMeshes, true);

            intersects.forEach(hit => {
                if (hit.object === player) return;
                if (hit.distance < camToPlayerDist) {
                    if (hit.object.parent === scene) {
                        if (hit.object.isMesh && hit.object.material) {
                            if (Array.isArray(hit.object.material)) {
                                hit.object.material.forEach(mat => {
                                    if (!mat) return;
                                    mat.transparent = true;
                                    mat.opacity = 0.3;
                                    mat.depthWrite = false;
                                    mat.needsUpdate = true;
                                });
                            } else {
                                hit.object.material.transparent = true;
                                hit.object.material.opacity = 0.3;
                                hit.object.material.depthWrite = false;
                                hit.object.material.needsUpdate = true;
                            }
                        }
                    } else {
                        hit.object.parent.traverse(child => {
                            if (child.isMesh && child.material) {
                                if (Array.isArray(child.material)) {
                                    child.material.forEach(mat => {
                                        if (!mat) return;
                                        mat.transparent = true;
                                        mat.opacity = 0.3;
                                        mat.depthWrite = false;
                                        mat.needsUpdate = true;
                                    });
                                } else {
                                    child.material.transparent = true;
                                    child.material.opacity = 0.3;
                                    child.material.depthWrite = false;
                                    child.material.needsUpdate = true;
                                }
                            }
                        });
                    }
                }
            });
        }

        function updateFirstPersonMovement(delta) {
            if (!isInInteriorRef.current || !player) return;

            const move = moveInputRef.current;
            const speed = 3.0; // Скорость движения в интерьере
            const rotSpeed = Math.PI * 0.5; // Скорость поворота

            // Проверка триггера выхода по внутренней точке
            if (interiorExitPosRef.current && player.position.distanceTo(interiorExitPosRef.current) < 0.7) {
                exitInterior();
                return;
            }

            // Поворот влево-вправо (A/D или стрелки)
            if (move.left) player.rotation.y += rotSpeed * delta;
            if (move.right) player.rotation.y -= rotSpeed * delta;
            
            // Камера следует за вращением тела
            const headHeight = 1.6;
            const camBase = new THREE.Vector3(player.position.x, player.position.y + headHeight, player.position.z);
            const camForward = new THREE.Vector3(0, 0, -0.08).applyEuler(new THREE.Euler(0, player.rotation.y, 0));
            fpCamRef.current.position.copy(camBase.add(camForward));
            const lookForward = new THREE.Vector3(0, 0, -1).applyEuler(new THREE.Euler(0, player.rotation.y, 0));
            fpCamRef.current.lookAt(fpCamRef.current.position.clone().add(lookForward));

            // Упрощенная система коллизий
            const tryMove = (direction) => {
                const moveDistance = speed * delta;
                const playerRadius = 0.4; // Радиус игрока
                const playerHeight = 1.6; // Высота игрока

                // Получаем коллайдеры из JSON (приоритет)
                let jsonColliders = jsonCollidersRef.current || [];
                console.log('🔍 JSON коллайдеров:', jsonColliders.length);
                
                // Если JSON коллайдеров нет, используем коллайдеры из модели
                let colliders = interiorCollidersRef.current || [];
                console.log('🔍 Модельных коллайдеров:', colliders.length);

                // Если коллайдеров нет, собираем их из группы интерьера
                if (colliders.length === 0 && interiorGroupRef.current) {
                    colliders = [];
                    interiorGroupRef.current.traverse((child) => {
                        if (child.isMesh && child.geometry && child.visible) {
                            // Пропускаем интерактивные объекты и сферы
                            if (child.userData && (child.userData.interactable || child.userData.payload)) return;
                            if (child.geometry.type === 'SphereGeometry') return;
                            colliders.push(child);
                        }
                    });
                    interiorCollidersRef.current = colliders;
                    console.log('Собрано коллайдеров интерьера:', colliders.length);
                }

                // Проверяем коллизии
                const checkCollision = (testPosition) => {
                    // Создаем AABB для игрока
                    const playerBox = new THREE.Box3();
                    const playerMin = new THREE.Vector3(
                        testPosition.x - playerRadius,
                        testPosition.y,
                        testPosition.z - playerRadius
                    );
                    const playerMax = new THREE.Vector3(
                        testPosition.x + playerRadius,
                        testPosition.y + playerHeight,
                        testPosition.z + playerRadius
                    );
                    playerBox.setFromPoints([playerMin, playerMax]);

                    // Сначала проверяем JSON коллайдеры (приоритет)
                    if (jsonColliders.length > 0) {
                        console.log('🔍 Проверяем', jsonColliders.length, 'JSON коллайдеров');
                        console.log('🔍 Player box:', playerBox.min, '->', playerBox.max);
                        
                        for (let i = 0; i < jsonColliders.length; i++) {
                            const jsonCollider = jsonColliders[i];
                            try {
                                console.log(`🔍 Проверяем коллайдер ${i}:`, jsonCollider.box.min, '->', jsonCollider.box.max);
                                
                                // Проверяем пересечение с JSON коллайдером
                                const intersects = playerBox.intersectsBox(jsonCollider.box);
                                console.log(`🔍 Результат intersectsBox: ${intersects}`);
                                
                                if (intersects) {
                                    console.log('🚫 КОЛЛИЗИЯ с JSON коллайдером', i, '!');
                                    console.log('  Player box:', playerBox.min, '->', playerBox.max);
                                    console.log('  JSON Collider box:', jsonCollider.box.min, '->', jsonCollider.box.max);
                                    return true;
                                }
                                
                                // Ручная проверка пересечения
                                const manualX = playerBox.min.x <= jsonCollider.box.max.x && playerBox.max.x >= jsonCollider.box.min.x;
                                const manualY = playerBox.min.y <= jsonCollider.box.max.y && playerBox.max.y >= jsonCollider.box.min.y;
                                const manualZ = playerBox.min.z <= jsonCollider.box.max.z && playerBox.max.z >= jsonCollider.box.min.z;
                                const manualIntersects = manualX && manualY && manualZ;
                                
                                console.log(`🔍 Ручная проверка - X: ${manualX}, Y: ${manualY}, Z: ${manualZ}, Результат: ${manualIntersects}`);
                                
                                if (manualIntersects) {
                                    console.log('🚫 РУЧНАЯ КОЛЛИЗИЯ с JSON коллайдером', i, '!');
                                    return true;
                                }
                            } catch (error) {
                                console.warn('Ошибка при проверке JSON коллизии:', error);
                                continue;
                            }
                        }
                    }

                    // Затем проверяем коллайдеры из модели (если JSON коллайдеров нет)
                    if (jsonColliders.length === 0) {
                        for (const collider of colliders) {
                            if (!collider.geometry || !collider.visible) continue;

                            try {
                                // Обновляем матрицу мира для коллайдера
                                collider.updateMatrixWorld(true);
                                
                                // Создаем Box3 для коллайдера в мировых координатах
                                const colliderBox = new THREE.Box3();
                                colliderBox.setFromObject(collider);

                                // Проверяем пересечение
                                if (playerBox.intersectsBox(colliderBox)) {
                                    console.log('🚫 КОЛЛИЗИЯ! Объект:', collider.name || 'unnamed');
                                    console.log('  Player box:', playerBox.min, '->', playerBox.max);
                                    console.log('  Collider box:', colliderBox.min, '->', colliderBox.max);
                                    return true;
                                } else {
                                    // Отладка: почему коллизия не обнаружена
                                    if (!window.collisionDebugShown) {
                                        console.log('🔍 Отладка коллизии для', collider.name || 'unnamed');
                                        console.log('  Player box:', playerBox.min, '->', playerBox.max);
                                        console.log('  Collider box:', colliderBox.min, '->', colliderBox.max);
                                        console.log('  Пересечение по X:', playerBox.min.x <= colliderBox.max.x && playerBox.max.x >= colliderBox.min.x);
                                        console.log('  Пересечение по Y:', playerBox.min.y <= colliderBox.max.y && playerBox.max.y >= colliderBox.min.y);
                                        console.log('  Пересечение по Z:', playerBox.min.z <= colliderBox.max.z && playerBox.max.z >= colliderBox.min.z);
                                        window.collisionDebugShown = true;
                                    }
                                }
                            } catch (error) {
                                console.warn('Ошибка при проверке коллизии:', error);
                                continue;
                            }
                        }
                    }
                    return false;
                };

                // Применяем движение с проверкой коллизий
                const targetPosition = player.position.clone();
                targetPosition.add(direction.clone().multiplyScalar(moveDistance));

                // Проверяем коллизии по осям отдельно для плавного движения
                let safePosition = player.position.clone();
                
                // Отладочная информация о коллайдерах (только при первом движении)
                if ((jsonColliders.length > 0 || colliders.length > 0) && !window.colliderDebugShown) {
                    console.log('🔍 Проверяем коллизии с', jsonColliders.length, 'JSON коллайдерами и', colliders.length, 'модельными коллайдерами');
                    console.log('📍 Позиция игрока:', player.position);
                    
                    // Показываем JSON коллайдеры
                    jsonColliders.forEach((col, i) => {
                        console.log(`JSON Коллайдер ${i}:`, 
                            'Min:', col.box.min, 'Max:', col.box.max, 'Size:', col.box.getSize(new THREE.Vector3()));
                        console.log(`  Игрок Y: ${player.position.y}, JSON Коллайдер Y: ${col.box.min.y} - ${col.box.max.y}`);
                    });
                    
                    // Показываем модельные коллайдеры
                    colliders.forEach((col, i) => {
                        col.updateMatrixWorld(true);
                        const box = new THREE.Box3().setFromObject(col);
                        console.log(`Модельный Коллайдер ${i}:`, col.name || 'unnamed', 
                            'Min:', box.min, 'Max:', box.max, 'Size:', box.getSize(new THREE.Vector3()));
                        console.log(`  Игрок Y: ${player.position.y}, Модельный Коллайдер Y: ${box.min.y} - ${box.max.y}`);
                    });
                    window.colliderDebugShown = true;
                }
                console.log('Исходная позиция:', safePosition);

                // Проверяем движение по X
                if (Math.abs(direction.x) > 0.001) {
                    const xTestPosition = safePosition.clone();
                    xTestPosition.x = targetPosition.x;
                    const hasCollisionX = checkCollision(xTestPosition);
                    if (!hasCollisionX) {
                        safePosition.x = targetPosition.x;
                    } else {
                        console.log('🚫 X коллизия заблокирована');
                    }
                }

                // Проверяем движение по Z
                if (Math.abs(direction.z) > 0.001) {
                    const zTestPosition = safePosition.clone();
                    zTestPosition.z = targetPosition.z;
                    const hasCollisionZ = checkCollision(zTestPosition);
                    if (!hasCollisionZ) {
                        safePosition.z = targetPosition.z;
                    } else {
                        console.log('🚫 Z коллизия заблокирована');
                    }
                }
                // Обновляем позицию игрока
                player.position.copy(safePosition);
            };

            const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(player.quaternion);
            const right = new THREE.Vector3(1, 0, 0).applyQuaternion(player.quaternion);
            
                // Применяем движение с проверкой коллизий
                if (move.forward) {
                    tryMove(forward);
                }
                if (move.backward) {
                    tryMove(forward.clone().multiplyScalar(-1));
                }
                if (move.strafeLeft) {
                    tryMove(right.clone().multiplyScalar(-1));
                }
                if (move.strafeRight) {
                    tryMove(right);
                }

            // Отправляем позицию внутри интерьера
            if (socketRef.current) {
                socketRef.current.emit('playerMovement', { x: player.position.x, y: player.position.y, z: player.position.z });
            }
        }

        function updateCameraFollow() {
            if (!player) return;

            const target = player.position.clone();
            if (cameraRef.current === fpCamRef.current) {
                const yaw = player.rotation.y;
                const pitch = fpPitchRef.current;
                const headPos = target.clone().add(new THREE.Vector3(0, 1.6, 0));
                cameraRef.current.position.copy(headPos);
                const forward = new THREE.Vector3(0, 0, -1).applyEuler(
                    new THREE.Euler(pitch, yaw, 0, 'YXZ')
                );
                cameraRef.current.lookAt(headPos.clone().add(forward));
                return;
            }

            const polar = basePolar + cameraPitchOffset;
            const planar = radius * Math.cos(polar);
            const yOff = radius * Math.sin(polar);
            // Горизонтальный угол = исходный азимут + относительный поворот (±90°)
            const azimuth = baseAzimuth0 + horizontalYaw;
            const xOff = planar * Math.cos(azimuth);
            const zOff = planar * Math.sin(azimuth);

            // Плавная интерполяция позиции камеры
            const targetPosition = new THREE.Vector3(
                target.x + xOff,
                target.y + yOff,
                target.z + zOff
            );

            cameraRef.current.position.lerp(targetPosition, 0.1);
            cameraRef.current.lookAt(target);
        }

        function animate() {
            requestAnimationFrame(animate);

            // Проверяем, что все необходимые объекты инициализированы
            if (!renderer || !scene || !cameraRef.current) {
                console.warn('Пропускаем анимацию - не все объекты инициализированы');
                return;
            }

            // Блокировка управления при потере соединения
            if (connectionLostRef.current) {
                // Останавливаем любые движения
                if (moveInputRef.current) {
                    Object.keys(moveInputRef.current).forEach(k => moveInputRef.current[k] = false);
                }
                // Скрыть маркер назначения
                if (destinationMarker) destinationMarker.visible = false;
            }

            if (!clock || typeof clock.getDelta !== 'function') {
                console.warn('Clock не инициализирован');
                return;
            }
            const delta = Math.min(clock.getDelta(), 0.1); // Ограничиваем delta для стабильности

            // Обновляем анимации
            if (mixer && typeof mixer.update === 'function') {
                mixer.update(delta);
            }

            // Обновляем движение игрока
            // В интерьере отключаем автодвижение по кликам (двигаемся только WASD)
            if (!isInInteriorRef.current && typeof updateDestinationMovement === 'function') {
                updateDestinationMovement(delta);
            }
            if (typeof updateFirstPersonMovement === 'function') {
                updateFirstPersonMovement(delta);
            }

            // Обновляем других игроков
            if (remotePlayers) {
                for (let id in remotePlayers) {
                    const r = remotePlayers[id];
                    if (r && r.model && r.targetPosition) {
                        r.model.position.lerp(r.targetPosition, 0.15); // Увеличиваем скорость интерполяции
                    }
                    if (r && r.mixer && typeof r.mixer.update === 'function') {
                        r.mixer.update(delta);
                    }
                }
            }

            // Обновляем прозрачность и видимость объектов (реже)
            if (Math.floor(Date.now() / 100) % 3 === 0) {
                if (typeof updateTransparency === 'function') {
                    updateTransparency();
                }
                if (typeof updateCityObjectVisibility === 'function') {
                    updateCityObjectVisibility();
                }
            }

            // Обновляем камеру
            if (typeof updateCameraFollow === 'function') {
                updateCameraFollow();
            }

            // Рендерим сцену
            if (renderer && scene && cameraRef.current) {
                try {
                    renderer.render(scene, cameraRef.current);
                } catch (error) {
                    console.error('Ошибка рендеринга:', error);
                    // Не освобождаем материалы здесь, чтобы не усугублять ошибку на следующих кадрах
                }
            } else {
                console.warn('Renderer, scene или camera не инициализированы:', {
                    renderer: !!renderer,
                    scene: !!scene,
                    camera: !!cameraRef.current
                });
            }
        }

        (async () => {
            await init();
            animate();
        })();

        function onWindowResize() {
            const aspect = window.innerWidth / window.innerHeight;
            if (orthoCamRef.current) {
                orthoCamRef.current.left = -200 * aspect;
                orthoCamRef.current.right = 200 * aspect;
                orthoCamRef.current.top = 200;
                orthoCamRef.current.bottom = -200;
                orthoCamRef.current.updateProjectionMatrix();
            }
            if (fpCamRef.current) {
                fpCamRef.current.aspect = aspect;
                fpCamRef.current.updateProjectionMatrix();
            }
            if (rendererRef.current) {
                rendererRef.current.setSize(window.innerWidth, window.innerHeight);
            }
        }
        window.addEventListener('resize', onWindowResize, false);
        
        // Отключаем браузерное масштабирование
        document.addEventListener('wheel', (e) => {
            if (e.ctrlKey) {
                e.preventDefault();
            }
        }, { passive: false });
        
        document.addEventListener('keydown', (e) => {
            if (e.ctrlKey && (e.key === '+' || e.key === '-' || e.key === '=')) {
                e.preventDefault();
            }
        });
        
        // Показываем подсказку об управлении камерой
        setTimeout(() => {
            showCameraControlsHint();
        }, 3000);

        return () => {
            clearInterval(balanceInterval);
            clearInterval(statusInterval);
            
            // Очищаем overlay загрузки
            if (overlayEl) {
                removeLoadingOverlay();
            }
            
            // Очищаем все таймеры overlay
            if (overlayTimeoutRef.current) {
                clearTimeout(overlayTimeoutRef.current);
            }

            // Очищаем таймеры throttling
            if (wheelTimeout) {
                clearTimeout(wheelTimeout);
                wheelTimeout = null;
            }
            if (mouseMoveTimeout) {
                clearTimeout(mouseMoveTimeout);
                mouseMoveTimeout = null;
            }

            window.removeEventListener('keydown', onKeyDown);
            window.removeEventListener('keyup', onKeyUp);
            if (renderer && renderer.domElement) {
                renderer.domElement.removeEventListener('pointerdown', onDocumentMouseDown);
                renderer.domElement.removeEventListener('wheel', onMouseWheel);
                renderer.domElement.removeEventListener('mousemove', onMouseLookMove);
            }
            document.removeEventListener('pointerlockchange');
            window.removeEventListener('resize', onWindowResize);
            if (renderer && renderer.domElement && renderer.domElement.parentNode) {
                renderer.domElement.parentNode.removeChild(renderer.domElement);
            }
            if (localStream.current) {
                localStream.current.getTracks().forEach(track => track.stop());
            }
            Object.keys(voiceConnections.current).forEach(peerId => {
                cleanupVoiceConnection(peerId);
            });
            if (interiorGroupRef.current) {
                scene.remove(interiorGroupRef.current);
                interiorGroupRef.current = null;
            }
        };
    }, []);

    const [showWorldMap, setShowWorldMap] = useState(false);
    const [cities, setCities] = useState([]);

    // Получить список городов при открытии карты мира
    async function openWorldMap() {
        setShowWorldMap(true);
        const token = localStorage.getItem('token');
        const res = await fetch('/api/cities', { headers: { Authorization: `Bearer ${token}` } });
        console.log('Ответ /api/cities:', res);
        if (res.ok) {
            const data = await res.json();
            console.log('Данные городов:', data);
            setCities(data);
        } else {
            console.warn('Ошибка загрузки городов:', res.status, res.statusText);
        }
    }

    function closeWorldMap() {
        setShowWorldMap(false);
    }

    async function handleCitySelect(cityId) {
        setShowWorldMap(false);
        // Отправляем событие на сервер
        socketRef.current?.emit('cityChange', { cityId });
        // Обновляем профиль в sessionStorage
        const token = localStorage.getItem('token');
        const res = await fetch('/api/me', { headers: { Authorization: `Bearer ${token}` } });
        if (res.ok) {
            const profile = await res.json();
            profile.last_city_id = cityId; // явно обновляем поле
            sessionStorage.setItem('user_profile', JSON.stringify(profile));
        }
        window.location.reload();
    }

    return (
        <div ref={mountRef} style={{ position: 'relative', width: '100vw', height: '100vh' }}>
            <div style={{ position: 'absolute', top: 20, left: 20, zIndex: 1000, background: 'rgba(0,0,0,0.6)', color: '#fff', padding: '4px 8px', borderRadius: 4 }}>
                Сытость: {satiety}
            </div>
            <div style={{ position: 'absolute', top: 50, left: 20, zIndex: 1000, background: 'rgba(0,0,0,0.6)', color: '#fff', padding: '4px 8px', borderRadius: 4 }}>
                Жажда: {thirst}
            </div>
            {/* HUD: сытость/жажда */}
            <div style={{
                position: 'absolute',
                left: 20, top: 20,
                display: 'flex',
                gap: 12,
                flexDirection: 'column',
                zIndex: 10000,
                width: 260,
            }}>
                {[{ label: 'Сытость', value: satiety }, { label: 'Жажда', value: thirst }].map((bar) => (
                    <div key={bar.label} style={{
                        background: 'rgba(15,15,20,0.75)',
                        borderRadius: 12,
                        padding: '10px 12px',
                        boxShadow: '0 4px 16px rgba(0,0,0,0.35)',
                        backdropFilter: 'blur(4px)',
                    }}>
                        <div style={{
                            display: 'flex', justifyContent: 'space-between',
                            fontSize: 13, color: '#B8C0CC', marginBottom: 6,
                            fontWeight: 600, letterSpacing: 0.3,
                        }}>
                            <span>{bar.label}</span>
                            <span>{Math.round(bar.value)}%</span>
                        </div>
                        <div style={{
                            height: 10,
                            borderRadius: 999,
                            background: 'rgba(255,255,255,0.08)',
                            overflow: 'hidden',
                        }}>
                            <div style={{
                                height: '100%',
                                width: `${Math.max(0, Math.min(100, bar.value))}%`,
                                borderRadius: 999,
                                // красивый градиент: зелёный → жёлтый → красный
                                background: 'linear-gradient(90deg, #22c55e, #eab308, #ef4444)',
                                transition: 'width 300ms ease',
                                boxShadow: '0 0 6px rgba(255,255,255,0.35) inset',
                            }} />
                        </div>
                    </div>
                ))}
            </div>
            <button
                style={{
                    position: 'absolute',
                    top: 20,
                    right: 250, // Измените позицию по необходимости
                    zIndex: 1000,
                    padding: '10px 18px',
                    background: '#8B4513',
                    color: 'white',
                    border: 'none',
                    borderRadius: '8px',
                    fontSize: '18px',
                    cursor: 'pointer',
                    boxShadow: '0 2px 8px rgba(0,0,0,0.15)'
                }}
                onClick={() => setShowQuests(true)}
            >
                Квесты
            </button>
            <div style={{ position: 'absolute', top: 80, left: 20, zIndex: 1000, background: 'rgba(0,0,0,0.6)', color: '#fff', padding: '4px 8px', borderRadius: 4 }}>
                Баланс: {balance}
            </div>
            <div style={{ position: 'absolute', top: 20, right: 150, zIndex: 1000, background: 'rgba(0,0,0,0.6)', color: '#fff', padding: '4px 8px', borderRadius: 4 }}>
                X: {playerCoords.x} Y: {playerCoords.y} Z: {playerCoords.z}
            </div>
            {/* Индикатор связи в правом нижнем углу */}
            <div style={{ position: 'absolute', right: 20, bottom: 20, zIndex: 10000, display: 'flex', alignItems: 'center', gap: 8,
                background: 'rgba(15,15,20,0.75)', color: '#fff', padding: '8px 10px', borderRadius: 10, backdropFilter: 'blur(4px)'}}>
                <div style={{ width: 10, height: 10, borderRadius: '50%', background: connectionLost ? '#ef4444' : (latencyMs == null ? '#f59e0b' : (latencyMs < 80 ? '#22c55e' : latencyMs < 160 ? '#eab308' : '#ef4444')) }} />
                <div style={{ fontSize: 12, opacity: 0.9 }}>
                    {connectionLost ? 'Связь: нет' : `Пинг: ${latencyMs ?? '—'} ms`}
                </div>
            </div>
            <div style={{ position: 'absolute', bottom: 20, left: 20, zIndex: 1000, background: 'rgba(0,0,0,0.6)', color: '#fff', padding: '4px 8px', borderRadius: 4 }}>
                {(() => {
                    if (!gameTime) return 'Загрузка времени...';
                    // Сервер шлёт ISO (gameTime.js -> toISOString). Отображаем игровое время (ускоренное в 8 раз)
                    const d = new Date(gameTime);
                    return d.toLocaleString();
                })()}
            </div>

            {/* Оверлей при потере соединения */}
            {connectionLost && (
                <div style={{ position: 'absolute', inset: 0, zIndex: 20000, background: 'rgba(0,0,0,0.8)', display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
                    <div style={{ background: 'rgba(20,20,25,0.95)', padding: '24px 28px', borderRadius: 12, color: '#fff', width: 420, textAlign: 'center', boxShadow: '0 12px 40px rgba(0,0,0,0.45)' }}>
                        <div style={{ fontSize: 20, fontWeight: 700, marginBottom: 10 }}>Соединение потеряно</div>
                        <div style={{ fontSize: 14, opacity: 0.9, marginBottom: 16 }}>Связь с сервером была прервана. Пожалуйста, перезайдите в игру.</div>
                        <button onClick={() => window.location.reload()} style={{
                            background: '#ef4444', border: 'none', color: '#fff', padding: '10px 14px', borderRadius: 8, cursor: 'pointer', fontWeight: 700
                        }}>Перезайти</button>
                    </div>
                </div>
            )}
            {/* Кнопка карты мира */}
            <button
                style={{
                    position: 'absolute',
                    top: 20,
                    right: 20,
                    zIndex: 1000,
                    padding: '10px 18px',
                    background: '#0047ab',
                    color: 'white',
                    border: 'none',
                    borderRadius: '8px',
                    fontSize: '18px',
                    cursor: 'pointer',
                    boxShadow: '0 2px 8px rgba(0,0,0,0.15)'
                }}
                onClick={openWorldMap}
            >
                Карта мира
            </button>

            {isInInterior && (
                <button
                    style={{
                        position: 'absolute',
                        top: 60,
                        right: 20,
                        zIndex: 1000,
                        padding: '10px 18px',
                        background: '#0047ab',
                        color: 'white',
                        border: 'none',
                        borderRadius: '8px',
                        fontSize: '18px',
                        cursor: 'pointer',
                        boxShadow: '0 2px 8px rgba(0,0,0,0.15)'
                    }}
                    onClick={exitInterior}
                >
                    Выйти
                </button>
            )}

            {isInInterior && isTouchDevice && (
                <div style={{ position: 'absolute', bottom: 20, left: 20, zIndex: 1000 }}>
                    <div style={{ display: 'grid', gridTemplateColumns: '40px 40px', gridTemplateRows: '40px 40px 40px', gap: '5px', gridTemplateAreas: "'up up' 'left right' 'down down'" }}>
                        <button style={{ gridArea: 'up' }} onTouchStart={() => startMove('forward')} onTouchEnd={() => stopMove('forward')}>↑</button>
                        <button style={{ gridArea: 'left' }} onTouchStart={() => startMove('left')} onTouchEnd={() => stopMove('left')}>←</button>
                        <button style={{ gridArea: 'right' }} onTouchStart={() => startMove('right')} onTouchEnd={() => stopMove('right')}>→</button>
                        <button style={{ gridArea: 'down' }} onTouchStart={() => startMove('backward')} onTouchEnd={() => stopMove('backward')}>↓</button>
                    </div>
                </div>
            )}

            {selectedHouse && !isInInterior && (
                <div style={{
                    position: 'absolute',
                    bottom: 20,
                    left: '50%',
                    transform: 'translateX(-50%)',
                    background: 'rgba(0,0,0,0.7)',
                    color: '#fff',
                    padding: '10px 20px',
                    borderRadius: '8px',
                    zIndex: 1000
                }}>
                    <button
                        onClick={() => enterInteriorMode(selectedHouse.id)}
                        style={{
                            fontSize: '18px',
                            padding: '8px 16px',
                            background: '#00aaff',
                            border: 'none',
                            borderRadius: '6px',
                            cursor: 'pointer'
                        }}
                    >
                        Войти в здание
                    </button>
                    <button
                        onClick={() => setSelectedHouse(null)}
                        style={{
                            marginLeft: '10px',
                            fontSize: '18px',
                            background: '#aaa',
                            border: 'none',
                            borderRadius: '6px',
                            cursor: 'pointer'
                        }}
                    >
                        Отмена
                    </button>
                </div>
            )}


            {/* Модальное окно выбора города */}
            {showWorldMap && (
                <div style={{
                    position: 'fixed',
                    top: 0,
                    left: 0,
                    width: '100vw',
                    height: '100vh',
                    background: 'rgba(0,0,0,0.5)',
                    zIndex: 2000,
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'center'
                }}>
                    <div style={{
                        background: 'white',
                        borderRadius: '16px',
                        padding: '32px',
                        minWidth: '350px',
                        boxShadow: '0 4px 24px rgba(0,0,0,0.25)'
                    }}>
                        <h2 style={{ marginTop: 0 }}>Выберите город</h2>
                        <ul style={{ listStyle: 'none', padding: 0 }}>
                            {cities.map(city => (
                                <li key={city.id} style={{ margin: '12px 0' }}>
                                    <button
                                        style={{
                                            width: '100%',
                                            padding: '12px',
                                            fontSize: '16px',
                                            borderRadius: '8px',
                                            border: '1px solid #0047ab',
                                            background: '#f1f6ff',
                                            color: '#0047ab',
                                            cursor: 'pointer',
                                            transition: 'background 0.2s'
                                        }}
                                        onClick={() => handleCitySelect(city.id)}
                                    >
                                        {city.name} ({city.country_name})
                                    </button>
                                </li>
                            ))}
                        </ul>
                        <button onClick={closeWorldMap} style={{ marginTop: 16, background: '#eee', border: 'none', borderRadius: 8, padding: '8px 16px', cursor: 'pointer' }}>Закрыть</button>
                    </div>
                </div>
            )}
            {showQuests && (
                <QuestSystem onClose={() => setShowQuests(false)} />
            )}
            {selectedHouse && (
                <div style={{
                    position: 'absolute',
                    top: 20, right: 20,
                    background: 'rgba(0,0,0,0.8)',
                    color: '#fff', padding: 16,
                    borderRadius: 8, minWidth: 220
                }}>
                    <h3 style={{ margin: 0, marginBottom: 8 }}>🏠 {selectedHouse.type}</h3>
                    <p style={{ margin: '4px 0' }}>
                        <b>ID:</b> {selectedHouse.id}
                    </p>
                    <p style={{ margin: '4px 0' }}>
                        <b>Стоимость аренды:</b> {selectedHouse.rent}
                    </p>
                    <p style={{ margin: '4px 0' }}>
                        <b>Налог:</b> {selectedHouse.tax}
                    </p>
                    <div style={{ marginTop: 12, display: 'flex', gap: 8 }}>
                        <button onClick={() => enterHouse(selectedHouse)} style={btnStyle}>Войти</button>
                        <button onClick={() => viewStats(selectedHouse)} style={btnStyle}>Статистика</button>
                        {selectedHouse.organizationId && (
                            <>
                                <button onClick={() => openOrganizationMenu(selectedHouse.organizationId)} style={btnStyle}>Меню</button>
                                <button onClick={() => openOrganizationPanel(selectedHouse.organizationId)} style={btnStyle}>Управление</button>
                            </>
                        )}
                    </div>
                </div>
            )}
            {showDialog && currentDialog && (
                <div style={{
                    position: 'fixed',
                    top: '50%',
                    left: '50%',
                    transform: 'translate(-50%, -50%)',
                    background: 'rgba(0,0,0,0.85)',
                    color: 'white',
                    padding: '20px',
                    borderRadius: '10px',
                    zIndex: 3000,
                    minWidth: '300px',
                    border: '2px solid #555',
                    display: 'flex',
                    flexDirection: 'column'
                }}>
                    <div style={{
                        display: 'flex',
                        justifyContent: 'space-between',
                        alignItems: 'center',
                        marginBottom: '15px',
                        borderBottom: '1px solid #444',
                        paddingBottom: '10px'
                    }}>
                        <div style={{ display: 'flex', alignItems: 'center' }}>
                            {currentDialog.avatar && (
                                <img
                                    src={currentDialog.avatar}
                                    alt={currentDialog.name}
                                    style={{
                                        width: '50px',
                                        height: '50px',
                                        borderRadius: '50%',
                                        marginRight: '10px',
                                        objectFit: 'cover'
                                    }}
                                />
                            )}
                            <h3 style={{ margin: 0 }}>{currentDialog.name}</h3>
                        </div>
                        <button
                            onClick={() => setShowDialog(false)}
                            style={{
                                background: 'transparent',
                                border: 'none',
                                color: 'white',
                                fontSize: '20px',
                                cursor: 'pointer'
                            }}
                        >
                            ✕
                        </button>
                    </div>

                    {currentForm ? (
                        <form onSubmit={handleFormSubmit}>
                            <h4 style={{ marginTop: 0 }}>{currentForm.title}</h4>
                            {currentForm.fields.map((field, idx) => (
                                <div key={idx} style={{ marginBottom: '15px' }}>
                                    <label style={{ display: 'block', marginBottom: '5px' }}>
                                        {field.label}
                                    </label>
                                    {field.type === 'textarea' ? (
                                        <textarea
                                            name={field.name}
                                            placeholder={field.placeholder}
                                            required={field.required}
                                            onChange={handleFormChange}
                                            style={{
                                                width: '100%',
                                                minHeight: '80px',
                                                padding: '8px',
                                                borderRadius: '4px',
                                                background: 'rgba(255,255,255,0.1)',
                                                border: '1px solid #555',
                                                color: 'white'
                                            }}
                                        />
                                    ) : (
                                        <input
                                            type={field.type}
                                            name={field.name}
                                            placeholder={field.placeholder}
                                            required={field.required}
                                            onChange={handleFormChange}
                                            style={{
                                                width: '100%',
                                                padding: '8px',
                                                borderRadius: '4px',
                                                background: 'rgba(255,255,255,0.1)',
                                                border: '1px solid #555',
                                                color: 'white'
                                            }}
                                        />
                                    )}
                                </div>
                            ))}
                            <button
                                type="submit"
                                style={{
                                    padding: '8px 16px',
                                    background: '#3a5f8d',
                                    color: 'white',
                                    border: 'none',
                                    borderRadius: '4px',
                                    cursor: 'pointer',
                                    width: '100%'
                                }}
                            >
                                {currentForm.submit_text || 'Отправить'}
                            </button>
                        </form>
                    ) : (
                        <>
                            <p style={{ marginBottom: '20px', minHeight: '60px' }}>
                                {currentDialog.dialog[dialogIndex].text}
                            </p>
                            {currentDialog.dialog[dialogIndex].answers?.length > 0 ? (
                                <div style={{
                                    display: 'flex',
                                    flexDirection: 'column',
                                    gap: '8px',
                                    marginBottom: '20px'
                                }}>
                                    {currentDialog.dialog[dialogIndex].answers.map((answer, idx) => (
                                        <button
                                            key={idx}
                                            onClick={() => handleAnswerSelect(answer)}
                                            style={{
                                                padding: '8px 16px',
                                                background: '#3a5f8d',
                                                color: 'white',
                                                border: 'none',
                                                borderRadius: '4px',
                                                cursor: 'pointer',
                                                textAlign: 'left'
                                            }}
                                        >
                                            {answer.text}
                                        </button>
                                    ))}
                                </div>
                            ) : (
                                <div style={{ display: 'flex', justifyContent: 'flex-end' }}>
                                    <button
                                        onClick={() => setShowDialog(false)}
                                        style={{
                                            padding: '8px 16px',
                                            background: '#4a76a8',
                                            color: 'white',
                                            border: 'none',
                                            borderRadius: '4px',
                                            cursor: 'pointer'
                                        }}
                                    >
                                        Закрыть
                                    </button>
                                </div>
                            )}
                        </>
                    )}
                </div>
            )}
            {selectedPlayer && (
                <div
                    ref={statsRef}
                    style={{
                        position: 'absolute',
                        top: 20, left: 20,
                        background: 'rgba(0,0,0,0.8)',
                        color: '#fff',
                        padding: 16,
                        borderRadius: 8,
                        minWidth: 260,
                        zIndex: 100
                    }}
                >
                    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                        <h3 style={{ margin: 0 }}>
                            {selectedPlayer.firstName} {selectedPlayer.lastName}
                        </h3>
                        <button
                            onClick={() => { setSelectedPlayer(null); setPlayerStats(null); }}
                            style={{
                                background: 'transparent',
                                border: 'none',
                                color: '#fff',
                                fontSize: '16px',
                                cursor: 'pointer'
                            }}
                        >
                            ✕
                        </button>
                    </div>

                    <div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
                        <button onClick={viewStats} style={btnStyle}>Посмотреть статистику</button>
                        <button style={btnStyle} onClick={() => { /* познакомиться */ }}>Познакомиться</button>
                    </div>

                    {playerStats && (
                        <div style={{ marginTop: 12, lineHeight: '1.4em' }}>
                            <p><b>Баланс:</b> {playerStats.balance}</p>
                            <p><b>Часов игры:</b> {playerStats.hoursPlayed}</p>
                            <p><b>Репутация:</b> {playerStats.reputation}</p>
                            <p><b>Телефон:</b> {playerStats.phone || '—'}</p>
                            <p><b>Спортивность:</b> {playerStats.sportiness}</p>
                            <p><b>Уровень здоровья:</b> {playerStats.healthLevel}</p>
                            <p><b>Уровень стресса:</b> {playerStats.stressLevel}</p>
                            <p><b>Болезни:</b> {playerStats.diseases?.join(', ') || 'нет'}</p>
                        </div>
                    )}
                </div>
            )}

            {orgMenu && (
                <div style={{
                    position: 'absolute',
                    top: '50%', left: '50%',
                    transform: 'translate(-50%, -50%)',
                    background: 'rgba(0,0,0,0.85)',
                    color: '#fff',
                    padding: 16,
                    borderRadius: 10,
                    minWidth: 260,
                    maxWidth: 420,
                    zIndex: 3000
                }}>
                    <h3 style={{ marginTop: 0, marginBottom: 10 }}>{orgMenu.name}</h3>

                    {/* orgMenu.menu теперь массив элементов */}
                    {(!orgMenu.menu || orgMenu.menu.length === 0) && <p>Меню пусто</p>}
                    {Array.isArray(orgMenu.menu) && orgMenu.menu.map(it => (
                        <div key={it.key} style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
                            <div>
                                <div style={{ fontWeight: 600 }}>{it.title || it.key}</div>
                                {it.price != null && <div style={{ opacity: .8, fontSize: 12 }}>{Number(it.price)} ₽</div>}
                            </div>
                            <button onClick={() => purchaseItem(orgMenu.id, it.key)}>Купить</button>
                        </div>
                    ))}

                    <div style={{ textAlign: 'right', marginTop: 10 }}>
                        <button onClick={() => setOrgMenu(null)}>Закрыть</button>
                    </div>
                </div>
            )}


            {orgPanelId && (
                <OrgControlPanel orgId={orgPanelId} onClose={() => setOrgPanelId(null)} />
            )}

            {showInventory && (
                <Inventory items={inventory} onUse={handleItemAction} />
            )}
            {selectedTransaction && (
                <div style={{
                    padding: '20px',
                    background: '#1a1a1a',
                    borderTop: '1px solid #333'
                }}>
                    <h3 style={{ marginTop: 0 }}>Детали транзакции #{selectedTransaction.id}</h3>
                    <div style={{
                        display: 'grid',
                        gridTemplateColumns: '1fr 1fr',
                        gap: '10px',
                        marginBottom: '15px'
                    }}>
                        <div><strong>Дата:</strong> {selectedTransaction.date} {selectedTransaction.time}</div>
                        <div><strong>Сумма:</strong> {selectedTransaction.amount}</div>
                        <div><strong>Назначение:</strong> {selectedTransaction.purpose || '—'}</div>
                        <div><strong>IP-адрес:</strong> {selectedTransaction.ip || 'скрыто'}</div>
                        <div><strong>Город:</strong> {selectedTransaction.city}</div>
                        <div><strong>Устройство:</strong> {selectedTransaction.device || 'скрыто'}</div>
                        <div><strong>Получатель:</strong> {selectedTransaction.recipient}</div>
                    </div>

                    {/* Подсказки для подозрительных транзакций */}
                    {selectedTransaction._isSuspicious && markedTransactions.includes(selectedTransaction.id) && (
                        <div style={{
                            padding: '10px',
                            background: '#2a1a1a',
                            borderRadius: '5px',
                            marginBottom: '15px'
                        }}>
                            <h4 style={{ marginTop: 0 }}>🔍 Обнаруженная аномалия:</h4>
                            {selectedTransaction._anomalyType === 0 && (
                                <p>Географический прыжок: транзакция из {selectedTransaction.city} всего через час после предыдущей из другого города.</p>
                            )}
                            {selectedTransaction._anomalyType === 1 && (
                                <p>Подозрительное устройство ({selectedTransaction._realDevice}) и отсутствие назначения платежа.</p>
                            )}
                            {selectedTransaction._anomalyType === 2 && (
                                <p>Многократные переводы одному получателю ({selectedTransaction.recipient}) с большими суммами.</p>
                            )}
                        </div>
                    )}

                    <div style={{ display: 'flex', gap: '10px' }}>
                        <button
                            style={{
                                background: '#3498db',
                                color: 'white',
                                border: 'none',
                                padding: '8px 15px',
                                borderRadius: '3px',
                                cursor: 'pointer'
                            }}
                            onClick={() => handleDecryptField(selectedTransaction.id, 'ip')}
                            disabled={decryptAttempts <= 0 || selectedTransaction.ip}
                        >
                            🕵️ Расшифровать IP ({decryptAttempts} осталось)
                        </button>
                        <button
                            style={{
                                background: '#3498db',
                                color: 'white',
                                border: 'none',
                                padding: '8px 15px',
                                borderRadius: '3px',
                                cursor: 'pointer'
                            }}
                            onClick={() => handleDecryptField(selectedTransaction.id, 'device')}
                            disabled={decryptAttempts <= 0 || selectedTransaction.device}
                        >
                            🕵️ Расшифровать устройство ({decryptAttempts} осталось)
                        </button>
                    </div>
                </div>
            )}

            {gameResult === 'complete' && (
                <div style={{
                    position: 'fixed',
                    top: 0,
                    left: 0,
                    width: '100vw',
                    height: '100vh',
                    background: 'rgba(0,0,0,0.9)',
                    display: 'flex',
                    alignItems: 'center',
                    justifyContent: 'center',
                    zIndex: 3000
                }}>
                    <div style={{
                        background: '#1a2a1a',
                        padding: '40px',
                        borderRadius: '10px',
                        maxWidth: '600px',
                        textAlign: 'center'
                    }}>
                        <h2 style={{ color: '#4CAF50' }}>Этап пройден!</h2>
                        <p style={{ fontSize: '18px', margin: '20px 0' }}>
                            Поздравляем! Вы успешно завершили все уровни игры "Чистка или компромат".
                        </p>
                        <p style={{ marginBottom: '30px' }}>
                            Ваши навыки анализа транзакций на высоте!
                        </p>
                        <button
                            style={{
                                background: '#2196F3',
                                color: 'white',
                                border: 'none',
                                padding: '12px 24px',
                                borderRadius: '5px',
                                fontSize: '16px',
                                cursor: 'pointer'
                            }}
                            onClick={() => {
                                setGameResult(null);
                                setShowCleanupGame(false);
                                setCurrentLevel(1); // Сброс уровня
                            }}
                        >
                            Закрыть
                        </button>
                    </div>
                </div>
            )}
            {gameResult === 'fail' && (
                <div style={{
                    position: 'fixed',
                    top: '50%',
                    left: '50%',
                    transform: 'translate(-50%, -50%)',
                    background: 'rgba(0,0,0,0.9)',
                    padding: '20px',
                    borderRadius: '10px',
                    zIndex: 3000,
                    textAlign: 'center'
                }}>
                    <h2 style={{ color: '#e74c3c' }}>Время вышло!</h2>
                    <p style={{ fontSize: '18px' }}>Вы провалили задание, попробуйте еще раз</p>
                    <p style={{ color: '#aaa' }}>Игра перезапустится через 3 секунды...</p>
                </div>
            )}
            {showCleanupGame && !gameCompleted && (
                <div style={{
                    position: 'fixed',
                    top: 0,
                    left: 0,
                    width: '100vw',
                    height: '100vh',
                    background: 'rgba(0,0,0,0.9)',
                    zIndex: 2000,
                    display: 'flex',
                    flexDirection: 'column',
                    alignItems: 'center',
                    justifyContent: 'center',
                    color: '#0f0',
                    fontFamily: 'monospace',
                    padding: '20px'
                }}>
                    <div style={{
                        width: '90%',
                        maxWidth: '1200px',
                        background: '#111',
                        border: '1px solid #333',
                        borderRadius: '5px',
                        overflow: 'hidden'
                    }}>
                        {/* Заголовок */}
                        <div style={{
                            display: 'flex',
                            justifyContent: 'space-between',
                            alignItems: 'center',
                            padding: '10px 20px',
                            background: '#222',
                            borderBottom: '1px solid #333'
                        }}>

                            <h2 style={{ margin: 0 }}>Чистка или компромат (Уровень {currentLevel})</h2>
                            <div style={{ display: 'flex', gap: '20px' }}>
                                <span>Время: {Math.floor(timeLeft / 60)}:{String(timeLeft % 60).padStart(2, '0')}</span>
                                <span>Расшифровки: {decryptAttempts}</span>
                                <span>Найдено: {suspiciousFound}/3</span>
                            </div>
                        </div>

                        {/* Комментарии Серёги */}
                        {seregaComments.length > 0 && (
                            <div style={{
                                padding: '10px',
                                background: '#1a1a1a',
                                borderBottom: '1px solid #333',
                                fontStyle: 'italic'
                            }}>
                                {seregaComments[seregaComments.length - 1].text}
                            </div>
                        )}

                        {/* Таблица транзакций */}
                        <div style={{
                            maxHeight: '60vh',
                            overflowY: 'auto'
                        }}>
                            <table style={{
                                width: '100%',
                                borderCollapse: 'collapse'
                            }}>
                                <thead>
                                    <tr style={{ background: '#1a1a1a' }}>
                                        <th style={{ padding: '10px', textAlign: 'left' }}>Дата</th>
                                        <th style={{ padding: '10px', textAlign: 'left' }}>Сумма</th>
                                        <th style={{ padding: '10px', textAlign: 'left' }}>Назначение</th>
                                        <th style={{ padding: '10px', textAlign: 'left' }}>IP-адрес</th>
                                        <th style={{ padding: '10px', textAlign: 'left' }}>Город</th>
                                        <th style={{ padding: '10px', textAlign: 'left' }}>Устройство</th>
                                        <th style={{ padding: '10px', textAlign: 'left' }}>Получатель</th>
                                        <th style={{ padding: '10px', textAlign: 'left' }}>Действия</th>
                                    </tr>
                                </thead>
                                <tbody>
                                    {cleanupGameData?.map((tx) => (
                                        <tr
                                            key={tx.id}
                                            style={{
                                                background: markedTransactions.includes(tx.id)
                                                    ? (tx._isSuspicious ? '#2a1a1a' : '#3a1a1a')
                                                    : '#1a1a1a',
                                                borderBottom: '1px solid #333',
                                                cursor: 'pointer'
                                            }}
                                            onClick={() => setSelectedTransaction(tx)}
                                        >
                                            <td style={{ padding: '10px' }}>{tx.date}</td>
                                            <td style={{ padding: '10px' }}>{tx.amount}</td>
                                            <td style={{ padding: '10px' }}>{tx.purpose || '—'}</td>
                                            <td style={{ padding: '10px' }}>{tx.ip || 'скрыто'}</td>
                                            <td style={{ padding: '10px' }}>{tx.city}</td>
                                            <td style={{ padding: '10px' }}>{tx.device || 'скрыто'}</td>
                                            <td style={{ padding: '10px' }}>{tx.recipient}</td>
                                            <td style={{ padding: '10px', display: 'flex', gap: '5px' }}>
                                                <button
                                                    style={{
                                                        background: markedTransactions.includes(tx.id)
                                                            ? (tx._isSuspicious ? '#27ae60' : '#e74c3c')
                                                            : '#333',
                                                        color: 'white',
                                                        border: 'none',
                                                        padding: '5px 10px',
                                                        borderRadius: '3px',
                                                        cursor: 'pointer'
                                                    }}
                                                    onClick={(e) => {
                                                        e.stopPropagation();
                                                        handleMarkTransaction(tx.id);
                                                    }}
                                                >
                                                    {markedTransactions.includes(tx.id) ? '✓ Помечено' : 'Пометить'}
                                                </button>
                                            </td>
                                        </tr>
                                    ))}
                                </tbody>
                            </table>
                        </div>

                        {/* Детали транзакции */}
                        {selectedTransaction && (
                            <div style={{
                                padding: '20px',
                                background: '#1a1a1a',
                                borderTop: '1px solid #333'
                            }}>
                                <h3 style={{ marginTop: 0 }}>Детали транзакции #{selectedTransaction.id}</h3>
                                <div style={{
                                    display: 'grid',
                                    gridTemplateColumns: '1fr 1fr',
                                    gap: '10px',
                                    marginBottom: '15px'
                                }}>
                                    <div><strong>Дата:</strong> {selectedTransaction.date} {selectedTransaction.time}</div>
                                    <div><strong>Сумма:</strong> {selectedTransaction.amount}</div>
                                    <div><strong>Назначение:</strong> {selectedTransaction.purpose || '—'}</div>
                                    <div><strong>IP-адрес:</strong> {selectedTransaction.ip || 'скрыто'}</div>
                                    <div><strong>Город:</strong> {selectedTransaction.city}</div>
                                    <div><strong>Устройство:</strong> {selectedTransaction.device || 'скрыто'}</div>
                                    <div><strong>Получатель:</strong> {selectedTransaction.recipient}</div>
                                </div>

                                {/* Подсказки для подозрительных транзакций */}
                                {selectedTransaction._isSuspicious && markedTransactions.includes(selectedTransaction.id) && (
                                    <div style={{
                                        padding: '10px',
                                        background: '#2a1a1a',
                                        borderRadius: '5px',
                                        marginBottom: '15px'
                                    }}>
                                        <h4 style={{ marginTop: 0 }}>🔍 Обнаруженная аномалия:</h4>
                                        {selectedTransaction._anomalyType === 0 && (
                                            <p>Географический прыжок: транзакция из {selectedTransaction.city} всего через час после предыдущей из другого города.</p>
                                        )}
                                        {selectedTransaction._anomalyType === 1 && (
                                            <p>Подозрительное устройство ({selectedTransaction._realDevice}) и отсутствие назначения платежа.</p>
                                        )}
                                        {selectedTransaction._anomalyType === 2 && (
                                            <p>Многократные переводы одному получателю ({selectedTransaction.recipient}) с большими суммами.</p>
                                        )}
                                    </div>
                                )}

                                <div style={{ display: 'flex', gap: '10px' }}>
                                    <button
                                        style={{
                                            background: '#3498db',
                                            color: 'white',
                                            border: 'none',
                                            padding: '8px 15px',
                                            borderRadius: '3px',
                                            cursor: 'pointer'
                                        }}
                                        onClick={() => handleDecryptField(selectedTransaction.id, 'ip')}
                                        disabled={decryptAttempts <= 0 || selectedTransaction.ip}
                                    >
                                        🕵️ Расшифровать IP ({decryptAttempts} осталось)
                                    </button>
                                    <button
                                        style={{
                                            background: '#3498db',
                                            color: 'white',
                                            border: 'none',
                                            padding: '8px 15px',
                                            borderRadius: '3px',
                                            cursor: 'pointer'
                                        }}
                                        onClick={() => handleDecryptField(selectedTransaction.id, 'device')}
                                        disabled={decryptAttempts <= 0 || selectedTransaction.device}
                                    >
                                        🕵️ Расшифровать устройство ({decryptAttempts} осталось)
                                    </button>
                                </div>
                            </div>
                        )}

                        {/* Результат игры */}
                        {gameResult === 'success' && (
                            <div style={{
                                margin: '20px 0',
                                textAlign: 'center',
                                fontSize: '18px'
                            }}>
                                <p>Текущий уровень: {currentLevel}</p>
                                <div style={{
                                    width: '100%',
                                    height: '20px',
                                    backgroundColor: '#333',
                                    borderRadius: '10px',
                                    margin: '10px 0'
                                }}>
                                    <div style={{
                                        width: `${(currentLevel % 5) * 20}%`,
                                        height: '100%',
                                        backgroundColor: '#4CAF50',
                                        borderRadius: '10px'
                                    }}></div>
                                </div>
                                <p>Следующий уровень загружается...</p>
                            </div>
                        )}
                    </div>
                </div>
            )}
            <DialogWindow
                currentDialog={currentDialog}
                dialogIndex={dialogIndex}
                showDialog={showDialog}
                formData={formData}
                currentForm={currentForm}
                handleAnswerSelect={handleAnswerSelect}
                handleFormSubmit={handleFormSubmit}
                handleFormChange={handleFormChange}
                setShowDialog={setShowDialog}
            />
            {selectedPlayer && (
                <div
                    ref={statsRef}
                    style={{
                        position: 'absolute',
                        top: 20, left: 20,
                        background: 'rgba(0,0,0,0.8)',
                        color: '#fff',
                        padding: 16,
                        borderRadius: 8,
                        minWidth: 260,
                        zIndex: 100
                    }}
                >
                    <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
                        <h3 style={{ margin: 0 }}>
                            {selectedPlayer.firstName} {selectedPlayer.lastName}
                        </h3>
                        <button
                            onClick={() => { setSelectedPlayer(null); setPlayerStats(null); }}
                            style={{
                                background: 'transparent',
                                border: 'none',
                                color: '#fff',
                                fontSize: '16px',
                                cursor: 'pointer'
                            }}
                        >
                            ✕
                        </button>
                    </div>

                    <div style={{ display: 'flex', gap: 8, marginTop: 8 }}>
                        <button onClick={viewStats} style={btnStyle}>Посмотреть статистику</button>
                        <button style={btnStyle} onClick={() => { /* познакомиться */ }}>Познакомиться</button>
                    </div>

                    {playerStats && (
                        <div style={{ marginTop: 12, lineHeight: '1.4em' }}>
                            <p><b>Баланс:</b> {playerStats.balance}</p>
                            <p><b>Часов игры:</b> {playerStats.hoursPlayed}</p>
                            <p><b>Репутация:</b> {playerStats.reputation}</p>
                            <p><b>Телефон:</b> {playerStats.phone || '—'}</p>
                            <p><b>Спортивность:</b> {playerStats.sportiness}</p>
                            <p><b>Уровень здоровья:</b> {playerStats.healthLevel}</p>
                            <p><b>Уровень стресса:</b> {playerStats.stressLevel}</p>
                            <p><b>Болезни:</b> {playerStats.diseases?.join(', ') || 'нет'}</p>
                        </div>
                    )}
                </div>
            )}
            {showMiniGame && (
                <div style={{
                    position: 'fixed',
                    top: 0,
                    left: 0,
                    width: '100vw',
                    height: '100vh',
                    background: 'rgba(0,0,0,0.95)',
                    zIndex: 2000,
                    display: 'flex',
                    flexDirection: 'column',
                    alignItems: 'center',
                    justifyContent: 'center',
                    fontFamily: '"Courier New", monospace',
                    color: '#0f0',
                    backdropFilter: 'blur(5px)'
                }}>
                    {/* Terminal-like header */}
                    <div style={{
                        width: '90%',
                        maxWidth: '800px',
                        background: '#111',
                        borderTopLeftRadius: '10px',
                        borderTopRightRadius: '10px',
                        padding: '10px 20px',
                        display: 'flex',
                        justifyContent: 'space-between',
                        alignItems: 'center',
                        borderBottom: '1px solid #333'
                    }}>
                        <div style={{ display: 'flex', alignItems: 'center' }}>
                            <div style={{
                                width: '12px',
                                height: '12px',
                                borderRadius: '50%',
                                background: '#ff5f56',
                                marginRight: '8px'
                            }}></div>
                            <div style={{
                                width: '12px',
                                height: '12px',
                                borderRadius: '50%',
                                background: '#ffbd2e',
                                marginRight: '8px'
                            }}></div>
                            <div style={{
                                width: '12px',
                                height: '12px',
                                borderRadius: '50%',
                                background: '#27c93f'
                            }}></div>
                            <span style={{ marginLeft: '15px', color: '#ccc' }}>terminal — hack_system</span>
                        </div>
                        <button
                            onClick={() => {
                                setShowMiniGame(false);
                                setPasswordCorrect(false);
                                setAudioUrl("/audio/firs.ogg");
                            }}
                            style={{
                                background: 'transparent',
                                border: 'none',
                                color: '#ccc',
                                fontSize: '18px',
                                cursor: 'pointer'
                            }}
                        >
                            ✕
                        </button>
                    </div>

                    {/* Main terminal content */}
                    <div style={{
                        width: '90%',
                        maxWidth: '800px',
                        height: '60vh',
                        background: 'rgba(0, 20, 0, 0.2)',
                        padding: '20px',
                        overflowY: 'auto',
                        border: '1px solid #0a0',
                        boxShadow: '0 0 20px rgba(0, 255, 0, 0.1)',
                        position: 'relative'
                    }}>
                        {/* Terminal text */}
                        <div style={{ marginBottom: '20px' }}>
                            <p style={{ color: '#0f0', margin: '5px 0' }}>
                                <span style={{ color: '#0af' }}>user@hack-system:</span>~
                                <span style={{ color: '#0f0' }}>$</span> sudo access mainframe
                            </p>
                            <p style={{ color: '#f50', margin: '5px 0' }}>
                                [sudo] password for user: ********
                            </p>
                            <p style={{ color: '#0f0', margin: '5px 0' }}>
                                <span style={{ color: '#0af' }}>user@hack-system:</span>~
                                <span style={{ color: '#0f0' }}>$</span> Trying to bypass security...
                            </p>
                        </div>

                        {/* Waveform visualization */}
                        <div style={{
                            width: '100%',
                            height: '100px',
                            background: 'rgba(0, 30, 0, 0.3)',
                            margin: '20px 0',
                            border: '1px solid #0a0',
                            display: 'flex',
                            alignItems: 'center',
                            justifyContent: 'center'
                        }}>
                            <WaveformPlayer
                                url={audioUrl}
                                playing={isPlaying}
                                width={600}
                                height={80}
                                waveColor="#0f0"
                                progressColor="#0a0"
                                cursorColor="#0f0"
                            />
                        </div>

                        {/* Serega's comment */}
                        <div style={{
                            padding: '10px',
                            background: 'rgba(0, 40, 0, 0.3)',
                            borderLeft: '3px solid #0f0',
                            margin: '20px 0'
                        }}>
                            <p style={{ color: '#ff0', margin: '0', fontStyle: 'italic' }}>
                                <span style={{ color: '#0af' }}>SEREGA_PIRAT:</span>
                                {seregaComments.length > 0 ? seregaComments[seregaComments.length - 1].text : "Ну чё, хакер, разберёшься?"}
                            </p>
                        </div>

                        {/* Password options */}
                        <div style={{ marginTop: '30px' }}>
                            <p style={{ color: '#0f0', marginBottom: '10px' }}>
                                Available password fragments:
                            </p>
                            <div style={{
                                display: 'grid',
                                gridTemplateColumns: 'repeat(2, 1fr)',
                                gap: '10px',
                                marginBottom: '20px'
                            }}>
                                {passwordCorrect ? (
                                    programmingLanguages.map((lang, index) => (
                                        <div key={index} style={{
                                            padding: '10px',
                                            background: 'rgba(0, 50, 0, 0.3)',
                                            border: '1px solid #0a0',
                                            borderRadius: '5px',
                                            textAlign: 'center',
                                            cursor: 'pointer',
                                            transition: 'all 0.2s',
                                            ':hover': {
                                                background: 'rgba(0, 80, 0, 0.5)',
                                                boxShadow: '0 0 10px rgba(0, 255, 0, 0.3)'
                                            }
                                        }}>
                                            {lang}
                                        </div>
                                    ))
                                ) : (
                                    ['ab3 Df7 Gh9', 'Q1 wE4 rT6', 'mN8 2kP 5zX', 'L0 p09 vB7'].map((item, index) => (
                                        <div key={index} style={{
                                            padding: '10px',
                                            background: 'rgba(0, 50, 0, 0.3)',
                                            border: '1px solid #0a0',
                                            borderRadius: '5px',
                                            textAlign: 'center'
                                        }}>
                                            {item}
                                        </div>
                                    ))
                                )}
                            </div>

                            {/* Password input */}
                            <div style={{ position: 'relative' }}>
                                <span style={{ color: '#0f0' }}>Enter password:</span>
                                <input
                                    type="text"
                                    placeholder="Type here and press Enter..."
                                    onKeyDown={handlePasswordInput}
                                    style={{
                                        width: '100%',
                                        padding: '10px',
                                        marginTop: '5px',
                                        background: 'rgba(0, 0, 0, 0.5)',
                                        border: '1px solid #0a0',
                                        color: '#0f0',
                                        fontFamily: '"Courier New", monospace',
                                        fontSize: '16px',
                                        outline: 'none'
                                    }}
                                />
                                <div style={{
                                    position: 'absolute',
                                    bottom: '-20px',
                                    right: '0',
                                    color: '#888',
                                    fontSize: '12px'
                                }}>
                                    Hint: Try common passwords first
                                </div>
                            </div>
                        </div>
                    </div>

                    {/* Controls */}
                    <div style={{
                        width: '90%',
                        maxWidth: '800px',
                        display: 'flex',
                        justifyContent: 'space-between',
                        padding: '15px 20px',
                        background: '#111',
                        borderBottomLeftRadius: '10px',
                        borderBottomRightRadius: '10px',
                        borderTop: '1px solid #333'
                    }}>
                        <button
                            onClick={() => setIsPlaying(!isPlaying)}
                            style={{
                                padding: '8px 15px',
                                background: isPlaying ? '#f50' : '#0a0',
                                border: 'none',
                                borderRadius: '5px',
                                color: '#fff',
                                cursor: 'pointer',
                                fontFamily: '"Courier New", monospace'
                            }}
                        >
                            {isPlaying ? 'Pause Sound' : 'Play Sound'}
                        </button>
                        <div style={{ color: '#888' }}>
                            Status: {passwordCorrect ? 'ACCESS GRANTED' : 'ACCESS DENIED'}
                        </div>
                    </div>
                </div>
            )}
            {orgMenu && (
                <div style={{
                    position: 'absolute',
                    top: 20,
                    right: 20,
                    background: 'rgba(0,0,0,0.8)',
                    color: '#fff',
                    padding: 16,
                    borderRadius: 8,
                    minWidth: 220
                }}>
                    <h3 style={{ margin: 0, marginBottom: 8 }}>{orgMenu.name}</h3>
                    {orgMenu.menu && Object.keys(orgMenu.menu).map(key => (
                        <div key={key} style={{ marginBottom: 8 }}>
                            <span>{orgMenu.menu[key].title} — {orgMenu.menu[key].price}₽</span>
                            <button onClick={() => buyItem(key)} style={{ marginLeft: 8 }}>Купить</button>
                        </div>
                    ))}
                    <button onClick={() => setOrgMenu(null)} style={{ marginTop: 8 }}>Закрыть</button>
                </div>
            )}
            <DoubleTapWrapper
                onDoubleTap={() => setIsChatVisible(false)}
                onTap={() => { if (!isChatVisible) setIsChatVisible(true); }}
            >
                <div
                    style={{
                        position: 'absolute',
                        top: '20px',
                        left: '20px',
                        width: '25%',
                        height: '5%',
                        padding: '10px',
                        borderRadius: '15px',
                        fontSize: '14px',
                        zIndex: 10,
                        opacity: isChatVisible ? 1 : 0,
                        transition: 'opacity 0.3s ease',
                        // Разрешаем клики даже когда невидим
                        pointerEvents: 'auto',
                        // Прозрачная область для кликов когда скрыт
                        cursor: isChatVisible ? 'default' : 'pointer'
                    }}
                    onDoubleClick={() => setIsChatVisible(false)}
                    onClick={() => {
                        if (!isChatVisible) {
                            setIsChatVisible(true);
                        }
                    }
                    }
                >
                    <div style={{
                        display: 'flex',
                        justifyContent: 'space-between',
                        alignItems: 'center',
                        marginBottom: '10px'
                    }}>
                        <span>Голосовой чат: {micEnabled ? 'Вкл' : 'Выкл'}</span>
                        <button
                            onClick={toggleMicrophone}
                            style={{
                                ...btnStyle,
                                background: micEnabled ? '#dc3545' : '#28a745'
                            }}
                        >
                            {micEnabled ? 'Выключить микрофон' : 'Включить микрофон'}
                        </button>
                    </div>
                    <div id="chatMessages" style={{
                        height: '150px',
                        overflowY: 'auto',
                        padding: '5px',
                        borderRadius: '10px',
                        color: 'white'
                    }}>
                    </div>
                </div>
            </DoubleTapWrapper>

            <DoubleTapWrapper
                onDoubleTap={() => setIsChatVisible(false)}
                onTap={() => { if (!isChatVisible) setIsChatVisible(true); }}
            >
                <div
                    style={{
                        position: 'absolute',
                        bottom: '20px',
                        left: '20px',
                        width: '25%',
                        height: '5%',
                        padding: '10px',
                        borderRadius: '15px',
                        fontSize: '14px',
                        zIndex: 10,
                        opacity: isChatVisible ? 1 : 0,
                        transition: 'opacity 0.3s ease',
                        // Разрешаем клики даже когда невидим
                        pointerEvents: 'auto',
                        // Прозрачная область для кликов когда скрыт
                        cursor: isChatVisible ? 'default' : 'pointer'
                    }}
                    onDoubleClick={() => setIsChatVisible(false)}
                    onClick={() => {
                        if (!isChatVisible) {
                            setIsChatVisible(true);
                        }
                    }
                    }
                >
                    <input
                        id="chatInput"
                        type="text"
                        placeholder="Введите сообщение..."
                        style={{
                            width: '65%',
                            padding: '5px',
                            position: 'relative',
                            left: '10px',
                            bottom: '5%',
                            opacity: '50%'
                        }}
                        onKeyDown={(e) => {
                            if (e.key === 'Enter') {
                                const msg = e.target.value.trim();
                                if (msg) {
                                    socketRef.current?.emit('chatMessage', {
                                        message: msg,
                                        name: mountRef.current
                                    });
                                    console.log('отправил', msg);
                                    e.target.value = '';
                                }
                            }
                        }}
                    />
                </div>
            </DoubleTapWrapper>
            {/*Телефон*/}
            <DoubleTapWrapper
                onDoubleTap={() => setIsPhoneVisible(false)}
                onTap={() => { if (!isPhoneVisible) setIsPhoneVisible(true); }}
            >
                <div
                    style={{
                        position: "absolute",
                        bottom: "20px",
                        right: "20px",
                        background: "linear-gradient(#e66465, #9198e5)",
                        width: "200px",
                        aspectRatio: "10 / 19.5",
                        borderRadius: "1.5em",
                        border: "0.5em solid black",
                        overflow: "hidden",
                        zIndex: 100,
                        display: "flex",
                        flexDirection: "column",
                        justifyContent: "space-between",
                        opacity: isPhoneVisible ? 1 : 0,
                        transition: 'opacity 0.3s ease',
                        // Разрешаем клики даже когда невидим
                        pointerEvents: 'auto',
                        // Прозрачная область для кликов когда скрыт
                        cursor: isPhoneVisible ? 'default' : 'pointer'
                    }}
                    onDoubleClick={() => setIsPhoneVisible(false)}
                    onClick={() => {
                        if (!isPhoneVisible) {
                            setIsPhoneVisible(true);
                        }
                    }
                    }
                >
                    {/* Содержимое телефона */}
                    <div style={{ flex: 1, position: "relative", pointerEvents: isPhoneVisible ? 'auto' : 'none' }}>
                        {!appsHidden ? (
                            // Иконки приложений
                            <div className="app-grid" style={{
                                display: "grid",
                                gridTemplateColumns: "repeat(3, 1fr)",
                                gap: "0.5em",
                                padding: "0.5em"
                            }}>
                                {[
                                    { src: "https://cdn-icons-png.flaticon.com/512/174/174855.png", alt: "YouTube", app: "YouTube" },
                                    { src: "https://cdn-icons-png.flaticon.com/512/732/732200.png", alt: "Gmail", app: "Gmail" },
                                    { src: "https://cdn-icons-png.flaticon.com/512/1828/1828864.png", alt: "Камера", app: "Camera" },
                                    { src: "https://cdn.iconscout.com/icon/free/png-512/free-telegram-logo-icon-download-in-svg-png-gif-file-formats--social-media-brand-pack-logos-icons-3073750.png?f=webp&w=512", alt: "Telegram", app: "Telegram" },
                                    { src: "https://cdn-icons-png.flaticon.com/512/732/732200.png", alt: "Gmail" },
                                    { src: "https://cdn-icons-png.flaticon.com/512/2111/2111398.png", alt: "Instagram" },
                                    { src: "https://cdn-icons-png.flaticon.com/512/732/732228.png", alt: "Google Drive" },
                                    { src: "https://cdn-icons-png.flaticon.com/512/732/732190.png", alt: "Chrome", app: "Chrome" },
                                    { src: "https://cdn-icons-png.flaticon.com/512/270/270798.png", alt: "Settings", app: "Settings" },
                                    {
                                        src: "https://cdn-icons-png.flaticon.com/512/1828/1828817.png",
                                        alt: "Phone",
                                        app: "Phone"
                                    },
                                    { src: "https://cdn-icons-png.flaticon.com/512/1828/1828864.png", alt: "Камера" },
                                    { src: "https://cdn-icons-png.flaticon.com/512/1828/1828911.png", alt: "Gallery" },
                                    { src: "https://cdn-icons-png.flaticon.com/512/1828/1828970.png", alt: "Music" },
                                    { src: "https://cdn-icons-png.flaticon.com/512/1828/1828961.png", alt: "Notes" },
                                    { src: "https://cdn-icons-png.flaticon.com/512/1828/1828843.png", alt: "Clock" },
                                    { src: "https://cdn-icons-png.flaticon.com/512/1828/1828998.png", alt: "Files" }
                                ].map((app, index) => (
                                    <button
                                        key={index}
                                        style={{
                                            width: "100%",
                                            aspectRatio: "1 / 1",
                                            borderRadius: "0.5em",
                                            border: "none",
                                            backgroundImage: `url(${app.src})`,
                                            backgroundSize: "contain",
                                            backgroundPosition: "center",
                                            backgroundRepeat: "no-repeat",
                                            cursor: "pointer"
                                        }}
                                        aria-label={app.alt}
                                        onClick={() => handleAppClick(app.app)}
                                    />
                                ))}
                            </div>
                        ) : (
                            // Псевдо-сайт
                            <div style={{
                                position: "absolute",
                                top: 0,
                                left: 0,
                                padding: "1em",
                                width: "100%",
                                height: "93.175%",
                                background: "#fff",
                                color: "#000",
                                overflowY: "auto",
                                fontSize: "10px",
                                lineHeight: "1.4"
                            }}>
                                <div style={{ marginBottom: "1em", fontWeight: "bold" }}>{activeApp}</div>
                                {activeApp === "YouTube" && (
                                    <div style={bodyStyle}>
                                        <header style={headerStyle}>
                                            <h1>Недвижимость в Санкт-Петербурге</h1>
                                            <p>Лучшие предложения прямо сейчас</p>
                                        </header>
                                        <main style={mainStyle}>
                                            <div style={listingStyle}>
                                                <img
                                                    src="https://yandex-images.clstorage.net/V5t2lR153/5b1b76_Cs6Z/J2fT6H2GNMqQp5pP1PgV1n2hU6uO-QeqmIIO5oUFJLYGmDdlCheTdwp3Fes87_2cZGawZZUtHoYEDrfWOBlbiuYjgPmtwWLeQiBPTdQ5VVEq8ZfsmHgQ7AgVGTbHR7J3R1e4bddLCTyQvMi04j_pSmQy9iMF_IUd1JkuWinczlhhK1WtM5byh965VsSTMNfWbyFXJR71HOMX0Rw31Y_p6pfcemgeRsf2335F-O3zoYSuPrl1TTeCksKfLpcukMeRISgY6HjUd0NRNRyK1_QfkCfrkiYVc5oglB6Xt9-MYaLXmjWjFccHcRa2yvqouCvFazm99gHwwxdOGGMWIFgClmkiWaZ8EzXHnrUfhB24kdXm6F6qkDrZ5FiVEz5Uh2ipkFD0ZFNHwrRY88t4LbXqxOl9PrrANY-TgZnplSDSDJchKllhNdTzzdF2VYSduBHY465UYpy6EWqZ2NO51YUpKl3QvOPViYP-mHyNuupxbUPtv3V0TLPD1kAQ518omM_eJGCXoDpUMoFUNNlF1PHY0Ssg0aVVsF3h3xNeN9qKrqNR3faslMZOt9Z8jTtgu-gE5PH1vIh8TN8H3Cle5tPEWCRuW-553fpCnbfSTBR8FhujqZzqm7Dbq9QYkPBfh6evnhU6rl4HhbXRfkR26HpiAK7ydLMMcElZxV9unC3Zy1Tjq1Nu8ZU7i9c1HsXcvZ0aJWRRJJC5E-2b29H3GkjtZ5ibdy-eSAI30LbN-CT56cqlfzoyinGA2Y2ZoFutGUsXqiSdrXPecY5XfJnMU79enmUnUS1Q-VKgVFxZ-5LB56rfkXXsX0UHux0wwj8g9yrKofW0M4j3T5NCmqsdaVwMGCbpVqX0mTOL0Lfdwdyw0FovaJ9j3HbVqtVWXPMUACVh1Z757FJHzDyaO8I64LVpjGQ3PTSK9A4bhlap2igVChZqbxli91w-ipgxXcPWPBoTL-pRqBg3He7UUN_zms"
                                                    alt="Квартира у метро"
                                                    style={imageStyle}
                                                />
                                                <h3 style={listingTitleStyle}>2-комнатная квартира у метро</h3>
                                                <p>Площадь: 58 м² | Цена: 9 500 000 ₽</p>
                                            </div>
                                            <div style={listingStyle}>
                                                <img
                                                    src="https://img.gta5-mods.com/q95/images/beach-apartment/69814f-GTA5%202016-03-06%2023-11-55-41.png"
                                                    alt="ЖК Комфорт"
                                                    style={imageStyle}
                                                />
                                                <p>Студия 28 м² | Цена: 5 800 000 ₽</p>
                                            </div>
                                        </main>
                                    </div>
                                )}
                                {activeApp === "Gmail" && (
                                    <div>
                                        <p>📧 Входящие:</p>
                                        <ul>
                                            <li><b>От:</b> Папа — "Где ты гуляешь?"</li>
                                            <li><b>От:</b> Курьер — "Ваш заказ доставлен"</li>
                                            <li><b>От:</b> Izя — "Ты идешь сегодня?" ❤️</li>
                                        </ul>
                                    </div>
                                )}
                                {activeApp === "Camera" && (
                                    <div style={bodyStyle}>
                                        <header style={headerStyle}>
                                            <h1>Недвижимость в Санкт-Петербурге</h1>
                                            <p>Лучшие предложения прямо сейчас</p>
                                        </header>
                                        <main style={mainStyle}>
                                            <div style={listingStyle}>
                                                <img
                                                    src="https://yandex-images.clstorage.net/V5t2lR153/5b1b76_Cs6Z/J2fT6H2GNMqQp5pP1PgV1n2hU6uO-QeqmIIO5oUFJLYGmDdlCheTdwp3Fes87_2cZGawZZUtHoYEDrfWOBlbiuYjgPmtwWLeQiBPTdQ5VVEq8ZfsmHgQ7AgVGTbHR7J3R1e4bddLCTyQvMi04j_pSmQy9iMF_IUd1JkuWinczlhhK1WtM5byh965VsSTMNfWbyFXJR71HOMX0Rw31Y_p6pfcemgeRsf2335F-O3zoYSuPrl1TTeCksKfLpcukMeRISgY6HjUd0NRNRyK1_QfkCfrkiYVc5oglB6Xt9-MYaLXmjWjFccHcRa2yvqouCvFazm99gHwwxdOGGMWIFgClmkiWaZ8EzXHnrUfhB24kdXm6F6qkDrZ5FiVEz5Uh2ipkFD0ZFNHwrRY88t4LbXqxOl9PrrANY-TgZnplSDSDJchKllhNdTzzdF2VYSduBHY465UYpy6EWqZ2NO51YUpKl3QvOPViYP-mHyNuupxbUPtv3V0TLPD1kAQ518omM_eJGCXoDpUMoFUNNlF1PHY0Ssg0aVVsF3h3xNeN9qKrqNR3faslMZOt9Z8jTtgu-gE5PH1vIh8TN8H3Cle5tPEWCRuW-553fpCnbfSTBR8FhujqZzqm7Dbq9QYkPBfh6evnhU6rl4HhbXRfkR26HpiAK7ydLMMcElZxV9unC3Zy1Tjq1Nu8ZU7i9c1HsXcvZ0aJWRRJJC5E-2b29H3GkjtZ5ibdy-eSAI30LbN-CT56cqlfzoyinGA2Y2ZoFutGUsXqiSdrXPecY5XfJnMU79enmUnUS1Q-VKgVFxZ-5LB56rfkXXsX0UHux0wwj8g9yrKofW0M4j3T5NCmqsdaVwMGCbpVqX0mTOL0Lfdwdyw0FovaJ9j3HbVqtVWXPMUACVh1Z757FJHzDyaO8I64LVpjGQ3PTSK9A4bhlap2igVChZqbxli91w-ipgxXcPWPBoTL-pRqBg3He7UUN_zms"
                                                    alt="Квартира у метро"
                                                    style={imageStyle}
                                                />
                                                <h3 style={listingTitleStyle}>2-комнатная квартира у метро</h3>
                                                <p>Площадь: 58 м² | Цена: 9 500 000 ₽</p>
                                            </div>
                                            <div style={listingStyle}>
                                                <img
                                                    src="https://img.gta5-mods.com/q95/images/beach-apartment/69814f-GTA5%202016-03-06%2023-11-55-41.png"
                                                    alt="ЖК Комфорт"
                                                    style={imageStyle}
                                                />
                                                <p>Студия 28 м² | Цена: 5 800 000 ₽</p>
                                            </div>
                                        </main>
                                    </div>
                                )}

                                {activeApp === "Chrome" && (
                                    <div style={bodyStyle}>
                                        <header style={headerStyle}>
                                            <h1>Прогресс квестов</h1>
                                        </header>
                                        <main style={mainStyle}>
                                            {questsProgress.length === 0 ? (
                                                <p>Нет активных квестов</p>
                                            ) : (
                                                questsProgress.map(quest => (
                                                    <div key={quest.id} style={listingStyle}>
                                                        <h3 style={listingTitleStyle}>{quest.title}</h3>
                                                        <div style={{
                                                            width: '100%',
                                                            height: '20px',
                                                            backgroundColor: '#e0e0e0',
                                                            borderRadius: '10px',
                                                            margin: '10px 0'
                                                        }}>
                                                            <div style={{
                                                                width: `${quest.progress}%`,
                                                                height: '100%',
                                                                backgroundColor: quest.progress === 100 ? '#4CAF50' : '#2196F3',
                                                                borderRadius: '10px',
                                                                transition: 'width 0.3s ease'
                                                            }}></div>
                                                        </div>
                                                        <p>Выполнено: {quest.completed} из {quest.total} ({quest.progress}%)</p>
                                                    </div>
                                                ))
                                            )}
                                        </main>
                                    </div>
                                )}

                                {activeApp === "Telegram" && (
                                    <div style={{ width: '100%', height: '100%', display: 'flex', flexDirection: 'column', background: '#fff' }}>
                                        {/* Заголовок приложения */}
                                        <div style={{ padding: '8px 12px', background: '#0088cc', color: '#fff', fontWeight: 700, textAlign: 'center' }}>Shipgram</div>
                                        {/* Контент */}
                                        <div style={{ flex: 1, display: 'flex', minHeight: 0 }}>
                                            {/* Список контактов */}
                                            <div style={{ width: isPhoneNarrow ? (activeChat ? '0%' : '100%') : '30%', display: isPhoneNarrow && activeChat ? 'none' : 'block', borderRight: '1px solid #ddd', overflowY: 'auto', background: '#fff' }}>
                                                <div style={{ padding: 10, fontWeight: 600, borderBottom: '1px solid #eee' }}>Контакты</div>
                                                {tgLoading && (
                                                    <div style={{ padding: 12, color: '#666' }}>Загрузка…</div>
                                                )}
                                                {tgError && (
                                                    <div style={{ padding: 12, color: '#b91c1c' }}>{tgError}</div>
                                                )}
                                                {!tgLoading && !tgError && telegramContacts.length === 0 && (
                                                    <div style={{ padding: 12, color: '#666' }}>Контакты не найдены</div>
                                                )}
                                                {telegramContacts.map((user) => (
                                                    <div key={user.id} onClick={() => setActiveChat(user)} style={{ padding: '10px 12px', display: 'flex', alignItems: 'center', gap: 8, cursor: 'pointer', color: '#111' }}>
                                                        <div style={{ position: 'relative' }}>
                                                        <div style={{ width: 28, height: 28, borderRadius: 14, background: '#e5e7eb', display: 'flex', alignItems: 'center', justifyContent: 'center', fontSize: 12 }}>
                                                            {user.firstName?.[0]}{user.lastName?.[0]}
                                                            </div>
                                                            {user.isOnline && (
                                                                <div style={{
                                                                    position: 'absolute',
                                                                    bottom: 0,
                                                                    right: 0,
                                                                    width: 10,
                                                                    height: 10,
                                                                    borderRadius: 5,
                                                                    background: '#10b981',
                                                                    border: '2px solid #fff',
                                                                    boxShadow: '0 0 0 1px #e5e7eb'
                                                                }} />
                                                            )}
                                                        </div>
                                                        <div style={{ overflow: 'hidden', flex: 1 }}>
                                                            <div style={{ 
                                                                whiteSpace: 'nowrap', 
                                                                textOverflow: 'ellipsis', 
                                                                overflow: 'hidden',
                                                                fontWeight: user.unreadCount > 0 ? 'bold' : 'normal'
                                                            }}>
                                                                {`${user.firstName || ''} ${user.lastName || ''}`}
                                                            </div>
                                                            <div style={{ 
                                                                fontSize: 12, 
                                                                color: user.isOnline ? '#10b981' : '#6b7280',
                                                                fontWeight: user.isOnline ? 600 : 400
                                                            }}>
                                                                {user.isOnline ? 'Онлайн' : (
                                                                    user.lastSeen ? 
                                                                    `Был(а) ${new Date(user.lastSeen).toLocaleString('ru-RU', {
                                                                        month: 'short',
                                                                        day: 'numeric',
                                                                        hour: '2-digit',
                                                                        minute: '2-digit'
                                                                    })}` : 
                                                                    'Офлайн'
                                                                )}
                                                            </div>
                                                        </div>
                                                        {/* Счетчик непрочитанных сообщений */}
                                                        {user.unreadCount > 0 && (
                                                            <div style={{
                                                                background: '#ef4444',
                                                                color: 'white',
                                                                borderRadius: '50%',
                                                                minWidth: '20px',
                                                                height: '20px',
                                                                display: 'flex',
                                                                alignItems: 'center',
                                                                justifyContent: 'center',
                                                                fontSize: '12px',
                                                                fontWeight: 'bold',
                                                                padding: '2px'
                                                            }}>
                                                                {user.unreadCount > 99 ? '99+' : user.unreadCount}
                                                            </div>
                                                        )}
                                                    </div>
                                                ))}
                                            </div>
                                                {/* Область чата */}
                                                <div style={{ flex: 1, display: isPhoneNarrow && !activeChat ? 'none' : 'flex', flexDirection: 'column', background: '#fff', overflowX: 'hidden', overflowY: 'auto' }}>
                                                    {activeChat && (
                                                        <>
                                                            <div style={{ padding: '8px 12px', borderBottom: '1px solid #eee', display: 'flex', alignItems: 'center', gap: 8 }}>
                                                                {isPhoneNarrow && (
                                                                    <button onClick={() => setActiveChat(null)} style={{ border: 'none', background: 'transparent', fontSize: 16, cursor: 'pointer' }}>←</button>
                                                                )}
                                                                <span style={{ fontWeight: 600 }}>{activeChat.firstName} {activeChat.lastName}</span>
                                                            </div>
                                                            <div id="chatContainer" style={{ flex: 1, overflowY: 'auto', padding: 10, background: '#fafafa' }}>
                                                                {console.log("UserProfile ID:", activeChat.id, "Type:", typeof activeChat.id)}
                                                                {console.log("First message sender_id:", messages[0]?.sender_id, "Type:", typeof messages[0]?.sender_id)}
                                                                {messages.length === 0 ? (
                                                                    <p style={{ textAlign: 'center', color: '#666' }}>Нет сообщений</p>
                                                                ) : (
                                                                    messages.map(msg => (
                                                                        <div key={msg.id} style={{ display: 'flex', justifyContent: (Number(msg.sender_id) == Number(activeChat.id)) ? 'flex-start' : 'flex-end', margin: '8px 0' }}>
                                                                            <div style={{
                                                                                maxWidth: 'min(40%, 30ch)', // Ограничение и по % и по символам 
                                                                                background: (Number(msg.sender_id) == Number(activeChat.id)) ? '#e5e5ea' : '#0084ff',
                                                                                color: (Number(msg.sender_id) == Number(activeChat.id)) ? '#000' : '#fff',
                                                                                padding: '8px 12px',
                                                                                borderRadius: 12,
                                                                                wordWrap: 'break-word',
                                                                                overflowWrap: 'break-word',
                                                                                whiteSpace: 'pre-wrap'
                                                                            }}>
                                                                                {msg.message}
                                                                            </div>
                                                                        </div>
                                                                    ))
                                                                )}
                                                            </div>
                                                            <div style={{
                                                                padding: 8,
                                                                display: 'flex',
                                                                gap: 8,
                                                                borderTop: '1px solid #eee',
                                                                background: '#fff',
                                                                width: '100%'
                                                            }}>
                                                                <input
                                                                    type="text"
                                                                    value={newMessage}
                                                                    onChange={(e) => setNewMessage(e.target.value)}
                                                                    placeholder="Сообщение"
                                                                    onKeyDown={(e) => { if (e.key === 'Enter') sendMessage(); }}
                                                                    style={{
                                                                        flex: 1,
                                                                        padding: '8px 8px',
                                                                        width: '80%',
                                                                        maxWidth: '140px',
                                                                        borderRadius: 12,
                                                                        border: '1px solid #ddd',
                                                                        boxSizing: 'border-box',
                                                                        wordWrap: 'break-word',
                                                                        whiteSpace: 'pre-wrap'
                                                                    }}
                                                                />
                                                                <button onClick={sendMessage} style={{ padding: '8px 8px', background: '#0084ff', color: '#fff', border: 'none', borderRadius: 12, cursor: 'pointer' }}>➤</button>
                                                            </div>
                                                        </>
                                                    )}
                                                {!activeChat && (
                                                    <div style={{ margin: 'auto', color: '#666' }}>Выберите контакт</div>
                                                )}
                                            </div>
                                        </div>
                                    </div>
                                )}


                            </div>
                        )}
                    </div>

                    {/* Нижняя кнопка */}
                    <div style={{
                        backgroundColor: "black",
                        width: "100%",
                        height: "10%",
                        borderTop: "0.5em solid black",
                        display: "flex",
                        justifyContent: "center",
                        alignItems: "center"
                    }}>
                        <div
                            style={{
                                backgroundColor: "white",
                                width: "15%",
                                aspectRatio: "1 / 1",
                                borderRadius: "50%",
                                border: "2px solid black"
                            }}
                        >
                            <button onClick={closeApp} style={{
                                opacity: 0,
                                position: "absolute",
                                bottom: "6px",
                                left: "50%",
                                transform: "translateX(-50%)",
                                padding: "0.5em 1em",
                                borderRadius: "10em",
                                background: "#000",
                                color: "white",
                                border: "none",
                                cursor: "pointer"
                            }}>
                                ⬅ Назад
                            </button>
                        </div>
                    </div>
                </div>
            </DoubleTapWrapper>
        </div>
    );
}

const btnStyle = {
    flex: 1,
    padding: '8px 12px',
    background: '#17a2b8',
    border: 'none',
    borderRadius: 4,
    color: '#fff',
    cursor: 'pointer'
};

export default Game;