<?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="/user/plugins/sitemap/sitemap.xsl"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">
  <url>
    <loc>https://wiki.botmek.com/articles</loc>
    <xhtml:link rel="alternate" hreflang="ru" href="https://wiki.botmek.com/articles" />
    <lastmod>2019-03-11</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://wiki.botmek.com</loc>
    <xhtml:link rel="alternate" hreflang="ru" href="https://wiki.botmek.com/" />
    <lastmod>2020-08-06</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://wiki.botmek.com/document/all</loc>
    <xhtml:link rel="alternate" hreflang="ru" href="https://wiki.botmek.com/document/all" />
    <lastmod>2019-03-11</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://wiki.botmek.com/document/hotkey</loc>
    <xhtml:link rel="alternate" hreflang="ru" href="https://wiki.botmek.com/document/hotkey" />
    <lastmod>2019-03-22</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://wiki.botmek.com/document/hwnd</loc>
    <xhtml:link rel="alternate" hreflang="ru" href="https://wiki.botmek.com/document/hwnd" />
    <lastmod>2019-03-22</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://wiki.botmek.com/document/logs</loc>
    <xhtml:link rel="alternate" hreflang="ru" href="https://wiki.botmek.com/document/logs" />
    <lastmod>2019-03-25</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://wiki.botmek.com/document/macro</loc>
    <xhtml:link rel="alternate" hreflang="ru" href="https://wiki.botmek.com/document/macro" />
    <lastmod>2019-03-25</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://wiki.botmek.com/document/makroamc</loc>
    <xhtml:link rel="alternate" hreflang="ru" href="https://wiki.botmek.com/document/makroamc" />
    <lastmod>2019-03-25</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://wiki.botmek.com/document/profile</loc>
    <xhtml:link rel="alternate" hreflang="ru" href="https://wiki.botmek.com/document/profile" />
    <lastmod>2019-03-25</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://wiki.botmek.com/document/script</loc>
    <xhtml:link rel="alternate" hreflang="ru" href="https://wiki.botmek.com/document/script" />
    <lastmod>2019-03-25</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://wiki.botmek.com/document/settings</loc>
    <xhtml:link rel="alternate" hreflang="ru" href="https://wiki.botmek.com/document/settings" />
    <lastmod>2019-03-25</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://wiki.botmek.com/document/share</loc>
    <xhtml:link rel="alternate" hreflang="ru" href="https://wiki.botmek.com/document/share" />
    <lastmod>2019-03-25</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://wiki.botmek.com/faq</loc>
    <xhtml:link rel="alternate" hreflang="ru" href="https://wiki.botmek.com/faq" />
    <lastmod>2019-03-25</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://wiki.botmek.com/home</loc>
    <xhtml:link rel="alternate" hreflang="ru" href="https://wiki.botmek.com/home" />
    <lastmod>2020-08-06</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
  <url>
    <loc>https://wiki.botmek.com/videoobzory</loc>
    <xhtml:link rel="alternate" hreflang="ru" href="https://wiki.botmek.com/videoobzory" />
    <lastmod>2019-03-25</lastmod>
    <changefreq>daily</changefreq>
    <priority>1.0</priority>
  </url>
</urlset>


