// ============================================================
// GAME.JS — Основная логика игры
// ============================================================

const Game = {

    // ── Состояние ──
    state: 'menu',       // menu | playing | combat | levelup
    player: null,
    map: [],
    mapId: 'village',
    maps: {},
    enemies: [],
    npcs: [],
    decorations: [],
    groundItems: [],
    weather: 'none',
    weatherParts: [],
    timeOfDay: 12,       // 0–24
    dayCount: 1,
    daySpeed: 0.00014,   // скорость смены дня (1/мс)
    combatEnemy: null,
    pendingLevelUp: false,
    mouse: { x:0, y:0, tx:-1, ty:-1 },
    time: 0,
    lastTime: 0,
    openPanels: new Set(),
    _keysDown: new Set(),
    saveSlot: 0,
    _sessionStart: 0,
    loreNotes: [],
    _combatLog: [],
    _blinkInterval: null,
    _abyssParticles: null,
    _paused: false,
    _invTab: 'equip',
    _exitingToMenu: false,

    LOCATIONS:   {},  // заполняется DataLoader из data/world.json
    NPC_DIALOGS: {},  // заполняется DataLoader из data/world.json
    _WORLD:      {},  // заполняется DataLoader из data/world.json

    // ══════════════════════════════════════════
    // ЗАПУСК ИГРЫ
    // ══════════════════════════════════════════
    start(classId, slot = 0) {
        this.saveSlot = slot;
        this._sessionStart = Date.now();
        document.getElementById('start-screen').style.display = 'none';
        Renderer.init('gameCanvas');
        this.buildAllMaps();
        this.player = RPG.createCharacter(classId);
        // Стартовые предметы
        const kit = RPG.getStarterItems(classId);
        kit.forEach(it => RPG.addToInventory(this.player, it));
        // Экипировать оружие и щит если есть
        const weapon = this.player.inventory.find(i => i.slot === 'weapon');
        if (weapon) RPG.equip(this.player, weapon);
        const shield = this.player.inventory.find(i => i.slot === 'shield');
        if (shield) RPG.equip(this.player, shield);
        // Первый квест
        this.giveQuest('q_first');
        this.loadMap('village');
        this.state = 'playing';
        this._initCamera();
        this.setupInput();
        this.updateHUD();
        this.showMsg('Добро пожаловать в Хроники Эйдона!', '#ffd700');
        _stopMenuBgm();
        Audio.init(); Audio.playTheme('village');
        requestAnimationFrame(t => this.loop(t));
    },

    loadAndStart(slot = 0) {
        const data = RPG.load(slot);
        if (!data) return;
        this.saveSlot = slot;
        this._sessionStart = Date.now();
        document.getElementById('start-screen').style.display = 'none';
        Renderer.init('gameCanvas');
        this.buildAllMaps();
        this.player   = data.player;
        this.dayCount = data.dayCount || 1;
        this.timeOfDay= data.timeOfDay|| 12;
        this.loadMap(data.mapId || 'village');
        this.state = 'playing';
        this._initCamera();
        this.setupInput();
        this.updateHUD();
        this.showMsg('Игра загружена! День '+this.dayCount, '#4f4');
        _stopMenuBgm();
        Audio.init(); Audio.playTheme(data.mapId || 'village');
        requestAnimationFrame(t => this.loop(t));
    },

    // ══════════════════════════════════════════
    // ГЕНЕРАЦИЯ КАРТ
    // ══════════════════════════════════════════
    buildAllMaps() {
        ['village','tavern','forest','dungeon','cave','mountain','swamp','ruins','abyss'].forEach(id => {
            this.maps[id] = this.genMap(id);
        });
    },

    genMap(id) {
        const S = 15;
        const m = Array.from({length:S}, () => Array(S).fill(0));

        // Границы — стена
        for (let y=0;y<S;y++) for (let x=0;x<S;x++) {
            if (x===0||x===S-1||y===0||y===S-1) m[y][x] = 4;
        }

        // Заполнение по типу
        const R = (n) => Math.random() < n;
        for (let y=1;y<S-1;y++) for (let x=1;x<S-1;x++) {
            switch(id) {
                case 'village':
                    m[y][x] = R(0.65)?0: R(0.6)?9:8; break;
                case 'forest':
                    m[y][x] = R(0.5)?0: R(0.5)?2: R(0.4)?1:8; break;
                case 'dungeon':
                    m[y][x] = R(0.15)?4: R(0.6)?9:8; break;
                case 'cave':
                    m[y][x] = R(0.2)?4: R(0.4)?2: R(0.2)?6:8; break;
                case 'mountain':
                    m[y][x] = R(0.25)?4: R(0.5)?2:7; break;
                case 'swamp':
                    m[y][x] = R(0.4)?10: R(0.3)?1:8; break;
                case 'ruins':
                    m[y][x] = R(0.12)?4: R(0.25)?2: R(0.1)?3:9; break;
                case 'abyss':
                    m[y][x] = R(0.08)?2: R(0.04)?6:12; break;
                case 'tavern':
                    m[y][x] = 5; break;
            }
        }

        // Центральная поляна
        for (let y=5;y<9;y++) for (let x=5;x<9;x++) {
            m[y][x] = id==='dungeon'?9: id==='cave'?8: id==='swamp'?10: id==='mountain'?2: id==='ruins'?9: id==='tavern'?5: 0;
        }

        // Дорожка к центру
        if (id==='village') {
            for (let x=5;x<9;x++) m[13][x]=9;
        }

        return m;
    },

    // ══════════════════════════════════════════
    // ЗАГРУЗКА ЛОКАЦИИ
    // ══════════════════════════════════════════
    loadMap(id) {
        this.mapId = id;
        this.map   = this.maps[id] || this.maps.village;
        // Переставить игрока
        if (this.player) {
            this.player.x = 7; this.player.y = 7;
            this.player.tx= 7; this.player.ty= 7;
            this.player.isMoving = false;
        }
        this.spawnEnemies();
        this.spawnNPCs();
        this.spawnDecos();
        this.spawnGroundItems();
        this.spawnLoreNotes();
        this.setWeather();
    },

    setWeather() {
        const opts = (this._WORLD.weather || {})[this.mapId] || ['none'];
        this.weather = opts[Math.floor(Math.random() * opts.length)];
        this.weatherParts = [];
    },

    // ══════════════════════════════════════════
    // СПАВН ОБЪЕКТОВ
    // ══════════════════════════════════════════
    spawnEnemies() {
        this.enemies = [];
        const p = this.player;
        const lvl = p ? p.level : 1;
        const list = ((this._WORLD.spawns || {})[this.mapId]) || [];
        list.forEach(s => this.enemies.push(RPG.createEnemy(s.t, lvl + (s.lOff || 0), s.x, s.y)));
    },

    spawnNPCs() {
        this.npcs = [];
        const list = ((this._WORLD.npcs || {})[this.mapId]) || [];
        list.forEach(n => this.npcs.push({ ...n }));
    },

    spawnDecos() {
        this.decorations = [];
        const list = ((this._WORLD.decos || {})[this.mapId]) || [];
        list.forEach(d => this.decorations.push({ ...d }));
    },

    spawnLoreNotes() {
        const found = this.player ? (this.player.foundNotes || []) : [];
        this.loreNotes = RPG.LORE_NOTES
            .filter(n => n.mapId === this.mapId)
            .map(n => ({ ...n, collected: found.includes(n.id) }));
    },

    spawnGroundItems() {
        this.groundItems = [];
        if (Math.random() < 0.4) {
            this.groundItems.push({
                x: 4 + Math.floor(Math.random()*7),
                y: 4 + Math.floor(Math.random()*7),
                collected: false,
                ...RPG.createItem('rnd_gi', 'potion', 'Зелье HP',
                    { healAmount:30, value:20, stackable:true, qty:1, icon:'🧪' })
            });
        }
        if (Math.random() < 0.3) {
            this.groundItems.push({
                x: 3 + Math.floor(Math.random()*9),
                y: 3 + Math.floor(Math.random()*9),
                collected: false,
                ...RPG.createItem('rnd_gold','gold','Золото',
                    { value: 10+Math.floor(Math.random()*20), stackable:true, qty:1, icon:'💰' })
            });
        }
    },

    // ══════════════════════════════════════════
    // ИГРОВОЙ ЦИКЛ
    // ══════════════════════════════════════════
    loop(ts) {
        if (this._exitingToMenu) { this._exitingToMenu = false; return; }
        if (this._paused) { requestAnimationFrame(t => this.loop(t)); return; }
        const dt = Math.min(ts - this.lastTime, 50);
        this.lastTime = ts;
        this.time = ts;

        this.update(dt);
        this.render();
        requestAnimationFrame(t => this.loop(t));
    },

    update(dt) {
        if (this.state !== 'playing') return;

        // День/ночь
        this.timeOfDay += dt * this.daySpeed * 24;
        if (this.timeOfDay >= 24) {
            this.timeOfDay -= 24;
            this.dayCount++;
            this.showMsg('День '+this.dayCount, '#ffd700');
            this.spawnEnemies(); // Враги возрождаются каждый день
        }

        // Погода
        this.updateWeather(dt);

        // Движение игрока
        if (this.player.isMoving) {
            this.player.mp_move += dt / 260;
            if (this.player.mp_move >= 1) {
                this.player.mp_move = 0;
                this.player.isMoving = false;
                this.player.x = this.player.tx;
                this.player.y = this.player.ty;
                this.onPlayerLanded();
                this._updateInteractHint();
                this._tryMoveFromKeys();
            }
        }

        Renderer.updateParticles(dt);
        Renderer.updateFloatingTexts(dt);
        Renderer.updateShake();

        // Плавное следование камеры за игроком
        const drawX = this.player.isMoving
            ? this.player.x + (this.player.tx - this.player.x) * this.player.mp_move
            : this.player.x;
        const drawY = this.player.isMoving
            ? this.player.y + (this.player.ty - this.player.y) * this.player.mp_move
            : this.player.y;
        const camTargX = -((drawX - drawY) * Renderer.TW / 2);
        const camTargY = -((drawX + drawY) * Renderer.TH / 2) - Renderer.OY + Renderer.canvas.height * 0.5;
        const lf = Math.min(1, dt * 0.009);
        Renderer.camera.x += (camTargX - Renderer.camera.x) * lf;
        Renderer.camera.y += (camTargY - Renderer.camera.y) * lf;
    },

    updateWeather(dt) {
        if (this.weather === 'rain') {
            for (let i=0; i<4; i++) {
                this.weatherParts.push({ x:Math.random()*900, y:-5, speed:4+Math.random()*5 });
            }
            this.weatherParts.forEach(p => p.y += p.speed);
            this.weatherParts = this.weatherParts.filter(p => p.y < 620);
        } else if (this.weather === 'snow') {
            for (let i=0; i<2; i++) {
                this.weatherParts.push({ x:Math.random()*900, y:-5, speed:0.8+Math.random()*1.5, r:1+Math.random()*2 });
            }
            this.weatherParts.forEach(p => { p.y += p.speed; p.x += Math.sin(this.time/800+p.y)*0.5; });
            this.weatherParts = this.weatherParts.filter(p => p.y < 620);
        }
    },

    onPlayerLanded() {
        const px = this.player.x, py = this.player.y;

        // Порталы
        const portal = this.decorations.find(d => d.type==='portal' && d.x===px && d.y===py);
        if (portal) { this.travelTo(portal.destination); return; }

        // Подбор предметов
        const gi = this.groundItems.find(i => !i.collected && i.x===px && i.y===py);
        if (gi) {
            gi.collected = true;
            if (gi.type === 'gold') {
                this.player.gold += gi.value;
                this.showMsg('+'+gi.value+' золота 💰', '#ffd700');
                Renderer.addParticle(px, py, 'gold');
                this.checkAchievements('gold');
            } else {
                RPG.addToInventory(this.player, gi);
                this.showMsg('Найдено: '+gi.name, '#4f4');
                this.checkAchievements('inv_full');
            }
            this.updateHUD();
        }

        // Сбор записок лора
        const note = this.loreNotes && this.loreNotes.find(n => !n.collected && n.gx===px && n.gy===py);
        if (note) {
            note.collected = true;
            this.player.foundNotes = this.player.foundNotes || [];
            if (!this.player.foundNotes.includes(note.id)) {
                this.player.foundNotes.push(note.id);
                this.showMsg('📖 Найдена запись: '+note.title, '#88aaff');
                Audio.playOpenChest();
                // Показать подсказку о слабости врага
                if (note.reveals && note.reveals.hint) {
                    setTimeout(() => this.showMsg('💡 ' + note.reveals.hint, '#ffdd44'), 1500);
                }
                // Проверка: собраны все записки локации → бонус
                this._checkLoreLocationBonus(note.mapId);
                this.checkAchievements('lore_read');
            }
        }

        // Столкновение с врагом
        const enemy = this.enemies.find(e => Math.round(e.x)===px && Math.round(e.y)===py);
        if (enemy) this.startCombat(enemy);

        // NPC
        const npc = this.npcs.find(n => n.x===px && n.y===py);
        if (npc) this.interactNPC(npc);
    },

    _initCamera() {
        const px = this.player.x, py = this.player.y;
        Renderer.camera.x = -((px - py) * Renderer.TW / 2);
        Renderer.camera.y = -((px + py) * Renderer.TH / 2) - Renderer.OY + Renderer.canvas.height * 0.5;
    },

    travelTo(dest) {
        if (!this.maps[dest]) return;
        this.closeAllPanels();
        this.loadMap(dest);
        this._initCamera();
        const locName = (this.LOCATIONS[dest] || {}).name || dest;
        this.showMsg('Переход: ' + locName, '#88aaff');
        this.updateQuestProgress('visit', dest);
        this.checkAchievements('visit', dest);
        Audio.playTheme(dest);
        // Void-частицы в Бездне
        clearInterval(this._abyssParticles);
        if (dest === 'abyss') {
            this._abyssParticles = setInterval(() => {
                if (this.mapId !== 'abyss') { clearInterval(this._abyssParticles); return; }
                const rx = 1 + Math.floor(Math.random()*13);
                const ry = 1 + Math.floor(Math.random()*13);
                Renderer.addParticle(rx, ry, 'void', 2);
            }, 900);
        }
        this.autoSave();
    },

    // ══════════════════════════════════════════
    // ДВИЖЕНИЕ
    // ══════════════════════════════════════════
    _tryMoveFromKeys() {
        const k = this._keysDown;
        if (k.has('ArrowUp')    || k.has('w') || k.has('W')) { this.movePlayer( 0,-1); return; }
        if (k.has('ArrowDown')  || k.has('s') || k.has('S')) { this.movePlayer( 0, 1); return; }
        if (k.has('ArrowLeft')  || k.has('a') || k.has('A')) { this.movePlayer(-1, 0); return; }
        if (k.has('ArrowRight') || k.has('d') || k.has('D')) { this.movePlayer( 1, 0); return; }
    },

    movePlayer(dx, dy) {
        if (this.player.isMoving || this.state !== 'playing') return;
        if (this.openPanels.size > 0) return;
        const nx = Math.round(this.player.x) + dx;
        const ny = Math.round(this.player.y) + dy;
        if (!RPG.isPassable(this.map, nx, ny)) return;
        // Не идти туда, где стоит враг — бой начнётся по клику
        const enemy = this.enemies.find(e => Math.round(e.x)===nx && Math.round(e.y)===ny);
        if (enemy) { this.startCombat(enemy); return; }
        const npc = this.npcs.find(n => n.x===nx && n.y===ny);
        if (npc) { this.interactNPC(npc); return; }
        this.player.tx = nx; this.player.ty = ny;
        this.player.isMoving = true; this.player.mp_move = 0;
        Audio.playStep();
        if (dx > 0) this.player.facing = 'right';
        else if (dx < 0) this.player.facing = 'left';
        else if (dy > 0) this.player.facing = 'down';
        else this.player.facing = 'up';
    },

    // ══════════════════════════════════════════
    // ВВОД
    // ══════════════════════════════════════════
    setupInput() {
        const canvas = document.getElementById('gameCanvas');
        document.addEventListener('keydown', e => { this._keysDown.add(e.key); this.onKey(e.key); });
        document.addEventListener('keyup',   e => this._keysDown.delete(e.key));
        canvas.addEventListener('mousemove', e => {
            const r = canvas.getBoundingClientRect();
            this.mouse.x = e.clientX - r.left;
            this.mouse.y = e.clientY - r.top;
            const iso = Renderer.fromIso(this.mouse.x, this.mouse.y);
            this.mouse.tx = iso.x; this.mouse.ty = iso.y;
        });
        canvas.addEventListener('click', () => {
            if (this.openPanels.size > 0 || this.state !== 'playing') return;
            if (this.mouse.tx < 0) return;
            const px = Math.round(this.player.x), py = Math.round(this.player.y);
            const dx = this.mouse.tx - px, dy = this.mouse.ty - py;
            if (Math.abs(dx) + Math.abs(dy) === 1 && !this.player.isMoving) {
                this.movePlayer(dx, dy);
            }
        });
    },

    onKey(key) {
        // Движение
        if (this.state === 'playing' && !this.player.isMoving) {
            if (key==='ArrowUp'   ||key==='w'||key==='W') this.movePlayer(0,-1);
            if (key==='ArrowDown' ||key==='s'||key==='S') this.movePlayer(0,1);
            if (key==='ArrowLeft' ||key==='a'||key==='A') this.movePlayer(-1,0);
            if (key==='ArrowRight'||key==='d'||key==='D') this.movePlayer(1,0);
        }
        // Панели
        if (key==='i'||key==='I') this.togglePanel('inventory');
        if (key==='q'||key==='Q') this.togglePanel('quest');
        if (key==='t'||key==='T') this.togglePanel('perk');
        if (key==='c'||key==='C') this.togglePanel('craft');
        if (key==='b'||key==='B') this.togglePanel('bestiary');
        if (key==='h'||key==='H') this.togglePanel('achiev');
        if (key==='e'||key==='E') this.togglePanel('enchant');
        if (key==='l'||key==='L') this.togglePanel('lore');
        if (key==='m'||key==='M') this.togglePanel('worldmap');
        if (key==='p'||key==='P') this.saveGame();
        if (key==='f'||key==='F') { if (this.state==='playing' && this.openPanels.size===0) this._interactNearest(); }
        if (key==='Escape') {
            if (this.openPanels.size > 0 && !this.openPanels.has('pause')) {
                this.closeAllPanels();
            } else {
                this._togglePause();
            }
        }
        // Бой
        if (this.state==='combat') {
            if (key==='1') this.combatAct('attack');
            if (key==='2') this.combatAct('item');
            if (key==='3') this.combatAct('flee');
        }
    },

    // ══════════════════════════════════════════
    // ВЗАИМОДЕЙСТВИЕ С ОКРУЖЕНИЕМ (F)
    // ══════════════════════════════════════════
    _getNearbyInteractable() {
        const px = this.player.x, py = this.player.y;
        // Сначала проверяем соседние тайлы, потом текущий (портал на текущем уже auto-trigger)
        const tiles = [{x:px,y:py-1},{x:px,y:py+1},{x:px-1,y:py},{x:px+1,y:py},{x:px,y:py}];
        for (const {x,y} of tiles) {
            const portal = this.decorations.find(d => d.type==='portal' && d.x===x && d.y===y);
            if (portal) return { kind:'portal', obj:portal, label: portal.name || 'Портал' };
            const npc = this.npcs.find(n => n.x===x && n.y===y);
            if (npc) return { kind:'npc', obj:npc, label: npc.name };
        }
        return null;
    },

    _updateInteractHint() {
        const hint = document.getElementById('interact-hint');
        if (!hint) return;
        if (this.state !== 'playing' || this.openPanels.size > 0 || this._paused) {
            hint.classList.remove('visible');
            return;
        }
        const nearby = this._getNearbyInteractable();
        if (nearby) {
            const icon = nearby.kind === 'portal' ? '🚪' : '💬';
            const verb = nearby.kind === 'portal' ? 'Войти' : 'Говорить';
            hint.innerHTML = `<span class="hint-key">F</span>${icon} ${verb}: <b style="color:#ffd700">${nearby.label}</b>`;
            hint.classList.add('visible');
        } else {
            hint.classList.remove('visible');
        }
    },

    _interactNearest() {
        const nearby = this._getNearbyInteractable();
        if (!nearby) return;
        if (nearby.kind === 'portal') {
            this.travelTo(nearby.obj.destination);
        } else {
            this.interactNPC(nearby.obj);
        }
    },

    // ══════════════════════════════════════════
    // РЕНДЕР
    // ══════════════════════════════════════════
    render() {
        const brightness = this.getDayBrightness();
        Renderer._currentMapId = this.mapId;

        Renderer.clear(brightness);
        Renderer.drawStars(this.time, brightness);

        // Карта
        const hover = { x: this.mouse.tx, y: this.mouse.ty };
        Renderer.drawMap(this.map, hover, this.time);

        // Сортировка объектов по глубине (изометрический порядок)
        const objs = [];

        this.decorations.forEach(d => objs.push({ depth: d.x+d.y, draw: () => Renderer.drawDecoration(d, this.time) }));
        this.groundItems.filter(i=>!i.collected).forEach(i => objs.push({ depth: i.x+i.y-0.1, draw: () => Renderer.drawGroundItem(i, this.time) }));
        if (this.loreNotes) this.loreNotes.filter(n=>!n.collected).forEach(n => objs.push({ depth: n.gx+n.gy-0.05, draw: () => Renderer.drawLoreNote(n, this.time) }));
        this.enemies.forEach(e => objs.push({ depth: e.x+e.y, draw: () => Renderer.drawEnemy(e, this.time) }));
        this.npcs.forEach(n => objs.push({ depth: n.x+n.y, draw: () => Renderer.drawNPC(n, this.time) }));

        const pd = this.player.isMoving
            ? this.player.x + (this.player.tx-this.player.x)*this.player.mp_move
              + this.player.y + (this.player.ty-this.player.y)*this.player.mp_move
            : this.player.x + this.player.y;
        objs.push({ depth: pd, draw: () => Renderer.drawPlayer(this.player, this.time) });

        objs.sort((a,b) => a.depth - b.depth).forEach(o => o.draw());

        // Маркеры квестов (! / ? / ✓ над NPC)
        Renderer.drawQuestMarkers(this.npcs, this._getQuestMarkerData(), this.time);

        // Частицы
        Renderer.drawParticles();

        // Погода
        if (this.weather==='rain') Renderer.drawRain(this.weatherParts);
        if (this.weather==='snow') Renderer.drawSnow(this.weatherParts);
        if (this.weather==='fog')  Renderer.drawFog(this.time);

        // Динамическое освещение — собираем источники света
        const lights = [];
        this.decorations.forEach(d => {
            if (d.type === 'torch')   lights.push({ x: d.x, y: d.y, radius: 100, flicker: true  });
            if (d.type === 'crystal') lights.push({ x: d.x, y: d.y, radius: 75,  flicker: false });
            if (d.type === 'portal')  lights.push({ x: d.x, y: d.y, radius: 60,  flicker: false });
        });
        // Игрок тоже излучает свет (маги — больше)
        const plrLightX = this.player.isMoving ? this.player.x + (this.player.tx - this.player.x) * this.player.mp_move : this.player.x;
        const plrLightY = this.player.isMoving ? this.player.y + (this.player.ty - this.player.y) * this.player.mp_move : this.player.y;
        const plrR = (this.player.class === 'mage' || this.player.class === 'necromancer') ? 75 : 48;
        lights.push({ x: plrLightX, y: plrLightY, radius: plrR, flicker: false });
        Renderer.drawLightMask(brightness, lights, this.time);

        // Всплывающие числа урона (поверх темноты — всегда видны)
        Renderer.drawFloatingTexts();

        // Атмосфера Бездны
        if (this.mapId === 'abyss') Renderer.drawAbyssAtmosphere(this.time);

        // Вспышка экрана (при ударах и т.д.)
        Renderer.drawFlash();

        // HUD canvas-часть: название локации, день, время
        this.renderCanvasHUD();

        // Миникарта
        Renderer.drawMinimap(this.map, this.player, this.enemies, this._getMinimapQuestDots());
    },

    renderCanvasHUD() {
        const ctx = Renderer.ctx;
        const loc = this.LOCATIONS[this.mapId];
        ctx.fillStyle = '#ffd700'; ctx.font = 'bold 14px Arial'; ctx.textAlign = 'left';
        ctx.fillText(loc ? loc.name : '', 10, 62);
        ctx.fillStyle = '#888'; ctx.font = '11px Arial';
        ctx.fillText('День '+this.dayCount+' · '+Math.floor(this.timeOfDay)+':00', 10, 76);
        if (this.weather!=='none') {
            const w = {rain:'🌧️',snow:'❄️',fog:'🌫️',sunny:'☀️'}[this.weather]||'';
            ctx.fillText(w, 130, 76);
        }
    },

    getDayBrightness() {
        const h = this.timeOfDay;
        if (h>=6&&h<18)  return 1;
        if (h>=18&&h<20) return 1 - (h-18)*0.3;
        if (h>=4&&h<6)   return 0.4 + (h-4)*0.3;
        return 0.4;
    },

    // ══════════════════════════════════════════
    // ПАНЕЛИ (HTML-UI)
    // ══════════════════════════════════════════
    togglePanel(name) {
        const el = document.getElementById(name+'-panel');
        if (!el) return;
        if (this.openPanels.has(name)) {
            el.classList.remove('open');
            this.openPanels.delete(name);
        } else {
            this.openPanels.add(name);
            el.classList.add('open');
            if (name==='inventory') this.renderInventoryPanel();
            if (name==='quest')     this.renderQuestPanel();
            if (name==='shop')      this.renderShopPanel();
            if (name==='perk')      this.renderPerkPanel();
            if (name==='craft')     this.renderCraftPanel();
            if (name==='bestiary')  this.renderBestiaryPanel();
            if (name==='achiev')    this.renderAchievPanel();
            if (name==='enchant')   this.renderEnchantPanel();
            if (name==='lore')      this.renderLorePanel();
            if (name==='worldmap')  this.renderWorldMapPanel();
        }
    },

    openPanel(name) {
        if (this.openPanels.has(name)) return;
        const el = document.getElementById(name+'-panel');
        if (!el) return;
        this.openPanels.add(name);
        el.classList.add('open');
        this._updateInteractHint();
    },

    closePanel(name) {
        const el = document.getElementById(name+'-panel');
        if (el) { el.classList.remove('open'); }
        this.openPanels.delete(name);
        this._updateInteractHint();
    },

    closeAllPanels() {
        ['inventory','shop','quest','dialog','combat','skill','perk','craft',
         'bestiary','achiev','enchant','lore','worldmap','pause'].forEach(n => this.closePanel(n));
        this._paused = false;
        if (this.state==='combat') this.state = 'playing';
    },

    _togglePause() {
        if (this.state !== 'playing' && this.state !== 'combat') return;
        if (this.openPanels.has('pause')) {
            this.resumeGame();
        } else {
            this._paused = true;
            const el = document.getElementById('pause-panel');
            if (el) el.classList.add('open');
            this.openPanels.add('pause');
            const sl = document.getElementById('vol-slider');
            if (sl && Audio._master) sl.value = Math.round(Audio._master.gain.value * 100);
        }
    },

    resumeGame() {
        this._paused = false;
        this.closePanel('pause');
    },

    exitToMenu() {
        this._paused = false;
        this._exitingToMenu = true;
        this.closeAllPanels();
        // Сплэш при возврате из игры не показываем
        const splash = document.getElementById('splash-screen');
        if (splash) splash.style.display = 'none';
        document.getElementById('start-screen').style.display = '';
        document.getElementById('menu-main').style.display = 'flex';
        document.getElementById('menu-class').style.display = 'none';
        menuBuildSlots();
        menuStartAnim();
        // Вернуть музыку меню
        Audio.stopMusic();
        const bgm = document.getElementById('menu-bgm');
        if (bgm) { bgm.currentTime = 0; bgm.play().catch(() => {}); }
    },

    // ──── Инвентарь ────
    renderInventoryPanel() {
        this.switchInvTab(this._invTab || 'equip');
    },

    switchInvTab(tab) {
        this._invTab = tab;
        document.querySelectorAll('.inv-tab').forEach((btn, i) => {
            btn.classList.toggle('active', ['equip','items','stats'][i] === tab);
        });
        document.querySelectorAll('.inv-tab-pane').forEach(pane => {
            pane.classList.toggle('active', pane.id === 'inv-tab-' + tab);
        });
        if (tab === 'equip') this._renderPaperDoll();
        if (tab === 'items') this._renderItemsGrid();
        if (tab === 'stats') this._renderStatsDetail();
    },

    _renderPaperDoll() {
        const p = this.player;
        // Портрет персонажа
        const cvs = document.getElementById('portrait-inv');
        if (cvs) Renderer.drawPlayerPortrait(p, cvs);
        // Слоты экипировки
        const slotLabels = { head:'Шлем', weapon:'Оружие', chest:'Броня',
                             shield:'Щит', legs:'Поножи', feet:'Сапоги', acc:'Украшение' };
        Object.entries(slotLabels).forEach(([slot, label]) => {
            const el = document.getElementById('pd-' + slot);
            if (!el) return;
            const item = p.equipment[slot];
            if (item) {
                el.classList.add('filled');
                const enchTag = item.enchant && RPG.ENCHANTS[item.enchant]
                    ? ' ' + RPG.ENCHANTS[item.enchant].icon : '';
                let stat = '';
                if (item.damage)  stat = '⚔️+'+item.damage;
                if (item.defense) stat = '🛡️+'+item.defense;
                el.innerHTML = `<div class="pd-item-icon">${item.icon||'📦'}</div>
                    <div class="pd-item-name">${item.name}${enchTag}</div>
                    <div class="pd-item-stat">${stat}</div>`;
                el.onclick = () => {
                    RPG.unequip(p, slot);
                    this.updateHUD(); this._renderPaperDoll();
                    this.showMsg('Снято: ' + item.name);
                };
            } else {
                el.classList.remove('filled');
                el.innerHTML = `<small>${label}</small>`;
                el.onclick = null;
            }
            // DnD: принять предмет из вкладки «Предметы»
            el.ondragover = e => { e.preventDefault(); el.classList.add('drag-over'); };
            el.ondragleave = () => el.classList.remove('drag-over');
            el.ondrop = e => {
                e.preventDefault(); el.classList.remove('drag-over');
                const idx = parseInt(e.dataTransfer.getData('invIdx'));
                if (isNaN(idx)) return;
                const dragged = p.inventory[idx];
                if (!dragged || dragged.slot !== slot) return;
                const r = RPG.equip(p, dragged);
                this.showMsg(r.msg); this.updateHUD(); this._renderPaperDoll();
            };
        });
    },

    _renderItemsGrid() {
        const p = this.player;
        const invGrid = document.getElementById('inv-grid');
        if (!invGrid) return;
        invGrid.innerHTML = '';
        p.inventory.forEach((item, idx) => {
            const div = document.createElement('div');
            const rc = RPG.RARITY_COLORS[item.rarity||'common'];
            div.className = 'inv-slot r-'+(item.rarity||'common');
            div.style.borderColor = rc;
            let statStr = '';
            if (item.damage)      statStr = '⚔️+'+item.damage;
            else if (item.defense)    statStr = '🛡️+'+item.defense;
            else if (item.healAmount) statStr = '❤️+'+item.healAmount;
            else if (item.restoreMp)  statStr = '💧+'+item.restoreMp;
            div.innerHTML = `
                <div class="is-icon">${item.icon||'📦'}</div>
                <div class="is-name">${item.name.substring(0,14)}</div>
                <div class="is-val">${statStr}</div>
                ${item.stackable && item.qty > 1 ? `<div class="is-qty">×${item.qty}</div>` : ''}`;
            div.onclick = () => {
                if (item.slot) {
                    const r = RPG.equip(p, item);
                    this.showMsg(r.msg);
                } else {
                    const r = RPG.useItem(p, item);
                    this.showMsg(r.msg);
                }
                this.updateHUD();
                this.renderInventoryPanel();
            };
            // Drag & Drop
            div.draggable = true;
            div.addEventListener('dragstart', e => {
                e.dataTransfer.setData('invIdx', idx);
                e.dataTransfer.effectAllowed = 'move';
                setTimeout(() => div.classList.add('dragging'), 0);
            });
            div.addEventListener('dragend', () => div.classList.remove('dragging'));
            div.addEventListener('dragover', e => { e.preventDefault(); div.classList.add('drag-over'); });
            div.addEventListener('dragleave', () => div.classList.remove('drag-over'));
            div.addEventListener('drop', e => {
                e.preventDefault();
                div.classList.remove('drag-over');
                const fromIdx = parseInt(e.dataTransfer.getData('invIdx'));
                if (isNaN(fromIdx) || fromIdx === idx) return;
                [p.inventory[fromIdx], p.inventory[idx]] = [p.inventory[idx], p.inventory[fromIdx]];
                this._renderItemsGrid();
            });
            if (item.slot && p.equipment[item.slot] === item) div.classList.add('equipped');
            invGrid.appendChild(div);
        });
    },

    _renderStatsDetail() {
        const el = document.getElementById('inv-stats-detail');
        if (!el) return;
        const p = this.player;
        const s  = RPG.getTotalStats(p);
        const eq = RPG.getEqBonus(p);
        const sb = RPG.getSetBonus(p);

        const eqStr = (eq.str||0) + (eq.damage||0);
        const eqDef = (eq.def||0) + (eq.defense||0);
        const eqMag = eq.mag || 0;
        const eqHp  = eq.hp  || 0;
        const eqMp  = eq.mp  || 0;

        const baseStr = p.baseStr || p.str;
        const grStr   = Math.max(0, p.str - baseStr);
        const baseDef = p.baseDef || p.def;
        const grDef   = Math.max(0, p.def - baseDef);
        const baseMag = p.baseMag || p.mag;
        const grMag   = Math.max(0, p.mag - baseMag);

        const critPct   = Math.round((0.10 + (p.spd||0)*0.008)*100);
        const dodge     = RPG._sumPerkVal ? Math.round(RPG._sumPerkVal(p,'dodge')*100) : 0;
        const dblAtk    = RPG._sumPerkVal ? Math.round(RPG._sumPerkVal(p,'doubleAtk')*100) : 0;
        const lifesteal = RPG._sumPerkVal ? Math.round(RPG._sumPerkVal(p,'lifesteal')*100) : 0;

        const row = (icon, label, val, breakdown, barMax) => {
            const pct = barMax ? Math.min(100, Math.round(val/barMax*100)) : 0;
            return `<div class="stat-row">
                <span class="stat-label">${icon} ${label}</span>
                <span class="stat-value">${val}</span>
                <span class="stat-breakdown">${breakdown}</span>
            </div>${barMax ? `<div class="stat-bar"><div class="stat-bar-fill" style="width:${pct}%"></div></div>` : ''}`;
        };

        const fmtBreak = (base, growth, equip, set) => {
            let parts = [`база <b>${base}</b>`];
            if (growth > 0) parts.push(`рост <b>+${growth}</b>`);
            if (equip  > 0) parts.push(`экип <b>+${equip}</b>`);
            if (set    > 0) parts.push(`набор <b>+${set}</b>`);
            return parts.join(' ');
        };

        el.innerHTML = `
        <div class="stat-group">
            <div class="stat-group-title">Основные</div>
            ${row('⚔️','Урон',    s.damage,  fmtBreak(baseStr, grStr, eqStr, sb.str||0), 100)}
            ${row('🛡️','Защита', s.defense,  fmtBreak(baseDef, grDef, eqDef, sb.def||0), 80)}
            ${row('✨','Магия',   s.magic,   fmtBreak(baseMag, grMag, eqMag, sb.mag||0), 80)}
        </div>
        <div class="stat-group">
            <div class="stat-group-title">Живучесть</div>
            ${row('❤️','HP макс', p.maxHp, eqHp||sb.hp ? `экип <b>+${eqHp+(sb.hp||0)}</b>` : '—', 0)}
            ${row('💧','MP макс', p.maxMp, eqMp||sb.mp ? `экип <b>+${eqMp+(sb.mp||0)}</b>` : '—', 0)}
            ${row('⭐','Уровень', p.level, `опыт: ${p.exp}/${p.expNext}`, 0)}
        </div>
        <div class="stat-group">
            <div class="stat-group-title">Боевые</div>
            ${row('💥','Крит',      critPct+'%', `база 10% + спд×0.8% (спд: <b>${p.spd||0}</b>)`, 50)}
            ${row('⚡','Скорость',  p.spd||0, `база <b>${p.baseSpd||p.spd||0}</b> + рост <b>+${Math.max(0,(p.spd||0)-(p.baseSpd||p.spd||0))}</b>`, 20)}
            ${dodge   >0 ? row('👤','Уклонение', dodge+'%',   `из перков`, 50) : ''}
            ${dblAtk  >0 ? row('🗡️','Двойной удар', dblAtk+'%', `из перков`, 50) : ''}
            ${lifesteal>0? row('🩸','Вампиризм', lifesteal+'%', `из перков`, 30) : ''}
        </div>`;
    },

    // ──── Магазин ────
    renderShopPanel() {
        document.getElementById('shop-gold-val').textContent = this.player.gold;
        const grid = document.getElementById('shop-grid');
        grid.innerHTML = '';
        RPG.SHOP_ITEMS.forEach(raw => {
            const item = RPG.createItem(raw.id, raw.type, raw.name, raw.opts);
            const div = document.createElement('div');
            div.className = 'shop-item';
            let statStr = '';
            if (item.damage)    statStr += '⚔️ '+item.damage+' ';
            if (item.defense)   statStr += '🛡️ '+item.defense+' ';
            if (item.healAmount)statStr += '❤️ +'+item.healAmount;
            if (item.bonusMag)  statStr += '✨ +'+item.bonusMag;
            div.innerHTML = `
                <div class="si-name">${item.icon||''} ${item.name}</div>
                <div class="si-price">💰 ${item.value}</div>
                <div class="si-stat">${statStr}</div>`;
            div.onclick = () => this.buyItem(item);
            if (this.player.gold < item.value) div.classList.add('cant-afford');
            grid.appendChild(div);
        });
    },

    buyItem(item) {
        if (this.player.gold < item.value) { this.showMsg('Недостаточно золота!', '#f44'); return; }
        this.player.gold -= item.value;
        const copy = { ...item, id: item.id+'_'+Date.now() };
        RPG.addToInventory(this.player, copy);
        this.showMsg('Куплено: '+item.name, '#4f4');
        this.updateHUD();
        document.getElementById('shop-gold-val').textContent = this.player.gold;
    },

    // ──── Квесты ────
    renderQuestPanel() {
        const ql = document.getElementById('quest-list');
        ql.innerHTML = '';

        // ── Сюжетные квесты (активные) ──
        const activeStory = this.player.quests.filter(q => q.isStory && !q.done);
        if (activeStory.length) {
            const t = document.createElement('div'); t.className = 'q-sec'; t.textContent = '📖 Сюжетные задания';
            ql.appendChild(t);
            activeStory.forEach(pq => {
                const sq = RPG.getStoryQuest(pq.id);
                if (!sq) return;
                const stage = sq.stages[pq.stageIdx];
                if (!stage) return;

                const stageChecks = sq.stages.map((st, idx) => {
                    const isDone    = pq.completedStages.includes(idx);
                    const isCurrent = idx === pq.stageIdx;
                    const clr = isDone ? '#27ae60' : isCurrent ? '#ffd700' : '#333';
                    const pfx = isDone ? '✓' : isCurrent ? '▶' : '○';
                    const prog = isCurrent && st.need > 1 ? ` <span style="color:#666">(${pq.progress}/${st.need})</span>` : '';
                    return `<div style="font-size:9px;color:${clr};padding:1px 0;padding-left:${idx*6}px">${pfx} ${st.title}${prog}</div>`;
                }).join('');

                const pct = stage.need > 0 ? Math.min(pq.progress / stage.need, 1) * 100 : 0;
                const div = document.createElement('div');
                div.className = 'q-card active';
                div.style.borderLeft = '3px solid #ffaa44';
                div.innerHTML = `
                    <div style="display:flex;align-items:center;gap:6px;margin-bottom:3px">
                        <span style="font-size:13px">${sq.icon}</span>
                        <div class="q-name">${sq.name}</div>
                        <span style="font-size:9px;color:#555;margin-left:auto">от: ${sq.giverNpc}</span>
                    </div>
                    <div class="q-desc">${stage.desc}</div>
                    <div style="margin:4px 0">${stageChecks}</div>
                    ${stage.need > 1 ? `<div class="q-pbar"><div class="q-pfill" style="width:${pct}%"></div></div>` : ''}
                    <div class="q-reward" style="color:#ffaa44">Этап: +${stage.reward.exp} XP · +${stage.reward.gold} золота</div>`;
                ql.appendChild(div);
            });
        }

        // ── Обычные квесты (активные) ──
        const activeSimple = this.player.quests.filter(q => !q.isStory && !q.done);
        if (activeSimple.length) {
            const t = document.createElement('div'); t.className = 'q-sec'; t.textContent = 'Задания';
            ql.appendChild(t);
            activeSimple.forEach(q => {
                const qdb = RPG.QUEST_DB.find(d=>d.id===q.id);
                const div = document.createElement('div'); div.className = 'q-card active';
                const pct = q.need > 0 ? Math.min(q.progress/q.need,1)*100 : 100;
                div.innerHTML = `
                    <div class="q-name">${qdb?qdb.name:q.id}</div>
                    <div class="q-desc">${qdb?qdb.desc:''}</div>
                    <div class="q-pbar"><div class="q-pfill" style="width:${pct}%"></div></div>
                    <div class="q-reward">+${q.reward.exp} XP · +${q.reward.gold} золота</div>`;
                ql.appendChild(div);
            });
        }

        // ── Выполненные ──
        const completed = this.player.quests.filter(q => q.done);
        if (completed.length) {
            const t = document.createElement('div'); t.className = 'q-sec'; t.textContent = 'Выполненные';
            ql.appendChild(t);
            completed.slice(-10).forEach(q => {
                const name = q.isStory
                    ? (RPG.getStoryQuest(q.id) || {}).name || q.id
                    : (RPG.QUEST_DB.find(d=>d.id===q.id) || {}).name || q.id;
                const div = document.createElement('div'); div.className = 'q-card completed';
                div.innerHTML = `<div class="q-name">✓ ${name}</div>`;
                ql.appendChild(div);
            });
        }

        if (!this.player.quests.length) {
            ql.innerHTML = '<div style="padding:12px;color:#555;font-size:12px">Нет квестов. Поговори с NPC!</div>';
        }
    },

    // ──── Диалог ────
    showDialog(npcName, text, options) {
        document.getElementById('dlg-npc-name').textContent = npcName;
        document.getElementById('dlg-text').textContent = text;
        const optEl = document.getElementById('dlg-options');
        optEl.innerHTML = '';
        (options||[]).forEach(opt => {
            const btn = document.createElement('button');
            btn.className = 'dlg-opt';
            btn.textContent = opt.label;
            btn.onclick = () => { opt.action(); };
            optEl.appendChild(btn);
        });
        this.openPanel('dialog');
    },

    // ──── Меню путешествия ────
    showTravelMenu() {
        const opts = Object.entries(this.LOCATIONS).map(([id,loc]) => ({
            label: (id===this.mapId?'✦ ':'')+loc.name+(id===this.mapId?' (здесь)':''),
            action: () => { this.closePanel('dialog'); if (id!==this.mapId) this.travelTo(id); }
        }));
        opts.push({ label:'❌ Закрыть', action:()=>this.closePanel('dialog') });
        this.showDialog('🗺️ Карта мира', 'Выберите локацию для путешествия:', opts);
    },

    // ══════════════════════════════════════════
    // NPC ВЗАИМОДЕЙСТВИЕ
    // ══════════════════════════════════════════
    interactNPC(npc) {
        if (npc.type === 'shop') {
            this.renderShopPanel();
            this.openPanel('shop');
            return;
        }
        if (npc.type === 'healer') {
            this.showDialog(npc.name, 'Могу вас исцелить за 20 золота. Хотите?', [
                { label:'💚 Исцелить (-20 💰)', action:()=>{
                    if (this.player.gold >= 20) {
                        this.player.gold -= 20;
                        this.player.hp = this.player.maxHp;
                        this.player.mp = this.player.maxMp;
                        this.updateHUD(); this.showMsg('Исцелён!', '#4f4');
                        Renderer.addParticle(this.player.x, this.player.y, 'heal', 10);
                    } else { this.showMsg('Нужно 20 золота!', '#f44'); }
                    this.closePanel('dialog');
                }},
                { label:'❌ Нет', action:()=>this.closePanel('dialog') }
            ]);
            return;
        }
        if (npc.type === 'quest') {
            this._handleQuestNPC(npc);
            return;
        }
        this._startBranchDialog(npc);
    },

    // ══════════════════════════════════════════
    // КВЕСТЫ
    // ══════════════════════════════════════════
    giveQuest(id) {
        if (this.player.quests.find(q=>q.id===id)) return;
        const qdb = RPG.QUEST_DB.find(q=>q.id===id);
        if (!qdb) return;
        this.player.quests.push({ id, progress:0, need:qdb.need, reward:qdb.reward, done:false });
        this.showMsg('Новый квест: '+qdb.name, '#ffd700');
    },

    getUnlockedQuests() {
        const done = this.player.quests.filter(q=>q.done).map(q=>q.id);
        // Квесты открываются по мере выполнения предыдущих
        const chain = ['q_first','q_wolves','q_forest','q_slime','q_bandit','q_dungeon','q_skel','q_troll','q_cave','q_spider','q_dragon','q_lich',
                       'q_goblin_king','q_corvus','q_hydra','q_frost_giant','q_colossus','q_shadow','q_chaos_lord'];
        const idx = chain.findIndex(id => !done.includes(id));
        if (idx < 0) return [];
        return RPG.QUEST_DB.filter(q => chain.slice(0, Math.min(idx+3, chain.length)).includes(q.id) && !done.includes(q.id));
    },

    updateQuestProgress(type, target) {
        let anyCompleted = false;
        // Обычные квесты
        this.player.quests.forEach(q => {
            if (q.done || q.isStory) return;
            const qdb = RPG.QUEST_DB.find(d=>d.id===q.id);
            if (!qdb) return;
            if (qdb.type === type && (qdb.target === target || qdb.target === 'any')) {
                q.progress++;
                if (q.progress >= q.need) {
                    q.done = true;
                    this.player.exp  += q.reward.exp;
                    this.player.gold += q.reward.gold;
                    this.showMsg('✓ Квест выполнен: '+qdb.name+' · +'+q.reward.exp+' XP', '#ffd700');
                    anyCompleted = true;
                    this.checkAchievements('quest_done');
                }
            }
        });
        // Сюжетные квесты
        const storyResults = RPG.updateStoryQuestProgress(this.player, type, target);
        if (storyResults && storyResults.length > 0) {
            storyResults.forEach(r => {
                this.showMsg('📜 Этап выполнен: ' + r.stage.title + '! Поговори с ' + r.sq.giverNpc, '#ffaa44');
                anyCompleted = true;
            });
        }
        if (anyCompleted) {
            if (RPG.checkLevelUp(this.player)) this.triggerLevelUp();
            this.updateHUD();
            this.renderQuestPanel();
        }
    },

    // ══════════════════════════════════════════
    // БОЙ
    // ══════════════════════════════════════════
    startCombat(enemy) {
        this.state = 'combat';
        this.combatEnemy = enemy;
        this.player.isMoving = false;
        this.openPanel('combat');
        this.refreshCombatPanel();
        if (enemy.isMini) {
            const bar = document.getElementById('boss-bar');
            bar.style.display = 'block';
            document.getElementById('boss-bar-name').textContent = '⚔️ ' + enemy.name.toUpperCase();
            document.getElementById('boss-bar-fill').style.width = '100%';
            document.getElementById('boss-bar-text').textContent = 'HP: ' + enemy.hp + ' / ' + enemy.maxHp;
            this.showMsg('⚠️ МИНИ-БОСС: ' + enemy.name + '!', '#ff4400');
        } else {
            this.showMsg('Бой с ' + enemy.name + '!', '#e74c3c');
        }
        Audio.playTheme('combat');
        clearInterval(this._blinkInterval);
        this._blinkInterval = setInterval(() => this._doPortraitBlink(), 4000 + Math.random() * 2000);
    },

    refreshCombatPanel() {
        const e = this.combatEnemy;
        if (!e) return;
        // Портреты
        Renderer.drawEnemyPortrait(e, document.getElementById('portrait-enemy'));
        Renderer.drawPlayerPortrait(this.player, document.getElementById('portrait-player'));
        // Враг
        document.getElementById('cf-ename').textContent = e.name + ' Lv.'+e.level + (e.isBoss?' 👹':'');
        const ehpPct = e.hp / e.maxHp;
        const efill = document.getElementById('cf-ehp');
        efill.style.width = Math.max(0, ehpPct*100)+'%';
        if (ehpPct < 0.3)      efill.style.background = 'linear-gradient(to right,#6a0000,#ff2200)';
        else if (ehpPct < 0.6) efill.style.background = 'linear-gradient(to right,#6a4400,#e67e00)';
        else                   efill.style.background = '';
        document.getElementById('cf-ehpt').textContent = 'HP: '+Math.max(0,e.hp)+'/'+e.maxHp;
        document.getElementById('cf-estatus').textContent = e.status ? '⚠️ '+e.status : '';
        // Игрок
        document.getElementById('cf-pname').textContent = RPG.CLASSES[this.player.class].name+' Lv.'+this.player.level;
        const phpPct = this.player.hp / this.player.maxHp;
        const pfill = document.getElementById('cf-php');
        pfill.style.width = Math.max(0, phpPct*100)+'%';
        if (phpPct < 0.3)      pfill.style.background = 'linear-gradient(to right,#3a0000,#ff2200)';
        else if (phpPct < 0.6) pfill.style.background = 'linear-gradient(to right,#5a4400,#e6a000)';
        else                   pfill.style.background = '';
        document.getElementById('cf-phpt').textContent = 'HP: '+Math.floor(this.player.hp)+'/'+this.player.maxHp+' | MP: '+Math.floor(this.player.mp)+'/'+this.player.maxMp;
        document.getElementById('cf-pstatus').textContent = this.player.status ? '⚠️ '+this.player.status : '';

        // Boss bar update
        if (e.isMini) {
            const pct = Math.max(0, e.hp / e.maxHp * 100);
            document.getElementById('boss-bar-fill').style.width = pct + '%';
            document.getElementById('boss-bar-text').textContent = 'HP: ' + Math.max(0, e.hp) + ' / ' + e.maxHp;
        }

        // Кнопки действий
        const acts = document.getElementById('cbt-actions');
        acts.innerHTML = '';
        const addBtn = (label, cls, cb) => {
            const b = document.createElement('button');
            b.className = 'cbt-btn ' + cls;
            b.textContent = label;
            b.onclick = cb;
            acts.appendChild(b);
        };
        addBtn('⚔️ Атака (1)', 'b-atk', ()=>this.combatAct('attack'));
        // Заклинания
        this.player.learnedSpells.forEach((spId, i) => {
            const sp = RPG.SPELLS[spId];
            if (!sp) return;
            const disabled = this.player.mp < sp.mp;
            const b = document.createElement('button');
            b.className = 'cbt-btn b-spl' + (disabled?' disabled':'');
            b.textContent = sp.icon+' '+sp.name+' ('+sp.mp+'MP)';
            if (!disabled) b.onclick = ()=>this.combatCastSpell(spId);
            acts.appendChild(b);
        });
        // Зелья
        const potions = this.player.inventory.filter(i=>i.type==='potion'&&i.healAmount);
        if (potions.length) addBtn('🧪 Зелье (2)', 'b-itm', ()=>this.combatAct('item'));
        addBtn('🏃 Бежать (3)', 'b-fle', ()=>this.combatAct('flee'));
    },

    addCombatLog(msg, color) {
        this._combatLog.unshift({ msg, color: color || '#aaaaaa' });
        if (this._combatLog.length > 5) this._combatLog.pop();
        const log = document.getElementById('cbt-log');
        if (!log) return;
        log.innerHTML = this._combatLog
            .map((e, i) => `<div style="color:${e.color};opacity:${1 - i*0.16}">${e.msg}</div>`)
            .join('');
    },

    combatAct(action) {
        if (this.state !== 'combat') return;
        const enemy = this.combatEnemy;
        let msg = '';
        let particleType = 'hit';

        // Перк: регенерация HP в начале каждого хода
        const regenHp = RPG._sumPerkVal(this.player, 'regenHp');
        if (regenHp > 0 && this.player.hp < this.player.maxHp) {
            const actual = Math.min(regenHp, this.player.maxHp - this.player.hp);
            this.player.hp += actual;
            Renderer.addFloatingText(this.player.x, this.player.y, '+' + actual, '#44ff88', 13);
        }

        if (action === 'attack') {
            // Тик статуса врага
            const dotDmg = RPG.tickStatus(enemy);
            if (dotDmg > 0) this.addCombatLog(`${enemy.name} получает ${dotDmg} урона от ${enemy.status}!`);

            // Неуязвимость мини-босса
            if (enemy._invincible > 0) {
                enemy._invincible--;
                this.addCombatLog('🛡️ ' + enemy.name + ' неуязвим! Атака отражена!');
                Renderer.addFloatingText(enemy.x, enemy.y, 'БЛОК', '#888888', 16);
                this.refreshCombatPanel(); this.updateHUD();
                document.getElementById('cbt-status').textContent = 'Ход врага...';
                setTimeout(() => this.enemyTurn(), 700);
                return;
            }

            // Уклонение в тень (Shadow Assassin)
            if (enemy.ai === 'shadow' && Math.random() < 0.35) {
                this.addCombatLog('👤 ' + enemy.name + ' уклоняется в тень!');
                Renderer.addFloatingText(enemy.x, enemy.y, 'УКЛОН', '#9988cc', 15);
                this.refreshCombatPanel(); this.updateHUD();
                document.getElementById('cbt-status').textContent = 'Ход врага...';
                setTimeout(() => this.enemyTurn(), 700);
                return;
            }

            // Сдвиг в пустоту (Мрак Безликий — фаза 2+)
            if (enemy.ai === 'chaos' && enemy._chaosPhase2 && Math.random() < 0.20) {
                this.addCombatLog('🌑 ' + enemy.name + ' сдвигается в пустоту!');
                Renderer.addFloatingText(enemy.x, enemy.y, 'ПУСТОТА', '#660066', 16);
                this.refreshCombatPanel(); this.updateHUD();
                document.getElementById('cbt-status').textContent = 'Ход врага...';
                setTimeout(() => this.enemyTurn(), 700);
                return;
            }

            // Призрак в эфирном плане — атака проходит насквозь
            if (enemy._phased) {
                enemy._phased = false;
                this.addCombatLog('👻 Призрак уходит в эфир! Атака проходит насквозь!');
                this.refreshCombatPanel(); this.updateHUD();
                document.getElementById('cbt-status').textContent = 'Ход врага...';
                setTimeout(() => this.enemyTurn(), 700);
                return;
            }

            const r = RPG.attackEnemy(this.player, enemy);
            const elemTag = r.elemType === 'weak' ? ' ⚡СЛАБОСТЬ!' : r.elemType === 'resist' ? ' 🛡️УСТОЙЧИВ' : '';
            msg = `Атака: ${r.dmg} урона${r.crit?' 💥 КРИТ!':''}${elemTag}`;
            Renderer.addParticle(enemy.x, enemy.y, 'hit');
            Renderer.shakeScreen(r.crit ? 8 : 4);
            Renderer.flashScreen(r.crit ? '#ff6600' : '#ff2200', r.crit ? 0.25 : 0.18);
            const _pe = document.getElementById('portrait-enemy');
            if (_pe) { _pe.classList.add('portrait-hit'); setTimeout(() => _pe.classList.remove('portrait-hit'), 280); }
            Audio.playHit(r.crit);
            Renderer.addFloatingText(enemy.x, enemy.y, '-' + r.dmg,
                r.crit ? '#ff8800' : '#ff4444', r.crit ? 22 : 17);
            // Плавающий текст слабости/сопротивления
            if (r.elemType === 'weak') {
                setTimeout(() => Renderer.addFloatingText(enemy.x, enemy.y, 'СЛАБОСТЬ!', '#ffdd00', 14), 200);
            } else if (r.elemType === 'resist') {
                setTimeout(() => Renderer.addFloatingText(enemy.x, enemy.y, 'УСТОЙЧИВ', '#8888aa', 14), 200);
            }
            if (r.crit) this.checkAchievements('crit');
            this.player.stats.kills = this.player.stats.kills || 0;

            if (enemy.hp <= 0) { this.endCombat(true); return; }

        } else if (action === 'item') {
            // Сначала ищем боевое зелье (яд/огонь), потом лечебное
            const combatPot = this.player.inventory.find(i => i.type==='potion' && i.combatEffect);
            const healPot   = this.player.inventory.find(i => i.type==='potion' && i.healAmount && !i.combatEffect);
            const pot = combatPot || healPot;
            if (pot) {
                const r = RPG.useItem(this.player, pot, enemy);
                msg = r.msg;
                if (r.combatUsed) {
                    // боевое зелье — эффект на врага
                    Renderer.addParticle(enemy.x, enemy.y, 'hit');
                    Audio.playSpell('fire');
                    if (r.dmg) Renderer.addFloatingText(enemy.x, enemy.y, '-'+r.dmg, '#ff6600', 18);
                    if (enemy.hp <= 0) { this.endCombat(true); return; }
                } else {
                    Renderer.addParticle(this.player.x, this.player.y, 'heal');
                    if (pot.healAmount) Renderer.addFloatingText(this.player.x, this.player.y, '+'+pot.healAmount, '#44ff88', 17);
                }
            } else {
                msg = 'Нет зелий!';
            }
        } else if (action === 'flee') {
            if (Math.random() < 0.5) {
                this.endCombatFlee();
                return;
            }
            msg = 'Не удалось сбежать!';
        }

        this.addCombatLog(msg);
        this.refreshCombatPanel();
        this.updateHUD();

        if (action !== 'flee' || msg === 'Не удалось сбежать!') {
            document.getElementById('cbt-status').textContent = 'Ход врага...';
            setTimeout(() => this.enemyTurn(), 700);
        }
    },

    combatCastSpell(spellId) {
        if (this.state !== 'combat') return;
        const enemy = this.combatEnemy;
        const r = RPG.castSpell(this.player, spellId, enemy);
        if (!r.ok) { this.showMsg(r.msg, '#f44'); return; }
        Audio.playSpell(r.particleType || 'magic');
        this.checkAchievements('spell');

        let msg = r.spellName + ': ';
        if (r.dmg) {
            const elemTag = r.elemType === 'weak' ? ' ⚡СЛАБОСТЬ!' : r.elemType === 'resist' ? ' 🛡️УСТОЙЧИВ' : '';
            msg += r.dmg+' урона' + elemTag;
            Renderer.addParticle(enemy.x, enemy.y, r.particleType);
            Renderer.shakeScreen(r.elemType === 'weak' ? 8 : 5);
            const dmgCol = r.particleType === 'fire' ? '#ff6600' :
                           r.particleType === 'ice'  ? '#88ccff' :
                           r.particleType === 'holy' ? '#ffdd44' : '#aa44ff';
            Renderer.addFloatingText(enemy.x, enemy.y, '-' + r.dmg, dmgCol, r.elemType === 'weak' ? 22 : 19);
            if (r.elemType === 'weak') {
                setTimeout(() => Renderer.addFloatingText(enemy.x, enemy.y, 'СЛАБОСТЬ!', '#ffdd00', 14), 200);
            } else if (r.elemType === 'resist') {
                setTimeout(() => Renderer.addFloatingText(enemy.x, enemy.y, 'УСТОЙЧИВ', '#8888aa', 14), 200);
            }
        }
        if (r.heal) {
            msg += '+'+r.heal+' HP';
            Renderer.addParticle(this.player.x, this.player.y, 'heal');
            Renderer.addFloatingText(this.player.x, this.player.y, '+' + r.heal, '#44ff88', 18);
        }
        if (r.msg)  msg = r.msg;

        this.addCombatLog(msg);
        this.refreshCombatPanel();
        this.updateHUD();

        if (enemy.hp <= 0) { this.endCombat(true); return; }
        document.getElementById('cbt-status').textContent = 'Ход врага...';
        setTimeout(() => this.enemyTurn(), 700);
    },

    enemyTurn() {
        if (this.state !== 'combat') return;
        const e = this.combatEnemy;
        const p = this.player;
        const hpPct = e.hp / e.maxHp;

        // Тик статуса игрока
        const pdot = RPG.tickStatus(p);
        if (pdot > 0) this.addCombatLog(`Вы получаете ${pdot} урона от ${p.status}!`);

        // Оглушение (пропускаем ход врага? нет — оглушён игрок)
        if (p.stunned) {
            p.stunned = false;
            this.addCombatLog('💫 Вы оглушены и пропускаете ход!');
            this.updateHUD(); this.refreshCombatPanel();
            document.getElementById('cbt-status').textContent = 'Ваш ход';
            if (p.hp <= 0) { this.endCombat(false); return; }
            return;
        }

        // ── AI: регенерация тролля ────────────────────────
        if (e.ai === 'regen' && hpPct < 0.9) {
            const regen = Math.max(4, Math.floor(e.maxHp * 0.05));
            e.hp = Math.min(e.hp + regen, e.maxHp);
            Renderer.addFloatingText(e.x, e.y, '+'+regen, '#44ff88', 14);
            this.addCombatLog(`🔄 ${e.name} регенерирует +${regen} HP!`);
        }

        // ── AI: самоисцеление ведьмы/лича ────────────────
        if (e.ai === 'hex' && hpPct < 0.45 && e.mp >= 20 && Math.random() < 0.55) {
            const heal = Math.floor(e.maxHp * 0.18);
            e.hp = Math.min(e.hp + heal, e.maxHp);
            e.mp -= 20;
            Renderer.addFloatingText(e.x, e.y, '✨+'+heal, '#cc88ff', 16);
            this.addCombatLog(`✨ ${e.name} произносит заклинание исцеления! +${heal} HP`);
            this.updateHUD(); this.refreshCombatPanel();
            document.getElementById('cbt-status').textContent = 'Ваш ход';
            return; // тратит ход на лечение
        }

        // ── AI: призыв нежити личем ──────────────────────
        if (e.ai === 'summon' && hpPct < 0.5 && !e._summoned) {
            e._summoned = true;
            const summon = RPG.createEnemy('skeleton', p.level + 1, e.x, e.y);
            if (!e._activeSummons) e._activeSummons = [];
            e._activeSummons.push(summon);
            this.addCombatLog(`⚠️ ${e.name} призывает ${summon.name}!`);
            Audio.playSpell('magic');
        }

        // ── AI: Военный клич Короля Гоблинов ─────────────
        if (e.ai === 'warcry' && hpPct < 0.60 && !e._warcryed) {
            e._warcryed = true;
            e.dmg = Math.floor(e.dmg * 1.30);
            const minion = RPG.createEnemy('goblin', Math.max(1, p.level - 1), e.x, e.y);
            if (!e._activeSummons) e._activeSummons = [];
            e._activeSummons.push(minion);
            this.addCombatLog(`📣 ${e.name} издаёт военный клич! Призван гоблин! Урон +30%!`);
            Audio.playSpell('magic');
        }

        // ── AI: Некромант Корвус — призыв и неуязвимость ──
        if (e.ai === 'necroboss') {
            if (hpPct < 0.65 && !e._necroSummoned) {
                e._necroSummoned = true;
                const z = RPG.createEnemy('zombie', p.level + 1, e.x, e.y);
                if (!e._activeSummons) e._activeSummons = [];
                e._activeSummons.push(z);
                this.addCombatLog(`💀 ${e.name} призывает Зомби из тьмы!`);
                Audio.playSpell('magic');
            }
            if (hpPct < 0.40 && !e._phylactery) {
                e._phylactery = true;
                e._invincible = 2;
                this.addCombatLog(`💜 ${e.name} заряжает филактерий! Неуязвим 2 хода!`);
                Audio.playSpell('magic');
                this.updateHUD(); this.refreshCombatPanel();
                document.getElementById('cbt-status').textContent = 'Ваш ход';
                return;
            }
        }

        // ── AI: Болотная Гидра — регенерация голов ────────
        if (e.ai === 'hydra' && hpPct < 0.80) {
            const regen = Math.floor(e.maxHp * 0.07);
            e.hp = Math.min(e.hp + regen, e.maxHp);
            Renderer.addFloatingText(e.x, e.y, '+'+regen, '#44ff88', 14);
            this.addCombatLog(`🐍 ${e.name} отращивает голову! +${regen} HP`);
        }

        // ── AI: Каменный Колосс — счётчик брони ──────────
        if (e.ai === 'colossus') {
            e._colTurn = (e._colTurn || 0) + 1;
            if (e._colTurn % 3 === 0) {
                e._invincible = 1;
                this.addCombatLog(`🗿 ${e.name} закрывается каменной бронёй! (1 ход)`);
                this.updateHUD(); this.refreshCombatPanel();
                document.getElementById('cbt-status').textContent = 'Ваш ход';
                return;
            }
        }

        // ── AI: Призрак Ирис — счётчик тени ──────────────
        if (e.ai === 'shadow') {
            e._shadowTurn = (e._shadowTurn || 0) + 1;
        }

        // ── AI: Мрак Безликий — фаза 1 (призыв теней) ────
        if (e.ai === 'chaos') {
            if (hpPct < 0.70 && !e._chaosSummon1) {
                e._chaosSummon1 = true;
                const s1 = RPG.createEnemy('ghost', p.level + 2, e.x, e.y);
                const s2 = RPG.createEnemy('wyvern', p.level + 1, e.x, e.y);
                if (!e._activeSummons) e._activeSummons = [];
                e._activeSummons.push(s1, s2);
                this.addCombatLog(`🌑 ${e.name} призывает тени!`);
                Audio.playSpell('magic');
            }
            // Фаза 2: истинная форма при HP < 50%
            if (hpPct < 0.50 && !e._chaosPhase2) {
                e._chaosPhase2 = true;
                e.dmg = Math.floor(e.dmg * 1.35);
                e._invincible = 1;
                this.addCombatLog(`⚠️ ФАЗА 2: ${e.name} принимает истинную форму! Урон +35%!`);
                Audio.playSpell('magic');
                this.updateHUD(); this.refreshCombatPanel();
                document.getElementById('cbt-status').textContent = 'Ваш ход';
                return;
            }
        }

        // ── Атака врага ──────────────────────────────────
        const r = RPG.enemyAttackPlayer(e, p);
        let extraLog = '';

        // AI: берсерк (орк, йети) при HP < 30%
        if ((e.ai === 'berserk') && hpPct < 0.30) {
            if (!e._berserked) { e._berserked = true; this.addCombatLog(`😡 ${e.name} впадает в ярость!`); }
            const bonus = Math.floor(r.dmg * 0.5);
            p.hp = Math.max(0, p.hp - bonus);
            r.dmg += bonus;
            extraLog += ' 😡БЕРСЕРК';
        }

        // AI: ярость дракона — двойной удар при HP < 40%
        if (e.ai === 'fury' && hpPct < 0.40 && Math.random() < 0.55) {
            const r2 = RPG.enemyAttackPlayer(e, p);
            extraLog += ` + второй удар (${r2.dmg})`;
            Renderer.addFloatingText(p.x, p.y, '-'+r2.dmg, '#ff4444', 15);
        }

        // AI: яд паука
        if (e.ai === 'venom' && !p.status && Math.random() < 0.40) {
            p.status = 'poison'; p.statusTurns = 3; p.dotDmg = 6;
            extraLog += ' ☠️Яд!';
        }

        // AI: гниение зомби — снижает защиту
        if (e.ai === 'decay' && Math.random() < 0.30 && !p._decayed) {
            p._decayed = true;
            p.def = Math.max(0, p.def - 2);
            extraLog += ' 🦠-2 защиты';
        }

        // AI: ослепление летучей мыши — следующий удар игрока промахивается
        if (e.ai === 'swarm' && Math.random() < 0.35) {
            p._blinded = true;
            extraLog += ' 👁️Слепота!';
        }

        // AI: оглушение голема
        if (e.ai === 'stun' && Math.random() < 0.28) {
            p.stunned = true;
            extraLog += ' 💫Оглушение!';
        }

        // AI: вой волка — усиление следующей атаки
        if (e.ai === 'howl' && hpPct < 0.50 && !e._howled) {
            e._howled = true;
            e._dmgBonus = Math.floor(e.dmg * 0.4);
            this.addCombatLog(`🐺 ${e.name} воет, усиливая следующий удар!`);
        }

        // AI: разбойник крадёт золото
        if (e.ai === 'steal' && Math.random() < 0.20 && p.gold > 0) {
            const stolen = Math.min(Math.floor(8 + Math.random()*12), p.gold);
            p.gold -= stolen;
            extraLog += ` 💰-${stolen} укр.`;
        }

        // AI: трус-гоблин паникует при низком HP
        if (e.ai === 'coward' && hpPct < 0.20 && Math.random() < 0.40) {
            this.addCombatLog(`💨 ${e.name} паникует и не может атаковать!`);
            this.updateHUD(); this.refreshCombatPanel();
            document.getElementById('cbt-status').textContent = 'Ваш ход';
            return;
        }

        // AI: кислота слизня — небольшое DOT
        if (e.ai === 'acid' && !p.status && Math.random() < 0.35) {
            p.status = 'burn'; p.statusTurns = 2; p.dotDmg = 4;
            extraLog += ' 🟢Кислота!';
        }

        // AI: фазирование призрака — следующая атака игрока промажет
        if (e.ai === 'phase' && !e._phased && Math.random() < 0.32) {
            e._phased = true;
            extraLog += ' 👻Эфир!';
        }

        // AI: пикирование виверны — усиленный удар при HP < 60%
        if (e.ai === 'dive' && hpPct < 0.60 && Math.random() < 0.38) {
            const bonus = Math.floor(r.dmg * 0.55);
            p.hp = Math.max(0, p.hp - bonus);
            r.dmg += bonus;
            extraLog += ' 🦅ПИКЕ!';
        }

        // ── AI: Болотная Гидра — доп. удары ──────────────
        if (e.ai === 'hydra') {
            if (hpPct < 0.50) {
                const r2 = RPG.enemyAttackPlayer(e, p);
                p.hp = Math.max(0, p.hp - 0); // уже вычтено в enemyAttackPlayer
                Renderer.addFloatingText(p.x, p.y, '-'+r2.dmg, '#ff4444', 15);
                extraLog += ` 🐍 вторая голова (${r2.dmg})`;
            }
            if (hpPct < 0.30) {
                const r3 = RPG.enemyAttackPlayer(e, p);
                Renderer.addFloatingText(p.x, p.y, '-'+r3.dmg, '#ff4444', 15);
                extraLog += ` 🐍 третья голова (${r3.dmg})`;
            }
        }

        // ── AI: Ледяной Великан — метель/заморозка ────────
        if (e.ai === 'frost') {
            if (hpPct < 0.50 && Math.random() < 0.45) {
                const frostBonus = Math.floor(r.dmg * 0.6);
                p.hp = Math.max(0, p.hp - frostBonus);
                r.dmg += frostBonus;
                p.stunned = true;
                extraLog += ` ❄️МЕТЕЛЬ!`;
            } else if (!p.status && Math.random() < 0.30) {
                p.status = 'freeze'; p.statusTurns = 1; p.dotDmg = 0;
                p.stunned = true;
                extraLog += ' ❄️Заморозка!';
            }
        }

        // ── AI: Каменный Колосс — сокрушительный удар ────
        if (e.ai === 'colossus' && hpPct < 0.40 && !e._shattered) {
            e._shattered = true;
            const shatBonus = Math.floor(r.dmg * 1.5);
            p.hp = Math.max(0, p.hp - shatBonus);
            r.dmg += shatBonus;
            extraLog += ` 💥СОКРУШЕНИЕ!`;
        }

        // ── AI: Призрак Ирис — удар из тени ──────────────
        if (e.ai === 'shadow') {
            if ((e._shadowTurn || 0) % 3 === 0) {
                const shadowBonus = Math.floor(r.dmg * 2);
                p.hp = Math.max(0, p.hp - shadowBonus);
                r.dmg += shadowBonus;
                extraLog += ' 🗡️ИЗ ТЕНИ!';
            }
            if (hpPct < 0.50 && !p.status && Math.random() < 0.40) {
                p.status = 'poison'; p.statusTurns = 4; p.dotDmg = 8;
                extraLog += ' ☠️Яд клинка!';
            }
        }

        // ── AI: Вампиризм Корвуса ─────────────────────────
        if (e.ai === 'necroboss') {
            const vamp = Math.floor(r.dmg * 0.25);
            if (vamp > 0) {
                e.hp = Math.min(e.hp + vamp, e.maxHp);
                Renderer.addFloatingText(e.x, e.y, '🩸+'+vamp, '#cc44ff', 13);
                extraLog += ` 🩸+${vamp}`;
            }
        }

        // ── AI: Мрак Безликий — дебаффы и фаза 3 (вампиризм) ──
        if (e.ai === 'chaos') {
            // Случайный дебафф (25% шанс)
            if (!p.status && Math.random() < 0.25) {
                const pick = Math.floor(Math.random() * 3);
                if (pick === 0) { p.status = 'poison'; p.statusTurns = 3; p.dotDmg = 12; extraLog += ' ☠️Хаос-яд!'; }
                if (pick === 1) { p.status = 'burn';   p.statusTurns = 2; p.dotDmg = 14; extraLog += ' 🔥Хаос-огонь!'; }
                if (pick === 2) { p.stunned = true; extraLog += ' 💫Оглушение хаоса!'; }
            }
            // Фаза 3: вампиризм при HP < 30%
            if (hpPct < 0.30) {
                if (!e._chaosPhase3) { e._chaosPhase3 = true; this.addCombatLog(`⚠️ ФАЗА 3: Мрак поглощает всё вокруг!`); }
                const vamp3 = Math.floor(r.dmg * 0.40);
                if (vamp3 > 0) {
                    e.hp = Math.min(e.hp + vamp3, e.maxHp);
                    Renderer.addFloatingText(e.x, e.y, '🌑+'+vamp3, '#660066', 14);
                    extraLog += ` 🌑+${vamp3}HP`;
                }
            }
        }

        const msg = `${e.name}: ${r.dmg} урона${r.crit?' 💥':''}${extraLog}`;
        this.addCombatLog(msg);
        Audio.playHit(r.crit);
        e.isAtk = true;
        setTimeout(() => { if (e) e.isAtk = false; }, 420);
        Renderer.addParticle(p.x, p.y, 'hit', 4);
        Renderer.shakeScreen(r.crit ? 5 : 3);
        Renderer.flashScreen('#8800ff', r.crit ? 0.25 : 0.18);
        const _pp = document.getElementById('portrait-player');
        if (_pp) { _pp.classList.add('portrait-hit'); setTimeout(() => _pp.classList.remove('portrait-hit'), 280); }
        Renderer.addFloatingText(p.x, p.y, '-' + r.dmg,
            r.crit ? '#ff6600' : '#ffaa44', r.crit ? 20 : 16);
        this.updateHUD();
        this.refreshCombatPanel();

        // ── Атаки призванных существ ────────────────────
        if (e._activeSummons && e._activeSummons.length > 0) {
            for (const summon of e._activeSummons) {
                const sDmg = Math.max(1, Math.floor(summon.dmg * (0.5 + Math.random() * 0.5)) - Math.floor(p.def * 0.3));
                p.hp = Math.max(0, p.hp - sDmg);
                this.addCombatLog(`⚔️ ${summon.name} атакует! ${sDmg} урона`);
                Renderer.addFloatingText(p.x, p.y, '-'+sDmg, '#ff8844', 13);
            }
            this.updateHUD();
            this.refreshCombatPanel();
        }

        document.getElementById('cbt-status').textContent = 'Ваш ход';

        if (p.hp <= 0) { this.endCombat(false); return; }
    },

    endCombat(won) {
        clearInterval(this._blinkInterval);
        const e = this.combatEnemy;
        const hpBefore = this.player.hp;
        this.state = 'playing';
        this.closePanel('combat');

        // Скрыть boss bar
        document.getElementById('boss-bar').style.display = 'none';

        if (won) {
            // Лут
            const loot = RPG.generateLoot(e);
            let goldGained = 0;
            loot.forEach(it => {
                if (it.type==='gold') { this.player.gold += it.value; goldGained += it.value; }
                else RPG.addToInventory(this.player, it);
            });
            // Уникальный лут мини-босса
            if (e.uniqueLoot) {
                const ul = e.uniqueLoot;
                const item = Object.assign({ id: ul.id, type: ul.type, name: ul.name }, ul.opts);
                RPG.addToInventory(this.player, item);
                setTimeout(() => this.showMsg('⚜️ ЛЕГЕНДАРНЫЙ: ' + ul.name + '!', '#ffd700'), 800);
            }
            // Опыт
            this.player.exp += e.exp;
            this.player.stats.kills = (this.player.stats.kills||0) + 1;
            if (e.isMini) {
                this.showMsg(`⚔️ Мини-босс повержен! +${e.exp} XP +${goldGained} 💰`, '#ff6644');
            } else {
                this.showMsg(`Победа! +${e.exp} XP +${goldGained} 💰`, '#ffd700');
            }
            Renderer.addParticle(e.x, e.y, 'death', 12);
            Renderer.addParticle(e.x, e.y, 'gold', 5);
            this.enemies = this.enemies.filter(en => en !== e);
            // Квест
            this.updateQuestProgress('kill', e.type);
            // Бестиарий
            this.player.bestiary = this.player.bestiary || {};
            const prevKills = this.player.bestiary[e.type] || 0;
            this.player.bestiary[e.type] = prevKills + 1;
            if (prevKills === 0) this.showMsg('📖 Бестиарий: ' + e.name + ' открыт!', '#88aaff');
            // Уровень
            if (RPG.checkLevelUp(this.player)) this.triggerLevelUp();
            // Достижения
            this.checkAchievements('kill');
            if (e.isBoss || e.isMini) this.checkAchievements('kill_boss');
            if (e.isMini) this.checkAchievements('mini_boss_kill');
            if (e.type === 'chaos_lord') this.checkAchievements('mega_boss');
            if (hpBefore >= this.player.maxHp) this.checkAchievements('no_damage');
            this.checkAchievements('gold');
            this.checkAchievements('bestiary');
            Audio.playVictory();
            Audio.playTheme(this.mapId);
        } else {
            this.player.hp = Math.max(1, Math.floor(this.player.maxHp * 0.3));
            this.player.gold = Math.max(0, this.player.gold - 15);
            this.showMsg('Поражение! Потеряно 15 💰', '#e74c3c');
            Audio.playDeath();
            Audio.playTheme(this.mapId);
        }

        this.combatEnemy = null;
        if (this.player) {
            this.player.deathSaveUsed = false; // сброс смертного рывка
            this.player._decayed  = false;
            this.player.stunned   = false;
            this.player._blinded  = false;
        }
        // Сбросить флаги врага
        if (e) { e._phased = false; e._berserked = false; e._activeSummons = null; }
        this.updateHUD();
        this.autoSave();
    },

    endCombatFlee() {
        clearInterval(this._blinkInterval);
        this.state = 'playing';
        this.closePanel('combat');
        document.getElementById('boss-bar').style.display = 'none';
        this.combatEnemy = null;
        if (this.player) this.player.deathSaveUsed = false;
        this.showMsg('Сбежали!', '#aaa');
        Audio.playTheme(this.mapId);
    },

    // ══════════════════════════════════════════
    // ПОВЫШЕНИЕ УРОВНЯ
    // ══════════════════════════════════════════
    triggerLevelUp() {
        this.player.perkPoints = (this.player.perkPoints || 0) + 1;
        this.showMsg('🌟 Уровень ' + this.player.level + '! Получено +1 очко таланта', '#ffd700');
        Renderer.addParticle(this.player.x, this.player.y, 'holy', 15);
        Renderer.shakeScreen(3);
        Audio.playLevelUp();
        this.checkAchievements('level');
        // Открыть дерево перков с небольшой задержкой
        setTimeout(() => {
            this.renderPerkPanel();
            this.openPanel('perk');
        }, 400);
    },

    // ══════════════════════════════════════════
    // HUD ОБНОВЛЕНИЕ
    // ══════════════════════════════════════════
    updateHUD() {
        const p = this.player;
        if (!p) return;
        const s = RPG.getTotalStats(p);
        document.getElementById('h-hp').textContent    = Math.floor(p.hp)+'/'+p.maxHp;
        document.getElementById('h-mp').textContent    = Math.floor(p.mp)+'/'+p.maxMp;
        document.getElementById('h-lv').textContent    = p.level;
        document.getElementById('h-gold').textContent  = p.gold;
        document.getElementById('h-atk').textContent   = s.damage;
        document.getElementById('h-def').textContent   = s.defense;
        document.getElementById('b-hp').style.width    = (p.hp/p.maxHp*100)+'%';
        document.getElementById('b-mp').style.width    = (p.mp/p.maxMp*100)+'%';
        document.getElementById('b-exp').style.width   = (p.exp/p.expNext*100)+'%';
    },

    // ══════════════════════════════════════════
    // СООБЩЕНИЯ
    // ══════════════════════════════════════════
    showMsg(text, color) {
        const overlay = document.getElementById('msg-overlay');
        const div = document.createElement('div');
        div.className = 'msg-pop';
        div.textContent = text;
        if (color) div.style.color = color;
        overlay.appendChild(div);
        setTimeout(() => { if (div.parentNode) div.parentNode.removeChild(div); }, 3000);
    },

    // ══════════════════════════════════════════
    // СОХРАНЕНИЕ / ЗАГРУЗКА
    // ══════════════════════════════════════════
    _updatePlayTime() {
        const elapsed = Math.floor((Date.now() - this._sessionStart) / 1000);
        this.player._playTime = (this.player._playTime || 0) + elapsed;
        this._sessionStart = Date.now();
    },

    saveGame() {
        this._updatePlayTime();
        const ok = RPG.save({
            player:    this.player,
            mapId:     this.mapId,
            dayCount:  this.dayCount,
            timeOfDay: this.timeOfDay,
        }, this.saveSlot);
        this.showMsg(ok ? '💾 Сохранено!' : 'Ошибка!', ok ? '#4f4' : '#f44');
        // Индикатор
        const ind = document.getElementById('save-ind');
        if (ind) {
            ind.style.opacity = '1';
            clearTimeout(ind._t);
            ind._t = setTimeout(() => { ind.style.opacity = '0'; }, 2000);
        }
    },

    autoSave() {
        this._updatePlayTime();
        RPG.save({
            player:    this.player,
            mapId:     this.mapId,
            dayCount:  this.dayCount,
            timeOfDay: this.timeOfDay,
        }, this.saveSlot);
    },

    // ══════════════════════════════════════════
    // ДЕРЕВО ПЕРКОВ
    // ══════════════════════════════════════════
    renderPerkPanel() {
        const p = this.player;
        const tree = RPG.PERK_TREE[p.class];
        if (!tree) return;

        const pts = p.perkPoints || 0;
        document.getElementById('perk-points-display').textContent =
            pts > 0 ? `⭐ Очков: ${pts}` : 'Очков: 0';
        document.getElementById('perk-class-name').textContent =
            RPG.CLASSES[p.class].name + ' · Изучено: ' + (p.perks || []).length + ' перков';

        const container = document.getElementById('perk-branches');
        container.innerHTML = '';

        tree.branches.forEach(branch => {
            const col = document.createElement('div');
            col.className = 'perk-branch';
            col.innerHTML = `<div class="perk-branch-title">${branch.icon} ${branch.name}</div>`;

            branch.perks.forEach((perk, idx) => {
                if (idx > 0) {
                    const prevPerk = branch.perks[idx - 1];
                    const prevLearned = (p.perks || []).includes(prevPerk.id);
                    const curLearned  = (p.perks || []).includes(perk.id);
                    const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
                    svg.setAttribute('width', '100%'); svg.setAttribute('height', '18');
                    svg.style.cssText = 'display:block;margin:2px 0';
                    const ln = document.createElementNS('http://www.w3.org/2000/svg', 'line');
                    ln.setAttribute('x1', '50%'); ln.setAttribute('y1', '0');
                    ln.setAttribute('x2', '50%'); ln.setAttribute('y2', '18');
                    ln.setAttribute('stroke', (prevLearned && curLearned) ? '#ffd700' : '#2a2a4a');
                    ln.setAttribute('stroke-width', (prevLearned && curLearned) ? '2' : '1.5');
                    if (!(prevLearned && curLearned)) ln.setAttribute('stroke-dasharray', '5,3');
                    svg.appendChild(ln);
                    col.appendChild(svg);
                }

                const learned   = (p.perks || []).includes(perk.id);
                const req       = RPG.getPerkPrereq(p.class, perk.id);
                const prereqOk  = !req || (p.perks || []).includes(req);
                const available = prereqOk && !learned && pts > 0;

                const card = document.createElement('div');
                card.className = 'perk-card ' + (learned ? 'learned' : available ? 'available' : 'locked');
                card.innerHTML = `
                    <div class="perk-tier-badge">T${perk.tier}</div>
                    <div class="perk-icon">${perk.icon}</div>
                    <div class="perk-name">${perk.name}</div>
                    <div class="perk-desc">${perk.desc}</div>`;

                if (available) {
                    card.onclick = () => {
                        const r = RPG.applyPerk(p, perk.id);
                        this.showMsg(r.ok ? '✨ ' + r.msg : r.msg, r.ok ? '#ffd700' : '#f44');
                        if (r.ok) {
                            this.updateHUD();
                            this.renderPerkPanel();
                            Renderer.addParticle(p.x, p.y, 'holy', 8);
                        }
                    };
                }
                col.appendChild(card);
            });

            container.appendChild(col);
        });
    },

    // ══════════════════════════════════════════
    // КРАФТИНГ
    // ══════════════════════════════════════════
    _craftActiveCategory: 'potions',
    _craftSelectedRecipe: null,

    renderCraftPanel() {
        const cats = { potions:'🧪 Зелья', alchemy:'⚗️ Алхимия', runes:'🔮 Руны', enhance:'⚙️ Улучшения', equipment:'⚔️ Снаряжение' };
        const tabs = document.getElementById('craft-tabs');
        tabs.innerHTML = '';
        Object.entries(cats).forEach(([key, label]) => {
            const btn = document.createElement('button');
            btn.className = 'craft-tab' + (this._craftActiveCategory === key ? ' active' : '');
            btn.textContent = label;
            btn.onclick = () => { this._craftActiveCategory = key; this._craftSelectedRecipe = null; this.renderCraftPanel(); };
            tabs.appendChild(btn);
        });

        const list = document.getElementById('craft-recipe-list');
        list.innerHTML = '';
        RPG.CRAFT_RECIPES.filter(r => r.category === this._craftActiveCategory).forEach(recipe => {
            const canCraft = RPG.canCraft(this.player, recipe.id);
            const btn = document.createElement('button');
            btn.className = 'craft-recipe-btn' + (canCraft ? ' can-craft' : '') + (this._craftSelectedRecipe === recipe.id ? ' selected' : '');
            btn.textContent = recipe.icon + ' ' + recipe.name;
            btn.onclick = () => { this._craftSelectedRecipe = recipe.id; this._renderCraftDetail(recipe); };
            list.appendChild(btn);
        });

        if (this._craftSelectedRecipe) {
            const rec = RPG.CRAFT_RECIPES.find(r => r.id === this._craftSelectedRecipe);
            if (rec) this._renderCraftDetail(rec);
        }
    },

    _renderCraftDetail(recipe) {
        const detail = document.getElementById('craft-detail');
        const canCraft = RPG.canCraft(this.player, recipe.id);

        const ingRows = recipe.ingredients.map(ing => {
            const playerQty = this.player.inventory
                .filter(i => i.id === ing.id || i.id.startsWith(ing.id + '_'))
                .reduce((sum, i) => sum + (i.qty || 1), 0);
            const have = playerQty >= ing.qty;
            const ld = RPG.LOOT_DB[ing.id];
            const name = ld ? ld.n : ing.id;
            return `<div class="craft-ing ${have ? 'have' : 'missing'}">
                <span>${name}</span><span>${playerQty}/${ing.qty} ${have ? '✓' : '✗'}</span>
            </div>`;
        }).join('');

        const r = recipe.result;
        const statStr = [
            r.opts.damage   ? `⚔️ +${r.opts.damage} урон`   : '',
            r.opts.defense  ? `🛡️ +${r.opts.defense} защита` : '',
            r.opts.healAmount ? `❤️ +${r.opts.healAmount} HP` : '',
            r.opts.restoreMp  ? `💧 +${r.opts.restoreMp} МА` : '',
            r.opts.bonusStr ? `💪 +${r.opts.bonusStr} СИЛ`  : '',
            r.opts.bonusDef ? `🛡️ +${r.opts.bonusDef} ЗАЩ`  : '',
            r.opts.bonusMag ? `✨ +${r.opts.bonusMag} МАГ`  : '',
            r.opts.bonusHp  ? `❤️ +${r.opts.bonusHp} HP`   : '',
            r.opts.bonusMp  ? `💧 +${r.opts.bonusMp} МА`   : '',
        ].filter(Boolean).join(' · ') || (r.opts.desc || '');

        const rarityColor = RPG.RARITY_COLORS[r.opts.rarity] || '#888';
        const rarityStr   = r.opts.rarity ? `<span style="color:${rarityColor}">${r.opts.rarity}</span> · ` : '';

        detail.innerHTML = `
            <div style="font-size:28px;text-align:center;margin-bottom:5px">${recipe.icon}</div>
            <div style="font-size:14px;font-weight:bold;color:#ffd700;text-align:center;margin-bottom:10px">${recipe.name}</div>
            <div style="font-size:9px;color:#555;text-transform:uppercase;margin-bottom:5px">Ингредиенты:</div>
            ${ingRows}
            <div style="margin-top:12px;font-size:9px;color:#555;text-transform:uppercase;margin-bottom:5px">Результат:</div>
            <div style="background:rgba(12,12,30,0.8);border:1px solid #2a2a4a;border-radius:4px;padding:8px;font-size:10px;color:#bbb">
                ${r.opts.icon || ''} <b style="color:#eee">${r.name}</b><br>
                ${rarityStr}${statStr}
            </div>
            <button onclick="Game._doCraft('${recipe.id}')"
                style="width:100%;margin-top:12px;padding:9px;
                       background:${canCraft ? 'rgba(15,50,15,0.9)' : 'rgba(12,12,12,0.7)'};
                       border:2px solid ${canCraft ? '#27ae60' : '#2a2a2a'};
                       border-radius:5px;color:${canCraft ? '#4f4' : '#444'};
                       font-size:13px;cursor:${canCraft ? 'pointer' : 'not-allowed'}">
                ${canCraft ? '⚒️ Создать' : '⚒️ Не хватает материалов'}
            </button>`;
    },

    _doCraft(recipeId) {
        const r = RPG.craft(this.player, recipeId);
        this.showMsg(r.msg, r.ok ? '#4f4' : '#f44');
        if (r.ok) {
            Renderer.addParticle(this.player.x, this.player.y, 'magic', 8);
            this._craftSelectedRecipe = recipeId;
            this.renderCraftPanel();
            this.updateHUD();
            this.checkAchievements('craft');
            this.checkAchievements('inv_full');
        }
    },

    // ══════════════════════════════════════════
    // СЮЖЕТНЫЕ КВЕСТЫ — ДИАЛОГ С NPC
    // ══════════════════════════════════════════
    _handleQuestNPC(npc) {
        const p = this.player;
        const opts = [];

        // Сюжетные квесты от этого NPC
        RPG.STORY_QUESTS.filter(sq => sq.giverNpc === npc.name).forEach(sq => {
            const pq = RPG.getPlayerStoryQuest(p, sq.id);

            if (!pq) {
                // Предложить взять квест
                opts.push({
                    label: '📋 ' + sq.icon + ' ' + sq.name,
                    action: () => {
                        const stage0 = sq.stages[0];
                        this.showDialog(npc.name, stage0.dialogBefore, [
                            { label: '✅ Принять задание', action: () => {
                                RPG.giveStoryQuest(p, sq.id);
                                this.closePanel('dialog');
                                this.showMsg('📜 Новый квест: ' + sq.name, '#ffd700');
                                this.renderQuestPanel();
                            }},
                            { label: '❌ Не сейчас', action: () => this.closePanel('dialog') }
                        ]);
                    }
                });
            } else if (!pq.done) {
                // Есть ли завершённый неотмеченный этап?
                const lastNum = pq.completedStages.filter(s => typeof s === 'number');
                const lastIdx = lastNum.length > 0 ? lastNum[lastNum.length - 1] : -1;
                const hasUnack = lastIdx >= 0 && !pq.completedStages.includes('ack_' + lastIdx);

                if (hasUnack) {
                    const completedStage = sq.stages[lastIdx];
                    const nextStage = sq.stages[pq.stageIdx];
                    opts.push({
                        label: '📜 ' + sq.name + ' — отчитаться',
                        action: () => {
                            // Выдать награду за этап
                            p.exp  += completedStage.reward.exp;
                            p.gold += completedStage.reward.gold;
                            pq.completedStages.push('ack_' + lastIdx);
                            this.showMsg(`✓ Этап "${completedStage.title}" · +${completedStage.reward.exp} XP · +${completedStage.reward.gold} зол.`, '#ffd700');
                            if (RPG.checkLevelUp(p)) this.triggerLevelUp();
                            this.updateHUD();
                            this.renderQuestPanel();

                            const afterText = completedStage.dialogAfter + (nextStage && !pq.done ? '\n\n► ' + nextStage.title + ': ' + nextStage.desc : '');
                            this.showDialog(npc.name, afterText, [
                                { label: pq.done ? '🎉 Завершить' : '► Продолжить', action: () => this.closePanel('dialog') }
                            ]);
                        }
                    });
                } else {
                    // Напомнить текущий этап
                    const cur = sq.stages[pq.stageIdx];
                    opts.push({
                        label: '📜 ' + sq.name + ' (текущее задание)',
                        action: () => {
                            const text = (cur.dialogBefore || cur.desc) + '\n\nПрогресс: ' + pq.progress + '/' + cur.need;
                            this.showDialog(npc.name, text, [
                                { label: 'Понял, иду', action: () => this.closePanel('dialog') }
                            ]);
                        }
                    });
                }
            } else {
                opts.push({
                    label: '✓ ' + sq.name + ' (завершён)',
                    action: () => {
                        this.showDialog(npc.name, 'Ты уже выполнил это задание. Да пребудет с тобой удача, герой!', [
                            { label: 'До свидания', action: () => this.closePanel('dialog') }
                        ]);
                    }
                });
            }
        });

        // Обычные квесты от любого quest-NPC
        const unlocked = this.getUnlockedQuests();
        unlocked.forEach(qdb => {
            const has = p.quests.find(q => q.id === qdb.id);
            if (!has) {
                opts.push({ label: '📋 ' + qdb.name, action: () => {
                    this.giveQuest(qdb.id);
                    this.closePanel('dialog');
                    this.renderQuestPanel();
                }});
            }
        });

        opts.push({ label: '❌ Уйти', action: () => this.closePanel('dialog') });

        const hasNew = opts.some(o => o.label.startsWith('📋'));
        const hasReport = opts.some(o => o.label.includes('отчитаться'));
        const msg = hasReport ? 'Жду твоего доклада, странник!' :
                    hasNew    ? 'Есть задания для тебя, странник!' :
                                'Нет новых заданий. Возвращайся позже.';
        this.showDialog(npc.name, msg, opts);
    },

    // ══════════════════════════════════════════
    // МАРКЕРЫ КВЕСТОВ НА КАРТЕ
    // ══════════════════════════════════════════
    _getQuestMarkerData() {
        const p = this.player;
        const markers = {};

        RPG.STORY_QUESTS.forEach(sq => {
            const pq = RPG.getPlayerStoryQuest(p, sq.id);
            if (!pq) {
                // Квест ещё не взят — показать "!" у выдающего NPC
                if (!markers[sq.giverNpc]) markers[sq.giverNpc] = 'give';
            } else if (!pq.done) {
                const lastNum  = pq.completedStages.filter(s => typeof s === 'number');
                const lastIdx  = lastNum.length > 0 ? lastNum[lastNum.length - 1] : -1;
                const hasUnack = lastIdx >= 0 && !pq.completedStages.includes('ack_' + lastIdx);
                if (hasUnack) {
                    markers[sq.giverNpc] = 'advance'; // мигающий "?" — надо отчитаться
                }
            } else {
                if (!markers[sq.giverNpc]) markers[sq.giverNpc] = 'complete';
            }
        });

        // Обычные квесты — ставить "!" у quest-NPC если есть незанятые квесты
        if (this.getUnlockedQuests().some(q => !p.quests.find(pq => pq.id === q.id))) {
            this.npcs.forEach(npc => {
                if (npc.type === 'quest' && !markers[npc.name]) markers[npc.name] = 'give';
            });
        }

        return markers;
    },

    _getMinimapQuestDots() {
        const p = this.player;
        const dots = [];
        const markers = this._getQuestMarkerData();

        this.npcs.forEach(npc => {
            if (markers[npc.name]) {
                dots.push({ x: Math.round(npc.x), y: Math.round(npc.y), type: markers[npc.name] });
            }
        });

        // Цели visit-этапов — показать на миникарте
        p.quests.filter(q => q.isStory && !q.done).forEach(pq => {
            const sq = RPG.getStoryQuest(pq.id);
            if (!sq) return;
            const stage = sq.stages[pq.stageIdx];
            if (stage && stage.type === 'visit') {
                const portal = this.decorations.find(d => d.type === 'portal' && d.destination === stage.target);
                if (portal) dots.push({ x: Math.round(portal.x), y: Math.round(portal.y), type: 'target' });
            }
        });

        return dots;
    },

    // ══════════════════════════════════════════
    // БЕСТИАРИЙ
    // ══════════════════════════════════════════
    renderBestiaryPanel() {
        const grid = document.getElementById('bestiary-grid');
        if (!grid) return;
        grid.innerHTML = '';
        const p = this.player;
        const bestiary = p.bestiary || {};

        const WEAK_ICONS = { fire:'🔥', ice:'❄️', holy:'✨', magic:'🔮', physical:'⚔️', poison:'☠️' };
        const WEAK_NAMES = { fire:'Огонь', ice:'Лёд', holy:'Святость', magic:'Магия', physical:'Физика', poison:'Яд' };

        Object.entries(RPG.ENEMY_DB).forEach(([type, db]) => {
            const kills = bestiary[type] || 0;
            const seen  = kills > 0;

            const card = document.createElement('div');
            card.className = 'beast-card ' + (seen ? 'seen' : 'unseen');

            // Мини-canvas с портретом
            const cvs = document.createElement('canvas');
            cvs.className = 'beast-canvas';
            cvs.width  = 64;
            cvs.height = 72;
            card.appendChild(cvs);

            // Рисуем портрет через Renderer (масштаб ~0.7 от 90x100)
            // Создаём временный объект с нужными полями
            Renderer.drawEnemyPortrait({ type }, cvs);

            const nameEl = document.createElement('div');
            nameEl.className = 'beast-name';
            nameEl.textContent = seen ? db.name : '???';
            card.appendChild(nameEl);

            if (seen) {
                const loreEl = document.createElement('div');
                loreEl.className = 'beast-lore';
                loreEl.textContent = db.lore || '';
                card.appendChild(loreEl);

                const killsEl = document.createElement('div');
                killsEl.className = 'beast-kills';
                killsEl.textContent = '⚔ Убито: ' + kills;
                card.appendChild(killsEl);

                // Слабость/сопротивление видны если: нашёл лор-записку или убил 3+ врагов
                const foundNotes = p.foundNotes || [];
                const hasLoreHint = RPG.LORE_NOTES.some(n => n.reveals && n.reveals.enemy === type && foundNotes.includes(n.id));
                const knowsWeakness = hasLoreHint || kills >= 3;

                if (db.weakness) {
                    const weakEl = document.createElement('div');
                    weakEl.className = 'beast-weak';
                    if (knowsWeakness) {
                        weakEl.textContent = '⚡ Слабость: ' + (WEAK_ICONS[db.weakness]||'') + ' ' + (WEAK_NAMES[db.weakness]||db.weakness);
                    } else {
                        weakEl.textContent = '⚡ Слабость: ???';
                        weakEl.style.opacity = '0.5';
                    }
                    card.appendChild(weakEl);
                }
                if (db.resist) {
                    const resEl = document.createElement('div');
                    resEl.className = 'beast-resist';
                    if (knowsWeakness) {
                        resEl.textContent = '🛡 Устойчив: ' + (WEAK_ICONS[db.resist]||'') + ' ' + (WEAK_NAMES[db.resist]||db.resist);
                    } else {
                        resEl.textContent = '🛡 Устойчив: ???';
                        resEl.style.opacity = '0.5';
                    }
                    card.appendChild(resEl);
                }
            }

            grid.appendChild(card);
        });
    },

    // NPC_DIALOGS заполняется DataLoader из data/world.json

    _startBranchDialog(npc) {
        const tree = this.NPC_DIALOGS[npc.name];
        if (!tree) {
            this.showDialog(npc.name, 'Привет, путник!',
                [{ label:'Пока', action:()=>this.closePanel('dialog') }]);
            return;
        }
        this._showDialogNode(npc.name, tree, 'start');
    },

    _showDialogNode(npcName, tree, nodeKey) {
        const node = tree[nodeKey];
        if (!node) { this.closePanel('dialog'); return; }
        const opts = (node.opts || []).map(opt => ({
            label: opt.label,
            action: () => {
                // Стоимость
                if (opt.cost) {
                    if (this.player.gold < opt.cost) {
                        this.showMsg('Недостаточно золота!', '#f44');
                        this.closePanel('dialog');
                        return;
                    }
                    this.player.gold -= opt.cost;
                }
                // Награды
                const rew = opt.reward || (node.reward);
                if (rew) {
                    if (rew.exp)  { this.player.exp += rew.exp; this.showMsg('+'+rew.exp+' опыта', '#ffd700'); if (RPG.checkLevelUp(this.player)) this.triggerLevelUp(); }
                    if (rew.hp)   { this.player.hp = Math.min(this.player.maxHp, this.player.hp + rew.hp); this.showMsg('HP восстановлено!', '#4f4'); }
                    if (rew.mp)   { this.player.mp = Math.min(this.player.maxMp, this.player.mp + rew.mp); this.showMsg('MP восстановлено!', '#88f'); }
                    if (rew.cure) { this.player.status = null; this.player.statusTurns = 0; }
                    if (rew.item) {
                        const ld = RPG.LOOT_DB[rew.item];
                        if (ld) {
                            const it = RPG.createItem(rew.item+'_gift', ld.t, ld.n, { value:ld.v, qty:rew.qty||1, stackable:true, icon:ld.icon||'📦' });
                            RPG.addToInventory(this.player, it);
                            this.showMsg('Получено: '+it.name+(rew.qty>1?' ×'+rew.qty:''), '#4f4');
                        }
                    }
                    if (rew.buff) {
                        this.player.buffs = this.player.buffs || [];
                        this.player.buffs.push({ stat:rew.buff, val:1.5, expires: Date.now()+30000 });
                        this.showMsg('Бафф активен!', '#88f');
                    }
                    this.updateHUD();
                }
                if (opt.next) this._showDialogNode(npcName, tree, opt.next);
                else this.closePanel('dialog');
            }
        }));
        this.showDialog(npcName, node.text, opts);
    },

    // ══════════════════════════════════════════
    // ЖУРНАЛ ЛОРА
    // ══════════════════════════════════════════
    renderLorePanel() {
        const container = document.getElementById('lore-list');
        if (!container) return;
        container.innerHTML = '';
        const found = this.player.foundNotes || [];
        const all = RPG.LORE_NOTES;
        const total = all.length;
        const cnt   = found.length;
        const header = document.getElementById('lore-count');
        if (header) header.textContent = `Найдено: ${cnt} / ${total}`;

        if (cnt === 0) {
            container.innerHTML = '<div class="lore-empty">Вы ещё не нашли ни одной записи.<br>Исследуйте локации!</div>';
            return;
        }

        const MAP_NAMES = { village:'Деревня', forest:'Лес', dungeon:'Подземелье', cave:'Пещера', mountain:'Горы', swamp:'Болото', ruins:'Руины', abyss:'Бездна' };
        // Группируем по локациям
        const byMap = {};
        all.filter(n => found.includes(n.id)).forEach(n => {
            if (!byMap[n.mapId]) byMap[n.mapId] = [];
            byMap[n.mapId].push(n);
        });

        Object.entries(byMap).forEach(([mapId, notes]) => {
            const groupEl = document.createElement('div');
            groupEl.className = 'lore-group';
            const titleEl = document.createElement('div');
            titleEl.className = 'lore-group-title';
            titleEl.textContent = MAP_NAMES[mapId] || mapId;
            groupEl.appendChild(titleEl);

            notes.forEach(note => {
                const card = document.createElement('div');
                card.className = 'lore-card';
                const hintHtml = note.reveals && note.reveals.hint
                    ? `<div class="lc-hint">💡 ${note.reveals.hint}</div>` : '';
                card.innerHTML = `
                    <div class="lc-head"><span class="lc-icon">${note.icon}</span><span class="lc-title">${note.title}</span></div>
                    <div class="lc-text">${note.text}</div>${hintHtml}`;
                groupEl.appendChild(card);
            });

            // Показать статус сбора записок локации
            const totalInMap = all.filter(n => n.mapId === mapId).length;
            const foundInMap = all.filter(n => n.mapId === mapId && found.includes(n.id)).length;
            const statusEl = document.createElement('div');
            statusEl.className = 'lore-map-status';
            if (foundInMap >= totalInMap) {
                statusEl.textContent = `✅ Все записки собраны! (${foundInMap}/${totalInMap})`;
                statusEl.style.color = '#44ff88';
            } else {
                statusEl.textContent = `📜 ${foundInMap}/${totalInMap} записок`;
                statusEl.style.color = '#888';
            }
            groupEl.appendChild(statusEl);
            container.appendChild(groupEl);
        });
    },

    // Бонус за сбор всех записок в локации
    _checkLoreLocationBonus(mapId) {
        const all = RPG.LORE_NOTES.filter(n => n.mapId === mapId);
        const found = this.player.foundNotes || [];
        const allCollected = all.every(n => found.includes(n.id));
        if (!allCollected) return;

        // Уже получал бонус за эту локацию?
        this.player._loreBonus = this.player._loreBonus || [];
        if (this.player._loreBonus.includes(mapId)) return;
        this.player._loreBonus.push(mapId);

        // Бонус: +3 к случайному стату + золото
        const bonuses = [
            { stat: 'baseStr', label: 'СИЛ' },
            { stat: 'baseDef', label: 'ЗАЩ' },
            { stat: 'baseMag', label: 'МАГ' },
            { stat: 'baseSpd', label: 'СКР' },
        ];
        const pick = bonuses[Math.floor(Math.random() * bonuses.length)];
        this.player[pick.stat] += 2;
        if (pick.stat === 'baseStr') this.player.str = this.player.baseStr;
        if (pick.stat === 'baseDef') this.player.def = this.player.baseDef;
        if (pick.stat === 'baseMag') this.player.mag = this.player.baseMag;
        if (pick.stat === 'baseSpd') this.player.spd = this.player.baseSpd;
        this.player.gold += 50;

        const MAP_NAMES = { village:'Деревню', forest:'Лес', dungeon:'Подземелье', cave:'Пещеру', mountain:'Горы', swamp:'Болото', ruins:'Руины', abyss:'Бездну' };
        setTimeout(() => {
            this.showMsg(`📚 Все записки собраны: ${MAP_NAMES[mapId] || mapId}!`, '#ffdd44');
        }, 2500);
        setTimeout(() => {
            this.showMsg(`🎁 Бонус знаний: +2 ${pick.label}, +50 золота`, '#44ff88');
        }, 4000);
        this.updateHUD();
    },

    // ══════════════════════════════════════════
    // ЗАЧАРОВАНИЕ
    // ══════════════════════════════════════════
    _enchantSelectedItem: null,

    renderEnchantPanel(selectedItem) {
        const p = this.player;
        const leftEl  = document.getElementById('enchant-item-list');
        const rightEl = document.getElementById('enchant-detail');
        if (!leftEl || !rightEl) return;

        // Собираем зачаруемые предметы: экипированные + в инвентаре с slot
        const enchantable = [];
        Object.entries(p.equipment).forEach(([slot, it]) => {
            if (it) enchantable.push({ item:it, source:'eq', slot });
        });
        p.inventory.forEach(it => {
            if (it.slot) enchantable.push({ item:it, source:'inv', slot:it.slot });
        });

        if (!selectedItem && this._enchantSelectedItem) {
            // проверить что он ещё существует
            const found = enchantable.find(e => e.item.id === this._enchantSelectedItem.id);
            if (found) selectedItem = found.item;
        }
        if (!selectedItem && enchantable.length > 0) selectedItem = enchantable[0].item;
        this._enchantSelectedItem = selectedItem || null;

        // Левая колонка — список предметов
        leftEl.innerHTML = '';
        enchantable.forEach(({ item, source }) => {
            const btn = document.createElement('div');
            btn.className = 'enchant-item-btn' + (item === selectedItem ? ' active' : '');
            const enchIcon = item.enchant && RPG.ENCHANTS[item.enchant] ? RPG.ENCHANTS[item.enchant].icon : '';
            btn.innerHTML = `<span class="eib-icon">${item.icon||'📦'}</span><span class="eib-name">${item.name}</span>${enchIcon ? `<span class="eib-ench">${enchIcon}</span>` : ''}<span class="eib-src">${source==='eq'?'👕экип.':'🎒инв.'}</span>`;
            btn.onclick = () => this.renderEnchantPanel(item);
            leftEl.appendChild(btn);
        });

        // Правая колонка — список зачарований
        rightEl.innerHTML = '';
        if (!selectedItem) {
            rightEl.innerHTML = '<div class="ench-empty">Нет предметов для зачарования</div>';
            return;
        }

        const header = document.createElement('div');
        header.className = 'ench-item-header';
        header.innerHTML = `<span class="eih-icon">${selectedItem.icon||'📦'}</span><span class="eih-name">${selectedItem.name}</span>`;
        if (selectedItem.enchant && RPG.ENCHANTS[selectedItem.enchant]) {
            const cur = RPG.ENCHANTS[selectedItem.enchant];
            header.innerHTML += `<span class="eih-cur">Текущее: ${cur.icon} ${cur.name}</span>`;
        }
        rightEl.appendChild(header);

        const enchants = RPG.getAvailableEnchants(p, selectedItem);
        if (enchants.length === 0) {
            const empty = document.createElement('div');
            empty.className = 'ench-empty';
            empty.textContent = 'Нет доступных зачарований для этого предмета';
            rightEl.appendChild(empty);
            return;
        }

        enchants.forEach(en => {
            const card = document.createElement('div');
            card.className = 'enchant-card' + (en.canDo ? '' : ' disabled') + (selectedItem.enchant === en.id ? ' current' : '');
            const matOk = en.hasMat ? '✅' : '❌';
            const goldOk = en.hasGold ? '✅' : '❌';
            card.innerHTML = `
                <div class="ec-head"><span class="ec-icon">${en.icon}</span><span class="ec-name">${en.name}</span><span class="ec-desc">${en.desc}</span></div>
                <div class="ec-cost">${goldOk} 💰 ${en.cost} &nbsp; ${matOk} ${en.matName} ×${en.matQty} (есть: ${en.matCount})</div>`;
            if (en.canDo) {
                card.onclick = () => {
                    const result = RPG.enchantItem(p, selectedItem, en.id);
                    this.showMsg(result.msg, result.ok ? '#adf' : '#f88');
                    if (result.ok) {
                        this.updateHUD();
                        this.renderEnchantPanel(selectedItem);
                        this.checkAchievements('enchant');
                    }
                };
            }
            rightEl.appendChild(card);
        });
    },

    // ══════════════════════════════════════════
    // ДОСТИЖЕНИЯ
    // ══════════════════════════════════════════
    ACHIEVEMENTS_DB: {
        'first_blood':   { icon:'🩸', name:'Первая кровь',      desc:'Убей первого врага'                },
        'kill50':        { icon:'⚔️', name:'Убийца',             desc:'Убей 50 врагов',                   maxProgress:50,   getProgress: p => p.stats?.kills||0 },
        'kill_boss':     { icon:'👹', name:'Охотник за боссами', desc:'Убей любого мини-босса'            },
        'boss_all_mini': { icon:'🏆', name:'Чемпион',            desc:'Убей всех 6 мини-боссов',          maxProgress:6,    getProgress: p => { const ids=['goblin_king','corvus','hydra','frost_giant','stone_colossus','shadow_assassin']; return ids.filter(t=>(p.bestiary||{})[t]>0).length; } },
        'boss_mega':     { icon:'💀', name:'Легенда',             desc:'Убей Мрака Безликого'              },
        'lvl5':          { icon:'⭐', name:'Опытный',             desc:'Достигни 5 уровня',                maxProgress:5,    getProgress: p => Math.min(p.level||1, 5) },
        'lvl10':         { icon:'🌟', name:'Ветеран',             desc:'Достигни 10 уровня',               maxProgress:10,   getProgress: p => Math.min(p.level||1, 10) },
        'rich':          { icon:'💰', name:'Богач',               desc:'Накопи 500 золота',                maxProgress:500,  getProgress: p => Math.min(p.gold||0, 500) },
        'gold1000':      { icon:'👑', name:'Золотой король',      desc:'Накопи 1000 золота',               maxProgress:1000, getProgress: p => Math.min(p.gold||0, 1000) },
        'explorer':      { icon:'🗺️', name:'Исследователь',     desc:'Посети все 8 локаций',             maxProgress:8,    getProgress: p => { const v=p._visited; return v instanceof Set?v.size:Array.isArray(v)?v.length:0; } },
        'abyss':         { icon:'🌑', name:'Путь в бездну',      desc:'Достигни локации Бездна'           },
        'crafter':       { icon:'⚗️', name:'Алхимик',            desc:'Скрафти 5 предметов',              maxProgress:5,    getProgress: p => p._craftCount||0 },
        'bestiary10':    { icon:'📖', name:'Зоолог',              desc:'Открой 10 записей бестиария',      maxProgress:10,   getProgress: p => Object.keys(p.bestiary||{}).length },
        'no_damage':     { icon:'🛡️', name:'Непробиваемый',     desc:'Выиграй бой без потери HP'         },
        'crit10':        { icon:'💥', name:'Снайпер',             desc:'Нанеси 10 критических ударов',     maxProgress:10,   getProgress: p => Math.min(p._critCount||0, 10) },
        'spells10':      { icon:'✨', name:'Чародей',             desc:'Используй заклинания 10 раз',      maxProgress:10,   getProgress: p => Math.min(p._spellCount||0, 10) },
        'lore_all':      { icon:'📜', name:'Летописец',           desc:'Прочти все записки на карте'       },
        'quests10':      { icon:'📋', name:'Герой',               desc:'Выполни 10 квестов',               maxProgress:10,   getProgress: p => Math.min((p.quests||[]).filter(q=>q.done).length, 10) },
        'enchanter':     { icon:'🔮', name:'Зачарователь',        desc:'Зачаруй предмет'                   },
        'inv_full':      { icon:'🎒', name:'Барахольщик',         desc:'Собери 20 предметов в инвентаре',  maxProgress:20,   getProgress: p => Math.min((p.inventory||[]).length, 20) },
    },

    checkAchievements(trigger, value) {
        const p = this.player;
        if (!p) return;
        // Восстановить Set из любого формата (Array после загрузки, {} из старых сохранений, или уже Set)
        if (!p.achievements || !(p.achievements instanceof Set)) {
            p.achievements = new Set(Array.isArray(p.achievements) ? p.achievements : []);
        }
        const unlock = id => {
            if (p.achievements.has(id)) return;
            const a = this.ACHIEVEMENTS_DB[id]; if (!a) return;
            p.achievements.add(id);
            this.showAchievement(a);
        };
        if (trigger === 'kill') {
            unlock('first_blood');
            if ((p.stats.kills || 0) >= 50) unlock('kill50');
        }
        if (trigger === 'kill_boss') unlock('kill_boss');
        if (trigger === 'mini_boss_kill') {
            const miniBossIds = ['goblin_king','corvus','hydra','frost_giant','stone_colossus','shadow_assassin'];
            const killed = miniBossIds.filter(t => (p.bestiary || {})[t] > 0);
            if (killed.length >= 6) unlock('boss_all_mini');
        }
        if (trigger === 'mega_boss') unlock('boss_mega');
        if (trigger === 'level') {
            if (p.level >= 5)  unlock('lvl5');
            if (p.level >= 10) unlock('lvl10');
        }
        if (trigger === 'gold') {
            if (p.gold >= 500)  unlock('rich');
            if (p.gold >= 1000) unlock('gold1000');
        }
        if (trigger === 'visit') {
            if (!(p._visited instanceof Set))
                p._visited = new Set(Array.isArray(p._visited) ? p._visited : []);
            p._visited.add(value);
            if (p._visited.size >= 8) unlock('explorer');
            if (value === 'abyss') unlock('abyss');
        }
        if (trigger === 'craft') {
            p._craftCount = (p._craftCount || 0) + 1;
            if (p._craftCount >= 5) unlock('crafter');
        }
        if (trigger === 'bestiary') {
            if (Object.keys(p.bestiary || {}).length >= 10) unlock('bestiary10');
        }
        if (trigger === 'no_damage') unlock('no_damage');
        if (trigger === 'crit') {
            p._critCount = (p._critCount || 0) + 1;
            if (p._critCount >= 10) unlock('crit10');
        }
        if (trigger === 'spell') {
            p._spellCount = (p._spellCount || 0) + 1;
            if (p._spellCount >= 10) unlock('spells10');
        }
        if (trigger === 'lore_read') {
            p._loreRead = (p._loreRead || 0) + 1;
            if (p._loreRead >= (RPG.LORE_NOTES ? RPG.LORE_NOTES.length : 999)) unlock('lore_all');
        }
        if (trigger === 'quest_done') {
            const done = (p.quests || []).filter(q => q.done).length;
            if (done >= 10) unlock('quests10');
        }
        if (trigger === 'enchant') unlock('enchanter');
        if (trigger === 'inv_full') {
            if (p.inventory.length >= 20) unlock('inv_full');
        }
    },

    showAchievement(a) {
        document.getElementById('ach-text').textContent = a.icon + ' ' + a.name + ' — ' + a.desc;
        const toast = document.getElementById('ach-toast');
        toast.classList.add('show');
        setTimeout(() => toast.classList.remove('show'), 3500);
    },

    renderAchievPanel() {
        const p = this.player;
        if (!p) return;
        const unlocked = (p.achievements instanceof Set) ? p.achievements :
                         new Set(Array.isArray(p.achievements) ? p.achievements : []);
        const grid = document.getElementById('achiev-grid');
        if (!grid) return;
        grid.innerHTML = '';
        Object.entries(this.ACHIEVEMENTS_DB).forEach(([id, a]) => {
            const isUnlocked = unlocked.has(id);
            const div = document.createElement('div');
            div.className = 'ach-card ' + (isUnlocked ? 'unlocked' : 'locked');
            div.innerHTML = `<span class="ach-icon">${a.icon}</span>
                             <div class="ach-name">${a.name}</div>
                             <div class="ach-desc">${a.desc}</div>`;
            if (a.maxProgress && !isUnlocked) {
                const prog = Math.min(a.maxProgress, a.getProgress(p));
                const pct  = Math.round(prog / a.maxProgress * 100);
                div.innerHTML += `<div class="ach-progress"><div class="ach-prog-fill" style="width:${pct}%"></div></div>
                                  <div class="ach-prog-text">${prog}/${a.maxProgress}</div>`;
            }
            grid.appendChild(div);
        });
    },

    // ══════════════════════════════════════════
    // АНИМАЦИЯ ПОРТРЕТОВ
    // ══════════════════════════════════════════
    _doPortraitBlink() {
        ['player', 'enemy'].forEach(who => {
            const el = document.getElementById('blink-' + who);
            if (!el) return;
            el.classList.add('blinking');
            setTimeout(() => el.classList.remove('blinking'), 120);
        });
    },

    // ══════════════════════════════════════════
    // КАРТА МИРА
    // ══════════════════════════════════════════
    MAP_GRAPH: {
        village:  ['forest','dungeon','swamp','tavern'],
        tavern:   ['village'],
        forest:   ['village','cave','mountain','dungeon'],
        dungeon:  ['village','forest'],
        cave:     ['forest','mountain'],
        mountain: ['forest','cave','ruins'],
        swamp:    ['village'],
        ruins:    ['mountain','abyss'],
        abyss:    ['ruins'],
    },
    MAP_NODES: {
        village:  { x:240, y:195, icon:'🏘️',  name:'Деревня'    },
        tavern:   { x:290, y:205, icon:'🍺',  name:'Таверна'    },
        forest:   { x:155, y:140, icon:'🌲',  name:'Лес'        },
        dungeon:  { x:170, y:250, icon:'🏰',  name:'Подземелье' },
        cave:     { x: 75, y:170, icon:'⛰️', name:'Пещера'    },
        mountain: { x: 80, y: 90, icon:'🏔️', name:'Горы'      },
        swamp:    { x:330, y:250, icon:'🌿',  name:'Болото'     },
        ruins:    { x:345, y:100, icon:'🗿',  name:'Руины'     },
        abyss:    { x:440, y: 50, icon:'🌑',  name:'Бездна'    },
    },

    renderWorldMapPanel() {
        const cvs = document.getElementById('worldmap-canvas');
        if (!cvs) return;
        const ctx = cvs.getContext('2d');
        const p = this.player;
        const visited = p._visited instanceof Set ? p._visited : new Set(Array.isArray(p._visited) ? p._visited : []);
        const cur = this.mapId;
        const t = Date.now();

        // Фон
        ctx.fillStyle = '#07070f';
        ctx.fillRect(0, 0, cvs.width, cvs.height);
        // Сетка
        ctx.strokeStyle = '#0e0e22'; ctx.lineWidth = 1;
        for (let i=0;i<cvs.width;i+=30) { ctx.beginPath(); ctx.moveTo(i,0); ctx.lineTo(i,cvs.height); ctx.stroke(); }
        for (let i=0;i<cvs.height;i+=30) { ctx.beginPath(); ctx.moveTo(0,i); ctx.lineTo(cvs.width,i); ctx.stroke(); }

        // Соединения (рисуем только уникальные пары)
        const drawn = new Set();
        Object.entries(this.MAP_GRAPH).forEach(([from, tos]) => {
            const n1 = this.MAP_NODES[from];
            tos.forEach(to => {
                const key = [from,to].sort().join('-');
                if (drawn.has(key)) return;
                drawn.add(key);
                const n2 = this.MAP_NODES[to];
                const v1 = visited.has(from)||from===cur, v2 = visited.has(to)||to===cur;
                ctx.beginPath(); ctx.moveTo(n1.x, n1.y); ctx.lineTo(n2.x, n2.y);
                ctx.strokeStyle = (v1&&v2) ? '#2a2a5a' : '#12122a';
                ctx.lineWidth = (v1&&v2) ? 2 : 1;
                ctx.setLineDash((v1&&v2) ? [] : [5,4]);
                ctx.stroke(); ctx.setLineDash([]);
            });
        });

        // Узлы
        Object.entries(this.MAP_NODES).forEach(([id, node]) => {
            const isCur      = id === cur;
            const isVisited  = visited.has(id);
            const isNeighbor = (this.MAP_GRAPH[cur]||[]).includes(id);
            const pulse      = Math.sin(t/700) * 3;
            const r = isCur ? 22 + pulse : isNeighbor ? 19 : 17;

            ctx.beginPath(); ctx.arc(node.x, node.y, r, 0, Math.PI*2);
            if (isCur) {
                const g = ctx.createRadialGradient(node.x,node.y,0,node.x,node.y,r);
                g.addColorStop(0,'#ffd700'); g.addColorStop(1,'#a06000');
                ctx.fillStyle = g; ctx.shadowColor = '#ffd700'; ctx.shadowBlur = 16;
            } else if (isVisited) {
                ctx.fillStyle = '#1a1a3a'; ctx.shadowBlur = 0;
            } else {
                ctx.fillStyle = '#0a0a18'; ctx.shadowBlur = 0;
            }
            ctx.fill();

            ctx.beginPath(); ctx.arc(node.x, node.y, r, 0, Math.PI*2);
            ctx.strokeStyle = isCur ? '#ffd700' : isNeighbor ? '#3a6a9a' : isVisited ? '#2a2a5a' : '#151530';
            ctx.lineWidth = isCur ? 3 : isNeighbor ? 2 : 1;
            ctx.stroke(); ctx.shadowBlur = 0;

            ctx.font = `${isCur?17:13}px serif`; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
            ctx.globalAlpha = (isVisited||isCur) ? 1 : 0.25;
            ctx.fillText(node.icon, node.x, node.y);
            ctx.font = `bold ${isCur?11:9}px Arial`;
            ctx.fillStyle = isCur ? '#ffd700' : isVisited ? '#aaaacc' : '#2a2a5a';
            ctx.fillText(node.name, node.x, node.y + r + 11);
            ctx.globalAlpha = 1;
        });

        // Обработчик клика
        cvs.onclick = (ev) => {
            const rect = cvs.getBoundingClientRect();
            const mx = (ev.clientX - rect.left) * (cvs.width  / rect.width);
            const my = (ev.clientY - rect.top)  * (cvs.height / rect.height);
            Object.entries(this.MAP_NODES).forEach(([id, node]) => {
                if (Math.hypot(mx - node.x, my - node.y) < 24 && id !== cur) {
                    if ((this.MAP_GRAPH[cur]||[]).includes(id)) {
                        this.togglePanel('worldmap');
                        this.travelTo(id);
                    } else {
                        this.showMsg('⛔ Сначала доберитесь туда пешком', '#f44');
                    }
                }
            });
        };
    },
};
