Animesss Labyrinth Map

Интерактивная карта лабиринта (совместное прохождение)

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, Greasemonkey alebo Violentmonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey, % alebo Violentmonkey.

Na nainštalovanie skriptu si budete musieť nainštalovať rozšírenie, ako napríklad Tampermonkey alebo Userscripts.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie, ako napríklad Tampermonkey.

Na inštaláciu tohto skriptu je potrebné nainštalovať rozšírenie správcu používateľských skriptov.

(Už mám správcu používateľských skriptov, nechajte ma ho nainštalovať!)

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie, ako napríklad Stylus.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

Na inštaláciu tohto štýlu je potrebné nainštalovať rozšírenie správcu používateľských štýlov.

(Už mám správcu používateľských štýlov, nechajte ma ho nainštalovať!)

// ==UserScript==
// @name         Animesss Labyrinth Map
// @namespace    https://animesss.com/
// @version      10.15
// @description  Интерактивная карта лабиринта (совместное прохождение)
// @author       VladLIO
// @license      MIT
// @match        https://animesss.com/*
// @match        https://animesss.tv/*
// @grant        none
// @connect      labyrinthmap.uk
// @run-at       document-end
// ==/UserScript==

var PW = (typeof unsafeWindow !== 'undefined' && unsafeWindow) ? unsafeWindow : window;

var LM_IS_CLUB_PAGE = /^\/clubs\/\d+\/?$/.test(location.pathname || '');
var LM_IS_LABYRINTH_PAGE = /\/labyrinth(\/|$)/.test(location.pathname || '');

window._lmXHRPatched = false;
if (LM_IS_LABYRINTH_PAGE) {
(function() {
  var origOpen = PW.XMLHttpRequest.prototype.open;
  var origSend = PW.XMLHttpRequest.prototype.send;
  window._lmXHRPatched = true;

  var _lmPendingQuiz = null;
  function _lmQuizCapture(resp, reqBody) {
    if (!resp) return;
    if ((resp.event === 'quiz' || resp.event === 'resonance_room_result') && resp.quiz && resp.quiz.answers && resp.text) {
      _lmPendingQuiz = { quiz_id: resp.quiz.id || null, text: String(resp.text), answers: resp.quiz.answers };
      try { if (window._lmQL) window._lmQL(resp.quiz.id || null, String(resp.text), resp.quiz.answers); } catch(e) {}
    } else if (resp.event === 'quiz_result' && resp.correct_answer != null && _lmPendingQuiz) {
      var pq = _lmPendingQuiz; _lmPendingQuiz = null;
      var correctText = pq.answers[String(resp.correct_answer)];
      if (!correctText) return;
      var selected = null;
      try { var k = new URLSearchParams(reqBody).get('answer'); if (k) selected = pq.answers[k]; } catch(e) {}
      if (window._lmReportQuiz) window._lmReportQuiz({
        quiz_id: pq.quiz_id, question_text: pq.text, answers: pq.answers,
        correct_answer_text: correctText, selected_answer: selected, is_correct: !!resp.is_correct
      });
    }
  }

  PW.XMLHttpRequest.prototype.open = function(method, url) {
    this._lmUrl = url;
    return origOpen.apply(this, arguments);
  };
  PW.XMLHttpRequest.prototype.send = function(body) {
    if (this._lmUrl && this._lmUrl.indexOf('mod=animesss_game') !== -1 &&
        typeof body === 'string') {
      if (window._lmResPending && body.indexOf('action=step') === -1) {
        var _resxhr = this;
        _resxhr.addEventListener('load', function() { try { if (window._lmResCheck) window._lmResCheck(_resxhr.responseText || ''); } catch (e) {} });
      }
      if (body.indexOf('action=step') !== -1) {
        var xhr = this;
        xhr.addEventListener('load', function() {
          try {
            var resp = JSON.parse(xhr.responseText);
            if (!resp) return;
            if (!PW.labyrinthData) PW.labyrinthData = {};
            if (resp.mapData) PW.labyrinthData.mapData = resp.mapData;
            if (resp.emission && typeof resp.emission === 'object') PW.labyrinthData.emission = resp.emission;
            if (resp.fatigue_counter && typeof resp.fatigue_counter === 'object') PW.labyrinthData.fatigue_counter = resp.fatigue_counter;
            if (typeof resp.fate_room !== 'undefined') PW.labyrinthData.fateRoom = resp.fate_room || null;
            if (typeof resp.echo_room !== 'undefined') PW.labyrinthData.echoRoom = resp.echo_room || null;
            if (typeof resp.can_place_room_object !== 'undefined') {
              PW.labyrinthData.canPlaceObject = resp.can_place_room_object;
              PW.labyrinthData.canPlaceRoomObject = resp.can_place_room_object;
            }

            try { _lmQuizCapture(resp, body); } catch(_e) {}

            try { if (window._lmFatPing) window._lmFatPing(); } catch(_e) {}

            try { if (window._lmScanUnknown) window._lmScanUnknown(resp); } catch(_e) {}
          } catch(e) {}
          if (window._lmApplyHistoricalPath) {
            setTimeout(window._lmApplyHistoricalPath, 150);
          }
          if (window._lmUpdateObjBadge) {
            setTimeout(window._lmUpdateObjBadge, 200);
          }
          if (window._lmApplyEmissionCells) {
            setTimeout(window._lmApplyEmissionCells, 250);
          }
        });
      }

      if (body.indexOf('action=answer_fate_room') !== -1) {
        var _fxhr = this;

        var _fm = /(?:^|&)choice=([^&]+)/.exec(body);
        var _fchoice = _fm ? decodeURIComponent(_fm[1]) : null;
        var _fr = PW.labyrinthData && PW.labyrinthData.fateRoom;
        var _fx = _fr ? _fr.x : null, _fy = _fr ? _fr.y : null;
        var _ftype = (_fr && _fr.options && _fchoice != null && _fr.options[_fchoice]) ? _fr.options[_fchoice].type : null;
        _fxhr.addEventListener('load', function() {
          try {
            if (_fxhr.status < 200 || _fxhr.status >= 300) return;
            var _rok = true;
            try { var _rj = JSON.parse(_fxhr.responseText); if (_rj && _rj.error) _rok = false; } catch(e) {}
            if (_rok && _ftype && _fx != null && _fy != null && window._lmSendEv) {
              window._lmSendEv({
                x: _fx, y: _fy, ev: _ftype,
                acc_before: null, acc_after: null, acc_delta: 0,
                dat: { fate_resolved: true },
                session_id: window._lmPushSid || null,
                step_index: null
              });

              try {
                var _st = PW.labyrinthData && PW.labyrinthData.mapData && PW.labyrinthData.mapData.steps;
                if (_st) for (var _qi=0; _qi<_st.length; _qi++) { if (_st[_qi].x===_fx && _st[_qi].y===_fy) _st[_qi].event = _ftype; }
              } catch(e) {}
            }
          } catch(e) {}
        });
      }

      if (body.indexOf('action=answer_quiz') !== -1) {
        var _qxhr = this, _qbody = body;
        _qxhr.addEventListener('load', function() {
          try { _lmQuizCapture(JSON.parse(_qxhr.responseText), _qbody); } catch(e) {}
        });
      }

      if (body.indexOf('resonance') !== -1) {
        var _rxhr = this, _rbody = body;
        _rxhr.addEventListener('load', function() {
          try { _lmQuizCapture(JSON.parse(_rxhr.responseText), _rbody); } catch(e) {}
        });
      }
    }
    return origSend.apply(this, arguments);
  };
})();
}

(function() {
  var _ls = {}, _ss = {};
  var _rawLS = null, _rawSS = null;
  try { _rawLS = window.localStorage;   } catch(e) {}
  try { _rawSS = window.sessionStorage; } catch(e) {}
  function _testStorage(raw) {
    if (!raw) return false;
    try { raw.setItem('__lm__','1'); raw.removeItem('__lm__'); return true; } catch(e) { return false; }
  }
  var hasLS = _testStorage(_rawLS);
  var hasSS = _testStorage(_rawSS);
  window.lmLS = {
    get:    function(k)   { try { return hasLS ? _rawLS.getItem(k)    : (_ls[k]!==undefined?_ls[k]:null); } catch(e) { return _ls[k]!==undefined?_ls[k]:null; } },
    set:    function(k,v) { try { if(hasLS) _rawLS.setItem(k,v);    else _ls[k]=String(v); } catch(e) { _ls[k]=String(v); } },
    remove: function(k)   { try { if(hasLS) _rawLS.removeItem(k);   else delete _ls[k]; } catch(e) { delete _ls[k]; } }
  };
  window.lmSS = {
    get:    function(k)   { try { return hasSS ? _rawSS.getItem(k)   : (_ss[k]!==undefined?_ss[k]:null); } catch(e) { return _ss[k]!==undefined?_ss[k]:null; } },
    set:    function(k,v) { try { if(hasSS) _rawSS.setItem(k,v);   else _ss[k]=String(v); } catch(e) { _ss[k]=String(v); } },
    remove: function(k)   { try { if(hasSS) _rawSS.removeItem(k);  else delete _ss[k]; } catch(e) { delete _ss[k]; } }
  };
})();

(function() {
  function _gmFetch(url, opts) {
    return new Promise(function(resolve, reject) {
      var o = opts || {};
      var req = GM_xmlhttpRequest({
        method:  o.method || 'GET',
        url:     url,
        headers: o.headers || {},
        data:    o.body   || null,
        responseType: o.lmResponseType === 'arraybuffer' ? 'arraybuffer' : 'text',
        timeout: 20000,
        onload: function(r) {
          var rawHeaders = String(r.responseHeaders || '').split(/\r?\n/), headerMap = {};
          rawHeaders.forEach(function(line){ var p=line.indexOf(':'); if(p>0) headerMap[line.slice(0,p).trim().toLowerCase()]=line.slice(p+1).trim(); });
          var buffer = r.response instanceof ArrayBuffer ? r.response : null;
          var text = buffer ? new TextDecoder('utf-8').decode(new Uint8Array(buffer)) : (r.responseText || String(r.response || ''));
          resolve({
            ok:     r.status >= 200 && r.status < 300,
            status: r.status,
            headers:{ get:function(name){ return headerMap[String(name || '').toLowerCase()] || null; } },
            text:   function() { return Promise.resolve(text); },
            json:   function() { try { return Promise.resolve(JSON.parse(text)); } catch(e) { return Promise.reject(e); } },
            arrayBuffer:function(){ return buffer ? Promise.resolve(buffer) : Promise.resolve(new TextEncoder().encode(text).buffer); }
          });
        },
        onerror:   function() { reject(new Error('network error')); },
        ontimeout: function() { reject(new Error('timeout')); }
      });
      if (o.signal && typeof o.signal.addEventListener === 'function')
        o.signal.addEventListener('abort', function(){ try { req.abort(); } catch(e) {} reject(new Error('aborted')); }, { once:true });
    });
  }
  window.lmFetch = (typeof GM_xmlhttpRequest === 'function') ? _gmFetch : function(u,o){ return fetch(u,o); };
})();

(function () {
  'use strict';

  var IS_LABYRINTH = LM_IS_LABYRINTH_PAGE;
  var IS_CLUB_PAGE = LM_IS_CLUB_PAGE;

  var LM_MODE = 'production';
  var LM_ENVIRONMENTS = {
    production: Object.freeze({ name:'production', apiBase:'https://labyrinthmap.uk/api', wsUrl:'wss://labyrinthmap.uk/ws', workerBase:null })
  };
  var LM_ENV = LM_ENVIRONMENTS[LM_MODE];
  if (!LM_ENV) throw new Error('Unknown Labyrinth environment: ' + LM_MODE);
  window.LM_ENV_CONFIG = LM_ENV;
  var LM_API_BASE = LM_ENV.apiBase;
  var LM_WS_URL   = LM_ENV.wsUrl;
  var LM_SCRIPT_VERSION = (typeof GM_info !== 'undefined' && GM_info.script && GM_info.script.version) ? GM_info.script.version : '10.15';
  var VPS_URL        = LM_API_BASE;
  var FAST_TYPES = ['unknown','empty','penalty','quiz','puzzle','reward','luck_altar','locked_chest','spiritual_teleport','echo_room','mini_boss','relic_room','card_trader','fate_room','mimic_chest','mimic_chest_back','hard_boss','jackpot','collection','club_war_room','resonance_room','recovery_room','personal_mine','kobold_room','reward_card','trap_back','room_gift','room_trap','shield_block','mimic_chest_reward','start','room_trap_result'];
  var FAST_MAX_ROOMS = 250000;
  var FAST_HEADER_SIZE = 24;
  var FAST_RECORD_SIZE = 6;
  function fastTypeHash() {
    var h = 0x811c9dc5, s = FAST_TYPES.join('|');
    for (var i = 0; i < s.length; i++) { h ^= s.charCodeAt(i); h = Math.imul(h, 0x01000193); }
    return h >>> 0;
  }
  var FAST_TYPE_HASH = fastTypeHash();
  var _fastMapFailures = 0;
  var _fastMapRetryAt = 0;
  function _lmFastMap() {
    if (Date.now() < _fastMapRetryAt) return false;
    try { if (window.lmLS.get('lm_map_fast') === '0') return false; } catch (e) {}
    return true;
  }
  function _lmCutBelow() {
    try { return window.lmLS.get('lm_cut_below') === '1'; } catch (e) { return false; }
  }
  var _playerPos = {};
  function _lmLoadPositions() {
    if (!_lmFeatOn('positions')) {
      if (Object.keys(_playerPos).length) { _playerPos = {}; try { patchSiteMap(); if (fmOpen) drawFull(); } catch(e){} }
      return;
    }
    try {
      window.lmFetch(VPS_URL + '/players/positions', { cache:'no-store', headers: vpsAuthHeaders(false) })
        .then(function(r){
          if (r.status === 403 || r.status === 401) { if (Object.keys(_playerPos).length) { _playerPos = {}; try { patchSiteMap(); if (fmOpen) drawFull(); } catch(e){} } return null; }
          if (!r.ok) return null;
          return r.json();
        })
        .then(function(d){
          if (!d || !d.rooms) return;
          var m = {}; d.rooms.forEach(function(rm){ m[rm.x + '_' + rm.y] = rm.c; });
          _playerPos = m;
          try { patchSiteMap(); if (fmOpen) drawFull(); } catch(e){}
        }).catch(function(){});
    } catch (e) {}
  }
  var _lmFeatures = {};
  function _lmFeatOn(name) { return !!(_lmFeatures && _lmFeatures[name]); }
  window._lmFeatOn = _lmFeatOn;
  function _lmLoadDirectives() {
    try {
      window.lmFetch(VPS_URL + '/directives', { cache:'no-store', headers: vpsAuthHeaders(false) })
        .then(function(r){ return r && r.ok ? r.json() : null; })
        .then(function(d){ if (d && d.features) { _lmFeatures = d.features; window._lmFeatsReady = true; _lmUpdateFatigue(); _lmLoadFatigue(); try { if (_lmFeatOn('positions')) _lmLoadPositions(); } catch (e) {} try { _lmClubWarApply(); } catch (e) {} try { _lmQuizFromPage(); } catch (e) {} } })
        .catch(function(){});
    } catch (e) {}
  }
  function _lmQuizFromPage() {
    try {
      if (!_lmFeatOn('quiz')) return;
      var ld = PW.labyrinthData || {};
      var q = ld.quiz;
      if (!q || !q.answers || !Object.keys(q.answers).length) return;
      var text = q.text || q.question || q.title || '';
      if (!text) {
        var el = document.querySelector('#labyrinthQuizQuestion, .labyrinth__quiz-question, .labyrinth-quiz__question, .quiz-question');
        if (el) text = (el.textContent || '').trim();
      }
      if (!text) return;
      window._lmQL(q.id || null, text, q.answers);
    } catch (e) {}
  }
  var _fatPingT = null;
  window._lmFatPing = function() {
    if (!_lmFeatOn('fatigue')) return;
    try { _lmFatWatch(); } catch (e) {}
    setTimeout(function () { try { _lmFatWatch(); } catch (e) {} }, 500);
    if (_fatPingT) clearTimeout(_fatPingT);
    _fatPingT = setTimeout(_lmLoadFatigue, 1200);
  };
  var _fatCount = null, _fatThreshold = null, _fatHist = [], _fatSent = {}, _lmPrevRb = null;
  var _fatFromJson = false, _fatSkipped = false;
  function _lmFatSyncJson(ld) {
    try {
      var fc = ld && ld.fatigue_counter;
      if (fc && typeof fc.value === 'number' && isFinite(fc.value)) {
        _fatFromJson = true;
        _fatCount = fc.value;
        _fatThreshold = 10;
        _fatSkipped = !!fc.skipped;
      }
    } catch (e) {}
  }
  try {
    window._lmLongTasks = { n: 0, total: 0, max: 0 };
    var _lmLtObs = new PerformanceObserver(function (list) {
      list.getEntries().forEach(function (en) {
        window._lmLongTasks.n++;
        window._lmLongTasks.total += Math.round(en.duration);
        if (en.duration > window._lmLongTasks.max) window._lmLongTasks.max = Math.round(en.duration);
      });
    });
    try { _lmLtObs.observe({ type: 'longtask', buffered: true }); }
    catch (e2) { _lmLtObs.observe({ entryTypes: ['longtask'] }); }
  } catch (e) {}
  window._lmDiag = function () {
    var d = {};
    try { d.features = Object.keys(_lmFeatures || {}); } catch (e) {}
    try { d.bootMap = window._lmBootMap || null; } catch (e) {}
    try { d.longTasks = window._lmLongTasks || null; } catch (e) {}
    try { d.fatigue = { count: _fatCount, threshold: _fatThreshold }; } catch (e) {}
    try {
      var cr = (typeof _cloudMapCount === 'number' && _cloudMapCount > 0) ? _cloudMapCount
             : (typeof cloudMap === 'object' && cloudMap ? Object.keys(cloudMap).length : null);
      d.map = { clientRooms: cr, serverSeq: parseInt(window.lmLS.get(LS_SERVER_SEQ) || '0', 10) };
    } catch (e) {}
    try { d.blocked = (typeof _lmIsBlocked === 'function') ? _lmIsBlocked() : null; } catch (e) {}
    try { d.accDepth = window._lmAccDepth || 1; } catch (e) {}
    return d;
  };
  function _lmGameSteps() {
    var el = document.getElementById('labyrinthTotalSteps');
    if (!el) return null;
    var v = parseInt(String(el.textContent || '').replace(/\s+/g, ''), 10);
    return Number.isFinite(v) ? v : null;
  }
  function _lmFatWatch() {
    var ld = PW.labyrinthData || {};
    _lmFatSyncJson(ld);
    var st = _lmGameSteps();
    var em = !!(ld.emission && ld.emission.active);

    var fat = ld.fatigue;
    if (fat && fat.active) {
      var fkey = 'fat_' + (st !== null ? st : 'x');
      if (!_fatSent[fkey]) {
        _fatSent[fkey] = 1;
        window.lmFetch(VPS_URL + '/fatigue/mark', {
          method: 'POST', headers: vpsAuthHeaders(true),
          body: JSON.stringify({ events: [{ key: fkey, steps: (st !== null ? st : 0), kind: 'fatigue',
                                             step_back: fat.step_back || null, emission: em }],
                                 fatigue_raw: fat })
        }).then(function () { _lmLoadFatigue(); }).catch(function () {});
      }
    }

    var le = ld.lastEvent;
    var isRb = (le === 'trap_back' || le === 'mimic_chest_back');
    if (isRb && le !== _lmPrevRb) {
      var rkind = (le === 'mimic_chest_back') ? 'mimic' : 'trap';
      var rkey = 'rb_' + rkind + '_' + (st !== null ? st : 'x');
      if (!_fatSent[rkey]) {
        _fatSent[rkey] = 1;
        window.lmFetch(VPS_URL + '/fatigue/mark', {
          method: 'POST', headers: vpsAuthHeaders(true),
          body: JSON.stringify({ events: [{ key: rkey, steps: (st !== null ? st : 0), kind: rkind, emission: em }],
                                 fatigue_raw: (typeof ld.fatigue !== 'undefined') ? ld.fatigue : null })
        }).then(function () { _lmLoadFatigue(); }).catch(function () {});
      }
    }
    _lmPrevRb = isRb ? le : null;
  }
  function _lmLoadFatigue() {
    try {
      if (!_lmFeatOn('fatigue')) { _lmUpdateFatigue(); return; }
      _lmFatSyncJson(PW.labyrinthData || {});
      var _st = _lmGameSteps();
      window.lmFetch(VPS_URL + '/fatigue' + (_st !== null ? '?steps=' + _st : ''), { cache:'no-store', headers: vpsAuthHeaders(false) })
        .then(function(r){ return r && r.ok ? r.json() : null; })
        .then(function(d){ if (d && d.ok) { if (!_fatFromJson) { _fatCount = (typeof d.count === 'number') ? d.count : null; _fatThreshold = (typeof d.threshold === 'number') ? d.threshold : null; } _fatHist = d.history || []; _lmUpdateFatigue(); if (_wbActive === 'fat') window._lmRenderFatHist(); } })
        .catch(function(){});
    } catch (e) {}
  }
  function _lmUpdateFatigue() {
    var on = _lmFeatOn('fatigue') && LM_IS_LABYRINTH_PAGE;
    var block = document.getElementById('lm-mi-fat-block');
    var btn  = document.getElementById('lm-wb-fat');
    if (block) block.style.display = on ? '' : 'none';
    if (btn)  btn.style.display  = on ? '' : 'none';
    if (!on) return;
    var v = document.getElementById('lm-mi-fat-val');
    if (v) {
      if (_fatCount === null) { v.innerHTML = '<span style="opacity:.45">—</span>'; return; }
      var col = '#6ee786', tail = '', tip = '';
      if (_fatThreshold) {
        col = _fatCount >= _fatThreshold ? '#ff6b6b' : (_fatCount >= _fatThreshold - 2 ? '#ffb073' : '#6ee786');
        tail = '<span style="opacity:.5;font-size:.75em"> / ' + _fatThreshold + '</span>';
      }
      if (_fatFromJson) {
        var left = Math.max(0, 10 - _fatCount);
        tip = left === 0 ? 'Усталость на следующем шаге!' : ('Осталось ' + left + ' шаг.(а/ов) до усталости. Ловушка отката и мимик отката сбрасывают счётчик.');
        if (_fatSkipped) tip += ' Последний шаг не засчитан (шанс первооткрывателя).';
      }
      v.innerHTML = '<span style="color:' + col + '"' + (tip ? ' title="' + tip + '"' : '') + '>' + _fatCount + '</span>' + tail;
    }
  }
  window._lmRenderFatHist = function() {
    var box = document.getElementById('lm-wi-fat'); if (!box) return;
    var NM = { trap_back: '↺ Ловушка отката', mimic_chest_back: '🎭 Мимик отката', fatigue: '💤 Усталость' };
    if (!_fatHist.length) { box.innerHTML = '<div class="lm-wi-loading">Ждём первый откат</div>'; return; }
    box.innerHTML = '<div style="padding:9px 11px;font-size:12px;max-height:240px;overflow:auto">' +
      '<div style="opacity:.65;margin-bottom:7px">' + (_fatFromJson
        ? ('Счётчик усталости: <b>' + (_fatCount === null ? '—' : _fatCount) + '/10</b>' + (_fatCount === null ? '' : ' · осталось <b>' + Math.max(0, 10 - _fatCount) + '</b>'))
        : ('Сейчас усталость: <b>' + (_fatCount === null ? '—' : _fatCount) + '</b> шагов')) + '</div>' +
      _fatHist.map(function(h){
        var t = ''; try { t = new Date(h.ts).toLocaleString('ru-RU', {day:'2-digit',month:'2-digit',hour:'2-digit',minute:'2-digit'}); } catch(e) {}
        return '<div style="display:flex;gap:10px;align-items:center;padding:5px 0;border-bottom:1px solid rgba(255,255,255,.06)">' +
          '<span style="flex:1">' + (NM[h.ev] || h.ev) + ((h.emission && h.ev !== 'fatigue') ? ' <span style="color:#ffb073">×2</span>' : '') + '</span>' +
          '<span style="opacity:.55">' + t + '</span>' +
          '<span style="min-width:64px;text-align:right"><b>' + (h.walked == null ? '—' : h.walked) + '</b> шаг.</span></div>';
      }).join('') + '</div>';
  };
  window._lmQL = function(qid, text, answers) {
    try {
      if (!_lmFeatOn('quiz')) return;
      window.lmFetch(VPS_URL + '/quiz/lookup', { method:'POST', headers: vpsAuthHeaders(true),
        body: JSON.stringify({ quiz_id: qid, question_text: text, answers: answers }) })
        .then(function(r){ return r && r.ok ? r.json() : null; })
        .then(function(d){ if (d && d.found && d.correct) _lmQMark(answers, d.correct); })
        .catch(function(){});
    } catch (e) {}
  };
  function _lmQMark(answers, val) {
    try {
      var key = null; for (var k in answers) { if (answers[k] === val) { key = k; break; } }
      var tries = 0, iv = setInterval(function(){
        tries++;
        var btns = document.querySelectorAll('.labyrinth__quiz-btn');
        if (btns.length) {
          var btn = key ? document.querySelector('.labyrinth__quiz-btn[data-answer="' + key + '"]') : null;
          if (!btn) { for (var i=0;i<btns.length;i++){ if ((btns[i].textContent||'').trim() === String(val).trim()) { btn = btns[i]; break; } } }
          if (btn) { btn.style.boxShadow = '0 0 14px 4px rgba(0,255,0,.6), inset 0 0 10px rgba(0,255,0,.22)'; btn.style.border = '2px solid #0f0'; btn.style.borderRadius = btn.style.borderRadius || '8px'; }
          clearInterval(iv);
        }
        if (tries > 25) clearInterval(iv);
      }, 200);
    } catch (e) {}
  }
  function _lmCWRaidBtns() {
    return ['labyrinthClubWarSoftBtn','labyrinthClubWarNormalBtn','labyrinthClubWarFullBtn']
      .map(function(id){ return document.getElementById(id); }).filter(Boolean);
  }
  function _lmCWBlockRaids(block) {
    _lmCWRaidBtns().forEach(function(b){
      b.classList.toggle('lm-cw-blocked', !!block);
      if (block) { if (!b.disabled) { b.disabled = true; b.setAttribute('data-lmcw','1'); } }
      else if (b.getAttribute('data-lmcw') === '1') { b.disabled = false; b.removeAttribute('data-lmcw'); }
    });
  }
  function _lmCWActiveAlly() {
    try {
      var f = _lmFeatures && _lmFeatures.club_war_marks; if (!f) return false;
      var marks = f.marks || {};
      var a = document.querySelector('#labyrinthClubWar .labyrinth__club-war-club.is-active[data-club-id]');
      return !!(a && marks[String(a.getAttribute('data-club-id'))] === 'ally');
    } catch (e) { return false; }
  }
  function _lmCWClear(panel) {
    try {
      panel.querySelectorAll('.lm-cw-ally,.lm-cw-target').forEach(function(b){ b.classList.remove('lm-cw-ally','lm-cw-target'); });
      _lmCWBlockRaids(false);
      var n = document.getElementById('lm-cw-note'); if (n && n.style.display !== 'none') n.style.display = 'none';
    } catch (e) {}
  }
  function _lmClubWarApply() {
    try {
      var panel = document.getElementById('labyrinthClubWar');
      if (!panel) return;
      var f = _lmFeatures && _lmFeatures.club_war_marks;
      if (!f) { _lmCWClear(panel); return; }
      var applyTo = f.apply_to_clubs;
      var cw = PW.labyrinthData && PW.labyrinthData.clubWarRoom;
      var myClub = (cw && cw.my_club_id != null) ? Number(cw.my_club_id) : null;
      var scoped = !Array.isArray(applyTo) || !applyTo.length || (myClub != null && applyTo.indexOf(myClub) !== -1);
      if (!scoped) { _lmCWClear(panel); return; }
      var marks = f.marks || {};
      panel.querySelectorAll('.labyrinth__club-war-club[data-club-id]').forEach(function(b){
        var m = marks[String(b.getAttribute('data-club-id'))];
        var wantAlly = (m === 'ally'), wantTgt = (m === 'target');
        if (b.classList.contains('lm-cw-ally') !== wantAlly) b.classList.toggle('lm-cw-ally', wantAlly);
        if (b.classList.contains('lm-cw-target') !== wantTgt) b.classList.toggle('lm-cw-target', wantTgt);
      });
      var ally = _lmCWActiveAlly();
      _lmCWBlockRaids(ally);
      var n = document.getElementById('lm-cw-note');
      if (n) { var nd = ally ? 'block' : 'none'; if (n.style.display !== nd) n.style.display = nd; }
    } catch (e) {}
  }
  function _lmClubWarInit() {
    try {
      if (window._lmCWInit) return;
      var panel = document.getElementById('labyrinthClubWar');
      if (!panel) return;
      window._lmCWInit = 1;
      if (!document.getElementById('lm-cw-style')) {
        var st = document.createElement('style'); st.id = 'lm-cw-style';
        st.textContent =
          '.labyrinth__club-war-club.lm-cw-ally{box-shadow:0 0 14px 3px rgba(34,197,94,.45),inset 0 0 10px rgba(34,197,94,.18)!important;border:2px solid #22c55e!important}' +
          '.labyrinth__club-war-club.lm-cw-target{box-shadow:0 0 14px 3px rgba(255,70,70,.4),inset 0 0 10px rgba(255,70,70,.16)!important;border:2px solid #ff5555!important}' +
          '.labyrinth__club-war-club.lm-cw-ally .labyrinth__club-war-club-name::after{content:" 🟢";font-size:12px}' +
          '.labyrinth__club-war-club.lm-cw-target .labyrinth__club-war-club-name::after{content:" 🔴";font-size:12px}' +
          '.lm-cw-blocked{pointer-events:none!important;opacity:.4!important;filter:grayscale(1)!important}' +
          '#lm-cw-note{display:none;margin:0 0 10px;padding:9px 12px;border-radius:12px;background:rgba(34,197,94,.12);border:1px solid rgba(34,197,94,.4);color:#8ff0a4;font-size:13px;font-weight:700}';
        document.head.appendChild(st);
      }
      if (!document.getElementById('lm-cw-note')) {
        var actions = document.getElementById('labyrinthClubWarActions');
        var note = document.createElement('div');
        note.id = 'lm-cw-note';
        note.textContent = '🟢 Союзний клуб — атака заблокована.';
        if (actions && actions.parentNode) actions.parentNode.insertBefore(note, actions);
        else panel.appendChild(note);
      }
      _lmCWRaidBtns().forEach(function(b){
        if (b._lmCWGuard) return; b._lmCWGuard = 1;
        b.addEventListener('click', function(e){
          if (_lmCWActiveAlly()) { e.stopImmediatePropagation(); e.preventDefault(); try { lmInfo('Союзний клуб — атака заблокована'); } catch (_e) {} }
        }, true);
      });
      var obs = new MutationObserver(function(){ _lmClubWarApply(); });
      obs.observe(panel, { subtree:true, childList:true, attributes:true, attributeFilter:['class','style'] });
      _lmClubWarApply();
    } catch (e) {}
  }
  var _vpsAlive      = true;
  var LM_DEBUG       = false;
  var _lmLastInfo = { message: '', at: 0 };
  function lmInfo(message) {
    _lmLastInfo = { message: message, at: Date.now() };
    if (elInfo) elInfo.textContent = message;
  }
  function lmWarn(message) {
    if (LM_DEBUG && !IS_CLUB_PAGE) console.warn('[Карта] ' + message);
  }
  function lmDebug() {
    if (LM_DEBUG && !IS_CLUB_PAGE) console.log.apply(console, arguments);
  }

  var EMISSION_DURATION_SEC = 3600;

  function getMoscowDate() {
    return new Date().toLocaleDateString('en-CA', {timeZone: 'Europe/Moscow'});
  }
  function getMoscowTimeStr(tsMs) {
    return new Date(tsMs).toLocaleTimeString('ru-RU', {hour: '2-digit', minute: '2-digit', timeZone: 'Europe/Moscow'});
  }

  var _lmDevTokMem = {};
  function _lmDeviceToken() {
    var _u = (PW.visitor_name || '').toLowerCase().replace(/[^a-z0-9_]/g, '_').slice(0, 30);
    var K = 'lm_dev_tok_v1' + (_u ? '_u_' + _u : '');
    if (_lmDevTokMem[K]) return _lmDevTokMem[K];
    var t = '';
    try { if (typeof GM_getValue === 'function') t = GM_getValue(K, '') || ''; } catch (e) {}
    if (!t) { try { t = window.localStorage.getItem(K) || ''; } catch (e) {} }
    if (!t) { try { t = window.sessionStorage.getItem(K) || ''; } catch (e) {} }
    if (!t) {
      try {
        t = 'lmd_' + (window.crypto && crypto.randomUUID ? crypto.randomUUID()
              : (Date.now().toString(36) + Math.random().toString(36).slice(2) + Math.random().toString(36).slice(2)));
      } catch (e) { t = 'lmd_' + Date.now().toString(36) + Math.random().toString(36).slice(2); }
      try { if (typeof GM_setValue === 'function') GM_setValue(K, t); } catch (e) {}
      try { window.localStorage.setItem(K, t); } catch (e) {}
      try { window.sessionStorage.setItem(K, t); } catch (e) {}
    }
    _lmDevTokMem[K] = t;
    return t;
  }
  var _lmDeviceBound = false;
  function _lmBindDevice() {
    if (_lmDeviceBound) return;
    var u = PW.visitor_name || '', t = _lmDeviceToken();
    if (!u || !t) return;
    _lmDeviceBound = true;
    var lbl = '';
    try { lbl = (navigator.platform || '') + '|' + (location.host || ''); } catch (e) {}
    window.lmFetch(LM_API_BASE + '/device/bind', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'X-Script-Version': LM_SCRIPT_VERSION },
      body: JSON.stringify({ username: u, device_token: t, label: lbl })
    }).catch(function () { _lmDeviceBound = false; });
  }
  function getAuth() {
    var u = PW.visitor_name || '';
    var t = _lmDeviceToken();
    return (u && t) ? {username: u, token: t} : null;
  }

  function parseDat(v) {
    if (!v) return null;
    if (typeof v === 'object') return v;
    try { return JSON.parse(v); } catch(e) { return null; }
  }

  function findClubLink(doc) {
    var link = doc.querySelector('.usn__club-item-top a[href*="/clubs/"]');
    if (!link) {
      var all = doc.querySelectorAll('a[href*="/clubs/"]');
      for (var i = 0; i < all.length; i++) {
        if (/\/clubs\/\d+\//.test(all[i].getAttribute('href') || '')) { link = all[i]; break; }
      }
    }
    return link;
  }

  function parseUserGuardianItems(doc) {
    var items = [];
    doc.querySelectorAll('.user-labyrinth__item').forEach(function(item) {
      var roomEl = item.querySelector('.user-labyrinth__room');
      var nameEl = item.querySelector('.user-labyrinth__guardian-name');
      var imgEl  = item.querySelector('.user-labyrinth__guardian');
      var dateEl = item.querySelector('.user-labyrinth__date');
      if (!roomEl) return;
      var coords = parseRoomText(roomEl.textContent.trim());
      items.push({
        name: nameEl ? nameEl.textContent.trim().split('\n')[0].trim() : '',
        img:  imgEl  ? imgEl.src : '',
        room: roomEl.textContent.trim(),
        date: dateEl ? dateEl.textContent.trim() : '',
        x: coords ? coords.x : 0,
        y: coords ? coords.y : 0
      });
    });
    return items;
  }

  function parseClubGuardianItems(doc) {
    var items = [];
    doc.querySelectorAll('.club-labyrinth__item').forEach(function(item) {
      var roomEl = item.querySelector('.club-labyrinth__room');
      var textEl = item.querySelector('.club-labyrinth__text b');
      var imgEl  = item.querySelector('.club-labyrinth__avatar');
      var dateEl = item.querySelector('.club-labyrinth__date');
      if (!roomEl) return;
      var coords = parseRoomText(roomEl.textContent.trim());
      items.push({
        name: textEl ? textEl.textContent.trim() : '',
        img:  imgEl  ? imgEl.src : '',
        room: roomEl.textContent.trim(),
        date: dateEl ? dateEl.textContent.trim() : '',
        x: coords ? coords.x : 0,
        y: coords ? coords.y : 0
      });
    });
    return items;
  }

  function _lmXPlayer() {
    var n = PW.visitor_name || '';
    try { for (var i = 0; i < n.length; i++) { if (n.charCodeAt(i) > 255) return encodeURIComponent(n); } } catch (e) { return encodeURIComponent(n); }
    return n;
  }
  window._lmXPlayer = _lmXPlayer;
  function vpsAuthHeaders(withJson) {
    var headers = {
      'X-Player': _lmXPlayer(),
      'X-Script-Version': LM_SCRIPT_VERSION,
      'Authorization': 'Bearer ' + _lmDeviceToken()
    };
    if (withJson) headers['Content-Type'] = 'application/json';
    return headers;
  }
  window.LM_VPS_AUTH_HEADERS = vpsAuthHeaders;
  window._lmDevTok = _lmDeviceToken;
  window._lmBindDevice = _lmBindDevice;
  window._lmCollectAccRows = function (cb) {
    var done = false, attempt = 0, ckey = 'lm_acc_cache' + _lmUserSuffix, diag = [];
    function report(n) {
      try {
        var rk = 'lm_acc_diag' + _lmUserSuffix;
        var last = parseInt(window.lmLS.get(rk) || '0', 10);
        if (Date.now() - last < 21600000) return;
        window.lmLS.set(rk, String(Date.now()));
        var v = (typeof GM_info !== 'undefined' && GM_info.script && GM_info.script.version) ? GM_info.script.version : '10.15';
        window.lmFetch((window.LM_ENV_CONFIG && window.LM_ENV_CONFIG.apiBase) + '/report', {
          method: 'POST', cache: 'no-store',
          headers: vpsAuthHeaders(true),
          body: JSON.stringify({ trigger: 'acc_collect_fail', username: PW.visitor_name || 'unknown', script_version: v, url: location.href, user_agent: navigator.userAgent, user_message: 'AUTO: сбор истории ACC не удался, rows=' + n, diagnostics: { acc_attempts: diag } })
        }).catch(function () {});
      } catch (e) {}
    }
    function fin(rows) { if (done) return; done = true; var n = rows ? rows.length : 0; if (n < 8) report(n); try { cb(rows || []); } catch (e) {} }
    try {
      var cached = JSON.parse(window.lmLS.get(ckey) || 'null');
      if (cached && (Date.now() - (cached.t || 0) < 172800000) && Array.isArray(cached.rows) && cached.rows.length >= 8) return fin(cached.rows);
    } catch (e) {}
    setTimeout(function () { fin([]); }, 45000);
    function parseRows(html) {
      var doc = new DOMParser().parseFromString(html, 'text/html');
      var trs = doc.querySelectorAll('.ncard-transactions__table tbody tr');
      if (!trs.length) trs = doc.querySelectorAll('table tbody tr');
      var rows = [];
      trs.forEach(function (tr) {
        var tds = tr.querySelectorAll('td');
        if (tds.length < 2) return;
        var amount = tds[0].textContent.trim(), balance = tds[1].textContent.trim();
        var neg = amount.indexOf('-') >= 0;
        var d = parseInt(amount.replace(/[^0-9]/g, ''), 10) * (neg ? -1 : 1);
        var b = parseInt(balance.replace(/[^0-9]/g, ''), 10);
        if (!isNaN(d) && !isNaN(b)) rows.push({ acc_delta: d, acc_after: b });
      });
      return rows;
    }
    function fetchPage(n) {
      var u = n > 1 ? location.origin + '/acchistory/page/' + n + '/' : location.origin + '/acchistory/';
      var st = 0;
      return window.lmFetch(u, { headers: {}, credentials: 'same-origin' })
        .then(function (r) { st = r.status; if (!r.ok) throw new Error('http ' + r.status); return r.text(); })
        .then(function (html) { return { rows: parseRows(html), s: st, len: html.length, cls: html.indexOf('ncard-transactions__table') >= 0 }; });
    }
    function tryFetch() {
      attempt++;
      var depth = Math.max(1, Math.min(8, window._lmAccDepth || 1));
      var acc = [], pg = 1;
      function finishAttempt() {
        if (acc.length >= 8) {
          try { window.lmLS.set(ckey, JSON.stringify({ t: Date.now(), rows: acc })); } catch (e) {}
          fin(acc);
        } else if (attempt < 4) { setTimeout(tryFetch, 1500 * attempt + Math.floor(Math.random() * 2500)); }
        else { fin(acc); }
      }
      function next() {
        fetchPage(pg).then(function (r) {
          diag.push({ a: attempt, p: pg, s: r.s, len: r.len, cls: r.cls, rows: r.rows.length });
          acc = acc.concat(r.rows);
          pg++;
          if (pg <= depth) { setTimeout(next, 700 + Math.floor(Math.random() * 600)); return; }
          finishAttempt();
        }).catch(function (e) {
          diag.push({ a: attempt, p: pg, err: String((e && e.message) || e).slice(0, 60) });
          finishAttempt();
        });
      }
      next();
    }
    tryFetch();
  };
  var _lmUserSuffix = (function() {
    var u = (PW.visitor_name || '').toLowerCase().replace(/[^a-z0-9_]/g, '_').slice(0, 30);
    return u ? '_u_' + u : '';
  })();
  window._lmAccCacheClear = function () { try { window.lmLS.remove('lm_acc_cache' + _lmUserSuffix); } catch (e) {} };

  var SESSION_ID = (function() {
    var today = getMoscowDate();
    var key = 'lm_session_v1' + _lmUserSuffix;
    try {
      var stored = JSON.parse(window.lmLS.get(key) || '{}');
      if (stored.date === today && stored.id) return stored.id;
      var newId = 'sess_' + Date.now() + '_' + Math.random().toString(36).slice(2,8);
      window.lmLS.set(key, JSON.stringify({date: today, id: newId}));
      return newId;
    } catch(e) {
      return 'sess_' + Date.now() + '_' + Math.random().toString(36).slice(2,8);
    }
  })();
  var PUSH_SESSION_ID = SESSION_ID + '_page_' + Date.now().toString(36) + '_' + Math.random().toString(36).slice(2,7);
  window._lmPushSid = PUSH_SESSION_ID;

  var LS_KEY         = 'lm_cloud_map';
  var LS_STEPS_CACHE = 'lm_steps_cache_v1'     + _lmUserSuffix;
  var LS_STEPS_SENT  = 'lm_steps_sent_v1'      + _lmUserSuffix;
  var LS_FLUSH_COUNT = 'lm_flush_count_v1'     + _lmUserSuffix;
  var LS_SERVER_SEQ  = 'lm_server_step_seq_v2' + _lmUserSuffix;

  function getCachedSteps() {
    try { return JSON.parse(window.lmLS.get(LS_STEPS_CACHE) || '[]'); } catch(e) { return []; }
  }

  function getSentCount() {
    return parseInt(window.lmLS.get(LS_STEPS_SENT) || '0', 10);
  }

  var CACHE_MAX  = 500;
  var CACHE_TRIM = 100;
  var FULL_VIEW_MIN_CELL = 6;
  var FULL_VIEW_MAX_CELL = 12;
  var FULL_FOCUS_SCALE   = 0.42;
  var FOCUS_ZOOM_NEAR    = 1.1;

  function stepCacheKey(s, index) {
    var ev = s && (s.event || s.ev) || 'unknown';
    var idx = s && s.step_index != null ? s.step_index : index;
    var sid = s && s.session_id ? s.session_id : SESSION_ID;
    return sid + '|' + idx + '|' + (s && s.x) + '|' + (s && s.y) + '|' + ev;
  }

  function cacheSteps(steps) {
    if (!steps || !steps.length) return;
    try {
      var stored = getCachedSteps();
      var existingKeys = {};
      var nextServerIndex = parseInt(window.lmLS.get(LS_SERVER_SEQ) || '0', 10);
      if (!Number.isFinite(nextServerIndex) || nextServerIndex < 0) nextServerIndex = 0;
      var migrated = false;

      stored.forEach(function(s, i) {
        existingKeys[s._lmCacheKey || stepCacheKey(s, s._lmIndex != null ? s._lmIndex : i)] = true;
        if (!Number.isInteger(Number(s._lmServerStepIndex))) {
          s._lmServerStepIndex = nextServerIndex++;
          migrated = true;
        } else {
          nextServerIndex = Math.max(nextServerIndex, Number(s._lmServerStepIndex) + 1);
        }
      });

      var added = 0;
      steps.forEach(function(s, i) {
        var key = stepCacheKey(s, i);
        if (existingKeys[key]) return;
        var serverIndex;
        if (Number.isInteger(Number(s._lmServerStepIndex))) {
          serverIndex = Number(s._lmServerStepIndex);
          if (serverIndex >= nextServerIndex) nextServerIndex = serverIndex + 1;
        } else {
          serverIndex = nextServerIndex++;
          s._lmServerStepIndex = serverIndex;
        }
        var copy = Object.assign({}, s, {
          _lmIndex: i,
          _lmCacheKey: key,
          _lmServerStepIndex: serverIndex,
          _lmCapturedAt: new Date().toISOString(),
          session_id: s.session_id || PUSH_SESSION_ID,
          step_index: serverIndex
        });
        if (PW.labyrinthData && PW.labyrinthData.emission && PW.labyrinthData.emission.active) {
          var _em = PW.labyrinthData.emission;
          copy._dat = Object.assign({}, copy._dat || copy.dat || {}, {
            during_emission: true,
            emission: { active: true, last_start_at: _em.last_start_at, last_end_at: _em.last_end_at, seconds_left: _em.seconds_left }
          });
        }
        stored.push(copy);
        existingKeys[key] = true;
        added++;
      });

      if (added === 0 && !migrated) return;
      window.lmLS.set(LS_SERVER_SEQ, String(nextServerIndex));

      if (stored.length > CACHE_MAX) {
        var sentIdx = Math.min(getSentCount(), stored.length);
        var trimCount = Math.min(Math.max(CACHE_TRIM, stored.length - CACHE_MAX), sentIdx);
        if (trimCount > 0) {
          stored = stored.slice(trimCount);
          window.lmLS.set(LS_STEPS_SENT, String(Math.max(0, sentIdx - trimCount)));
        }
      }
      window.lmLS.set(LS_STEPS_CACHE, JSON.stringify(stored));
    } catch(e) { lmDebug('[Карта] Ошибка локального кеша', e); }
  }

  function collectProfile() {
    var ld = PW.labyrinthData || {};
    var boostItems = (ld.boosts && ld.boosts.items) || {};
    var mine = ld.personalMine || {};
    var onLabyrinthPage = !!document.getElementById('labyrinthMap') ||
                          /^\/labyrinth\/?/.test(window.location.pathname || '');
    var position = onLabyrinthPage && mapData() && mapData().current
      ? mapData().current
      : null;

    function textNumber(id) {
      var el = document.getElementById(id);
      if (!el) return null;
      var value = parseInt(String(el.textContent || '').replace(/\s+/g, ''), 10);
      return Number.isFinite(value) ? value : null;
    }

    var result = {
      acc_balance:  textNumber('labyrinthBank'),
      total_steps:  textNumber('labyrinthTotalSteps'),
      today_steps:  textNumber('labyrinthTodaySteps'),
      max_steps:    textNumber('labyrinthMaxSteps'),
      trap_backs:   (mapData()&&mapData().steps||[]).filter(function(s){return s.event==='trap_back';}).length,
      boost_vision: boostItems.vision  || 0,
      boost_shield: boostItems.shield  || 0,
      boost_reward: boostItems.reward  || 0,
      boost_berserk:boostItems.berserk || 0,
      mine_has:          mine.has_mine ? 1 : 0,
      mine_level:        mine.has_mine ? Number(mine.level || 0) : null,
      mine_acc:          mine.has_mine ? Number(mine.pending_acc || mine.acc || 0) : null,
      mine_cards:        mine.has_mine ? Number(mine.pending_cards || mine.cards || 0) : null,
      mine_cards_max:    mine.has_mine ? Number(mine.max_cards || mine.cards_max || 0) : null,
      mine_pct:          mine.has_mine ? Number(mine.storage_progress || mine.storage_pct || 0) : null,
      mine_acc_per_hour: mine.has_mine ? Number(mine.acc_per_hour || 0) : null,
      mine_x:            mine.has_mine && Number.isFinite(Number(mine.x ?? mine.mine_x)) ? Number(mine.x ?? mine.mine_x) : null,
      mine_y:            mine.has_mine && Number.isFinite(Number(mine.y ?? mine.mine_y)) ? Number(mine.y ?? mine.mine_y) : null,
      mine_updated_at:   mine.has_mine ? new Date().toISOString() : null,
      mine_auto_enabled:   window.lmLS.get('lm_mine_auto_enabled'  + _lmUserSuffix) !== 'false' ? true : false,
      mine_auto_threshold: parseInt(window.lmLS.get('lm_mine_threshold' + _lmUserSuffix) || '100', 10),
      user_level:   PW.user_level || 0,
      group_id:     PW.dle_group || 0,
      stars:        PW.stars_user_rating || 0,
      last_event:   ld.lastEvent || '',
    };

    if (position && Number.isFinite(Number(position.x)) && Number.isFinite(Number(position.y))) {
      result.current_x = Number(position.x);
      result.current_y = Number(position.y);
    }

    Object.keys(result).forEach(function(key) {
      if (result[key] == null || Number.isNaN(result[key])) delete result[key];
    });

    return result;
  }

  var _profileSyncTimer = null;
  var _lastProfileJson = '';

  function syncProfileToVPS(force) {
    var auth = getAuth();
    if (!auth) return Promise.resolve(false);
    var username = auth.username, token = auth.token;

    var profile = collectProfile();
    var encoded = JSON.stringify(profile);
    if (!force && encoded === _lastProfileJson) return Promise.resolve(true);

    return window.lmFetch(VPS_URL + '/profile-sync', {
      method: 'POST',
      headers: vpsAuthHeaders(true),
      body: encoded
    }).then(function(r) {
      if (!r.ok) throw new Error('HTTP ' + r.status);
      return r.json();
    }).then(function() {
      _lastProfileJson = encoded;
      return true;
    }).catch(function(e) {
      lmDebug('[Карта] profile-sync:', e);
      return false;
    });
  }

  function scheduleProfileSync() {
    if (_profileSyncTimer) clearTimeout(_profileSyncTimer);
    _profileSyncTimer = setTimeout(function() {
      _profileSyncTimer = null;
      syncProfileToVPS(false);
    }, 1200);
  }

  var GUARDIAN_EVENTS = {guardian_user:1,guardian_club:1,can_capture:1};
  var ROOM_OBJ_EVENTS  = {room_trap:1,room_gift:1};
  var VARIABLE_EV      = {reward:1,reward_card:1,locked_chest:1,locked_chest_result:1,
    luck_altar:1,luck_altar_result:1,card_trader:1,card_trader_result:1,
    mimic_chest:1,mimic_chest_hit:1,mimic_chest_killed:1,mimic_chest_escape:1,
    mimic_chest_reward:1,mimic_chest_back:1,personal_mine:1,personal_mine_created:1,
    personal_mine_collect:1,foreign_mine:1,echo_room:1,echo_room_result:1,fate_room:1,fate_room_result:1,
    room_trap:1,room_gift:1};

  function filterNewSteps(steps) {
    var result = [];
    var seen = {};
    for (var i = 0; i < steps.length; i++) {
      var s = steps[i];
      var key = s.x + '_' + s.y;
      var ev = s.event || 'unknown';
      var cloudRoom = cloudMap[key];

      if (GUARDIAN_EVENTS[ev]) { result.push(s); continue; }

      if (ROOM_OBJ_EVENTS[ev]) { result.push(s); continue; }

      if (!cloudRoom) { if (!seen[key]) { seen[key]=1; result.push(s); } continue; }

      if (cloudRoom.event === 'unknown' && ev !== 'unknown') {
        if (!seen[key]) { seen[key]=1; result.push(s); } continue;
      }

      if (VARIABLE_EV[ev] && !seen[key]) {
        seen[key]=1; result.push(s); continue;
      }

      if (ev !== 'unknown' && cloudRoom.event !== ev && !seen[key]) {
        seen[key]=1; result.push(s);
      }
    }
    return result;
  }

  window._lmReportQuiz = function(payload) {
    try {
      if (!payload || !payload.question_text || !payload.correct_answer_text) return;
      window.lmFetch(VPS_URL + '/quiz/report', {
        method: 'POST',
        headers: vpsAuthHeaders(true),
        body: JSON.stringify(payload)
      }).then(function(r){ if (r && r.ok) lmDebug('[LabMap] вікторину збережено'); }).catch(function() {});
    } catch(e) {}
  };

  var _flushInProgress = false;
  function flushStepsToServer() {
    if (_flushInProgress) return;
    try {
      var stored  = getCachedSteps();
      var sentCnt = Math.min(getSentCount(), stored.length);
      var allNew  = stored.slice(sentCnt);
      if (!allNew.length) return;

      _flushInProgress = true;
      var toSend = filterNewSteps(allNew);
      var auth = getAuth();
      if (!auth) { _flushInProgress = false; return; }
      var username = auth.username, pushToken = auth.token;
      var vpsSteps = allNew.map(function(s) {
        return {
          x: s.x, y: s.y, ev: s.ev || s.event || 'empty',
          acc_delta: s.acc_delta != null ? Number(s.acc_delta) : null,
          acc_after: s.acc_after != null ? Number(s.acc_after) : null,
          session_id: s.session_id || PUSH_SESSION_ID,
          step_index: Number.isInteger(Number(s._lmServerStepIndex)) ? Number(s._lmServerStepIndex) : (s.step_index != null ? Number(s.step_index) : null),
          ts: s._lmCapturedAt || s.ts || null,
          dat: parseDat(s.dat || s._dat)
        };
      });

      function sendPrimary() {
        return window.lmFetch(VPS_URL + '/push', {
          method: 'POST',
          headers: vpsAuthHeaders(true),
          body: JSON.stringify({ steps: vpsSteps })
        }).then(function(r) {
          if (!r.ok) throw new Error('HTTP ' + r.status);
          return r.json();
        }).then(function(d) {
          _vpsAlive = true;
          return { ok: true, reserve: false, saved: d && d.saved != null ? d.saved : allNew.length };
        });
      }

      sendPrimary().catch(function() {
        _vpsAlive = false;
        throw new Error('primary_unavailable');
      }).then(function(result) {
        var totalSent = sentCnt + allNew.length;
        var currentStored = getCachedSteps();
        var acknowledged = Math.min(totalSent, currentStored.length);
        window.lmLS.set(LS_STEPS_SENT, String(acknowledged));
        window.lmLS.set(LS_FLUSH_COUNT, String(acknowledged));
        lmInfo('Карта обновлена');
      }).catch(function() {
        lmWarn('Карта сохранена локально');
      }).then(function() {
        _flushInProgress = false;
      }).catch(function() {
        _flushInProgress = false;
      });
    } catch(e) {
      _flushInProgress = false;
      lmWarn('Ошибка подготовки данных к отправке');
      lmDebug(e);
    }
  }

  function syncCacheWithSession() {
    var d = mapData();
    if (!d || !d.steps || !d.steps.length) return;
    cacheSteps(d.steps);
  }
  var LS_REFRESH_KEY = 'lm_last_refresh';
  var LS_FULL_REFRESH_KEY = 'lm_last_full_refresh';
  var CELL_SIZE      = 28;
  var ZOOM_IN        = 1.18;
  var ZOOM_OUT       = 0.85;
  var ZOOM_MIN       = 0.2;
  var ZOOM_MAX       = 5;

  var DETAIL_FIELD = {q:'q',o:'o',c:'c',p:'p',a:'a',bl:'bl',bh:'bh',r:'r',ml:'ml',ma:'ma',mc:'mc',eu:'eu',ar:'ar',cr:'cr'};
  var LM_RESULT_EV = {trap_back:1, echo_room_result:1, fate_room_result:1, room_trap_result:1};

  function getEmissionStepDat() {
    var ld = PW.labyrinthData || {};
    if (!ld.emission || !ld.emission.last_start_at) return null;
    var d = {
      emission: {
        active: !!ld.emission.active,
        last_start_at: ld.emission.last_start_at,
        last_end_at: ld.emission.last_end_at || 0,
        cooldown_left: ld.emission.next_check_left || ld.emission.cooldown_left || 0
      }
    };
    if (ld.emission.active) d.during_emission = true;
    return d;
  }

  function scrapeRoomDetails(eventType) {
    function getText(sel) {
      var el = document.querySelector(sel);
      return el ? el.textContent.trim().substring(0, 200) : null;
    }
    function getNum(sel) {
      var t = getText(sel);
      return t ? (parseFloat(t.replace(/[^0-9.\-]/g,'')) || null) : null;
    }
    var d = {};
    try {
      switch (eventType) {
        case 'quiz': case 'quiz_result':
          var q = getText('#labyrinthQuizQuestion,.labyrinth-quiz__question,.labyrinth__quiz-question,[class*="quiz"][class*="question"]');
          if (q) d[DETAIL_FIELD.q] = q;
          var opts = [];
          document.querySelectorAll('#labyrinthQuiz .button,.labyrinth-quiz__answer,.labyrinth__quiz-answer,[class*="quiz"][class*="answer"]')
            .forEach(function(el){ var t=el.textContent.trim(); if(t) opts.push(t.substring(0,100)); });
          if (opts.length) d[DETAIL_FIELD.o] = opts;
          break;
        case 'mini_boss': case 'hard_boss':
          var c = getText('#labyrinthHardBossNeedName,#labyrinthMiniBossName,#labyrinthHardBossName,.labyrinth__hardboss-card-name,.character-name,.labyrinth-boss__card-name,[class*="boss"][class*="card"]');
          if (c) d[DETAIL_FIELD.c] = c.substring(0,80);
          var hp = getNum('#labyrinthMiniBossIndicator,#labyrinthHardBossIndicator,.health-indicator,.labyrinth-boss__hp,[class*="boss"][class*="hp"]');
          if (hp) d[DETAIL_FIELD.bh] = hp;
          var bl = getNum('#labyrinthHardBossNeedRank,.labyrinth__hardboss-card-rank,.labyrinth-boss__level,[class*="boss"][class*="level"]');
          if (bl) d[DETAIL_FIELD.bl] = bl;
          break;
        case 'card_trader': case 'card_trader_result':
          var tc = getText('.labyrinth__trader-name,.labyrinth-trader__card-name,[class*="trader"][class*="card"]');
          if (tc) d[DETAIL_FIELD.c] = tc.substring(0,80);
          var tp = getNum('.labyrinth__trader-price,.labyrinth-trader__price,[class*="trader"][class*="price"]');
          if (tp) d[DETAIL_FIELD.p] = tp;
          break;
        case 'locked_chest': case 'locked_chest_result':
          var cr = getText('#labyrinthEventText,.labyrinth__chest-desc,.labyrinth__chest-title,.labyrinth-chest__result,[class*="chest"][class*="result"],[class*="chest"][class*="reward"]');
          if (cr) d[DETAIL_FIELD.cr] = cr.substring(0,100);
          break;
        case 'luck_altar': case 'luck_altar_result':
          var ar = getText('.labyrinth__luck-title,.labyrinth__luck-desc,.labyrinth-altar__result,[class*="altar"][class*="result"]');
          if (ar) d[DETAIL_FIELD.ar] = ar.substring(0,100);
          break;
        case 'relic_room':
          var rl = getText('.labyrinth-relic__name,[class*="relic"][class*="name"],[class*="relic"]');
          if (rl) d[DETAIL_FIELD.r] = rl.substring(0,80);
          break;
        case 'echo_room': case 'echo_room_result':
          var eu = getText('#labyrinthEchoUser .labyrinth__echo-user-name,#labyrinthEchoUser,.labyrinth-echo__username,[class*="echo"][class*="user"],[class*="echo"]');
          if (eu) d[DETAIL_FIELD.eu] = eu.substring(0,40);
          break;
        case 'fate_room': case 'fate_room_result':
          var fr = PW.labyrinthData && PW.labyrinthData.fateRoom;
          if (fr) d.fate = fr;
          break;
        case 'personal_mine': case 'personal_mine_collect':
          var pm = PW.labyrinthData && PW.labyrinthData.personalMine;
          if (pm) {
            if (pm.level)         d[DETAIL_FIELD.ml] = pm.level;
            if (pm.pending_acc)   d[DETAIL_FIELD.ma] = pm.pending_acc;
            if (pm.pending_cards) d[DETAIL_FIELD.mc] = pm.pending_cards;
          }
          break;
        case 'reward':
          var ra = getNum('#labyrinthEventAcc,.labyrinth__event-acc,.labyrinth-reward__amount,[class*="reward"][class*="amount"]');
          if (ra) d[DETAIL_FIELD.a] = ra;
          break;
        case 'penalty':
          var pa = getNum('#labyrinthEventAcc,.labyrinth__event-acc,.labyrinth-penalty__amount,[class*="penalty"][class*="amount"]');
          if (pa) d[DETAIL_FIELD.a] = -Math.abs(pa);
          break;
      }
    } catch(e) {}
    return Object.keys(d).length ? d : null;
  }

  var COLORS = {
    start:'#1a2a5e', reward:'#0d2e12', penalty:'#2e0a0a',
    quiz:'#0a1550', quiz_result:'#0a1550', puzzle:'#1e0d35',
    jackpot:'#2e2200', reward_card:'#002e28', shield_block:'#2e0a0a',
    empty:'#0a0c18', collection:'#001a38', trap_back:'#2e0505',
    mini_boss:'#2e1000', hard_boss:'#12003a', luck_altar:'#062006',
    luck_altar_result:'#062006', locked_chest:'#2e2000',
    locked_chest_result:'#2e2000', card_trader:'#001230',
    card_trader_result:'#001230', relic_room:'#160030',
    recovery_room:'#002e22', room_trap:'#1a0f00', room_gift:'#1a0f00',
    personal_mine:'#1a1200', personal_mine_created:'#0d1a0d', personal_mine_collect:'#001a1a',
    foreign_mine:'#1a0800', club_war_room:'#1a0020',
    echo_room:'#0d1520', echo_room_result:'#0d1520', fate_room:'#24143a', fate_room_result:'#24143a', fatigue:'#1a1000',
    spiritual_teleport:'#160030', resonance_room:'#062028', kobold_room:'#241407',
    mimic_chest:'#2a0030', mimic_chest_hit:'#2a0030', mimic_chest_killed:'#2a0030',
    mimic_chest_escape:'#2a0030', mimic_chest_reward:'#2a0030', mimic_chest_back:'#2a0030',
    guardian_user:'#5a3a00', guardian_club:'#1a0040', can_capture:'#003a1a',
    unknown:'#080a12'
  };
  var ICONS = {
    start:'⚑', reward:'+', penalty:'!', quiz:'?', quiz_result:'?',
    puzzle:'🧩', jackpot:'★', reward_card:'🎴', shield_block:'!',
    empty:'·', collection:'🃏', trap_back:'↺', mini_boss:'👹',
    hard_boss:'💀', luck_altar:'🍀', luck_altar_result:'🍀',
    locked_chest:'🔒', locked_chest_result:'🎁', card_trader:'🛒',
    card_trader_result:'🛒', relic_room:'🔮', recovery_room:'💚',
    room_trap:'👤', room_gift:'👤', spiritual_teleport:'🌀',
    mimic_chest:'👅', mimic_chest_hit:'👅', mimic_chest_killed:'👅',
    mimic_chest_escape:'👅', mimic_chest_reward:'👅', mimic_chest_back:'👅',
    guardian_user:'👑', guardian_club:'🛡', can_capture:'⚐',
    personal_mine:'⛏', personal_mine_created:'⛏', personal_mine_collect:'⛏',
    foreign_mine:'⚒', fatigue:'💤', club_war_room:'🏴',
    echo_room:'👣', echo_room_result:'👣', fate_room:'🧵', fate_room_result:'🧵',
    resonance_room:'📡', kobold_room:'👺',
    rl:'🔮', mx:'👅', mr:'👅'
  };
  var ICOLORS = {
    start:'#9fb4ff', reward:'#ffd66b', penalty:'#ff8b8b',
    quiz:'#8bb4ff', quiz_result:'#8bb4ff', puzzle:'#e0d0ff',
    jackpot:'#ffd66b', reward_card:'#aaffee', shield_block:'#ff8b8b',
    empty:'rgba(255,255,255,.3)', collection:'#8fd3ff', trap_back:'#ff8a8a',
    mini_boss:'#ffbb88', hard_boss:'#cc88ff', luck_altar:'#6ee786',
    luck_altar_result:'#6ee786', locked_chest:'#ffd66b',
    locked_chest_result:'#ffd66b', card_trader:'#8fd3ff',
    card_trader_result:'#8fd3ff', relic_room:'#c59cff',
    recovery_room:'#6ee786', room_trap:'#ffd66b', room_gift:'#ffd66b',
    spiritual_teleport:'#c59cff',
    mimic_chest:'#ff9ecb', mimic_chest_hit:'#ff9ecb', mimic_chest_killed:'#ff9ecb',
    mimic_chest_escape:'#ff9ecb', mimic_chest_reward:'#ff9ecb', mimic_chest_back:'#ff9ecb',
    guardian_user:'#ffd700', guardian_club:'#c59cff', can_capture:'#44ff88',
    personal_mine:'#ffd66b', personal_mine_created:'#6ee786', personal_mine_collect:'#aaffee',
    foreign_mine:'#ff9944', fatigue:'#ffd66b', club_war_room:'#ff6666',
    echo_room:'#b0a0ff', echo_room_result:'#b0a0ff', fate_room:'#d8a8ff', fate_room_result:'#d8a8ff',
    resonance_room:'#5fe0ff', kobold_room:'#e8a15c',
    rl:'#c59cff', mx:'#ff9ecb', mr:'#ff9ecb'
  };
  var NAMES = {
    start:'Старт', reward:'Награда', penalty:'Штраф',
    quiz:'Викторина', quiz_result:'Викторина', puzzle:'Пазл',
    jackpot:'Джекпот', reward_card:'Карта', shield_block:'Штраф (щит)',
    empty:'Пустая', collection:'Коллекция', trap_back:'Откат',
    mini_boss:'Мини-босс', hard_boss:'Хард-босс', luck_altar:'Алтарь',
    luck_altar_result:'Алтарь', locked_chest:'Сундук',
    locked_chest_result:'Сундук✓', card_trader:'Торговец',
    card_trader_result:'Торговец', relic_room:'Реликвия',
    recovery_room:'Лечение', room_trap:'Комната игрока',
    room_gift:'Комната игрока', spiritual_teleport:'Телепорт',
    mimic_chest:'Мимик', mimic_chest_hit:'Мимик⚔',
    mimic_chest_killed:'Мимик💀', mimic_chest_escape:'Мимик🏃',
    mimic_chest_reward:'Мимик+', mimic_chest_back:'Мимик↺',
    guardian_user:'Занято стражем', guardian_club:'Захвачено клубом',
    can_capture:'Можно захватить', unknown:'Неизвестная',
    personal_mine:'Шахта', personal_mine_created:'Моя шахта⛏', personal_mine_collect:'Шахта✓',
    foreign_mine:'Чужая шахта⚒', fatigue:'Усталость',
    club_war_room:'Битва клубов', echo_room:'Отголосок', echo_room_result:'Отголосок✓',
    fate_room:'Выбор судьбы', fate_room_result:'Выбор судьбы✓', resonance_room:'Резонанс', kobold_room:'Кобольд',
    rl:'Реликвия', mx:'Мимик↺', mr:'Мимик+'
  };
  var DESCS = {
    start:'Начало пути лабиринта', reward:'Комната с ACC наградой',
    penalty:'Штрафная комната, теряешь ACC', shield_block:'Штраф был заблокирован щитом',
    quiz:'Вопрос на знание аниме', quiz_result:'Вопрос на знание аниме',
    puzzle:'Головоломка с выбором пути', jackpot:'Редкая удача — крупная награда',
    reward_card:'Можно получить карту аниме', empty:'Пустая комната, ничего не происходит',
    collection:'Проверка коллекции карт', trap_back:'Ловушка отката — возврат назад',
    mini_boss:'Мини-босс, нужно победить', hard_boss:'Сложный босс, нужна карта',
    luck_altar:'Алтарь удачи — случайный эффект', luck_altar_result:'Алтарь удачи — случайный эффект',
    locked_chest:'Закрытый сундук с наградой', locked_chest_result:'Сундук открыт, получена награда',
    card_trader:'Торговец продаёт карту за ACC', card_trader_result:'Торговец продаёт карту за ACC',
    relic_room:'Реликвия — особый предмет', recovery_room:'Восстановление — бонус к ходам',
    room_trap:'Здесь ловушка или подарок от другого игрока', room_gift:'Здесь ловушка или подарок от другого игрока',
    spiritual_teleport:'Духовный телепорт — перемещение', mimic_chest:'Редкий монстр — сундук-мимик',
    mimic_chest_hit:'Редкий монстр — сундук-мимик', mimic_chest_killed:'Сундук-мимик был побеждён',
    mimic_chest_escape:'Игрок сбежал от мимика', mimic_chest_reward:'Получена награда от мимика',
    mimic_chest_back:'Мимик вернул игрока назад', guardian_user:'Здесь стоит личный страж игрока',
    guardian_club:'Здесь стоит страж клуба', can_capture:'Свободная комната — можно поставить стража',
    personal_mine:'Комната шахты', personal_mine_created:'Ты основал здесь шахту',
    personal_mine_collect:'Ты собрал добычу из своей шахты',
    foreign_mine:'Здесь шахта другого игрока', club_war_room:'Комната битвы клубов',
    echo_room:'Комната отголосков', echo_room_result:'Комната отголосков — выбор уже сделан',
    fate_room:'Комната выбора судьбы', fate_room_result:'Выбор судьбы уже сделан',
    resonance_room:'Комната резонанса — можно вызвать событие с других координат', kobold_room:'Комната кобольда',
    fatigue:'Усталость от лабиринта'
  };
  var LEGEND_ITEMS = [
    ['start','Старт'],['reward','Награда'],['jackpot','Джекпот'],['penalty','Штраф'],
    ['quiz','Викторина'],['puzzle','Пазл'],['mini_boss','Мини-босс'],
    ['hard_boss','Хард-босс'],['reward_card','Карта'],['luck_altar','Алтарь'],
    ['card_trader','Торговец'],['relic_room','Реликвия'],['collection','Коллекция'],
    ['locked_chest','Сундук'],['room_trap','Игрок'],['mimic_chest','Мимик'],
    ['recovery_room','Лечение'],['spiritual_teleport','Телепорт'],['trap_back','Откат'],
    ['empty','Пусто'],['can_capture','Взять'],['guardian_user','Страж'],
    ['guardian_club','Страж клуба'],['personal_mine','Шахта'],['foreign_mine','Чужая шахта'],
    ['club_war_room','Битва клубов'],['echo_room','Комната отголосков'],['fate_room','Выбор судьбы'],['resonance_room','Резонанс'],['kobold_room','Кобольд'],['__variable__','🔀 Переменные']
  ];
  var LM_LABELS = {}; LEGEND_ITEMS.forEach(function(i){ LM_LABELS[i[0]] = i[1]; });
  var LM_FILTER_GROUPS = [
    ['✅ Хорошие', ['reward','jackpot','reward_card','luck_altar','relic_room','collection','locked_chest','recovery_room','can_capture','personal_mine']],
    ['☠️ Плохие',  ['penalty','mini_boss','hard_boss','mimic_chest','trap_back','room_trap','foreign_mine','club_war_room','kobold_room']],
    ['◽ Нейтральные', ['start','quiz','puzzle','card_trader','spiritual_teleport','empty','guardian_user','guardian_club','fate_room','resonance_room','echo_room','__variable__']]
  ];

  var cloudMap       = {};
  var _cloudMapCount = 0;
  var LS_BLOCKED = 'lm_blocked' + _lmUserSuffix;
  function _lmIsBlocked(){ return window.lmLS.get(LS_BLOCKED) === '1'; }
  function _lmSetBlocked(on){ try { if (on) window.lmLS.set(LS_BLOCKED, '1'); else window.lmLS.remove(LS_BLOCKED); } catch(e) {} }
  var roomsCache     = null;
  var _lastCutY      = null;
  var fmOpen         = false;
  var fmX = 0, fmY = 0, fmScale = 1;
  var drag           = false, dsx = 0, dsy = 0;
  var rafId          = null;
  var filterSet      = {};
  var showMyPath     = false;
  var cleanViewMode  = false;
  var ownershipCache = null;
  var highlightRoom  = null;
  var _lmPatching    = false;
  var _pushTimer     = null;
  var _lmPencil      = 'off';
  var _lmRoute       = [];
  var LS_ROUTE       = 'lm_route' + _lmUserSuffix;
  function _routeLoad(){ try { var a = JSON.parse(window.lmLS.get(LS_ROUTE) || '[]'); _lmRoute = Array.isArray(a) ? a : []; } catch(e){ _lmRoute = []; } }
  function _routeSave(){ try { window.lmLS.set(LS_ROUTE, JSON.stringify(_lmRoute)); } catch(e){} }
  var _routeSyncTimer = null;
  function _routeSyncToServer(){
    if (_routeSyncTimer) clearTimeout(_routeSyncTimer);
    _routeSyncTimer = setTimeout(function(){
      try { window.lmFetch(VPS_URL + '/route', { method:'POST', headers: vpsAuthHeaders(true), body: JSON.stringify({ route: _lmRoute }) }).catch(function(){}); } catch(e){}
    }, 1500);
  }
  function _routeRestoreFromServer(){
    if (_lmRoute.length) return;
    try {
      window.lmFetch(VPS_URL + '/route', { headers: vpsAuthHeaders(false) })
        .then(function(r){ return r.ok ? r.json() : null; })
        .then(function(d){ if (d && Array.isArray(d.route) && d.route.length && !_lmRoute.length) { _lmRoute = d.route; _routeSave(); if (fmOpen) drawFull(); patchSiteMap(); } })
        .catch(function(){});
    } catch(e){}
  }
  function _routeIdx(x,y){ for (var i=0;i<_lmRoute.length;i++) if (_lmRoute[i].x===x && _lmRoute[i].y===y) return i; return -1; }
  function _routeTrimToCurrent(){
    if (!_lmRoute.length) return;
    var c = curPos();
    if (!c || !Number.isFinite(Number(c.x))) return;
    var idx = _routeIdx(Number(c.x), Number(c.y));
    if (idx >= 0) {
      _lmRoute = _lmRoute.slice(idx + 1);
      _routeSave(); _routeSyncToServer();
      if (fmOpen) drawFull();
      if (window._lmPencilCount) window._lmPencilCount();
    }
  }
  function _adjacent(a,b){ return Math.abs(a.x-b.x) + Math.abs(a.y-b.y) === 1; }
  function _lmMsg(title, sub){ try { showToast(title, sub); hideToast(2200); } catch(e){} }

  function _esc(s){ return String(s==null?'':s).replace(/[&<>"]/g,function(c){return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c];}); }
  var LS_CONFIRMS = 'lm_confirms' + _lmUserSuffix;
  function _cfLoad(){ try { var o=JSON.parse(window.lmLS.get(LS_CONFIRMS)||'{}'); return (o&&typeof o==='object')?o:{}; } catch(e){ return {}; } }
  function _cfSave(o){ try { window.lmLS.set(LS_CONFIRMS, JSON.stringify(o)); } catch(e){} }
  function _cfEnabled(key){ var o=_cfLoad(); return o[key]!==false; }
  var LM_CONFIRM_LIST = [
    { key:'place_gift',   ico:'🎁', label:'Оставить подарок' },
    { key:'place_trap',   ico:'🕸', label:'Оставить ловушку' },
    { key:'rob_mine',     ico:'⚔',  label:'Ограбить шахту' },
    { key:'help_mine',    ico:'🤝', label:'Помочь со сбором' },
    { key:'echo_repeat',  ico:'👣', label:'Повторить путь' },
    { key:'raid_soft',    ico:'🏴', label:'Лёгкий рейд' },
    { key:'raid_normal',  ico:'⚔',  label:'Обычный штурм' },
    { key:'raid_full',    ico:'💥', label:'Полное вторжение' },
    { key:'collect_mine', ico:'⛏', label:'Забрать добычу из шахты' },
    { key:'upgrade_mine', ico:'⬆', label:'Улучшить шахту' },
    { key:'buy_attempt',  ico:'👣', label:'Купить дополнительный ход' },
    { key:'boost',        ico:'✨', label:'Активировать бусты' }
  ];
  function _renderSettings(){
    var body = document.getElementById('lm-set-body'); if (!body) return;
    var _fastPref = true; try { _fastPref = window.lmLS.get('lm_map_fast') !== '0'; } catch (e) {}
    var rows = LM_CONFIRM_LIST.map(function(it){
      var on = _cfEnabled(it.key);
      return '<div class="lm-set-row"><span class="lm-set-lbl">'+it.ico+' '+_esc(it.label)+'</span><label class="lm-set-sw"><input type="checkbox" data-cf="'+_esc(it.key)+'"'+(on?' checked':'')+'><span></span></label></div>';
    }).join('');
    var layout = '<div class="lm-set-sec-title">🧭 Расположение кнопок</div>'+
      '<div class="lm-set-hint">По умолчанию кнопки «Вид», «Карта» и таймер — на самой карте. Можно вернуть их как было раньше: кнопки — в шапку блока карты, таймер — в строку статистики.</div>'+
      '<div class="lm-set-row"><span class="lm-set-lbl">🔀 «Вид», «Карта» и таймер — как раньше</span><label class="lm-set-sw"><input type="checkbox" id="lm-set-btnhdr"'+(_lmBtnsInHeader()?' checked':'')+'><span></span></label></div>'+
      '<div class="lm-set-hint">Перенести таймер выброса вниз, на панель карты (рядом с таймером). В строке статистики сверху останется только усталость.</div>'+
      '<div class="lm-set-row"><span class="lm-set-lbl">⚡ Таймер выброса — на карте</span><label class="lm-set-sw"><input type="checkbox" id="lm-set-emonmap"'+(_lmEmOnMap()?' checked':'')+'><span></span></label></div>'+
      '<div class="lm-set-sec-title">🗺 Карта</div>'+
      '<div class="lm-set-hint">Быстрая карта грузится одним лёгким запросом (для слабых телефонов). Если карта отображается неправильно — выключи, тогда грузится полная (медленнее, но сразу со всеми деталями). По умолчанию включена.</div>'+
      '<div class="lm-set-row"><span class="lm-set-lbl">⚡ Быстрая карта</span><label class="lm-set-sw"><input type="checkbox" id="lm-set-fastmap"'+(_fastPref?' checked':'')+'><span></span></label></div>'+
      '<div class="lm-set-hint">Не показывать комнаты ниже (позади) твоей позиции — туда, куда уже не вернёшься.</div>'+
      '<div class="lm-set-row"><span class="lm-set-lbl">🚫 Не показывать комнаты ниже моей позиции</span><label class="lm-set-sw"><input type="checkbox" id="lm-set-cutbelow"'+(_lmCutBelow()?' checked':'')+'><span></span></label></div>';
    body.innerHTML = layout+'<div class="lm-set-sec-title">✅ Окна подтверждения</div><div class="lm-set-hint">Включённое действие сначала спросит подтверждение. Выключи то, где вопрос не нужен — действие выполнится сразу, одним кликом.</div>'+rows;
    var _bh = document.getElementById('lm-set-btnhdr');
    if (_bh) _bh.addEventListener('change', function(){ window.lmLS.set(LS_BTNS_HEADER, this.checked ? '1' : '0'); _lmApplyBtnLayout(); });
    var _eom = document.getElementById('lm-set-emonmap');
    if (_eom) _eom.addEventListener('change', function(){ window.lmLS.set('lm_em_onmap', this.checked ? '1' : '0'); _lmApplyEmOnMap(); });
    var _cb = document.getElementById('lm-set-cutbelow');
    if (_cb) _cb.addEventListener('change', function(){ window.lmLS.set('lm_cut_below', this.checked ? '1' : '0'); _lastCutY = null; invalidateRoomsCache(); try { updateMapInfo(); drawMini(); if (fmOpen) drawFull(); patchSiteMap(); } catch(e) {} });
    var _fm = document.getElementById('lm-set-fastmap');
    if (_fm) _fm.addEventListener('change', function(){ window.lmLS.set('lm_map_fast', this.checked ? '1' : '0'); _fastMapFailures = 0; _fastMapRetryAt = 0; try { lmInfo(this.checked ? 'Быстрая карта включена, обновляю…' : 'Полная карта, обновляю…'); loadCloud(); } catch(e) {} });
    Array.prototype.forEach.call(body.querySelectorAll('input[data-cf]'), function(inp){
      inp.addEventListener('change', function(){ var c=_cfLoad(); c[this.getAttribute('data-cf')]=this.checked; _cfSave(c); });
    });
  }
  function showSettings(){
    var back = document.getElementById('lm-set-back');
    if (!back) {
      back = document.createElement('div'); back.id='lm-set-back';
      back.innerHTML = '<div id="lm-set-box"><div id="lm-set-head"><b>⚙ Настройки скрипта</b><button id="lm-set-close">✕</button></div><div id="lm-set-body"></div></div>';
      document.body.appendChild(back);
      back.addEventListener('click', function(e){ if (e.target===back) back.classList.remove('on'); });
      back.querySelector('#lm-set-close').addEventListener('click', function(){ back.classList.remove('on'); });
    }
    _renderSettings();
    back.classList.add('on');
  }
  window._lmShowSettings = showSettings;
  var _pDownX = 0, _pDownY = 0, _touchMoved = false;
  function _lmPencilAt(cx, cy) {
    if (!Number.isFinite(cx) || !Number.isFinite(cy)) return;
    if (_lmPencil === 'remove') {
      var ri = _routeIdx(cx, cy);
      if (ri >= 0) { _lmRoute = _lmRoute.slice(0, ri); _routeSave(); _routeSyncToServer(); if (fmOpen) drawFull(); patchSiteMap(); if (window._lmPencilCount) window._lmPencilCount(); }
      return;
    }
    if (_lmPencil !== 'add') return;
    if (!_lmRoute.length) {
      var cur = curPos();
      if (!cur || !Number.isFinite(Number(cur.x))) { _lmMsg('Позиция неизвестна', ''); return; }
      if (cx === cur.x && cy === cur.y) return;
      if (!_adjacent({x:cx,y:cy}, {x:Number(cur.x),y:Number(cur.y)})) { _lmMsg('Начните с соседней клетки', 'рядом с вашей позицией'); return; }
      if (cy === Number(cur.y) + 1) { _lmMsg('Вниз ходить нельзя', 'только вверх / влево / вправо'); return; }
    } else {
      var last = _lmRoute[_lmRoute.length - 1];
      if (cx === last.x && cy === last.y) return;
      if (_routeIdx(cx, cy) >= 0) { _lmMsg('Клетка уже в маршруте', ''); return; }
      if (!_adjacent({x:cx,y:cy}, last)) { _lmMsg('Только соседняя клетка', 'вверх / влево / вправо'); return; }
      if (cy === last.y + 1) { _lmMsg('Вниз ходить нельзя', 'только вверх / влево / вправо'); return; }
    }
    _lmRoute.push({ x: cx, y: cy });
    _routeSave(); _routeSyncToServer(); if (fmOpen) drawFull(); patchSiteMap(); if (window._lmPencilCount) window._lmPencilCount();
  }
  function _lmPencilFromPoint(clientX, clientY) {
    if (!elMbody) return;
    var rect = elMbody.getBoundingClientRect();
    var cs = CELL_SIZE * fmScale;
    _lmPencilAt(Math.floor((clientX - rect.left - fmX) / cs), Math.floor((clientY - rect.top - fmY) / cs));
  }
  var _pushPending   = false;
  var _wbActive      = null;
  var elMbody, elTooltip, elInfo, elSt, elFc, elCvs;

  function clr(ev)      { return COLORS[ev]  || COLORS.unknown; }
  function ico(ev)      { return ICONS[ev]   || ''; }
  function iclr(ev)     { return ICOLORS[ev] || 'rgba(255,255,255,.7)'; }
  function roomName(ev) { return NAMES[ev]   || ev; }
  function roomDesc(ev) { return DESCS[ev]   || ''; }
  function mapData()    { return PW.labyrinthData && PW.labyrinthData.mapData; }
  function curPos()     { var d = mapData(); return (d && d.current) || {x:0, y:0}; }

  function formatCoords(x, y) {
    var depth = -y;
    var dir = x < 0 ? 'Западная комната ' + (-x) : x > 0 ? 'Восточная комната ' + x : 'Центральный путь';
    return 'Глубина ' + depth + ' \u2022 ' + dir;
  }

  function parseRoomText(text) {
    var t = text.replace(/[\u2022\u00b7]/g,' ').replace(/\s+/g,' ').trim();
    var dm = t.match(/\d+/g);
    if (!dm || !dm.length) return null;
    var depth = parseInt(dm[0]);
    var y = -depth, x = 0;
    var roomNum = dm[1] ? parseInt(dm[1]) : 1;
    if (t.indexOf('Западная') >= 0 || t.indexOf('западн') >= 0)  x = -roomNum;
    else if (t.indexOf('Восточная') >= 0 || t.indexOf('восточн') >= 0) x = roomNum;
    return {x:x, y:y};
  }

  function invalidateRoomsCache() { roomsCache = null; }

  var _gridCenter = null;

  function lmDiagFn() {
    var mapEl = document.getElementById('labyrinthMap');
    if (!mapEl) { console.error('labyrinthMap not found'); return; }
    var cells = mapEl.querySelectorAll('.labyrinth-cell');
    var cur = curPos();
    console.log('=== LM DIAG ===');
    console.log('Total cells:', cells.length, '| curPos:', JSON.stringify(cur));
    var firstRect = cells[0] ? cells[0].getBoundingClientRect() : null;
    if (firstRect) {
      var rowCount = 0;
      for (var ri = 0; ri < cells.length; ri++) {
        var r = cells[ri].getBoundingClientRect();
        if (Math.abs(r.top - firstRect.top) < 2) rowCount++;
        else break;
      }
      console.log('Grid width:', rowCount);
    }
    for (var i = 0; i < Math.min(3, cells.length); i++) {
      console.log('cell['+i+']: data-x='+cells[i].getAttribute('data-x')+' data-y='+cells[i].getAttribute('data-y'));
    }
    for (var j = 0; j < cells.length; j++) {
      var cl = cells[j];
      var isCur  = cl.classList.contains('labyrinth-cell--current');
      var isAvail= cl.classList.contains('labyrinth-cell--available');
      if (!isCur && !isAvail) continue;
      var tp = isCur ? 'CURRENT' : 'AVAILABLE';
      var rect2 = cl.getBoundingClientRect();
      console.log(tp+' idx='+j+' data-x='+cl.getAttribute('data-x')+' data-y='+cl.getAttribute('data-y'));
    }
    console.log('=== END ===');
  }

  var _debugOverlayOn = false;
  function lmDebugFn() {
    var mapEl = document.getElementById('labyrinthMap');
    if (!mapEl) return;
    _debugOverlayOn = !_debugOverlayOn;
    var cells = mapEl.querySelectorAll('.labyrinth-cell');
    cells.forEach(function(cell, idx){
      var existing = cell.querySelector('.lm-dbg');
      if (existing) existing.parentNode.removeChild(existing);
      if (!_debugOverlayOn) return;
      var dx = cell.getAttribute('data-x') || '?';
      var dy = cell.getAttribute('data-y') || '?';
      var col = idx % 25, row = Math.floor(idx/25);
      var span = document.createElement('span');
      span.className = 'lm-dbg';
      span.style.cssText = 'position:absolute;top:0;left:0;font-size:4px;color:rgba(255,255,0,.8);line-height:1;z-index:99;pointer-events:none;background:rgba(0,0,0,.5);padding:1px;';
      span.textContent = dx+','+dy+'\n'+col+','+row;
      cell.style.position = 'relative';
      cell.appendChild(span);
    });
  }

  window.lmDiag  = lmDiagFn;
  window.lmDebug = lmDebugFn;

  function detectGridCenter(cells) {
    for (var i = 0; i < cells.length; i++) {
      if (!cells[i].classList.contains('labyrinth-cell--current')) continue;
      var col = i % 25;
      var row = Math.floor(i / 25);
      return { gx: col, gy: row, useIndex: true };
    }
    return null;
  }

  function applyCleanView() {
    var mapEl = document.getElementById('labyrinthMap');
    if (!mapEl) return;
    var cells = mapEl.querySelectorAll('.labyrinth-cell');
    cells.forEach(function(cell) {
      cell.classList.remove('lm-known');
      if (!cell.classList.contains('labyrinth-cell--visited')) {
        cell.removeAttribute('data-event');
      }
      var icon = cell.querySelector('.lm-cell-icon');
      if (icon) icon.parentNode.removeChild(icon);
      cell.classList.remove('lm-emission-cell');
      var emi = cell.querySelector('.lm-emission-ico');
      if (emi) emi.parentNode.removeChild(emi);
      cell.classList.remove('lm-route-cell');
      var rar = cell.querySelector('.lm-route-tunnel');
      if (rar) rar.parentNode.removeChild(rar);
    });
  }

  function removeCleanView() { patchSiteMapDelayed(); if (window._lmApplyEmissionCells) window._lmApplyEmissionCells(); }

  function patchSiteMap() {
    _routeTrimToCurrent();
    if (cleanViewMode) return;
    if (_lmIsBlocked()) { try { applyCleanView(); } catch(e) {} return; }
    var mapEl = document.getElementById('labyrinthMap');
    if (!mapEl) return;
    var d = mapData();
    if (!d || !d.current) return;
    var cur = d.current;
    var rooms = allRooms();
    var cells = mapEl.querySelectorAll('.labyrinth-cell');
    if (!cells.length) return;
    var center = detectGridCenter(cells);
    if (!center) return;
    _lmPatching = true;
    var cellsArr = Array.prototype.slice.call(cells);
    cellsArr.forEach(function(cell, idx) {
      var gx, gy;
      if (center.useIndex) { gx = idx % 25; gy = Math.floor(idx / 25); }
      else {
        var dxA = cell.getAttribute('data-x');
        var dyA = cell.getAttribute('data-y');
        gx = parseInt(dxA || '', 10); gy = parseInt(dyA || '', 10);
        if (isNaN(gx) || isNaN(gy)) { gx = idx % 25; gy = Math.floor(idx / 25); }
      }
      var lx = cur.x + (gx - center.gx);
      var ly = cur.y + (gy - center.gy);
      var _rIdx = _routeIdx(lx, ly);
      if (_rIdx >= 0) {
        cell.classList.add('lm-route-cell');
        var _dirs = {}, _nb = [];
        if (_rIdx > 0) _nb.push(_lmRoute[_rIdx - 1]);
        else if (cur && Number.isFinite(Number(cur.x)) && _adjacent({x:lx,y:ly}, {x:Number(cur.x),y:Number(cur.y)})) _nb.push({x:Number(cur.x),y:Number(cur.y)});
        if (_lmRoute[_rIdx + 1]) _nb.push(_lmRoute[_rIdx + 1]);
        _nb.forEach(function(n){ if (n.y < ly) _dirs.up=1; else if (n.y > ly) _dirs.down=1; else if (n.x > lx) _dirs.right=1; else if (n.x < lx) _dirs.left=1; });
        var _tun = cell.querySelector('.lm-route-tunnel');
        if (!_tun) { _tun = document.createElement('span'); _tun.className = 'lm-route-tunnel'; cell.appendChild(_tun); }
        var _h = '';
        ['up','down','left','right'].forEach(function(dd){ if (_dirs[dd]) _h += '<i class="lm-tb lm-tb-'+dd+'"></i>'; });
        _h += '<i class="lm-tb-c'+(_lmRoute[_rIdx+1]?'':' end')+'"></i>';
        _tun.innerHTML = _h;
      } else {
        cell.classList.remove('lm-route-cell');
        var _ot = cell.querySelector('.lm-route-tunnel');
        if (_ot) _ot.parentNode.removeChild(_ot);
      }
      var _pc = _playerPos[lx + '_' + ly];
      var _pm = cell.querySelector('.lm-player-mark');
      if (_pc && !cell.classList.contains('labyrinth-cell--current')) {
        if (!_pm) {
          _pm = document.createElement('span'); _pm.className = 'lm-player-mark';
          _pm.style.cssText = 'position:absolute;bottom:1px;right:1px;min-width:13px;height:13px;padding:0 2px;border-radius:7px;background:#22c55e;color:#04120a;font:800 9px/13px system-ui;text-align:center;z-index:9;box-shadow:0 0 5px rgba(34,197,94,.95);pointer-events:none';
          cell.appendChild(_pm);
        }
        _pm.textContent = _pc > 1 ? String(_pc) : '•';
      } else if (_pm) { _pm.parentNode.removeChild(_pm); }
      if (cell.classList.contains('labyrinth-cell--current')) return;
      if (cell.classList.contains('labyrinth-cell--visited')) {
        cell.classList.remove('lm-known');
        var oldVisIcon = cell.querySelector('.lm-cell-icon');
        if (oldVisIcon) oldVisIcon.parentNode.removeChild(oldVisIcon);
        return;
      }
      var key = lx + '_' + ly;
      var room = rooms[key];
      if (!room || room.event === 'unknown') {
        cell.classList.remove('lm-known');
        cell.removeAttribute('data-event');
        var oldIcon = cell.querySelector('.lm-cell-icon');
        if (oldIcon) oldIcon.parentNode.removeChild(oldIcon);
        return;
      }
      var evName = room.event;
      var iconChar = ico(evName) || '';
      var iconColor = iclr(evName) || 'rgba(255,255,255,.7)';
      cell.classList.add('lm-known');
      cell.setAttribute('data-event', evName);
      var iconSpan = cell.querySelector('.lm-cell-icon');
      if (!iconSpan) {
        iconSpan = document.createElement('span');
        iconSpan.className = 'lm-cell-icon';
        cell.appendChild(iconSpan);
      }
      iconSpan.textContent = iconChar;
      iconSpan.style.color = iconColor;
    });
    _lmPatching = false;
  }

  function patchSiteMapDelayed() {
    var tries = 0;
    function attempt() {
      var mapEl = document.getElementById('labyrinthMap');
      var cells = mapEl && mapEl.querySelectorAll('.labyrinth-cell');
      var hasCurrent = false;
      if (cells && cells.length > 0) {
        for (var i = 0; i < cells.length; i++) {
          if (cells[i].classList.contains('labyrinth-cell--current')) { hasCurrent = true; break; }
        }
      }
      if (hasCurrent) { patchSiteMap(); }
      else if (tries++ < 30) { setTimeout(attempt, 300); }
    }
    attempt();
  }

  function updateMapInfo(cachedRooms) {
    var rooms = cachedRooms || allRooms();
    var keys = Object.keys(rooms);
    var total = keys.length;
    var maxDepth = 0;
    keys.forEach(function(k){
      var p = k.split('_');
      var y = Math.abs(+p[1]||0);
      if (y > maxDepth) maxDepth = y;
    });
    var el = function(id){ return document.getElementById(id); };
    if (el('lm-mi-rooms-val'))   el('lm-mi-rooms-val').textContent   = total;
    if (el('lm-mi-depth-val'))   el('lm-mi-depth-val').textContent   = maxDepth;
    var st = el('lm-st');
    if (st) st.textContent = 'База: '+_cloudMapCount+' | Всего: '+total;
    updateEmissionInfo();
  }

  var _emissionTimer = null;
  var _emissionCheckAt = 0;
  var _emissionCheckSnap = 0;
  var _emActiveEndAt = 0;
  var _emActiveSnap = 0;

  function _startEmissionTimer() {
    if (_emissionTimer) return;
    _emissionTimer = setInterval(function() { updateEmissionInfo(); _lmSyncArenaTimer(); _emMirrorToArena(); }, 1000);
  }
  function _stopEmissionTimer() {
    if (_emissionTimer) { clearInterval(_emissionTimer); _emissionTimer = null; }
  }

  var _emPhaseShown = null;
  var _emPhaseDismissed = null;
  function _emShowBanner(kind) {
    if (_emPhaseShown === kind || _emPhaseDismissed === kind) return;
    var arena = document.querySelector('.labyrinth__arena'); if (!arena) return;
    _emPhaseShown = kind;
    var ov = document.getElementById('lm-em-refresh');
    if (!ov) {
      ov = document.createElement('div'); ov.id = 'lm-em-refresh';
      ov.innerHTML = '<div class="lm-emr-box"><div class="lm-emr-ico">⚡</div>'+
        '<div class="lm-emr-title" id="lm-emr-title"></div>'+
        '<div class="lm-emr-sub">Время у игры примерное и может сдвигаться на пару минут. Обновите страницу, чтобы проверить актуальное состояние.</div>'+
        '<div class="lm-emr-btns"><button class="lm-emr-go" id="lm-emr-go">🔄 Обновить</button><button class="lm-emr-x" id="lm-emr-x">Позже</button></div></div>';
      arena.appendChild(ov);
      ov.querySelector('#lm-emr-go').addEventListener('click', function(){ try { location.reload(); } catch(e) {} });
      ov.querySelector('#lm-emr-x').addEventListener('click', function(){ _emPhaseDismissed = _emPhaseShown; var o=document.getElementById('lm-em-refresh'); if(o) o.style.display='none'; _emPhaseShown=null; });
    }
    var ttl = ov.querySelector('#lm-emr-title');
    if (ttl) ttl.textContent = (kind === 'start') ? 'Выброс вот-вот начнётся' : 'Выброс заканчивается';
    ov.style.display = 'flex';
  }
  function _emClearBanner() {
    _emPhaseShown = null; _emPhaseDismissed = null;
    var ov = document.getElementById('lm-em-refresh'); if (ov) ov.style.display = 'none';
  }

  function updateEmissionInfo() {
    var valEl = document.getElementById('lm-mi-emission-val');
    var lblEl = document.getElementById('lm-mi-emission-lbl');
    var icoEl = document.getElementById('lm-mi-emission-ico');
    var card  = document.getElementById('lm-mi-emission-card');
    if (!valEl) return;

    var ld = PW.labyrinthData || {};
    var em = ld.emission;

    if (!em || !em.last_start_at) {
      _stopEmissionTimer();
      _emClearBanner();
      valEl.textContent = '—';
      if (lblEl) lblEl.textContent = 'выброс';
      if (icoEl) icoEl.textContent = '🌩';
      if (card)  card.classList.remove('is-active');
      _emMirrorToArena();
      return;
    }

    var now = Math.floor(Date.now() / 1000);

    if (em.active) {
      var sl = Number(em.seconds_left);
      if (!Number.isFinite(sl) || sl <= 0) {
        sl = (em.active_until ? em.active_until : (em.last_start_at + EMISSION_DURATION_SEC)) - now;
      }
      if (!_emActiveEndAt || Math.abs(sl - _emActiveSnap) > 5) { _emActiveEndAt = now + sl; _emActiveSnap = sl; }
      var left = Math.round(_emActiveEndAt - now);
      _startEmissionTimer();
      try { window.lmLS.remove('lm_em_expect'); } catch (e) {}
      if (icoEl) icoEl.textContent = '⚡';
      if (card)  card.classList.add('is-active');
      if (left > 0) {
        _emClearBanner();
        var hh = Math.floor(left / 3600);
        var mm = Math.floor((left % 3600) / 60), ss = left % 60;
        valEl.textContent = hh > 0
          ? hh + ':' + (mm < 10 ? '0' : '') + mm + ':' + (ss < 10 ? '0' : '') + ss
          : mm + ':' + (ss < 10 ? '0' : '') + ss;
        if (lblEl) lblEl.textContent = 'до конца выброса';
      } else {
        valEl.textContent = 'скоро конец';
        if (lblEl) lblEl.textContent = 'выброс идёт';
        _emShowBanner('end');
      }
      _emMirrorToArena();
    } else {
      if (card)  card.classList.remove('is-active');
      if (icoEl) icoEl.textContent = '🌩';
      _emActiveEndAt = 0; _emActiveSnap = 0;

      var nextCheck = (Number(em.next_check_left) > 0 ? Number(em.next_check_left) : 0) || (Number(em.cooldown_left) > 0 ? Number(em.cooldown_left) : 0);
      var lastEnd   = em.last_end_at || 0;
      var lastStart = em.last_start_at || 0;

      if (nextCheck > 0) {

        if (!_emissionCheckAt || Math.abs(nextCheck - _emissionCheckSnap) > 10) {
          _emissionCheckAt  = now + nextCheck;
          _emissionCheckSnap = nextCheck;
          try { window.lmLS.set('lm_em_expect', String(_emissionCheckAt)); } catch (e) {}
        }
        var checkLeft = _emissionCheckAt - now;
        if (checkLeft > 0) {
          _emClearBanner();
          _startEmissionTimer();
          var chh = Math.floor(checkLeft / 3600);
          var cmm = Math.floor((checkLeft % 3600) / 60);
          var css = checkLeft % 60;
          valEl.textContent = chh > 0
            ? chh + ':' + (cmm < 10 ? '0' : '') + cmm + ':' + (css < 10 ? '0' : '') + css
            : cmm + ':' + (css < 10 ? '0' : '') + css;
          if (lblEl) lblEl.textContent = 'до выброса';
        } else {
          if (card) card.classList.remove('is-active');
          if (icoEl) icoEl.textContent = '🌩';
          _stopEmissionTimer();
          valEl.textContent = 'уточнение';
          if (lblEl) lblEl.textContent = 'ожидаем данные сервера';
          _emShowBanner('start');
        }
      } else {
        _emissionCheckAt  = 0;
        _emissionCheckSnap = 0;
        _emClearBanner();
        var _exp = 0; try { _exp = parseInt(window.lmLS.get('lm_em_expect') || '0', 10); } catch (e) {}
        if (_exp && now >= _exp - 120 && now - _exp < 2700) {
          _startEmissionTimer();
          valEl.textContent = 'вот-вот';
          if (lblEl) lblEl.textContent = 'ожидаем выброс';
          _emMirrorToArena();
          return;
        }
        if (_exp && now - _exp >= 2700) { try { window.lmLS.remove('lm_em_expect'); } catch (e) {} }
        _stopEmissionTimer();
        if (lastEnd > 0) {
          var endHH = new Date(lastEnd * 1000).getHours();
          var endMM = new Date(lastEnd * 1000).getMinutes();
          valEl.textContent = (endHH < 10 ? '0' : '') + endHH + ':' + (endMM < 10 ? '0' : '') + endMM;
          if (lblEl) {
            var dur = lastEnd - lastStart;
            lblEl.textContent = 'последний (' + Math.round(dur/60) + ' мин)';
          }
        } else {
          valEl.textContent = '?';
          if (lblEl) lblEl.textContent = 'нет данных';
        }
      }
    }
    _emMirrorToArena();
  }

  var VARIABLE_EVENTS = {
    room_trap:true, room_gift:true, reward:true, reward_card:true,
    locked_chest:true, locked_chest_result:true, luck_altar:true, luck_altar_result:true,
    card_trader:true, card_trader_result:true,
    mimic_chest:true, mimic_chest_hit:true, mimic_chest_killed:true,
    mimic_chest_escape:true, mimic_chest_reward:true, mimic_chest_back:true,
    personal_mine:true, personal_mine_created:true, personal_mine_collect:true,
    foreign_mine:true, echo_room:true, echo_room_result:true, fate_room:true, fate_room_result:true
  };

  function allRooms() {
    var _cutY = null;
    if (_lmCutBelow()) {
      var _c = mapData(); _cutY = (_c && _c.current) ? Number(_c.current.y) : null;
      if (_cutY !== _lastCutY) { roomsCache = null; _lastCutY = _cutY; }
    } else if (_lastCutY !== null) { roomsCache = null; _lastCutY = null; }
    if (roomsCache) return roomsCache;
    var r = {}, k;
    for (k in cloudMap) {
      var room = cloudMap[k], ev = room.event;
      var roomObj = room.room_object || null;
      if (ev === 'room_trap' || ev === 'room_gift') { roomObj = ev; ev = room.base_type || ev; }
      else if (LM_RESULT_EV[ev] && room.base_type && room.base_type !== 'unknown') { ev = room.base_type; }
      var altTypes = (room.alt_types || []).filter(function(t){
        return t !== 'room_trap' && t !== 'room_gift' && t !== 'room_player' && t !== 'shield_block';
      });
      if (ev === 'unknown' && altTypes.length > 0) { ev = altTypes[0]; altTypes = altTypes.slice(1); }
      var rDat = room.dat || {};
      r[k] = {event:ev, guardian:room.guardian||null, altTypes:altTypes, roomObject:roomObj,
              emissionVisited:!!(rDat.emission_visited), emissionEv:rDat.emission_ev||null};
    }
    var d = mapData();
    if (d && d.steps) {
      for (var i = 0; i < d.steps.length; i++) {
        var s = d.steps[i], key = s.x+'_'+s.y;
        var isRoomEv = (s.event==='room_trap'||s.event==='room_gift');
        if (!r[key]) {
          r[key] = { event: s.event, guardian: null, visits: 1, altTypes: [], roomObject: isRoomEv ? s.event : null };
        } else {
          var cur = r[key];
          if (isRoomEv) { cur.roomObject = s.event; }
          else if (cur.event === 'unknown' && s.event && s.event !== 'unknown') { cur.event = s.event; }
          else if (cur.event !== s.event && s.event && s.event !== 'unknown') {
            if (VARIABLE_EVENTS[cur.event] || VARIABLE_EVENTS[s.event]) {
              if (cur.altTypes.indexOf(s.event) === -1) cur.altTypes.push(s.event);
            }
          }
        }
      }
    }
    if (_cutY !== null && Number.isFinite(_cutY)) {
      for (k in r) { if ((+k.split('_')[1]) > _cutY) delete r[k]; }
    }
    roomsCache = r;
    return r;
  }

  var MAP_DB_NAME='animesss-labyrinth-map-v2';
  try { indexedDB.deleteDatabase('animesss-labyrinth-map'); } catch(e){}
  var MAP_DB_VERSION=1;
  function openMapDb(){
    return new Promise(function(resolve,reject){
      if(!window.indexedDB){reject(new Error('indexeddb_unavailable'));return;}
      var request=indexedDB.open(MAP_DB_NAME,MAP_DB_VERSION);
      request.onupgradeneeded=function(){
        var db=request.result;
        if(!db.objectStoreNames.contains('rooms'))db.createObjectStore('rooms',{keyPath:'key'});
        if(!db.objectStoreNames.contains('meta'))db.createObjectStore('meta',{keyPath:'name'});
      };
      request.onsuccess=function(){resolve(request.result);};
      request.onerror=function(){reject(request.error);};
    });
  }
  function idbGetAllRooms(){return openMapDb().then(function(db){return new Promise(function(resolve,reject){var tx=db.transaction('rooms','readonly'),r=tx.objectStore('rooms').getAll();r.onsuccess=function(){resolve(r.result||[])};r.onerror=function(){reject(r.error)}})});}
  function idbCountRooms(){return openMapDb().then(function(db){return new Promise(function(resolve,reject){var tx=db.transaction('rooms','readonly'),r=tx.objectStore('rooms').count();r.onsuccess=function(){resolve(Number(r.result||0))};r.onerror=function(){reject(r.error)}})});}
  function idbGetMeta(name){return openMapDb().then(function(db){return new Promise(function(resolve,reject){var r=db.transaction('meta','readonly').objectStore('meta').get(name);r.onsuccess=function(){resolve(r.result?r.result.value:null)};r.onerror=function(){reject(r.error)}})});}
  function idbPutMeta(name,value){return openMapDb().then(function(db){return new Promise(function(resolve,reject){var tx=db.transaction('meta','readwrite');tx.objectStore('meta').put({name:name,value:value});tx.oncomplete=function(){resolve()};tx.onerror=function(){reject(tx.error)}})});}
  function idbReplaceRooms(rooms,version){
    rooms=Array.isArray(rooms)?rooms:[];
    return openMapDb().then(function(db){
      return new Promise(function(resolve,reject){
        var clearTx=db.transaction('rooms','readwrite');
        clearTx.objectStore('rooms').clear();
        clearTx.oncomplete=function(){resolve(db);};
        clearTx.onerror=function(){reject(clearTx.error);};
      });
    }).then(function(db){
      var index=0;
      var batchSize=1500;
      function writeNext(){
        if(index>=rooms.length){
          return new Promise(function(resolve,reject){
            var tx=db.transaction('meta','readwrite');
            var meta=tx.objectStore('meta');
            meta.put({name:'map_version',value:Number(version||0)});
            meta.put({name:'map_complete',value:true});
            meta.put({name:'room_count',value:rooms.length});
            tx.oncomplete=function(){resolve();};
            tx.onerror=function(){reject(tx.error);};
          });
        }
        var end=Math.min(index+batchSize,rooms.length);
        return new Promise(function(resolve,reject){
          var tx=db.transaction('rooms','readwrite');
          var store=tx.objectStore('rooms');
          for(var i=index;i<end;i++){
            var room=rooms[i];
            if(room&&room.x!=null&&room.y!=null){
              store.put(Object.assign({key:room.x+'_'+room.y},room));
            }
          }
          tx.oncomplete=function(){index=end;resolve();};
          tx.onerror=function(){reject(tx.error);};
        }).then(writeNext);
      }
      return writeNext();
    });
  }
  function idbApplyChanges(changes,version){return openMapDb().then(function(db){return new Promise(function(resolve,reject){var tx=db.transaction(['rooms','meta'],'readwrite'),store=tx.objectStore('rooms');(changes||[]).forEach(function(room){if(room&&room.x!=null&&room.y!=null)store.put(Object.assign({key:room.x+'_'+room.y},room));});var meta=tx.objectStore('meta');meta.put({name:'map_version',value:Number(version||0)});meta.put({name:'map_complete',value:true});tx.oncomplete=function(){resolve()};tx.onerror=function(){reject(tx.error)}})});}
  function roomsArrayToCloud(rows){var converted={};(rows||[]).forEach(function(room){if(room&&room.x!=null&&room.y!=null)converted[room.x+'_'+room.y]={event:room.ev||room.event||'unknown',base_type:room.base_type||null,current_event:room.current_event||null,guardian:room.guardian||null,alt_types:room.alt_types||[],room_object:room.current_event||room.room_object||null,first_user:room.first_user||null,first_at:room.first_at||null,last_user:room.last_user||null,last_at:room.last_at||null,visits:room.visits||0,dat:room.dat||null,state_version:room.state_version||0};});return converted;}

  function idbStreamRooms(onBatch, onDone, onError) {
    openMapDb().then(function(db) {
      var tx = db.transaction('rooms', 'readonly');
      var store = tx.objectStore('rooms');
      if (typeof store.getAll === 'function') {
        var rq = store.getAll();
        rq.onsuccess = function() {
          var rows = rq.result || [];
          if (rows.length) onBatch(rows);
          if (onDone) onDone();
        };
        rq.onerror = function() { if (onError) onError(rq.error); };
        return;
      }
      var batch = [];
      var BATCH = 3000;
      var req = store.openCursor();
      req.onsuccess = function(e) {
        var cursor = e.target.result;
        if (cursor) {
          batch.push(cursor.value);
          if (batch.length >= BATCH) {
            onBatch(batch.splice(0, BATCH));
          }
          cursor.continue();
        } else {
          if (batch.length) onBatch(batch);
          if (onDone) onDone();
        }
      };
      req.onerror = function() { if (onError) onError(req.error); };
    }).catch(function(e) { if (onError) onError(e); });
  }

  function loadFromCache() {
    if (_lmIsBlocked()) { cloudMap = {}; _cloudMapCount = 0; invalidateRoomsCache(); return; }
    var rendered = false;
    var _bt0 = (window.performance && performance.now) ? performance.now() : Date.now();
    function _btDone(src) {
      try {
        var _bt1 = (window.performance && performance.now) ? performance.now() : Date.now();
        window._lmBootMap = { ms: Math.round(_bt1 - _bt0), rooms: _cloudMapCount, src: src };
      } catch (e) {}
    }
    idbStreamRooms(
      function(batch) {
        if (!cloudMap) cloudMap = {};
        batch.forEach(function(room) {
          if (!room || room.x == null || room.y == null) return;
          if (Math.abs(room.y) > 8000 || Math.abs(room.x) > 8000) return;
          cloudMap[room.x + '_' + room.y] = {
            event: room.ev || room.event || 'unknown',
            base_type: room.base_type || null,
            current_event: room.current_event || null,
            guardian: room.guardian || null,
            alt_types: room.alt_types || [],
            room_object: room.current_event || room.room_object || null,
            first_user: room.first_user || null,
            first_at: room.first_at || null,
            last_user: room.last_user || null,
            last_at: room.last_at || null,
            visits: room.visits || 0,
            dat: room.dat || null,
            state_version: room.state_version || 0
          };
        });
        rendered = true;
      },
      function() {
        if (rendered) {
          _cloudMapCount = Object.keys(cloudMap).length;
          invalidateRoomsCache();
          drawMini();
          if (fmOpen) drawFull();
          _btDone('idb');
        }
        if (!rendered) {
          try {
            var raw = window.lmLS.get(LS_KEY);
            if (raw) {
              var parsed = JSON.parse(raw);
              Object.keys(parsed).forEach(function(k) {
                var p = k.split('_');
                if (Math.abs(+p[0]) > 8000 || Math.abs(+p[1]) > 8000) delete parsed[k];
              });
              cloudMap = parsed;
              _cloudMapCount = Object.keys(parsed).length;
              invalidateRoomsCache();
              var migrate = Object.keys(parsed).map(function(key) {
                var p = key.split('_');
                return Object.assign({x: Number(p[0]), y: Number(p[1]), ev: parsed[key].event}, parsed[key]);
              });
              idbReplaceRooms(migrate, 0).catch(function() {});
              drawMini();
              if (fmOpen) drawFull();
            }
          } catch(e) {}
          _btDone('ls');
        } else {
          patchSiteMapDelayed();
        }
      },
      function() {
        try {
          var raw = window.lmLS.get(LS_KEY);
          if (raw) {
            var _p = JSON.parse(raw);
            Object.keys(_p).forEach(function(k){ var _c=k.split('_'); if(Math.abs(+_c[0])>8000||Math.abs(+_c[1])>8000) delete _p[k]; });
            cloudMap = _p; _cloudMapCount = Object.keys(_p).length; invalidateRoomsCache(); drawMini();
          }
        } catch(e) {}
      }
    );
    return true;
  }

  function fetchFullMapPaged(request, onProgress){
    var byKey=Object.create(null);
    var ordered=[];
    var version=0;
    var cursor=null;
    var lastCursorKey='';
    var page=0;
    var pageSize=2000;
    var maxPages=100;

    function addRooms(rows){
      (rows||[]).forEach(function(room){
        if(!room||room.x==null||room.y==null)return;
        var key=room.x+'_'+room.y;
        if(!byKey[key])ordered.push(key);
        byKey[key]=room;
      });
    }

    function next(){
      if(page>=maxPages)throw new Error('map_pages_limit');
      var url=VPS_URL+'/map/full?limit='+pageSize;
      if(cursor)url+='&after_y='+encodeURIComponent(cursor.y)+'&after_x='+encodeURIComponent(cursor.x);
      return request(url).then(function(d){
        page++;
        if(d&&d.blocked)return {rooms:[],version:0,pages:page,blocked:true};
        version=Number(d&&d.version||version||0);
        var rows=Array.isArray(d&&d.rooms)?d.rooms:[];
        addRooms(rows);

        if(onProgress) onProgress(ordered.length, page);

        var nextCursor=d&&d.next_cursor;
        if(!nextCursor&&rows.length>=pageSize){
          var last=rows[rows.length-1];
          if(last&&last.x!=null&&last.y!=null)nextCursor={y:last.y,x:last.x};
        }

        var shouldContinue=Boolean((d&&d.has_more)||rows.length>=pageSize);
        if(!shouldContinue||!nextCursor){
          return {rooms:ordered.map(function(k){return byKey[k];}),version:version,pages:page};
        }

        var cursorKey=String(nextCursor.y)+'_'+String(nextCursor.x);
        if(cursorKey===lastCursorKey)throw new Error('map_cursor_stuck:'+cursorKey);
        lastCursorKey=cursorKey;
        cursor={y:Number(nextCursor.y),x:Number(nextCursor.x)};
        return next();
      });
    }

    return next();
  }

  var _cloudLoading = false;
  function clearBlockedStateFromServer() {
    if (_lmIsBlocked()) _lmSetBlocked(false);
  }
  function enforceBlockedState() {
    _lmSetBlocked(true);
    cloudMap = {}; _cloudMapCount = 0; invalidateRoomsCache();
    try { idbReplaceRooms([], 0); } catch (e) {}
    try { applyCleanView(); drawMini(); } catch (e2) {}
  }
  function apiError(response) {
    return response.json().catch(function(){ return {}; }).then(function(body) {
      var code = body.code || body.error || ('http_' + response.status);
      var err = new Error(String(code)); err.status = response.status; err.body = body; throw err;
    });
  }
  function parseFastMap(ab) {
    if (!(ab instanceof ArrayBuffer) || ab.byteLength < FAST_HEADER_SIZE) throw new Error('fast_truncated_header');
    var dv = new DataView(ab);
    if (dv.getUint32(0, false) !== 0x4C4D4632) throw new Error('fast_bad_magic');
    var headerSize = dv.getUint16(4, true), recordSize = dv.getUint16(6, true);
    var N = dv.getUint32(8, true), typeHash = dv.getUint32(16, true);
    if (headerSize !== FAST_HEADER_SIZE || recordSize !== FAST_RECORD_SIZE) throw new Error('fast_schema_mismatch');
    if (typeHash !== FAST_TYPE_HASH) throw new Error('fast_type_table_mismatch');
    if (N > FAST_MAX_ROOMS) throw new Error('fast_room_limit');
    if (ab.byteLength !== headerSize + N * recordSize) throw new Error('fast_length_mismatch');
    var xs = new Int16Array(ab, headerSize, N), ys = new Int16Array(ab, headerSize + 2 * N, N);
    var ts = new Uint8Array(ab, headerSize + 4 * N, N), fs = new Uint8Array(ab, headerSize + 5 * N, N);
    var cm = {}, i, t, f, g;
    for (i = 0; i < N; i++) {
      t = FAST_TYPES[ts[i]];
      if (!t) t = 'unknown';
      f = fs[i]; g = f & 3;
      cm[xs[i] + '_' + ys[i]] = { event:t, base_type:(t === 'unknown' ? null : t), current_event:null,
        guardian:g === 1 ? 'guardian_user' : g === 2 ? 'guardian_club' : g === 3 ? 'can_capture' : null,
        alt_types:[], room_object:(f & 4) ? 'room_gift' : ((f & 8) ? 'room_trap' : null),
        dat:(f & 16) ? { emission_visited:true, fast_overview:true } : { fast_overview:true } };
    }
    return { rooms:cm, count:N, mapVersion:dv.getUint32(12, true) };
  }
  var _roomDetailCache = new Map();
  var _roomDetailPending = new Map();
  var ROOM_DETAIL_TTL = 5 * 60 * 1000;
  var ROOM_DETAIL_MAX = 200;
  function loadRoomDetail(x, y) {
    var key = x + '_' + y, cached = _roomDetailCache.get(key);
    if (cached && Date.now() - cached.at < ROOM_DETAIL_TTL) return Promise.resolve(cached.data);
    if (_roomDetailPending.has(key)) return _roomDetailPending.get(key);
    var pending = window.lmFetch(VPS_URL + '/room/' + encodeURIComponent(x) + '/' + encodeURIComponent(y), {
      cache:'no-store', headers:vpsAuthHeaders(false)
    }).then(function(r){
      if (r.status === 404) return { found:false };
      if (!r.ok) return apiError(r);
      return r.json();
    }).then(function(d){
      if (!d || !d.found) return d;
      var current = cloudMap[key] || {};
      cloudMap[key] = Object.assign({}, current, {
        event:d.base_type || d.ev || current.event || 'unknown', base_type:d.base_type || null,
        current_event:d.current_event || null, guardian:d.guardian || null,
        alt_types:Array.isArray(d.alt_types) ? d.alt_types : [], room_object:d.room_object || null,
        visits:Number(d.visits || 0), unique_users:Number(d.unique_users || 0), state_version:Number(d.state_version || 0),
        dat:{ emission_visited:!!d.emission_visited, fast_overview:false, detail_loaded_at:Date.now() }
      });
      _roomDetailCache.delete(key); _roomDetailCache.set(key, { at:Date.now(), data:d });
      while (_roomDetailCache.size > ROOM_DETAIL_MAX) _roomDetailCache.delete(_roomDetailCache.keys().next().value);
      invalidateRoomsCache();
      return d;
    }).catch(function(e){
      if (e && e.status === 403 && e.body && e.body.code === 'blocked') enforceBlockedState();
      throw e;
    }).then(function(result){ _roomDetailPending.delete(key); return result; }, function(error){ _roomDetailPending.delete(key); throw error; });
    _roomDetailPending.set(key, pending);
    return pending;
  }
  function loadCloudFast(onDone) {
    if (_cloudLoading) { if (onDone) onDone(); return; }
    _cloudLoading = true;
    function finish() { _cloudLoading = false; if (onDone) onDone(); }
    var opts = { cache:'no-store', headers:vpsAuthHeaders(false) };
    window.lmFetch(VPS_URL + '/map/version', opts)
      .then(function(r){ if (!r.ok) return apiError(r); return r.json(); })
      .then(function(meta) {
        if (meta.blocked) { enforceBlockedState(); var e = new Error('blocked'); e.blocked = true; throw e; }
        if (meta.need_auth || meta.forbidden || meta.outdated) throw new Error(meta.outdated ? 'client_outdated' : 'invalid_auth');
        clearBlockedStateFromServer();
        return window.lmFetch(VPS_URL + '/map/fast', { cache:'no-cache', headers:vpsAuthHeaders(false), lmResponseType:'arraybuffer' });
      })
      .then(function(r) {
        if (!r.ok) return apiError(r);
        var ct = String(r.headers && r.headers.get ? (r.headers.get('content-type') || '') : '').toLowerCase();
        if (ct.indexOf('application/vnd.labyrinth.map+binary') < 0 && ct.indexOf('application/octet-stream') < 0)
          throw new Error('fast_bad_content_type');
        return r.arrayBuffer();
      })
      .then(function(ab) {
        var parsed = parseFastMap(ab);
        cloudMap = parsed.rooms; _cloudMapCount = parsed.count; invalidateRoomsCache();
        _fastMapFailures = 0; _fastMapRetryAt = 0;
        try { updateMapInfo(); drawMini(); if (fmOpen) drawFull(); patchSiteMapDelayed(); } catch (e) {}
        lmInfo('Быстрая карта: ' + parsed.count + ' комнат');
        finish();
      })
      .catch(function(e) {
        if (e && e.blocked) { finish(); return; }
        if (e && e.status === 403 && e.body && (e.body.code === 'blocked' || e.body.error === 'blocked')) {
          enforceBlockedState(); finish(); return;
        }
        if (e && (e.status === 401 || e.status === 403 || e.message === 'invalid_auth' || e.message === 'client_outdated')) {
          lmWarn(e.message === 'client_outdated' ? 'Потрібне оновлення скрипта' : 'Потрібна повторна авторизація');
          finish(); return;
        }
        _fastMapFailures++;
        _fastMapRetryAt = Date.now() + Math.min(60000, 1000 * Math.pow(2, Math.min(_fastMapFailures, 6)));
        _cloudLoading = false;
        lmWarn('Быстрая карта недоступна, обычная загрузка');
        loadCloud(onDone);
      });
  }
  function loadCloud(onDone, onProgress) {
    if (_lmFastMap()) { return loadCloudFast(onDone); }
    if(_cloudLoading){if(onDone)onDone();return;}
    _cloudLoading=true;

    function finish(){_cloudLoading=false;if(onDone)onDone();}
    function request(url){return window.lmFetch(url,{cache:'no-store',headers:vpsAuthHeaders(false)}).then(function(r){if(!r.ok)throw new Error('HTTP '+r.status);return r.json();});}
    function rebuildFull(){
      return fetchFullMapPaged(request, onProgress).then(function(d){
        if(d.blocked)return idbReplaceRooms([],0).then(function(){return {mode:'blocked',version:0,count:0,pages:d.pages};});
        if(!d.rooms||d.rooms.length<2)throw new Error('full_map_too_small:'+((d.rooms&&d.rooms.length)||0));
        return idbReplaceRooms(d.rooms,d.version).then(function(){return {mode:'full',version:d.version,count:d.rooms.length,pages:d.pages};});
      });
    }

    function rebuildFromD1(){
      return Promise.reject(new Error('cross_environment_fallback_disabled'));
    }

    Promise.all([
      idbGetMeta('map_version').catch(function(){return 0;}),
      idbGetMeta('map_complete').catch(function(){return false;}),
      idbCountRooms().catch(function(){return 0;}),
      request(VPS_URL+'/map/version')
    ]).then(function(values){
      var localVersion=Number(values[0]||0);
      var complete=values[1]===true;
      var localCount=Number(values[2]||0);
      var remote=values[3]||{};
      if (remote.blocked) { _lmSetBlocked(true); return idbReplaceRooms([],0).then(function(){ return {mode:'blocked',version:0,count:0}; }); }
      if (remote.need_auth || remote.forbidden || remote.outdated) {
        var authError = new Error(remote.outdated ? 'client_outdated' : 'invalid_auth');
        authError.authFatal = true;
        throw authError;
      }
      _lmSetBlocked(false);
      var remoteVersion=Number(remote.version||0);
      var remoteCount=Number(remote.rooms_count||0);
      var countMatches=!remoteCount||localCount===remoteCount;

      if(complete&&localCount>0&&countMatches&&localVersion===remoteVersion){
        return {mode:'same',version:remoteVersion,count:localCount};
      }

      if(complete&&localCount>0&&localVersion&&remoteVersion>localVersion){
        return request(VPS_URL+'/map/changes?after='+localVersion+'&limit=5000').then(function(d){
          if(d.has_more)throw new Error('too_many_changes');
          return idbApplyChanges(d.changes||[],d.version).then(function(){
            return idbCountRooms().then(function(newCount){
              if(remoteCount&&newCount!==remoteCount)throw new Error('count_drift');
              return {mode:'changes',version:d.version,count:newCount};
            });
          });
        });
      }

      return rebuildFull();
    }).catch(function(error){
      if (error && error.authFatal) throw error;
      lmDebug('[Карта] Полная перестройка кеша:',error);
      return rebuildFull().catch(function(e2){
        lmDebug('[Карта] VPS недоступен, пробуем D1:',e2);
        return idbCountRooms().catch(function(){return 0;}).then(function(cnt){
          if(cnt>100)throw new Error('use_cache');
          return rebuildFromD1();
        });
      });
    }).then(function(){return idbGetAllRooms();})
      .then(function(rows){
        cloudMap=roomsArrayToCloud(rows);
        _cloudMapCount = Object.keys(cloudMap).length;
        invalidateRoomsCache();
        updateMapInfo();
        drawMini();
        if(fmOpen)drawFull();
        patchSiteMapDelayed();
        lmInfo('Карта обновлена');
        finish();
      }).catch(function(e){
        lmWarn('Используется кеш карты');
        lmDebug(e);
        finish();
      });
  }

  var _stepsPushing = false;

  var FLUSH_EVERY_STEPS = 1;

  function schedulePush(steps, sid) {
    cacheSteps(steps);

    var stored = getCachedSteps();
    var sentCount = Math.min(getSentCount(), stored.length);
    var pendingCount = Math.max(0, stored.length - sentCount);

    if (pendingCount < FLUSH_EVERY_STEPS) return;

    if (_pushTimer) clearTimeout(_pushTimer);
    _pushPending = true;
    _pushTimer = setTimeout(function() {
      _pushPending = false;
      _pushTimer = null;
      syncCacheWithSession();
      flushStepsToServer();
    }, 700);
  }

  function pushSteps(steps, sid) {
    if (_stepsPushing) return Promise.resolve(null);
    _stepsPushing = true;
    var auth = getAuth();
    if (!auth) { _stepsPushing = false; return Promise.resolve(null); }
    var username = auth.username, token = auth.token;
    var vpsSteps = steps.map(function(s, i) {
      var rawStepIndex = s.step_index != null ? Number(s.step_index) : i;
      var stableStepIndex = Number.isInteger(Number(s._lmServerStepIndex))
        ? Number(s._lmServerStepIndex)
        : (Number.isInteger(rawStepIndex) ? rawStepIndex : i);
      return { x:s.x, y:s.y, ev:s.ev||s.event||'empty',
        acc_delta:s.acc_delta != null ? Number(s.acc_delta) : null,
        acc_after:s.acc_after != null ? Number(s.acc_after) : null,
        session_id:sid||SESSION_ID, step_index:stableStepIndex,
        dat: parseDat(s.dat || s._dat) };
    });

    function primary() {
      return window.lmFetch(VPS_URL + '/push', {
        method:'POST', headers:vpsAuthHeaders(true),
        body:JSON.stringify({steps:vpsSteps})
      }).then(function(r) {
        if (!r.ok) throw new Error('HTTP ' + r.status);
        return r.json();
      }).then(function(d) {
        _vpsAlive = true;
        return { data:d, reserve:false };
      });
    }

    return primary().catch(function() {
      _vpsAlive = false;
      throw new Error('primary_unavailable');
    }).then(function(result) {
      lmInfo('Карта обновлена');
      return result.data;
    }).catch(function(e) {
      lmWarn('Карта сохранена локально');
      lmDebug(e);
      return null;
    }).then(function(result) {
      _stepsPushing = false;
      return result;
    });
  }

  function getCurrentRoomOwnership() {
    var cur = curPos();
    var owDiv = document.getElementById('labyrinthOwnership');
    if (!owDiv || window.getComputedStyle(owDiv).display==='none') return [];
    var captUser = document.getElementById('labyrinthCaptureUserBtn');
    var captClub = document.getElementById('labyrinthCaptureClubBtn');
    var tribute  = document.getElementById('labyrinthPayTributeBtn');
    function vis(el) { return el && window.getComputedStyle(el).display!=='none'; }
    var guardian = null;
    if (vis(tribute)) guardian = vis(captClub) ? 'guardian_club' : 'guardian_user';
    else if (vis(captUser)||vis(captClub)) guardian = 'can_capture';
    if (!guardian) return [];
    var d = mapData(), realEvent = 'unknown';
    if (d && d.steps) {
      for (var i = d.steps.length-1; i >= 0; i--) {
        if (d.steps[i].x===cur.x && d.steps[i].y===cur.y) { realEvent=d.steps[i].event; break; }
      }
    }
    return [{x:cur.x, y:cur.y, event:realEvent, guardian:guardian}];
  }

  function fetchOwnership(callback) {
    if (ownershipCache !== null) { callback(ownershipCache); return; }
    var username = PW.visitor_name || '';
    if (!username) { callback([]); return; }
    var parser = new DOMParser(), rooms = [];
    fetch('/user/' + encodeURIComponent(username) + '/')
      .then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
      .then(function(html) {
        var doc = parser.parseFromString(html, 'text/html');
        doc.querySelectorAll('.user-labyrinth__item').forEach(function(item) {
          var el = item.querySelector('.user-labyrinth__room');
          if (el) { var c = parseRoomText(el.textContent.trim()); if (c) rooms.push({x:c.x,y:c.y,event:'unknown',guardian:'guardian_user'}); }
        });
        var clubLink = findClubLink(doc);
        if (clubLink) {
          return fetch(clubLink.getAttribute('href')).then(function(r){ if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
            .then(function(ch) {
              var cd = parser.parseFromString(ch, 'text/html');
              cd.querySelectorAll('.club-labyrinth__item').forEach(function(item) {
                var el = item.querySelector('.club-labyrinth__room');
                if (el) { var c = parseRoomText(el.textContent.trim()); if (c) rooms.push({x:c.x,y:c.y,event:'unknown',guardian:'guardian_club'}); }
              });
              ownershipCache = rooms; callback(rooms);
            });
        } else { ownershipCache = rooms; callback(rooms); }
      })
      .catch(function(e) { lmDebug('[LabMap] fetchOwnership:', e); ownershipCache = []; callback([]); });
  }

  function drawCell(ctx, px, py, cs, event, isCurrent, guardian, dimmed, hasAlt, roomObject) {
    var r = Math.max(1, cs * 0.12);
    ctx.fillStyle = isCurrent ? '#2a1800' : dimmed ? '#0d0f1a' : clr(event);
    ctx.beginPath();
    if (ctx.roundRect) ctx.roundRect(px+.5, py+.5, cs-1, cs-1, r);
    else ctx.rect(px+.5, py+.5, cs-1, cs-1);
    ctx.fill();
    ctx.strokeStyle = isCurrent ? 'rgba(255,214,107,.6)' : 'rgba(255,255,255,.07)';
    ctx.lineWidth = .5; ctx.stroke();
    if (isCurrent) {
      ctx.shadowColor='#ffd66b'; ctx.shadowBlur=cs*.8;
      ctx.strokeStyle='rgba(255,214,107,.9)'; ctx.lineWidth=1; ctx.stroke();
      ctx.shadowBlur=0;
    }
    if (cs < 8 || dimmed) return;
    var iconTxt = isCurrent ? '⚔' : ico(event);
    if (iconTxt) {
      ctx.font = Math.max(8, cs*.58) + 'px sans-serif';
      ctx.fillStyle = isCurrent ? '#ffd66b' : iclr(event);
      ctx.textAlign='center'; ctx.textBaseline='middle';
      ctx.fillText(iconTxt, px+cs/2, py+cs/2);
    }
    if (guardian && cs >= 8) {
      var gc = guardian==='guardian_user' ? '#ffd700' : guardian==='guardian_club' ? '#c59cff' : '#44ff88';
      ctx.strokeStyle=gc; ctx.lineWidth=Math.max(1,cs*.07); ctx.globalAlpha=.75;
      ctx.beginPath();
      if (ctx.roundRect) ctx.roundRect(px+.5,py+.5,cs-1,cs-1,r);
      else ctx.rect(px+.5,py+.5,cs-1,cs-1);
      ctx.stroke(); ctx.globalAlpha=1;
      if (cs >= 14) {
        var gico = guardian==='guardian_user'?'👑':guardian==='guardian_club'?'🛡':'⚐';
        ctx.font=Math.max(7,cs*.3)+'px sans-serif';
        ctx.fillStyle=gc; ctx.textAlign='right'; ctx.textBaseline='top';
        ctx.fillText(gico, px+cs-1, py+1);
      }
    }
    if (hasAlt && !dimmed && cs >= 10) {
      var asz = Math.max(6, cs * 0.28);
      ctx.font = 'bold ' + asz + 'px sans-serif';
      var isVarRoom = event !== 'unknown' && VARIABLE_EVENTS[event];
      ctx.fillStyle = isVarRoom ? 'rgba(200,150,255,0.98)' : 'rgba(255,220,120,0.95)';
      ctx.textAlign = 'left'; ctx.textBaseline = 'bottom';
      ctx.fillText(isVarRoom ? '~' : '?', px + 2, py + cs - 1);
    }
  }

  function drawMini() {
    var rooms = allRooms(), keys = Object.keys(rooms);
    if (elSt) elSt.textContent = 'База: '+_cloudMapCount+' | Всего: '+keys.length;
    updateMapInfo(rooms);
  }

  var LS_FMVIEW = 'lm_fullmap_view' + _lmUserSuffix;
  var _lmFmView = null;
  function _lmSaveFmView() {
    if (!Number.isFinite(fmX) || !Number.isFinite(fmY) || !Number.isFinite(fmScale)) return;
    _lmFmView = { x: fmX, y: fmY, s: fmScale };
    try { window.lmLS.set(LS_FMVIEW, JSON.stringify(_lmFmView)); } catch (e) {}
  }
  function _lmLoadFmView() {
    if (_lmFmView) return _lmFmView;
    try {
      var o = JSON.parse(window.lmLS.get(LS_FMVIEW) || 'null');
      if (o && Number.isFinite(o.x) && Number.isFinite(o.y) && Number.isFinite(o.s)) { _lmFmView = o; return o; }
    } catch (e) {}
    return null;
  }
  function openFull(skipCenter) {
    fmOpen=true;
    document.getElementById('lm-modal').classList.add('on');
    updateEmissionInfo();
    if (!skipCenter) {
      var sv = _lmLoadFmView();
      requestAnimationFrame(function(){
        if (!fmOpen) return;
        if (sv) { fmScale = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, sv.s)); fmX = sv.x; fmY = sv.y; drawFull(); }
        else fitReadableMap();
      });
    }
  }
  function closeFull() { _lmSaveFmView(); fmOpen=false; document.getElementById('lm-modal').classList.remove('on'); }

  function fullMapBounds() {
    var rooms = allRooms(), keys = Object.keys(rooms);
    if (!keys.length) return null;
    var minX=Infinity, maxX=-Infinity, minY=Infinity, maxY=-Infinity;
    for (var i=0; i<keys.length; i++) {
      var p=keys[i].split('_'), rx=+p[0], ry=+p[1];
      if (!Number.isFinite(rx) || !Number.isFinite(ry)) continue;
      if (rx<minX) minX=rx; if (rx>maxX) maxX=rx;
      if (ry<minY) minY=ry; if (ry>maxY) maxY=ry;
    }
    return minX===Infinity ? null : {minX:minX,maxX:maxX,minY:minY,maxY:maxY};
  }

  function setMapView(x, y, cellSize) {
    if (!elMbody) return;
    var cs = Math.max(1, Math.min(CELL_SIZE, cellSize || CELL_SIZE * fmScale));
    fmScale = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, cs / CELL_SIZE));
    cs = CELL_SIZE * fmScale;
    fmX = elMbody.offsetWidth / 2 - Number(x || 0) * cs;
    fmY = elMbody.offsetHeight / 2 - Number(y || 0) * cs;
    drawFull();
  }

  function fitReadableMap() {
    if (!elMbody) return;
    var b = fullMapBounds();
    if (!b) { centerOn(0, 0, FULL_FOCUS_SCALE); return; }
    var c = curPos();
    var cx = Number.isFinite(Number(c.x)) ? Number(c.x) : (b.minX + b.maxX) / 2;
    var cy = Number.isFinite(Number(c.y)) ? Number(c.y) : (b.minY + b.maxY) / 2;
    var W = Math.max(100, elMbody.offsetWidth - 72);
    var H = Math.max(100, elMbody.offsetHeight - 96);
    var visibleX = Math.max(160, Math.min(360, W / FULL_VIEW_MIN_CELL));
    var visibleY = Math.max(95, Math.min(210, H / FULL_VIEW_MIN_CELL));
    var cs = Math.min(W / visibleX, H / visibleY, FULL_VIEW_MAX_CELL);
    cs = Math.max(FULL_VIEW_MIN_CELL, cs);
    setMapView(cx, cy, cs);
  }

  function fitAllRooms() {
    if (!elMbody) return;
    var b = fullMapBounds();
    if (!b) { centerOn(0, 0); return; }
    var W=elMbody.offsetWidth, H=elMbody.offsetHeight;
    var spanX=Math.max(1, b.maxX-b.minX+1), spanY=Math.max(1, b.maxY-b.minY+1);
    var csX = W / spanX, csY = H / spanY;
    var cs  = Math.min(csX, csY, CELL_SIZE);
    cs = Math.max(0.5, cs);
    fmScale = cs / CELL_SIZE;
    var midX=(b.minX+b.maxX)/2, midY=(b.minY+b.maxY)/2;
    fmX=W/2-midX*cs; fmY=H/2-midY*cs;
    drawFull();
  }

  function centerOn(x,y,scale) {
    if (!elMbody) return;
    if (Number.isFinite(Number(scale))) fmScale = Math.max(ZOOM_MIN, Math.min(ZOOM_MAX, Number(scale)));
    setMapView(x, y, CELL_SIZE * fmScale);
  }

  function drawFull() {
    if (!elFc || !elMbody) return;
    window._lmFmX = fmX; window._lmFmY = fmY; window._lmFmScale = fmScale;
    elFc.width=elMbody.offsetWidth; elFc.height=elMbody.offsetHeight;
    var ctx=elFc.getContext('2d');
    ctx.clearRect(0,0,elFc.width,elFc.height);
    var rooms=allRooms(), keys=Object.keys(rooms);
    if (!keys.length) return;
    var cs=CELL_SIZE*fmScale, cur=curPos(), hasFilter=Object.keys(filterSet).length>0;
    ctx.strokeStyle='rgba(255,255,255,.04)'; ctx.lineWidth=.5;
    ctx.beginPath();
    for (var gx=((fmX%cs)+cs)%cs; gx<elFc.width; gx+=cs) { ctx.moveTo(gx,0); ctx.lineTo(gx,elFc.height); }
    for (var gy=((fmY%cs)+cs)%cs; gy<elFc.height; gy+=cs) { ctx.moveTo(0,gy); ctx.lineTo(elFc.width,gy); }
    ctx.stroke();
    var myPathSet = {};
    if (showMyPath) {
      if (window._lmMyPathData) {
        myPathSet = window._lmMyPathData;
      }

      if (window._lmEmissionCells) window._lmEmissionCells.forEach(function(k){ myPathSet[k]=true; });
      var dm2=mapData();
      if (dm2&&dm2.steps) { for (var si=0;si<dm2.steps.length;si++) { var ss=dm2.steps[si]; myPathSet[ss.x+'_'+ss.y]=true; } }
    }
    for (var i=0; i<keys.length; i++) {
      var p=keys[i].split('_'),x=+p[0],y=+p[1],room=rooms[keys[i]];
      var px=fmX+x*cs, py=fmY+y*cs;
      if (px<-cs||px>elFc.width+cs||py<-cs||py>elFc.height+cs) continue;
      var isCur=x===cur.x&&y===cur.y;
      var _altClean=(room.altTypes||[]).filter(function(t){return t!=='room_player'&&t!=='room_trap'&&t!=='room_gift';}),
          _isVar=_altClean.length>0&&room.event!=='unknown'&&VARIABLE_EVENTS[room.event],
          matchesFilter=filterSet[room.event]||(room.guardian&&filterSet[room.guardian])||(filterSet['__has_alt__']&&_altClean.length>0&&!_isVar)||(filterSet['__variable__']&&_isVar)||(filterSet['__players__']&&_playerPos[keys[i]])||(filterSet['room_trap']&&room.roomObject&&(room.roomObject==='room_trap'||room.roomObject==='room_gift'));
      var dimmed=(hasFilter&&!matchesFilter&&!isCur)||(showMyPath&&!myPathSet[keys[i]]&&!isCur);
      drawCell(ctx,px,py,cs,room.event,isCur,room.guardian,dimmed,room.altTypes&&room.altTypes.length>0,room.roomObject);
      var _emCell = window._lmEmissionCells && window._lmEmissionCells.has(keys[i]);

      var _emMark = (room.emissionEv && room.emissionEv !== room.event) || room.emissionVisited || _emCell;
      if (room.event === 'unknown' && (room.emissionEv || room.emissionVisited || _emCell) && cs >= 4 && !dimmed) {
        ctx.save();
        ctx.fillStyle = 'rgba(180,40,40,0.2)';
        ctx.fillRect(px, py, cs, cs);
        ctx.restore();
      }
      if (_emMark && cs >= 12 && !dimmed) {
        ctx.save();
        ctx.font = (cs * 0.26) + 'px sans-serif';
        ctx.fillStyle = 'rgba(255,215,70,0.95)';
        ctx.textAlign = 'right';
        ctx.textBaseline = 'bottom';
        ctx.fillText('⚡', px + cs - cs * 0.08, py + cs - cs * 0.06);
        ctx.restore();
      }
    }
    if (showMyPath) {

      var pathSteps = [];
      if (window._lmHistSteps && window._lmHistSteps.length) pathSteps = window._lmHistSteps;
      var dm3=mapData();
      if (dm3&&dm3.steps&&dm3.steps.length) pathSteps = pathSteps.concat(dm3.steps);
      if (pathSteps.length > 1) {
        ctx.save();
        ctx.strokeStyle='rgba(255,214,107,.5)'; ctx.lineWidth=Math.max(1,cs*.12); ctx.lineJoin='round'; ctx.lineCap='round';
        ctx.beginPath();
        for (var li=1;li<pathSteps.length;li++) {
          var pa=pathSteps[li-1], pb=pathSteps[li];
          var pdx=Math.abs((+pa.x)-(+pb.x)), pdy=Math.abs((+pa.y)-(+pb.y));
          if ((pdx>1||pdy>1) || (pdx===0&&pdy===0)) continue;
          ctx.moveTo(fmX+pa.x*cs+cs/2,fmY+pa.y*cs+cs/2);
          ctx.lineTo(fmX+pb.x*cs+cs/2,fmY+pb.y*cs+cs/2);
        }
        ctx.stroke();
        ctx.restore();
      }
    }
    if (_lmRoute.length) {
      var _rc = curPos();
      ctx.save();
      ctx.strokeStyle = 'rgba(60,240,190,.85)';
      ctx.lineWidth = Math.max(2, cs * 0.14);
      ctx.lineJoin = 'round'; ctx.lineCap = 'round';
      ctx.beginPath();
      if (_rc && Number.isFinite(Number(_rc.x)) && _adjacent({x:Number(_rc.x),y:Number(_rc.y)}, _lmRoute[0])) ctx.moveTo(fmX + _rc.x * cs + cs / 2, fmY + _rc.y * cs + cs / 2);
      else ctx.moveTo(fmX + _lmRoute[0].x * cs + cs / 2, fmY + _lmRoute[0].y * cs + cs / 2);
      for (var _ri = 0; _ri < _lmRoute.length; _ri++) ctx.lineTo(fmX + _lmRoute[_ri].x * cs + cs / 2, fmY + _lmRoute[_ri].y * cs + cs / 2);
      ctx.stroke();
      ctx.strokeStyle = 'rgba(60,240,190,.95)';
      ctx.lineWidth = Math.max(1, cs * 0.08);
      for (var _rj = 0; _rj < _lmRoute.length; _rj++) {
        var _rp = _lmRoute[_rj], _rpx = fmX + _rp.x * cs, _rpy = fmY + _rp.y * cs;
        if (_rpx < -cs || _rpx > elFc.width + cs || _rpy < -cs || _rpy > elFc.height + cs) continue;
        ctx.strokeRect(_rpx + 1, _rpy + 1, cs - 2, cs - 2);
      }
      ctx.restore();
    }
    if (highlightRoom) {
      if (Date.now() < highlightRoom.until) {
        var hx=fmX+highlightRoom.x*cs, hy=fmY+highlightRoom.y*cs;
        if (hx>-cs&&hx<elFc.width+cs&&hy>-cs&&hy<elFc.height+cs) {
          var pulse=0.4+0.6*Math.abs(Math.sin(Date.now()/200));
          ctx.save();
          ctx.shadowColor='#ffd66b'; ctx.shadowBlur=cs*1.2*pulse;
          ctx.strokeStyle='rgba(255,220,80,'+(0.7+0.3*pulse)+')';
          ctx.lineWidth=Math.max(2,cs*.18);
          ctx.beginPath();
          var hr=Math.max(2,cs*.15);
          if(ctx.roundRect) ctx.roundRect(hx+.5,hy+.5,cs-1,cs-1,hr);
          else ctx.rect(hx+.5,hy+.5,cs-1,cs-1);
          ctx.stroke(); ctx.restore();
          requestAnimationFrame(drawFull);
        }
      } else { highlightRoom=null; }
    }
    if (elInfo) {
      var cx=Math.round((elFc.width/2-fmX)/cs),cy=Math.round((elFc.height/2-fmY)/cs);
      elInfo.textContent=formatCoords(cx,cy)+' · База '+_cloudMapCount+' · Всего '+keys.length;
    }
    if (Object.keys(_playerPos).length && cs >= 2) {
      ctx.save();
      for (var _ppk in _playerPos) {
        var _ppp=_ppk.split('_'), _ppx=fmX+(+_ppp[0])*cs, _ppy=fmY+(+_ppp[1])*cs;
        if (_ppx<-cs||_ppx>elFc.width+cs||_ppy<-cs||_ppy>elFc.height+cs) continue;
        var _ppc=_playerPos[_ppk];
        ctx.beginPath(); ctx.fillStyle='rgba(34,197,94,.95)';
        ctx.arc(_ppx+cs*0.5, _ppy+cs*0.5, Math.max(1.5,cs*0.2), 0, Math.PI*2); ctx.fill();
        ctx.lineWidth=Math.max(.5,cs*0.05); ctx.strokeStyle='rgba(4,18,10,.85)'; ctx.stroke();
        if (_ppc>1 && cs>=11) { ctx.fillStyle='#04120a'; ctx.font='700 '+Math.round(cs*0.34)+'px system-ui'; ctx.textAlign='center'; ctx.textBaseline='middle'; ctx.fillText(String(_ppc), _ppx+cs*0.5, _ppy+cs*0.54); }
      }
      ctx.restore();
    }
  }

  function injectTopBtn() {
    var tabsBtns = document.querySelector('.ncard__tabs-btns');
    if (!tabsBtns || tabsBtns.querySelector('.lm-top-btn')) return;
    var topUrl = 'https://' + location.hostname + '/users_top/';
    var btn = document.createElement('a');
    btn.href = topUrl; btn.className = 'ncard__tabs-btn btn c-gap-10 lm-top-btn';
    btn.innerHTML = '<span class="fal fa-trophy"></span>Топ лабиринта';
    tabsBtns.appendChild(btn);
  }

  var HISTORY_KEYWORDS = ['лабиринт','лабиринта','испытание дао','алтаря удачи','небесный кирпич'];
  var HISTORY_ICONS = [
    { re:/мини.?босс/i, icon:'👹', color:'#ffbb88' },
    { re:/хард.?босс/i, icon:'💀', color:'#cc88ff' },
    { re:/викторин/i, icon:'?', color:'#8bb4ff' },
    { re:/джекпот/i, icon:'★', color:'#ffd66b' },
    { re:/сундук.мимик/i, icon:'👅', color:'#ff9ecb' },
    { re:/сундук/i, icon:'🔒', color:'#ffd66b' },
    { re:/реликвия/i, icon:'🔮', color:'#c59cff' },
    { re:/телепорт/i, icon:'🌀', color:'#c59cff' },
    { re:/торговец/i, icon:'🛒', color:'#8fd3ff' },
    { re:/алтарь/i, icon:'🍀', color:'#6ee786' },
    { re:/страж/i, icon:'👑', color:'#ffd700' },
    { re:/коллекци/i, icon:'🃏', color:'#8fd3ff' },
    { re:/пазл/i, icon:'🧩', color:'#e0d0ff' },
    { re:/небесный кирпич/i, icon:'🧱', color:'#8fd3ff' },
    { re:/испытание дао/i, icon:'☯', color:'#c59cff' },
    { re:/откат|ловушка/i, icon:'↺', color:'#ff8a8a' },
    { re:/штраф.*лабиринт/i, icon:'!', color:'#ff8b8b' },
    { re:/штраф.*дао/i, icon:'!', color:'#ff8b8b' },
    { re:/награда.*лабиринт/i, icon:'+', color:'#ffd66b' },
    { re:/награда.*босс/i, icon:'+', color:'#ffd66b' },
    { re:/лабиринт/i, icon:'⚔', color:'rgba(255,255,255,.45)' }
  ];

  function histIcon(desc) {
    for (var i=0;i<HISTORY_ICONS.length;i++) {
      if (HISTORY_ICONS[i].re.test(desc)) return HISTORY_ICONS[i];
    }
    return { icon:'⚔', color:'rgba(255,255,255,.35)' };
  }

  function isLabRow(desc) {
    var d = desc.toLowerCase();
    for (var i=0;i<HISTORY_KEYWORDS.length;i++) { if (d.indexOf(HISTORY_KEYWORDS[i])!==-1) return true; }
    return false;
  }

  var LS_HIST_KEY      = 'lm_hist_cache_v3'   + _lmUserSuffix;
  var LS_HIST_DATE_KEY = 'lm_hist_cache_date' + _lmUserSuffix;
  var histCache = {};
  var histTotal = null;
  var histCurPage = 1;
  try {
    var _raw = window.lmLS.get(LS_HIST_KEY);
    if (_raw) { histCache = JSON.parse(_raw); histTotal = Object.keys(histCache).length || null; }
  } catch(e) {}

  function parseHistoryHtml(html, pageNum) {
    var parser = new DOMParser();
    var doc = parser.parseFromString(html, 'text/html');
    var rows = [];
    var allRows = [];
    var trs = doc.querySelectorAll('.ncard-transactions__table tbody tr');
    if (!trs.length) trs = doc.querySelectorAll('table tbody tr');
    var allAmounts = [];
    trs.forEach(function(tr) {
      var tds = tr.querySelectorAll('td');
      if (tds.length < 4) return;
      var amount  = tds[0].textContent.trim();
      var balance = tds[1].textContent.trim();
      var date    = tds[2].textContent.trim();
      var descEl  = tds[3];
      var descClone = descEl.cloneNode(true);
      var cardDiv = descClone.querySelector('.acc-history__card');
      if (cardDiv) cardDiv.parentNode.removeChild(cardDiv);
      var desc = descClone.textContent.replace(/\s+/g,' ').trim();
      var amt = parseFloat(amount.replace(/[^0-9.\-]/g,''));
      if (isNaN(amt)) return;
      var dl = desc.toLowerCase();
      if (amt > 0) {
        if (isLabRow(desc)) allAmounts.push({ amt: amt, isLost: false });
      } else {
        var isShutraf = dl.indexOf('штраф') !== -1;
        var isPokupka = dl.indexOf('за покупку') !== -1;
        var isVitrina = dl.indexOf('витрин') !== -1;
        if (isShutraf && !isPokupka && !isVitrina) allAmounts.push({ amt: amt, isLost: true });
      }
      if (!desc) return;
      var _row = { amount: amount, balance: balance, date: date, desc: desc };
      allRows.push(_row);
      if (isLabRow(desc)) rows.push(_row);
    });
    var totalPages = pageNum;
    var pLinks = doc.querySelectorAll('.acc-history__page');
    pLinks.forEach(function(a) {
      var n = parseInt(a.textContent.trim(), 10);
      if (!isNaN(n) && n > totalPages) totalPages = n;
    });
    var trapBackCount = 0;
    var roomCounts = {};
    var DESC_MAP = [
      { re: /мини.?босс/i,          ev: 'mini_boss' },
      { re: /сложн\w* босс/i,        ev: 'hard_boss' },
      { re: /сундук.мимик/i,         ev: 'mimic_chest' },
      { re: /открытие сундука/i,      ev: null },
      { re: /сундука в лабиринте/i,   ev: 'locked_chest' },
      { re: /алтар\w* удачи/i,        ev: 'luck_altar' },
      { re: /викторин/i,              ev: 'quiz' },
      { re: /испытание дао/i,         ev: 'puzzle' },
      { re: /персональной шахты/i,    ev: 'personal_mine' },
      { re: /штраф в лабиринте/i,     ev: 'penalty' },
      { re: /награда в лабиринте/i,   ev: 'reward' },
      { re: /награда за победу над/i, ev: null },
      { re: /откат|ловушка отката/i,  ev: 'trap_back' },
      { re: /коллекц/i,               ev: 'collection' },
      { re: /торговец/i,              ev: 'card_trader' },
      { re: /реликв/i,                ev: 'relic_room' },
      { re: /восстановление|хода/i,   ev: 'recovery_room' },
    ];
    var _seenKeys = {};
    for (var _ri = 0; _ri < rows.length; _ri++) {
      var _desc = rows[_ri].desc;
      if (/откат/i.test(_desc)) trapBackCount++;
      var _matched = false;
      for (var _di = 0; _di < DESC_MAP.length; _di++) {
        if (!DESC_MAP[_di].re.test(_desc)) continue;
        _matched = true;
        var _ev = DESC_MAP[_di].ev;
        if (_ev === null) break;
        var _deduKey = _ev + '_' + (rows[_ri].date || '').replace(/[^0-9]/g, '').slice(0, 12);
        if (_ev === 'locked_chest' && _seenKeys[_deduKey]) break;
        _seenKeys[_deduKey] = true;
        roomCounts[_ev] = (roomCounts[_ev] || 0) + 1;
        break;
      }
    }
    return { rows: rows, allRows: allRows, totalPages: totalPages, allAmounts: allAmounts, trapBackCount: trapBackCount, roomCounts: roomCounts };
  }

  function loadHistPage(pageNum, onDone) {
    if (histCache[pageNum]) { onDone(histCache[pageNum]); return; }
    var url = pageNum > 1 ? '/acchistory/page/' + pageNum + '/' : '/acchistory/';
    fetch(url, { credentials: 'same-origin' })
      .then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
      .then(function(html) {
        var result = parseHistoryHtml(html, pageNum);
        histCache[pageNum] = result;
        if (histTotal === null || result.totalPages > histTotal) histTotal = result.totalPages;
        try { window.lmLS.set(LS_HIST_KEY, JSON.stringify(histCache)); } catch(e) {}
        onDone(result);
      })
      .catch(function(e) {
        lmDebug('[LabMap] history err:', e);
        onDone({ rows: [], totalPages: pageNum });
      });
  }

  function buildAccTotals() {
    var earned = 0, lost = 0;
    Object.keys(histCache).forEach(function(p) {
      var amounts = histCache[p].allAmounts || [];
      amounts.forEach(function(item) {
        if (item.isLost) lost += Math.abs(item.amt);
        else earned += item.amt;
      });
    });
    return { earned: earned, lost: lost };
  }

  function buildAllTimeTrapCount() {
    var total = 0;
    Object.keys(histCache).forEach(function(p) {
      total += (histCache[p].trapBackCount || 0);
    });
    return total;
  }

  function buildAllTimeRoomCounts() {
    var merged = {};
    Object.keys(histCache).forEach(function(p) {
      var rc = histCache[p].roomCounts || {};
      Object.keys(rc).forEach(function(ev) {
        merged[ev] = (merged[ev] || 0) + rc[ev];
      });
    });
    return merged;
  }

  function buildStepsStats() {
    var d = mapData();
    if (!d || !d.steps || !d.steps.length) return null;
    var counts = {};
    d.steps.forEach(function(s) {
      if (s.event==='room_trap'||s.event==='room_gift') return;
      counts[s.event] = (counts[s.event]||0) + 1;
    });
    delete counts['start'];
    var total = d.steps.length - 1;
    var histRooms = buildAllTimeRoomCounts();
    var hasHistData = Object.keys(histRooms).length > 0;
    if (hasHistData) {
      Object.keys(histRooms).forEach(function(ev) {
        var sessionCount = counts[ev] || 0;
        var histCount    = histRooms[ev] || 0;
        counts[ev] = Math.max(sessionCount, histCount);
      });
      total = 0;
      Object.keys(counts).forEach(function(ev) { total += counts[ev]; });
    }
    var todayTrap = counts['trap_back'] || 0;
    var allTimeTotalEl = document.getElementById('labyrinthTotalSteps');
    var domTotal = allTimeTotalEl
      ? (parseInt(allTimeTotalEl.textContent.replace(/\D/g,''), 10) || total)
      : total;
    var allTimeTotal = window._lmAllTimeTotal || domTotal;
    var dbLoaded    = window._lmAllTimeTrapBacks !== undefined;
    var allTimeTrap = dbLoaded ? window._lmAllTimeTrapBacks : buildAllTimeTrapCount();
    var histLoaded  = dbLoaded || allTimeTrap > 0;
    var trapCount   = histLoaded ? allTimeTrap : todayTrap;
    var lostRooms   = trapCount * 5;
    var sorted = Object.keys(counts).sort(function(a,b){ return counts[b]-counts[a]; });
    return {
      total: total,
      allTimeTotal: allTimeTotal,
      counts: counts,
      top: sorted,
      trapCount: trapCount,
      lostRooms: lostRooms,
      historyLoaded: histLoaded
    };
  }

  function renderHistPopup(result, page) {
    var pop = document.getElementById('lm-hpop');
    if (!pop) return;
    var listEl = pop.querySelector('.lm-hist-body');
    if (!listEl) return;
    var totalPages = histTotal || result.totalPages || 1;
    var rows = result.rows;
    var freshHtml = '';
    var lastDate = window.lmLS.get(LS_HIST_DATE_KEY);
    var pagesLoaded = Object.keys(histCache).length;
    if (lastDate && pagesLoaded > 0) {
      var d = new Date(lastDate);
      var pad = function(n){ return n < 10 ? '0'+n : ''+n; };
      var dateStr = pad(d.getDate())+'.'+pad(d.getMonth()+1)+'.'+d.getFullYear()+' '+pad(d.getHours())+':'+pad(d.getMinutes());
      freshHtml = '<div class="lm-hist-fresh">🕐 Актуально на: <b>'+dateStr+'</b> &bull; Стр.: <b>'+pagesLoaded+' из '+(histTotal||totalPages||'?')+'</b></div>';
    } else {
      freshHtml = '<div class="lm-hist-fresh lm-hist-fresh--warn">⚠ Нажмите 🔄 Обновить инфу для загрузки всех данных.</div>';
    }
    var stats = buildStepsStats();
    var statsHtml = '';
    if (stats) {
      var itemsHtml = stats.top.map(function(ev) {
        var pct = Math.round(stats.counts[ev] / stats.total * 100);
        var barColor = ev === 'empty' ? 'rgba(255,255,255,0.25)' : iclr(ev)+'44';
        return '<div class="lm-hs-item"><span class="lm-hs-ico" style="background:'+clr(ev)+';color:'+iclr(ev)+'">'+ico(ev)+'</span><span class="lm-hs-name">'+roomName(ev)+'</span><div class="lm-hs-bar-wrap"><div class="lm-hs-bar" style="width:'+pct+'%;background:'+barColor+'"></div></div><span class="lm-hs-cnt">'+stats.counts[ev]+' <span class="lm-hs-pct">('+pct+'%)</span></span></div>';
      }).join('');
      var acc = buildAccTotals();
      var trapSub = stats.historyLoaded ? 'за всё время' : 'сегодня';
      var cardsHtml = '<div class="lm-sc-grid">' +
        '<div class="lm-sc-card"><div class="lm-sc-label">Всего комнат</div><div class="lm-sc-val">'+stats.allTimeTotal+'</div><div class="lm-sc-sub">комнат пройдено</div></div>' +
        '<div class="lm-sc-card lm-sc-trap">' +
        '<div class="lm-sc-label">Откаты</div>' +
        '<div class="lm-sc-val lm-sc-bad">' + (stats.historyLoaded ? (stats.trapCount > 0 ? '−'+stats.trapCount : '0') : '?') + '</div>' +
        '<div class="lm-sc-trap-row">' +
          (stats.historyLoaded && stats.lostRooms > 0
            ? '<span class="lm-sc-rooms">−'+stats.lostRooms+' комнат &bull; '+trapSub+'</span>'
            : '<span class="lm-sc-hint">'+(stats.historyLoaded ? '0 откатов' : 'нажми Обновить')+'</span>') +
          (window._lmRollbackPlace ? '<span class="lm-sc-badge">Топ #'+window._lmRollbackPlace+'</span>' : '') +
        '</div>' +
      '</div>' +
        '<div class="lm-sc-card"><div class="lm-sc-label">Заработано</div><div class="lm-sc-val lm-sc-good">+'+acc.earned+'</div><div class="lm-sc-sub">АСС за всё время</div></div>' +
        '<div class="lm-sc-card"><div class="lm-sc-label">Потеряно</div><div class="lm-sc-val lm-sc-bad">−'+acc.lost+'</div><div class="lm-sc-sub">АСС на штрафах</div></div>' +
        '</div>';
      statsHtml = cardsHtml+'<div class="lm-hist-stats"><div class="lm-hist-stats-ttl">📊 Статистика по комнатам</div><div class="lm-hist-stats-list">'+itemsHtml+'</div></div>';
    }
    var rowsHtml = '';
    if (rows.length) {
      rowsHtml = rows.map(function(row) {
        var amt = parseFloat(row.amount.replace(/[^0-9.\-+]/g,''));
        var isPlus  = (!isNaN(amt) && amt > 0) || row.amount.indexOf('+') !== -1;
        var isMinus = (!isNaN(amt) && amt < 0) || row.amount.indexOf('-') !== -1;
        var amtCls  = isPlus ? 'lm-ha-plus' : isMinus ? 'lm-ha-minus' : '';
        var hi = histIcon(row.desc);
        return '<div class="lm-hr"><div class="lm-hr-ico" style="background:'+hi.color+'18;border-color:'+hi.color+'44"><span style="color:'+hi.color+'">'+hi.icon+'</span></div><div class="lm-hr-main"><div class="lm-hr-desc">'+row.desc+'</div><div class="lm-hr-meta">'+row.date+(row.balance?' &bull; Баланс: <b>'+row.balance+'</b> ACC':'')+'</div></div><div class="lm-hr-amt '+amtCls+'">'+row.amount+(row.amount.indexOf('ACC')===-1?' ACC':'')+'</div></div>';
      }).join('');
    } else {
      rowsHtml = '<div class="lm-hist-empty">Записей лабиринта не найдено на странице '+page+'.</div>';
    }
    var pagHtml = '';
    if (totalPages > 1) {
      var start = Math.max(1, page-2), end = Math.min(totalPages, page+2);
      var nums = '';
      if (start > 1) nums += '<button class="lm-hp-btn" data-p="1">1</button>'+(start>2?'<span class="lm-hp-dots">…</span>':'');
      for (var pi=start; pi<=end; pi++) {
        nums += '<button class="lm-hp-btn'+(pi===page?' lm-hp-cur':'')+'" data-p="'+pi+'">'+pi+'</button>';
      }
      if (end < totalPages) nums += (end<totalPages-1?'<span class="lm-hp-dots">…</span>':'')+'<button class="lm-hp-btn" data-p="'+totalPages+'">'+totalPages+'</button>';
      pagHtml = '<div class="lm-hist-pag"><button class="lm-hp-nav" id="lm-hprev" '+(page<=1?'disabled':'')+'>‹ Новее</button>'+nums+'<button class="lm-hp-nav" id="lm-hnext" '+(page>=totalPages?'disabled':'')+'>Старее ›</button></div>';
    }
    listEl.innerHTML = freshHtml + statsHtml + '<div class="lm-hist-rows-head">📜 Записи из лабиринта (страница '+page+')</div><div class="lm-hist-rows">'+rowsHtml+'</div>'+pagHtml;
    listEl.querySelectorAll('.lm-hp-btn[data-p]').forEach(function(btn) {
      btn.addEventListener('click', function() { loadAndRenderHist(parseInt(this.dataset.p, 10)); });
    });
    var prev = listEl.querySelector('#lm-hprev');
    var next = listEl.querySelector('#lm-hnext');
    if (prev) prev.addEventListener('click', function() { if (page>1) loadAndRenderHist(page-1); });
    if (next) next.addEventListener('click', function() { if (page<totalPages) loadAndRenderHist(page+1); });
  }

  function loadAndRenderHist(page) {
    histCurPage = page;
    var listEl = document.querySelector('#lm-hpop .lm-hist-body');
    if (listEl) listEl.innerHTML = '<div class="lm-spop-loading">Загрузка страницы '+page+'...</div>';
    loadHistPage(page, function(result) { renderHistPopup(result, page); });
  }

  function injectStyles() {
    var style = document.createElement('style');
    style.textContent = [
      '#lm-wrap{width:100%;max-width:100%;min-width:0;margin-bottom:6px;border-radius:16px;background:linear-gradient(135deg,rgba(30,33,52,.96) 0%,rgba(16,18,31,.98) 100%);border:1px solid rgba(255,255,255,.1);color:#fff;overflow:hidden;box-shadow:0 8px 32px rgba(0,0,0,.35);container-type:inline-size;}',
      '#lm-hdr{display:flex;align-items:center;justify-content:space-between;gap:12px;padding:12px 14px;border-bottom:1px solid rgba(255,255,255,.08);background:rgba(255,255,255,.025);min-width:0;}',
      '#lm-ttl{display:flex;align-items:center;gap:8px;min-width:0;font-size:12px;font-weight:900;text-transform:uppercase;letter-spacing:.04em;color:rgba(226,232,240,.72);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}',
      '#lm-ttl-dot{width:8px;height:8px;border-radius:50%;background:#4a5ac7;box-shadow:0 0 8px rgba(74,90,199,.7);animation:lm-pulse 2s infinite;}',
      '@keyframes lm-pulse{0%,100%{opacity:1;transform:scale(1)}50%{opacity:.5;transform:scale(.8)}}',
      '#lm-btns{display:flex;align-items:center;justify-content:flex-end;gap:8px;min-width:0;}',
      '#lm-rbtn{min-height:36px;padding:8px 14px;border:none;border-radius:8px;background:linear-gradient(135deg,#4a5ac7,#3a4ab0);color:#fff;font-size:11px;font-weight:800;cursor:pointer;box-shadow:0 2px 12px rgba(74,90,199,.4);transition:.15s;white-space:nowrap;}',
      '#lm-rbtn:hover{background:linear-gradient(135deg,#5b6dd8,#4a5ac7);transform:translateY(-1px);}',
      '#lm-rbtn.loading{opacity:.6;cursor:not-allowed;transform:none;}',
      '#lm-pbtn{min-height:36px;padding:8px 14px;border:none;border-radius:8px;background:linear-gradient(135deg,#b02a59,#8a1f45);color:#fff;font-size:11px;font-weight:800;cursor:pointer;box-shadow:0 2px 12px rgba(176,42,89,.4);transition:.15s;white-space:nowrap;}',
      '#lm-pbtn:hover{background:linear-gradient(135deg,#c03468,#b02a59);transform:translateY(-1px);}',
      '#lm-cleanbtn{min-height:36px;padding:8px 14px;border:none;border-radius:8px;background:rgba(255,255,255,.08);color:rgba(255,255,255,.65);font-size:11px;font-weight:800;cursor:pointer;border:1px solid rgba(255,255,255,.12);transition:.15s;white-space:nowrap;}',
      '#lm-cleanbtn:hover{background:rgba(255,255,255,.14);color:#fff;transform:translateY(-1px);}',
      '#lm-cleanbtn.active{background:rgba(255,214,107,.18);color:#ffd66b;border-color:rgba(255,214,107,.45);}',
      '#lm-setbtn{min-height:36px;padding:8px 12px;border:1px solid rgba(255,255,255,.12);border-radius:8px;background:rgba(255,255,255,.08);color:rgba(255,255,255,.72);font-size:15px;line-height:1;cursor:pointer;transition:.15s;}',
      '#lm-setbtn:hover{background:rgba(255,255,255,.16);color:#fff;transform:translateY(-1px);}',
      '#lm-set-back{position:fixed;inset:0;z-index:2000000;background:rgba(3,6,14,.72);display:none;align-items:center;justify-content:center;padding:16px;}',
      '#lm-set-back.on{display:flex;}',
      '#lm-set-box{width:min(520px,96vw);max-height:88vh;overflow:auto;background:#0d1322;border:1px solid #233;border-radius:16px;box-shadow:0 24px 70px rgba(0,0,0,.6);color:#dfe8f5;font:14px system-ui,sans-serif;}',
      '#lm-set-head{display:flex;align-items:center;gap:10px;padding:16px 18px;border-bottom:1px solid #1c2740;position:sticky;top:0;background:#0d1322;}',
      '#lm-set-head b{font-size:16px;color:#9fc5ff;flex:1;}',
      '#lm-set-close{width:30px;height:30px;border:none;border-radius:8px;background:#c0392b;color:#fff;font-size:17px;cursor:pointer;}',
      '#lm-set-body{padding:16px 18px;}',
      '.lm-set-sec-title{font-size:13px;font-weight:800;color:#cfe;margin:4px 0 6px;}',
      '.lm-set-hint{font-size:12px;color:rgba(226,232,240,.6);margin-bottom:12px;}',
      '.lm-set-row{display:flex;align-items:center;gap:10px;justify-content:space-between;padding:9px 11px;background:#111a2e;border:1px solid #1c2740;border-radius:10px;margin-bottom:7px;}',
      '.lm-set-row .lm-set-lbl{flex:1;font-size:13px;line-height:1.35;}',
      '.lm-set-sw{position:relative;width:44px;height:24px;flex-shrink:0;cursor:pointer;}',
      '.lm-set-sw input{opacity:0;width:0;height:0;}',
      '.lm-set-sw span{position:absolute;inset:0;background:rgba(255,255,255,.15);border-radius:24px;transition:.2s;}',
      '.lm-set-sw span:before{content:"";position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s;}',
      '.lm-set-sw input:checked + span{background:#12b891;}',
      '.lm-set-sw input:checked + span:before{transform:translateX(20px);}',
      '#lm-anbtn{min-height:36px;padding:8px 14px;border:1px solid rgba(120,180,255,.3);border-radius:8px;background:rgba(90,150,255,.12);color:#9fc5ff;font-size:11px;font-weight:800;cursor:pointer;transition:.15s;white-space:nowrap;}',
      '#lm-anbtn:hover{background:rgba(90,150,255,.22);color:#fff;transform:translateY(-1px);}',
      '#lm-msgbtn{position:relative;min-height:36px;padding:8px 12px;border:1px solid rgba(120,180,255,.3);border-radius:8px;background:rgba(90,150,255,.12);color:#9fc5ff;font-size:15px;line-height:1;font-weight:800;cursor:pointer;transition:.15s;white-space:nowrap;}',
      '#lm-msgbtn:hover{background:rgba(90,150,255,.22);color:#fff;transform:translateY(-1px);}',
      '#lm-msgbtn.has-unread{border-color:#ff3b3b;color:#fff;background:rgba(255,59,59,.18);}',
      '#lm-chatbtn{position:relative;min-height:36px;padding:8px 12px;border:1px solid rgba(60,220,180,.35);border-radius:8px;background:rgba(40,200,160,.14);color:#6ee7c0;font-size:14px;line-height:1;font-weight:800;cursor:pointer;transition:.15s;white-space:nowrap;}',
      '#lm-chatbtn:hover{background:rgba(40,200,160,.26);color:#fff;transform:translateY(-1px);}',
      '#lm-chatbtn.has-unread{border-color:#ff3b3b;color:#fff;background:rgba(255,59,59,.18);}',
      '#lm-chatbadge{display:none;position:absolute;top:-7px;right:-7px;min-width:18px;height:18px;padding:0 4px;border-radius:9px;background:#ff2d2d;color:#fff;font-size:10px;font-weight:900;line-height:18px;text-align:center;}',
      '#lm-chatbadge.on{display:block;}',
      '#lm-msgbadge{display:none;position:absolute;top:-7px;right:-7px;min-width:18px;height:18px;padding:0 4px;border-radius:9px;background:#ff2d2d;color:#fff;font-size:10px;font-weight:900;line-height:18px;text-align:center;box-shadow:0 0 8px rgba(255,45,45,.9);}',
      '#lm-msgbadge.show{display:block;}',
      '#lm-map-info{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:0;border-bottom:1px solid rgba(255,255,255,.07);}',
      '.lm-mi-card{display:flex;align-items:center;gap:10px;min-width:0;padding:11px 16px;}',
      '.lm-mi-card:first-child{border-right:1px solid rgba(255,255,255,.07);}',
      '.lm-mi-ico{font-size:22px;flex-shrink:0;}',
      '.lm-mi-val{font-size:20px;font-weight:850;line-height:1;color:#fff;margin-bottom:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}',
      '.lm-mi-val-sm{font-size:15px;font-weight:700;line-height:1;color:rgba(255,255,255,.78);margin-bottom:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}',
      '.lm-mi-sep{height:1px;background:rgba(255,255,255,.08);margin:5px 0;}',
      '.lm-mi-lbl{font-size:10px;color:rgba(255,255,255,.46);text-transform:uppercase;letter-spacing:.04em;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}',
      '#lm-mi-emission-card.is-active .lm-mi-ico{animation:lmEmissionPulse 1.2s ease-in-out infinite;}',
      '@keyframes lmEmissionPulse{0%,100%{opacity:1;}50%{opacity:.4;}}',
      '#lm-action-bar{display:flex;gap:8px;padding:11px 14px;border-bottom:1px solid rgba(255,255,255,.07);background:rgba(0,0,0,.15);min-width:0;}',
      '.labyrinth-cell.lm-emission-cell{position:relative!important;}',
      '.lm-emission-ico{position:absolute;bottom:1px;right:2px;font-size:7px;line-height:1;pointer-events:none;z-index:5;filter:drop-shadow(0 0 2px rgba(255,220,0,.8));}',
      '.lm-abar-btn{flex:1 1 0;min-width:0;display:flex;align-items:center;justify-content:center;gap:7px;padding:9px 10px;border:1px solid rgba(255,255,255,.1);background:rgba(255,255,255,.05);color:rgba(255,255,255,.68);font-size:11px;font-weight:800;cursor:pointer;border-radius:10px;transition:background .15s,border-color .15s,color .15s;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}',
      '.lm-abar-btn:hover{background:rgba(255,255,255,.1);border-color:rgba(255,255,255,.2);color:rgba(255,255,255,.9);}',
      '.lm-abar-ico{font-size:15px;line-height:1;flex-shrink:0;}',
      '#lm-wb-guard.active{background:rgba(255,215,0,.15);border-color:rgba(255,215,0,.5);color:#ffd700;}',
      '#lm-wb-club.active{background:rgba(197,156,255,.15);border-color:rgba(197,156,255,.5);color:#c59cff;}',
      '#lm-wb-hist.active{background:rgba(103,232,249,.15);border-color:rgba(103,232,249,.5);color:#67e8f9;}',
      '#lm-inline-panel{display:none;border-top:0;}',
      '#lm-inline-panel.on{display:block;}',
      '#lm-wi-guard,#lm-wi-club{max-height:280px;overflow-y:auto;}',
      '.lm-wi-item{display:flex;align-items:center;gap:10px;padding:9px 16px;cursor:pointer;transition:.12s;border-bottom:1px solid rgba(255,255,255,.04);}',
      '.lm-wi-item:last-child{border-bottom:none;}',
      '.lm-wi-item:hover{background:rgba(255,255,255,.05);}',
      '.lm-wi-img{width:34px;height:48px;border-radius:6px;object-fit:cover;flex-shrink:0;}',
      '.lm-wi-info{flex:1;min-width:0;}',
      '.lm-wi-name{font-size:12px;font-weight:700;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}',
      '.lm-wi-room{font-size:11px;color:rgba(255,255,255,.55);margin-top:2px;}',
      '.lm-wi-date{font-size:10px;color:rgba(255,255,255,.35);margin-top:1px;}',
      '.lm-wi-nav{font-size:14px;color:rgba(255,255,255,.3);flex-shrink:0;}',
      '.lm-wi-empty{padding:20px;text-align:center;font-size:12px;color:rgba(255,255,255,.35);}',
      '.lm-wi-loading{padding:16px;text-align:center;font-size:12px;color:rgba(255,255,255,.4);}',
      '#lm-wi-hist{max-height:420px;overflow-y:auto;}',
      '#lm-cvs-wrap{display:none!important;}#lm-cvs{display:none!important;}#lm-st{display:none!important;}',
      '@container (max-width:620px){#lm-hdr{flex-direction:column;align-items:stretch;padding:11px;}#lm-btns{justify-content:flex-start;overflow-x:auto;overflow-y:hidden;margin:0 -11px;padding:0 11px 2px;scrollbar-width:none;}#lm-btns::-webkit-scrollbar{display:none;}#lm-cleanbtn,#lm-rbtn,#lm-pbtn{flex:0 0 auto;min-height:38px;padding:8px 12px;}#lm-map-info{grid-template-columns:repeat(2,minmax(0,1fr));}.lm-mi-card{padding:12px 11px;gap:8px;}.lm-mi-val{font-size:20px;}.lm-mi-lbl{font-size:9px;}#lm-action-bar{overflow-x:auto;overflow-y:hidden;flex-wrap:nowrap;padding:10px 11px;scrollbar-width:none;}#lm-action-bar::-webkit-scrollbar{display:none;}.lm-abar-btn{flex:0 0 auto;min-width:134px;font-size:10px;padding:10px 12px;}.lm-sc-grid{grid-template-columns:repeat(2,minmax(0,1fr));gap:7px!important;}.lm-sc-card{min-width:0;padding:9px 10px;}.lm-sc-val{font-size:18px;}.lm-hist-body{padding:8px;}.lm-hr{gap:7px;padding:7px 8px;}.lm-hr-amt{font-size:12px;}}',
      '@container (max-width:420px){#lm-wrap{border-radius:12px;}#lm-ttl{font-size:11px;}#lm-map-info{grid-template-columns:1fr;}.lm-mi-card:first-child{border-right:0;border-bottom:1px solid rgba(255,255,255,.07);}.lm-mi-ico{width:24px;font-size:20px;text-align:center;}.lm-mi-val{font-size:19px;}#lm-cleanbtn,#lm-rbtn,#lm-pbtn{font-size:10px;padding:8px 10px;}.lm-sc-grid{grid-template-columns:1fr!important;}.lm-hr-desc{white-space:normal;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;}.lm-hr-amt{align-self:flex-start;white-space:nowrap;}}',
      '@media(max-width:640px){#lm-map-info{grid-template-columns:1fr 1fr;}.lm-mi-val{font-size:18px;}.lm-abar-btn{font-size:10px;padding:9px 8px;}}',
      '#lm-modal{display:none;position:fixed;inset:0;z-index:999999;background:radial-gradient(circle at 50% 30%,#12141f 0%,#0a0b11 75%);flex-direction:column;touch-action:none;overflow:hidden;color:#f8fbff;height:100vh;height:100dvh;}',
      '#lm-modal:before{content:"";position:absolute;inset:0;pointer-events:none;background:radial-gradient(circle at 50% 22%,rgba(74,90,199,.07),transparent 50%);opacity:.9;}',
      '#lm-modal.on{display:flex;}',
      '#lm-mhdr{position:relative;z-index:4;display:flex;flex-direction:column;gap:8px;padding:calc(10px + env(safe-area-inset-top)) 14px 10px;background:linear-gradient(180deg,rgba(30,33,52,.92),rgba(22,24,40,.88));border-bottom:1px solid rgba(255,255,255,.08);box-shadow:0 12px 32px rgba(0,0,0,.32);backdrop-filter:blur(14px);flex-shrink:0;}',
      '#lm-mhdr-top{display:flex;align-items:center;justify-content:space-between;gap:12px;width:100%;min-width:0;}',
      '#lm-mhdr-btns{display:flex;align-items:center;gap:8px;flex-wrap:nowrap;overflow-x:auto;overflow-y:hidden;scrollbar-width:none;margin:0 -14px;padding:0 14px 2px;}',
      '#lm-mhdr-btns::-webkit-scrollbar{display:none;}',
      '#lm-mttl{display:flex;align-items:center;gap:9px;min-width:0;font-size:14px;font-weight:900;letter-spacing:.02em;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}',
      '#lm-mttl:before{content:"";width:9px;height:9px;border-radius:50%;background:#4a5ac7;box-shadow:0 0 14px rgba(74,90,199,.85);flex:0 0 auto;animation:lm-pulse 2s infinite;}',
      '#lm-b0,#lm-b1,#lm-bf,#lm-bpath,#lm-bguard,#lm-bclub,#lm-bhist{min-height:36px;padding:8px 12px;border-radius:8px;font-size:11px;font-weight:800;line-height:1;cursor:pointer;position:relative;display:inline-flex;align-items:center;justify-content:center;gap:6px;white-space:nowrap;box-shadow:inset 0 1px 0 rgba(255,255,255,.08);transition:background .14s,border-color .14s,color .14s,transform .14s;}',
      '#lm-b0{background:rgba(99,102,241,.16);color:#b9c4ff;border:1px solid rgba(129,140,248,.38);}#lm-b0:hover{background:rgba(99,102,241,.28);color:#fff;transform:translateY(-1px);}',
      '#lm-b1{background:rgba(245,158,11,.16);color:#ffd66b;border:1px solid rgba(245,158,11,.42);}#lm-b1:hover{background:rgba(245,158,11,.3);color:#fff;transform:translateY(-1px);}',
      '#lm-bf{background:rgba(139,92,246,.16);color:#d8c4ff;border:1px solid rgba(139,92,246,.4);}#lm-bf:hover{background:rgba(139,92,246,.3);color:#fff;transform:translateY(-1px);}#lm-bf.active{background:#7c3aed;color:#fff;border-color:#a78bfa;}',
      '#lm-bpath{background:rgba(16,185,129,.14);color:#7cf4a2;border:1px solid rgba(16,185,129,.38);}#lm-bpath:hover{background:rgba(16,185,129,.28);color:#fff;transform:translateY(-1px);}#lm-bpath.active{background:#059669;color:#fff;border-color:#34d399;}',
      '#lm-bpencil,.lm-pctl{min-height:36px;padding:8px 12px;border-radius:8px;font-size:11px;font-weight:800;line-height:1;cursor:pointer;display:inline-flex;align-items:center;justify-content:center;gap:6px;white-space:nowrap;border:1px solid rgba(60,240,190,.4);background:rgba(60,240,190,.12);color:#7ff0d0;transition:.14s;}',
      '#lm-bpencil:hover,.lm-pctl:hover{background:rgba(60,240,190,.24);color:#fff;transform:translateY(-1px);}',
      '#lm-bpencil.active,.lm-pctl.active{background:#12b891;color:#fff;border-color:#3ef0c0;}',
      '.lm-pctl{padding:9px 12px;}',
      '#lm-pencil-panel{display:none;position:relative;z-index:4;align-items:center;flex-wrap:wrap;gap:8px;padding:10px 14px;background:linear-gradient(180deg,rgba(24,40,44,.96),rgba(18,30,34,.96));border-bottom:1px solid rgba(60,240,190,.3);flex-shrink:0;}',
      '#lm-pencil-panel.on{display:flex;}',
      '.lm-pp-title{font-size:13px;font-weight:900;color:#7ff0d0;white-space:nowrap;}',
      '.lm-pp-hint{font-size:11px;color:rgba(226,232,240,.55);}',
      '.lm-pp-count{font-size:12px;font-weight:800;color:#cfe;background:rgba(60,240,190,.15);border:1px solid rgba(60,240,190,.3);border-radius:8px;padding:4px 9px;white-space:nowrap;}',
      '.lm-pctl.danger{border-color:rgba(255,90,90,.5);background:rgba(255,90,90,.14);color:#ff9b9b;}',
      '.lm-pctl.danger:hover{background:rgba(255,90,90,.26);color:#fff;}',
      '.lm-pctl.done{border-color:rgba(120,160,255,.5);background:rgba(90,150,255,.16);color:#bcd3ff;}',
      '.lm-pctl.done:hover{background:rgba(90,150,255,.3);color:#fff;}',
      '.labyrinth-cell.lm-route-cell{position:relative!important;}',
      '.lm-route-tunnel{position:absolute;inset:0;pointer-events:none;z-index:2;}',
      '.lm-route-tunnel .lm-tb{position:absolute;background:rgba(60,240,190,.9);border-radius:3px;box-shadow:0 0 5px rgba(60,240,190,.75);}',
      '.lm-tb-up{left:50%;top:-2px;width:22%;height:56%;transform:translateX(-50%);}',
      '.lm-tb-down{left:50%;bottom:-2px;width:22%;height:56%;transform:translateX(-50%);}',
      '.lm-tb-left{top:50%;left:-2px;height:22%;width:56%;transform:translateY(-50%);}',
      '.lm-tb-right{top:50%;right:-2px;height:22%;width:56%;transform:translateY(-50%);}',
      '.lm-route-tunnel .lm-tb-c{position:absolute;left:50%;top:50%;width:22%;height:22%;transform:translate(-50%,-50%);background:rgba(60,240,190,.95);border-radius:50%;box-shadow:0 0 5px rgba(60,240,190,.8);}',
      '.lm-route-tunnel .lm-tb-c.end{width:40%;height:40%;background:#7cffdd;box-shadow:0 0 9px rgba(60,240,190,.95);}',
      '.labyrinth__personal-mine.lm-mine-slim{background:#171a28!important;border:1px solid rgba(255,255,255,.06)!important;border-radius:16px!important;}',
      '.labyrinth__personal-mine.lm-mine-slim .labyrinth__personal-mine-text{display:none!important;}',
      '.labyrinth__personal-mine.lm-mine-slim .labyrinth__personal-mine-head{margin-bottom:8px!important;}',
      '.labyrinth__personal-mine.lm-mine-slim .labyrinth__personal-mine-stats{margin:8px 0!important;}',
      '.labyrinth__personal-mine.lm-mine-slim .labyrinth__personal-mine-progress{margin:8px 0!important;}',
      '.lm-mine-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:8px;align-items:start;}',
      '.lm-mine-col{display:flex;flex-direction:column;gap:5px;min-width:0;}',
      '.lm-mine-col .button{width:100%!important;margin:0!important;}',
      '.lm-mine-col .labyrinth__personal-mine-upgrade,.lm-mine-col #labyrinthPersonalMineProgressTimer{font-size:11px!important;line-height:1.35!important;color:#d4e0f2!important;opacity:1!important;text-align:center;margin:0!important;background:rgba(255,255,255,.07)!important;border:1px solid rgba(255,255,255,.11)!important;border-radius:8px!important;padding:6px 8px!important;}',
      '.labyrinth__personal-mine.lm-mine-slim #labyrinthPersonalMineTitle{display:none!important;}',
      '.labyrinth__personal-mine.lm-mine-slim .labyrinth__personal-mine-label{font-size:21px!important;font-weight:900!important;color:#fff!important;text-transform:none!important;letter-spacing:.01em!important;opacity:1!important;}',
      '.labyrinth__personal-mine.lm-mine-slim .labyrinth__personal-mine-icon{display:none!important;}',
      '#lm-bguard{background:rgba(234,179,8,.14);color:#ffe16d;border:1px solid rgba(234,179,8,.34);}#lm-bguard:hover{background:rgba(234,179,8,.27);color:#fff;transform:translateY(-1px);}#lm-bguard.active{background:#a16207;color:#fff;border-color:#eab308;}',
      '#lm-bclub{background:rgba(168,85,247,.14);color:#d8b4fe;border:1px solid rgba(168,85,247,.34);}#lm-bclub:hover{background:rgba(168,85,247,.27);color:#fff;transform:translateY(-1px);}#lm-bclub.active{background:#6b21a8;color:#fff;border-color:#c084fc;}',
      '#lm-bhist{background:rgba(6,182,212,.14);color:#67e8f9;border:1px solid rgba(6,182,212,.34);}#lm-bhist:hover{background:rgba(6,182,212,.27);color:#fff;transform:translateY(-1px);}#lm-bhist.active{background:#0e7490;color:#fff;border-color:#22d3ee;}',
      '#lm-bc{width:40px;height:40px;border:none;border-radius:8px;padding:0;background:rgba(239,68,68,.95);color:#fff;font-size:22px;font-weight:800;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;box-shadow:0 8px 22px rgba(239,68,68,.22);}#lm-bc:hover{background:#f87171;}',
      '#lm-brefresh{height:40px;padding:0 15px;gap:7px;border-radius:8px;background:linear-gradient(135deg,#4a5ac7,#3a4ab0);color:#fff;font-size:11px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;cursor:pointer;display:flex;align-items:center;justify-content:center;flex-shrink:0;border:none;white-space:nowrap;box-shadow:0 2px 12px rgba(74,90,199,.4);transition:.15s;}',
      '#lm-brefresh:hover{background:linear-gradient(135deg,#5b6dd8,#4a5ac7);transform:translateY(-1px);}',
      '#lm-brefresh.loading{opacity:.6;cursor:not-allowed;}',
      '#lm-brefresh.loading .lm-ref-ico{display:inline-block;animation:lm-spin 1s linear infinite;}',
      '@keyframes lm-spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}',
      '#lm-hdr-right{display:flex;align-items:center;gap:8px;flex-shrink:0;margin-left:auto;}',
      '.lm-pop{display:none;position:fixed;top:0;left:0;z-index:9999999;background:rgba(15,23,42,.96);border:1px solid rgba(148,163,184,.22);border-radius:12px;padding:0;box-shadow:0 18px 52px rgba(0,0,0,.58);overflow:hidden;max-width:calc(100vw - 16px);backdrop-filter:blur(14px);}',
      '.lm-pop.on{display:flex;flex-direction:column;}',
      '#lm-gpop,#lm-cpop{width:min(380px,calc(100vw - 24px));max-height:460px;}',
      '#lm-fpop{width:min(360px,calc(100vw - 24px));padding:12px;}',
      '#lm-hpop{width:min(520px,calc(100vw - 24px));max-height:min(560px,80vh);}',
      '#lm-hpop .lm-pop-head{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-bottom:1px solid rgba(255,255,255,.08);flex-shrink:0;}',
      '#lm-hpop .lm-pop-title{font-size:12px;font-weight:800;color:#fff;text-transform:uppercase;letter-spacing:.04em;}',
      '#lm-hpop .lm-pop-link{font-size:10px;color:rgba(255,255,255,.4);text-decoration:none;}',
      '@media(max-width:600px){.lm-pop.on{left:8px!important;right:8px!important;width:auto!important;max-width:none!important;max-height:calc(100dvh - 130px);}}',
      '.lm-hist-body{overflow-y:auto;padding:10px;display:flex;flex-direction:column;gap:8px;}',
      '.lm-sc-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;margin-bottom:10px;}',
      '.lm-sc-card{padding:10px 12px;border-radius:12px;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.08);}',
      '.lm-sc-label{font-size:10px;text-transform:uppercase;letter-spacing:.04em;color:rgba(255,255,255,.45);margin-bottom:4px;}',
      '.lm-sc-val{font-size:20px;font-weight:800;color:#fff;line-height:1.1;margin-bottom:3px;}',
      '.lm-sc-good{color:#6ee786;}.lm-sc-bad{color:#ff8b8b;}.lm-sc-trap .lm-sc-val{margin-bottom:3px}.lm-sc-trap-row{display:flex;align-items:center;gap:5px;flex-wrap:wrap;margin-top:3px}.lm-sc-rooms{font-size:10px;color:rgba(255,133,133,.75);line-height:1.3}.lm-sc-hint{font-size:10px;color:rgba(255,255,255,.4)}.lm-sc-badge{display:inline-flex;align-items:center;padding:1px 6px;border-radius:999px;font-size:10px;font-weight:800;line-height:1.6;background:linear-gradient(135deg,rgba(255,100,100,.22),rgba(255,60,60,.12));border:1px solid rgba(255,100,100,.35);color:#ff9e9e;white-space:nowrap}',
      '.lm-sc-sub{font-size:10px;color:rgba(255,255,255,.4);line-height:1.3;}',
      '.lm-hist-stats{padding:10px;border-radius:12px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.07);}',
      '.lm-hist-stats-ttl{font-size:11px;color:rgba(255,255,255,.55);text-transform:uppercase;letter-spacing:.04em;margin-bottom:8px;}',
      '.lm-hist-stats-list{display:flex;flex-direction:column;gap:4px;}',
      '.lm-hs-item{display:flex;align-items:center;gap:8px;padding:5px 0;}',
      '.lm-hs-ico{width:24px;height:24px;border-radius:5px;display:flex;align-items:center;justify-content:center;font-size:13px;flex-shrink:0;}',
      '.lm-hs-name{font-size:12px;color:rgba(255,255,255,.85);width:90px;flex-shrink:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}',
      '.lm-hs-bar-wrap{flex:1;min-width:30px;height:6px;border-radius:3px;background:rgba(255,255,255,.08);overflow:hidden;}',
      '.lm-hs-bar{height:6px;border-radius:3px;transition:width .3s;}',
      '.lm-hs-cnt{font-size:12px;font-weight:600;color:#fff;min-width:58px;text-align:right;white-space:nowrap;}',
      '.lm-hs-pct{font-size:10px;font-weight:400;color:rgba(255,255,255,.45);}',
      '.lm-hist-fresh{font-size:11px;color:rgba(255,255,255,.45);padding:6px 10px;border-radius:8px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.07);margin-bottom:8px;line-height:1.5;}',
      '.lm-hist-fresh b{color:rgba(255,255,255,.8);}',
      '.lm-hist-fresh--warn{color:#ffd66b;border-color:rgba(255,214,107,.25);background:rgba(255,214,107,.07);}',
      '.lm-hist-rows-head{font-size:11px;font-weight:700;color:rgba(255,255,255,.5);text-transform:uppercase;letter-spacing:.04em;padding:2px 0;}',
      '.lm-hist-rows{display:flex;flex-direction:column;gap:5px;}',
      '.lm-hr{display:flex;align-items:center;gap:8px;padding:7px 9px;border-radius:10px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.06);}',
      '.lm-hr-ico{width:32px;height:32px;border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:15px;flex-shrink:0;border:1px solid transparent;}',
      '.lm-hr-main{flex:1;min-width:0;}',
      '.lm-hr-desc{font-size:12px;font-weight:700;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}',
      '.lm-hr-meta{font-size:10px;color:rgba(255,255,255,.42);margin-top:1px;}',
      '.lm-hr-meta b{color:rgba(255,255,255,.7);}',
      '.lm-hr-amt{font-size:12px;font-weight:800;flex-shrink:0;white-space:nowrap;}',
      '.lm-ha-plus{color:#6ee786;}.lm-ha-minus{color:#ff8b8b;}',
      '.lm-hist-empty{padding:18px;text-align:center;font-size:12px;color:rgba(255,255,255,.4);}',
      '.lm-hist-pag{display:flex;align-items:center;justify-content:center;gap:4px;padding:8px 0 2px;border-top:1px solid rgba(255,255,255,.07);flex-wrap:wrap;}',
      '.lm-hp-nav{padding:4px 10px;border:none;border-radius:7px;background:rgba(6,182,212,.2);color:#67e8f9;font-size:11px;font-weight:700;cursor:pointer;}',
      '.lm-hp-nav:disabled{opacity:.3;cursor:not-allowed;}',
      '.lm-hp-btn{width:28px;height:28px;border:none;border-radius:6px;background:rgba(255,255,255,.08);color:rgba(255,255,255,.7);font-size:11px;font-weight:700;cursor:pointer;}',
      '.lm-hp-btn:hover{background:rgba(255,255,255,.18);color:#fff;}',
      '.lm-hp-cur{background:rgba(6,182,212,.35)!important;color:#fff!important;}',
      '.lm-hp-dots{font-size:11px;color:rgba(255,255,255,.3);padding:0 3px;}',
      '.lm-spop-head{display:flex;align-items:center;justify-content:space-between;padding:10px 14px;border-bottom:1px solid rgba(255,255,255,.08);flex-shrink:0;}',
      '.lm-spop-title{font-size:12px;font-weight:800;color:#fff;text-transform:uppercase;letter-spacing:.04em;}',
      '.lm-spop-count{font-size:11px;color:rgba(255,255,255,.5);margin-left:6px;}',
      '.lm-spop-list{overflow-y:auto;padding:8px;}',
      '.lm-si{display:flex;align-items:center;gap:10px;padding:8px 10px;border-radius:10px;cursor:pointer;transition:background .12s;border:1px solid transparent;}',
      '.lm-si:hover{background:rgba(255,255,255,.06);border-color:rgba(255,255,255,.08);}',
      '.lm-si-img{width:38px;height:54px;border-radius:6px;object-fit:cover;flex-shrink:0;}',
      '.lm-si-info{min-width:0;flex:1;}',
      '.lm-si-name{font-size:12px;font-weight:700;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}',
      '.lm-si-room{font-size:11px;color:rgba(255,255,255,.65);margin-top:2px;}',
      '.lm-si-date{font-size:10px;color:rgba(255,255,255,.4);margin-top:2px;}',
      '.lm-si-nav{font-size:16px;color:rgba(255,255,255,.4);flex-shrink:0;}',
      '.lm-spop-loading{padding:20px;text-align:center;color:rgba(255,255,255,.5);font-size:12px;}',
      '#lm-fpop-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px;}',
      '#lm-fpop-title{font-size:12px;font-weight:800;color:#fff;text-transform:uppercase;letter-spacing:.04em;}',
      '#lm-fpop-clear{padding:3px 8px;border:none;border-radius:6px;background:#b02a59;color:#fff;font-size:10px;font-weight:700;cursor:pointer;}',
      '#lm-fpop-grid{display:flex;flex-wrap:wrap;gap:5px;}',
      '.lm-fi{display:flex;align-items:center;gap:4px;font-size:11px;color:rgba(255,255,255,.7);cursor:pointer;padding:4px 8px;border-radius:8px;border:1px solid rgba(255,255,255,.08);background:rgba(255,255,255,.04);transition:all .12s;}',
      '.lm-fi:hover{background:rgba(255,255,255,.1);color:#fff;}',
      '.lm-fi.sel{border-color:rgba(255,255,255,.45);background:rgba(255,255,255,.16);color:#fff;font-weight:700;}',
      '.lm-fi-alt{border-color:rgba(255,214,107,.25)!important;background:rgba(255,214,107,.08)!important;}',
      '.lm-fi-alt.sel{border-color:rgba(255,214,107,.6)!important;background:rgba(255,214,107,.22)!important;}',
      '.lm-fid{width:14px;height:14px;border-radius:3px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:10px;}',
      '#lm-mbody{flex:1;min-height:0;overflow:hidden;position:relative;cursor:grab;background:transparent;touch-action:none;}',
      '#lm-mbody.dr{cursor:grabbing;}#lm-fc{position:absolute;top:0;left:0;}',
      '#lm-info{position:absolute;z-index:3;bottom:14px;left:50%;transform:translateX(-50%);max-width:calc(100% - 148px);padding:7px 14px;border-radius:999px;background:rgba(7,11,22,.82);border:1px solid rgba(148,163,184,.22);color:rgba(226,232,240,.82);font-size:11px;font-weight:700;pointer-events:none;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;box-shadow:0 10px 24px rgba(0,0,0,.28);backdrop-filter:blur(10px);}',
      '#lm-zbtns{position:absolute;z-index:3;right:14px;top:14px;display:flex;flex-direction:column;gap:8px;}',
      '.lm-zb{width:42px;height:42px;border:1px solid rgba(148,163,184,.26);border-radius:8px;background:rgba(15,23,42,.78);color:#fff;font-size:20px;font-weight:800;cursor:pointer;box-shadow:0 10px 26px rgba(0,0,0,.26);backdrop-filter:blur(10px);transition:background .14s,transform .14s,border-color .14s;}',
      '.lm-zb:hover{background:rgba(37,99,235,.42);border-color:rgba(147,197,253,.5);transform:translateY(-1px);}',
      '#lm-legend-bar{position:relative;z-index:4;display:flex;flex-wrap:wrap;gap:5px 7px;padding:8px 12px calc(8px + env(safe-area-inset-bottom));background:linear-gradient(0deg,rgba(30,33,52,.94),rgba(22,24,40,.9));border-top:1px solid rgba(255,255,255,.08);box-shadow:0 -12px 32px rgba(0,0,0,.28);flex-shrink:0;align-items:flex-start;overflow:hidden;backdrop-filter:blur(14px);}',
      '.lm-lb-item{display:flex;align-items:center;gap:5px;min-height:24px;padding:3px 7px;border-radius:8px;background:rgba(255,255,255,.035);border:1px solid rgba(255,255,255,.06);font-size:10px;color:rgba(226,232,240,.72);white-space:nowrap;flex:0 0 auto;}',
      '.lm-lb-ico{width:16px;height:16px;border-radius:4px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:10px;}',
      '@media(max-width:860px){#lm-mhdr{padding-left:10px;padding-right:10px;}#lm-mhdr-btns{margin:0 -10px;padding:0 10px 2px;}#lm-b0,#lm-b1,#lm-bf,#lm-bpath,#lm-bguard,#lm-bclub,#lm-bhist{min-height:42px;padding:9px 12px;font-size:11px;flex:0 0 auto;}#lm-brefresh{height:40px;}#lm-zbtns{right:10px;top:10px;}.lm-zb{width:44px;height:44px;}#lm-info{bottom:12px;max-width:calc(100% - 116px);font-size:10px;padding:7px 11px;}#lm-legend-bar{gap:4px 6px;padding-left:10px;padding-right:10px;}.lm-lb-item{font-size:9px;padding:3px 6px;}}',
      '@media(max-width:520px){#lm-mttl{font-size:13px;}#lm-brefresh{width:42px;padding:0;font-size:0;}#lm-brefresh .lm-ref-ico{font-size:15px;}#lm-bc{width:42px;height:42px;}#lm-zbtns{top:auto;bottom:14px;right:10px;flex-direction:row;}#lm-info{left:10px;right:108px;bottom:14px;transform:none;max-width:none;text-align:left;line-height:44px;padding-top:0;padding-bottom:0;height:44px;box-sizing:border-box;}.lm-lb-item span{max-width:72px;overflow:hidden;text-overflow:ellipsis;}}',
      '#lm-tooltip{position:fixed;z-index:9999999;pointer-events:none;background:rgba(10,12,24,.95);border:1px solid rgba(255,255,255,.15);border-radius:10px;padding:8px 12px;color:#fff;font-size:12px;line-height:1.5;display:none;min-width:210px;max-width:260px;box-shadow:0 4px 16px rgba(0,0,0,.5);}',
      '#lm-tooltip b{display:block;font-size:13px;margin-bottom:2px;}',
      '#lm-tooltip .tt-desc{color:rgba(255,255,255,.75);font-size:11px;margin-bottom:3px;}',
      '#lm-tooltip .tt-guard{color:#ffd700;font-size:11px;margin-bottom:2px;font-weight:600;}',
      '#lm-tooltip .tt-coord{color:rgba(255,255,255,.5);font-size:11px;white-space:nowrap;}',
      '#lm-tooltip .tt-alt{color:#ffd66b;font-size:11px;margin-bottom:2px;}',
      '#lm-tooltip .tt-obj{font-size:11px;font-weight:700;margin-bottom:3px;padding:2px 6px;border-radius:4px;}',
      '#lm-tooltip .tt-trap{color:#ff8b8b;background:rgba(255,100,100,.12);}',
      '#lm-tooltip .tt-gift{color:#ffd66b;background:rgba(255,214,107,.12);}',
      '#lm-tooltip .tt-var{color:#c084fc;background:rgba(192,132,252,.1);}',
      '#lm-confirm{display:none;position:fixed;inset:0;z-index:9999999;background:rgba(0,0,0,.7);align-items:center;justify-content:center;}',
      '#lm-confirm.on{display:flex;}',
      '#lm-cbox{background:#1a1d2a;border:1px solid rgba(255,255,255,.15);border-radius:18px;padding:24px 28px;max-width:340px;width:90%;color:#fff;}',
      '#lm-cbox h3{margin:0 0 10px;font-size:16px;font-weight:800;}',
      '#lm-cbox p{margin:0 0 8px;font-size:13px;color:rgba(255,255,255,.7);line-height:1.5;}',
      '#lm-cbox .warn{color:#ffd66b;font-size:12px;margin-bottom:8px;}',
      '#lm-cbox .time{font-size:12px;color:rgba(255,255,255,.5);margin-bottom:16px;}',
      '#lm-cbtns{display:flex;gap:10px;}',
      '#lm-cyes{flex:1;padding:10px;border:none;border-radius:10px;background:#4a5ac7;color:#fff;font-size:14px;font-weight:700;cursor:pointer;}',
      '#lm-cno{flex:1;padding:10px;border:none;border-radius:10px;background:rgba(255,255,255,.08);color:#fff;font-size:14px;font-weight:700;cursor:pointer;}',
      '#lm-boost-confirm{display:none;position:fixed;inset:0;z-index:9999999;background:rgba(0,0,0,.75);align-items:center;justify-content:center;}',
      '#lm-boost-confirm.on{display:flex;}',
      '#lm-boost-box{background:linear-gradient(135deg,#1a1d2a,#0f1220);border:1px solid rgba(255,255,255,.15);border-radius:20px;padding:28px;max-width:320px;width:90%;color:#fff;text-align:center;}',
      '#lm-boost-box .lm-boost-conf-ico{font-size:42px;margin-bottom:12px;line-height:1;}',
      '#lm-boost-box .lm-boost-conf-title{font-size:18px;font-weight:800;margin-bottom:8px;}',
      '#lm-boost-box .lm-boost-conf-desc{font-size:13px;color:rgba(255,255,255,.65);margin-bottom:20px;line-height:1.5;}',
      '#lm-boost-box .lm-boost-conf-btns{display:flex;gap:10px;}',
      '#lm-boost-conf-yes{flex:1;padding:12px;border:none;border-radius:12px;background:#4a5ac7;color:#fff;font-size:14px;font-weight:700;cursor:pointer;}',
      '#lm-boost-conf-no{flex:1;padding:12px;border:none;border-radius:12px;background:rgba(255,255,255,.08);color:#fff;font-size:14px;font-weight:700;cursor:pointer;}',
      '#lm-toast{position:fixed;top:14px;left:50%;transform:translateX(-50%);z-index:99999999;pointer-events:none;display:flex;flex-direction:column;align-items:center;gap:6px;min-width:280px;max-width:min(480px,90vw);}',
      '.lm-toast-msg{width:100%;padding:10px 16px;border-radius:12px;background:linear-gradient(135deg,#1a2a4a,#0d1a30);border:1px solid rgba(74,144,226,.4);box-shadow:0 4px 20px rgba(0,0,0,.5);color:#fff;font-size:13px;font-weight:600;line-height:1.4;opacity:0;transform:translateY(-12px);transition:opacity .25s,transform .25s;}',
      '.lm-toast-msg.on{opacity:1;transform:translateY(0);}',
      '.lm-toast-msg b{color:#67e8f9;display:block;margin-bottom:2px;}',
      '#lm-confirm2{display:none;position:fixed;inset:0;z-index:9999999;background:rgba(0,0,0,.7);align-items:center;justify-content:center;}',
      '#lm-confirm2.on{display:flex;}',
      '#lm-cbox2{background:#1a1d2a;border:1px solid rgba(255,255,255,.15);border-radius:18px;padding:24px 28px;max-width:340px;width:90%;color:#fff;}',
      '#lm-cbox2 h3{margin:0 0 10px;font-size:16px;font-weight:800;}',
      '#lm-cbox2 p{margin:0 0 8px;font-size:13px;color:rgba(255,255,255,.7);line-height:1.5;}',
      '#lm-cbox2 .warn{color:#ffd66b;font-size:12px;margin-bottom:8px;}',
      '#lm-cbox2 .time{font-size:12px;color:rgba(255,255,255,.5);margin-bottom:16px;}',
      '#lm-cbtns2{display:flex;gap:10px;}',
      '#lm-cyes2{flex:1;padding:10px;border:none;border-radius:10px;background:#4a5ac7;color:#fff;font-size:14px;font-weight:700;cursor:pointer;}',
      '#lm-cno2{flex:1;padding:10px;border:none;border-radius:10px;background:rgba(255,255,255,.08);color:#fff;font-size:14px;font-weight:700;cursor:pointer;}',
      '.labyrinth-cell.lm-known{position:relative!important;}',
      '.lm-cell-icon{position:absolute!important;inset:0!important;display:flex!important;align-items:center!important;justify-content:center!important;font-size:clamp(5px,1.3vw,11px)!important;line-height:1!important;pointer-events:none!important;z-index:3!important;}',
      '.labyrinth-cell.lm-known:not(.labyrinth-cell--visited):not(.labyrinth-cell--available):not(.labyrinth-cell--current){background:rgba(255,255,255,.07)!important;opacity:0.85!important;}',
      '#lm-gamestats{display:grid;grid-template-columns:repeat(auto-fit,minmax(140px,1fr));gap:10px;margin:0 0 4px 0;padding:14px;background:#171a28;border:1px solid rgba(255,255,255,.06);border-radius:16px;}',
      '.labyrinth__top-tabs{background:#171a28!important;border:1px solid rgba(255,255,255,.06)!important;border-radius:16px!important;padding:8px!important;margin-bottom:10px!important;}',
      '.labyrinth__top-tabs .ncard__tabs{width:100%!important;justify-content:stretch!important;}',
      '.labyrinth__top-tabs .ncard__tabs-btns{width:100%!important;flex-wrap:nowrap!important;gap:6px!important;}',
      '.labyrinth__top-tabs .ncard__tabs-btn{flex:1 1 0!important;min-width:0!important;justify-content:center!important;text-align:center!important;padding:9px 10px!important;background:linear-gradient(135deg,rgba(34,46,82,.5),rgba(16,22,42,.55))!important;border:1px solid rgba(120,160,255,.16)!important;border-radius:10px!important;color:#cdd9ee!important;font-weight:700!important;font-size:12px!important;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:border-color .15s,transform .15s,background .15s!important;}',
      '.labyrinth__top-tabs .ncard__tabs-btn:hover{border-color:rgba(120,160,255,.42)!important;color:#fff!important;transform:translateY(-1px);}',
      '.labyrinth__top-tabs .ncard__tabs-btn.is-active{background:linear-gradient(135deg,#4a5ac7,#3a4ab0)!important;border-color:#5b6dd8!important;color:#fff!important;box-shadow:0 2px 12px rgba(74,90,199,.35)!important;}',
      '.labyrinth__top-tabs .labyrinth-music-switch{order:-1!important;flex:0 0 auto!important;width:42px;height:38px;border-radius:10px!important;background:rgba(34,46,82,.55)!important;border:2px solid rgba(120,160,255,.55)!important;color:#cdd9ee!important;font-size:15px!important;}',
      '.labyrinth__top-tabs .labyrinth-music-switch.is-off{border-color:#ff4d4d!important;color:#ff5b5b!important;background:rgba(255,60,60,.14)!important;}',
      '@media(max-width:760px){.labyrinth__top-tabs .ncard__tabs-btns{flex-wrap:wrap!important;}.labyrinth__top-tabs .ncard__tabs-btn{flex:1 1 auto!important;font-size:11px!important;padding:8px!important;}}',
      '#labyrinthBoosts{background:#171a28!important;border:1px solid rgba(255,255,255,.06)!important;border-radius:16px!important;padding:14px 16px!important;margin-bottom:10px!important;}',
      '#labyrinthBoosts .labyrinth__boosts-title{display:none!important;}',
      '.labyrinth__body{margin-top:0!important;}',
      '#labyrinthBoosts .labyrinth__boost-btn{background:linear-gradient(135deg,rgba(34,46,82,.5),rgba(16,22,42,.55))!important;border:1px solid rgba(120,160,255,.16)!important;border-radius:14px!important;box-shadow:0 2px 10px rgba(0,0,0,.2)!important;transition:border-color .15s,transform .15s!important;}',
      '#labyrinthBoosts .labyrinth__boost-btn:not([disabled]):hover{border-color:rgba(120,160,255,.42)!important;transform:translateY(-1px);}',
      '#lm-gamestats .labyrinth__stat{background:linear-gradient(135deg,rgba(34,46,82,.55),rgba(16,22,42,.6))!important;border:1px solid rgba(120,160,255,.18)!important;border-radius:14px!important;padding:13px 16px!important;display:flex!important;flex-direction:column!important;gap:5px!important;box-shadow:0 3px 14px rgba(0,0,0,.28)!important;margin:0!important;min-width:0!important;cursor:default!important;transform:none!important;}',
      '#lm-gamestats .labyrinth__stat:hover{border-color:rgba(120,160,255,.18)!important;transform:none!important;background:linear-gradient(135deg,rgba(34,46,82,.55),rgba(16,22,42,.6))!important;box-shadow:0 3px 14px rgba(0,0,0,.28)!important;}',
      '#lm-gamestats .labyrinth__stat-label{font-size:10px!important;letter-spacing:.07em!important;text-transform:uppercase!important;color:#8093b6!important;font-weight:800!important;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}',
      '#lm-gamestats .labyrinth__stat-value{font-size:20px!important;font-weight:800!important;color:#eaf1ff!important;line-height:1.15!important;}',
      '.labyrinth__header.lm-hdr-stretched .labyrinth__title-block{flex:1 1 auto!important;max-width:none!important;}',
      '.labyrinth__header.lm-hdr-stretched .labyrinth__subtitle{max-width:none!important;}',
      '.labyrinth__header.lm-hdr-stretched{display:none!important;}',
      '@media (max-width:640px){#lm-gamestats{grid-template-columns:repeat(2,minmax(0,1fr));}#lm-gamestats .labyrinth__stat-value{font-size:18px!important;}}',
      '#lm-topdeck{display:grid;grid-template-columns:minmax(0,1fr) 420px;gap:20px;align-items:stretch;margin:24px 24px 20px;padding:18px;border:1px solid rgba(255,255,255,.1);border-radius:22px;background:linear-gradient(135deg,rgba(24,27,44,.55),rgba(14,16,27,.6));box-shadow:0 10px 34px rgba(0,0,0,.28);}',
      '#lm-topdeck .lm-td-left{display:flex;flex-direction:column;gap:18px;min-width:0;}',
      '#lm-topdeck .lm-td-right{display:flex;flex-direction:column;min-width:0;}',
      '#lm-topdeck .lm-td-left>#lm-wrap{margin-bottom:0;}',
      '#lm-topdeck .lm-td-left>#lm-gamestats{margin:0;flex:1 1 auto;align-content:start;}',
      '#lm-topdeck .lm-td-right>#labyrinthPersonalMine{height:100%;box-sizing:border-box;margin:0;border-radius:16px;}',
      '#lm-topdeck .lm-td-left>#lm-wrap{border-radius:16px;}',
      '#lm-mine-ph{display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;gap:14px;height:100%;min-height:220px;box-sizing:border-box;padding:30px 24px;border-radius:16px;background:radial-gradient(circle at top right,rgba(255,196,87,.10),transparent 46%),linear-gradient(135deg,rgba(30,33,52,.9),rgba(16,18,31,.92));border:1px dashed rgba(255,255,255,.16);}',
      '#lm-mine-ph .lm-mph-ico{width:66px;height:66px;border-radius:20px;display:flex;align-items:center;justify-content:center;font-size:32px;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.1);box-shadow:0 8px 22px rgba(0,0,0,.25);}',
      '#lm-mine-ph .lm-mph-title{font-size:18px;font-weight:800;color:#fff;}',
      '#lm-mine-ph .lm-mph-text{font-size:13px;line-height:1.55;color:rgba(255,255,255,.6);max-width:280px;}',
      '@media (max-width:1280px){#lm-topdeck{grid-template-columns:minmax(0,1fr) 360px;}}',
      '.animesss-labyrinth .labyrinth__body.lm-body-deck{margin:0 24px;padding:20px;gap:20px;border:1px solid rgba(255,255,255,.1);border-radius:22px;background:linear-gradient(135deg,rgba(24,27,44,.55),rgba(14,16,27,.6));box-shadow:0 10px 34px rgba(0,0,0,.28);}',
      '.animesss-labyrinth .labyrinth__body.lm-body-deck .labyrinth__tips{display:none!important;}',
      '#lm-botdeck{margin:20px 24px 0;padding:20px;border:1px solid rgba(255,255,255,.1);border-radius:22px;background:linear-gradient(135deg,rgba(24,27,44,.55),rgba(14,16,27,.6));box-shadow:0 10px 34px rgba(0,0,0,.28);}',
      '#lm-botdeck>.labyrinth-global-stats{margin:0!important;padding:0!important;border:none!important;background:transparent!important;box-shadow:none!important;}',
      '.animesss-labyrinth .animesss-chat.animesss-chat--labyrinth{margin:20px 24px 0!important;border:1px solid rgba(255,255,255,.1)!important;border-radius:20px!important;background:linear-gradient(135deg,rgba(24,27,44,.72),rgba(14,16,27,.8))!important;box-shadow:0 10px 34px rgba(0,0,0,.28)!important;}',
      '.animesss-labyrinth .labyrinth-legend-full{margin:20px 24px 24px!important;padding:20px!important;border:1px solid rgba(255,255,255,.1)!important;border-radius:20px!important;background:linear-gradient(135deg,rgba(24,27,44,.55),rgba(14,16,27,.6))!important;box-shadow:0 10px 34px rgba(0,0,0,.28)!important;}',
      '.animesss-labyrinth .labyrinth-legend-full__title{color:#fff!important;font-weight:800!important;}',
      '.animesss-labyrinth .labyrinth-legend-full__grid>div{padding:7px 10px!important;border-radius:10px!important;background:rgba(255,255,255,.04)!important;border:1px solid rgba(255,255,255,.06)!important;color:rgba(255,255,255,.82)!important;}',
      '@media(max-width:640px){.animesss-labyrinth .animesss-chat.animesss-chat--labyrinth{margin-left:14px!important;margin-right:14px!important;}.animesss-labyrinth .labyrinth-legend-full{margin-left:14px!important;margin-right:14px!important;padding:14px!important;}}',
      '.labyrinth__arena.lm-arena-styled{background:linear-gradient(135deg,rgba(30,33,52,.96),rgba(16,18,31,.98))!important;padding-top:56px!important;}',
      '#lm-arena-bar{position:absolute;top:0;left:0;right:0;z-index:6;display:flex;align-items:center;justify-content:space-between;gap:10px;padding:11px 14px;box-sizing:border-box;}',
      '#lm-arena-bar .lm-ab-right{display:flex;align-items:center;gap:10px;}',
      '#lm-cleanbtn,#lm-pbtn{display:none;}',
      'html.lm-btns-header #lm-cleanbtn,html.lm-btns-header #lm-pbtn{display:inline-flex;align-items:center;}',
      'html.lm-btns-header #lm-arena-bar{display:none;}',
      'html.lm-btns-header .labyrinth__arena.lm-arena-styled{padding-top:14px!important;}',
      '#lm-arena-bar button{min-height:38px;padding:8px 15px;border-radius:10px;border:none;cursor:pointer;font-size:11px;font-weight:800;white-space:nowrap;transition:.15s;line-height:1;}',
      '#lm-ab-view{background:rgba(255,255,255,.08);color:rgba(255,255,255,.72);border:1px solid rgba(255,255,255,.14)!important;box-shadow:0 4px 16px rgba(0,0,0,.22);}',
      '#lm-ab-view:hover{background:rgba(255,255,255,.16);color:#fff;transform:translateY(-1px);}',
      '#lm-ab-view.active{background:rgba(255,214,107,.18);color:#ffd66b;border-color:rgba(255,214,107,.45)!important;}',
      '#lm-ab-map{background:linear-gradient(135deg,#b02a59,#8a1f45);color:#fff;box-shadow:0 4px 16px rgba(176,42,89,.4);}',
      '#lm-ab-map:hover{background:linear-gradient(135deg,#c03468,#b02a59);transform:translateY(-1px);}',
      '#lm-ab-timer{display:flex;flex-direction:column;justify-content:center;align-items:center;min-height:38px;min-width:82px;padding:6px 14px;border-radius:10px;background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.12);box-sizing:border-box;box-shadow:0 4px 16px rgba(0,0,0,.22);}',
      '#lm-ab-timer .lm-abt-val{font-size:15px;font-weight:800;color:#fff;line-height:1;white-space:nowrap;}',
      '#lm-ab-timer .lm-abt-lbl{font-size:8px;letter-spacing:.05em;text-transform:uppercase;color:rgba(255,255,255,.5);margin-top:3px;line-height:1;white-space:nowrap;}',
      '@media (max-width:640px){#lm-arena-bar{padding:9px 10px;}#lm-ab-timer{min-width:64px;padding:5px 9px;}.labyrinth__arena.lm-arena-styled{padding-top:52px!important;}}',
      '.labyrinth__arena.lm-arena-styled{position:relative;}',
      '#lm-ab-emission{display:none;flex-direction:column;justify-content:center;align-items:center;min-height:38px;min-width:82px;padding:6px 12px;border-radius:10px;background:rgba(255,176,115,.1);border:1px solid rgba(255,176,115,.28);box-sizing:border-box;box-shadow:0 4px 16px rgba(0,0,0,.22);}',
      '#lm-ab-emission.is-active{background:rgba(255,176,115,.2);border-color:rgba(255,176,115,.5);}',
      '#lm-ab-emission .lm-abe-val{font-size:14px;font-weight:800;color:#ffcfa3;line-height:1;white-space:nowrap;}',
      '#lm-ab-emission .lm-abe-lbl{font-size:8px;letter-spacing:.04em;text-transform:uppercase;color:rgba(255,207,163,.7);margin-top:3px;line-height:1;white-space:nowrap;}',
      'html.lm-em-onmap:not(.lm-btns-header) #lm-ab-emission{display:flex;}',
      'html.lm-em-onmap:not(.lm-btns-header) #lm-mi-emission-val,html.lm-em-onmap:not(.lm-btns-header) #lm-mi-emission-lbl,html.lm-em-onmap:not(.lm-btns-header) #lm-mi-emission-ico{display:none;}',
      '#lm-em-refresh{position:absolute;inset:0;z-index:20;display:none;align-items:center;justify-content:center;padding:16px;background:rgba(8,10,18,.74);backdrop-filter:blur(2px);-webkit-backdrop-filter:blur(2px);}',
      '#lm-em-refresh .lm-emr-box{max-width:340px;width:100%;text-align:center;padding:22px 20px;border-radius:16px;background:linear-gradient(135deg,rgba(30,22,18,.96),rgba(18,14,12,.96));border:1px solid rgba(255,176,115,.4);box-shadow:0 14px 44px rgba(0,0,0,.5);}',
      '#lm-em-refresh .lm-emr-ico{font-size:34px;line-height:1;margin-bottom:8px;}',
      '#lm-em-refresh .lm-emr-title{font-size:17px;font-weight:800;color:#ffcfa3;margin-bottom:6px;}',
      '#lm-em-refresh .lm-emr-sub{font-size:12px;color:rgba(255,255,255,.62);line-height:1.4;margin-bottom:16px;}',
      '#lm-em-refresh .lm-emr-btns{display:flex;gap:10px;justify-content:center;}',
      '#lm-em-refresh button{min-height:40px;padding:9px 18px;border-radius:10px;border:none;cursor:pointer;font-size:13px;font-weight:800;transition:.15s;}',
      '#lm-em-refresh .lm-emr-go{background:linear-gradient(135deg,#c86a2a,#a8521f);color:#fff;box-shadow:0 4px 16px rgba(200,106,42,.4);}',
      '#lm-em-refresh .lm-emr-go:hover{transform:translateY(-1px);}',
      '#lm-em-refresh .lm-emr-x{background:rgba(255,255,255,.08);color:rgba(255,255,255,.7);border:1px solid rgba(255,255,255,.14)!important;}',
    ].join('\n');
    document.head.appendChild(style);
  }

  function _lmMoveGameStats(wrap, arenaEl) {
    try {
      var ids = ['labyrinthTodaySteps', 'labyrinthRemainingSteps', 'labyrinthTotalSteps', 'labyrinthBank'];
      var _st = document.getElementById('labyrinthStatusText');
      if (_st && _st.closest) { var _sc = _st.closest('.labyrinth__stat'); if (_sc) _sc.style.display = 'none'; }
      var bar = document.getElementById('lm-gamestats');
      if (!bar) {
        bar = document.createElement('div'); bar.id = 'lm-gamestats';
        if (arenaEl && arenaEl.parentNode) arenaEl.parentNode.insertBefore(bar, arenaEl);
        else if (wrap && wrap.parentNode) wrap.parentNode.insertBefore(bar, wrap.nextSibling);
        else return;
      }
      ids.forEach(function(id) {
        var span = document.getElementById(id);
        if (!span || !span.closest) return;
        var card = span.closest('.labyrinth__stat');
        if (card && card.parentNode !== bar) bar.appendChild(card);
      });
      var hdr = document.querySelector('.labyrinth__header');
      if (hdr) hdr.classList.add('lm-hdr-stretched');
      _lmPlaceTimerStat();
    } catch(e) {}
  }

  function _lmDeckDesktop(){ return window.innerWidth > 980; }
  function _lmMineVisible(){
    var m = document.getElementById('labyrinthPersonalMine');
    if (!m || m.style.display === 'none') return false;
    try { return window.getComputedStyle(m).display !== 'none'; } catch(e) { return true; }
  }
  function _lmDeckMineToggle(){
    var deck = document.getElementById('lm-topdeck'); if (!deck) return;
    var ph = document.getElementById('lm-mine-ph'); if (!ph) return;
    ph.style.display = _lmMineVisible() ? 'none' : 'flex';
  }
  function _lmBuildTopDeck() {
    try {
      var body = document.querySelector('.labyrinth__body');
      var left = document.querySelector('.labyrinth__left');
      var right = document.querySelector('.labyrinth__right');
      if (!body || !left || !right) return;
      var wrap = document.getElementById('lm-wrap');
      var stats = document.getElementById('lm-gamestats');
      var mine = document.getElementById('labyrinthPersonalMine');
      var deck = document.getElementById('lm-topdeck');
      if (!_lmDeckDesktop()) {
        if (deck) {
          var arena = left.querySelector('.labyrinth__arena');
          if (wrap) left.insertBefore(wrap, left.firstChild);
          if (stats) { if (wrap) left.insertBefore(stats, wrap.nextSibling); else if (arena) left.insertBefore(stats, arena); else left.appendChild(stats); }
          if (mine) right.insertBefore(mine, right.firstChild);
          if (deck.parentNode) deck.parentNode.removeChild(deck);
        }
        return;
      }
      if (!deck) {
        deck = document.createElement('div'); deck.id = 'lm-topdeck';
        deck.innerHTML = '<div class="lm-td-left"></div><div class="lm-td-right"></div>';
        if (body.parentNode) body.parentNode.insertBefore(deck, body);
      }
      var dl = deck.querySelector('.lm-td-left');
      var dr = deck.querySelector('.lm-td-right');
      if (wrap && wrap.parentNode !== dl) dl.appendChild(wrap);
      if (stats && stats.parentNode !== dl) dl.appendChild(stats);
      if (mine && mine.parentNode !== dr) dr.appendChild(mine);
      var ph = document.getElementById('lm-mine-ph');
      if (!ph) {
        ph = document.createElement('div'); ph.id = 'lm-mine-ph';
        ph.innerHTML = '<div class="lm-mph-ico">⛏</div><div class="lm-mph-title">Персональная шахта</div><div class="lm-mph-text">У тебя пока нет шахты. Найди её в лабиринте — и она появится здесь.</div>';
      }
      if (ph.parentNode !== dr) dr.appendChild(ph);
      _lmDeckMineToggle();
    } catch (e) {}
  }

  function _lmBuildBotDeck() {
    try {
      var body = document.querySelector('.labyrinth__body');
      var left = document.querySelector('.labyrinth__left');
      var right = document.querySelector('.labyrinth__right');
      var stats = document.querySelector('.labyrinth-global-stats');
      if (!body || !stats) return;
      var deck = document.getElementById('lm-botdeck');
      if (!_lmDeckDesktop()) {
        body.classList.remove('lm-body-deck');
        if (deck) {
          var tips = right && right.querySelector('.labyrinth__tips');
          if (right && tips) right.insertBefore(stats, tips.nextSibling);
          else if (right) right.appendChild(stats);
          else if (left) left.appendChild(stats);
          if (deck.parentNode) deck.parentNode.removeChild(deck);
        }
        return;
      }
      if (!deck) {
        deck = document.createElement('div'); deck.id = 'lm-botdeck';
        if (body.parentNode) body.parentNode.insertBefore(deck, body.nextSibling);
      }
      if (stats.parentNode !== deck) deck.appendChild(stats);
      body.classList.add('lm-body-deck');
    } catch (e) {}
  }

  var LS_BTNS_HEADER = 'lm_btns_header' + _lmUserSuffix;
  function _lmBtnsInHeader() { return window.lmLS.get(LS_BTNS_HEADER) === '1'; }
  function _lmEmOnMap() { return window.lmLS.get('lm_em_onmap') === '1'; }
  function _emMirrorToArena() {
    try {
      var v = document.getElementById('lm-mi-emission-val'), l = document.getElementById('lm-mi-emission-lbl'),
          i = document.getElementById('lm-mi-emission-ico'), c = document.getElementById('lm-mi-emission-card');
      var av = document.getElementById('lm-ab-em-val'), al = document.getElementById('lm-ab-em-lbl'),
          ai = document.getElementById('lm-ab-em-ico'), ac = document.getElementById('lm-ab-emission');
      if (av && v) av.textContent = v.textContent;
      if (al && l) al.textContent = l.textContent;
      if (ai && i) ai.textContent = i.textContent;
      if (ac && c) ac.classList.toggle('is-active', c.classList.contains('is-active'));
      if (c) {
        if (_lmEmOnMap() && !document.documentElement.classList.contains('lm-btns-header')) {
          var fat = document.getElementById('lm-mi-fat-block');
          var fatOn = fat && fat.style.display !== 'none';
          c.style.display = fatOn ? '' : 'none';
        } else { c.style.display = ''; }
      }
    } catch (e) {}
  }
  function _lmApplyEmOnMap() {
    try {
      document.documentElement.classList.toggle('lm-em-onmap', _lmEmOnMap());
      _emMirrorToArena();
    } catch (e) {}
  }
  function _lmPlaceTimerStat() {
    try {
      var span = document.getElementById('labyrinthTimerText');
      if (!span || !span.closest) return;
      var card = span.closest('.labyrinth__stat'); if (!card) return;
      var bar = document.getElementById('lm-gamestats');
      if (_lmBtnsInHeader()) {
        if (bar && card.parentNode !== bar) bar.appendChild(card);
      } else {
        var stats = document.querySelector('.labyrinth__header .labyrinth__stats');
        if (bar && card.parentNode === bar && stats) stats.appendChild(card);
      }
    } catch (e) {}
  }
  function _lmApplyBtnLayout() {
    try {
      document.documentElement.classList.toggle('lm-btns-header', _lmBtnsInHeader());
      _lmPlaceTimerStat();
      var h = document.getElementById('lm-cleanbtn'), a = document.getElementById('lm-ab-view');
      if (h) h.classList.toggle('active', cleanViewMode);
      if (a) a.classList.toggle('active', cleanViewMode);
      _emMirrorToArena();
    } catch (e) {}
  }

  function _lmSyncArenaTimer() {
    var t = document.getElementById('lm-ab-timer'); if (!t) return;
    var src = document.getElementById('labyrinthTimerText');
    var vv = t.querySelector('.lm-abt-val');
    if (vv) vv.textContent = (src && (src.textContent || '').trim()) || '00:00';
  }
  function _lmBuildArenaBar() {
    try {
      var arena = document.querySelector('.labyrinth__arena');
      if (!arena) return;
      arena.classList.add('lm-arena-styled');
      if (!document.getElementById('lm-arena-bar')) {
        var bar = document.createElement('div'); bar.id = 'lm-arena-bar';
        bar.innerHTML = '<button id="lm-ab-view" title="Скрыть/показать иконки скрипта на карте">👁 Вид</button>'+
          '<div class="lm-ab-right">'+
            '<div id="lm-ab-emission" title="Таймер выброса"><span class="lm-abe-val" id="lm-ab-em-val">—</span><span class="lm-abe-lbl" id="lm-ab-em-lbl">выброс</span></div>'+
            '<div id="lm-ab-timer"><span class="lm-abt-val">00:00</span><span class="lm-abt-lbl">таймер</span></div>'+
            '<button id="lm-ab-map" title="Открыть полную карту">⛶ Карта</button>'+
          '</div>';
        arena.insertBefore(bar, arena.firstChild);
        var vbtn = document.getElementById('lm-ab-view');
        vbtn.classList.toggle('active', cleanViewMode);
        vbtn.addEventListener('click', function() {
          cleanViewMode = !cleanViewMode;
          if (cleanViewMode) applyCleanView(); else removeCleanView();
          _lmApplyBtnLayout();
        });
        document.getElementById('lm-ab-map').addEventListener('click', function() { openFull(); });
      }
      var tEl = document.getElementById('labyrinthTimerText');
      if (tEl && !window._lmTimerObs) {
        window._lmTimerObs = new MutationObserver(function() { _lmSyncArenaTimer(); });
        window._lmTimerObs.observe(tEl, { childList: true, characterData: true, subtree: true });
      }
      _lmSyncArenaTimer();
      _lmApplyBtnLayout();
      _lmApplyEmOnMap();
    } catch (e) {}
  }

  function buildDOM() {
    var wrap = document.createElement('div'); wrap.id='lm-wrap';
    wrap.innerHTML=
      '<div id="lm-hdr">'+
        '<div id="lm-ttl"><div id="lm-ttl-dot"></div>Карта лабиринта</div>'+
        '<div id="lm-btns">'+
          '<button id="lm-msgbtn" title="Сообщения от админа лабиринта">💬<span id="lm-msgbadge"></span></button>'+
          '<button id="lm-chatbtn" title="Чат пользователей скрипта">🗨 Чат<span id="lm-chatbadge"></span></button>'+
          '<button id="lm-anbtn" title="Статус скрипта и отправка отчёта о поломке">📊 Статус</button>'+
          '<button id="lm-cleanbtn" title="Скрыть/показать иконки скрипта на карте">👁 Вид</button>'+
          '<button id="lm-rbtn">🔄 Обновить</button>'+
          '<button id="lm-pbtn">⛶ Карта</button>'+
          '<button id="lm-setbtn" title="Настройки скрипта">⚙</button>'+
        '</div>'+
      '</div>'+
      '<div id="lm-map-info">'+
        '<div class="lm-mi-card">'+
          '<div class="lm-mi-ico">🗺</div>'+
          '<div style="min-width:0;flex:1">'+
            '<div class="lm-mi-val" id="lm-mi-rooms-val">—</div>'+
            '<div class="lm-mi-lbl">комнат в базе</div>'+
            '<div class="lm-mi-sep"></div>'+
            '<div class="lm-mi-val-sm" id="lm-mi-depth-val">—</div>'+
            '<div class="lm-mi-lbl">макс. глубина</div>'+
          '</div>'+
        '</div>'+
        '<div class="lm-mi-card" id="lm-mi-emission-card">'+
          '<div class="lm-mi-ico" id="lm-mi-emission-ico">🌩</div>'+
          '<div style="min-width:0;flex:1">'+
            '<div class="lm-mi-val-sm" id="lm-mi-emission-val">—</div>'+
            '<div class="lm-mi-lbl" id="lm-mi-emission-lbl">выброс</div>'+
            '<div id="lm-mi-fat-block" style="display:none">'+
              '<div class="lm-mi-sep"></div>'+
              '<div class="lm-mi-val-sm" id="lm-mi-fat-val">—</div>'+
              '<div class="lm-mi-lbl">💤 усталость</div>'+
            '</div>'+
          '</div>'+
        '</div>'+
      '</div>'+
      '<div id="lm-action-bar">'+
        '<button class="lm-abar-btn" id="lm-wb-guard"><span class="lm-abar-ico">👑</span>Стражи</button>'+
        '<button class="lm-abar-btn" id="lm-wb-club"><span class="lm-abar-ico">🛡</span>Клуб</button>'+
        '<button class="lm-abar-btn" id="lm-wb-hist"><span class="lm-abar-ico">📜</span>История</button>'+
        '<button class="lm-abar-btn" id="lm-wb-fat" style="display:none"><span class="lm-abar-ico">💤</span>Откаты</button>'+
      '</div>'+
      '<div id="lm-inline-panel">'+
        '<div id="lm-wi-guard" style="display:none"><div class="lm-wi-loading">Загрузка...</div></div>'+
        '<div id="lm-wi-club"  style="display:none"><div class="lm-wi-loading">Загрузка...</div></div>'+
        '<div id="lm-wi-hist"  style="display:none"><div class="lm-hist-body"><div class="lm-wi-loading">Загрузка...</div></div></div>'+
        '<div id="lm-wi-fat"   style="display:none"><div class="lm-wi-loading">Загрузка...</div></div>'+
      '</div>'+
      '<canvas id="lm-cvs" style="display:none"></canvas>'+
      '<div id="lm-st" style="display:none"></div>';

    var arenaEl = document.querySelector('.labyrinth__arena');
    var placed = false;
    if (arenaEl) { arenaEl.parentNode.insertBefore(wrap, arenaEl); placed = true; }
    if (!placed) {
      var fallbacks = ['.labyrinth__left', '.animesss-labyrinth'];
      for (var ti=0;ti<fallbacks.length;ti++) { var el=document.querySelector(fallbacks[ti]); if(el){el.parentNode.insertBefore(wrap,el.nextSibling);placed=true;break;} }
    }
    if (!placed) document.body.appendChild(wrap);

    _lmMoveGameStats(wrap, arenaEl);
    _lmBuildTopDeck(); _lmBuildArenaBar(); _lmBuildBotDeck();
    setTimeout(function(){ _lmMoveGameStats(wrap, arenaEl); _lmBuildTopDeck(); _lmBuildArenaBar(); _lmBuildBotDeck(); }, 800);
    setTimeout(function(){ _lmMoveGameStats(wrap, arenaEl); _lmBuildTopDeck(); _lmBuildArenaBar(); _lmBuildBotDeck(); }, 2500);
    try {
      var hdrEl = document.querySelector('.labyrinth__header');
      if (hdrEl && !window._lmStatsObs) {
        window._lmStatsObs = new MutationObserver(function(){ _lmMoveGameStats(wrap, arenaEl); _lmBuildTopDeck(); _lmBuildArenaBar(); _lmBuildBotDeck(); });
        window._lmStatsObs.observe(hdrEl, { childList: true, subtree: false });
      }
    } catch(e) {}
    try {
      if (!window._lmDeckResize) {
        window._lmDeckResize = true;
        window.addEventListener('resize', function(){ clearTimeout(window._lmDeckRT); window._lmDeckRT = setTimeout(function(){ _lmBuildTopDeck(); _lmBuildBotDeck(); }, 150); });
      }
    } catch(e) {}

    var modal = document.createElement('div'); modal.id='lm-modal';
    modal.innerHTML=
      '<div id="lm-mhdr">'+
        '<div id="lm-mhdr-top">'+
          '<div id="lm-mttl">Полная карта лабиринта</div>'+
          '<div id="lm-hdr-right">'+
            '<button id="lm-brefresh" title="Обновить карту"><span class="lm-ref-ico">🔄</span> Обновить</button>'+
            '<button id="lm-bc">×</button>'+
          '</div>'+
        '</div>'+
        '<div id="lm-mhdr-btns">'+
          '<button id="lm-b1" title="Перейти к моей позиции">⚔ Моя позиция</button>'+
          '<button id="lm-b0" title="Центрировать старт">⚑ Старт</button>'+
          '<button id="lm-bguard" title="Показать моих стражей">👑 Мои стражи</button>'+
          '<button id="lm-bclub" title="Показать стражей клуба">🛡 Стражи клуба</button>'+
          '<button id="lm-bhist" title="История лабиринта">📜 История</button>'+
          '<button id="lm-bpath" title="Показать мой путь">👣 Мой путь</button>'+
          '<button id="lm-bpencil" title="Нарисовать свой маршрут">✏️ Путь</button>'+
          '<button id="lm-bf" title="Фильтр комнат">⊞ Фильтр</button>'+
        '</div>'+
      '</div>'+
      '<div id="lm-pencil-panel">'+
        '<span class="lm-pp-title">✏️ Маршрут:</span>'+
        '<button id="lm-padd" class="lm-pctl active">➕ Добавлять</button>'+
        '<button id="lm-prem" class="lm-pctl">➖ Убирать</button>'+
        '<span class="lm-pp-count" id="lm-pp-count">0</span>'+
        '<span style="flex:1;min-width:8px"></span>'+
        '<button id="lm-pclr" class="lm-pctl danger">🗑 Стереть</button>'+
        '<button id="lm-pdone" class="lm-pctl done">✓ Готово</button>'+
        '<div class="lm-pp-hint" style="flex-basis:100%">Кликай клетки на карте: сначала рядом с собой, потом соседние (вверх/влево/вправо)</div>'+
      '</div>'+
      '<div id="lm-mbody"><canvas id="lm-fc"></canvas>'+
        '<div id="lm-info">Карта готова</div>'+
        '<div id="lm-zbtns"><button class="lm-zb" id="lm-zi">+</button><button class="lm-zb" id="lm-zo">−</button></div>'+
      '</div>'+
      '<div id="lm-legend-bar">'+
        LEGEND_ITEMS.map(function(i){ return '<div class="lm-lb-item"><div class="lm-lb-ico" style="background:'+clr(i[0])+';color:'+iclr(i[0])+'">'+ico(i[0])+'</div><span>'+i[1]+'</span></div>'; }).join('')+
      '</div>';
    document.body.appendChild(modal);

    var fpop = document.createElement('div'); fpop.id='lm-fpop'; fpop.className='lm-pop';
    function _fsec(t){ return '<div style="flex-basis:100%;width:100%;font:800 10px/1 system-ui;color:#8093b6;text-transform:uppercase;letter-spacing:.06em;margin:10px 2px 4px">'+t+'</div>'; }
    function _fitem(ev,lbl,bg,fg,ic){ return '<div class="lm-fi" data-ev="'+ev+'"><div class="lm-fid" style="background:'+(bg||clr(ev))+';color:'+(fg||iclr(ev))+'">'+(ic||ico(ev))+'</div><span>'+(lbl||LM_LABELS[ev]||ev)+'</span></div>'; }
    var _fg = _fsec('👥 Игроки') + _fitem('__players__','Где игроки','#0a2818','#22c55e','👤');
    LM_FILTER_GROUPS.forEach(function(g){ _fg += _fsec(g[0]) + g[1].map(function(ev){ return _fitem(ev); }).join(''); });
    _fg += _fsec('🔧 Прочее') + _fitem('__has_alt__','Разные типы','#2a1a4a','#ffd66b','👁');
    fpop.innerHTML='<div id="lm-fpop-head"><div id="lm-fpop-title">⊞ Фильтр комнат</div><button id="lm-fpop-clear">× Сбросить</button></div><div id="lm-fpop-grid">'+_fg+'</div>';
    modal.appendChild(fpop);

    var gpop = document.createElement('div'); gpop.id='lm-gpop'; gpop.className='lm-pop';
    gpop.innerHTML='<div class="lm-spop-head"><div><span class="lm-spop-title">👑 Мои стражи</span><span class="lm-spop-count" id="lm-gcount"></span></div></div><div class="lm-spop-list lm-spop-loading" id="lm-glist">Загрузка...</div>';
    modal.appendChild(gpop);

    var cpop = document.createElement('div'); cpop.id='lm-cpop'; cpop.className='lm-pop';
    cpop.innerHTML='<div class="lm-spop-head"><div><span class="lm-spop-title">🛡 Стражи клуба</span><span class="lm-spop-count" id="lm-ccount"></span></div></div><div class="lm-spop-list lm-spop-loading" id="lm-clist">Загрузка...</div>';
    modal.appendChild(cpop);

    var hpop = document.createElement('div'); hpop.id='lm-hpop'; hpop.className='lm-pop';
    hpop.innerHTML='<div class="lm-pop-head"><span class="lm-pop-title">📜 История лабиринта</span><a class="lm-pop-link" href="/acchistory/" target="_blank">↗ Полная история</a></div><div class="lm-hist-body"><div class="lm-spop-loading">Загрузка...</div></div>';
    modal.appendChild(hpop);

    var tt = document.createElement('div'); tt.id='lm-tooltip'; document.body.appendChild(tt);

    var boostConf = document.createElement('div'); boostConf.id='lm-boost-confirm';
    boostConf.innerHTML='<div id="lm-boost-box"><div class="lm-boost-conf-ico" id="lm-boost-conf-ico">👁</div><div class="lm-boost-conf-title" id="lm-boost-conf-title">Активировать буст?</div><div class="lm-boost-conf-desc" id="lm-boost-conf-desc"></div><div class="lm-boost-conf-btns"><button id="lm-boost-conf-no">Отмена</button><button id="lm-boost-conf-yes">Активировать</button></div></div>';
    document.body.appendChild(boostConf);

    var conf = document.createElement('div'); conf.id='lm-confirm';
    conf.innerHTML='<div id="lm-cbox"><h3>🔄 Обновить карту?</h3><p>Скрипт отправит ваши данные в облако и загрузит свежую карту.</p><div class="time" id="lm-ctime"></div><div id="lm-cbtns"><button id="lm-cno">Отмена</button><button id="lm-cyes">Обновить</button></div></div>';
    document.body.appendChild(conf);

    var conf2 = document.createElement('div'); conf2.id='lm-confirm2';
    conf2.innerHTML='<div id="lm-cbox2"><h3>🔄 Обновить карту?</h3><p>Карта будет полностью обновлена. Может занять до 2 минут.</p><div class="time" id="lm-ctime2"></div><div id="lm-cbtns2"><button id="lm-cno2">Отмена</button><button id="lm-cyes2">Обновить</button></div></div>';
    document.body.appendChild(conf2);

    var toast = document.createElement('div'); toast.id='lm-toast';
    document.body.appendChild(toast);
  }

  function closeAllPopups() {
    ['lm-gpop','lm-cpop','lm-fpop','lm-hpop'].forEach(function(id) {
      var el = document.getElementById(id); if (el) el.classList.remove('on');
    });
    ['lm-bguard','lm-bclub','lm-bf','lm-bhist'].forEach(function(id) {
      var el = document.getElementById(id); if (el) el.classList.remove('active');
    });
  }

  function fixPopupPos(popEl, btnEl) {
    var btn = btnEl || popEl.parentElement;
    var bRect = btn.getBoundingClientRect();
    popEl.style.top  = (bRect.bottom + 8) + 'px';
    popEl.style.left = bRect.left + 'px';
    popEl.style.right = 'auto';
    var pRect = popEl.getBoundingClientRect();
    var over = pRect.right - (window.innerWidth - 8);
    if (over > 0) popEl.style.left = Math.max(8, bRect.left - over) + 'px';
    if (pRect.bottom > window.innerHeight - 8) {
      popEl.style.top = Math.max(8, bRect.top - pRect.height - 8) + 'px';
    }
  }

  function bindEvents() {
    var conf = document.getElementById('lm-confirm');
    var fpop = document.getElementById('lm-fpop');

    elMbody   = document.getElementById('lm-mbody');
    elTooltip = document.getElementById('lm-tooltip');
    elInfo    = document.getElementById('lm-info');
    elSt      = document.getElementById('lm-st');
    elFc      = document.getElementById('lm-fc');
    elCvs     = document.getElementById('lm-cvs');

    function wbClose() {
      _wbActive = null;
      document.getElementById('lm-inline-panel').classList.remove('on');
      ['lm-wb-guard','lm-wb-club','lm-wb-hist','lm-wb-fat'].forEach(function(id){
        var b = document.getElementById(id); if(b) b.classList.remove('active');
      });
      ['lm-wi-guard','lm-wi-club','lm-wi-hist','lm-wi-fat'].forEach(function(id){
        var el = document.getElementById(id); if(el) el.style.display = 'none';
      });
    }

    function wbOpen(tab) {
      if (_wbActive === tab) { wbClose(); return; }
      wbClose();
      _wbActive = tab;
      document.getElementById('lm-inline-panel').classList.add('on');
      document.getElementById('lm-wb-'+tab).classList.add('active');
      var panel = document.getElementById('lm-wi-'+tab);
      if (panel) panel.style.display = 'block';
      if (tab === 'guard') {
        loadWbGuardian('user', document.getElementById('lm-wi-guard'));
      } else if (tab === 'club') {
        loadWbGuardian('club', document.getElementById('lm-wi-club'));
      } else if (tab === 'hist') {
        var hBody = document.querySelector('#lm-wi-hist .lm-hist-body');
        if (hBody) {
          if (Object.keys(histCache).length === 0) {
            histTotal = null; histCurPage = 1;
            hBody.innerHTML = '<div class="lm-wi-loading">Загрузка...</div>';
            loadHistPage(1, function(result) { renderHistInline(result, 1); });
          } else {
            renderHistInline(histCache[histCurPage] || histCache[1] || {rows:[],totalPages:1}, histCurPage || 1);
          }
        }
      }
      if (tab === 'fat') { if (window._lmRenderFatHist) window._lmRenderFatHist(); }
    }

    document.getElementById('lm-wb-guard').addEventListener('click', function(){ wbOpen('guard'); });
    document.getElementById('lm-wb-club').addEventListener('click',  function(){ wbOpen('club');  });
    document.getElementById('lm-wb-hist').addEventListener('click',  function(){ wbOpen('hist');  });
    var _fatBtn = document.getElementById('lm-wb-fat');
    if (_fatBtn) _fatBtn.addEventListener('click', function(){ wbOpen('fat'); });

    var inlinePanel = document.getElementById('lm-inline-panel');
    if (inlinePanel) inlinePanel.addEventListener('click', function(e){ e.stopPropagation(); });

    document.getElementById('lm-rbtn').addEventListener('click', function() {
      var conf2 = document.getElementById('lm-confirm2');
      var timeEl2 = document.getElementById('lm-ctime2');
      if (conf2) {
        if (timeEl2) {
          var lastT2 = window.lmLS.get(LS_FULL_REFRESH_KEY);
          if (lastT2) { var diff2=Math.floor((Date.now()-parseInt(lastT2))/60000); timeEl2.textContent=diff2<1?'Последнее: только что':'Последнее: '+diff2+' мин. назад'; }
          else timeEl2.textContent='Ещё не обновляли.';
        }
        conf2.classList.add('on');
      }
    });
    document.getElementById('lm-cno').addEventListener('click', function(){ conf.classList.remove('on'); });
    conf.addEventListener('click', function(e){ if(e.target===conf) conf.classList.remove('on'); });
    document.getElementById('lm-cyes').addEventListener('click', function() {
      conf.classList.remove('on');
      window.lmLS.set(LS_REFRESH_KEY, String(Date.now()));
      doRefresh();
    });

    var _pBtn = document.getElementById('lm-pbtn');
    if (_pBtn) _pBtn.addEventListener('click', function(){ openFull(); });
    var _anBtn = document.getElementById('lm-anbtn');
    if (_anBtn) _anBtn.addEventListener('click', function(){ if (window._lmOpenReport) window._lmOpenReport('', null); });
    var _msgBtn = document.getElementById('lm-msgbtn');
    if (_msgBtn) _msgBtn.addEventListener('click', function(){ if (window._lmOpenMessages) window._lmOpenMessages(); });
    if (window._lmMsgPoll) window._lmMsgPoll();

    var _cleanBtn = document.getElementById('lm-cleanbtn');
    if (_cleanBtn) _cleanBtn.addEventListener('click', function() {
      cleanViewMode = !cleanViewMode;
      if (cleanViewMode) applyCleanView(); else removeCleanView();
      _lmApplyBtnLayout();
    });
    var _setBtn = document.getElementById('lm-setbtn');
    if (_setBtn) _setBtn.addEventListener('click', showSettings);
    document.getElementById('lm-bc').addEventListener('click', closeFull);

    document.getElementById('lm-brefresh').addEventListener('click', function() {
      var conf2 = document.getElementById('lm-confirm2');
      var timeEl2 = document.getElementById('lm-ctime2');
      if (timeEl2) {
        var lastT = window.lmLS.get(LS_FULL_REFRESH_KEY);
        if (lastT) {
          var diff = Math.floor((Date.now() - parseInt(lastT)) / 60000);
          timeEl2.textContent = diff < 1 ? 'Последнее обновление: только что' : 'Последнее обновление: ' + diff + ' мин. назад';
        } else { timeEl2.textContent = 'Вы ещё не обновляли информацию.'; }
      }
      conf2.classList.add('on');
    });

    var conf2El = document.getElementById('lm-confirm2');
    document.getElementById('lm-cno2').addEventListener('click', function() { conf2El.classList.remove('on'); });
    conf2El.addEventListener('click', function(e) { if (e.target === conf2El) conf2El.classList.remove('on'); });
    document.getElementById('lm-cyes2').addEventListener('click', function() {
      conf2El.classList.remove('on');
      window.lmLS.set(LS_FULL_REFRESH_KEY, String(Date.now()));
      doFullRefresh();
    });

    document.addEventListener('keydown', function(e){ if(e.key==='Escape') closeFull(); });
    document.getElementById('lm-b0').addEventListener('click', function(){
      highlightRoom={x:0,y:0,until:Date.now()+2000};
      if(!fmOpen) { openFull(true); setTimeout(function(){ centerOn(0,0,FOCUS_ZOOM_NEAR); }, 50); }
      else { centerOn(0,0,FOCUS_ZOOM_NEAR); }
    });
    document.getElementById('lm-b1').addEventListener('click', function(){
      var c=curPos();
      highlightRoom={x:c.x,y:c.y,until:Date.now()+2000};
      if(!fmOpen) { openFull(true); setTimeout(function(){ centerOn(c.x,c.y,FOCUS_ZOOM_NEAR); }, 50); }
      else { centerOn(c.x,c.y,FOCUS_ZOOM_NEAR); }
    });
    document.getElementById('lm-zi').addEventListener('click', function(){ fmScale=Math.min(fmScale*ZOOM_IN,ZOOM_MAX); drawFull(); });
    document.getElementById('lm-zo').addEventListener('click', function(){ fmScale=Math.max(fmScale*ZOOM_OUT,ZOOM_MIN); drawFull(); });

    document.getElementById('lm-bpath').addEventListener('click', function(){
      showMyPath=!showMyPath;
      this.classList.toggle('active',showMyPath);
      this.textContent=showMyPath?'👣 Все игроки':'👣 Мой путь';
      if (showMyPath && !window._lmMyPathData) {
        window.lmFetch(VPS_URL+'/my/path', { headers: vpsAuthHeaders(false) })
          .then(function(r){ if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
          .then(function(d){
            if (d && Array.isArray(d.steps)) {
              window._lmMyPathData = {};
              d.steps.forEach(function(s){ window._lmMyPathData[s.x+'_'+s.y]=true; });
              window._lmHistSteps = d.steps;
              if (Array.isArray(d.emission_cells)) window._lmEmissionCells = new Set(d.emission_cells.map(function(c){ return c.x+'_'+c.y; }));
              drawFull();
            }
          }).catch(function(){});
        return;
      }
      drawFull();
    });

    function _pencilCount(){ var el=document.getElementById('lm-pp-count'); if(el) el.textContent='Клеток: '+_lmRoute.length; }
    window._lmPencilCount = _pencilCount;
    function _pencilSub(mode){
      _lmPencil=mode;
      var a=document.getElementById('lm-padd'), r=document.getElementById('lm-prem');
      if(a) a.classList.toggle('active',mode==='add');
      if(r) r.classList.toggle('active',mode==='remove');
    }
    function _pencilOpen(open){
      var p=document.getElementById('lm-pencil-panel'), b=document.getElementById('lm-bpencil');
      if(p) p.classList.toggle('on',open);
      if(b) b.classList.toggle('active',open);
      if(open){ _pencilSub('add'); _pencilCount(); } else { _lmPencil='off'; }
      if(fmOpen) requestAnimationFrame(drawFull);
    }
    document.getElementById('lm-bpencil').addEventListener('click', function(){ _pencilOpen(_lmPencil==='off'); });
    document.getElementById('lm-padd').addEventListener('click', function(){ _pencilSub('add'); });
    document.getElementById('lm-prem').addEventListener('click', function(){ _pencilSub('remove'); });
    document.getElementById('lm-pdone').addEventListener('click', function(){ _pencilOpen(false); });
    document.getElementById('lm-pclr').addEventListener('click', function(){
      if(!_lmRoute.length){ _lmMsg('Маршрут пуст',''); return; }
      if(confirm('Стереть весь нарисованный маршрут?')){ _lmRoute=[]; _routeSave(); _routeSyncToServer(); if(fmOpen)drawFull(); patchSiteMap(); _pencilCount(); _lmMsg('Маршрут стёрт',''); }
    });
    var _ppEl = document.getElementById('lm-pencil-panel');
    if (_ppEl) ['wheel','touchmove'].forEach(function(ev){ _ppEl.addEventListener(ev, function(e){ e.stopPropagation(); }, { passive: true }); });
    function _inPencilPanel(t){ return !!(t && t.closest && t.closest('#lm-pencil-panel')); }

    document.getElementById('lm-bf').addEventListener('click', function(e){
      e.stopPropagation();
      var wasOpen = fpop.classList.contains('on');
      closeAllPopups();
      if (!wasOpen) { fpop.classList.add('on'); this.classList.add('active'); fixPopupPos(fpop, this); }
    });
    fpop.addEventListener('click', function(e){
      e.stopPropagation();
      var fi = e.target.closest('.lm-fi[data-ev]');
      if (fi) {
        var ev = fi.dataset.ev;
        if(filterSet[ev]) delete filterSet[ev]; else filterSet[ev]=true;
        fi.classList.toggle('sel',!!filterSet[ev]);
        var n=Object.keys(filterSet).length, bf=document.getElementById('lm-bf');
        bf.childNodes[0].textContent=n>0?'⊞ Фильтр ('+n+')':'⊞ Фильтр';
        drawFull(); return;
      }
      if (e.target.id==='lm-fpop-clear') {
        filterSet={};
        fpop.querySelectorAll('.lm-fi').forEach(function(x){ x.classList.remove('sel'); });
        var bf2=document.getElementById('lm-bf');
        bf2.childNodes[0].textContent='⊞ Фильтр'; bf2.classList.remove('active');
        fpop.classList.remove('on'); drawFull();
      }
    });

    document.getElementById('lm-bguard').addEventListener('click', function(e){
      var pop = document.getElementById('lm-gpop'), wasOpen = pop.classList.contains('on');
      closeAllPopups();
      if (!wasOpen) { pop.classList.add('on'); this.classList.add('active'); loadGuardianPopup('user'); fixPopupPos(pop, this); }
    });
    document.getElementById('lm-bclub').addEventListener('click', function(e){
      var pop = document.getElementById('lm-cpop'), wasOpen = pop.classList.contains('on');
      closeAllPopups();
      if (!wasOpen) { pop.classList.add('on'); this.classList.add('active'); loadGuardianPopup('club'); fixPopupPos(pop, this); }
    });

    document.getElementById('lm-bhist').addEventListener('click', function(e){
      e.stopPropagation();
      var pop = document.getElementById('lm-hpop'), wasOpen = pop.classList.contains('on');
      closeAllPopups();
      if (!wasOpen) {
        pop.classList.add('on'); this.classList.add('active');
        if (Object.keys(histCache).length === 0) {
          histTotal = null; histCurPage = 1;
          loadAndRenderHist(1);
        } else {
          renderHistPopup(histCache[histCurPage] || histCache[1] || {rows:[],totalPages:1}, histCurPage || 1);
        }
        fixPopupPos(pop, document.getElementById('lm-bhist'));
      }
    });

    ['lm-gpop','lm-cpop','lm-fpop','lm-hpop'].forEach(function(id) {
      var el = document.getElementById(id);
      if (el) el.addEventListener('click', function(e){ e.stopPropagation(); });
    });

    document.addEventListener('click', function(e){
      var modalPopupIds = ['lm-bguard','lm-bclub','lm-bf','lm-bhist'];
      var insideModal = modalPopupIds.some(function(id){
        var b = document.getElementById(id);
        return b && b.contains(e.target);
      });
      if (!insideModal) closeAllPopups();
      var wrap2 = document.getElementById('lm-wrap');
      if (wrap2 && !wrap2.contains(e.target)) {
        var panel = document.getElementById('lm-inline-panel');
        if (panel && panel.classList.contains('on')) {
          document.getElementById('lm-inline-panel').classList.remove('on');
          ['lm-wb-guard','lm-wb-club','lm-wb-hist'].forEach(function(id){
            var b=document.getElementById(id); if(b) b.classList.remove('active');
          });
          ['lm-wi-guard','lm-wi-club','lm-wi-hist'].forEach(function(id){
            var el=document.getElementById(id); if(el) el.style.display='none';
          });
          _wbActive = null;
        }
      }
    });

    elMbody.addEventListener('mousedown', function(e){ drag=true; dsx=e.clientX-fmX; dsy=e.clientY-fmY; elMbody.classList.add('dr'); _pDownX=e.clientX; _pDownY=e.clientY; });
    document.addEventListener('mousemove', function(e){
      if (!drag) return;
      fmX=e.clientX-dsx; fmY=e.clientY-dsy;
      if (rafId) return;
      rafId=requestAnimationFrame(function(){ rafId=null; drawFull(); });
    });
    document.addEventListener('mouseup', function(e){ var _clk = drag && _lmPencil!=='off' && !_inPencilPanel(e.target) && Math.abs(e.clientX-_pDownX)<6 && Math.abs(e.clientY-_pDownY)<6; drag=false; elMbody.classList.remove('dr'); if(rafId){ cancelAnimationFrame(rafId); rafId=null; } if(_clk) _lmPencilFromPoint(e.clientX, e.clientY); });

    var pinchDist0 = 0, pinchScale0 = 1, pinchFmX0 = 0, pinchFmY0 = 0, pinchCx = 0, pinchCy = 0;
    var tapStartX = 0, tapStartY = 0;

    function getTouchDist(e) {
      var dx = e.touches[0].clientX - e.touches[1].clientX;
      var dy = e.touches[0].clientY - e.touches[1].clientY;
      return Math.sqrt(dx*dx + dy*dy);
    }

    elMbody.addEventListener('touchstart', function(e) {
      e.preventDefault();
      if (e.touches.length === 1) {
        pinchDist0 = 0;
        var t = e.touches[0];
        drag = true; dsx = t.clientX - fmX; dsy = t.clientY - fmY;
        tapStartX = t.clientX; tapStartY = t.clientY; _touchMoved = false;
      } else if (e.touches.length === 2) {
        drag = false; pinchDist0 = getTouchDist(e); pinchScale0 = fmScale;
        pinchFmX0 = fmX; pinchFmY0 = fmY;
        var rect = elMbody.getBoundingClientRect();
        pinchCx = ((e.touches[0].clientX + e.touches[1].clientX) / 2) - rect.left;
        pinchCy = ((e.touches[0].clientY + e.touches[1].clientY) / 2) - rect.top;
      }
    }, { passive: false });

    elMbody.addEventListener('touchmove', function(e) {
      e.preventDefault();
      if (e.touches.length === 2 && pinchDist0 > 0) {
        var dist = getTouchDist(e);
        var ns = Math.max(ZOOM_MIN, Math.min(pinchScale0 * (dist / pinchDist0), ZOOM_MAX));
        fmX = pinchCx - (pinchCx - pinchFmX0) * (ns / pinchScale0);
        fmY = pinchCy - (pinchCy - pinchFmY0) * (ns / pinchScale0);
        fmScale = ns;
        if (rafId) return;
        rafId = requestAnimationFrame(function() { rafId = null; drawFull(); });
      } else if (e.touches.length === 1 && drag) {
        var t = e.touches[0];
        if (Math.abs(t.clientX - tapStartX) > 6 || Math.abs(t.clientY - tapStartY) > 6) _touchMoved = true;
        fmX = t.clientX - dsx; fmY = t.clientY - dsy;
        if (rafId) return;
        rafId = requestAnimationFrame(function() { rafId = null; drawFull(); });
      }
    }, { passive: false });

    elMbody.addEventListener('touchend', function(e) {
      e.preventDefault(); pinchDist0 = 0;
      if (e.touches.length === 0) {
        if (drag && !_touchMoved && _lmPencil !== 'off' && !_inPencilPanel(e.target)) _lmPencilFromPoint(tapStartX, tapStartY);
        drag = false;
      }
      else if (e.touches.length === 1) {
        var t = e.touches[0]; dsx = t.clientX - fmX; dsy = t.clientY - fmY; drag = true;
      }
    }, { passive: false });

    elMbody.addEventListener('wheel',function(e){
      e.preventDefault();
      var rect=elMbody.getBoundingClientRect(), mx=e.clientX-rect.left, my=e.clientY-rect.top;
      var factor=e.deltaY>0?ZOOM_OUT:ZOOM_IN, ns=Math.max(ZOOM_MIN,Math.min(fmScale*factor,ZOOM_MAX));
      fmX=mx-(mx-fmX)*(ns/fmScale); fmY=my-(my-fmY)*(ns/fmScale); fmScale=ns;
      if (!rafId) rafId=requestAnimationFrame(function(){ rafId=null; drawFull(); });
    },{passive:false});

    var ttRafId=null;
    elMbody.addEventListener('mousemove',function(e){
      if (drag) { elTooltip.style.display='none'; return; }
      if (ttRafId) return;
      ttRafId=requestAnimationFrame(function(){
        ttRafId=null;
        var rect=elMbody.getBoundingClientRect(), cs=CELL_SIZE*fmScale;
        var cx=Math.floor((e.clientX-rect.left-fmX)/cs), cy=Math.floor((e.clientY-rect.top-fmY)/cs);
        var rooms=allRooms(), room=rooms[cx+'_'+cy];
        if (room) {
          var cur2=curPos(), isCur=cx===cur2.x&&cy===cur2.y;
          var gLine=room.guardian?'<div class="tt-guard">'+(room.guardian==='guardian_user'?'👑 Личный страж':room.guardian==='guardian_club'?'🛡 Страж клуба':'⚐ Можно захватить')+'</div>':'';
          var mmAltFiltered = (room.altTypes||[]).filter(function(t){ return t!=='room_player'&&t!=='room_trap'&&t!=='room_gift'&&t!=='shield_block'&&t!==room.emissionEv; });
          var mmAltLine = '';
          if (mmAltFiltered.length) {
            var mmIsVar = room.event!=='unknown' && VARIABLE_EVENTS[room.event];
            if (mmIsVar) {
              var mmAll = [ico(room.event)+' '+roomName(room.event)].concat(mmAltFiltered.map(function(t){ return ico(t)+' '+roomName(t); }));
              mmAltLine = '<div class="tt-alt tt-var">🔀 Варианты: '+mmAll.join(' / ')+'</div>';
            } else {
              mmAltLine = '<div class="tt-alt">Также видели: '+mmAltFiltered.map(function(t){ return ico(t)+' '+roomName(t); }).join(', ')+'</div>';
            }
          }
          var mmObjLine = '';
          var mmEmLine = '';
          if (!isCur && room.emissionVisited && room.emissionEv && room.emissionEv !== room.event) {
            mmEmLine = '<div class="tt-alt" style="color:#ffcf4d">⚡ Во время выброса: '+ico(room.emissionEv)+' '+roomName(room.emissionEv)+'</div>';
          }
          var knownType = room.event && room.event !== 'unknown';
          var mmRealHdr = isCur ? '⚔ Ваша позиция' : (knownType ? ico(room.event)+' '+roomName(room.event) : '❔ Неизвестная комната');
          var mmRealLine = isCur ? '' : '<div class="tt-desc" style="color:#9fd6ff">Реальный тип: '+(knownType ? roomName(room.event) : 'не найдено')+'</div>';
          elTooltip.innerHTML='<b>'+mmRealHdr+'</b>'+
            mmRealLine+mmObjLine+mmEmLine+mmAltLine+gLine+
            '<div class="tt-coord">'+formatCoords(cx,cy)+'</div>';
          elTooltip.style.display='block';
          var tx=e.clientX+14, ty=e.clientY-10;
          if(tx+270>window.innerWidth) tx=e.clientX-274;
          elTooltip.style.left=tx+'px'; elTooltip.style.top=ty+'px';
        } else elTooltip.style.display='none';
      });
    });
    elMbody.addEventListener('mouseleave',function(){ elTooltip.style.display='none'; });

    var BOOST_INFO = {
      'labyrinthBoostVisionBtn':  { ico:'👁',  name:'Всевидящее oko',          desc:'Откроет тип следующей комнаты перед входом' },
      'labyrinthBoostShieldBtn':  { ico:'🛡',  name:'Щит',                     desc:'Защитит от следующего штрафа в лабиринте' },
      'labyrinthBoostRewardBtn':  { ico:'🎯',  name:'Гарантированная награда', desc:'Гарантирует награду в следующей комнате' },
      'labyrinthBoostBerserkBtn': { ico:'⚔',  name:'Режим берсерка',           desc:'Усиливает урон по боссам' }
    };
    var _pendingBoostBtn = null, _pendingIsRoomAction = false, _pendingIsBuy = false;

    function interceptRoomAction(btn, key, ico2, name, desc) {
      if (!btn) return;
      btn.addEventListener('click', function(e) {
        if (_roomActionConfirmed) return;
        if (!_cfEnabled(key)) return;
        e.stopImmediatePropagation(); e.preventDefault();
        document.getElementById('lm-boost-conf-ico').textContent   = ico2;
        document.getElementById('lm-boost-conf-title').textContent = name + '?';
        document.getElementById('lm-boost-conf-desc').textContent  = desc;
        _pendingBoostBtn = btn; _pendingIsRoomAction = true;
        document.getElementById('lm-boost-confirm').classList.add('on');
      }, true);
    }
    var _roomActionConfirmed = false;

    interceptRoomAction(document.getElementById('labyrinthPlaceTrapBtn'), 'place_trap', '🕸', 'Оставить ловушку', 'Ловушка остановит первого игрока, который войдёт сюда. Стоимость — 1 ход.');
    interceptRoomAction(document.getElementById('labyrinthPlaceGiftBtn'), 'place_gift', '🎁', 'Оставить подарок', 'Подарок сразу достанется следующему игроку. Стоимость — 1 ход.');
    interceptRoomAction(document.getElementById('labyrinthRobMineBtn'),   'rob_mine', '⚔', 'Ограбить шахту',   'Вы заберёте добычу из шахты другого игрока.');
    interceptRoomAction(document.getElementById('labyrinthEchoRepeatBtn'), 'echo_repeat', '👣', 'Повторить путь', 'Ты повторишь путь другого игрока в этой комнате.');
    interceptRoomAction(document.getElementById('labyrinthClubWarSoftBtn'),   'raid_soft', '🏴', 'Лёгкий рейд',       'Небольшая атака на вражеский клуб.');
    interceptRoomAction(document.getElementById('labyrinthClubWarNormalBtn'), 'raid_normal', '⚔',  'Обычный штурм',     'Стандартная атака на вражеский клуб.');
    interceptRoomAction(document.getElementById('labyrinthClubWarFullBtn'),   'raid_full', '💥', 'Полное вторжение',  'Мощная атака на вражеский клуб. Требует больше ресурсов.');
    interceptRoomAction(document.getElementById('labyrinthHelpMineBtn'),  'help_mine', '🤝', 'Помочь со сбором', 'Вы поможете другому игроку собрать добычу быстрее.');

    try { _lmClubWarInit(); } catch (e) {}

    var _mineConfirmed = false;
    var collectMineBtn = document.getElementById('labyrinthCollectMineBtn');
    if (collectMineBtn) {
      collectMineBtn.addEventListener('click', function(e) {
        if (_mineConfirmed) return;
        if (!_cfEnabled('collect_mine')) return;
        e.stopImmediatePropagation(); e.preventDefault();
        var pm = PW.labyrinthData && PW.labyrinthData.personalMine;
        var desc = pm ? 'Забрать ' + (pm.pending_acc || 0) + ' АСС и ' + (pm.pending_cards || 0) + ' карт из шахты ур. ' + (pm.level || 1) + '.' : 'Забрать накопленную добычу из персональной шахты.';
        document.getElementById('lm-boost-conf-ico').textContent   = '⛏';
        document.getElementById('lm-boost-conf-title').textContent = 'Забрать добычу?';
        document.getElementById('lm-boost-conf-desc').textContent  = desc;
        _pendingBoostBtn = collectMineBtn; _pendingIsRoomAction = true;
        document.getElementById('lm-boost-confirm').classList.add('on');
      }, true);
    }

    var upgradeMineBtn = document.getElementById('labyrinthUpgradeMineBtn');
    if (upgradeMineBtn) {
      upgradeMineBtn.addEventListener('click', function(e) {
        if (_mineConfirmed) return;
        if (!_cfEnabled('upgrade_mine')) return;
        e.stopImmediatePropagation(); e.preventDefault();
        var pm = PW.labyrinthData && PW.labyrinthData.personalMine;
        var price = pm ? (pm.next_upgrade_price || pm.upgrade_price || 1000) : 1000;
        var lvl   = pm ? (pm.level || 1) : 1;
        document.getElementById('lm-boost-conf-ico').textContent   = '⬆';
        document.getElementById('lm-boost-conf-title').textContent = 'Улучшить шахту?';
        document.getElementById('lm-boost-conf-desc').textContent  = 'Улучшение с уровня ' + lvl + ' до ' + (lvl + 1) + ' стоит ' + price + ' АСС.';
        _pendingBoostBtn = upgradeMineBtn; _pendingIsRoomAction = true;
        document.getElementById('lm-boost-confirm').classList.add('on');
      }, true);
    }

    var _buyConfirmed = false;
    var buyBtn = document.getElementById('labyrinthBuyAttemptBtn');
    if (buyBtn) {
      buyBtn.addEventListener('click', function(e) {
        if (_buyConfirmed) return;
        if (!_cfEnabled('buy_attempt')) return;
        e.stopImmediatePropagation(); e.preventDefault();
        var buyText    = document.getElementById('labyrinthBuyAttemptText');
        var buySubtext = document.getElementById('labyrinthBuyAttemptSubtext');
        var descText   = (buyText ? buyText.textContent.trim() : '') + (buySubtext ? ' ' + buySubtext.textContent.trim() : '');
        document.getElementById('lm-boost-conf-ico').textContent   = '👣';
        document.getElementById('lm-boost-conf-title').textContent = 'Купить +1 ход?';
        document.getElementById('lm-boost-conf-desc').textContent  = descText || 'Подтвердите покупку дополнительного хода';
        _pendingBoostBtn = buyBtn; _pendingIsBuy = true;
        document.getElementById('lm-boost-confirm').classList.add('on');
      }, true);
    }

    var _boostConfirmed = false;
    Object.keys(BOOST_INFO).forEach(function(btnId) {
      var btn = document.getElementById(btnId);
      if (!btn) return;
      btn.addEventListener('click', function(e) {
        if (_boostConfirmed) return;
        if (btn.disabled) return;
        if (!_cfEnabled('boost')) return;
        e.stopImmediatePropagation(); e.preventDefault();
        var info = BOOST_INFO[btnId];
        document.getElementById('lm-boost-conf-ico').textContent   = info.ico;
        document.getElementById('lm-boost-conf-title').textContent = 'Активировать «' + info.name + '»?';
        document.getElementById('lm-boost-conf-desc').textContent  = info.desc;
        _pendingBoostBtn = btn;
        document.getElementById('lm-boost-confirm').classList.add('on');
      }, true);
    });

    document.getElementById('lm-boost-conf-no').addEventListener('click', function() {
      document.getElementById('lm-boost-confirm').classList.remove('on');
      _pendingBoostBtn = null; _pendingIsBuy = false; _pendingIsRoomAction = false;
    });
    document.getElementById('lm-boost-confirm').addEventListener('click', function(e) {
      if (e.target === this) { this.classList.remove('on'); _pendingBoostBtn = null; _pendingIsBuy = false; _pendingIsRoomAction = false; }
    });
    document.getElementById('lm-boost-conf-yes').addEventListener('click', function() {
      document.getElementById('lm-boost-confirm').classList.remove('on');
      if (_pendingBoostBtn) {
        var btn = _pendingBoostBtn;
        var isBuy = _pendingIsBuy, isRoom = _pendingIsRoomAction;
        _pendingBoostBtn = null; _pendingIsBuy = false; _pendingIsRoomAction = false;
        if (isBuy)       { _buyConfirmed = true; }
        else if (isRoom) { _roomActionConfirmed = true; _mineConfirmed = true; }
        else             { _boostConfirmed = true; }
        btn.click();
        _boostConfirmed = false; _buyConfirmed = false; _roomActionConfirmed = false; _mineConfirmed = false;
      }
    });

    var tooltipPinned = false;
    function showTooltipAt(clientX, clientY) {
      var rect = elMbody.getBoundingClientRect(), cs = CELL_SIZE * fmScale;
      var cx = Math.floor((clientX - rect.left - fmX) / cs);
      var cy = Math.floor((clientY - rect.top  - fmY) / cs);
      var rooms = allRooms(), room = rooms[cx+'_'+cy];
      if (room) {
        var cur2 = curPos(), isCur = cx===cur2.x && cy===cur2.y;
        var gLine = room.guardian ? '<div class="tt-guard">'+(room.guardian==='guardian_user'?'👑 Личный страж':room.guardian==='guardian_club'?'🛡 Страж клуба':'⚐ Можно захватить')+'</div>' : '';
        var altFiltered2 = (room.altTypes||[]).filter(function(t){ return t!=='room_player'&&t!=='room_trap'&&t!=='room_gift'&&t!=='shield_block'&&t!==room.emissionEv; });
        var altLine2 = '';
        if (altFiltered2.length) {
          var isVar2 = room.event!=='unknown' && VARIABLE_EVENTS[room.event];
          if (isVar2) {
            var allVar2 = [ico(room.event)+' '+roomName(room.event)].concat(altFiltered2.map(function(t){ return ico(t)+' '+roomName(t); }));
            altLine2 = '<div class="tt-alt tt-var">🔀 Варианты: '+allVar2.join(' / ')+'</div>';
          } else {
            altLine2 = '<div class="tt-alt">Также видели: '+altFiltered2.map(function(t){ return ico(t)+' '+roomName(t); }).join(', ')+'</div>';
          }
        }
        var objLine2 = '';
        var knownType2 = room.event && room.event !== 'unknown';
        var realHdr2 = isCur ? '⚔ Ваша позиция' : (knownType2 ? ico(room.event)+' '+roomName(room.event) : '❔ Неизвестная комната');
        var realLine2 = isCur ? '' : '<div class="tt-desc" style="color:#9fd6ff">Реальный тип: '+(knownType2 ? roomName(room.event) : 'не найдено')+'</div>';
        var emLine2 = (!isCur && room.emissionVisited && room.emissionEv && room.emissionEv !== room.event)
          ? '<div class="tt-alt" style="color:#ffcf4d">⚡ Во время выброса: '+ico(room.emissionEv)+' '+roomName(room.emissionEv)+'</div>' : '';
        elTooltip.innerHTML = '<b>'+realHdr2+'</b>'+
          realLine2+objLine2+emLine2+altLine2+gLine+
          '<div class="tt-coord">'+formatCoords(cx,cy)+'</div>';
        var tw = Math.min(260, window.innerWidth - 16);
        var tx = clientX - tw / 2;
        if (tx < 8) tx = 8;
        if (tx + tw + 8 > window.innerWidth) tx = window.innerWidth - tw - 8;
        var ty = clientY - 130;
        if (ty < 60) ty = clientY + 20;
        elTooltip.style.left = tx + 'px'; elTooltip.style.top = ty + 'px';
        elTooltip.style.display = 'block'; tooltipPinned = true;
        return true;
      }
      elTooltip.style.display = 'none'; tooltipPinned = false; return false;
    }

    function showTooltipAndLoadDetail(clientX, clientY) {
      if (!showTooltipAt(clientX, clientY)) return;
      var rect=elMbody.getBoundingClientRect(), cs=CELL_SIZE*fmScale;
      var cx=Math.floor((clientX-rect.left-fmX)/cs), cy=Math.floor((clientY-rect.top-fmY)/cs);
      var raw=cloudMap && cloudMap[cx+'_'+cy];
      if (!raw || !(raw.dat && raw.dat.fast_overview)) return;
      loadRoomDetail(cx,cy).then(function(){ showTooltipAt(clientX,clientY); }).catch(function(){});
    }
    elMbody.addEventListener('click', function(e) { if (_lmPencil !== 'off') return; showTooltipAndLoadDetail(e.clientX, e.clientY); });
    elMbody.addEventListener('touchend', function(e) {
      if (_lmPencil !== 'off') return;
      if (pinchDist0 > 0) return;
      var t = e.changedTouches[0];
      var dx = t.clientX - tapStartX, dy = t.clientY - tapStartY;
      if (Math.sqrt(dx*dx + dy*dy) < 10) showTooltipAndLoadDetail(t.clientX, t.clientY);
    }, { passive: false });

    document.addEventListener('click', function(e) {
      if (tooltipPinned && !elMbody.contains(e.target)) {
        elTooltip.style.display = 'none'; tooltipPinned = false;
      }
    });
  }

  var _toastEl = null, _toastTimer = null;

  function showToast(title, sub, pct) {
    if (!_toastEl) _toastEl = document.getElementById('lm-toast');
    if (!_toastEl) return;
    var pctNum = (pct === undefined) ? -1 : pct;
    var barHtml = pctNum >= 0 ? '<div style="height:3px;border-radius:999px;background:rgba(255,255,255,.15);margin-top:4px;overflow:hidden"><div style="height:3px;border-radius:999px;background:linear-gradient(90deg,#4a90e2,#67e8f9);width:'+pctNum+'%"></div></div>' : '';
    _toastEl.innerHTML = '<div class="lm-toast-msg on"><b>'+title+'</b>'+(sub ? sub : '')+barHtml+'</div>';
    if (_toastTimer) clearTimeout(_toastTimer);
  }

  function hideToast(delay) {
    if (_toastTimer) clearTimeout(_toastTimer);
    _toastTimer = setTimeout(function() {
      if (!_toastEl) _toastEl = document.getElementById('lm-toast');
      if (_toastEl) _toastEl.innerHTML = '';
    }, delay || 2500);
  }

  function doFullRefresh() {
    var btn = document.getElementById('lm-brefresh');
    if (!btn || btn.classList.contains('loading')) return;
    btn.classList.add('loading'); btn.disabled = true;
    var d = mapData();
    var steps = d && d.steps && d.steps.length ? d.steps : [];
    var domOwn = getCurrentRoomOwnership();
    showToast('Обновление карты...', '', 10);
    var p = steps.length > 0 ? pushSteps(steps, SESSION_ID) : Promise.resolve();
    p.then(function() {
      showToast('Обновление карты...', '', 30);
      return new Promise(function(resolve) {
        ownershipCache = null;
        guardianDataCache = {user: null, club: null};
        fetchOwnership(function(hist) {
          var allOwn = domOwn.concat(hist);
          if (allOwn.length) pushSteps(allOwn, SESSION_ID+'_own').then(resolve).catch(resolve);
          else resolve();
        });
      });
    }).then(function() {
      showToast('Обновление карты...', '', 55);
      return new Promise(function(resolve) { loadCloud(resolve, function(count){ showToast('Обновление карты...', '', Math.min(88,55+Math.floor(count/500))); }); });
    }).then(function() {
      histCache = {}; histTotal = null; histCurPage = 1;
      window.lmLS.set(LS_HIST_DATE_KEY, new Date().toISOString());
      function loadPageSeq(pageNum) {
        return new Promise(function(resolve) {
          setTimeout(function() {
            loadHistPage(pageNum, function(result) {
              var total = histTotal || result.totalPages || 1;
              var pct = Math.round(60 + (pageNum / Math.max(total,1)) * 35);
              showToast('Обновление карты...', '', pct);
              if (pageNum < total) loadPageSeq(pageNum + 1).then(resolve);
              else resolve();
            });
          }, pageNum === 1 ? 0 : 700);
        });
      }
      showToast('Обновление карты...', '', 60);
      return loadPageSeq(1);
    }).then(function() {
      var pop = document.getElementById('lm-hpop');
      if (pop && pop.classList.contains('on')) renderHistPopup(histCache[1] || {rows:[], totalPages:1}, 1);
      checkAndSyncClub(true);
      showToast('Карта обновлена!', '', 100);
      hideToast(3000);
      btn.classList.remove('loading'); btn.disabled = false;
    }).catch(function(e) {
      showToast('Ошибка обновления', 'Попробуйте ещё раз');
      hideToast(4000);
      btn.classList.remove('loading'); btn.disabled = false;
    });
  }

  function fetchTrapBacksFromRollbackPage(callback) {
    var username = PW.visitor_name || '';
    if (!username) { callback(null); return; }
    fetch('/rollback/', { credentials: 'same-origin' })
      .then(function(r){ if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
      .then(function(html){
        var parser = new DOMParser();
        var doc = parser.parseFromString(html, 'text/html');
        var found = null;
        var userLink = doc.querySelector('a[href="/user/' + username + '/"].card-inline');
        if (userLink) {
          var valEl = userLink.querySelector('.users-top__value');
          if (valEl) {
            var m = valEl.textContent.replace(/[^0-9]/g, '');
            if (m) found = parseInt(m, 10);
          }
        }
        if (found === null) {
          var allCards = doc.querySelectorAll('.ncard__users-toplist .card-inline');
          allCards.forEach(function(card) {
            if (found !== null) return;
            var nameEl = card.querySelector('.card-inline__name');
            if (!nameEl) return;
            var name = nameEl.textContent.trim();
            if (name.toLowerCase() !== username.toLowerCase()) return;
            var valEl2 = card.querySelector('.users-top__value');
            if (valEl2) {
              var m2 = valEl2.textContent.replace(/[^0-9]/g, '');
              if (m2) found = parseInt(m2, 10);
            }
          });
        }
        var place = null;
        if (userLink) {
          var p = parseInt(userLink.getAttribute('data-place') || '', 10);
          if (!isNaN(p)) place = p;
        } else if (found !== null) {
          var allCards2 = doc.querySelectorAll('.ncard__users-toplist .card-inline');
          allCards2.forEach(function(card) {
            if (place !== null) return;
            var nameEl = card.querySelector('.card-inline__name');
            if (!nameEl || nameEl.textContent.trim().toLowerCase() !== username.toLowerCase()) return;
            var p2 = parseInt(card.getAttribute('data-place') || '', 10);
            if (!isNaN(p2)) place = p2;
          });
        }
        if (place !== null) window._lmRollbackPlace = place;
        lmDebug('[LabMap] /rollback/ откатов для', username, ':', found, '| Топ: №' + place);
        callback(found);
      })
      .catch(function(e){
        lmDebug('[LabMap] fetchTrapBacksFromRollbackPage:', e);
        callback(null);
      });
  }

  function doRefresh() {
    var btn=document.getElementById('lm-rbtn');
    btn.classList.add('loading'); btn.disabled=true;
    showToast('Обновление карты...', '', 10);
    setTimeout(function() {
      lmDebug('[Синхронизация] Автосинхронизация после обновления...');
      var tb = window._lmAllTimeTrapBacks;
      var tp = window._lmRollbackPlace;
      if (tb != null && tb > 0) {
        lmDebug('[Синхронизация] Отправляем откаты:', tb);
        sendRollbackToVPS(tb, tp || null);
      }
      sendAccHistoryToVPS();
      collectAndSendGuardians([]);
      setTimeout(function(){ checkAndSyncClub(true); }, 1000);
    }, 5000);
    syncCacheWithSession();
    flushStepsToServer();
    var d=mapData(), steps=d&&d.steps&&d.steps.length?d.steps:[];
    var domOwn=getCurrentRoomOwnership();
    var p=steps.length>0 ? pushSteps(steps,SESSION_ID) : Promise.resolve();
    p.then(function(){
      showToast('Обновление карты...', '', 30);
      return new Promise(function(resolve){
        ownershipCache = null;
        guardianDataCache = {user: null, club: null};
        fetchOwnership(function(hist){
          var allOwn=domOwn.concat(hist);
          var p1 = allOwn.length ? pushSteps(allOwn,SESSION_ID+'_own').catch(function(){}) : Promise.resolve();
          p1.then(function(){
            collectAndSendGuardians(allOwn);
          });
          p1.then(resolve).catch(resolve);
        });
      });
    }).then(function(){
      showToast('Обновление карты...', '', 55);
      return new Promise(function(resolve){ loadCloud(resolve, function(count){ showToast('Загрузка карты...', 'Карта: '+(count>=1000?Math.floor(count/1000)+'k ':count+' ')+'комнат', Math.min(88,55+Math.floor(count/500))); }); });
    }).then(function(){
      showToast('Обновление карты...', '', 75);
      var _ra = getAuth() || {};
      return Promise.all([
        window.lmFetch(VPS_URL+'/my/path', { headers: vpsAuthHeaders(false) })
          .then(function(r){ if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
          .then(function(d){
            if (d && Array.isArray(d.steps)) {
              window._lmMyPathData = {};
              d.steps.forEach(function(s){ window._lmMyPathData[s.x+'_'+s.y]=true; });
              window._lmHistSteps = d.steps;
              if (Array.isArray(d.emission_cells)) window._lmEmissionCells = new Set(d.emission_cells.map(function(c){ return c.x+'_'+c.y; }));
              lmDebug('[LabMap] My path:', d.steps.length, 'комнат из базы');
            }
          }).catch(function(){}),
        new Promise(function(resolveStats){
          fetchTrapBacksFromRollbackPage(function(rollbackCount) {
            if (rollbackCount !== null && rollbackCount > 0) {
              window._lmAllTimeTrapBacks = rollbackCount;
              lmDebug('[LabMap] /rollback/ откатов найдено:', rollbackCount);
              lmDebug('[Синхронизация] Вызываем sendRollbackToVPS...', typeof sendRollbackToVPS);
              sendRollbackToVPS(rollbackCount, window._lmRollbackPlace || null);
              resolveStats();
            } else {
              resolveStats();
            }
          });
        }).then(function(){
          var hpop = document.getElementById('lm-hpop');
          if (hpop && hpop.classList.contains('on')) {
            renderHistPopup(histCache[histCurPage||1] || {rows:[],totalPages:1}, histCurPage||1);
          }
          var whist = document.getElementById('lm-wi-hist');
          if (whist && whist.style.display !== 'none') {
            renderHistInline(histCache[histCurPage||1] || {rows:[],totalPages:1}, histCurPage||1);
          }
        })
      ]);
    }).then(function(){
      lmDebug('[Синхронизация] Отложенная отправка ACC-истории запущена');
      setTimeout(function() { sendAccHistoryToVPS(); }, 500);
      btn.classList.remove('loading'); btn.disabled=false;
      invalidateRoomsCache(); drawMini();
      if (fmOpen && showMyPath) drawFull();
      showToast('Карта обновлена!', '', 100);
      hideToast(2500);
      scheduleProfileSync();
      var currentMine = PW.labyrinthData && PW.labyrinthData.personalMine;
      if (currentMine && currentMine.has_mine) {
        var currentPosition = curPos();
        sendMineData(currentPosition.x, currentPosition.y, {
          level: Number(currentMine.level || 0),
          storage_pct: Number(currentMine.storage_progress || 0),
          acc_inside: Number(currentMine.pending_acc || currentMine.acc || 0),
          cards_current: Number(currentMine.pending_cards || currentMine.cards || 0),
          cards_max: Number(currentMine.max_cards || currentMine.cards_max || 0)
        });
      }
      setTimeout(function(){ checkAndSyncClub(true); }, 1000);
    }).catch(function(e){
      lmDebug('[LabMap] refresh err:',e);
      btn.classList.remove('loading'); btn.disabled=false;
    });
  }

  var guardianDataCache = {user:null, club:null};

  function collectAndSendGuardians(ownershipData) {
    var VPS_API  = LM_API_BASE;
    var auth = getAuth();
    if (!auth) return;
    var username = auth.username, token = auth.token;

    var guardians = [];

    if (ownershipData && ownershipData.length) {
      ownershipData.forEach(function(item) {
        if (!item || item.x == null || item.y == null) return;
        var ev = item.event_type || item.ev || item.guardian || '';
        var isClub = ev === 'guardian_club' || item.guardian_type === 'club';
        var isPersonal = ev === 'guardian_user' || item.guardian_type === 'personal' ||
                         ev.includes('own') || ev === 'protected';
        if (!isClub && !isPersonal) return;

        guardians.push({
          x: Number(item.x),
          y: Number(item.y),
          owner: item.owner || item.username || (isPersonal ? username : null),
          card_name: item.card_name || item.cardName || null,
          card_rank: item.card_rank || item.cardRank || null,
          tribute_count: item.tribute_count != null ? Number(item.tribute_count) : null,
          acc_per_pass: item.acc != null ? Number(item.acc) :
                        item.accPerPass != null ? Number(item.accPerPass) : null,
          guardian_type: isClub ? 'club' : 'personal',
          club_id: item.club_id || item.clubId || null,
          club_name: item.club_name || item.clubName || null,
          captured_by: item.captured_by || item.capturedBy || item.owner || item.username ||
                       (isPersonal ? username : null),
          captured_at: item.captured_at || item.capturedAt || null,
          confirmed: true,
          source: 'ownership'
        });
      });
    }

    var guardianRows = document.querySelectorAll(
      '.labyrinth-guardian-item, .guardian-row, [data-guardian], .ncard__guard-item'
    );
    guardianRows.forEach(function(el) {
      var xEl = el.querySelector('[data-x], .coord-x');
      var yEl = el.querySelector('[data-y], .coord-y');
      var ownerEl = el.querySelector('.guardian-owner, .owner-name, .card-inline__name');
      var cardEl  = el.querySelector('.card-name, .guardian-card');

      var x = xEl ? parseInt(xEl.getAttribute('data-x') || xEl.textContent) : null;
      var y = yEl ? parseInt(yEl.getAttribute('data-y') || yEl.textContent) : null;

      if (x == null || y == null) {
        var text = el.textContent || '';
        var m = text.match(/\(?\s*(-?\d+)\s*,\s*(-?\d+)\s*\)?/);
        if (m) { x = parseInt(m[1]); y = parseInt(m[2]); }
      }

      if (x == null || y == null) return;

      var rowText = (el.textContent || '').toLowerCase();
      var isClubGuardian = el.matches('[data-guardian="club"],.club-guardian') ||
                           rowText.indexOf('клуб') >= 0;
      var parsedOwner = ownerEl ? ownerEl.textContent.trim() : null;
      guardians.push({
        x: x,
        y: y,
        owner: parsedOwner || (!isClubGuardian ? username : null),
        card_name: cardEl ? cardEl.textContent.trim() : null,
        guardian_type: isClubGuardian ? 'club' : 'personal',
        captured_by: parsedOwner || (!isClubGuardian ? username : null),
        captured_at: null,
        confirmed: true,
        source: 'labyrinth_dom'
      });
    });

    var labData = PW.labyrinthData;
    if (labData && labData.guardians && labData.guardians.length) {
      labData.guardians.forEach(function(g) {
        if (g.x == null || g.y == null) return;
        guardians.push({
          x:         g.x,
          y:         g.y,
          owner:     g.owner || g.username || username,
          card_name: g.card_name || g.cardName || null,
          card_rank: g.card_rank || g.cardRank || null,
          tribute_count: g.tribute_count != null ? Number(g.tribute_count) : null,
          acc_per_pass: g.acc_per_pass != null ? Number(g.acc_per_pass) :
                        g.acc != null ? Number(g.acc) : null,
          guardian_type: (g.guardian_type === 'club' || g.guardian === 'guardian_club') ? 'club' : 'personal',
          club_id: g.club_id || null,
          club_name: g.club_name || null,
          captured_by: g.captured_by || g.capturedBy || username,
          captured_at: g.captured_at || g.capturedAt || null,
          confirmed: true,
          source: 'labyrinth_data'
        });
      });
    }

    if (!guardians.length) {
      lmDebug('[LGuard] Стражи не найдены');
      return;
    }

    var guardianByRoom = {};
    guardians.forEach(function(g) {
      if (!Number.isInteger(Number(g.x)) || !Number.isInteger(Number(g.y))) return;
      var key = Number(g.x) + '_' + Number(g.y);
      var score = (g.confirmed ? 8 : 0) + (g.owner ? 4 : 0) +
                  (g.card_name ? 2 : 0) + (g.club_id || g.club_name ? 2 : 0) +
                  (g.card_rank ? 1 : 0);
      if (!guardianByRoom[key] || score > guardianByRoom[key]._score) {
        guardianByRoom[key] = Object.assign({}, g, {_score: score});
      }
    });
    guardians = Object.keys(guardianByRoom).map(function(key) {
      var g = guardianByRoom[key];
      delete g._score;
      return g;
    }).filter(function(g) {
      return g.confirmed && (g.owner || g.card_name || g.club_id || g.club_name);
    });

    window.lmFetch(VPS_API + '/guardians', {
      method: 'POST',
      headers: vpsAuthHeaders(true),
      body: JSON.stringify({ guardians: guardians }),
    })
    .then(function(r){ if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
    .then(function(res){
      lmDebug('[LGuard] Стражи отправлены:', res.saved, '/', guardians.length);
    })
    .catch(function(e){
      lmDebug('[LGuard] Ошибка отправки стражей:', e.message);
    });
  }

  var _clubSyncPromise = null;
  var _clubSyncLastAt = 0;

  function checkAndSyncClub(force) {
    var now = Date.now();

    if (_clubSyncPromise) return _clubSyncPromise;
    if (!force && now - _clubSyncLastAt < 15000) return Promise.resolve(false);

    _clubSyncLastAt = now;
    _clubSyncPromise = Promise.resolve()
      .then(collectAndSendClubRooms)
      .catch(function() { return false; })
      .then(function(result) {
        _clubSyncPromise = null;
        return result;
      }, function() {
        _clubSyncPromise = null;
        return false;
      });

    return _clubSyncPromise;
  }

  function collectAndSendClubRooms() {
    var auth = getAuth();
    if (!auth) return Promise.resolve(false);
    var username = auth.username, token = auth.token;

    function parseClubDate(raw) {
      var text = String(raw || '').replace(/\s+/g, ' ').trim();
      var m = text.match(/(\d{2})\.(\d{2})\.(\d{4})\s*(?:в|,)?\s*(\d{1,2}):(\d{2})/i);
      if (!m) return null;
      var d = new Date(Number(m[3]), Number(m[2]) - 1, Number(m[1]), Number(m[4]), Number(m[5]), 0);
      return Number.isNaN(d.getTime()) ? null : d.toISOString();
    }

    function parseMember(item) {
      var nameEl = item.querySelector('.club__member-name');
      if (!nameEl) return null;
      var name = nameEl.textContent.trim();
      if (!name) return null;

      var statusEl = item.querySelector('.club__member-status');
      var contributionEl = item.querySelector('.club__member-contribution');
      var imageEl = item.querySelector('.club__member-image img');
      var status = statusEl ? statusEl.textContent.trim() : '';
      var role = 'member';

      if (/^Гильдмастер$/i.test(status)) role = 'leader';
      else if (/Зам\.?\s*Гильдмастера/i.test(status)) role = 'deputy';

      return {
        username: name,
        role: role,
        status: status || null,
        contribution: contributionEl ? Number((contributionEl.textContent || '').replace(/\D+/g, '')) || 0 : 0,
        is_online: item.classList.contains('club__member--online'),
        avatar: imageEl ? (imageEl.getAttribute('src') || null) : null
      };
    }

    function parseClubDocument(doc, clubUrl) {
      var root = doc.querySelector('.nclub.nclub-enter[data-id], .nclub-enter[data-id]');
      var rooms = [];
      var members = [];

      var dataId = root && root.getAttribute('data-id');
      var urlId = String(clubUrl || '').match(/\/clubs\/(\d+)(?:\/|$)/);
      var clubId = dataId && /^\d+$/.test(dataId) ? Number(dataId) :
                   urlId ? Number(urlId[1]) : null;

      var nameEl = doc.querySelector('.nclub-enter__main-name > div:first-child');
      var membersTextEl = doc.querySelector('.nclub-enter__main-members');
      var levelInfoEl = doc.querySelector('.nclub-enter__lvl-info > div:first-child');
      var activityEls = doc.querySelectorAll('.nclub-enter__activity-item');

      doc.querySelectorAll('.nclub-enter__members-list .club__member').forEach(function(item) {
        var member = parseMember(item);
        if (member) members.push(member);
      });

      var leaderMember = members.find(function(m) { return m.role === 'leader'; });
      var memberCount = members.length;
      if (membersTextEl) {
        var countMatch = membersTextEl.textContent.match(/(\d+)\s+участник/i);
        if (countMatch) memberCount = Number(countMatch[1]);
      }

      var enlightenment = null;
      if (membersTextEl) {
        var lightMatch = membersTextEl.textContent.match(/просветлен\w*\s+(\d+)/i);
        if (lightMatch) enlightenment = Number(lightMatch[1]);
      }
      if (enlightenment == null && activityEls.length > 1) {
        var n = Number((activityEls[1].querySelector('div') || {}).textContent || '');
        if (Number.isFinite(n)) enlightenment = n;
      }

      var level = null;
      if (levelInfoEl) {
        var levelMatch = levelInfoEl.textContent.match(/(\d+)\s+уровень/i);
        if (levelMatch) level = Number(levelMatch[1]);
      }

      var info = {
        id: clubId,
        name: nameEl ? nameEl.textContent.trim() : null,
        leader: leaderMember ? leaderMember.username : null,
        member_count: memberCount || 0,
        enlightenment: enlightenment,
        level: level
      };

      doc.querySelectorAll('.club-labyrinth__item').forEach(function(item) {
        var roomEl = item.querySelector('.club-labyrinth__room');
        if (!roomEl) return;

        var coords = parseRoomText(roomEl.textContent || '');
        if (!coords || !Number.isInteger(Number(coords.x)) || !Number.isInteger(Number(coords.y))) return;

        var ownerEl = item.querySelector('.club-labyrinth__text b');
        var dateEl = item.querySelector('.club-labyrinth__date');
        var badgeEl = item.querySelector('.club-labyrinth__badge');
        var avatarEl = item.querySelector('.club-labyrinth__avatar');

        var capturedBy = ownerEl ? ownerEl.textContent.trim() : null;

        rooms.push({
          x: Number(coords.x),
          y: Number(coords.y),
          club_id: info.id,
          club_name: info.name,
          club_owner: capturedBy,
          guardian_type: 'club',
          card_name: null,
          card_rank: null,
          captured_by: capturedBy,
          captured_at: dateEl ? parseClubDate(dateEl.textContent) : null,
          is_new: !!badgeEl,
          avatar: avatarEl ? (avatarEl.getAttribute('src') || null) : null,
          confirmed: true,
          source: 'club_page'
        });
      });

      return {club: info, rooms: rooms, members: members};
    }

    function send(payload) {
      var hasClub = payload && payload.club && (payload.club.id || payload.club.name);
      var hasRooms = payload && Array.isArray(payload.rooms) && payload.rooms.length;
      var hasMembers = payload && Array.isArray(payload.members) && payload.members.length;
      if (!hasClub && !hasRooms && !hasMembers) return Promise.resolve(false);

      return window.lmFetch(VPS_URL + '/club-rooms', {
        method: 'POST',
        headers: vpsAuthHeaders(true),
        body: JSON.stringify(payload)
      }).then(function(r) {
        if (!r.ok) throw new Error('HTTP ' + r.status);
        return r.json();
      }).then(function(res) {
        return true;
      }).catch(function(e) {
        lmDebug('[Карта] club sync send:', e);
        return false;
      });
    }

    function fetchText(url) {
      return fetch(url, {credentials: 'same-origin'}).then(function(r) {
        if (!r.ok) throw new Error('HTTP ' + r.status);
        return r.text();
      });
    }

    var parser = new DOMParser();
    var currentPath = window.location.pathname || '';

    if (/^\/clubs\/\d+\/?$/.test(currentPath) && document.querySelector('.nclub-enter[data-id]')) {
      return send(parseClubDocument(document, currentPath));
    }

    return fetchText('/user/' + encodeURIComponent(username) + '/')
      .then(function(html) {
        var profileDoc = parser.parseFromString(html, 'text/html');
        var clubLink = profileDoc.querySelector('.usn__club-item-top a[href*="/clubs/"]');

        if (!clubLink) {
          var links = profileDoc.querySelectorAll('a[href*="/clubs/"]');
          for (var i = 0; i < links.length; i++) {
            var href = links[i].getAttribute('href') || '';
            if (/\/clubs\/\d+\/?/.test(href)) {
              clubLink = links[i];
              break;
            }
          }
        }

        if (!clubLink) throw new Error('club_link_not_found');

        var href = clubLink.getAttribute('href');
        return fetchText(href).then(function(clubHtml) {
          return send(parseClubDocument(parser.parseFromString(clubHtml, 'text/html'), href));
        });
      })
      .catch(function(e) {
        lmDebug('[Карта] club sync:', e);
        return false;
      });
  }

  function loadGuardianPopup(type) {
    var listEl=document.getElementById(type==='user'?'lm-glist':'lm-clist');
    var countEl=document.getElementById(type==='user'?'lm-gcount':'lm-ccount');
    if(!listEl) return;
    if(guardianDataCache[type]){ renderGuardianList(type,guardianDataCache[type],listEl,countEl); return; }
    listEl.className='lm-spop-list lm-spop-loading'; listEl.textContent='Загрузка...';
    var username=PW.visitor_name||'';
    if(!username){ listEl.textContent='Не удалось определить пользователя.'; return; }
    var parser=new DOMParser();
    fetch('/user/'+encodeURIComponent(username)+'/')
    .then(function(r){ if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
    .then(function(html){
      var doc=parser.parseFromString(html,'text/html');
      if(type==='user'){
        var items=parseUserGuardianItems(doc);
        guardianDataCache.user=items; renderGuardianList(type,items,listEl,countEl);
      } else {
        var clubLink=findClubLink(doc);
        if(!clubLink){ listEl.textContent='Клуб не найден.'; return; }
        return fetch(clubLink.getAttribute('href')).then(function(r){ if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
        .then(function(ch){
          var items=parseClubGuardianItems(parser.parseFromString(ch,'text/html'));
          guardianDataCache.club=items; renderGuardianList(type,items,listEl,countEl);
        });
      }
    }).catch(function(){ listEl.textContent='Ошибка загрузки.'; });
  }

  function renderGuardianList(type, items, listEl, countEl) {
    if(countEl) countEl.textContent='('+items.length+')';
    if(!items.length){ listEl.className='lm-spop-list lm-spop-loading'; listEl.textContent='Нет данных.'; return; }
    listEl.className='lm-spop-list'; listEl.innerHTML='';
    items.forEach(function(it){
      var div=document.createElement('div'); div.className='lm-si'; div.title='Перейти на карте';
      div.innerHTML=(it.img?'<img class="lm-si-img" src="'+it.img+'" alt="">':'')+
        '<div class="lm-si-info"><div class="lm-si-name">'+it.name+'</div><div class="lm-si-room">'+it.room+'</div><div class="lm-si-date">'+it.date+'</div></div>'+
        '<div class="lm-si-nav">→</div>';
      div.addEventListener('click', function(){
        closeAllPopups();
        highlightRoom = {x: it.x, y: it.y, until: Date.now() + 3000};
        openFull(true);
        setTimeout(function() { centerOn(it.x, it.y, FULL_FOCUS_SCALE); }, 50);
      });
      listEl.appendChild(div);
    });
  }

  function loadWbGuardian(type, container) {
    if (!container) return;
    if (guardianDataCache[type]) { renderWbGuardianList(type, guardianDataCache[type], container); return; }
    container.innerHTML = '<div class="lm-wi-loading">Загрузка...</div>';
    var username = PW.visitor_name || '';
    if (!username) { container.innerHTML = '<div class="lm-wi-empty">Пользователь не определён</div>'; return; }
    var parser = new DOMParser();
    fetch('/user/'+encodeURIComponent(username)+'/')
      .then(function(r){ if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
      .then(function(html){
        var doc = parser.parseFromString(html, 'text/html');
        if (type === 'user') {
          var items = parseUserGuardianItems(doc);
          guardianDataCache.user = items; renderWbGuardianList('user', items, container);
        } else {
          var clubLink = findClubLink(doc);
          if (!clubLink) { container.innerHTML = '<div class="lm-wi-empty">Клуб не найден</div>'; return; }
          return fetch(clubLink.getAttribute('href')).then(function(r){ if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
            .then(function(ch){
              var items = parseClubGuardianItems(parser.parseFromString(ch, 'text/html'));
              guardianDataCache.club = items; renderWbGuardianList('club', items, container);
            });
        }
      }).catch(function(){ container.innerHTML = '<div class="lm-wi-empty">Ошибка загрузки</div>'; });
  }

  function renderWbGuardianList(type, items, container) {
    if (!items.length) { container.innerHTML = '<div class="lm-wi-empty">Нет данных</div>'; return; }
    var html = items.map(function(it){
      return '<div class="lm-wi-item" data-x="'+it.x+'" data-y="'+it.y+'">'+
        (it.img ? '<img class="lm-wi-img" src="'+it.img+'" alt="">' : '')+
        '<div class="lm-wi-info"><div class="lm-wi-name">'+it.name+'</div><div class="lm-wi-room">'+it.room+'</div><div class="lm-wi-date">'+it.date+'</div></div>'+
        '<div class="lm-wi-nav">→</div></div>';
    }).join('');
    container.innerHTML = html;
    container.querySelectorAll('.lm-wi-item').forEach(function(el){
      el.addEventListener('click', function(){
        var x = parseInt(this.getAttribute('data-x'), 10);
        var y = parseInt(this.getAttribute('data-y'), 10);
        highlightRoom = {x: x, y: y, until: Date.now() + 3000};
        openFull(true); setTimeout(function(){ centerOn(x, y, FULL_FOCUS_SCALE); }, 50);
      });
    });
  }

  function renderHistInline(result, page) {
    var container = document.querySelector('#lm-wi-hist .lm-hist-body');
    if (!container) return;
    var totalPages = histTotal || result.totalPages || 1;
    var rows = result.rows;
    var stats = buildStepsStats();
    var statsHtml = '';
    if (stats) {
      var acc = buildAccTotals();
      statsHtml =
        '<div class="lm-sc-grid" style="margin:10px;">'+
          '<div class="lm-sc-card"><div class="lm-sc-label">Всего комнат</div><div class="lm-sc-val">'+stats.allTimeTotal+'</div></div>'+
          '<div class="lm-sc-card lm-sc-trap">' +
            '<div class="lm-sc-label">Откаты</div>' +
            '<div class="lm-sc-val lm-sc-bad">' + (stats.historyLoaded ? (stats.trapCount > 0 ? '−'+stats.trapCount : '0') : '?') + '</div>' +
            '<div class="lm-sc-trap-row">' +
              (stats.historyLoaded && stats.lostRooms > 0
                ? '<span class="lm-sc-rooms">−'+stats.lostRooms+' комнат</span>'
                : '<span class="lm-sc-hint">'+(stats.historyLoaded?'нет откатов':'нажми Обновить')+'</span>') +
              (window._lmRollbackPlace ? '<span class="lm-sc-badge">Топ #'+window._lmRollbackPlace+'</span>' : '') +
            '</div>' +
          '</div>'+
          '<div class="lm-sc-card"><div class="lm-sc-label">Заработано</div><div class="lm-sc-val lm-sc-good">+'+acc.earned+'</div></div>'+
          '<div class="lm-sc-card"><div class="lm-sc-label">Потеряно</div><div class="lm-sc-val lm-sc-bad">−'+acc.lost+'</div></div>'+
        '</div>';
    }
    var rowsHtml = rows.length
      ? rows.map(function(row){
          var amt = parseFloat(row.amount.replace(/[^0-9.\-+]/g,''));
          var isPlus = (!isNaN(amt)&&amt>0)||row.amount.indexOf('+')!==-1;
          var isMinus= (!isNaN(amt)&&amt<0)||row.amount.indexOf('-')!==-1;
          var amtCls = isPlus?'lm-ha-plus':isMinus?'lm-ha-minus':'';
          var hi = histIcon(row.desc);
          return '<div class="lm-hr">'+
            '<div class="lm-hr-ico" style="background:'+hi.color+'18;border-color:'+hi.color+'44"><span style="color:'+hi.color+'">'+hi.icon+'</span></div>'+
            '<div class="lm-hr-main"><div class="lm-hr-desc">'+row.desc+'</div><div class="lm-hr-meta">'+row.date+'</div></div>'+
            '<div class="lm-hr-amt '+amtCls+'">'+row.amount+(row.amount.indexOf('ACC')===-1?' ACC':'')+'</div>'+
          '</div>';
        }).join('')
      : '<div class="lm-hist-empty">Записей не найдено (стр. '+page+')</div>';
    var pagHtml = '';
    if (totalPages > 1) {
      var start = Math.max(1,page-2), end = Math.min(totalPages,page+2), nums='';
      if(start>1) nums+='<button class="lm-hp-btn" data-p="1">1</button>'+(start>2?'<span class="lm-hp-dots">…</span>':'');
      for(var pi=start;pi<=end;pi++) nums+='<button class="lm-hp-btn'+(pi===page?' lm-hp-cur':'')+'" data-p="'+pi+'">'+pi+'</button>';
      if(end<totalPages) nums+=(end<totalPages-1?'<span class="lm-hp-dots">…</span>':'')+'<button class="lm-hp-btn" data-p="'+totalPages+'">'+totalPages+'</button>';
      pagHtml='<div class="lm-hist-pag">'+
        '<button class="lm-hp-nav" id="lm-wi-hprev" '+(page<=1?'disabled':'')+'>‹</button>'+nums+
        '<button class="lm-hp-nav" id="lm-wi-hnext" '+(page>=totalPages?'disabled':'')+'>›</button>'+
      '</div>';
    }
    container.innerHTML = statsHtml +
      '<div class="lm-hist-rows-head" style="padding:4px 10px;">📜 Страница '+page+'</div>'+
      '<div class="lm-hist-rows" style="padding:0 10px 8px;">'+rowsHtml+'</div>'+pagHtml;
    container.querySelectorAll('.lm-hp-btn[data-p]').forEach(function(btn){
      btn.addEventListener('click', function(e){
        e.stopPropagation();
        var p = parseInt(this.dataset.p,10);
        histCurPage = p;
        container.innerHTML = '<div class="lm-wi-loading">Загрузка стр. '+p+'...</div>';
        loadHistPage(p, function(r){ renderHistInline(r, p); });
      });
    });
    var prev = container.querySelector('#lm-wi-hprev');
    var next = container.querySelector('#lm-wi-hnext');
    if(prev) prev.addEventListener('click', function(e){
      e.stopPropagation();
      if(page>1){ histCurPage=page-1; container.innerHTML='<div class="lm-wi-loading">Загрузка...</div>'; loadHistPage(page-1,function(r){renderHistInline(r,page-1);}); }
    });
    if(next) next.addEventListener('click', function(e){
      e.stopPropagation();
      if(page<totalPages){ histCurPage=page+1; container.innerHTML='<div class="lm-wi-loading">Загрузка...</div>'; loadHistPage(page+1,function(r){renderHistInline(r,page+1);}); }
    });
  }

  function buildUI() {
    if (document.getElementById('lm-wrap')) return;
    injectStyles(); buildDOM(); bindEvents();
    lmInfo('Карта готова');
  }

  var LS_AUTO_SYNC_KEY = 'lm_last_auto_sync' + _lmUserSuffix;
  var AUTO_SYNC_INTERVAL = 24 * 60 * 60 * 1000;

  function autoSyncIfNeeded() {
    var lastSync = parseInt(window.lmLS.get(LS_AUTO_SYNC_KEY) || '0', 10);
    var now = Date.now();
    if (now - lastSync < AUTO_SYNC_INTERVAL) {
      var cacheLoaded = loadFromCache();
      var LS_CLOUD_UPDATED = 'lm_cloud_last_updated';
      var lastCloud = parseInt(window.lmLS.get(LS_CLOUD_UPDATED) || '0', 10);

      loadCloud(function() {
        window.lmLS.set(LS_CLOUD_UPDATED, String(Date.now()));
        drawMini();
        if (fmOpen) drawFull();
      });
      return;
    }
    setTimeout(function() {
      showToast('Обновление карты...', '', 5);
      var d = mapData(), steps = d && d.steps && d.steps.length ? d.steps : [];
      var domOwn = getCurrentRoomOwnership();
      var p = steps.length > 0 ? pushSteps(steps, SESSION_ID) : Promise.resolve();
      p.then(function() {
        showToast('Обновление карты...', '', 25);
        return new Promise(function(resolve) {
          ownershipCache = null; guardianDataCache = {user: null, club: null};
          fetchOwnership(function(hist) {
            var allOwn = domOwn.concat(hist);
            if (allOwn.length) pushSteps(allOwn, SESSION_ID+'_own').then(resolve).catch(resolve);
            else resolve();
          });
        });
      }).then(function() {
        showToast('Обновление карты...', '', 50);
        return new Promise(function(resolve) {
          loadCloud(resolve, function(count) {
            var pct = Math.min(88, 50 + Math.floor(count / 500));
            showToast('Обновление карты...', '', pct);
          });
        });
      }).then(function() {
        histCache = {}; histTotal = null; histCurPage = 1;
        window.lmLS.set(LS_HIST_DATE_KEY, new Date().toISOString());
        function autoLoadPage(pageNum) {
          return new Promise(function(resolve) {
            setTimeout(function() {
              loadHistPage(pageNum, function(result) {
                var total = histTotal || result.totalPages || 1;
                showToast('Авто-синхронизация...', 'История: стр. '+pageNum+' из '+total, Math.round(55 + (pageNum / Math.max(total,1)) * 40));
                if (pageNum < total && pageNum < 20) autoLoadPage(pageNum + 1).then(resolve);
                else resolve();
              });
            }, pageNum === 1 ? 0 : 700);
          });
        }
        return autoLoadPage(1);
      }).then(function() {
        window.lmLS.set(LS_AUTO_SYNC_KEY, String(Date.now()));
        showToast('Авто-синхронизация завершена!', 'Следующая через 24 часа', 100);
        hideToast(3000); drawMini();
      }).catch(function(e) { lmDebug('[LabMap] Auto-sync error:', e); hideToast(1000); });
    }, 3000);
  }

  function init() {
    lmInfo('Карта включена');
    _routeLoad();
    _routeRestoreFromServer();
    syncCacheWithSession();
    var _lastFlushCount = parseInt(window.lmLS.get(LS_FLUSH_COUNT) || '0');
    var _currentSteps   = mapData() && mapData().steps ? mapData().steps.length : 0;
    var _cachedSteps    = getCachedSteps();
    var _sentIdx        = getSentCount();
    var _hasPending     = _cachedSteps.length > _sentIdx;
    if (_sentIdx >= _cachedSteps.length && _currentSteps <= _lastFlushCount) {
      _hasPending = false;
    }
    if (_currentSteps > _lastFlushCount || _hasPending) {
      lmDebug('[LabMap] Отправка при запуске: шагов=' + _currentSteps + ' lastFlush=' + _lastFlushCount + ' pending=' + _hasPending);
      flushStepsToServer();
    } else {
      lmDebug('[LabMap] Новых данных нет — отправка пропущена');
    }
    window.addEventListener('beforeunload', function() {
      var d = mapData();
      if (d && d.steps && d.steps.length) cacheSteps(d.steps);
    });
    buildUI(); drawMini(); injectTopBtn();
    autoSyncIfNeeded();
    patchSiteMapDelayed();
    setTimeout(function() {
      fetchTrapBacksFromRollbackPage(function(count) {
        if (count !== null) {
          window._lmAllTimeTrapBacks = count;
          lmDebug('[LabMap] /rollback/ откатов при старте:', count);
        }
      });
    }, 1500);

    var lastN   = mapData()&&mapData().steps ? mapData().steps.length : 0;
    var c0      = curPos();
    var lastPos = c0.x+'_'+c0.y;

    var _stepProcessing = false;
    var _pendingStep = null;

    (function() {
      var mapEl = document.getElementById('labyrinthMap');
      if (!mapEl) { lmDebug('[LabMap] labyrinthMap not found'); return; }

      mapEl.addEventListener('click', function(e) {
        if (_lmPatching || _stepProcessing) return;
        var cell = e.target.closest
          ? e.target.closest('.labyrinth-cell--available')
          : (e.target.classList.contains('labyrinth-cell--available') ? e.target : null);
        if (!cell) return;
        var cur = curPos();
        var mapElLocal = document.getElementById('labyrinthMap');
        var cells = mapElLocal.querySelectorAll('.labyrinth-cell');
        var center = detectGridCenter(cells);
        if (!center) return;
        var cellsArr = Array.prototype.slice.call(cells);
        var idx = cellsArr.indexOf(cell);
        if (idx < 0) return;
        var gx, gy;
        if (center.useIndex) { gx = idx % 25; gy = Math.floor(idx / 25); }
        else {
          gx = parseInt(cell.getAttribute('data-x') || '', 10);
          gy = parseInt(cell.getAttribute('data-y') || '', 10);
          if (isNaN(gx) || isNaN(gy)) { gx = idx % 25; gy = Math.floor(idx / 25); }
        }
        var targetX = cur.x + (gx - center.gx);
        var targetY = cur.y + (gy - center.gy);
        lmDebug('[LabMap] cell click target:', targetX, targetY);
        _pendingStep = {cx: targetX, cy: targetY};
        _stepProcessing = true;
        waitForStep(targetX, targetY);
      }, true);

      var obs = new MutationObserver(function(mutations) {
        if (_lmPatching) return;
        var hasCurrentChange = false;
        for (var i = 0; i < mutations.length; i++) {
          var m = mutations[i];
          if (m.type === 'attributes' && m.attributeName === 'class') {
            if (m.target.classList &&
               (m.target.classList.contains('labyrinth-cell--current') ||
               (m.oldValue && m.oldValue.indexOf('labyrinth-cell--current') >= 0))) {
              hasCurrentChange = true; break;
            }
          }
          if (m.type === 'childList' && m.addedNodes.length > 10) { hasCurrentChange = true; break; }
        }
        if (!hasCurrentChange) return;
        var d = PW.labyrinthData;
        if (!d || !d.mapData || !d.mapData.current) return;
        var cx = d.mapData.current.x, cy = d.mapData.current.y;
        var pos = cx + '_' + cy;
        if (_stepProcessing && _pendingStep && _pendingStep.cx === cx && _pendingStep.cy === cy) {
          lastPos = pos; return;
        }
        if (pos === lastPos) return;
        if (_stepProcessing) return;
        lastPos = pos; _stepProcessing = true;
        lmDebug('[LabMap] step (observer):', cx, cy);
        waitForStep(cx, cy);
      });

      obs.observe(mapEl, {
        subtree: true, attributes: true,
        attributeFilter: ['class'], attributeOldValue: true, childList: true
      });
    })();

    _lmLoadPositions();
    setInterval(_lmLoadPositions, 20000);
    _lmLoadDirectives();
    setInterval(_lmLoadDirectives, 60000);

    setInterval(_lmLoadFatigue, 30000);

    setInterval(function() {
      if (_stepProcessing) return;
      var d = mapData(), n = d && d.steps ? d.steps.length : 0;
      var c = curPos(), pos = c.x + '_' + c.y;
      if (n !== lastN || pos !== lastPos) {
        lastN = n; lastPos = pos; _stepProcessing = true;
        lmDebug('[LabMap] fallback detector:', pos);
        waitForStep(c.x, c.y);
      }
    }, 5000);

    function inferCurrentEventFromDom(fallbackEvent) {
      var ev = fallbackEvent || 'unknown';
      if (ev && ev !== 'unknown' && ev !== 'empty') return ev;

      var text = '';
      var selectors = [
        '.labyrinth-last-event',
        '.labyrinth-event',
        '.labyrinth-info',
        '.labyrinth-sidebar',
        '#labyrinth',
        'main'
      ];
      for (var i = 0; i < selectors.length; i++) {
        var el = document.querySelector(selectors[i]);
        if (el && el.textContent) text += ' ' + el.textContent;
      }
      text = text.toLowerCase();

      var rules = [
        [/печать коллекционера|проверка одинаковых карт|коллекц/i, 'collection'],
        [/чужой подарок|подарок/i, 'room_gift'],
        [/замкненн(?:ый|ого) сундук|закрыт(?:ый|ого) сундук/i, 'locked_chest'],
        [/мини.?босс/i, 'mini_boss'],
        [/хард.?босс|сложн\w* босс/i, 'hard_boss'],
        [/викторин/i, 'quiz'],
        [/пазл|испытание дао/i, 'puzzle'],
        [/откат|ловушка отката/i, 'trap_back'],
        [/реликв/i, 'relic_room'],
        [/эхо|відгомін/i, 'echo_room'],
        [/пусто|обычная комната|центральная комната/i, 'empty']
      ];
      for (var r = 0; r < rules.length; r++) {
        if (rules[r][0].test(text)) return rules[r][1];
      }
      return ev || 'unknown';
    }

    var _syntheticStepNonce = 0;
    var _lastSyntheticKey = '';

    function sendSyntheticCurrentStep(cx, cy) {
      var key = cx + '_' + cy;
      if (_lastSyntheticKey === key) return;
      _lastSyntheticKey = key;

      setTimeout(function() {
        var ev = inferCurrentEventFromDom('unknown');
        var bankEl = document.getElementById('labyrinthBank');
        var accAfter = bankEl
          ? parseInt(String(bankEl.textContent || '').replace(/\s+/g, ''), 10)
          : null;

        var synthetic = {
          x: Number(cx),
          y: Number(cy),
          event: ev || 'unknown',
          ev: ev || 'unknown',
          acc_after: Number.isFinite(accAfter) ? accAfter : null,
          session_id: PUSH_SESSION_ID,
          step_index: 'synthetic_' + Date.now() + '_' + (++_syntheticStepNonce),
          ts: new Date().toISOString(),
          _lmSynthetic: true
        };

        var det = scrapeRoomDetails(synthetic.event);
        var emDat = getEmissionStepDat();
        synthetic._dat = det ? Object.assign({}, emDat, det) : (emDat || undefined);

        schedulePush([synthetic], PUSH_SESSION_ID);
        syncProfileToVPS(true);
      }, 850);
    }

    function waitForStep(cx, cy) {
      var targetKey = cx + '_' + cy;
      var maxTries = 20, tryCount = 0;
      function tryUpdate() {
        tryCount++;
        var curD = mapData();
        var steps = curD && curD.steps ? curD.steps : [];
        var lastStep = steps.length > 0 ? steps[steps.length - 1] : null;
        var stepReady = lastStep && (lastStep.x + '_' + lastStep.y === targetKey);
        var mapEl = document.getElementById('labyrinthMap');
        var curCell = mapEl && mapEl.querySelector('.labyrinth-cell--current');
        if (tryCount >= maxTries && !(stepReady && curCell)) {
          _pendingStep = null;
          _stepProcessing = false;

          var current = curD && curD.current ? curD.current : null;
          if (current &&
              Number(current.x) === Number(cx) &&
              Number(current.y) === Number(cy)) {
            sendSyntheticCurrentStep(cx, cy);
          }
          return;
        }
        if (stepReady && curCell) {
          lastN = steps.length; lastPos = targetKey; _pendingStep = null;
          invalidateRoomsCache(); updateMapInfo(); patchSiteMap();
          if (fmOpen) { drawFull(); centerOn(cx, cy); }
          if (steps.length > 0) {
            var lastS = steps[steps.length-1];
            setTimeout(function() {
              lastS.event = inferCurrentEventFromDom(lastS.event);
              lastS.ev = lastS.event;
              lastS.session_id = PUSH_SESSION_ID;
              var det = scrapeRoomDetails(lastS.event);
              var emDat = getEmissionStepDat();
              lastS._dat = det ? Object.assign({}, emDat, det) : (emDat || undefined);
              schedulePush(steps, PUSH_SESSION_ID);
            }, 650);
          }

          if (window._lmMyPathData) {
            window._lmMyPathData[cx + '_' + cy] = true;
          }

          _stepProcessing = false;
          lmDebug('[Карта] Комната обработана:', cx, cy);
        } else {
          setTimeout(tryUpdate, 150);
        }
      }
      setTimeout(tryUpdate, 100);
    }
  }

  function wait() {
    var MAX_TRIES=100, tries=0;
    function check() {
      tries++;
      var d2=mapData();
      if (d2&&d2.steps&&d2.steps.length>0) { lmDebug('[LabMap] Найдено! steps:',d2.steps.length); init(); }
      else if (tries<MAX_TRIES) setTimeout(check,300);
      else lmDebug('[LabMap] Data not found after',MAX_TRIES,'tries');
    }
    check();
  }

  if (IS_LABYRINTH) { wait(); }

  (function lmChat() {
    if (!IS_LABYRINTH || !(PW.visitor_name || '')) return;
    var POLL_MS = 3000, OLD_CHUNK = 40, isOpen = false, lastId = 0, firstId = 0, pollT = null, myNick = null, isAdmin = false, meLoaded = false, loadingNew = false, loadingOld = false, noMoreOld = false, seenIds = {}, replyTo = null;
    function cesc(s) { return String(s == null ? '' : s).replace(/[&<>"]/g, function (c) { return { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;' }[c]; }); }
    function cget(p) { return window.lmFetch(VPS_URL + p, { headers: vpsAuthHeaders(false), cache: 'no-store' }).then(function (r) { return r.json(); }); }
    function cpost(p, body) { return window.lmFetch(VPS_URL + p, { method: 'POST', headers: vpsAuthHeaders(true), body: JSON.stringify(body || {}) }).then(function (r) { return r.json().then(function (d) { return { ok: r.ok, d: d }; }); }); }

    function injectCss() {
      var s = document.createElement('style');
      s.textContent =
        '#lmc-panel{position:fixed;right:20px;bottom:82px;width:min(380px,calc(100vw - 32px));height:min(540px,calc(100vh - 130px));background:linear-gradient(180deg,#1b1d2a,#12131d);border:1px solid rgba(255,255,255,.1);border-radius:16px;box-shadow:0 20px 55px rgba(0,0,0,.55);z-index:2147482901;display:none;flex-direction:column;overflow:hidden;color:#eaf1ff}' +
        '#lmc-panel.on{display:flex}' +
        '#lmc-head{display:flex;align-items:center;justify-content:space-between;padding:12px 15px;border-bottom:1px solid rgba(255,255,255,.08);flex:0 0 auto}' +
        '#lmc-head b{font-size:15px}' +
        '#lmc-head .lmc-x{background:rgba(255,255,255,.08);border:none;color:#fff;width:30px;height:30px;border-radius:8px;font-size:17px;cursor:pointer}' +
        '#lmc-me{font-size:11px;color:#8093b6;margin-top:2px}' +
        '#lmc-me a{color:#6aa9ff;cursor:pointer;text-decoration:underline}' +
        '#lmc-body{flex:1;overflow-y:auto;padding:10px 12px;display:flex;flex-direction:column;gap:8px}' +
        '.lmc-msg{font-size:14px;line-height:1.4;word-break:break-word;overflow-wrap:anywhere}' +
        '.lmc-msg .lmc-nick{font-weight:800;color:#8fb4ff;margin-right:6px}' +
        '.lmc-msg.mine .lmc-nick{color:#6ee786}' +
        '.lmc-msg .lmc-adm{color:#ff8f8f}' +
        '.lmc-msg .lmc-t{font-size:10px;color:#5b6b8c;margin-left:6px}' +
        '.lmc-msg{position:relative;padding-right:26px}' +
        '.lmc-reply-btn{position:absolute;top:0;right:0;background:none;border:none;color:#5b6b8c;cursor:pointer;font-size:13px;opacity:0;transition:opacity .12s;padding:0 3px}' +
        '.lmc-msg:hover .lmc-reply-btn{opacity:1}' +
        '.lmc-quote{display:block;border-left:3px solid #4a5ac7;background:rgba(74,90,199,.12);border-radius:5px;padding:3px 8px;margin:0 0 4px;font-size:12px;line-height:1.35;cursor:pointer}' +
        '.lmc-quote .lmc-qn{font-weight:800;color:#8fb4ff;display:block}' +
        '.lmc-quote .lmc-qt{color:#9fb0d0;opacity:.85;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;display:block}' +
        '#lmc-replybar{display:none;align-items:center;gap:8px;padding:7px 12px;border-top:1px solid rgba(255,255,255,.08);background:rgba(74,90,199,.1);font-size:12.5px}' +
        '#lmc-replybar.on{display:flex}' +
        '#lmc-replybar .lmc-rb-txt{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:#b9c6e6}' +
        '#lmc-replybar .lmc-rb-txt b{color:#8fb4ff}' +
        '#lmc-replybar .lmc-rb-x{background:rgba(255,255,255,.1);border:none;color:#fff;width:22px;height:22px;border-radius:6px;cursor:pointer;flex:0 0 auto}' +
        '#lmc-foot{flex:0 0 auto;border-top:1px solid rgba(255,255,255,.08);padding:10px 12px;display:flex;gap:8px}' +
        '#lmc-in{flex:1;background:#0a1020;border:1px solid rgba(255,255,255,.14);border-radius:10px;color:#fff;padding:9px 11px;font-size:14px;outline:none;resize:none;max-height:80px}' +
        '#lmc-send{background:linear-gradient(135deg,#4a5ac7,#3544a5);border:none;color:#fff;border-radius:10px;padding:0 15px;font-weight:800;cursor:pointer}' +
        '#lmc-nickbox{padding:22px 18px;display:flex;flex-direction:column;gap:12px}' +
        '#lmc-nickbox .lmc-h{font-size:16px;font-weight:800}' +
        '#lmc-nickbox .lmc-hint{font-size:12.5px;color:#8093b6;line-height:1.5}' +
        '#lmc-nickin{background:#0a1020;border:1px solid rgba(255,255,255,.14);border-radius:10px;color:#fff;padding:10px 12px;font-size:15px;outline:none}' +
        '#lmc-nickok{background:linear-gradient(135deg,#4a5ac7,#3544a5);border:none;color:#fff;border-radius:10px;padding:11px;font-weight:800;cursor:pointer;font-size:14px}' +
        '#lmc-nickmsg{font-size:12.5px;min-height:16px}' +
        '.lmc-err{color:#ff8b8b}.lmc-ok{color:#6ee786}';
      document.head.appendChild(s);
    }

    function el(id) { return document.getElementById(id); }
    function nearBottom() { var b = el('lmc-body'); return b && (b.scrollHeight - b.scrollTop - b.clientHeight < 60); }
    function toBottom() { var b = el('lmc-body'); if (b) b.scrollTop = b.scrollHeight; }

    function msgsHtml(list) {
      return list.map(function (m) {
        var mine = myNick && m.display_nick === myNick;
        var t = ''; try { t = new Date(m.created_at).toLocaleTimeString('uk-UA', { hour: '2-digit', minute: '2-digit' }); } catch (e) {}
        var quote = '';
        if (m.reply_nick) {
          quote = '<span class="lmc-quote"' + (m.reply_to_id ? ' data-to="' + Number(m.reply_to_id) + '"' : '') + '><span class="lmc-qn">' + cesc(m.reply_nick) + '</span><span class="lmc-qt">' + cesc(m.reply_text || '') + '</span></span>';
        }
        return '<div class="lmc-msg' + (mine ? ' mine' : '') + '" data-id="' + Number(m.id) + '" data-nick="' + cesc(m.display_nick) + '" data-text="' + cesc(String(m.text || '').slice(0, 140)) + '">' + quote +
          '<span class="lmc-nick' + (m.is_admin ? ' lmc-adm' : '') + '">' + (m.is_admin ? '👑 ' : '') + cesc(m.display_nick) + '</span>' + cesc(m.text) + '<span class="lmc-t">' + t + '</span>' +
          '<button class="lmc-reply-btn" title="Ответить">↩</button></div>';
      }).join('');
    }
    function trackIds(list) {
      list.forEach(function (m) {
        var id = Number(m && m.id);
        if (!Number.isFinite(id)) return;
        seenIds[id] = 1;
        if (id > lastId) lastId = id;
        if (!firstId || id < firstId) firstId = id;
      });
    }
    function freshMsgs(list) {
      var out = [];
      (list || []).forEach(function (m) {
        var id = Number(m && m.id);
        if (!Number.isFinite(id) || seenIds[id]) return;
        out.push(m);
      });
      return out;
    }
    function renderMsgs(list, append) {
      var b = el('lmc-body'); if (!b) return;
      list = append ? freshMsgs(list) : (list || []);
      if (append && !list.length) return;
      var stick = nearBottom();
      if (append) b.insertAdjacentHTML('beforeend', msgsHtml(list));
      else b.innerHTML = msgsHtml(list) || '<div class="lmc-hint" style="color:#8093b6;font-size:13px">Пока нет сообщений. Напиши первым!</div>';
      trackIds(list);
      if (stick || !append) toBottom();
    }

    function loadHistory() {
      if (loadingNew) return;
      loadingNew = true;
      cget('/chat/history?after_id=' + lastId).then(function (d) {
        if (d && d.messages) renderMsgs(d.messages, lastId > 0);
      }).catch(function () {}).then(function () { loadingNew = false; });
    }
    function loadOlder() {
      if (loadingOld || noMoreOld || !firstId) return;
      loadingOld = true;
      var b = el('lmc-body'); var prevH = b.scrollHeight, prevTop = b.scrollTop;
      cget('/chat/history?before_id=' + firstId + '&limit=' + OLD_CHUNK).then(function (d) {
        var list = freshMsgs((d && d.messages) || []);
        if (list.length < OLD_CHUNK) noMoreOld = true;
        if (list.length) {
          b.insertAdjacentHTML('afterbegin', msgsHtml(list));
          trackIds(list);
          b.scrollTop = prevTop + (b.scrollHeight - prevH);
        }
      }).catch(function () {}).then(function () { loadingOld = false; });
    }
    function startPoll() { stopPoll(); pollT = setInterval(loadHistory, POLL_MS); }
    function stopPoll() { if (pollT) { clearInterval(pollT); pollT = null; } }

    function showChat() {
      el('lmc-nickwrap').style.display = 'none';
      el('lmc-chatwrap').style.display = 'flex';
      el('lmc-me').innerHTML = 'Ты: <b>' + cesc(myNick) + '</b>' + (isAdmin ? ' 👑' : '') + ' · <a id="lmc-changenick">сменить ник</a>';
      var cn = el('lmc-changenick'); if (cn) cn.onclick = function () { openNick(myNick); };
      lastId = 0; firstId = 0; seenIds = {}; noMoreOld = false; loadingNew = false; loadingOld = false; el('lmc-body').innerHTML = '';
      loadHistory(); startPoll();
    }
    function openNick(prefill) {
      stopPoll();
      el('lmc-chatwrap').style.display = 'none';
      el('lmc-nickwrap').style.display = 'flex';
      el('lmc-nickin').value = prefill || '';
      el('lmc-nickmsg').textContent = '';
      el('lmc-me').textContent = '';
      setTimeout(function () { try { el('lmc-nickin').focus(); } catch (e) {} }, 30);
    }
    function saveNick() {
      var v = (el('lmc-nickin').value || '').trim();
      if (v.length < 2) { el('lmc-nickmsg').className = 'lmc-err'; el('lmc-nickmsg').textContent = 'Минимум 2 символа'; return; }
      el('lmc-nickmsg').className = ''; el('lmc-nickmsg').textContent = 'Сохраняю…';
      cpost('/chat/nick', { nick: v }).then(function (r) {
        if (r.ok && r.d.ok) {
          myNick = r.d.nick;
          showChat();
        } else {
          var m = r.d.error === 'nick_taken' ? 'Ник занят, выбери другой' : r.d.error === 'site_nick' ? 'Это ник аккаунта на сайте — его может взять только его владелец' : r.d.error === 'reserved' ? 'Этот ник зарезервирован' : r.d.error === 'bad_nick' ? '2–20 символов, без эмодзи' : 'Ошибка: ' + (r.d.error || '');
          el('lmc-nickmsg').className = 'lmc-err'; el('lmc-nickmsg').textContent = m;
        }
      }).catch(function () { el('lmc-nickmsg').className = 'lmc-err'; el('lmc-nickmsg').textContent = 'Нет связи с сервером'; });
    }
    function setReply(id, nick, text) {
      var rid = Number(id) || null;
      var bar = el('lmc-replybar'); if (!bar) return;
      if (!rid) { replyTo = null; bar.classList.remove('on'); return; }
      replyTo = { id: rid, nick: nick, text: text };
      el('lmc-rb-txt').innerHTML = 'Ответ <b>' + cesc(nick) + '</b>: ' + cesc(String(text || '').slice(0, 80));
      bar.classList.add('on');
      try { el('lmc-in').focus(); } catch (e) {}
    }
    function clearReply() { replyTo = null; var bar = el('lmc-replybar'); if (bar) bar.classList.remove('on'); }
    function sendMsg() {
      var t = (el('lmc-in').value || '').trim(); if (!t) return;
      var r0 = replyTo;
      el('lmc-in').value = ''; clearReply();
      cpost('/chat/send', r0 ? { text: t, reply_to: r0.id } : { text: t }).then(function (r) {
        if (r.ok && r.d.ok) loadHistory();
        else if (r.d && r.d.error === 'too_fast') { el('lmc-in').value = t; if (r0) setReply(r0.id, r0.nick, r0.text); }
      }).catch(function () { el('lmc-in').value = t; });
    }

    function loadMe(then) {
      cget('/chat/me').then(function (d) {
        meLoaded = true; isAdmin = !!(d && d.is_admin); myNick = (d && d.nick) || null;
        then && then();
      }).catch(function () { then && then(); });
    }

    function openPanel() {
      el('lmc-panel').classList.add('on'); isOpen = true;
      var cb = el('lm-chatbadge'); if (cb) cb.classList.remove('on');
      function go() { if (myNick) showChat(); else openNick(''); }
      if (!meLoaded) loadMe(go); else go();
    }
    function closePanel() { el('lmc-panel').classList.remove('on'); isOpen = false; stopPoll(); }

    function build() {
      injectCss();
      var p = document.createElement('div'); p.id = 'lmc-panel';
      p.innerHTML =
        '<div id="lmc-head"><div><b>💬 Чат</b><div id="lmc-me"></div></div><button class="lmc-x">✕</button></div>' +
        '<div id="lmc-nickwrap" style="display:none;flex:1;flex-direction:column"><div id="lmc-nickbox">' +
          '<div class="lmc-h">Выбери ник для чата</div>' +
          '<div class="lmc-hint">Под этим ником тебя увидят другие. Ник уникальный, без эмодзи, 2–20 символов. Сменить можно в любой момент.</div>' +
          '<input id="lmc-nickin" maxlength="20" placeholder="Твой ник">' +
          '<button id="lmc-nickok">Сохранить и войти в чат</button>' +
          '<div id="lmc-nickmsg"></div>' +
        '</div></div>' +
        '<div id="lmc-chatwrap" style="display:none;flex:1;flex-direction:column;min-height:0">' +
          '<div id="lmc-body"></div>' +
          '<div id="lmc-replybar"><div class="lmc-rb-txt" id="lmc-rb-txt"></div><button class="lmc-rb-x" id="lmc-rb-x">✕</button></div>' +
          '<div id="lmc-foot"><textarea id="lmc-in" rows="1" maxlength="500" placeholder="Сообщение…"></textarea><button id="lmc-send">▶</button></div>' +
        '</div>';
      document.body.appendChild(p);
      document.addEventListener('click', function (e) {
        if (e.target && e.target.closest && e.target.closest('#lm-chatbtn')) { if (isOpen) closePanel(); else openPanel(); }
      });
      p.querySelector('.lmc-x').onclick = closePanel;
      el('lmc-nickok').onclick = saveNick;
      el('lmc-nickin').addEventListener('keydown', function (e) { if (e.key === 'Enter') saveNick(); });
      el('lmc-send').onclick = sendMsg;
      el('lmc-in').addEventListener('keydown', function (e) { if (e.key === 'Enter' && !e.shiftKey) { e.preventDefault(); sendMsg(); } });
      el('lmc-rb-x').onclick = clearReply;
      el('lmc-body').addEventListener('scroll', function () { if (el('lmc-body').scrollTop < 48) loadOlder(); }, { passive: true });
      el('lmc-body').addEventListener('click', function (e) {
        var rb = e.target.closest ? e.target.closest('.lmc-reply-btn') : null;
        if (rb) { var msg = rb.closest('.lmc-msg'); if (msg) setReply(msg.getAttribute('data-id'), msg.getAttribute('data-nick'), msg.getAttribute('data-text')); return; }
        var q = e.target.closest ? e.target.closest('.lmc-quote') : null;
        if (q && q.getAttribute('data-to')) {
          var tgt = el('lmc-body').querySelector('.lmc-msg[data-id="' + q.getAttribute('data-to') + '"]');
          if (tgt) { tgt.scrollIntoView({ block: 'center' }); tgt.style.transition = 'background .2s'; tgt.style.background = 'rgba(74,90,199,.28)'; setTimeout(function () { tgt.style.background = ''; }, 900); }
        }
      });
    }

    if (document.body) build(); else document.addEventListener('DOMContentLoaded', build);
  })();

  (function mineAutoCollect() {
    var LS_MINE_ENABLED   = 'lm_mine_auto_enabled'   + _lmUserSuffix;
    var LS_MINE_INTERVAL  = 'lm_mine_check_interval' + _lmUserSuffix;
    var LS_MINE_LAST      = 'lm_mine_last_check'     + _lmUserSuffix;
    var LS_MINE_THRESHOLD = 'lm_mine_threshold'      + _lmUserSuffix;
    var LS_MINE_HAS       = 'lm_mine_has'            + _lmUserSuffix;
    var LS_MINE_FILL_AT   = 'lm_mine_fill_at'        + _lmUserSuffix;
    var LS_MINE_RETRY_AT  = 'lm_mine_retry_at'       + _lmUserSuffix;
    var LS_MINE_FAILS     = 'lm_mine_fails'          + _lmUserSuffix;
    var NO_MINE_BACKOFF_MS = 60 * 60 * 1000;
    var DEFAULT_INTERVAL  = 5;
    var DEFAULT_THRESHOLD = 100;

    function isEnabled()    { return window.lmLS.get(LS_MINE_ENABLED) !== 'false'; }
    function getInterval()  { return parseInt(window.lmLS.get(LS_MINE_INTERVAL)  || DEFAULT_INTERVAL,  10); }
    function getThreshold() { return parseInt(window.lmLS.get(LS_MINE_THRESHOLD) || DEFAULT_THRESHOLD, 10); }
    function getUserHash()  { return PW.dle_login_hash || ''; }

    function checkMine(callback, forceFresh) {
      var hash = getUserHash();
      if (!hash) { callback(null); return; }

      if (!forceFresh && IS_LABYRINTH && PW.labyrinthData && PW.labyrinthData.personalMine) {
        callback(PW.labyrinthData.personalMine); return;
      }
      fetch('/labyrinth/?_=' + Date.now(), { credentials: 'same-origin', cache: 'no-store' })
        .then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.text(); })
        .then(function(html) {
          var marker = 'personalMine:';
          var idx = html.indexOf(marker);
          if (idx === -1) { callback(null); return; }
          var afterColon = html.substring(idx + marker.length).replace(/^\s+/, '');
          if (afterColon.indexOf('null') === 0) { callback(null); return; }
          if (afterColon[0] !== '{') { callback(null); return; }
          var depth = 0, pos = 0, len = afterColon.length;
          while (pos < len) {
            var ch = afterColon[pos];
            if (ch === '{') { depth++; }
            else if (ch === '}') { depth--; if (depth === 0) { pos++; break; } }
            else if (ch === '"') {
              pos++;
              while (pos < len) {
                if (afterColon[pos] === '\\' && pos + 1 < len) { pos += 2; continue; }
                if (afterColon[pos] === '"') break;
                pos++;
              }
            }
            pos++;
          }
          try {
            var mine = JSON.parse(afterColon.substring(0, pos));
            if (mine && mine.has_mine) callback(mine); else callback(null);
          } catch(e) { callback(null); }
        })
        .catch(function() { callback(null); });
    }

    function collectMine(onDone) {
      var hash = getUserHash();
      if (!hash) { onDone(false, 'No hash'); return; }
      fetch('/index.php?controller=ajax&mod=animesss_game', {
        method: 'POST', credentials: 'same-origin',
        headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 'X-Requested-With': 'XMLHttpRequest' },
        body: 'action=collect_personal_mine&user_hash=' + encodeURIComponent(hash)
      })
      .then(function(r) { if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
      .then(function(d) {
        if (d && !d.error) {
          onDone(true, d);
          try {
            if (window.DLEPush && DLEPush.success) {
              DLEPush.success('⛏ Шахта: собрано ' + (d.acc || 0) + ' АСС' + (d.cards_awarded && d.cards_awarded.length ? ' + ' + d.cards_awarded.length + ' карт' : ''));
            }
          } catch(e) {}
        } else { onDone(false, d && d.error ? d.error : d); }
      })
      .catch(function(e) { onDone(false, e.message); });
    }

    function setMineFillCache(m) {
      if (!m || !m.has_mine) { window.lmLS.set(LS_MINE_HAS, '0'); return; }
      window.lmLS.set(LS_MINE_HAS, '1');
      var thr = getThreshold();
      var pct = Number(m.storage_progress || 0);
      if (m.is_storage_full || pct >= thr) { window.lmLS.set(LS_MINE_FILL_AT, String(Date.now())); return; }
      var sl = Number(m.storage_seconds_left || 0);
      var toThr = sl * ((thr - pct) / Math.max(1, 100 - pct));
      window.lmLS.set(LS_MINE_FILL_AT, String(Date.now() + Math.max(0, toThr) * 1000));
    }

    function runCheck() {
      if (!isEnabled() || !getUserHash()) return;

      var _retryAt = parseInt(window.lmLS.get(LS_MINE_RETRY_AT) || '0', 10);
      if (_retryAt && Date.now() < _retryAt) return;

      var _fillAt = parseInt(window.lmLS.get(LS_MINE_FILL_AT) || '0', 10);
      var _believeReady = _fillAt > 0 && Date.now() >= _fillAt;
      var _haveSnap = IS_LABYRINTH && PW.labyrinthData && PW.labyrinthData.personalMine;
      var _willFetch = _believeReady || !_haveSnap;

      if (_willFetch) {
        var _lastChk = parseInt(window.lmLS.get(LS_MINE_LAST) || '0', 10);
        if (_lastChk && (Date.now() - _lastChk) < 10000) return;
        window.lmLS.set(LS_MINE_LAST, String(Date.now()));
      }

      checkMine(function(mine) {
        setMineFillCache(mine);
        if (!mine) return;
        var threshold = getThreshold();
        if (mine.can_collect && (mine.storage_progress >= threshold || mine.is_storage_full)) {
          collectMine(function(ok, data) {
            if (ok) {

              window.lmLS.remove(LS_MINE_RETRY_AT);
              window.lmLS.remove(LS_MINE_FAILS);
              var fullSecs = Number(mine.max_storage_seconds || 0);
              var toThrSec = fullSecs * (getThreshold() / 100);
              if (fullSecs > 0) window.lmLS.set(LS_MINE_FILL_AT, String(Date.now() + toThrSec * 1000));
              var _pml = PW.labyrinthData && PW.labyrinthData.personalMine;
              if (_pml) {
                _pml.storage_progress = 0; _pml.is_storage_full = false; _pml.can_collect = false;
                _pml.storage_seconds_passed = 0; _pml.storage_seconds_left = fullSecs;
                _pml.pending_acc = 0; _pml.pending_cards = 0;
              }
              lmDebug('[LabMap Mine] собрано:', data.acc, 'АСС; наступний збір ~через', Math.round(toThrSec / 60), 'хв');
            } else {

              var _fails = parseInt(window.lmLS.get(LS_MINE_FAILS) || '0', 10) + 1;
              window.lmLS.set(LS_MINE_FAILS, String(_fails));
              var _backoff = Math.min(60, 10 * Math.pow(2, _fails - 1)) * 60 * 1000;
              window.lmLS.set(LS_MINE_RETRY_AT, String(Date.now() + _backoff));
              lmDebug('[LabMap Mine] збір не вдався (#' + _fails + '), повтор через', Math.round(_backoff / 60000), 'хв');
            }
          });
        } else {

          window.lmLS.remove(LS_MINE_FAILS);

          if (_believeReady && (mine.storage_progress >= threshold || mine.is_storage_full)) {
            window.lmLS.set(LS_MINE_RETRY_AT, String(Date.now() + 30 * 60 * 1000));
          }
        }
      }, _believeReady);
    }

    var _mineTimer = null;
    function getSmartDelay() {

      var retryAt = parseInt(window.lmLS.get(LS_MINE_RETRY_AT) || '0', 10);
      if (retryAt && Date.now() < retryAt) return retryAt - Date.now();

      var pm = PW.labyrinthData && PW.labyrinthData.personalMine;

      if (pm && pm.has_mine && pm.max_storage_seconds > 0) {
        setMineFillCache(pm);
        var pct = Number(pm.storage_progress || 0);
        if (pct >= getThreshold() || pm.is_storage_full) return 0;
        var fa = parseInt(window.lmLS.get(LS_MINE_FILL_AT) || '0', 10);
        return Math.max(15000, (fa + 30000) - Date.now());
      }

      var last   = parseInt(window.lmLS.get(LS_MINE_LAST) || '0', 10);
      var baseMs = getInterval() * 60 * 1000;

      if ((pm && !pm.has_mine) || (!pm && window.lmLS.get(LS_MINE_HAS) === '0')) {
        return last === 0 ? 0 : Math.max(0, (last + Math.max(NO_MINE_BACKOFF_MS, baseMs)) - Date.now());
      }

      var fillAt = parseInt(window.lmLS.get(LS_MINE_FILL_AT) || '0', 10);
      if (fillAt) {
        var untilFull = (fillAt + 30000) - Date.now();
        if (untilFull <= 0) return 0;
        return Math.max(30000, Math.min(untilFull, NO_MINE_BACKOFF_MS));
      }

      return last === 0 ? 0 : Math.max(0, (last + baseMs) - Date.now());
    }
    function scheduleNext() {
      if (_mineTimer) clearTimeout(_mineTimer);
      if (!isEnabled()) return;
      var delay = getSmartDelay();
      _mineTimer = setTimeout(function() {
        _mineTimer = null;
        if (!isEnabled()) { scheduleNext(); return; }
        runCheck(); scheduleNext();
      }, delay);
    }

    function _fmtRuTime(s) {
      var h = 0, m = 0, sec = 0, mm;
      mm = s.match(/(\d+)\s*ч/);   if (mm) h = +mm[1];
      mm = s.match(/(\d+)\s*мин/); if (mm) m = +mm[1];
      mm = s.match(/(\d+)\s*сек/); if (mm) sec = +mm[1];
      if (!h && !m && !sec) return null;
      function p(n){ return (n < 10 ? '0' : '') + n; }
      var d = Math.floor(h / 24), hh = h % 24;
      return d > 0 ? (d + ' дн ' + p(hh) + ':' + p(m) + ':' + p(sec)) : (p(h) + ':' + p(m) + ':' + p(sec));
    }
    function _lmMineShorten() {
      var pt = document.getElementById('labyrinthPersonalMineProgressTimer');
      if (pt) { var tm = _fmtRuTime(pt.textContent); if (tm) { var w = 'Заполнение: ' + tm; if (pt.textContent !== w) pt.textContent = w; } }
      var ui = document.getElementById('labyrinthPersonalMineUpgradeInfo');
      if (ui) {
        var um = _fmtRuTime(ui.textContent);
        if (um) { var w2 = 'Улучшение: ' + um; if (ui.textContent !== w2) ui.textContent = w2; }
        else { var m2 = ui.textContent.replace(/^\s*Следующее улучшение доступно\.\s*Цена:\s*/, 'Улучшить за '); if (m2 !== ui.textContent) ui.textContent = m2; }
      }
    }
    function _lmMineRestyle() {
      try {
        var mine = document.getElementById('labyrinthPersonalMine');
        if (!mine) return;
        mine.classList.add('lm-mine-slim');
        var _rp = mine.parentElement;
        if (_rp && /labyrinth__right/.test(_rp.className || '')) {
          if (window.innerWidth >= 900) {
            if (_rp.firstElementChild !== mine) _rp.insertBefore(mine, _rp.firstElementChild);
          } else if (_rp.firstElementChild === mine && _rp.children.length > 1) {
            _rp.appendChild(mine);
          }
        }
        var actions = mine.querySelector('.labyrinth__personal-mine-actions');
        var collect = document.getElementById('labyrinthCollectMineBtn');
        var upgrade = document.getElementById('labyrinthUpgradeMineBtn');
        var ptimer = document.getElementById('labyrinthPersonalMineProgressTimer');
        var uinfo = document.getElementById('labyrinthPersonalMineUpgradeInfo');
        if (actions && collect && upgrade && !mine.querySelector('.lm-mine-grid')) {
          var grid = document.createElement('div'); grid.className = 'lm-mine-grid';
          var L = document.createElement('div'); L.className = 'lm-mine-col';
          var R = document.createElement('div'); R.className = 'lm-mine-col';
          L.appendChild(collect); if (ptimer) L.appendChild(ptimer);
          R.appendChild(upgrade); if (uinfo) R.appendChild(uinfo);
          grid.appendChild(L); grid.appendChild(R);
          actions.insertBefore(grid, actions.firstChild);
        }
        _lmMineShorten();
      } catch(e) {}
    }
    function injectMineSettingsBtn() {
      function tryInject() {
        var mineEl = document.getElementById('labyrinthPersonalMine');
        if (!mineEl || mineEl.style.display === 'none' || window.getComputedStyle(mineEl).display === 'none') return;
        _lmMineRestyle();
        _lmBuildTopDeck();
        if (mineEl.querySelector('#lm-mine-settings-btn')) return;
        var actionsEl = mineEl.querySelector('.labyrinth__personal-mine-actions');
        if (!actionsEl) return;
        var btn = document.createElement('button');
        btn.id = 'lm-mine-settings-btn'; btn.type = 'button'; btn.className = 'button';
        btn.innerHTML = '⚙ Авто-сбор';
        btn.style.cssText = 'width:100%;margin-top:8px;background:linear-gradient(135deg,#4a5ac7,#3a4ab0);border:none;color:#fff;font-weight:700;';
        btn.addEventListener('click', function(e) { e.stopImmediatePropagation(); e.preventDefault(); showMineSettings(); });
        actionsEl.appendChild(btn);
      }
      tryInject();
      [200, 500, 1000, 2000, 3000].forEach(function(ms) { setTimeout(tryInject, ms); });
      var _injectScheduled = false;
      var obs = new MutationObserver(function(mutations) {
        try { _lmMineShorten(); } catch(e) {}
        try { _lmDeckMineToggle(); } catch(e) {}
        if (_injectScheduled) return;
        for (var i = 0; i < mutations.length; i++) {
          var t = mutations[i].target;
          if (t.id === 'labyrinthPersonalMine' || (t.classList && t.classList.contains('labyrinth__personal-mine-actions'))) {
            _injectScheduled = true;
            setTimeout(function() { _injectScheduled = false; tryInject(); }, 100);
            return;
          }
        }
      });
      var mineEl = document.getElementById('labyrinthPersonalMine');
      if (mineEl) obs.observe(mineEl, { attributes: true, attributeFilter: ['style'], childList: true, subtree: true, characterData: true });
      var rightEl = document.querySelector('.labyrinth__right');
      if (rightEl) obs.observe(rightEl, { childList: true, subtree: false, attributes: true, attributeFilter: ['style'] });
    }

    function showMineSettings() {
      var existing = document.getElementById('lm-mine-modal');
      if (existing) { existing.classList.add('on'); return; }
      var modal = document.createElement('div'); modal.id = 'lm-mine-modal';
      var enabled   = isEnabled();
      var interval  = getInterval();
      var threshold = getThreshold();
      var last = parseInt(window.lmLS.get(LS_MINE_LAST) || '0', 10);
      var lastStr = last ? getMoscowTimeStr(last) : 'ещё не проверялась';
      modal.innerHTML =
        '<div id="lm-mine-overlay"></div>' +
        '<div id="lm-mine-box">' +
          '<div id="lm-mine-head"><div id="lm-mine-title">⛏ Авто-сбор шахты</div><button id="lm-mine-close">×</button></div>' +
          '<div id="lm-mine-body">' +
            '<div class="lm-mine-row">' +
              '<div class="lm-mine-label">Авто-сбор включён</div>' +
              '<label class="lm-mine-toggle"><input type="checkbox" id="lm-mine-enabled" ' + (enabled ? 'checked' : '') + '><span class="lm-mine-slider"></span></label>' +
            '</div>' +
            '<div class="lm-mine-row">' +
              '<div class="lm-mine-label">Собирать при заполненности</div>' +
              '<div class="lm-mine-input-wrap"><input type="number" id="lm-mine-threshold" value="' + threshold + '" min="1" max="100" step="1"><span class="lm-mine-pct-label">%</span></div>' +
            '</div>' +
            '<div class="lm-mine-row">' +
              '<div class="lm-mine-label">Резервный интервал (мин)</div>' +
              '<input type="number" id="lm-mine-interval" value="' + interval + '" min="1" max="1440" step="1">' +
            '</div>' +
            '<div class="lm-mine-info">Последняя проверка: <b>' + lastStr + '</b></div>' +
            '<div id="lm-mine-actions">' +
              '<button id="lm-mine-check-now" class="button button--primary">🔍 Проверить сейчас</button>' +
              '<button id="lm-mine-save" class="button">💾 Сохранить</button>' +
            '</div>' +
            '<div id="lm-mine-status"></div>' +
          '</div>' +
        '</div>';
      var style = document.createElement('style');
      style.textContent = '#lm-mine-modal{display:none;position:fixed;inset:0;z-index:9999999;align-items:center;justify-content:center;}#lm-mine-modal.on{display:flex;}#lm-mine-overlay{position:absolute;inset:0;background:rgba(0,0,0,.7);}#lm-mine-box{position:relative;z-index:2;background:#1a1d2a;border:1px solid rgba(255,255,255,.12);border-radius:20px;padding:0;width:min(380px,calc(100vw - 24px));color:#fff;overflow:hidden;}#lm-mine-head{display:flex;align-items:center;justify-content:space-between;padding:16px 20px;border-bottom:1px solid rgba(255,255,255,.08);}#lm-mine-title{font-size:16px;font-weight:800;}#lm-mine-close{width:32px;height:32px;border:none;border-radius:8px;background:#c0392b;color:#fff;font-size:18px;cursor:pointer;}#lm-mine-body{padding:18px 20px;display:flex;flex-direction:column;gap:14px;}.lm-mine-row{display:flex;align-items:center;justify-content:space-between;gap:10px;}.lm-mine-label{font-size:14px;color:rgba(255,255,255,.8);flex:1;}.lm-mine-toggle{position:relative;display:inline-block;width:44px;height:24px;}.lm-mine-toggle input{opacity:0;width:0;height:0;}.lm-mine-slider{position:absolute;inset:0;background:rgba(255,255,255,.15);border-radius:24px;cursor:pointer;transition:.2s;}.lm-mine-slider:before{content:"";position:absolute;height:18px;width:18px;left:3px;bottom:3px;background:#fff;border-radius:50%;transition:.2s;}.lm-mine-toggle input:checked + .lm-mine-slider{background:#4a5ac7;}.lm-mine-toggle input:checked + .lm-mine-slider:before{transform:translateX(20px);}.lm-mine-input-wrap{display:flex;align-items:stretch;flex-shrink:0;}#lm-mine-threshold{width:52px;padding:6px 8px;border:1px solid rgba(255,255,255,.15);border-radius:10px 0 0 10px;border-right:none;background:rgba(255,255,255,.06);color:#fff;font-size:14px;text-align:center;}.lm-mine-pct-label{font-size:13px;color:rgba(255,255,255,.65);padding:0 10px;border:1px solid rgba(255,255,255,.15);border-radius:0 10px 10px 0;background:rgba(255,255,255,.04);display:flex;align-items:center;}#lm-mine-interval{width:70px;padding:6px 10px;border:1px solid rgba(255,255,255,.15);border-radius:10px;background:rgba(255,255,255,.06);color:#fff;font-size:14px;text-align:center;}.lm-mine-info{font-size:12px;color:rgba(255,255,255,.45);padding:8px 12px;border-radius:10px;background:rgba(255,255,255,.04);}.lm-mine-info b{color:rgba(255,255,255,.75);}#lm-mine-actions{display:flex;flex-direction:column;gap:8px;}#lm-mine-actions .button{width:100%;justify-content:center;}#lm-mine-status{font-size:13px;font-weight:700;text-align:center;min-height:20px;color:#6ee786;}';
      document.head.appendChild(style);
      document.body.appendChild(modal);
      modal.classList.add('on');
      document.getElementById('lm-mine-close').addEventListener('click', function() { modal.classList.remove('on'); });
      document.getElementById('lm-mine-overlay').addEventListener('click', function() { modal.classList.remove('on'); });
      document.getElementById('lm-mine-save').addEventListener('click', function() {
        var en   = document.getElementById('lm-mine-enabled').checked;
        var inv  = parseInt(document.getElementById('lm-mine-interval').value, 10)  || DEFAULT_INTERVAL;
        var thr  = parseInt(document.getElementById('lm-mine-threshold').value, 10) || DEFAULT_THRESHOLD;
        inv = Math.max(1, Math.min(1440, inv)); thr = Math.max(1, Math.min(100, thr));
        window.lmLS.set(LS_MINE_ENABLED, String(en));
        window.lmLS.set(LS_MINE_INTERVAL, String(inv));
        window.lmLS.set(LS_MINE_THRESHOLD, String(thr));
        var st = document.getElementById('lm-mine-status');
        st.textContent = 'Сохранено!'; st.style.color = '#6ee786';
        scheduleNext();
        if (en) setTimeout(function() { runCheck(); }, 500);
        setTimeout(function() { st.textContent = ''; modal.classList.remove('on'); }, 1200);
      });
      document.getElementById('lm-mine-check-now').addEventListener('click', function() {
        var st = document.getElementById('lm-mine-status');
        st.style.color = 'rgba(255,255,255,.6)'; st.textContent = 'Проверяем...';
        checkMine(function(mine) {
          if (!mine) { st.style.color = '#ff8b8b'; st.textContent = 'Не удалось получить данные'; return; }
          var thr2 = parseInt((document.getElementById('lm-mine-threshold')||{}).value || getThreshold(), 10) || getThreshold();
          if (mine.storage_progress >= thr2 || mine.is_storage_full) {
            st.style.color = '#ffd66b'; st.textContent = mine.storage_progress + '%! Собираем...';
            collectMine(function(ok, data) {
              if (ok) { st.style.color = '#6ee786'; st.textContent = 'Собрано ' + (data.acc || 0) + ' АСС'; }
              else { st.style.color = '#ff8b8b'; st.textContent = 'Ошибка сбора'; }
            });
          } else {
            st.style.color = 'rgba(255,255,255,.6)'; st.textContent = 'Сейчас ' + mine.storage_progress + '% — порог ' + thr2 + '%';
          }
        });
      });
    }

    scheduleNext();
    if (IS_LABYRINTH) {
      if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', injectMineSettingsBtn); }
      else { injectMineSettingsBtn(); }
    }
    lmDebug('[LabMap Mine] авто-сбор инициализирован, интервал:', getInterval(), 'min');
  })();

function collectRoomExtra() {
  var result = {};
  var ld = PW.labyrinthData || {};

  var ro = ld.roomOpener;
  if (ro && ro.name) {
    result.first_opener = String(ro.name).trim();
    if (ro.date) {
      var dm = String(ro.date).match(/(\d{2})\.(\d{2})\.(\d{4})[^\d]*(\d{2}:\d{2})/);
      if (dm) result.first_opened_at = dm[3]+'-'+dm[2]+'-'+dm[1]+'T'+dm[4]+':00';
    }
  }

  if (!result.first_opener) {
    var openerEl = document.querySelector('.labyrinth__opener-name, .room-opener .username, [class*="opener"] .name');
    if (openerEl) {
      result.first_opener = openerEl.textContent.trim();
      var openerBlock = openerEl.closest('[class*="opener"], .labyrinth__opener');
      if (openerBlock) {
        var dateMatch = openerBlock.textContent.match(/(\d{2})\.(\d{2})\.(\d{4})[^\d]*(\d{2}:\d{2})/);
        if (dateMatch) result.first_opened_at = dateMatch[3]+'-'+dateMatch[2]+'-'+dateMatch[1]+'T'+dateMatch[4]+':00';
      }
    }
  }

  var pm = ld.personalMine;
  if (pm && pm.has_mine) {
    result.mine = {
      is_owner: true,
      level: Number(pm.level || 0),
      storage_pct: Number(pm.storage_progress || 0),
      acc_inside: Number(pm.pending_acc || 0),
      cards_current: Number(pm.pending_cards || 0),
      cards_max: Number(pm.max_cards || 0),
      acc_per_hour: Number(pm.acc_per_hour || 0),
      storage_seconds_left: Number(pm.storage_seconds_left || 0),
      is_storage_full: !!pm.is_storage_full
    };
  } else if (ld.foreignMine) {
    var fm = ld.foreignMine;
    result.mine = {
      is_owner: false,
      owner: fm.owner || fm.username || fm.user || null,
      level: Number(fm.level || 0),
      storage_pct: Number(fm.storage_progress || 0),
      acc_inside: Number(fm.pending_acc || fm.acc || 0),
      cards_current: Number(fm.pending_cards || fm.cards || 0),
      cards_max: Number(fm.max_cards || 0)
    };
  }

  if (ld.echoRoom) result.echo = ld.echoRoom;
  if (ld.fateRoom) result.fate = ld.fateRoom;
  if (ld.clubWarRoom) result.club_war = ld.clubWarRoom;
  if (typeof ld.canPlaceObject !== 'undefined') result.can_place_object = !!ld.canPlaceObject;
  if (ld.emission && ld.emission.last_start_at) {
    result.emission = {
      active: !!ld.emission.active,
      last_start_at: ld.emission.last_start_at,
      last_end_at: ld.emission.last_end_at || 0,
      cooldown_left: ld.emission.next_check_left || ld.emission.cooldown_left || 0
    };
    if (ld.emission.active) result.during_emission = true;
  }

  return result;
}

function sendRollbackToVPS(count, place) {
  lmDebug('[Синхронизация] sendRollbackToVPS:', count, place);
  var VPS_API  = LM_API_BASE;
  var auth = getAuth();
  if (!auth || count == null) return;
  var username = auth.username;

  window.lmFetch(VPS_API + '/sync/rollbacks', {
    method: 'POST',
    headers: vpsAuthHeaders(true),
    body: JSON.stringify({ trap_backs: count, rollback_place: place }),
  })
  .then(function(r){ if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
  .then(function(res){
    lmDebug('[Синхронизация] Откаты отправлены на VPS:', count, '| Топ:', place);
  })
  .catch(function() {});
}

function sendAccHistoryToVPS() {
  if (Date.now() - (window._lmLastAccSync || 0) < 20000) { lmDebug('[Синхронизация] acc-sync throttled (<20с)'); return; }
  window._lmLastAccSync = Date.now();
  lmDebug('[Синхронизация] sendAccHistoryToVPS викликано, histCache pages:', Object.keys(histCache).length);
  var VPS_API  = LM_API_BASE;
  var auth = getAuth();
  if (!auth) { lmDebug('[Синхронизация] Нет имени пользователя или токена'); return; }
  var username = auth.username;

  function doSend() {
    if (!histCache || !Object.keys(histCache).length) return;
    var rows = [];
    lmDebug('[Синхронизация] doSend histCache keys:', Object.keys(histCache));
    Object.keys(histCache).forEach(function(page) {
      var pageData = histCache[page];
      var pageRows = (pageData && pageData.allRows) || (pageData && pageData.rows);
      if (!pageRows) { lmDebug('[Синхронизация] page',page,'нет строк'); return; }
      lmDebug('[Синхронизация] page',page,'rows count:', pageRows.length);
      pageRows.forEach(function(row) {
        var desc    = row.desc || row.description || '';
        var date    = row.date || row.ts || null;
        var amount  = row.amount  !== undefined ? row.amount  : row.acc_delta;
        var balance = row.balance !== undefined ? row.balance : row.acc_after;
        if (!desc) return;

        var isoDate = null;
        if (date) {
          var dm = String(date).match(/(\d{2})\.(\d{2})\.(\d{4})\s+(\d{2}:\d{2}:\d{2})/);
          if (dm) isoDate = dm[3]+'-'+dm[2]+'-'+dm[1]+'T'+dm[4];
          else isoDate = date;
        }

        var amtStr = String(amount||'0');
        var isNeg  = amtStr.indexOf('-') >= 0;
        var delta  = parseInt(amtStr.replace(/[^0-9]/g,'')) * (isNeg ? -1 : 1);
        var bal    = balance !== null && balance !== undefined
                     ? parseInt(String(balance).replace(/[^0-9]/g,'')) : null;

        rows.push({
          ts:          isoDate,
          description: desc,
          acc_delta:   isNaN(delta) ? null : delta,
          acc_after:   bal,
        });
      });
    });
    lmDebug('[Синхронизация] Строк для отправки собрано:', rows.length, '| перший:', rows[0] ? JSON.stringify(rows[0]) : 'нет');
    if (!rows.length) return;
    window.lmFetch(VPS_API + '/sync/acc-history', {
      method: 'POST',
      headers: vpsAuthHeaders(true),
      body: JSON.stringify({ rows: rows }),
    })
    .then(function(r){ if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
    .then(function(res){
      lmDebug('[Синхронизация] ACC-история отправлена:', res.saved, 'строк');
    })
    .catch(function() {});
  }

  delete histCache[1];
  loadHistPage(1, function() { doSend(); });
}

function parseRoomDetails(ev, accDelta) {
  var labData = PW.labyrinthData;
  var evData  = labData && labData.lastEventData;
  var details = {};

  switch(ev) {
    case 'quiz': case 'quiz_result':
      var qEl = document.querySelector('#labyrinthQuizQuestion, .labyrinth-quiz__question, .labyrinth__quiz-question, .quiz-question');
      var aEl = document.querySelector('#labyrinthQuiz .button.is-active, .quiz-answer.selected, .labyrinth-quiz__answer.active, .labyrinth__quiz-answer.active, .answer-btn.active');
      if (qEl) details.question     = qEl.textContent.trim().slice(0,300);
      if (aEl) details.answer_given = aEl.textContent.trim().slice(0,100);
      details.is_correct = accDelta > 0;
      break;

    case 'personal_mine':
    case 'personal_mine_created':
    case 'personal_mine_collect':
      var mEl = document.querySelector('#labyrinthPersonalMine, .labyrinth__personal-mine, .mine-info, #labyrinthMineInfo, .labyrinth-mine');
      if (mEl) {
        var t = mEl.textContent;
        var lv = t.match(/уровень[^\d]*(\d+)/i)||t.match(/level[^\d]*(\d+)/i);
        var fl = t.match(/(\d+)\s*%/);
        var ac = t.match(/(\d+)\s*ACC/i);
        if (lv) details.level     = parseInt(lv[1]);
        if (fl) details.fill_pct  = parseInt(fl[1]);
        if (ac) details.acc_inside= parseInt(ac[1]);
      }
      if (evData) {
        details.level     = details.level    ||evData.level    ||null;
        details.fill_pct  = details.fill_pct ||evData.fillPct  ||null;
        details.acc_inside= details.acc_inside||evData.accInside||null;
      }
      details.action = accDelta > 0 ? 'collected' : 'viewed';
      break;

    case 'foreign_mine':
      var fmEl = document.querySelector('#labyrinthForeignMine .card-inline__name, #labyrinthForeignMine, .labyrinth__foreign-mine, .mine-owner, .foreign-mine .owner, .card-inline__name');
      if (fmEl) details.owner = fmEl.textContent.trim();
      else if (evData&&evData.owner) details.owner = evData.owner;
      details.action = accDelta !== 0 ? 'collected_tribute' : 'viewed';
      break;

    case 'card_trader': case 'card_trader_result':
      var cEl = document.querySelector('.labyrinth__trader-name, .trader-card .card-inline__name, .trade-card-name');
      var pEl = document.querySelector('.labyrinth__trader-price, .trader-price, .trade-cost');
      if (cEl) details.card_name = cEl.textContent.trim();
      if (pEl) details.price_acc = parseInt(pEl.textContent)||null;
      details.action = accDelta < 0 ? 'bought' : 'declined';
      break;

    case 'spiritual_teleport':
      if (evData) {
        details.teleport_to_x  = evData.toX   ||null;
        details.teleport_to_y  = evData.toY   ||null;
        details.guardian_owner = evData.owner ||null;
      }
      details.action = accDelta < 0 ? 'paid' : (accDelta > 0 ? 'won' : 'viewed');
      break;

    case 'mimic_chest': case 'mimic_chest_hit': case 'mimic_chest_killed':
    case 'mimic_chest_escape': case 'mimic_chest_reward': case 'mimic_chest_back':
      details.result = accDelta > 0 ? 'real_chest' : accDelta < 0 ? 'mimic_trap' : 'alive_chest';
      var mhpEl = document.querySelector('#labyrinthMimicHpText, .labyrinth__mimic-hp-text');
      if (mhpEl) details.hp = mhpEl.textContent.trim().slice(0,40);
      break;

    case 'room_gift':
      details.action = 'triggered';
      if (accDelta > 0) details.acc_inside = accDelta;
      break;

    case 'room_trap':
      details.action = 'triggered';
      if (accDelta < 0) details.acc_penalty = accDelta;
      break;

    case 'club_war_room':
      var cwEl = document.querySelector('#labyrinthClubWarClubs .is-active, .labyrinth__club-war-club.is-active, .club-war-target, .attack-club');
      if (cwEl) details.attacked_club = cwEl.textContent.trim();
      details.won = accDelta >= 0;
      details._admin_only = true;
      break;

    case 'dao_test': case 'dao_result':
      details.result = accDelta > 0 ? 'acc' : accDelta < 0 ? 'trap' : 'nothing';
      if (accDelta !== 0) details.acc_delta = accDelta;
      break;

    case 'relic_room':
      details.piece_or_full = accDelta >= 60 ? 'full' : 'piece';
      details.acc_value = accDelta;
      break;

    case 'fate_room': case 'fate_room_result':
      var frd = PW.labyrinthData && PW.labyrinthData.fateRoom;
      if (frd && frd.options) {
        details.has_options = true;
        details.option_count = Object.keys(frd.options).length;
      }
      details.choice_made = accDelta !== 0;
      break;
  }

  return Object.keys(details).length > 0 ? details : null;
}

function sendFirstOpener(x, y, opener, openedAt) {
  var VPS_API  = LM_API_BASE;
  if (!getAuth() || !opener) return;

  window.lmFetch(VPS_API + '/room/first-opener', {
    method: 'POST',
    headers: vpsAuthHeaders(true),
    body: JSON.stringify({ x: x, y: y, first_user: opener, first_at: openedAt }),
  }).catch(function(){});
}

function sendMineData(x, y, mine) {
  var VPS_API  = LM_API_BASE;
  if (!getAuth() || !mine) return;

  window.lmFetch(VPS_API + '/room/mine', {
    method: 'POST',
    headers: vpsAuthHeaders(true),
    body: JSON.stringify({ x: x, y: y, ...mine }),
  }).catch(function(){});
}

(function() {
  'use strict';

  const VPS_WS  = LM_WS_URL;
  const VPS_API = LM_API_BASE;

  const USERNAME = PW.visitor_name   || '';
  const TOKEN    = (typeof window._lmDevTok === 'function') ? window._lmDevTok() : (PW.dle_login_hash || '');
  if (!USERNAME || !TOKEN) return;
  if (!window.location.pathname.startsWith('/labyrinth')) return;

  const SS_KEY   = 'lm_step_' + USERNAME;
  const SS_BAL   = 'lm_bal_'  + USERNAME;

  let ws      = null;
  let authed  = false;
  let queue   = [];
  let pingTimer = null;
  let reconnectTimer = null;
  let reconnectDelay = 2000;
  let connecting = false;
  let registrationPromise = null;

  function _deviceLabel() {
    try { return (navigator.platform || '') + '|' + (location.host || ''); } catch (e) { return ''; }
  }
  function _bindDevice(accRows) {
    var body = { username: USERNAME, device_token: TOKEN, label: _deviceLabel() };
    if (accRows && accRows.length) body.acc_rows = accRows;
    return window.lmFetch(VPS_API + '/device/bind', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json', 'X-Script-Version': LM_SCRIPT_VERSION },
      body: JSON.stringify(body)
    }).then(function (response) {
      if (response.ok) return { ok: true };
      if (response.status === 403) return { ok: false, needAcc: true };
      if (response.status === 409) return { ok: false, conflict: true };
      return { ok: false, err: response.status };
    }).catch(function () { return { ok: false, err: 'net' }; });
  }
  var _lmBindCooldownUntil = 0;
  function _lmBindFail(resolve) { registrationPromise = null; _lmBindCooldownUntil = Date.now() + 60000; if (resolve) resolve(false); }
  function ensureRegistered() {
    if (registrationPromise) return registrationPromise;
    if (Date.now() < _lmBindCooldownUntil) return Promise.resolve(false);
    registrationPromise = _bindDevice(null).then(function (r) {
      if (r.ok) return true;
      if (!r.needAcc) { _lmBindFail(); return false; }
      return new Promise(function (resolve) {
        if (typeof window._lmCollectAccRows !== 'function') { _lmBindFail(resolve); return; }
        window._lmCollectAccRows(function (rows) {
          if (!rows || !rows.length) { _lmBindFail(resolve); return; }
          _bindDevice(rows).then(function (r2) {
            if (!r2.ok) {
              registrationPromise = null; _lmBindCooldownUntil = Date.now() + 60000;
              window._lmAccDepth = Math.min(8, (window._lmAccDepth || 1) + 1);
              if (typeof window._lmAccCacheClear === 'function') window._lmAccCacheClear();
            }
            resolve(!!r2.ok);
          });
        });
      });
    });
    return registrationPromise;
  }

  function startPing() {
    if (pingTimer) return;
    pingTimer = setInterval(function() {
      if (ws && ws.readyState === WebSocket.OPEN && authed) {
        try { ws.send(JSON.stringify({ type: 'ping' })); } catch(e) {}
      }
    }, 25000);
  }

  function scheduleReconnect() {
    if (reconnectTimer) return;
    reconnectTimer = setTimeout(function() {
      reconnectTimer = null;
      connect();
    }, reconnectDelay);
    reconnectDelay = Math.min(reconnectDelay * 2, 30000);
  }

  function connect() {
    if (connecting) return;
    if (ws && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)) return;
    connecting = true;

    ensureRegistered().then(function(registered) {
      if (!registered) {
        connecting = false;
        scheduleReconnect();
        return;
      }
      try {
        ws = new WebSocket(VPS_WS);
      } catch(e) {
        connecting = false;
        scheduleReconnect();
        return;
      }

      ws.onopen = function() {
        connecting = false;
        reconnectDelay = 2000;
        startPing();
        ws.send(JSON.stringify({ type: 'auth', user: USERNAME, token: TOKEN, script_version: LM_SCRIPT_VERSION }));
      };

      ws.onmessage = function(e) {
        try {
          var msg = JSON.parse(e.data);
          if (msg.type === 'auth_ok') {
            authed = true;
            lmDebug('[Карта] Синхронизация включена');
            var statusEl = document.getElementById('lm-status');
            if (statusEl) statusEl.textContent = '🟢 Карта подключена';
            var pending = queue.slice();
            queue = [];
            pending.forEach(function(ev){ sendEv(ev); });
          }
        } catch(err) {}
      };

      ws.onclose = function(event) {
        connecting = false;
        authed = false;
        var statusEl = document.getElementById('lm-status');
        if (event && event.code === 4001) {
          if (statusEl) statusEl.textContent = '🟡 Активна другая вкладка';
          reconnectDelay = 30000;
        } else {
          if (statusEl) statusEl.textContent = '🔴 Нет соединения';
        }
        scheduleReconnect();
      };

      ws.onerror = function() {
        try { ws.close(); } catch(e) {}
      };
    });
  }

  function sendEv(ev) {
    if (!ws || ws.readyState !== WebSocket.OPEN || !authed) {
      if (queue.length < 1000) queue.push(ev);
      return;
    }
    try { ws.send(JSON.stringify(Object.assign({ type: 'event' }, ev))); } catch(e) {}
  }

  function getBalance() {
    var el = document.getElementById('labyrinthBank');
    if (!el) return null;
    return parseInt(el.textContent.replace(/[^0-9]/g, '')) || 0;
  }
  window._lmGetBalance = getBalance;
  window._lmSendEv     = sendEv;

  function startPolling() {
    var sessionId = PUSH_SESSION_ID;

    var savedState = null;
    try {
      var raw = window.lmSS.get(SS_KEY);
      if (raw) savedState = JSON.parse(raw);
    } catch(e) {}

    var lastStepCount = savedState ? savedState.count : -1;
    var sentStepKeys  = savedState ? new Set(savedState.keys || []) : new Set();

    var prevBalance = null;
    try {
      var savedBal = window.lmSS.get(SS_BAL);
      if (savedBal) prevBalance = parseInt(savedBal);
    } catch(e) {}

    function saveState() {
      try {
        var keysArr = Array.from(sentStepKeys).slice(-200);
        window.lmSS.set(SS_KEY, JSON.stringify({
          count: lastStepCount,
          keys: keysArr
        }));
      } catch(e) {}
    }

    var initDone = false;
    var initTimer = setInterval(function() {
      var labData = PW.labyrinthData;
      if (!labData) return;
      var steps = labData.mapData && labData.mapData.steps;
      if (!steps) return;

      var today = getMoscowDate();
      var savedDay = null;
      try { savedDay = window.lmSS.get('lm_day_' + USERNAME); } catch(e) {}
      if (savedDay !== today) {
        sentStepKeys.clear();
        lastStepCount = -1;
        try { window.lmSS.set('lm_day_' + USERNAME, today); } catch(e) {}
      }

      if (lastStepCount === -1) {
        lastStepCount = steps.length;
        steps.forEach(function(s, i) {
          sentStepKeys.add(i + '_' + (s.event||'') + '_' + (s.x||0) + '_' + (s.y||0));
        });
        saveState();
      }

      prevBalance = getBalance();
      try { window.lmSS.set(SS_BAL, String(prevBalance)); } catch(e) {}

      clearInterval(initTimer);
      initDone = true;
    }, 500);

    setInterval(function() {
      if (!initDone) return;

      var labData = PW.labyrinthData;
      if (!labData) return;
      var cur   = labData.mapData && labData.mapData.current;
      var steps = labData.mapData && labData.mapData.steps;
      var ev    = labData.lastEvent;
      if (!cur || !steps) return;

      var stepCount = steps.length;

      if (stepCount < lastStepCount && lastStepCount > 0) {
        lastStepCount = stepCount;
        saveState();
      }

      for (var i = Math.max(0, lastStepCount); i < stepCount; i++) {
        var step = steps[i];
        if (!step) continue;

        var stepKey = i + '_' + (step.event||ev||'') + '_' + (step.x||cur.x||0) + '_' + (step.y||cur.y||0);
        if (sentStepKeys.has(stepKey)) continue;

        sentStepKeys.add(stepKey);

        var stableIdx;
        if (Number.isInteger(Number(step._lmServerStepIndex))) {
          stableIdx = Number(step._lmServerStepIndex);
        } else {
          var seq = parseInt(window.lmLS.get(LS_SERVER_SEQ) || '0', 10);
          if (!Number.isFinite(seq) || seq < 0) seq = 0;
          step._lmServerStepIndex = seq;
          window.lmLS.set(LS_SERVER_SEQ, String(seq + 1));
          stableIdx = seq;
        }

        (function(stepIdx, stepData, isLast) {
          var delay = isLast ? 800 : 0;
          setTimeout(function() {
            var balAfter = isLast ? getBalance() : null;
            if (balAfter !== null) {
              try { window.lmSS.set(SS_BAL, String(balAfter)); } catch(e) {}
            }
            var delta = (prevBalance !== null && balAfter !== null)
              ? balAfter - prevBalance : 0;

            var sx = stepData.x != null ? stepData.x : cur.x;
            var sy = stepData.y != null ? stepData.y : cur.y;
            var sev = stepData.event || ev || 'empty';

            var isCurrentRoom = isLast && sx === cur.x && sy === cur.y;
            var _extra = isCurrentRoom && window._lmCollectExtra
              ? window._lmCollectExtra()
              : {};

            var details = parseRoomDetails(sev, delta) || {};
            if (_extra.echo) details.echo = _extra.echo;
            if (_extra.fate) details.fate = _extra.fate;
            if (_extra.club_war) details.club_war = _extra.club_war;
            sendEv({
              x:          sx,
              y:          sy,
              ev:         sev,
              acc_before: prevBalance,
              acc_after:  balAfter != null ? balAfter : null,
              acc_delta:  delta,
              dat:        Object.keys(details).length ? details : null,
              session_id: sessionId,
              step_index: stepIdx
            });
            if (isCurrentRoom && _extra.first_opener && window._lmSendOpener) {
              window._lmSendOpener(sx, sy, _extra.first_opener, _extra.first_opened_at || null);
            }
            if (isCurrentRoom && _extra.mine && window._lmSendMine) {
              window._lmSendMine(sx, sy, _extra.mine);
            }

            if (isLast && balAfter !== null) prevBalance = balAfter;
          }, delay);
        })(stableIdx, step, i === stepCount - 1);
      }

      if (stepCount > lastStepCount) {
        lastStepCount = stepCount;
        saveState();
      }

    }, 1000);
  }

  connect();
  if (document.readyState === 'complete') {
    startPolling();
  } else {
    window.addEventListener('load', startPolling);
  }

})();

  window._lmCollectExtra = collectRoomExtra;
  window._lmSendOpener   = sendFirstOpener;
  window._lmSendMine     = sendMineData;

  var _lmHistSteps = null;
  var _lmEmissionCells = null;

  function lmApplyEmissionCells() {
    if (cleanViewMode) return;
    if (!_lmEmissionCells || !_lmEmissionCells.size) return;
    var mapEl = document.getElementById('labyrinthMap');
    if (!mapEl) return;
    var labData = PW.labyrinthData;
    var cur = labData && labData.mapData && labData.mapData.current;
    if (!cur) return;
    var curX = parseInt(cur.x, 10) || 0;
    var curY = parseInt(cur.y, 10) || 0;
    var C = 12;
    _lmEmissionCells.forEach(function(key) {
      var parts = key.split('_');
      var sx = parseInt(parts[0], 10);
      var sy = parseInt(parts[1], 10);
      var gx = C + (sx - curX);
      var gy = C + (sy - curY);
      if (gx < 0 || gx >= 25 || gy < 0 || gy >= 25) return;
      var cell = mapEl.querySelector('[data-x="' + gx + '"][data-y="' + gy + '"]');
      if (!cell) return;
      cell.classList.add('lm-emission-cell');
      if (!cell.querySelector('.lm-emission-ico')) {
        var ico = document.createElement('span');
        ico.className = 'lm-emission-ico';
        ico.textContent = '⚡';
        ico.title = 'Посещено во время выброса';
        cell.appendChild(ico);
      }
    });
  }
  window._lmApplyEmissionCells = lmApplyEmissionCells;

  function lmApplyHistoricalPath() {
    if (!_lmHistSteps || !_lmHistSteps.length) return;
    var mapEl = document.getElementById('labyrinthMap');
    if (!mapEl) return;
    var labData = PW.labyrinthData;
    var cur = labData && labData.mapData && labData.mapData.current;
    if (!cur) return;
    var curX = parseInt(cur.x, 10) || 0;
    var curY = parseInt(cur.y, 10) || 0;
    var C = 12;

    _lmHistSteps.forEach(function(s) {
      var sx = parseInt(s.x, 10);
      var sy = parseInt(s.y, 10);
      var ev = s.event || '';
      var gx = C + (sx - curX);
      var gy = C + (sy - curY);
      if (gx < 0 || gx >= 25 || gy < 0 || gy >= 25) return;
      var cell = mapEl.querySelector('[data-x="' + gx + '"][data-y="' + gy + '"]');
      if (!cell || !cell.classList.contains('labyrinth-cell--unknown')) return;
      cell.classList.remove('labyrinth-cell--unknown');
      cell.classList.add('labyrinth-cell--visited', 'lm-hist');
      if (ev) {
        cell.classList.add('labyrinth-cell--event-' + ev);
        cell.setAttribute('data-event', ev);
      }
    });
  }
  window._lmApplyHistoricalPath = lmApplyHistoricalPath;

  function lmFetchHistoricalPath() {
    if (!IS_LABYRINTH) return;
    var u = PW.visitor_name || '';
    if (!u) return;
    var LS_HIST    = 'lm_hist_' + u;
    var LS_HIST_TS = 'lm_hist_ts_' + u;
    var LS_EM      = 'lm_em_cells_' + u;
    try {
      var ts = parseInt(window.lmLS.get(LS_HIST_TS) || '0', 10);
      if (Date.now() - ts < 5 * 60 * 1000) {
        var cached = JSON.parse(window.lmLS.get(LS_HIST) || 'null');
        var cachedEm = JSON.parse(window.lmLS.get(LS_EM) || 'null');
        if (cached && cached.length && cachedEm !== null) {
          _lmHistSteps = cached;
          window._lmHistSteps = cached;

          if (!window._lmMyPathData) window._lmMyPathData = {};
          cached.forEach(function(s){ window._lmMyPathData[s.x+'_'+s.y]=true; });
          if (cachedEm && cachedEm.length) {
            _lmEmissionCells = new Set(cachedEm);
            window._lmEmissionCells = _lmEmissionCells;
          }
          setTimeout(function(){ lmApplyHistoricalPath(); lmApplyEmissionCells(); }, 100);
          return;
        }
      }
    } catch(e) {}

    window.lmFetch(VPS_URL + '/my/path', { headers: vpsAuthHeaders(false) })
      .then(function(r) { return r.ok ? r.json() : null; })
      .then(function(data) {
        if (!data || !Array.isArray(data.steps)) return;
        _lmHistSteps = data.steps;
        window._lmHistSteps = data.steps;
        if (!window._lmMyPathData) window._lmMyPathData = {};
        data.steps.forEach(function(s){ window._lmMyPathData[s.x+'_'+s.y]=true; });
        var emKeys = [];
        if (Array.isArray(data.emission_cells)) {
          emKeys = data.emission_cells.map(function(c){ return c.x + '_' + c.y; });
          _lmEmissionCells = new Set(emKeys);
          window._lmEmissionCells = _lmEmissionCells;
        }
        try {
          window.lmLS.set(LS_HIST, JSON.stringify(data.steps));
          window.lmLS.set(LS_HIST_TS, String(Date.now()));
          window.lmLS.set(LS_EM, JSON.stringify(emKeys));
        } catch(e) {}
        lmApplyHistoricalPath();
        lmApplyEmissionCells();
      })
      .catch(function() {});
  }

  if (IS_LABYRINTH) setTimeout(lmFetchHistoricalPath, 1500);

  if (IS_LABYRINTH) {
    setTimeout(sendAccHistoryToVPS, 9000);
    setInterval(sendAccHistoryToVPS, 15 * 60 * 1000);
  }

  (function loadMineSettingsFromServer() {
    var u = PW.visitor_name || '';
    if (!u) return;

    var lsEnabled   = window.lmLS.get('lm_mine_auto_enabled'  + _lmUserSuffix);
    var lsThreshold = window.lmLS.get('lm_mine_threshold'     + _lmUserSuffix);
    if (lsEnabled !== null && lsThreshold !== null) return;
    window.lmFetch(VPS_URL + '/my/profile', { headers: vpsAuthHeaders(false) })
      .then(function(r) { return r.ok ? r.json() : null; })
      .then(function(p) {
        if (!p) return;
        if (lsEnabled === null && p.mine_auto_enabled != null) {
          window.lmLS.set('lm_mine_auto_enabled' + _lmUserSuffix, p.mine_auto_enabled ? 'true' : 'false');
        }
        if (lsThreshold === null && p.mine_auto_threshold != null) {
          window.lmLS.set('lm_mine_threshold' + _lmUserSuffix, String(p.mine_auto_threshold));
        }
      })
      .catch(function() {});
  })();

  scheduleProfileSync();
  setInterval(function(){ syncProfileToVPS(false); }, 60000);
  if (IS_CLUB_PAGE) {
    setTimeout(function(){ checkAndSyncClub(true); }, 1200);

    setInterval(function(){ checkAndSyncClub(false); }, 10 * 60 * 1000);
  }

  window._lmVPS = {
    syncRollback: function() {
      var tb = window._lmAllTimeTrapBacks;
      var tp = window._lmRollbackPlace;
      lmDebug('[Синхронизация] Manual sync rollback:', tb, tp);
      if (tb) sendRollbackToVPS(tb, tp||null);
    },
    syncHistory: sendAccHistoryToVPS,
    syncAll: function() {
      window._lmVPS.syncRollback();
      sendAccHistoryToVPS();
    },
    getHistCache: function() { return histCache; },
    getHistSample: function() {
      var keys = Object.keys(histCache);
      if (!keys.length) return 'Кеш истории пуст';
      var first = histCache[keys[0]];
      console.log('pages:', keys.length, '| rows[0]:', JSON.stringify(first));
      return first;
    }
  };

})();

(function() {
  'use strict';
  if (window._lmReportInit) return; window._lmReportInit = true;
  if (!/animesss\.(com|tv)$/.test(location.hostname)) return;

  var REPORT_API = window.LM_ENV_CONFIG && window.LM_ENV_CONFIG.apiBase;
  var _lmErrBuf = [];
  function _lmPushErr(o) { try { _lmErrBuf.push(o); if (_lmErrBuf.length > 15) _lmErrBuf.shift(); } catch (e) {} }
  if (!REPORT_API) return;
  var VER = (typeof GM_info !== 'undefined' && GM_info.script && GM_info.script.version) ? GM_info.script.version : '10.15';
  function nick() { return PW.visitor_name || 'unknown'; }
  function onLab() { return /\/labyrinth(\/|$)/.test(location.pathname || ''); }

  var KNOWN = {};
  ('start reward penalty quiz quiz_result puzzle jackpot reward_card shield_block empty collection ' +
   'trap_back mini_boss hard_boss luck_altar luck_altar_result locked_chest locked_chest_result ' +
   'card_trader card_trader_result relic_room recovery_room room_trap room_gift spiritual_teleport ' +
   'mimic_chest mimic_chest_hit mimic_chest_killed mimic_chest_escape mimic_chest_reward mimic_chest_back ' +
   'guardian_user guardian_club can_capture personal_mine personal_mine_created personal_mine_collect ' +
   'foreign_mine fatigue club_war_room echo_room echo_room_result fate_room fate_room_result ' +
   'puzzle_result room_trap_result room_gift_result mini_boss_hit mini_boss_killed mini_boss_result ' +
   'hard_boss_hit hard_boss_killed boss_hit boss_killed personal_mine_upgrade resonance_room kobold_room ' +
   'rl mx mr success error ok').split(' ').forEach(function(k){ KNOWN[k] = 1; });

  function postReport(payload, timeoutMs) {
    var ctrl = new AbortController();
    var t = setTimeout(function(){ ctrl.abort(); }, timeoutMs || 30000);

    return window.lmFetch(REPORT_API + '/report', {
      method: 'POST', cache: 'no-store', credentials: 'omit', signal: ctrl.signal,
      headers: window.LM_VPS_AUTH_HEADERS(true),
      body: JSON.stringify(payload)
    }).then(function(r){ clearTimeout(t); if (!r.ok) throw new Error('HTTP ' + r.status); return r.json(); })
      .catch(function(e){ clearTimeout(t); throw e; });
  }

  function _slim(resp) {
    try {
      var c = {}; for (var k in resp) { if (k === 'mapData') continue;
        var v = resp[k];
        if (v && typeof v === 'object') { try { if (JSON.stringify(v).length > 4000) continue; } catch(e) { continue; } }
        c[k] = v;
      }
      if (resp.mapData && resp.mapData.steps) c._lastStep = resp.mapData.steps[resp.mapData.steps.length - 1] || null;
      return c;
    } catch(e) { return null; }
  }

  var _unknownSent = null;
  function _loadSent() {
    if (_unknownSent) return _unknownSent;
    _unknownSent = {};
    try { JSON.parse(window.lmLS.get('lm_unknown_reported') || '[]').forEach(function(t){ _unknownSent[t] = 1; }); } catch(e) {}
    return _unknownSent;
  }
  window._lmScanUnknown = function(resp) {
    if (!resp) return;
    var types = [];
    if (typeof resp.event === 'string') types.push(resp.event);
    try {
      var steps = resp.mapData && resp.mapData.steps;
      if (steps && steps.length) for (var i = Math.max(0, steps.length - 6); i < steps.length; i++) {
        if (steps[i] && typeof steps[i].event === 'string') types.push(steps[i].event);
      }
    } catch(e) {}
    var sent = _loadSent();
    var fresh = [];
    types.forEach(function(t){
      if (!t || KNOWN[t] || sent[t]) return;
      if (!/^[a-z][a-z0-9_]{1,39}$/.test(t)) return;
      if (/(_result|_hit|_killed|_escape|_reward|_back|_upgrade|_created|_collect)$/.test(t)) return;
      sent[t] = 1; fresh.push(t);
    });
    if (!fresh.length) return;
    try { window.lmLS.set('lm_unknown_reported', JSON.stringify(Object.keys(sent).slice(-200))); } catch(e) {}

    postReport({
      trigger: 'unknown_room', unknown_room: true,
      username: nick(), script_version: VER, url: location.href,
      x: (resp.x != null ? resp.x : null), y: (resp.y != null ? resp.y : null),
      user_message: 'AUTO: новые типы комнат: ' + fresh.join(', '),
      labyrinth_data: { new_types: fresh, event: resp.event || null, sample: _slim(resp) }
    }, 15000).catch(function(){});
  };

  var _lmLatestVer = null;
  function _vcmp(a, b) {
    var pa = String(a||'').split('.').map(function(n){ return parseInt(n,10)||0; });
    var pb = String(b||'').split('.').map(function(n){ return parseInt(n,10)||0; });
    for (var i = 0; i < Math.max(pa.length, pb.length); i++) { var d = (pa[i]||0) - (pb[i]||0); if (d) return d > 0 ? 1 : -1; }
    return 0;
  }
  function checkServer() {
    var ctrl = new AbortController();
    var t = setTimeout(function(){ ctrl.abort(); }, 8000);
    var t0 = Date.now();
    return window.lmFetch(REPORT_API + '/map/version', { cache:'no-store', headers:window.LM_VPS_AUTH_HEADERS(false), signal:ctrl.signal })
      .then(function(r){ clearTimeout(t);
        if (r.ok) return r.json().then(function(d){ if (d && d.latest_script) _lmLatestVer = d.latest_script; return { ok: true, label: 'Связь с сервером', detail: 'OK (' + (Date.now()-t0) + ' мс)' }; })
                                  .catch(function(){ return { ok: true, label: 'Связь с сервером', detail: 'OK (' + (Date.now()-t0) + ' мс)' }; });
        if (r.status === 520 || r.status === 521 || r.status === 522)
          return { ok: false, label: 'Связь с сервером', detail: 'Ошибка ' + r.status, advice: 'Сервер перегружен (Cloudflare). Подождите 1-2 минуты и попробуйте снова.' };
        if (r.status === 429)
          return { ok: false, label: 'Связь с сервером', detail: '429', advice: 'Слишком много запросов. Подождите минуту.' };
        return { ok: false, label: 'Связь с сервером', detail: 'HTTP ' + r.status, advice: 'Сервер ответил ошибкой. Сообщите разработчику - отправьте отчёт ниже.' };
      })
      .catch(function(e){ clearTimeout(t);
        return { ok: false, label: 'Связь с сервером', detail: (e.name === 'AbortError' ? 'таймаут' : 'нет связи'),
          advice: 'Соединение блокируется. Отключите AdBlock / приватные расширения для animesss и проверьте интернет.' };
      });
  }
  function diagnose() {
    var checks = [];
    var lsOk = false; try { window.localStorage.setItem('__lm_t','1'); window.localStorage.removeItem('__lm_t'); lsOk = true; } catch(e) {}
    checks.push(lsOk ? { ok: true, label: 'Сохранение данных', detail: 'работает' }
                     : { ok: false, label: 'Сохранение данных', detail: 'не работает', advice: 'Выключите режим Инкогнито — карта не сможет сохранять данные.' });
    checks.push({ ok: true, label: 'Скрипты Tampermonkey', detail: 'включены' });
    checks.push(window._lmXHRPatched ? { ok: true, label: 'Перехват данных лабиринта', detail: 'подключено' }
      : { ok: false, label: 'Перехват данных лабиринта', detail: 'не подключено',
          advice: onLab() ? 'Другое расширение мешает. Отключите лишние скрипты и обновите страницу.' : 'Откройте страницу лабиринта — перехват включается там.' });
    checks.push((PW.visitor_name) ? { ok: true, label: 'Авторизация на сайте', detail: 'успешна' }
      : { ok: false, label: 'Авторизация на сайте', detail: 'не успешна', advice: 'Войдите в аккаунт animesss.' });
    checks.push(onLab() ? { ok: true, label: 'Страница лабиринта', detail: 'открыта' }
      : { ok: false, label: 'Страница лабиринта', detail: 'не открыта', advice: 'Откройте лабиринт для полной проверки.' });
    return checkServer().then(function(srv){
      checks.splice(0, 0, srv);
      var outdated = _lmLatestVer && _vcmp(VER, _lmLatestVer) < 0;
      checks.push(outdated
        ? { ok: false, label: 'Версия скрипта', detail: VER + ' — устарела!', advice: 'Доступна новая версия ' + _lmLatestVer + '. Обновите скрипт на Greasyfork (в Tampermonkey → проверить обновления).' }
        : { ok: true, label: 'Версия скрипта', detail: VER + (_lmLatestVer ? ' (последняя)' : '') });
      return checks;
    });
  }

  function _lmDeepDiag() {
    var out = { ts: Date.now() };
    try {
      var cfg = window.LM_ENV_CONFIG || {};
      out.env = { ver: VER, apiBase: cfg.apiBase || REPORT_API || '', mode: String(window.LM_MODE || cfg.mode || ''),
        host: location.hostname, path: location.pathname, isLab: onLab(),
        loggedIn: !!(PW.visitor_name) || /(^|;\s*)dle_user_id=\d+/.test(document.cookie || '') };
    } catch (e) { out.env = { err: String(e) }; }
    try {
      var tok = (typeof window._lmDevTok === 'function') ? window._lmDevTok() : '';
      var lsW = false, ssW = false;
      try { localStorage.setItem('__lmd','1'); lsW = localStorage.getItem('__lmd') === '1'; localStorage.removeItem('__lmd'); } catch (e) {}
      try { sessionStorage.setItem('__lmd','1'); ssW = sessionStorage.getItem('__lmd') === '1'; sessionStorage.removeItem('__lmd'); } catch (e) {}
      var u = (nick() || '').toLowerCase().replace(/[^a-z0-9_]/g,'_').slice(0,30);
      var tokKey = 'lm_dev_tok_v1' + (u ? '_u_' + u : '');
      var tokInLS = false; try { tokInLS = !!localStorage.getItem(tokKey); } catch (e) {}
      out.auth = { hasToken: !!tok, tokenLen: tok ? tok.length : 0, tokenInLS: tokInLS, lsWrite: lsW, ssWrite: ssW, xhrPatched: !!window._lmXHRPatched };
    } catch (e) { out.auth = { err: String(e) }; }
    try { out.internal = (typeof window._lmDiag === 'function') ? window._lmDiag() : null; } catch (e) { out.internal = { err: String(e) }; }
    out.errors = (_lmErrBuf || []).slice(-15);
    var ctrl = new AbortController();
    var to = setTimeout(function(){ try { ctrl.abort(); } catch (e) {} }, 8000);
    var H = window.LM_VPS_AUTH_HEADERS(false);
    return Promise.all([
      window.lmFetch(REPORT_API + '/directives', { cache:'no-store', headers: H, signal: ctrl.signal })
        .then(function(r){ return { status: r.status }; }).catch(function(e){ return { status: 'ERR', err: String((e && e.name) || e) }; }),
      window.lmFetch(REPORT_API + '/map/version', { cache:'no-store', headers: H, signal: ctrl.signal })
        .then(function(r){ return r.ok ? r.json().then(function(d){ return { status: r.status, serverRooms: d && d.rooms_count, serverVer: d && d.version, needAuth: d && d.need_auth, latest: d && d.latest_script }; }) : { status: r.status }; })
        .catch(function(e){ return { status: 'ERR', err: String((e && e.name) || e) }; })
    ]).then(function(res){
      clearTimeout(to);
      out.live = { directives: res[0], mapVersion: res[1] };
      try {
        var cr = out.internal && out.internal.map ? out.internal.map.clientRooms : null;
        var sr = res[1] && res[1].serverRooms;
        if (cr != null && sr != null) out.live.mapBehind = sr - cr;
      } catch (e) {}
      return out;
    }).catch(function(e){ clearTimeout(to); out.liveErr = String(e); return out; });
  }

  function lsDump() {
    var out = {};
    try { var L = window.localStorage; for (var i = 0; i < L.length; i++) { var k = L.key(i);
      if (k && k.indexOf('lm') === 0) { var v = redactDiagnosticText(L.getItem(k)); out[k] = (v && v.length > 8000) ? v.slice(0,8000)+'…[обрезано]' : v; } } } catch(e) {}
    return out;
  }
  function redactDiagnosticText(value) {
    var text = String(value == null ? '' : value);
    var token = String(PW.dle_login_hash || '');
    var dtok = '';
    try { dtok = (typeof window._lmDevTok === 'function') ? window._lmDevTok() : ''; } catch (e) {}
    if (token) text = text.split(token).join('[REDACTED_TOKEN]');
    if (dtok) text = text.split(dtok).join('[REDACTED_TOKEN]');
    return text
      .replace(/(Authorization\s*[:=]\s*Bearer\s+)[A-Za-z0-9._~+\/-]{8,512}/gi, '$1[REDACTED]')
      .replace(/([?&](?:token|auth_token|access_token|key)=)[^&#"'\s]+/gi, '$1[REDACTED]')
      .replace(/((?:dle_login_hash|auth_token|access_token|refresh_token|admin_key|write_secret|password)\s*["']?\s*[:=]\s*["'])[^"']+/gi, '$1[REDACTED]');
  }
  function sanitizedPageHtml() {
    try {
      var clone = document.documentElement.cloneNode(true);
      Array.prototype.forEach.call(clone.querySelectorAll('script,input[type="password"],input[name*="token" i],input[name*="hash" i],input[name*="secret" i]'), function(el){
        if (el && el.parentNode) el.parentNode.removeChild(el);
      });
      var html = redactDiagnosticText(clone.outerHTML);
      return html.length > 2000000 ? html.slice(0, 2000000) + '<!-- truncated -->' : html;
    } catch(e) { return ''; }
  }
  function collect(trigger, userMsg, diagArr, errorInfo, includeHtml, deep) {
    var ld = null; try { if (PW.labyrinthData) { var s = JSON.stringify(PW.labyrinthData);
      s = redactDiagnosticText(s); ld = (s.length > 600000) ? { _note: 'labyrinthData слишком большой', size: s.length } : JSON.parse(s); } } catch(e) {}
    var _cur = null; try { _cur = PW.labyrinthData && PW.labyrinthData.mapData && PW.labyrinthData.mapData.current; } catch(e) {}
    var p = {
      trigger: trigger, username: nick(), script_version: VER, url: redactDiagnosticText(location.href),
      user_agent: navigator.userAgent, user_message: userMsg || '',
      x: (_cur && _cur.x != null) ? _cur.x : null,
      y: (_cur && _cur.y != null) ? _cur.y : null,
      diagnostics: { checks: diagArr || null, deep: deep || null }, labyrinth_data: ld, ls_dump: lsDump(), error: errorInfo || null
    };
    if (includeHtml) p.page_html = sanitizedPageHtml();
    return p;
  }

  function el(tag, css, html) { var e = document.createElement(tag); if (css) e.style.cssText = css; if (html != null) e.innerHTML = html; return e; }
  var Z = 2147483600;

  var CARD_CSS = 'background:linear-gradient(180deg,#141d33,#0d1322);border:1px solid #263655;border-radius:18px;box-shadow:0 28px 80px rgba(0,0,0,.65);padding:32px 38px;min-width:300px;max-width:90vw;text-align:center;color:#e6eefb;font:600 16px system-ui,sans-serif';
  function lockOverlay() {
    var o = el('div', 'position:fixed;inset:0;z-index:'+(Z+5)+';background:rgba(3,6,14,.88);backdrop-filter:blur(5px);display:flex;align-items:center;justify-content:center;cursor:wait');
    o.innerHTML = '<div id="lmLockCard" style="'+CARD_CSS+'">'
      + '<div style="width:54px;height:54px;margin:0 auto 20px;border:4px solid rgba(120,170,255,.18);border-top-color:#6ea8ff;border-radius:50%;animation:lmspin .9s linear infinite"></div>'
      + '<div style="font-size:17px">Отправка отчёта…</div>'
      + '<div style="font-weight:400;color:#8ea3c4;font-size:13px;margin-top:8px;line-height:1.5">Пожалуйста, подождите.<br>Ничего не нажимайте на странице.</div></div>';
    document.body.appendChild(o); return o;
  }
  function lockCard(lock, iconBg, icon, title, sub, buttonsHtml) {
    lock.style.cursor = 'default';
    var card = lock.querySelector('#lmLockCard'); if (!card) return;
    card.innerHTML =
      '<div style="width:66px;height:66px;margin:0 auto 18px;border-radius:50%;background:'+iconBg+';display:flex;align-items:center;justify-content:center;font-size:36px">'+icon+'</div>'
      + '<div style="font-size:19px">'+title+'</div>'
      + (sub ? '<div style="font-weight:400;color:#8ea3c4;font-size:13px;margin-top:10px;line-height:1.5">'+sub+'</div>' : '')
      + '<div style="margin-top:22px">'+buttonsHtml+'</div>';
  }
  var BTN_PRIMARY = 'background:linear-gradient(135deg,#3b82f6,#22b8cf);border:none;color:#fff;padding:11px 30px;border-radius:11px;cursor:pointer;font-weight:700;font-size:14px';
  var BTN_GHOST = 'background:#26324c;border:none;color:#cfdcf2;padding:11px 22px;border-radius:11px;cursor:pointer;font-size:14px;margin-left:10px';

  function openModal(prefillMsg, errorInfo) {
    var back = el('div', 'position:fixed;inset:0;z-index:'+Z+';background:rgba(3,6,14,.7);display:flex;align-items:center;justify-content:center;padding:16px');
    var box = el('div', 'width:min(560px,96vw);max-height:92vh;overflow:auto;background:#0d1322;border:1px solid #233;border-radius:14px;box-shadow:0 20px 60px rgba(0,0,0,.6);color:#dfe8f5;font:14px system-ui,sans-serif');
    box.innerHTML =
      '<div style="padding:16px 18px;border-bottom:1px solid #1c2740;display:flex;align-items:center;gap:10px">'
      + '<b style="font-size:15px;color:#8fd0ff">🛠 Сообщить о поломке</b>'
      + '<span style="flex:1"></span><button id="lmRClose" style="background:none;border:none;color:#8aa;font-size:20px;cursor:pointer">✕</button></div>'
      + '<div style="padding:16px 18px">'
      + '<div style="font-size:12px;color:#8aa;margin-bottom:6px">Проверка системы:</div>'
      + '<div id="lmDiag" style="background:#0a1020;border:1px solid #1c2740;border-radius:9px;padding:10px;font-size:13px;min-height:40px">Проверяю…</div>'
      + '<div style="font-size:12px;color:#8aa;margin:14px 0 6px">Опишите проблему (необязательно):</div>'
      + '<textarea id="lmRMsg" placeholder="Например: карта не открывается, шахта не собирается…" '
      + 'style="width:100%;box-sizing:border-box;min-height:78px;background:#0a1020;border:1px solid #233;border-radius:9px;color:#dfe8f5;padding:10px;font:13px system-ui;resize:vertical"></textarea>'
      + '<div style="font-size:11px;color:#6b7a93;margin-top:8px">Передається очищений діагностичний знімок без script-тегів, паролів і токена. Дані бачить тільки адміністратор.</div>'
      + '<div style="display:flex;gap:10px;margin-top:16px">'
      + '<button id="lmRSend" style="flex:1;background:linear-gradient(135deg,#3b82f6,#22b8cf);border:none;color:#fff;font-weight:700;padding:11px;border-radius:9px;cursor:pointer;font-size:14px">📤 Отправить отчёт</button>'
      + '<button id="lmRCancel" style="background:#1a2236;border:1px solid #2a3550;color:#bcd;padding:11px 16px;border-radius:9px;cursor:pointer">Отмена</button></div>'
      + '<div id="lmRResult" style="margin-top:12px"></div></div>';
    back.appendChild(box); document.body.appendChild(back);
    if (prefillMsg) box.querySelector('#lmRMsg').value = prefillMsg;
    function close() { try { document.body.removeChild(back); } catch(e) {} }
    box.querySelector('#lmRClose').onclick = close;
    box.querySelector('#lmRCancel').onclick = close;
    back.onclick = function(e){ if (e.target === back) close(); };

    var diagArr = null, deepDiag = null;
    var deepP = _lmDeepDiag().then(function(d){ deepDiag = d; }).catch(function(){});
    diagnose().then(function(arr){ diagArr = arr;
      box.querySelector('#lmDiag').innerHTML = arr.map(function(c){
        return '<div style="display:flex;gap:8px;padding:3px 0;align-items:flex-start">'
          + '<span>' + (c.ok ? '✅' : '❌') + '</span>'
          + '<div><b>' + c.label + ':</b> ' + (c.detail || '')
          + (c.advice ? '<div style="color:#ffb454;font-size:12px;margin-top:2px">→ ' + c.advice + '</div>' : '') + '</div></div>';
      }).join('');
    }).catch(function(){ box.querySelector('#lmDiag').textContent = 'Не удалось выполнить проверку.'; });

    box.querySelector('#lmRSend').onclick = function() {
      var msg = box.querySelector('#lmRMsg').value || '';
      close();
      var lock = lockOverlay();
      Promise.resolve(deepP).then(function(){
      var payload = collect('manual', msg, diagArr, errorInfo, true, deepDiag);
      return postReport(payload, 60000).then(function(res){
        lockCard(lock, 'rgba(46,204,113,.16)', '✅', 'Отчёт отправлен!',
          'Спасибо! Номер обращения: <b style="color:#9fc5ff">#' + (res && res.id || '?') + '</b>',
          '<button id="lmLockOk" style="'+BTN_PRIMARY+'">Готово</button>');
        var ok = document.getElementById('lmLockOk');
        if (ok) ok.onclick = function(){ try { document.body.removeChild(lock); } catch(e) {} };
      }).catch(function(e){
        lockCard(lock, 'rgba(255,170,50,.16)', '⚠️', 'Не удалось отправить',
          (e.message || 'ошибка связи'),
          '<button id="lmLockRetry" style="'+BTN_PRIMARY+'">Повторить</button>'
          + '<button id="lmLockClose" style="'+BTN_GHOST+'">Закрыть</button>');
        var rt = document.getElementById('lmLockRetry'), cl = document.getElementById('lmLockClose');
        if (rt) rt.onclick = function(){ try { document.body.removeChild(lock); } catch(e) {} openModal(msg, errorInfo); };
        if (cl) cl.onclick = function(){ try { document.body.removeChild(lock); } catch(e) {} };
      });
      });
    };
  }
  window._lmOpenReport = openModal;

  function addButton() {
    if (!onLab()) return;
    if (document.getElementById('lmReportBtn')) return;
    if (document.getElementById('lm-anbtn')) return;
    var b = el('button', 'position:fixed;left:14px;bottom:14px;z-index:'+(Z-10)+';background:#16203a;border:1px solid #2c3b5e;color:#9fc5ff;font:600 12px system-ui;padding:8px 12px;border-radius:10px;cursor:pointer;box-shadow:0 4px 14px rgba(0,0,0,.4);opacity:.85');
    b.id = 'lmReportBtn'; b.textContent = '🛠 Сообщить о поломке';
    b.title = 'Проверка и отправка отчёта разработчику';
    b.onmouseenter = function(){ b.style.opacity = '1'; };
    b.onmouseleave = function(){ b.style.opacity = '.85'; };
    b.onclick = function(){ openModal('', null); };
    document.body.appendChild(b);
  }

  var _errShown = false;
  function looksOurs(stack, src) {

    var s = (stack || '') + ' ' + (src || '');
    return /labyrinth_map|_lm[A-Z]|lmFetch|lmLS|Animesss Labyrinth/.test(s);
  }
  function offerError(info) {
    if (_errShown || !onLab()) return; _errShown = true;
    var tip = el('div', 'position:fixed;left:14px;bottom:58px;z-index:'+(Z-9)+';background:#2a1414;border:1px solid #5a2b2b;color:#ffd9d9;font:13px system-ui;padding:12px 14px;border-radius:11px;max-width:300px;box-shadow:0 6px 18px rgba(0,0,0,.5)');
    tip.innerHTML = '⚠️ Возможна ошибка в работе карты.<br>Отправить отчёт разработчику?'
      + '<div style="display:flex;gap:8px;margin-top:10px"><button id="lmEY" style="flex:1;background:#d13b3b;border:none;color:#fff;padding:7px;border-radius:7px;cursor:pointer;font-weight:600">Да</button>'
      + '<button id="lmEN" style="background:#3a2a2a;border:none;color:#eebcbc;padding:7px 14px;border-radius:7px;cursor:pointer">Нет</button></div>';
    document.body.appendChild(tip);
    var to = setTimeout(function(){ try { document.body.removeChild(tip); } catch(e) {} }, 15000);
    tip.querySelector('#lmEY').onclick = function(){ clearTimeout(to); try { document.body.removeChild(tip); } catch(e) {} openModal('АВТО: обнаружена ошибка скрипта.', info); };
    tip.querySelector('#lmEN').onclick = function(){ clearTimeout(to); try { document.body.removeChild(tip); } catch(e) {} };
  }
  window.addEventListener('error', function(e){
    try { if (!e || !e.error) return;
      var ours = looksOurs(e.error && e.error.stack, e.filename);
      _lmPushErr({ t: Date.now(), type: 'error', ours: !!ours, message: String(e.message||'').slice(0,200), src: String(e.filename||'').slice(0,160), line: e.lineno||0, stack: String((e.error&&e.error.stack)||'').slice(0,400) });
      if (!ours) return;
      offerError({ type: 'error', message: String(e.message||''), stack: String((e.error&&e.error.stack)||'').slice(0,2000), src: e.filename||'', line: e.lineno||0 });
    } catch(_) {}
  });
  window.addEventListener('unhandledrejection', function(e){
    try { var r = e && e.reason; var stack = (r && r.stack) || '';
      var ours = looksOurs(stack, '');
      _lmPushErr({ t: Date.now(), type: 'rejection', ours: !!ours, message: String((r&&r.message)||r||'').slice(0,200), stack: String(stack).slice(0,400) });
      if (!ours) return;
      offerError({ type: 'unhandledrejection', message: String((r&&r.message)||r||''), stack: String(stack).slice(0,2000) });
    } catch(_) {}
  });

  function init() { setTimeout(function(){ try { addButton(); } catch(e) {} }, 4000); }
  if (document.body) init(); else document.addEventListener('DOMContentLoaded', init);

  try { var st = document.createElement('style'); st.textContent = '@keyframes lmspin{to{transform:rotate(360deg)}}'; document.head.appendChild(st); } catch(e) {}
})();

(function() {
  'use strict';
  if (window._lmMsgInit) return; window._lmMsgInit = true;
  if (!/animesss\.(com|tv)$/.test(location.hostname)) return;

  var MSG_API = window.LM_ENV_CONFIG && window.LM_ENV_CONFIG.apiBase;
  if (!MSG_API) return;
  function nick() { return PW.visitor_name || ''; }
  var _msgs = [];
  var Z = 2147483600;

  function lastSeen() { try { return parseInt(window.lmLS.get('lm_msg_lastseen') || '0', 10) || 0; } catch(e) { return 0; } }
  function setLastSeen(id) { try { window.lmLS.set('lm_msg_lastseen', String(id)); } catch(e) {} }
  function unreadCount() { var ls = lastSeen(); return _msgs.filter(function(m){ return Number(m.id) > ls; }).length; }

  function updateBadge() {
    var b = document.getElementById('lm-msgbadge'), btn = document.getElementById('lm-msgbtn');
    if (!b || !btn) return;
    var n = unreadCount();
    if (n > 0) { b.textContent = n > 99 ? '99+' : String(n); b.classList.add('show'); btn.classList.add('has-unread'); }
    else { b.classList.remove('show'); btn.classList.remove('has-unread'); }
  }

  function poll() {
    var u = nick(); if (!u) return;
    var ctrl = new AbortController();
    var t = setTimeout(function(){ ctrl.abort(); }, 10000);
    window.lmFetch(MSG_API + '/messages?username=' + encodeURIComponent(u), { cache:'no-store', headers:window.LM_VPS_AUTH_HEADERS(false), signal:ctrl.signal })
      .then(function(r){ clearTimeout(t); return r.ok ? r.json() : null; })
      .then(function(d){ if (d && Array.isArray(d.messages)) { _msgs = d.messages; updateBadge(); } })
      .catch(function(){ clearTimeout(t); });
  }
  window._lmMsgPoll = poll;

  function fmtTime(iso) {
    try { var d = new Date(iso); return ('0'+d.getDate()).slice(-2)+'.'+('0'+(d.getMonth()+1)).slice(-2)+' '+('0'+d.getHours()).slice(-2)+':'+('0'+d.getMinutes()).slice(-2); } catch(e) { return ''; }
  }
  function esc(s) { return String(s == null ? '' : s).replace(/[&<>"]/g, function(c){ return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;'}[c]; }); }

  window._lmOpenMessages = function() {
    var back = document.createElement('div');
    back.style.cssText = 'position:fixed;inset:0;z-index:'+Z+';background:rgba(3,6,14,.7);display:flex;align-items:center;justify-content:center;padding:16px';
    var box = document.createElement('div');
    box.style.cssText = 'width:min(480px,96vw);max-height:88vh;display:flex;flex-direction:column;background:#0d1322;border:1px solid #233;border-radius:14px;box-shadow:0 20px 60px rgba(0,0,0,.6);color:#dfe8f5;font:14px system-ui,sans-serif';
    var rows = _msgs.slice().sort(function(a,b){ return Number(a.id)-Number(b.id); }).map(function(m){
      var upd = (m.msg_type === 'update' && m.action_url)
        ? '<div style="margin-top:8px"><a href="'+esc(m.action_url)+'" target="_blank" rel="noopener" style="display:inline-block;background:linear-gradient(135deg,#3b82f6,#22b8cf);color:#fff;text-decoration:none;padding:8px 16px;border-radius:8px;font-weight:700;font-size:13px">⬆ Обновить скрипт</a></div>'
        : '';
      var rep = m.allow_reply
        ? '<div class="lm-reply" data-mid="'+m.id+'" style="margin-top:10px;border-top:1px solid #1c2740;padding-top:8px">'
          + '<div style="font-size:11px;color:#8fd0ff;margin-bottom:5px">✍ Можно ответить админу:</div>'
          + '<textarea class="lm-reply-txt" placeholder="Ваш ответ..." style="width:100%;box-sizing:border-box;min-height:44px;background:#0a1020;border:1px solid #233;border-radius:8px;color:#dfe8f5;padding:8px;font:13px system-ui;resize:vertical"></textarea>'
          + '<div style="margin-top:6px"><button class="lm-reply-btn" style="background:linear-gradient(135deg,#3b82f6,#22b8cf);border:none;color:#fff;font-weight:700;padding:7px 16px;border-radius:8px;cursor:pointer;font-size:13px">Ответить</button><span class="lm-reply-res" style="margin-left:8px;font-size:12px"></span></div>'
          + '</div>'
        : '';
      return '<div style="background:#111a2e;border:1px solid #1c2740;border-radius:10px;padding:10px 12px;margin-bottom:10px">'
        + '<div style="font-size:11px;color:#8fd0ff;font-weight:700;margin-bottom:4px">👑 Админ лабиринта <span style="color:#5b6b85;font-weight:400;float:right">'+fmtTime(m.created_at)+'</span></div>'
        + '<div style="white-space:pre-wrap;line-height:1.5">'+esc(m.text)+'</div>' + upd + rep + '</div>';
    }).join('');
    box.innerHTML =
      '<div style="padding:14px 16px;border-bottom:1px solid #1c2740;display:flex;align-items:center;gap:10px">'
      + '<b style="font-size:15px;color:#8fd0ff">💬 Сообщения от админа</b><span style="flex:1"></span>'
      + '<button id="lmMsgClose" style="background:none;border:none;color:#8aa;font-size:20px;cursor:pointer">✕</button></div>'
      + '<div style="padding:14px 16px;overflow:auto">' + (rows || '<div style="text-align:center;color:#6b7a93;padding:30px 0">📭 У вас нет сообщений</div>') + '</div>';
    back.appendChild(box); document.body.appendChild(back);
    Array.prototype.forEach.call(box.querySelectorAll('.lm-reply-btn'), function(btn){
      btn.addEventListener('click', function(){
        var wrap = btn.closest ? btn.closest('.lm-reply') : null; if (!wrap) return;
        var mid = wrap.getAttribute('data-mid');
        var txtEl = wrap.querySelector('.lm-reply-txt'), res = wrap.querySelector('.lm-reply-res');
        var t = (txtEl.value || '').trim();
        if (!t) { res.style.color = '#ff9b9b'; res.textContent = 'Введите текст'; return; }
        btn.disabled = true; res.style.color = '#8aa'; res.textContent = 'Отправка...';
        window.lmFetch(MSG_API + '/message-reply', { method:'POST', cache:'no-store', headers:window.LM_VPS_AUTH_HEADERS(true), body:JSON.stringify({ username:nick(), message_id:Number(mid), text:t }) })
          .then(function(r){ return r.ok ? r.json() : null; })
          .then(function(d){ if (d && d.ok) { res.style.color='#6ee786'; res.textContent='✓ Ответ отправлен'; txtEl.value=''; txtEl.disabled=true; btn.style.display='none'; } else { res.style.color='#ff9b9b'; res.textContent='Ошибка'; btn.disabled=false; } })
          .catch(function(){ res.style.color='#ff9b9b'; res.textContent='Ошибка сети'; btn.disabled=false; });
      });
    });
    function close(){ try { document.body.removeChild(back); } catch(e) {} }
    box.querySelector('#lmMsgClose').onclick = close;
    back.onclick = function(e){ if (e.target === back) close(); };
    var maxId = _msgs.reduce(function(m,x){ return Math.max(m, Number(x.id)||0); }, 0);
    if (maxId > lastSeen()) setLastSeen(maxId);
    updateBadge();
  };

  poll();
  setInterval(poll, 90000);
  setInterval(updateBadge, 5000);
})();

(function () {
  var VPS = 'https://labyrinthmap.uk/api';
  function _cvHash() {
    try {
      var cv = document.createElement('canvas'); cv.width = 200; cv.height = 40;
      var ctx = cv.getContext('2d'); ctx.textBaseline = 'top'; ctx.font = '14px Arial';
      ctx.fillStyle = '#069'; ctx.fillText('lm-fp-❤█', 2, 2);
      ctx.strokeStyle = 'rgba(120,60,200,0.7)'; ctx.beginPath(); ctx.arc(50, 20, 15, 0, Math.PI * 2); ctx.stroke();
      var d = cv.toDataURL(), h = 0; for (var i = 0; i < d.length; i++) { h = (h * 31 + d.charCodeAt(i)) >>> 0; } return h.toString(16);
    } catch (e) { return ''; }
  }
  function _collect() {
    var n = navigator || {}, s = window.screen || {}, tz = '';
    try { tz = Intl.DateTimeFormat().resolvedOptions().timeZone || ''; } catch (e) {}
    return { ua:String(n.userAgent||''), lang:String(n.language||''), langs:(n.languages||[]).join(','),
      tz:tz, tzoff:new Date().getTimezoneOffset(),
      screen:(s.width||0)+'x'+(s.height||0)+'x'+(s.colorDepth||0), avail:(s.availWidth||0)+'x'+(s.availHeight||0),
      platform:String(n.platform||''), cores:n.hardwareConcurrency||0, mem:n.deviceMemory||0,
      touch:n.maxTouchPoints||0, dpr:window.devicePixelRatio||1, canvas:_cvHash() };
  }
  function _send() {
    try {
      if (!PW.visitor_name) return;
      var _dt = (typeof window._lmDevTok === 'function') ? window._lmDevTok() : (PW.dle_login_hash || '');
      if (!_dt) return;
      fetch(VPS + '/fingerprint', { method:'POST',
        headers:{ 'Content-Type':'application/json', 'X-Player':(typeof window._lmXPlayer==='function'?window._lmXPlayer():PW.visitor_name),
                  'Authorization':'Bearer '+_dt, 'X-Script-Version':((typeof GM_info!=='undefined'&&GM_info.script&&GM_info.script.version)?GM_info.script.version:'10.15') },
        body: JSON.stringify({ signals:_collect() }) }).catch(function(){});
    } catch (e) {}
  }
  if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', function(){ setTimeout(_send,3000); });
  else setTimeout(_send, 3000);
})();

(function () {
  'use strict';

  var VPS = 'https://labyrinthmap.uk/api';
  var VER = '2.0';

  var state = { show: false, arrow: '', word: '', offset: 0, remaining: 0, expire: 0 };
  var tick = null, lastSig = '';

  function accBal() {
    var el = document.getElementById('labyrinthBank');
    if (!el) return null;
    var n = parseInt((el.textContent || '').replace(/\D/g, ''), 10);
    return isFinite(n) ? n : null;
  }
  function curXY() {
    try {
      var c = PW.labyrinthData && PW.labyrinthData.mapData && PW.labyrinthData.mapData.current;
      return c ? { x: c.x, y: c.y } : { x: null, y: null };
    } catch (e) { return { x: null, y: null }; }
  }

  function send(k) {
    try {
      var _tok = (typeof window._lmDevTok === 'function') ? window._lmDevTok() : '';
      if (!PW.visitor_name || !_tok || !k || !k.type) return;
      var xy = curXY(), bal = accBal();
      var sig = k.type + '|' + (k.step != null ? k.step : '') + '|' + (k.next_direction || '') + '|' + xy.x + '|' + xy.y;
      if (sig === lastSig) return;
      lastSig = sig;
      fetch(VPS + '/kobold', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'X-Player': (typeof window._lmXPlayer==='function'?window._lmXPlayer():PW.visitor_name), 'Authorization': 'Bearer ' + _tok },
        body: JSON.stringify({ x: xy.x, y: xy.y, depth: xy.y != null ? Math.abs(xy.y) : null, acc_balance: bal, addon_version: VER, kobold: k })
      }).then(function (r) { return r.json(); }).then(function (d) { apply(d && d.kob); }).catch(function () {});
    } catch (e) {}
  }

  function apply(kob) {
    if (!kob || !kob.show) { clearHint(); return; }
    state.show = true;
    state.arrow = kob.arrow || '';
    state.word = kob.word || '';
    state.offset = Number(kob.cell_offset) || 0;
    state.remaining = Number(kob.remaining) || 0;
    state.expire = Number(kob.expire_at) || 0;
    showHint();
  }

  function styleOnce() {
    if (document.getElementById('kob-style')) return;
    var s = document.createElement('style');
    s.id = 'kob-style';
    s.textContent =
      '.kob-target{outline:3px solid #ffd54a!important;outline-offset:-2px;border-radius:20%;' +
      'box-shadow:0 0 16px rgba(255,213,74,.9),inset 0 0 12px rgba(255,213,74,.7)!important;' +
      'animation:kobPulse 1s ease-in-out infinite;position:relative;z-index:6!important;}' +
      '@keyframes kobPulse{0%,100%{filter:brightness(1)}50%{filter:brightness(1.5)}}' +
      '.kob-arrow{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);' +
      'font-size:clamp(12px,3vw,22px);line-height:1;color:#fff;text-shadow:0 0 6px #000,0 0 3px #000;' +
      'pointer-events:none;z-index:7;}' +
      '#kob-badge{position:fixed;left:50%;top:16px;transform:translateX(-50%);z-index:99999;' +
      'display:flex;align-items:center;gap:12px;padding:12px 18px;border-radius:16px;' +
      'background:linear-gradient(135deg,rgba(60,42,10,.96),rgba(30,20,4,.96));' +
      'border:1px solid rgba(255,213,74,.5);box-shadow:0 12px 34px rgba(0,0,0,.45);' +
      'color:#fff;font-family:inherit;font-size:15px;font-weight:700;max-width:calc(100vw - 24px);}' +
      '#kob-badge .kob-b-arrow{font-size:30px;color:#ffd54a;line-height:1;}' +
      '#kob-badge .kob-b-sub{font-size:12px;font-weight:500;color:rgba(255,255,255,.7);margin-top:2px;}' +
      '#kob-badge .kob-b-close{margin-left:8px;cursor:pointer;color:rgba(255,255,255,.55);font-size:18px;line-height:1;}';
    (document.head || document.documentElement).appendChild(s);
  }

  function clearCells() {
    var t = document.querySelectorAll('.kob-target');
    for (var i = 0; i < t.length; i++) {
      t[i].classList.remove('kob-target');
      var a = t[i].querySelector('.kob-arrow');
      if (a) a.parentNode.removeChild(a);
    }
  }

  function highlight() {
    clearCells();
    if (!state.show || !state.offset) return;
    var map = document.getElementById('labyrinthMap');
    if (!map) return;
    var cells = map.querySelectorAll('.labyrinth-cell');
    if (!cells.length) return;
    var cur = -1;
    for (var i = 0; i < cells.length; i++) {
      if (cells[i].classList.contains('labyrinth-cell--current')) { cur = i; break; }
    }
    if (cur < 0) return;
    var tIdx = cur + state.offset;
    if (tIdx < 0 || tIdx >= cells.length) return;
    var cell = cells[tIdx];
    cell.classList.add('kob-target');
    var arr = document.createElement('div');
    arr.className = 'kob-arrow';
    arr.textContent = state.arrow;
    cell.appendChild(arr);
  }

  function fmt(sec) {
    if (sec <= 0) return '00:00';
    var m = Math.floor(sec / 60), s = sec % 60;
    return (m < 10 ? '0' : '') + m + ':' + (s < 10 ? '0' : '') + s;
  }

  function renderBadge() {
    if (!state.show) return;
    styleOnce();
    var b = document.getElementById('kob-badge');
    if (!b) {
      b = document.createElement('div');
      b.id = 'kob-badge';
      document.body.appendChild(b);
    }
    var left = state.expire ? Math.max(0, state.expire - Math.floor(Date.now() / 1000)) : 0;
    b.innerHTML =
      '<span class="kob-b-arrow">' + state.arrow + '</span>' +
      '<div><div>Кобольд убежал ' + state.word + '</div>' +
      '<div class="kob-b-sub">Осталось шагов: ' + state.remaining +
      (state.expire ? ' · следы исчезнут через ' + fmt(left) : '') + '</div></div>' +
      '<span class="kob-b-close" title="Скрыть">×</span>';
    b.querySelector('.kob-b-close').onclick = clearHint;
  }

  function showHint() {
    styleOnce();
    highlight();
    renderBadge();
    if (tick) clearInterval(tick);
    tick = setInterval(function () {
      if (state.expire && state.expire - Math.floor(Date.now() / 1000) <= 0) { clearHint(); return; }
      highlight();
      renderBadge();
    }, 600);
  }

  function clearHint() {
    state.show = false;
    if (tick) { clearInterval(tick); tick = null; }
    clearCells();
    var b = document.getElementById('kob-badge');
    if (b) b.parentNode.removeChild(b);
  }

  function parseResp(text) {
    if (!text || typeof text !== 'string' || text.length > 800000 || text.indexOf('"kobold"') < 0) return;
    try {
      var d = JSON.parse(text);
      if (!d || typeof d !== 'object') return;
      var k = d.kobold || (d.data && d.data.kobold);
      if (k && typeof k === 'object') { send(k); }
      else if ('kobold' in d || (d.data && 'kobold' in d.data)) { clearHint(); }
    } catch (e) {}
  }

  var _send = PW.XMLHttpRequest.prototype.send;
  PW.XMLHttpRequest.prototype.send = function () {
    try {
      this.addEventListener('load', function () {
        try {
          var rt = (this.responseType === '' || this.responseType === 'text') ? this.responseText : null;
          if (rt) parseResp(rt);
        } catch (e) {}
      });
    } catch (e) {}
    return _send.apply(this, arguments);
  };

  if (window.fetch) {
    var _fetch = window.fetch;
    window.fetch = function () {
      return _fetch.apply(this, arguments).then(function (r) {
        try { r.clone().text().then(parseResp).catch(function () {}); } catch (e) {}
        return r;
      });
    };
  }

  function pollInitial() {
    try {
      var k = PW.labyrinthData && PW.labyrinthData.kobold;
      if (k && typeof k === 'object' && k.type) { send(k); }
    } catch (e) {}
  }
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', function () { setTimeout(pollInitial, 300); });
  } else {
    setTimeout(pollInitial, 300);
  }
  setTimeout(pollInitial, 1500);
})();

(function () {
  'use strict';
  if (window._lmResonInit) return; window._lmResonInit = true;
  if (!/animesss\.(com|tv)$/.test(location.hostname)) return;
  var API = window.LM_ENV_CONFIG && window.LM_ENV_CONFIG.apiBase;
  if (!API) return;

  function hdr(json) { try { return window.LM_VPS_AUTH_HEADERS(json); } catch (e) { return {}; } }
  function req(url, opts) {
    return (window.lmFetch || fetch)(url, opts).then(function (r) {
      if (r.ok) return r.json();
      var err = new Error('http'); err.status = r.status; throw err;
    });
  }

  var _types = null, _typesTried = false, _panel = null, _entitled = null, _pending = null;

  function inResonanceRoom() { return !!document.getElementById('labyrinthResonanceDepth'); }

  function fillForm(x, y) {
    var dep = document.getElementById('labyrinthResonanceDepth');
    var side = document.getElementById('labyrinthResonanceSide');
    if (!dep || !side) return false;
    dep.value = String(Math.abs(y)); dep.dispatchEvent(new Event('input', { bubbles: true }));
    var s = (x === 0) ? 'center' : (x > 0 ? 'east' : 'west');
    side.value = s; side.dispatchEvent(new Event('change', { bubbles: true }));
    if (x !== 0) {
      var o2 = document.getElementById('labyrinthResonanceOffset');
      if (o2) { o2.value = String(Math.abs(x)); o2.dispatchEvent(new Event('input', { bubbles: true })); }
    }
    return true;
  }

  function dirLabel(x, y) {
    var d = 'Глубина ' + Math.abs(y);
    if (x === 0) return d + ' · Центральная';
    return d + ' · ' + (x > 0 ? 'Восточная ' : 'Западная ') + Math.abs(x);
  }

  function markConsumed(x, y, source) {
    req(API + '/resonance/consumed', {
      method: 'POST', headers: hdr(true),
      body: JSON.stringify({ x: x, y: y, source: source })
    }).catch(function () {});
  }

  window._lmResCheck = function (text) {
    var p = window._lmResPending;
    if (!p) return;
    window._lmResPending = null;
    var low = String(text || '').toLowerCase();
    var used = low.indexOf('использ') >= 0;
    markConsumed(p.x, p.y, used ? 'reported_used' : 'used');
    var body = document.getElementById('lm-res-list');
    if (body) {
      var row = body.querySelector('[data-rx="' + p.x + '"][data-ry="' + p.y + '"]');
      if (row) {
        row.style.opacity = '.4';
        var fb = row.querySelector('.lm-res-fill');
        if (fb) { fb.textContent = used ? 'занято' : '✓ использовано'; fb.disabled = true; }
      }
    }
  };

  function renderTargets(list) {
    var box = document.getElementById('lm-res-list'); if (!box) return;
    if (!list || !list.length) { box.innerHTML = '<div class="lm-res-empty">Нет свободных комнат этого типа с игроком. Нажми обновить позже.</div>'; return; }
    box.innerHTML = list.map(function (t) {
      var age = t.age_sec != null ? (t.age_sec < 3600 ? Math.round(t.age_sec / 60) + ' мин' : Math.round(t.age_sec / 3600) + ' ч') : '';
      return '<div class="lm-res-row" data-rx="' + t.x + '" data-ry="' + t.y + '">' +
        '<div class="lm-res-info"><b>' + dirLabel(t.x, t.y) + '</b><span class="lm-res-age">игрок был ' + age + ' назад</span></div>' +
        '<button class="lm-res-fill" data-rx="' + t.x + '" data-ry="' + t.y + '">Заполнить</button></div>';
    }).join('');
    Array.prototype.forEach.call(box.querySelectorAll('.lm-res-fill'), function (b) {
      b.addEventListener('click', function () {
        var x = parseInt(b.getAttribute('data-rx'), 10), y = parseInt(b.getAttribute('data-ry'), 10);
        if (fillForm(x, y)) {
          _pending = { x: x, y: y, t: Date.now() };
          Array.prototype.forEach.call(box.querySelectorAll('.lm-res-fill'), function (o) { o.classList.remove('lm-res-active'); });
          b.classList.add('lm-res-active'); b.textContent = 'заполнено ↑';
          var gb = document.getElementById('labyrinthResonanceUseBtn'); if (gb) gb.scrollIntoView({ behavior: 'smooth', block: 'center' });
        }
      });
    });
  }

  function loadTargets() {
    var sel = document.getElementById('lm-res-type'); if (!sel) return;
    var type = sel.value; if (!type) return;
    var box = document.getElementById('lm-res-list'); if (box) box.innerHTML = '<div class="lm-res-empty">Ищу…</div>';
    req(API + '/resonance/targets?type=' + encodeURIComponent(type), { cache: 'no-store', headers: hdr(false) })
      .then(function (d) { if (d && d.ok) renderTargets(d.targets); })
      .catch(function (e) { if (box) box.innerHTML = '<div class="lm-res-empty">Ошибка' + (e && e.status ? ' (' + e.status + ')' : '') + '</div>'; });
  }

  function buildPanel(types) {
    if (document.getElementById('lm-res-panel')) return;
    var anchor = document.getElementById('labyrinthResonanceUseBtn');
    if (!anchor) return;
    var host = anchor.closest('div') || anchor.parentNode;
    injectCss();
    var p = document.createElement('div'); p.id = 'lm-res-panel';
    p.innerHTML =
      '<div class="lm-res-head">🔮 Поиск комнаты <span class="lm-res-badge">скрипт</span></div>' +
      '<div class="lm-res-ctl">' +
        '<select id="lm-res-type">' + types.map(function (t) { return '<option value="' + t.ev + '">' + t.label + '</option>'; }).join('') + '</select>' +
        '<button id="lm-res-find">Найти</button>' +
        '<button id="lm-res-refresh" title="Обновить список">⟳</button>' +
      '</div>' +
      '<div id="lm-res-list"></div>' +
      '<div class="lm-res-hint">Тип → Найти → Заполнить у нужной строки → жми Вызвать резонанс. Занято — бери следующую.</div>';
    host.parentNode.insertBefore(p, host.nextSibling);
    document.getElementById('lm-res-find').addEventListener('click', loadTargets);
    document.getElementById('lm-res-refresh').addEventListener('click', loadTargets);
    document.getElementById('lm-res-type').addEventListener('change', loadTargets);
    _panel = p;
  }

  function injectCss() {
    if (document.getElementById('lm-res-css')) return;
    var st = document.createElement('style'); st.id = 'lm-res-css';
    st.textContent =
      '#lm-res-panel{margin-top:14px;padding:14px;border-radius:14px;background:linear-gradient(135deg,rgba(40,24,60,.6),rgba(20,14,32,.7));border:1px solid rgba(160,110,255,.35);box-shadow:0 8px 26px rgba(0,0,0,.35);font:13px system-ui,sans-serif;color:#e7defb;}' +
      '#lm-res-panel .lm-res-head{font-weight:800;font-size:14px;margin-bottom:10px;color:#d9c2ff;display:flex;align-items:center;gap:8px;}' +
      '#lm-res-panel .lm-res-badge{font-size:9px;letter-spacing:.06em;text-transform:uppercase;background:rgba(160,110,255,.22);color:#cdb3ff;padding:2px 7px;border-radius:20px;font-weight:700;}' +
      '#lm-res-panel .lm-res-ctl{display:flex;gap:8px;align-items:center;}' +
      '#lm-res-panel select{flex:1;min-height:38px;padding:6px 10px;border-radius:9px;background:#160f24;border:1px solid #3a2b58;color:#e7defb;font:600 13px system-ui;}' +
      '#lm-res-panel button{min-height:38px;padding:8px 14px;border-radius:9px;border:none;cursor:pointer;font:700 13px system-ui;transition:.15s;}' +
      '#lm-res-find{background:linear-gradient(135deg,#8b52d8,#6a2fb0);color:#fff;box-shadow:0 4px 14px rgba(139,82,216,.4);}' +
      '#lm-res-find:hover{transform:translateY(-1px);}' +
      '#lm-res-refresh{background:rgba(255,255,255,.08);color:#cdb3ff;border:1px solid rgba(160,110,255,.3)!important;font-size:16px;padding:8px 12px;}' +
      '#lm-res-list{margin-top:10px;max-height:260px;overflow:auto;display:flex;flex-direction:column;gap:6px;}' +
      '.lm-res-row{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:8px 10px;border-radius:9px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.07);}' +
      '.lm-res-info{display:flex;flex-direction:column;min-width:0;}' +
      '.lm-res-info b{color:#fff;font-size:13px;}' +
      '.lm-res-age{font-size:10px;color:#9a86c0;margin-top:2px;}' +
      '.lm-res-fill{background:rgba(160,110,255,.18);color:#d9c2ff;border:1px solid rgba(160,110,255,.4)!important;white-space:nowrap;}' +
      '.lm-res-fill:hover:not(:disabled){background:rgba(160,110,255,.32);}' +
      '.lm-res-fill.lm-res-active{background:#6a2fb0;color:#fff;}' +
      '.lm-res-fill:disabled{cursor:default;opacity:.6;}' +
      '.lm-res-empty{padding:10px;text-align:center;color:#9a86c0;font-size:12px;}' +
      '.lm-res-hint{margin-top:9px;font-size:11px;color:#8f7cb8;line-height:1.4;}';
    document.head.appendChild(st);
  }

  function tick() {
    if (!inResonanceRoom()) { if (_panel) { try { _panel.remove(); } catch (e) {} _panel = null; } return; }
    if (_panel && document.getElementById('lm-res-panel')) return;
    _panel = null;
    if (_entitled === false) return;
    if (_types) { buildPanel(_types); return; }
    if (_typesTried) return;
    if (typeof window._lmFeatOn === 'function') {
      if (!window._lmFeatsReady) return;
      if (!window._lmFeatOn('resonance_finder')) { _entitled = false; return; }
    }
    _typesTried = true;
    req(API + '/resonance/types', { cache: 'no-store', headers: hdr(false) })
      .then(function (d) {
        if (d && d.ok && d.types && d.types.length) { _entitled = true; _types = d.types; buildPanel(_types); }
        else { _entitled = false; }
      })
      .catch(function (e) { if (e && e.status === 403) _entitled = false; else if (!(e && e.status === 401)) _typesTried = false; });
  }

  document.addEventListener('click', function (e) {
    try {
      var t = e.target; if (!t) return;
      var hit = (t.id === 'labyrinthResonanceUseBtn') || (t.closest && t.closest('#labyrinthResonanceUseBtn'));
      if (hit && _pending && (Date.now() - _pending.t < 120000)) {
        window._lmResPending = { x: _pending.x, y: _pending.y, t: Date.now() };
        _pending = null;
        setTimeout(function () { if (window._lmResPending && Date.now() - window._lmResPending.t > 15000) window._lmResPending = null; }, 15500);
      }
    } catch (err) {}
  }, true);

  setInterval(tick, 1500);
  setTimeout(tick, 2500);
})();