<script>

    //#region Переменные
    // Хендл наблюдателя за фиксированными элементами
    let _fixedObs = null;
    // id активного rAF (0 = не запущен)
    let _rafId = 0;
    // Теневой DOM и баннер
    let shadow = null;

    // Коллекция всех элементов с position: fixed на странице (актуально на момент обращения)
    const allFixedElements = new Set();
    // Коллекция уникальных элементов с position: fixed за которыми следим
    const watchElements = new Set();
    // Коллекция исключений
    const exceptionFilter = ['<html>', '<body>', '.tawk-mobile', '.mobile-menu-overlay', '.mobileWikiMenuOverlay', '.wLeftMenuBox', '.probootstrap-nav', '.navbar'];
    //#endregion

    //#region Функция: createBanner / Создать баннер
    function createBanner() {
        // Выйти если баннер уже создан
        if (shadow != null) return;
        // создаём контейнер и добавляем в body
        const container = document.createElement("div");
        container.id = "cookieBannerContainer";
        (document.body || document.documentElement).appendChild(container);

        // создаём теневой DOM и баннер
        shadow = container.attachShadow({ mode: "open" });
        shadow.innerHTML = `
            <style>
            .cookie-banner {
                display: none;
                position: fixed;
                bottom: 0px;
                left: 0px;
                background: #fff;
                color: rgba(0, 0, 0, 0.8);
                padding: 6px 36px 6px 18px;
                font-size: 13px;
                z-index: 999;
                border-radius: 6px 6px 0px 0px;
                box-shadow: 0 2px 6px rgba(0,0,0,0.4);
                width: 100%;
                box-sizing: border-box;
                line-height: 1.4;
            }
            .cookie-banner a { color: #4da6ff; text-decoration: none; }
            .cookie-banner button {
                position: absolute;
                top: 8px;
                right: 15px;
                background: transparent;
                border: none;
                cursor: pointer;
                width: 16px; height: 16px; padding: 0;
                display: flex; align-items: center; justify-content: center;
            }
            .cookie-banner button svg {
                width: 100%; height: 100%;
                stroke: rgba(0, 0, 0, 0.6); stroke-width: 2;
            }
            .cookie-banner button svg:hover {
                stroke: rgba(0, 0, 0, 1);
            }
            </style>
            <div class="cookie-banner" id="cookieBanner">
            <div style="display: none;">
                Продолжая пользоваться сайтом, вы соглашаетесь с использованием <a href="/license/privacy_policy.pdf" target="_blank">cookie</a>
            </div>
            <div >
                By continuing to use <strong>wiki.botmek.com</strong>,
                you agree to the use of <a href="/license/privacy_policy.pdf" target="_blank">cookie</a>.
            </div>
            <div style="display: none;">
                Продолжая использовать <strong>wiki.botmek.com</strong>,
                вы соглашаетесь на использование файлов cookie.
                Более подробную информацию можно найти в
                <a href="/license/privacy_policy.pdf" target="_blank">Политике cookie файлов</a>.
            </div>
            <button id="closeCookie" aria-label="Закрыть">
                <svg viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
                <line x1="2" y1="2" x2="14" y2="14"/>
                <line x1="14" y1="2" x2="2" y2="14"/>
                </svg>
            </button>
            </div>
            `;
        // Повесить событие закрытия баннера
        shadow.getElementById("closeCookie").onclick = function () { Stop(); };
        // Установить начальную высоту сдвига
        setShift();
    }
    //#endregion
    //#region Функция: removeBanner / Удалить баннер
    function removeBanner() {
        const container = document.getElementById("cookieBannerContainer");
        if (container) {
            container.remove(); // полностью убирает контейнер с шадоу
            shadow = null;      // сбрасываем ссылку на shadow, чтобы не висела
        }
    }
    //#endregion

    //#region Функция: StartWatchFixedElements / Следить за фиксированными элементами на странице
    /**
     * Обновляет Set allFixedElements по корню и его потомкам
     * @param {Element} [root=document.body||document.documentElement] - корень поддерева
     * @param {boolean} [remove=false] - true = удалить узлы, false = синхронизировать
     */
    const StartWatchFixedElements = function fn(root = (document.body || document.documentElement), remove = false) {
        // Если корня нет или это не Element — выходим
        if (!root || root.nodeType !== 1) return;

        // Обработчик одного элемента: либо чистим, либо добавляем/удаляем по computed style
        const handle = (el) => {
            // Для не-элементов ничего не делаем (TreeWalker и так даёт элементы, но проверка дёшевая)
            if (!el || el.nodeType !== 1) return;
            // В режиме remove = true — просто удаляем узел из реестра (и это достаточно быстро)
            if (remove) { allFixedElements.delete(el); return; }
            // Иначе — актуализируем запись по текущему значению position
            (getComputedStyle(el).position === 'fixed') ? allFixedElements.add(el) : allFixedElements.delete(el);
        };

        // Обрабатываем сам корень
        handle(root);

        // Создаём итератор по всем потомкам-элементам
        const tw = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
        // Обходим всех потомков и синхронизируем их в массив
        for (let n = tw.nextNode(); n; n = tw.nextNode()) handle(n);

        // Запустить наблюдатель за изменёнными узлами и поддеревьями
        if (_fixedObs == null) {
            _fixedObs = new MutationObserver(muts => {
                // Идём по всем пришедшим мутациям
                for (const m of muts) {
                    // Если поменялись class/style у элемента — пересканируем только его поддерево
                    if (m.type === 'attributes') { fn(m.target); }
                    // Если добавили/удалили узлы — работаем только с этими поддеревьями
                    else {
                        // Добавленные поддеревья — синхронизируем (добавим fixed-элементы, уберём не fixed)
                        m.addedNodes.forEach(n => fn(n));
                        // Удалённые поддеревья — быстро чистим их из реестра
                        m.removedNodes.forEach(n => fn(n, true));
                    }
                }
            });
            // Подписываемся на изменения по всему документу
            _fixedObs.observe(document.documentElement, {
                // Следим за всем поддеревом
                subtree: true,
                // Важно видеть добавления и удаления детей
                childList: true,
                // Важно видеть изменения, которые влияют на position через CSS
                attributes: true,
                // Как правило, достаточно class и style
                attributeFilter: ['class', 'style']
            });
        }
    }
    //#endregion
    //#region Функция: StopWatchFixedElements / Остановить слежение за фиксированными элементами на странице
    /**
     * Останавливает слежение за фиксированными элементами на странице и очищает массив
     */
    function StopWatchFixedElements() {
        if (_fixedObs) {
            _fixedObs.disconnect();
            _fixedObs = null;
        }
        allFixedElements.clear();
    }
    //#endregion

    //#region Функция: getBannerHeight / Получение текущей высоты баннера
    /**
     * Возвращает текущую высоту баннера cookie.
     * Если баннер скрыт — возвращает 0.
     *
     * @returns {number} Высота баннера в пикселях либо 0.
     */
    function getBannerHeight() {
        // Берём сам элемент баннера внутри теневого DOM
        const el = shadow.getElementById("cookieBanner");
        if (!el) return 0;

        // Получаем его вычисленные стили
        const style = getComputedStyle(el);
        // Если баннер скрыт любым способом, возвращаем 0
        if (style.display === "none" || style.visibility === "hidden" || style.opacity === "0") return 0;

        // Возвращаем фактическую высоту видимого баннера
        return (el.getBoundingClientRect().height || 0);
    }
    //#endregion

    //#region Функция: createStyle / Создать CSS-класс для сдвига
    /**
     * Создает на странице CSS-класс (.cookieshift) для сдвига через переменную --cookie-shift.
     */
    function createStyle() {
        // Если стиль уже добавлен — выходим, чтобы не дублировать
        if (document.getElementById("cookieShiftStyle")) return;
        // Текст CSS: объявляем переменную и определяем класс .cookieshift
        const css = `
        /* глобальная переменная сдвига от баннера */
        :root { --cookie-shift: 0px; }

        /* основной вариант: современный longhand translate */
        .cookieshift {
        will-change: transform, translate;
        translate: 0 var(--cookie-shift, 0px);
        }

        /* fallback для браузеров без longhand translate */
        @supports not (translate: 0 1px) {
        .cookieshift {
            /* применяем наш сдвиг + оставляем место для исходного transform */
            transform: translateY(var(--cookie-shift, 0px)) var(--cookie-base-transform, none);
            will-change: transform;
        }
        }
        `;
        // Создаём узел style и вставляем в head
        const style = document.createElement("style");  // создаём style
        style.id = "cookieShiftStyle";                  // id, чтобы не вставлять второй раз
        style.textContent = css;                        // кладём CSS-текст
        document.head.appendChild(style);               // добавляем в документ
    }
    //#endregion
    //#region Функция: removeStyle / Удалить CSS-класс для сдвига
    /**
     * Удаляет ранее созданный CSS-стиль и очищает все элементы от добавленных классов
     */
    function removeStyle() {
        // ищем по id
        const style = document.getElementById("cookieShiftStyle");
        // если есть — удаляем из DOM
        if (style) {
            // Удаляет класс .cookieshift у всех элементов на странице
            document.querySelectorAll(".cookieshift").forEach(el => removeClass(el));
            // Удаляем стиль
            style.remove();
        }
    }
    //#endregion

    //#region Функции: addClass / Добавить элементу класс сдвига
    /**
     * Устанавливает на элемент класс сдвига
     *
     * @param {Element} el - целевой элемент
     */
    function addClass(el) {
        if (!el.classList.contains("cookieshift")) {
            // сохраним исходный transform, чтобы потом вернуть (нужно в fallback)
            if (el.dataset.cookieBaseTransform == null) {
                el.dataset.cookieBaseTransform = el.style.transform || "";
                el.style.setProperty("--cookie-base-transform", el.dataset.cookieBaseTransform);
            }
            el.classList.add("cookieshift");
        }
    }
    //#endregion
    //#region Функции: removeClass / Удалить у элемента класс сдвига
    /**
     * Удаляет у элемента класс сдвига
     *
     * @param {Element} el - целевой элемент
     */
    function removeClass(el) {
        if (el.classList.contains("cookieshift")) {
            el.classList.remove("cookieshift");
            // вернём исходный transform
            if (el.dataset.cookieBaseTransform != null) {
                el.style.transform = el.dataset.cookieBaseTransform;
                el.style.removeProperty("--cookie-base-transform");
                delete el.dataset.cookieBaseTransform;
            }
        }
    }
    //#endregion

    //#region Функция: setShift / Установка глобальной величины сдвига
    /**
     * Устанавливает сдвиг в пикселях для всех элементов с классом .cookieshift.
     */
    function setShift() {
        // Высота сдвига в пикселях
        const height = getBannerHeight();
        // Быстро отбрасываем дробную часть и ставим минус для движения вверх
        document.documentElement.style.setProperty("--cookie-shift", `${-height | 0}px`);
    }
    //#endregion

    //#region Функция: getOverlaps / Получить все пересекающиеся с target элементы
    /**
     * Возвращает массив всех fixed-элементов, которые геометрически пересекаются с target.
     * Пересечение считается по getBoundingClientRect (z-index не учитывается).
     *
     * @param {Element} target                 - элемент, с которым сверяем пересечение
     * @param {string[]} [exclude=[]]          - список исключений: классы или атрибуты.
     *        Примеры значений: "my-class", ".my-class", "data-cookie-ignore", "[aria-hidden]"
     */
    function getOverlaps(target, exclude = []) {
        // Если target не валиден — возвращаем пусто
        if (!target || !(target instanceof Element)) return [];

        // Проверка условий
        const isExcluded = (el) => {
            for (let key of exclude) {
                if (!key) continue;
                // .class -> class
                if (key[0] === ".") {
                    const cls = key.slice(1);
                    if (cls && el.classList && el.classList.contains(cls)) return true;
                    continue;
                }
                // id: "#header"
                if (key.startsWith("#")) {
                    const id = key.slice(1);
                    if (id && el.id === id) return true;
                    continue;
                }
                // явный тег: "<body>"
                if (key.startsWith("<") && key.endsWith(">")) {
                    const tag = key.slice(1, -1).toLowerCase();
                    if (el.tagName?.toLowerCase() === tag) return true;
                    continue;
                }
                // [attr] -> attr
                if (key[0] === "[" && key[key.length - 1] === "]") {
                    key = key.slice(1, -1);
                }
                // class ИЛИ attr
                if ((el.classList && el.classList.contains(key)) || el.hasAttribute?.(key)) {
                    return true;
                }

            }
            return false;
        };

        // Быстрое пересечение двух прямоугольников
        const overlap = (a, b) => !(
            b.left >= a.right ||
            b.right <= a.left ||
            b.top >= a.bottom ||
            b.bottom <= a.top
        );

        // Прямоугольник целевого элемента
        const tr = target.getBoundingClientRect();
        if (tr.width === 0 || tr.height === 0) return [];

        const result = [];
        // Перебираем все элементы документа (только подключённые к DOM)
        for (const el of allFixedElements) {
            if (el === target) continue;                            // не сравниваем с самим собой
            if (isExcluded(el)) continue;                           // исключения по классам/атрибутам

            const r = el.getBoundingClientRect();
            if (r.width === 0 || r.height === 0) continue;          // пустые/невидимые боксы пропускаем

            if (overlap(tr, r)) result.push(el);                    // есть пересечение — добавляем
        }
        return result;
    }
    //#endregion

    //#region Функция: StartAdjustFixedElements / Слежение за изменением элементов
    const StartAdjustFixedElements = function fn() {
        if (!_rafId) {
            // Добавить начальный элемент слежения
            watchElements.add(shadow.getElementById("cookieBanner"));
            // Запустить цикл
            _rafId = requestAnimationFrame(fn);
            return;
        }

        // если вкладка скрыта — не жжём CPU
        if (document.visibilityState === 'hidden') {
            // дождёмся возврата во вкладку и продолжим
            const onVisible = () => {
                document.removeEventListener('visibilitychange', onVisible);
                _rafId = requestAnimationFrame(fn);
            };
            document.addEventListener('visibilitychange', onVisible);
            return;
        }

        const viewportH = window.innerHeight;
        const banner = shadow.getElementById("cookieBanner");

        // Перебрать все элементы за которыми следим
        watchElements.forEach(el => {
            // Если элемент удален из DOM - удалить из наблюдателя
            if (!el.isConnected) { watchElements.delete(el); return; }
            // Получить массив элементов с которыми пересекается текущий элемент
            const elements = getOverlaps(el, exceptionFilter);

            // Перебрать найденные элементы
            for (const el1 of elements) {
                // Добавить новые элементы для слежения (дубликаты игнорируются автоматически)
                watchElements.add(el1);

                // Если элемент занимает всю высоту экрана (с погрешностью 20px) - удалить класс
                const r1 = el1.getBoundingClientRect();
                if (r1.height >= (viewportH - 20)) removeClass(el1);
                // Добавить класс
                else if (el1 !== banner) addClass(el1);
            }

            // Тот же чек для базового элемента
            const r = el.getBoundingClientRect();
            if (r.height >= (viewportH - 20)) removeClass(el);
            else if (el !== banner) addClass(el);
        });

        _rafId = requestAnimationFrame(fn);
    };
    //#endregion
    //#region Функция: StopAdjustFixedElements / Остановка слежения за изменением элементов
    function StopAdjustFixedElements() {
        if (_rafId) {
            cancelAnimationFrame(_rafId);
            _rafId = 0;
        }
        // Очистить список
        watchElements.clear();
    }
    //#endregion


    //#region Событие: StartResizeWatch / Обновить размер сдвига при изменении размера окна
    function StartResizeWatch() {
        window.addEventListener("resize", setShift, { passive: true });
        if (window.visualViewport) {
            visualViewport.addEventListener("resize", setShift, { passive: true });
        }
    }
    //#endregion
    //#region Событие: StopResizeWatch / Остановить обновление размера сдвига при изменении размера окна
    function StopResizeWatch() {
        window.removeEventListener("resize", setShift);
        if (window.visualViewport) {
            visualViewport.removeEventListener("resize", setShift);
        }
    }
    //#endregion



    //#region Функция: Start / Запустить скрипт
    function Start() {
        // Если баннер был закрыт — ничего не делаем
        if (document.cookie.split('; ').some(row => row.startsWith('cookieBannerClosed='))) return;
        // Создать баннер
        createBanner();
        // Отобразить баннер
        shadow.getElementById("cookieBanner").style.display = "block";
        // Установить начальную высоту сдвига
        setShift();
        // Следить за фиксированными элементами на странице
        StartWatchFixedElements();
        // Создать стиль
        createStyle();
        // Следить за изменением размера окна
        StartResizeWatch();
        // Запустить цикл
        StartAdjustFixedElements();
    }
    //#endregion
    //#region Функция: Stop / Остановить скрипт
    function Stop() {
        // Скрываем баннер
        shadow.getElementById("cookieBanner").style.display = "none";
        // Фиксируем выбор в куке
        document.cookie = "cookieBannerClosed=1; path=/; max-age=" + (60 * 60 * 24 * 365);
        // Остановить цикл
        StopAdjustFixedElements();
        // Остановить слежение за фиксированными элементами на странице
        StopWatchFixedElements();
        // Удалить стиль
        removeStyle();
        // Остановить слежение за изменением размера окна
        StopResizeWatch();
        // Удалить баннер из DOM
        removeBanner();
    }
    //#endregion

    Start();
</script>