Ultimate Steam Enhancer

Добавляет множество функций для улучшения взаимодействия с магазином и сообществом (Полный список на странице скрипта)

目前為 2025-02-07 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         Ultimate Steam Enhancer
// @namespace    https://store.steampowered.com/
// @version      1.5
// @description  Добавляет множество функций для улучшения взаимодействия с магазином и сообществом (Полный список на странице скрипта)
// @author       0wn3df1x
// @license      MIT
// @require      https://code.jquery.com/jquery-3.6.0.min.js
// @match        https://store.steampowered.com/*
// @match        *://*steamcommunity.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_getResourceText
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_addStyle
// @connect      zoneofgames.ru
// @connect      raw.githubusercontent.com
// @connect      gist.githubusercontent.com
// @connect      store.steampowered.com
// @connect      api.steampowered.com
// @connect      steamcommunity.com
// @connect      shared.cloudflare.steamstatic.com
// @connect      umadb.ro
// @connect      api.github.com
// @connect      howlongtobeat.com
// ==/UserScript==

(function() {
    'use strict';

    const scriptsConfig = {
        // Основные скрипты
        gamePage: true, // Скрипт для страницы игры (индикаторы о наличии русского перевода; получение дополнительных обзоров) | https://store.steampowered.com/app/*
        hltbData: true, // Скрипт для страницы игры (HLTB; получение сведений о времени прохождения) | https://store.steampowered.com/app/*
        zogInfo: true, // Скрипт для страницы игры (ZOG; получение сведение о наличии русификаторов) | https://store.steampowered.com/app/*
        catalogInfo: true, // Скрипт для получения дополнительной информации об игре при наведении на неё на странице поиска по каталогу | https://store.steampowered.com/search/
        catalogHider: false, // Скрипт скрытия игр на странице поиска по каталогу | https://store.steampowered.com/search/
        newsFilter: true, // Скрипт для скрытия новостей в новостном центре: | https://store.steampowered.com/news/
        Kaznachei: true, // Скрипт для показа годовых и исторических продаж предмета на торговой площадке Steam | https://steamcommunity.com/market/listings/*
        homeInfo: false, // Скрипт для получения дополнительной информации об игре при наведении на неё на странице вашей активности Steam | https://steamcommunity.com/my/
        wishlistTracker: true, // Скрипт для получения уведомлений об изменении дат выхода игр из вашего списка желаемого Steam | https://steamcommunity.com/my/wishlist/

        // Дополнительные настройки
        autoExpandHltb: false, // Автоматически раскрывать спойлер HLTB
        autoLoadReviews: false, // Автоматически загружать дополнительные обзоры
        toggleEnglishLangInfo: false // Отображает данные об английском языке в дополнительной информации при поиске по каталогу и в активности (функция для переводчиков)
    };


    // Скрипт для страницы игры (индикаторы о наличии русского перевода; получение дополнительных обзоров) | https://store.steampowered.com/app/*
    if (scriptsConfig.gamePage && window.location.pathname.includes('/app/')) {
        (function() {
            'use strict';

            function createFruitIndicator(apple, hasSupport, orange) {
                const banana = document.createElement('div');
                banana.style.position = 'relative';
                banana.style.cursor = 'pointer';

                const grape = document.createElement('div');
                grape.style.width = '60px';
                grape.style.height = '60px';
                grape.style.borderRadius = '4px';
                grape.style.display = 'flex';
                grape.style.alignItems = 'center';
                grape.style.justifyContent = 'center';
                grape.style.background = hasSupport ? 'rgba(66, 135, 245, 0.2)' : 'rgba(0, 0, 0, 0.1)';
                grape.style.border = `1px solid ${hasSupport ? '#2A5891' : '#3c3c3c'}`;
                grape.style.opacity = '0.95';
                grape.style.transition = 'transform 0.3s ease, box-shadow 0.3s ease';
                grape.style.overflow = 'hidden';
                grape.style.position = 'relative';
                grape.style.transform = 'translateZ(0)';

                const kiwi = document.createElement('div');
                kiwi.innerHTML = apple;
                kiwi.style.width = '30px';
                kiwi.style.height = '30px';
                kiwi.style.display = 'block';
                kiwi.style.margin = '0 auto';
                kiwi.style.transition = 'fill 0.3s ease';

                grape.appendChild(kiwi);

                const svgElement = kiwi.querySelector('svg');

                function setColor(hasSupport) {
                    const borderColor = hasSupport ? '#2A5891' : '#3c3c3c';
                    const svgFill = hasSupport ? '#FFFFFF' : '#0E1C25';

                    grape.style.border = `1px solid ${borderColor}`;
                    svgElement.style.fill = svgFill;
                }

                setColor(hasSupport);

                const pineapple = document.createElement('div');
                const hasLabel = hasSupport ? orange : getGenitiveCase(orange);
                pineapple.textContent = hasSupport ? `Есть ${orange}` : `Нет ${hasLabel}`;
                pineapple.style.position = 'absolute';
                pineapple.style.top = '50%';
                pineapple.style.left = '100%';
                pineapple.style.transform = 'translateY(-50%) translateX(10px)';
                pineapple.style.background = 'rgba(0, 0, 0, 0.8)';
                pineapple.style.color = '#fff';
                pineapple.style.padding = '8px 12px';
                pineapple.style.borderRadius = '8px';
                pineapple.style.fontSize = '14px';
                pineapple.style.whiteSpace = 'nowrap';
                pineapple.style.opacity = '0';
                pineapple.style.transition = 'opacity 0.3s ease';
                pineapple.style.zIndex = '10000';
                pineapple.style.pointerEvents = 'none';
                banana.appendChild(pineapple);

                banana.addEventListener('mouseenter', () => {
                    grape.style.transform = 'scale(1.1) translateZ(0)';
                    pineapple.style.opacity = '1';
                });

                banana.addEventListener('mouseleave', () => {
                    grape.style.transform = 'scale(1) translateZ(0)';
                    pineapple.style.opacity = '0';
                });

                banana.appendChild(grape);
                return banana;
            }

            function getGenitiveCase(orange) {
                switch (orange) {
                    case 'интерфейс':
                        return 'интерфейса';
                    case 'озвучка':
                        return 'озвучки';
                    case 'субтитры':
                        return 'субтитров';
                    default:
                        return orange;
                }
            }

            function checkRussianSupport() {
                const mango = document.querySelector('#languageTable table.game_language_options');
                if (!mango) return {
                    interface: false,
                    voice: false,
                    subtitles: false
                };

                const strawberry = mango.querySelectorAll('tr');
                for (let blueberry of strawberry) {
                    const watermelon = blueberry.querySelector('td.ellipsis');
                    if (watermelon && /русский|Russian/i.test(watermelon.textContent.trim())) {
                        const cherry = blueberry.querySelector('td.checkcol:nth-child(2) span');
                        const raspberry = blueberry.querySelector('td.checkcol:nth-child(3) span');
                        const blackberry = blueberry.querySelector('td.checkcol:nth-child(4) span');

                        return {
                            interface: cherry !== null,
                            voice: raspberry !== null,
                            subtitles: blackberry !== null
                        };
                    }
                }
                return {
                    interface: false,
                    voice: false,
                    subtitles: false
                };
            }

            function addRussianIndicators() {
                const russianSupport = checkRussianSupport();
                if (!russianSupport) return;

                let lemon = document.querySelector('#gameHeaderImageCtn');
                if (!lemon) return;

                const lime = document.createElement('div');
                lime.style.position = 'absolute';
                lime.style.top = '-10px';
                lime.style.left = 'calc(100% + 10px)';
                lime.style.display = 'flex';
                lime.style.flexDirection = 'column';
                lime.style.gap = '15px';
                lime.style.alignItems = 'flex-start';
                lime.style.zIndex = '2';
                lime.style.marginTop = '10px';

                const peach = createFruitIndicator(`<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12,0C5.38,0,0,5.38,0,12s5.38,12,12,12s12-5.38,12-12S18.62,0,12,0z M12,22C6.49,22,2,17.51,2,12S6.49,2,12,2	s10,4.49,10,10S17.51,22,12,22z M10.5,10h3v8h-3V10z M10.5,5h3v3h-3V5z" /></svg>`, russianSupport.interface, 'интерфейс');
                const plum = createFruitIndicator(`<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M15,21v-2c3.86,0,7-3.14,7-7s-3.14-7-7-7V3c4.96,0,9,4.04,9,9S19.96,21,15,21z M15,17v-2c1.65,0,3-1.35,3-3s-1.35-3-3-3V7 c2.76,0,5,2.24,5,5S17.76,17,15,17z M1,12v4h5l6,5V3L6,8H1V12" /></svg>`, russianSupport.voice, 'озвучка');
                const apricot = createFruitIndicator(`<svg viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><g><path d="M11,24l-4.4-5H0V0h23v19h-7.6L11,24z M2,17h5.4l3.6,4l3.6-4H21V2H2V17z" /></g><g><rect x="5" y="8" width="3" height="3" /></g><g><rect x="10" y="8" width="3" height="3" /></g><g><rect x="15" y="8" width="3" height="3" /></g></svg>`, russianSupport.subtitles, 'субтитры');

                lime.appendChild(peach);
                lime.appendChild(plum);
                lime.appendChild(apricot);

                lemon.style.position = 'relative';
                lemon.appendChild(lime);

                const appName = document.querySelector('#appHubAppName.apphub_AppName');
                if (appName) {
                    appName.style.maxWidth = '530px';
                    appName.style.overflow = 'hidden';
                    appName.style.textOverflow = 'ellipsis';
                    appName.style.whiteSpace = 'nowrap';
                    appName.title = appName.textContent;
                }
            }

            const settings = {
                showTotalReviews: true,
                showNonChineseReviews: true,
                showRussianReviews: true
            };

            function fetchReviews(appid, language, callback) {
                let url = `https://store.steampowered.com/appreviews/${appid}?json=1&language=${language}&purchase_type=all`;
                GM_xmlhttpRequest({
                    method: "GET",
                    url: url,
                    onload: function(response) {
                        let data = JSON.parse(response.responseText);
                        callback(data);
                    }
                });
            }

            function fetchRussianReviewsHTML(appid, filter, callback) {
                let url = `https://store.steampowered.com/appreviews/${appid}?language=russian&purchase_type=all&filter=${filter}&day_range=365`;
                GM_xmlhttpRequest({
                    method: "GET",
                    url: url,
                    onload: function(response) {
                        let data = JSON.parse(response.responseText);
                        callback(data.html);
                    }
                });
            }

            function addStyles() {
                GM_addStyle(`
        .additional-reviews {
            margin-top: 10px;
        }
        .additional-reviews .user_reviews_summary_row {
            display: flex;
            line-height: 16px;
            cursor: pointer;
            margin-bottom: 5px;
        }
        .additional-reviews .subtitle {
            flex: 1;
            color: #556772;
            font-size: 12px;
        }
        .additional-reviews .summary {
            flex: 3;
            color: #c6d4df;
            font-size: 12px;
            overflow: hidden;
            white-space: nowrap;
            text-overflow: ellipsis;
        }
        .additional-reviews .game_review_summary {
            font-weight: normal;
        }
        .additional-reviews .positive {
            color: #66c0f4;
        }
        .additional-reviews .mixed {
            color: #B9A074;
        }
        .additional-reviews .negative {
            color: #a34c25;
        }
        .additional-reviews .no_reviews {
            color: #929396;
        }
        .additional-reviews .responsive_hidden {
            color: #556772;
            margin-left: 5px;
        }
        .modal {
            display: none;
            position: fixed;
            z-index: 1000;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            overflow: auto;
            background-color: rgba(0,0,0,0.8);
        }
        .modal-content {
            background-color: #1b2838;
            margin: 10% auto;
            padding: 20px;
            border: 1px solid #888;
            width: 80%;
            max-width: 800px;
            color: #c6d4df;
            position: relative;
            max-height: 80vh;
            overflow-y: auto;
        }
        .close {
            color: #aaa;
            position: sticky;
            top: 0;
            float: right;
            font-size: 28px;
            font-weight: bold;
            cursor: pointer;
            background: rgba(0,0,0,0.8);
            padding: 5px 10px;
            border-radius: 5px;
            transition: color 0.2s ease, background 0.2s ease, transform 0.2s ease;
            box-shadow: 0 2px 4px rgba(0,0,0,0.2);
        }
        .close:hover {
            color: #fff;
            background: #e64a4a;
            transform: scale(1.1);
        }
        .close:active {
            background: #c43c3c;
            transform: scale(0.95);
        }
        .refresh-button {
            position: left;
            top: 10px;
            right: 50px;
            background: #66c0f4;
            color: #1b2838;
            padding: 10px 20px;
            border: none;
            cursor: pointer;
            z-index: 1001;
            border-radius: 2px;
            transition: background 0.2s ease, color 0.2s ease;
        }
        .refresh-button:hover {
            background: #45b0e6;
            color: #fff;
        }
        .refresh-button:active {
            background: #329cd4;
            transform: translateY(1px);
        }
    `);
            }

            function formatNumber(number) {
                return number.toLocaleString('en-US');
            }

            function getReviewClass(percent, totalReviews) {
                if (totalReviews === 0) return 'no_reviews';
                if (percent >= 70) return 'positive';
                if (percent >= 40) return 'mixed';
                if (percent >= 1) return 'negative';
                return 'negative';
            }

            function addLoadButton() {
                let reviewsContainer = document.querySelector('.user_reviews');
                if (reviewsContainer) {
                    let additionalReviews = document.createElement('div');
                    additionalReviews.className = 'additional-reviews';

                    additionalReviews.innerHTML = `
            <div class="user_reviews_summary_row" id="load-reviews-button">
                <div class="subtitle column all">Доп. обзоры:</div>
                <div class="summary column">
                    <span class="game_review_summary no_reviews">Загрузить</span>
                </div>
            </div>
        `;

                    reviewsContainer.appendChild(additionalReviews);

                    document.getElementById('load-reviews-button').addEventListener('click', function() {
                        loadAdditionalReviews();
                    });

                    if (scriptsConfig.autoLoadReviews) {
                        loadAdditionalReviews();
                    }
                }
            }

            function loadAdditionalReviews() {
                let appid = window.location.pathname.match(/\/app\/(\d+)/)[1];
                let languages = [];
                let data = {};

                let loadButton = document.getElementById('load-reviews-button');
                if (loadButton) {
                    loadButton.querySelector('.game_review_summary').textContent = 'Загрузка...';
                }

                if (settings.showTotalReviews || settings.showNonChineseReviews) {
                    languages.push('all');
                }
                if (settings.showNonChineseReviews) {
                    languages.push('schinese');
                }
                if (settings.showRussianReviews) {
                    languages.push('russian');
                }

                languages.forEach(language => {
                    fetchReviews(appid, language, (response) => {
                        data[language] = response;
                        if (Object.keys(data).length === languages.length) {
                            displayAdditionalReviews(data['all'], data['schinese'], data['russian']);

                            if (loadButton) {
                                loadButton.querySelector('.game_review_summary').textContent = 'Загрузить';
                            }
                        }
                    });
                });
            }

            function displayAdditionalReviews(allData, schineseData, russianData) {
                let allReviews = allData ? allData.query_summary : null;
                let schineseReviews = schineseData ? schineseData.query_summary : null;
                let russianReviews = russianData ? russianData.query_summary : null;

                let additionalReviews = document.querySelector('.additional-reviews');
                if (additionalReviews) {
                    additionalReviews.innerHTML = '';

                    if (settings.showTotalReviews && allReviews) {
                        let allPercent = allReviews.total_reviews > 0 ? Math.round((allReviews.total_positive / allReviews.total_reviews) * 100) : 0;
                        let allClass = getReviewClass(allPercent, allReviews.total_reviews);
                        additionalReviews.innerHTML += `
                <div class="user_reviews_summary_row">
                    <div class="subtitle column all">Тотальные:</div>
                    <div class="summary column">
                        <span class="game_review_summary ${allClass}">${allPercent}% из ${formatNumber(allReviews.total_reviews)} положительные</span>
                    </div>
                </div>
            `;
                    }

                    if (settings.showNonChineseReviews && allReviews && schineseReviews) {
                        let schintotalrev = allReviews.total_reviews - schineseReviews.total_reviews;
                        let schintotapos = allReviews.total_positive - schineseReviews.total_positive;
                        let schinpercent = schintotalrev > 0 ? Math.round((schintotapos / schintotalrev) * 100) : 0;
                        let schinClass = getReviewClass(schinpercent, schintotalrev);
                        additionalReviews.innerHTML += `
                <div class="user_reviews_summary_row">
                    <div class="subtitle column all">Безкитайские:</div>
                    <div class="summary column">
                        <span class="game_review_summary ${schinClass}">${schinpercent}% из ${formatNumber(schintotalrev)} положительные</span>
                    </div>
                </div>
            `;
                    }

                    if (settings.showRussianReviews && russianReviews) {
                        let rustotalrev = russianReviews.total_reviews;
                        let ruspositive = russianReviews.total_positive;
                        let ruspercent = rustotalrev > 0 ? Math.round((ruspositive / rustotalrev) * 100) : 0;
                        let rusClass = getReviewClass(ruspercent, rustotalrev);
                        additionalReviews.innerHTML += `
                <div class="user_reviews_summary_row" id="russian-reviews-row">
                    <div class="subtitle column all">Русские:</div>
                    <div class="summary column">
                        <span class="game_review_summary ${rusClass}">${ruspercent}% из ${formatNumber(rustotalrev)} положительные</span>
                    </div>
                </div>
            `;

                        document.getElementById('russian-reviews-row').addEventListener('click', function() {
                            openModal();
                        });
                    }
                }
            }

            function openModal() {
                let modal = document.createElement('div');
                modal.className = 'modal';
                modal.innerHTML = `
        <div class="modal-content">
            <span class="close">×</span>
            <button class="refresh-button" id="refresh-reviews">Загрузить актуальные</button>
            <div id="reviews-container"></div>
        </div>
    `;
                document.body.appendChild(modal);

                modal.querySelector('.close').addEventListener('click', function() {
                    modal.style.display = 'none';
                });

                modal.querySelector('#refresh-reviews').addEventListener('click', function() {
                    refreshReviews(modal);
                });

                modal.style.display = 'block';

                loadReviews(modal, 'all');
            }

            function refreshReviews(modal) {
                modal.querySelector('#reviews-container').innerHTML = '';
                loadReviews(modal, 'recent');
            }

            function loadReviews(modal, filter) {
                fetchRussianReviewsHTML(window.location.pathname.match(/\/app\/(\d+)/)[1], filter, function(html) {
                    modal.querySelector('#reviews-container').innerHTML = html;
                    modal.querySelector('#LoadMoreReviewsall')?.remove();
                    modal.querySelector('#LoadMoreReviewsrecent')?.remove();
                });
            }

            function main() {
                addStyles();
                addRussianIndicators();
                addLoadButton();
            }

            main();
        })();
    }


    // Скрипт для страницы игры (HLTB; получение сведений о времени прохождения) | https://store.steampowered.com/app/*
    if (window.location.pathname.includes('/app/') && scriptsConfig.hltbData) {
        (async function() {
            let hltbBlock = document.createElement('div');
            hltbBlock.style.position = 'absolute';
            hltbBlock.style.top = '232px';
            hltbBlock.style.left = '334px';
            hltbBlock.style.width = '30px';
            hltbBlock.style.height = '30px';
            hltbBlock.style.background = 'rgba(27, 40, 56, 0.95)';
            hltbBlock.style.padding = '15px';
            hltbBlock.style.borderRadius = '4px';
            hltbBlock.style.border = '1px solid #3c3c3c';
            hltbBlock.style.boxShadow = '0 0 10px rgba(0, 0, 0, 0.5)';
            hltbBlock.style.zIndex = '2';
            hltbBlock.style.fontFamily = 'Arial, sans-serif';
            hltbBlock.style.overflow = 'hidden';
            hltbBlock.style.transition = 'all 0.3s ease';

            let triangle = document.createElement('div');
            triangle.className = 'triangle-down';
            triangle.style.position = 'absolute';
            triangle.style.bottom = '5px';
            triangle.style.left = '50%';
            triangle.style.transform = 'translateX(-50%)';
            triangle.style.width = '0';
            triangle.style.height = '0';
            triangle.style.borderLeft = '5px solid transparent';
            triangle.style.borderRight = '5px solid transparent';
            triangle.style.borderTop = '5px solid #67c1f5';
            triangle.style.cursor = 'pointer';
            hltbBlock.appendChild(triangle);

            let title = document.createElement('div');
            title.style.fontSize = '12px';
            title.style.fontWeight = 'bold';
            title.style.color = '#67c1f5';
            title.style.marginBottom = '10px';
            title.textContent = 'HLTB';
            title.style.cursor = 'pointer';
            hltbBlock.appendChild(title);

            let content = document.createElement('div');
            content.style.fontSize = '14px';
            content.style.color = '#c6d4df';
            content.style.display = 'none';
            content.style.whiteSpace = 'auto';
            content.style.padding = '0 0';
            hltbBlock.appendChild(content);

            document.querySelector('#gameHeaderImageCtn').appendChild(hltbBlock);

            const handleClick = async function() {
                if (content.style.display === 'none') {
                    hltbBlock.style.transition = 'width 0.3s ease, height 0.3s ease';
                    hltbBlock.style.width = '200px';
                    hltbBlock.style.height = '40px';
                    await new Promise(resolve => setTimeout(resolve, 300));

                    content.textContent = 'Ищем в базе...';
                    content.style.display = 'block';

                    triangle.classList.remove('triangle-down');
                    triangle.classList.add('triangle-up');
                    triangle.style.borderTop = 'none';
                    triangle.style.borderBottom = '5px solid #67c1f5';

                    let gameName = getGameName();
                    let gameNameNormalized = normalizeGameName(gameName);

                    let orangutanFetchUrl = 'https://umadb.ro/hltb/fetch.php';
                    let orangutanHltbUrl = "https://howlongtobeat.com";

                    try {
                        const response = await new Promise((resolve, reject) => {
                            GM_xmlhttpRequest({
                                method: "GET",
                                url: orangutanFetchUrl,
                                onload: resolve,
                                onerror: reject
                            });
                        });

                        if (response.status === 200) {
                            const key = response.responseText.trim();
                            orangutanHltbUrl = "https://howlongtobeat.com" + key;
                        } else {
                            throw new Error('Failed to fetch key. Status: ' + response.status);
                        }
                    } catch (error) {
                        content.textContent = 'Ошибка при получении ключа.';
                        return;
                    }

                    let chimpQuery = '{"searchType":"games","searchTerms":[' + gameNameNormalized + '],"searchPage":1,"size":20,"searchOptions":{"games":{"userId":0,"platform":"","sortCategory":"popular","rangeCategory":"main","rangeTime":{"min":null,"max":null},"gameplay":{"perspective":"","flow":"","genre":"","difficulty":""},"rangeYear":{"min":"","max":""},"modifier":""},"users":{"sortCategory":"postcount"},"lists":{"sortCategory":"follows"},"filter":"","sort":0,"randomizer":0},"useCache":true}';

                    GM_xmlhttpRequest({
                        method: "POST",
                        url: orangutanHltbUrl,
                        data: chimpQuery,
                        headers: {
                            "Content-Type": "application/json",
                            "origin": "https://howlongtobeat.com",
                            "referer": "https://howlongtobeat.com/"
                        },
                        onload: async function(response) {
                            let baboonData = {
                                count: 0,
                                data: []
                            };
                            if (!response.responseText.includes("<title>HowLongToBeat - 404</title>")) {
                                try {
                                    baboonData = JSON.parse(response.responseText);
                                } catch (e) {
                                    content.textContent = 'Ошибка при обработке данных.';
                                    return;
                                }
                            }

                            if (baboonData.count === 0 && /[а-яё]/i.test(gameName)) {
                                const appId = window.location.pathname.split('/')[2];
                                const steamApiUrl = `https://api.steampowered.com/IStoreBrowseService/GetItems/v1?input_json={"ids": [{"appid": ${appId}}], "context": {"language": "english", "country_code": "US", "steam_realm": 1}, "data_request": {"include_assets": true}}`;

                                try {
                                    const steamResponse = await new Promise((resolve, reject) => {
                                        GM_xmlhttpRequest({
                                            method: "GET",
                                            url: steamApiUrl,
                                            onload: resolve,
                                            onerror: reject
                                        });
                                    });

                                    if (steamResponse.status === 200) {
                                        const steamData = JSON.parse(steamResponse.responseText);
                                        const englishName = steamData.response.store_items[0].name;

                                        if (englishName) {
                                            gameName = englishName;
                                            gameNameNormalized = normalizeGameName(gameName);
                                            chimpQuery = '{"searchType":"games","searchTerms":[' + gameNameNormalized + '],"searchPage":1,"size":20,"searchOptions":{"games":{"userId":0,"platform":"","sortCategory":"popular","rangeCategory":"main","rangeTime":{"min":null,"max":null},"gameplay":{"perspective":"","flow":"","genre":"","difficulty":""},"rangeYear":{"min":"","max":""},"modifier":""},"users":{"sortCategory":"postcount"},"lists":{"sortCategory":"follows"},"filter":"","sort":0,"randomizer":0},"useCache":true}';

                                            const secondResponse = await new Promise((resolve, reject) => {
                                                GM_xmlhttpRequest({
                                                    method: "POST",
                                                    url: orangutanHltbUrl,
                                                    data: chimpQuery,
                                                    headers: {
                                                        "Content-Type": "application/json",
                                                        "origin": "https://howlongtobeat.com",
                                                        "referer": "https://howlongtobeat.com/"
                                                    },
                                                    onload: resolve,
                                                    onerror: reject
                                                });
                                            });

                                            if (secondResponse.status === 200) {
                                                baboonData = JSON.parse(secondResponse.responseText);
                                            }
                                        }
                                    }
                                } catch (error) {
                                    console.error('Ошибка при запросе к Steam API:', error);
                                }
                            }

                            if (baboonData.count > 0) {
                                const matches = findPossibleMatches(gameName, baboonData.data);
                                if (matches.length > 0) {
                                    renderPossibleMatches(matches);
                                    hltbBlock.style.height = `${content.scrollHeight + 30}px`;
                                    return;
                                }
                            }

                            renderContent(baboonData.data[0]);
                            hltbBlock.style.height = `${content.scrollHeight + 30}px`;
                        },
                        onerror: function(error) {
                            content.textContent = 'Ошибка при запросе к HLTB.';
                        },
                        ontimeout: function() {
                            content.textContent = 'Тайм-аут при запросе к HLTB.';
                        },
                        timeout: 10000
                    });
                } else {
                    content.style.display = 'none';
                    hltbBlock.style.height = '30px';
                    hltbBlock.style.width = '30px';

                    triangle.classList.remove('triangle-up');
                    triangle.classList.add('triangle-down');
                    triangle.style.borderBottom = 'none';
                    triangle.style.borderTop = '5px solid #67c1f5';
                }
            };

            title.onclick = handleClick;
            triangle.onclick = handleClick;

            function normalizeGameName(name) {
                return name
                    .normalize("NFD")
                    .replace(/[\u0300-\u036f]/g, "")
                    .replace(/[^a-zа-яё0-9 _'\-!]/gi, '')
                    .toLowerCase()
                    .split(/\s+/)
                    .map(word => `"${word}"`)
                    .join(",");
            }

            function findPossibleMatches(gameName, data) {
                const cleanGameName = gameName
                    .normalize("NFD")
                    .replace(/[\u0300-\u036f]/g, "")
                    .replace(/[^a-zа-яё0-9 _'\-!]/gi, '')
                    .toLowerCase();

                return data
                    .map(item => {
                        const cleanItemName = item.game_name
                            .normalize("NFD")
                            .replace(/[\u0300-\u036f]/g, "")
                            .replace(/[^a-zа-яё0-9 _'\-!]/gi, '')
                            .toLowerCase();

                        const similarity = calculateSimilarity(cleanGameName, cleanItemName);
                        const startsWith = cleanItemName.startsWith(cleanGameName);

                        return {
                            ...item,
                            percentage: similarity,
                            startsWith: startsWith
                        };
                    })
                    .filter(item => item.percentage > 50 || item.startsWith)
                    .sort((a, b) => {
                        if (a.startsWith && !b.startsWith) return -1;
                        if (!a.startsWith && b.startsWith) return 1;
                        return b.percentage - a.percentage;
                    })
                    .slice(0, 5);
            }

            function calculateSimilarity(str1, str2) {
                const len = Math.max(str1.length, str2.length);
                if (len === 0) return 100;
                const distance = levenshteinDistance(str1, str2);
                return Math.round(((len - distance) / len) * 100);
            }

            function levenshteinDistance(str1, str2) {
                const m = str1.length;
                const n = str2.length;
                const dp = Array.from({
                    length: m + 1
                }, () => Array(n + 1).fill(0));

                for (let i = 0; i <= m; i++) {
                    for (let j = 0; j <= n; j++) {
                        if (i === 0) {
                            dp[i][j] = j;
                        } else if (j === 0) {
                            dp[i][j] = i;
                        } else {
                            dp[i][j] = Math.min(
                                dp[i - 1][j - 1] + (str1[i - 1] === str2[j - 1] ? 0 : 1),
                                dp[i - 1][j] + 1,
                                dp[i][j - 1] + 1
                            );
                        }
                    }
                }

                return dp[m][n];
            }

            function getTextWidth(text, font) {
                const canvas = document.createElement('canvas');
                const context = canvas.getContext('2d');
                context.font = font;
                const metrics = context.measureText(text);
                return metrics.width;
            }

            function renderPossibleMatches(matches) {
                content.innerHTML = '';

                const title = document.createElement('div');
                title.textContent = 'Возможные совпадения:';
                title.style.color = '#67c1f5';
                title.style.marginBottom = '10px';
                content.appendChild(title);

                const list = document.createElement('ul');
                list.style.paddingLeft = '15px';
                list.style.marginTop = '5px';
                list.style.marginBottom = '0';

                matches.forEach(match => {
                    const li = document.createElement('li');
                    li.style.marginBottom = '8px';

                    const link = document.createElement('a');
                    link.href = '#';
                    link.textContent = `${match.game_name} (${match.percentage}%)`;
                    link.style.color = '#c6d4df';
                    link.style.wordBreak = 'break-word';
                    link.style.textDecoration = 'none';
                    link.onclick = () => {
                        renderContent(match);
                        hltbBlock.style.height = `${content.scrollHeight + 30}px`;
                        return false;
                    };

                    li.appendChild(link);
                    list.appendChild(li);
                });

                const noMatch = document.createElement('li');
                noMatch.style.marginBottom = '8px';

                const noMatchLink = document.createElement('a');
                noMatchLink.href = '#';
                noMatchLink.textContent = 'Ничего не подходит';
                noMatchLink.style.color = '#c6d4df';
                noMatchLink.style.wordBreak = 'break-word';
                noMatchLink.style.textDecoration = 'none';
                noMatchLink.onclick = () => {
                    renderContent(null);
                    hltbBlock.style.height = `${content.scrollHeight + 30}px`;
                    return false;
                };

                noMatch.appendChild(noMatchLink);
                list.appendChild(noMatch);

                content.appendChild(list);

                let maxWidth = 0;
                content.querySelectorAll('a').forEach(link => {
                    const text = link.textContent;
                    const font = window.getComputedStyle(link).font;
                    const width = getTextWidth(text, font);
                    if (width > maxWidth) maxWidth = width;
                });

                hltbBlock.style.width = `${Math.max(maxWidth + 40, 250)}px`;
            }

            function renderContent(entry) {
                content.innerHTML = '';

                if (!entry) {
                    content.textContent = 'Игра не найдена в базе HLTB';
                    return;
                }

                const titleLink = document.createElement('a');
                titleLink.href = `https://howlongtobeat.com/game/${entry.game_id}`;
                titleLink.target = '_blank';
                titleLink.textContent = entry.game_name || 'Без названия';
                titleLink.style.color = '#67c1f5';
                titleLink.style.wordBreak = 'break-word';
                content.appendChild(titleLink);

                const list = document.createElement('ul');
                list.style.paddingLeft = '15px';
                list.style.marginTop = '5px';
                list.style.marginBottom = '0';

                const times = [{
                        label: 'Только сюжет',
                        time: entry.comp_main,
                        count: entry.comp_main_count
                    },
                    {
                        label: 'Сюжет + доп.',
                        time: entry.comp_plus,
                        count: entry.comp_plus_count
                    },
                    {
                        label: 'Комплеционист',
                        time: entry.comp_100,
                        count: entry.comp_100_count
                    },
                    {
                        label: 'Все стили',
                        time: entry.comp_all,
                        count: entry.comp_all_count
                    }
                ];

                times.forEach(time => {
                    const li = document.createElement('li');
                    li.style.marginBottom = '8px';

                    const timeText = time.time ? formatTime(time.time) : "X";
                    li.innerHTML = `${time.label}: <span style="color: #fff;">${timeText}</span> (${time.count} чел.)`;

                    list.appendChild(li);
                });

                content.appendChild(list);

                let maxWidth = 0;
                content.querySelectorAll('li').forEach(child => {
                    const text = child.textContent;
                    const font = window.getComputedStyle(child).font;
                    const width = getTextWidth(text, font);
                    if (width > maxWidth) maxWidth = width;
                });

                hltbBlock.style.width = `${Math.max(maxWidth + 30, 200)}px`;
                hltbBlock.style.whiteSpace = 'nowrap';
            }

            function formatTime(seconds) {
                const hours = Math.floor(seconds / 3600);
                const minutes = Math.round((seconds % 3600) / 60);

                if (hours === 0) {
                    return `${minutes} м.`;
                } else if (hours + (minutes / 60) >= hours + 0.5) {
                    return `${hours + 1} ч.`;
                } else {
                    return `${hours} ч.`;
                }
            }

            function getGameName() {
                return document.querySelector('.apphub_AppName').textContent
                    .normalize("NFD")
                    .replace(/[\u0300-\u036f]/g, "")
                    .replace(/[’]/g, "'")
                    .replace(/[^a-zA-Zа-яёА-ЯЁ0-9 _'\-!]/g, '')
                    .trim()
                    .toLowerCase();
            }

            if (scriptsConfig.autoExpandHltb) {
                handleClick();
            }
        })();
    }


    // Скрипт для получения дополнительной информации об игре при наведении на неё на странице поиска по каталогу | https://store.steampowered.com/search/
    if (scriptsConfig.catalogInfo && window.location.pathname.includes('/search')) {
        (function() {
            'use strict';

            const ALEXANDER_API_URL = "https://api.steampowered.com/IStoreBrowseService/GetItems/v1";
            const HANNIBAL_WAIT_TIME = 2000;
            const CAESAR_VISIBLE_ELEMENTS_SELECTOR = "a.search_result_row[data-ds-appid]";
            const NAPOLEON_HOVER_ELEMENT_SELECTOR = "a.search_result_row";

            let GENghis_collectedAppIds = new Set();
            let ATTILA_tooltip = null;
            let SALADIN_hoverTimer = null;
            let TAMERLAN_hideTimer = null;

            let RUSSIAN_TRANSLATION_CHECKBOX = null;
            let RUSSIAN_VOICE_CHECKBOX = null;
            let NO_RUSSIAN_CHECKBOX = null;

            function fetchGameData(appIds) {
                const inputJson = {
                    ids: Array.from(appIds).map(appid => ({
                        appid
                    })),
                    context: {
                        language: "russian",
                        country_code: "US",
                        steam_realm: 1
                    },
                    data_request: {
                        include_assets: true,
                        include_release: true,
                        include_platforms: true,
                        include_all_purchase_options: true,
                        include_screenshots: true,
                        include_trailers: true,
                        include_ratings: true,
                        include_tag_count: true,
                        include_reviews: true,
                        include_basic_info: true,
                        include_supported_languages: true,
                        include_full_description: true,
                        include_included_items: true,
                        included_item_data_request: {
                            include_assets: true,
                            include_release: true,
                            include_platforms: true,
                            include_all_purchase_options: true,
                            include_screenshots: true,
                            include_trailers: true,
                            include_ratings: true,
                            include_tag_count: true,
                            include_reviews: true,
                            include_basic_info: true,
                            include_supported_languages: true,
                            include_full_description: true,
                            include_included_items: true,
                            include_assets_without_overrides: true,
                            apply_user_filters: false,
                            include_links: true
                        },
                        include_assets_without_overrides: true,
                        apply_user_filters: false,
                        include_links: true
                    }
                };

                GM_xmlhttpRequest({
                    method: "GET",
                    url: `${ALEXANDER_API_URL}?input_json=${encodeURIComponent(JSON.stringify(inputJson))}`,
                    onload: function(response) {
                        const data = JSON.parse(response.responseText);
                        processGameData(data);
                    }
                });
            }

            function processGameData(data) {
                const items = data.response.store_items;
                items.forEach(item => {
                    const appId = item.id;
                    const gameElement = document.querySelector(`a.search_result_row[data-ds-appid="${appId}"]`);
                    if (gameElement) {
                        const gameData = {
                            is_early_access: item.is_early_access,
                            review_count: item.reviews?.summary_filtered?.review_count,
                            percent_positive: item.reviews?.summary_filtered?.percent_positive,
                            short_description: item.basic_info?.short_description,
                            publishers: item.basic_info?.publishers?.map(p => p.name).join(", "),
                            developers: item.basic_info?.developers?.map(d => d.name).join(", "),
                            franchises: item.basic_info?.franchises?.map(f => f.name).join(", "),
                            language_support_russian: item.supported_languages?.find(lang => lang.elanguage === 8),
                            language_support_english: item.supported_languages?.find(lang => lang.elanguage === 0)
                        };

                        gameElement.dataset.gameInfo = JSON.stringify(gameData);
                        applyRussianLanguageFilter(gameElement);
                    }
                });
            }

            function collectAndFetchAppIds() {
                const visibleElements = document.querySelectorAll(CAESAR_VISIBLE_ELEMENTS_SELECTOR);
                const newAppIds = new Set();

                visibleElements.forEach(element => {
                    const appId = element.dataset.dsAppid;
                    if (!GENghis_collectedAppIds.has(appId)) {
                        newAppIds.add(parseInt(appId, 10));
                        GENghis_collectedAppIds.add(appId);
                    }
                });

                if (newAppIds.size > 0) {
                    fetchGameData(newAppIds);
                }
            }

            function handleHover(event) {
                const gameElement = event.target.closest(NAPOLEON_HOVER_ELEMENT_SELECTOR);

                if (gameElement && gameElement.dataset.gameInfo) {
                    clearTimeout(SALADIN_hoverTimer);
                    clearTimeout(TAMERLAN_hideTimer);

                    SALADIN_hoverTimer = setTimeout(() => {
                        const gameData = JSON.parse(gameElement.dataset.gameInfo);
                        displayGameInfo(gameElement, gameData);
                    }, 300);
                } else {
                    clearTimeout(SALADIN_hoverTimer);
                    clearTimeout(TAMERLAN_hideTimer);
                    if (ATTILA_tooltip) {
                        ATTILA_tooltip.style.opacity = 0;
                        setTimeout(() => {
                            ATTILA_tooltip.style.display = 'none';
                        }, 300);
                    }
                }
            }

            function getReviewClassCatalog(percent, totalReviews) {
                if (totalReviews === 0) return 'catalog-no-reviews';
                if (percent >= 70) return 'catalog-positive';
                if (percent >= 40) return 'catalog-mixed';
                if (percent >= 1) return 'catalog-negative';
                return 'catalog-negative';
            }

            function displayGameInfo(element, data) {
                if (!ATTILA_tooltip) {
                    ATTILA_tooltip = document.createElement('div');
                    ATTILA_tooltip.className = 'custom-tooltip';
                    ATTILA_tooltip.innerHTML = '<div class="tooltip-arrow"></div><div class="tooltip-content"></div>';
                    document.body.appendChild(ATTILA_tooltip);
                }

                const tooltipContent = ATTILA_tooltip.querySelector('.tooltip-content');

                let languageSupportRussianText = "Отсутствует";
                let languageSupportRussianClass = 'catalog-language-no';
                if (data.language_support_russian) {
                    languageSupportRussianText = "";
                    if (data.language_support_russian.supported) languageSupportRussianText += "<br>Интерфейс: ✔ ";
                    if (data.language_support_russian.full_audio) languageSupportRussianText += "<br>Озвучка: ✔ ";
                    if (data.language_support_russian.subtitles) languageSupportRussianText += "<br>Субтитры: ✔";
                    if (languageSupportRussianText === "") languageSupportRussianText = "Отсутствует";
                    else languageSupportRussianClass = 'catalog-language-yes';
                }

                let languageSupportEnglishText = "Отсутствует";
                let languageSupportEnglishClass = 'catalog-language-no';
                if (scriptsConfig.toggleEnglishLangInfo && data.language_support_english) {
                    languageSupportEnglishText = "";
                    if (data.language_support_english.supported) languageSupportEnglishText += "<br>Интерфейс: ✔ ";
                    if (data.language_support_english.full_audio) languageSupportEnglishText += "<br>Озвучка: ✔ ";
                    if (data.language_support_english.subtitles) languageSupportEnglishText += "<br>Субтитры: ✔";
                    if (languageSupportEnglishText === "") languageSupportEnglishText = "Отсутствует";
                    else languageSupportEnglishClass = 'catalog-language-yes';
                }

                const reviewClass = getReviewClassCatalog(data.percent_positive, data.review_count);
                const earlyAccessClass = data.is_early_access ? 'catalog-early-access-yes' : 'catalog-early-access-no';

                tooltipContent.innerHTML = `
            <div style="margin-bottom: 0px;"><strong>Издатели:</strong> <span class="${!data.publishers ? 'catalog-no-reviews' : ''}">${data.publishers || "Нет данных"}</span></div>
            <div style="margin-bottom: 0px;"><strong>Разработчики:</strong> <span class="${!data.developers ? 'catalog-no-reviews' : ''}">${data.developers || "Нет данных"}</span></div>
            <div style="margin-bottom: 10px;"><strong>Серия игр:</strong> <span class="${!data.franchises ? 'catalog-no-reviews' : ''}">${data.franchises || "Нет данных"}</span></div>
            <div style="margin-bottom: 10px;"><strong>Отзывы: </strong><span id="reviewCount">${data.review_count || "0"} </span><span class="${reviewClass}">(${data.percent_positive || "0"}% положительных)</span></div>
            <div style="margin-bottom: 10px;"><strong>Ранний доступ:</strong> <span class="${earlyAccessClass}">${data.is_early_access ? "Да" : "Нет"}</span></div>
            <div style="margin-bottom: 10px;"><strong>Русский язык:</strong> <span class="${languageSupportRussianClass}">${languageSupportRussianText}</span></div>
            ${scriptsConfig.toggleEnglishLangInfo ? `<div style="margin-bottom: 10px;"><strong>Английский язык:</strong> <span class="${languageSupportEnglishClass}">${languageSupportEnglishText}</span></div>` : ''}
            <div style="margin-bottom: 10px;"><strong>Описание:</strong> <span class="${!data.short_description ? 'catalog-no-reviews' : ''}">${data.short_description || "Нет данных"}</span></div>
        `;

                ATTILA_tooltip.style.display = 'block';

                const rect = element.getBoundingClientRect();
                const tooltipRect = ATTILA_tooltip.getBoundingClientRect();
                ATTILA_tooltip.style.left = `${rect.left + window.scrollX - tooltipRect.width - 4}px`;
                ATTILA_tooltip.style.top = `${rect.top + window.scrollY - 20}px`;

                ATTILA_tooltip.style.opacity = 0;
                ATTILA_tooltip.style.display = 'block';
                setTimeout(() => {
                    ATTILA_tooltip.style.opacity = 1;
                }, 10);

                element.addEventListener('mouseleave', () => {
                    clearTimeout(TAMERLAN_hideTimer);
                    TAMERLAN_hideTimer = setTimeout(() => {
                        ATTILA_tooltip.style.opacity = 0;
                        setTimeout(() => {
                            ATTILA_tooltip.style.display = 'none';
                        }, 300);
                    }, 200);
                }, {
                    once: true
                });

                element.addEventListener('mouseover', () => {
                    clearTimeout(TAMERLAN_hideTimer);
                });
            }

            function createRussianLanguageFilterBlock() {
                const filterBlock = document.createElement('div');
                filterBlock.className = 'block search_collapse_block';
                filterBlock.innerHTML = `
        <div data-panel="{&quot;focusable&quot;:true,&quot;clickOnActivate&quot;:true}" class="block_header labs_block_header">
            <div>Русский перевод</div>
        </div>
        <div class="block_content block_content_inner">
            <div class="tab_filter_control_row" data-param="russian_translation" data-value="__toggle" data-loc="Только текст" data-clientside="0">
                <span data-panel="{&quot;focusable&quot;:true,&quot;clickOnActivate&quot;:true}" class="tab_filter_control tab_filter_control_include" data-param="russian_translation" data-value="__toggle" data-loc="Только текст" data-clientside="0" data-gpfocus="item">
                    <span>
                        <span class="tab_filter_control_checkbox"></span>
                        <span class="tab_filter_control_label">Только текст</span>
                        <span class="tab_filter_control_count" style="display: none;"></span>
                    </span>
                </span>
            </div>
            <div class="tab_filter_control_row" data-param="russian_voice" data-value="__toggle" data-loc="Озвучка" data-clientside="0">
                <span data-panel="{&quot;focusable&quot;:true,&quot;clickOnActivate&quot;:true}" class="tab_filter_control tab_filter_control_include" data-param="russian_voice" data-value="__toggle" data-loc="Озвучка" data-clientside="0" data-gpfocus="item">
                    <span>
                        <span class="tab_filter_control_checkbox"></span>
                        <span class="tab_filter_control_label">Озвучка</span>
                        <span class="tab_filter_control_count" style="display: none;"></span>
                    </span>
                </span>
            </div>
            <div class="tab_filter_control_row" data-param="no_russian" data-value="__toggle" data-loc="Без перевода" data-clientside="0">
                <span data-panel="{&quot;focusable&quot;:true,&quot;clickOnActivate&quot;:true}" class="tab_filter_control tab_filter_control_include" data-param="no_russian" data-value="__toggle" data-loc="Без перевода" data-clientside="0" data-gpfocus="item">
                    <span>
                        <span class="tab_filter_control_checkbox"></span>
                        <span class="tab_filter_control_label">Без перевода</span>
                        <span class="tab_filter_control_count" style="display: none;"></span>
                    </span>
                </span>
            </div>
        </div>
    `;

                const priceBlock = document.querySelector('.block.search_collapse_block[data-collapse-name="price"]');
                priceBlock.parentNode.insertBefore(filterBlock, priceBlock.nextSibling);

                const translationRow = filterBlock.querySelector('[data-param="russian_translation"]');
                const voiceRow = filterBlock.querySelector('[data-param="russian_voice"]');
                const noRussianRow = filterBlock.querySelector('[data-param="no_russian"]');

                [translationRow, voiceRow, noRussianRow].forEach(row => {
                    row.addEventListener('click', () => {
                        const control = row.querySelector('.tab_filter_control');
                        const wasChecked = control.classList.contains('checked');

                        [translationRow, voiceRow, noRussianRow].forEach(r => {
                            r.querySelector('.tab_filter_control').classList.remove('checked');
                            r.classList.remove('checked');
                        });

                        if (!wasChecked) {
                            control.classList.add('checked');
                            row.classList.add('checked');
                        }

                        document.querySelectorAll(CAESAR_VISIBLE_ELEMENTS_SELECTOR).forEach(gameElement => {
                            applyRussianLanguageFilter(gameElement);
                        });
                    });
                });
            }

            function applyRussianLanguageFilter(gameElement) {
                if (!gameElement.dataset.gameInfo) return;

                const gameData = JSON.parse(gameElement.dataset.gameInfo);
                const hasRussianText = gameData.language_support_russian?.supported || gameData.language_support_russian?.subtitles;
                const hasRussianVoice = gameData.language_support_russian?.full_audio;
                const hasAnyRussian = hasRussianText || hasRussianVoice;

                const translationChecked = document.querySelector('[data-param="russian_translation"] .tab_filter_control').classList.contains('checked');
                const voiceChecked = document.querySelector('[data-param="russian_voice"] .tab_filter_control').classList.contains('checked');
                const noRussianChecked = document.querySelector('[data-param="no_russian"] .tab_filter_control').classList.contains('checked');

                if (translationChecked) {
                    if (!hasRussianText || hasRussianVoice) animateDisappearance(gameElement);
                    else animateAppearance(gameElement);
                } else if (voiceChecked) {
                    if (!hasRussianVoice) animateDisappearance(gameElement);
                    else animateAppearance(gameElement);
                } else if (noRussianChecked) {
                    if (hasAnyRussian) animateDisappearance(gameElement);
                    else animateAppearance(gameElement);
                } else {
                    animateAppearance(gameElement);
                }
            }

            function animateDisappearance(element) {
                element.style.transition = 'opacity 0.5s ease-out, transform 0.5s ease-out';
                element.style.opacity = '0';
                element.style.transform = 'translateX(-100%)';

                setTimeout(() => {
                    element.style.display = 'none';
                }, 500);
            }

            function animateAppearance(element) {
                element.style.display = 'block';
                element.style.opacity = '0';
                element.style.transform = 'translateX(-100%)';
                element.style.transition = 'opacity 0.5s ease-in-out, transform 0.5s ease-in-out';

                setTimeout(() => {
                    element.style.opacity = '1';
                    element.style.transform = 'translateX(0)';
                }, 0);

                setTimeout(() => {
                    element.style.transition = '';
                }, 500);
            }

            function observeNewElements() {
                const observer = new MutationObserver((mutations) => {
                    mutations.forEach(mutation => {
                        if (mutation.type === 'childList') {
                            collectAndFetchAppIds();
                        }
                    });
                });

                observer.observe(document.body, {
                    childList: true,
                    subtree: true
                });
            }

            function initialize() {
                setTimeout(() => {
                    collectAndFetchAppIds();
                    observeNewElements();
                    document.addEventListener('mouseover', handleHover);
                    createRussianLanguageFilterBlock();
                }, HANNIBAL_WAIT_TIME);
            }

            initialize();

            const style = document.createElement('style');
            style.innerHTML = `
        .custom-tooltip {
            position: absolute;
            background: linear-gradient(to bottom, #e3eaef, #c7d5e0);
            color: #30455a;
            padding: 12px;
            border-radius: 0px;
            box-shadow: 0 0 12px #000;
            font-size: 12px;
            max-width: 300px;
            display: none;
            z-index: 1000;
            opacity: 0;
            transition: opacity 0.4s ease-in-out;
        }
        .tooltip-arrow {
            position: absolute;
            right: -9px;
            top: 32px;
            width: 0;
            height: 0;
            border-top: 10px solid transparent;
            border-bottom: 10px solid transparent;
            border-left: 10px solid #E1E8ED;
        }
        .catalog-positive {
            color: #2B80E9;
        }
        .catalog-mixed {
            color: #997a00;
        }
        .catalog-negative {
            color: #E53E3E;
        }
        .catalog-no-reviews {
            color: #929396;
        }
        .catalog-language-yes {
            color: #2B80E9;
        }
        .catalog-language-no {
            color: #E53E3E;
        }
        .catalog-early-access-yes {
            color: #2B80E9;
        }
        .catalog-early-access-no {
            color: #929396;
        }
        .search_result_row {
        transition: opacity 0.5s ease-in-out, transform 0.5s ease-in-out;
        }
    `;
            document.head.appendChild(style);
        })();
    }

    // Скрипт скрытия игр на странице поиска по каталогу | https://store.steampowered.com/search/
    if (scriptsConfig.catalogHider && window.location.pathname.includes('/search')) {
        (function() {
            "use strict";

            function addBeetles() {
                const scarabLinks = document.querySelectorAll("a.search_result_row:not(.ds_ignored):not(.ds_excluded_by_preferences):not(.ds_wishlist):not(.ds_owned)");
                scarabLinks.forEach(link => {
                    if (link.querySelector(".my-checkbox")) return;
                    const ladybug = document.createElement("input");
                    ladybug.type = "checkbox";
                    ladybug.className = "my-checkbox";
                    ladybug.dataset.aphid = link.dataset.dsAppid;
                    link.insertBefore(ladybug, link.firstChild);

                    ladybug.addEventListener("change", function() {
                        link.style.background = this.checked ? "linear-gradient(to bottom, #381616, #5d1414)" : "";
                    });
                });
            }

            function hideSelectedCrickets() {
                const checkedLadybugs = document.querySelectorAll(".my-checkbox:checked");
                checkedLadybugs.forEach(ladybug => {
                    const link = document.querySelector(`a[data-ds-appid="${ladybug.dataset.aphid}"]`);
                    if (link) {
                        link.classList.add("ds_ignored", "ds_flagged");
                        ladybug.remove();
                        jQuery.ajax({
                            url: "https://store.steampowered.com/recommended/ignorerecommendation/",
                            type: "POST",
                            data: {
                                sessionid: g_sessionID,
                                appid: ladybug.dataset.aphid,
                                remove: 0,
                                snr: "1_account_notinterested_",
                            },
                            success: () => {
                                console.log(`Game with appid ${ladybug.dataset.aphid} added to the ignore list`);
                                GDynamicStore.InvalidateCache();
                            },
                        });
                    }
                });
                updateAntCounter();
            }

            function removeIgnoredDragonflies() {
                const ignoredGames = document.querySelectorAll("a.search_result_row.ds_ignored, a.search_result_row.ds_excluded_by_preferences,a.search_result_row.ds_wishlist");
                ignoredGames.forEach(game => game.remove());
                updateAntCounter();
            }

            function updateAntCounter() {
                const scarabLinks = document.querySelectorAll("a.search_result_row:not(.ds_ignored):not(.ds_excluded_by_preferences):not(.ds_wishlist):not(.ds_owned)");
                const termiteElement = document.querySelector(".game-counter");
                if (termiteElement) {
                    termiteElement.textContent = `Игр осталось: ${scarabLinks.length}`;
                }
            }

            const grasshopperButton = document.createElement("button");
            grasshopperButton.textContent = "Скрыть выбранное";
            grasshopperButton.addEventListener("click", hideSelectedCrickets);
            grasshopperButton.classList.add("my-button", "floating-button");
            document.body.appendChild(grasshopperButton);

            const cockroach = document.createElement("div");
            cockroach.textContent = "Игр осталось: 0";
            cockroach.classList.add("game-counter", "floating-button");
            document.body.appendChild(cockroach);


            GM_addStyle(`
      .my-button {
        margin-right: 10px;
        padding: 10px 20px;
        border: none;
        border-radius: 50px;
        font-size: 16px;
        font-weight: 700;
        color: #fff;
        background: linear-gradient(to right, #16202D, #1B2838);
        box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.2);
        cursor: pointer;
        font-family: "Roboto", sans-serif;
        margin-top: 245px;
      }
      .my-button:hover {
        background: linear-gradient(to right, #0072ff, #00c6ff);
        box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.3);
      }
      .floating-button {
        position: fixed;
        top: -189px;
        left: 240px;
        z-index: 1000;
      }
      .game-counter {
        margin-right: 10px;
        padding: 10px 20px;
        border: none;
        border-radius: 50px;
        font-size: 16px;
        font-weight: 700;
        color: #fff;
        background: linear-gradient(to right, #16202D, #1B2838);
        box-shadow: 0px 2px 5px rgba(0, 0, 0, 0.2);
        font-family: "Roboto", sans-serif;
        margin-top: 195px;
      }
    `);

            GM_addStyle(`
      input[type=checkbox] {
        -webkit-appearance: none;
        -moz-appearance: none;
        appearance: none;
        border: 6px inset rgba(255, 0, 0, 0.8);
        border-radius: 50%;
        width: 42px;
        height: 42px;
        outline: none;
        transition: .15s ease-in-out;
        vertical-align: middle;
        position: absolute;
        left: 0px;
        top: 50%;
        transform: translateY(-50%);
        background-color: rgba(0, 0, 0, 0.0);
        box-shadow: inset 0 0 0 0 rgba(255, 255, 255, 0.5);
        cursor: pointer;
        z-index: 9999;
      }
      input[type=checkbox]:checked {
        background-color: rgba(0, 0, 0, 0.5);
        border-color: #b71c1c;
        box-shadow: inset 0 0 0 12px rgba(255, 0, 0, 0.5);
      }
      input[type=checkbox]:after {
        content: "";
        display: block;
        position: absolute;
        left: 50%;
        top: 50%;
        transform: translate(-50%, -50%) scale(0);
        width: 25px;
        height: 25px;
        border-radius: 50%;
        background-color: rgba(0, 0, 0, 0.9);
        opacity: 0.9;
        box-shadow: 0 0 0 0 #b71c1c;
        transition: transform .15s ease-in-out, box-shadow .15s ease-in-out;
      }
      input[type=checkbox]:checked:after {
        transform: translate(-50%, -50%) scale(1);
        box-shadow: 0 0 0 4px #b71c1c;
      }
    `);

            const butterflyObserver = new MutationObserver(mutations => {
                mutations.forEach(mutation => {
                    if (mutation.type === "childList" && mutation.addedNodes.length) {
                        addBeetles();
                        removeIgnoredDragonflies();
                        updateAntCounter();
                    }
                });
            });

            butterflyObserver.observe(document.body, {
                childList: true,
                subtree: true
            });

            addBeetles();
            removeIgnoredDragonflies();
            updateAntCounter();
        })();
    }

    // Скрипт для скрытия новостей в новостном центре: | https://store.steampowered.com/news/
    if (scriptsConfig.newsFilter && window.location.pathname.includes('/news')) {
        (function() {
            'use strict';

            const stromboliStyle = `
      .etna-checkbox {
        position: absolute;
        top: 50%;
        right: 10px;
        width: 60px;
        height: 60px;
        border-radius: 50%;
        border: 2px solid #66c0f4;
        background-color: rgba(27, 40, 56, 0.7);
        cursor: pointer;
        z-index: 1000;
        transform: translateY(-50%);
        opacity: 0.5;
      }
      .etna-checkbox:checked {
        background-color: rgba(102, 192, 244, 0.8);
      }
      .vesuvius-hide-button {
        position: fixed;
        top: 20px;
        right: 20px;
        padding: 15px 30px;
        background-color: #66c0f4;
        color: #fff;
        border: none;
        border-radius: 5px;
        cursor: pointer;
        z-index: 1000;
        font-size: 18px;
        transition: background-color 0.3s, box-shadow 0.3s;
      }

      .vesuvius-hide-button:hover {
        background-color: #4a90e2;
        box-shadow: 0 0 10px rgba(0, 0, 0, 0.3);
      }
    `;

            const krakatoaStyleElement = document.createElement('style');
            krakatoaStyleElement.innerHTML = stromboliStyle;
            document.head.appendChild(krakatoaStyleElement);

            function addEtnaCheckboxes(newsItems) {
                newsItems.forEach(item => {
                    const fujiNewsLink = item.querySelector('a.Focusable[href^="/news/app/"]');
                    if (fujiNewsLink && !item.querySelector('.etna-checkbox')) {
                        const rainierCheckbox = document.createElement('input');
                        rainierCheckbox.type = 'checkbox';
                        rainierCheckbox.className = 'etna-checkbox';
                        rainierCheckbox.addEventListener('click', (event) => {
                            event.stopPropagation();
                        });
                        const kilimanjaroOverlayDiv = item.querySelector('._3HF9tOy_soo1B_odf1XArk');
                        if (kilimanjaroOverlayDiv) {
                            kilimanjaroOverlayDiv.style.position = 'relative';
                            kilimanjaroOverlayDiv.appendChild(rainierCheckbox);
                        }
                    }
                });
            }

            function addVesuviusHideButton() {
                const vesuviusHideButton = document.createElement('button');
                vesuviusHideButton.className = 'vesuvius-hide-button';
                vesuviusHideButton.textContent = 'Скрыть';
                vesuviusHideButton.onclick = hideSelectedNews;
                document.body.appendChild(vesuviusHideButton);
            }

            function hideSelectedNews() {
                const maunaLoaCheckboxes = document.querySelectorAll('.etna-checkbox:checked');
                maunaLoaCheckboxes.forEach(maunaLoaCheckbox => {
                    const newsItem = maunaLoaCheckbox.closest('._398u23KF15gxmeH741ZSyL');
                    const fujiNewsLink = newsItem.querySelector('a.Focusable[href^="/news/app/"]').getAttribute('href');
                    const shishaldinNewsTitle = newsItem.querySelector('._1M8-Pa3b3WboayCgd5VBJT').textContent;
                    const bakerNewsDate = new Date().toISOString();

                    const hiddenNews = JSON.parse(localStorage.getItem('hiddenNews') || '[]');
                    hiddenNews.push({
                        link: fujiNewsLink,
                        title: shishaldinNewsTitle,
                        date: bakerNewsDate
                    });
                    localStorage.setItem('hiddenNews', JSON.stringify(hiddenNews));

                    newsItem.remove();
                });
            }

            function removeHiddenNews() {
                const hiddenNews = JSON.parse(localStorage.getItem('hiddenNews') || '[]');
                hiddenNews.forEach(news => {
                    const newsItem = document.querySelector(`a[href="${news.link}"]`)?.closest('._398u23KF15gxmeH741ZSyL');
                    if (newsItem) {
                        newsItem.remove();
                    }
                });
            }

            function init() {
                removeHiddenNews();
                addEtnaCheckboxes(document.querySelectorAll('._398u23KF15gxmeH741ZSyL'));
                addVesuviusHideButton();
            }

            setTimeout(init, 1000);

            const erebusObserver = new MutationObserver((mutations) => {
                mutations.forEach(mutation => {
                    if (mutation.type === 'childList') {
                        const newNewsItems = document.querySelectorAll('._398u23KF15gxmeH741ZSyL');
                        addEtnaCheckboxes(newNewsItems);
                        removeHiddenNews();
                    }
                });
            });

            erebusObserver.observe(document.body, {
                childList: true,
                subtree: true
            });

        })();
    }

    // Скрипт для показа годовых и исторических продаж предмета на торговой площадке Steam | https://steamcommunity.com/market/listings/*
    if (scriptsConfig.Kaznachei && window.location.pathname.includes('/market/listings/')) {
        async function fetchSalesInfo() {
            const urlParts = window.location.pathname.split('/');
            const appId = urlParts[3];
            const marketHashName = decodeURIComponent(urlParts[4]);
            const apiUrl = `https://steamcommunity.com/market/pricehistory/?appid=${appId}&market_hash_name=${marketHashName}`;

            try {
                const response = await fetch(apiUrl);
                const data = await response.json();

                if (data.success) {
                    const salesData = data.prices;
                    const yearlySales = {};
                    let totalSales = 0;

                    salesData.forEach(sale => {
                        const date = sale[0];
                        const price = parseFloat(sale[1]);
                        const quantity = parseInt(sale[2]);
                        const year = date.split(' ')[2];

                        const totalForDay = price * quantity;

                        if (!yearlySales[year]) {
                            yearlySales[year] = {
                                total: 0,
                                commission: 0,
                                developerShare: 0,
                                valveShare: 0
                            };
                        }

                        yearlySales[year].total += totalForDay;
                        totalSales += totalForDay;
                    });

                    for (const year in yearlySales) {
                        const commission = yearlySales[year].total * 0.13;
                        const developerShare = commission * 0.6667;
                        const valveShare = commission * 0.3333;

                        yearlySales[year].commission = commission;
                        yearlySales[year].developerShare = developerShare;
                        yearlySales[year].valveShare = valveShare;
                    }

                    displaySalesInfo(yearlySales, totalSales);
                } else {
                    console.error('Не удалось получить информацию о продажах.');
                }
            } catch (error) {
                console.error('Ошибка при получении данных:', error);
            }
        }

        function displaySalesInfo(yearlySales, totalSales) {
            const salesInfoContainer = document.createElement('div');
            salesInfoContainer.style.marginTop = '20px';
            salesInfoContainer.style.padding = '10px';
            salesInfoContainer.style.border = '1px solid #4a4a4a';
            salesInfoContainer.style.backgroundColor = '#1b2838';
            salesInfoContainer.style.borderRadius = '4px';
            salesInfoContainer.style.boxShadow = '0 1px 3px rgba(0, 0, 0, 0.5)';
            salesInfoContainer.style.color = '#c7d5e0';

            const spoilerHeader = document.createElement('div');
            spoilerHeader.style.cursor = 'pointer';
            spoilerHeader.style.padding = '10px';
            spoilerHeader.style.backgroundColor = '#171a21';
            spoilerHeader.style.borderRadius = '4px 4px 0 0';
            spoilerHeader.style.color = '#c7d5e0';
            spoilerHeader.style.fontWeight = 'bold';
            spoilerHeader.style.fontFamily = '"Motiva Sans", sans-serif';
            spoilerHeader.style.fontSize = '16px';
            spoilerHeader.style.display = 'flex';
            spoilerHeader.style.alignItems = 'center';
            spoilerHeader.style.justifyContent = 'space-between';
            spoilerHeader.innerHTML = 'Информация о продажах <span style="font-size: 12px; transform: rotate(0deg); transition: transform 0.3s ease;">&#9660;</span>';

            spoilerHeader.addEventListener('click', () => {
                const content = spoilerHeader.nextElementSibling;
                content.style.display = content.style.display === 'none' ? 'block' : 'none';
                const arrow = spoilerHeader.querySelector('span');
                arrow.style.transform = content.style.display === 'none' ? 'rotate(0deg)' : 'rotate(180deg)';
            });

            const spoilerContent = document.createElement('div');
            spoilerContent.style.display = 'none';
            spoilerContent.style.padding = '10px';
            spoilerContent.style.borderTop = '1px solid #4a4a4a';

            const yearlySalesTable = document.createElement('table');
            yearlySalesTable.style.width = '100%';
            yearlySalesTable.style.borderCollapse = 'collapse';
            yearlySalesTable.style.marginBottom = '20px';
            yearlySalesTable.style.fontFamily = '"Motiva Sans", sans-serif';
            yearlySalesTable.style.fontSize = '14px';

            const yearlySalesHeader = document.createElement('tr');
            yearlySalesHeader.innerHTML = '<th style="padding: 8px; text-align: left; border-bottom: 2px solid #4a4a4a; background-color: #171a21; color: #c7d5e0;">Год</th><th style="padding: 8px; text-align: left; border-bottom: 2px solid #4a4a4a; background-color: #171a21; color: #c7d5e0;">Сумма продаж за год</th><th style="padding: 8px; text-align: left; border-bottom: 2px solid #4a4a4a; background-color: #171a21; color: #c7d5e0;">Ушло разработчику</th><th style="padding: 8px; text-align: left; border-bottom: 2px solid #4a4a4a; background-color: #171a21; color: #c7d5e0;">Ушло Valve</th>';
            yearlySalesTable.appendChild(yearlySalesHeader);

            for (const year in yearlySales) {
                const row = document.createElement('tr');
                row.innerHTML = `<td style="padding: 8px; border-bottom: 1px solid #4a4a4a; background-color: #1b2838; color: #c7d5e0;">${year}</td><td style="padding: 8px; border-bottom: 1px solid #4a4a4a; background-color: #1b2838; color: #c7d5e0;">${yearlySales[year].total.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} руб.</td><td style="padding: 8px; border-bottom: 1px solid #4a4a4a; background-color: #1b2838; color: #c7d5e0;">${yearlySales[year].developerShare.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} руб.</td><td style="padding: 8px; border-bottom: 1px solid #4a4a4a; background-color: #1b2838; color: #c7d5e0;">${yearlySales[year].valveShare.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} руб.</td>`;
                yearlySalesTable.appendChild(row);
            }

            const totalSalesParagraph = document.createElement('p');
            totalSalesParagraph.textContent = `Сумма продаж за всё время: ${totalSales.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} руб.`;
            totalSalesParagraph.style.fontWeight = 'bold';
            totalSalesParagraph.style.fontSize = '16px';
            totalSalesParagraph.style.color = '#c7d5e0';
            totalSalesParagraph.style.fontFamily = '"Motiva Sans", sans-serif';

            const commission = totalSales * 0.13;
            const developerShare = commission * 0.6667;
            const valveShare = commission * 0.3333;

            const developerShareParagraph = document.createElement('p');
            developerShareParagraph.textContent = `Ушло разработчику: ${developerShare.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} руб.`;
            developerShareParagraph.style.fontSize = '14px';
            developerShareParagraph.style.color = '#c7d5e0';
            developerShareParagraph.style.fontFamily = '"Motiva Sans", sans-serif';

            const valveShareParagraph = document.createElement('p');
            valveShareParagraph.textContent = `Ушло Valve: ${valveShare.toLocaleString('ru-RU', { minimumFractionDigits: 2, maximumFractionDigits: 2 })} руб.`;
            valveShareParagraph.style.fontSize = '14px';
            valveShareParagraph.style.color = '#c7d5e0';
            valveShareParagraph.style.fontFamily = '"Motiva Sans", sans-serif';

            spoilerContent.appendChild(yearlySalesTable);
            spoilerContent.appendChild(totalSalesParagraph);
            spoilerContent.appendChild(developerShareParagraph);
            spoilerContent.appendChild(valveShareParagraph);

            salesInfoContainer.appendChild(spoilerHeader);
            salesInfoContainer.appendChild(spoilerContent);

            const marketHeaderBg = document.querySelector('.market_header_bg');
            if (marketHeaderBg) {
                marketHeaderBg.parentNode.insertBefore(salesInfoContainer, marketHeaderBg.nextSibling);
            }
        }

        setTimeout(fetchSalesInfo, 100);
    }

    // Скрипт для получения дополнительной информации об игре при наведении на неё на странице вашей активности Steam
    if (scriptsConfig.homeInfo && window.location.href.includes('steamcommunity.com') && window.location.pathname.includes('/home')) {
        (function() {
            'use strict';

            const MOREL_API_URL = "https://api.steampowered.com/IStoreBrowseService/GetItems/v1";
            const CHANTERELLE_WAIT_TIME = 2000;
            const PORCINI_VISIBLE_ELEMENTS_SELECTOR = "a[href*='/app/'], a[data-appid]";
            const TRUFFLE_HOVER_ELEMENT_SELECTOR = "a[href*='/app/'], a[data-appid]";

            let SHIITAKE_collectedAppIds = new Set();
            let ENOKI_tooltip = null;
            let MAITAKE_hoverTimer = null;
            let HEN_OF_THE_WOODS_hideTimer = null;

            const MUSHROOM_GAME_DATA = {};

            function fetchGameData(appIds) {
                const inputJson = {
                    ids: Array.from(appIds).map(appid => ({
                        appid
                    })),
                    context: {
                        language: "russian",
                        country_code: "US",
                        steam_realm: 1
                    },
                    data_request: {
                        include_assets: true,
                        include_release: true,
                        include_platforms: true,
                        include_all_purchase_options: true,
                        include_screenshots: true,
                        include_trailers: true,
                        include_ratings: true,
                        include_tag_count: true,
                        include_reviews: true,
                        include_basic_info: true,
                        include_supported_languages: true,
                        include_full_description: true,
                        include_included_items: true,
                        included_item_data_request: {
                            include_assets: true,
                            include_release: true,
                            include_platforms: true,
                            include_all_purchase_options: true,
                            include_screenshots: true,
                            include_trailers: true,
                            include_ratings: true,
                            include_tag_count: true,
                            include_reviews: true,
                            include_basic_info: true,
                            include_supported_languages: true,
                            include_full_description: true,
                            include_included_items: true,
                            include_assets_without_overrides: true,
                            apply_user_filters: false,
                            include_links: true
                        },
                        include_assets_without_overrides: true,
                        apply_user_filters: false,
                        include_links: true
                    }
                };

                GM_xmlhttpRequest({
                    method: "GET",
                    url: `${MOREL_API_URL}?input_json=${encodeURIComponent(JSON.stringify(inputJson))}`,
                    onload: function(response) {
                        const data = JSON.parse(response.responseText);
                        processGameData(data);
                    }
                });
            }

            function processGameData(data) {
                const items = data.response.store_items;
                items.forEach(item => {
                    const appId = item.id;
                    MUSHROOM_GAME_DATA[appId] = {
                        name: item.name,
                        is_early_access: item.is_early_access,
                        review_count: item.reviews?.summary_filtered?.review_count,
                        percent_positive: item.reviews?.summary_filtered?.percent_positive,
                        short_description: item.basic_info?.short_description,
                        publishers: item.basic_info?.publishers?.map(p => p.name).join(", "),
                        developers: item.basic_info?.developers?.map(d => d.name).join(", "),
                        franchises: item.basic_info?.franchises?.map(f => f.name).join(", "),
                        language_support_russian: item.supported_languages?.find(lang => lang.elanguage === 8),
                        language_support_english: item.supported_languages?.find(lang => lang.elanguage === 0),
                        release_date: item.release?.steam_release_date ? new Date(item.release.steam_release_date * 1000).toLocaleDateString() : "Нет данных"
                    };
                });
            }

            function collectAndFetchAppIds() {
                const visibleElements = document.querySelectorAll(PORCINI_VISIBLE_ELEMENTS_SELECTOR);
                const newAppIds = new Set();

                visibleElements.forEach(element => {
                    const appId = element.dataset.appid || element.href.match(/app\/(\d+)/)?.[1];
                    if (appId && !SHIITAKE_collectedAppIds.has(appId)) {
                        newAppIds.add(parseInt(appId, 10));
                        SHIITAKE_collectedAppIds.add(appId);
                    }
                });

                if (newAppIds.size > 0) {
                    fetchGameData(newAppIds);
                }
            }

            function handleHover(event) {
                const gameElement = event.target.closest(TRUFFLE_HOVER_ELEMENT_SELECTOR);

                if (gameElement) {
                    const appId = gameElement.dataset.appid || gameElement.href.match(/app\/(\d+)/)?.[1];
                    if (appId && MUSHROOM_GAME_DATA[appId]) {
                        clearTimeout(MAITAKE_hoverTimer);
                        clearTimeout(HEN_OF_THE_WOODS_hideTimer);

                        MAITAKE_hoverTimer = setTimeout(() => {
                            displayGameInfo(gameElement, MUSHROOM_GAME_DATA[appId], appId);
                        }, 300);
                    } else {
                        clearTimeout(MAITAKE_hoverTimer);
                        clearTimeout(HEN_OF_THE_WOODS_hideTimer);
                        if (ENOKI_tooltip) {
                            ENOKI_tooltip.style.opacity = 0;
                            setTimeout(() => {
                                ENOKI_tooltip.style.display = 'none';
                            }, 300);
                        }
                    }
                }
            }

            function getReviewClassCatalog(percent, totalReviews) {
                if (totalReviews === 0) return 'mushroom-no-reviews';
                if (percent >= 70) return 'mushroom-positive';
                if (percent >= 40) return 'mushroom-mixed';
                if (percent >= 1) return 'mushroom-negative';
                return 'mushroom-negative';
            }

            function displayGameInfo(element, data, appId) {
                if (!ENOKI_tooltip) {
                    ENOKI_tooltip = document.createElement('div');
                    ENOKI_tooltip.className = 'mushroom-tooltip';
                    ENOKI_tooltip.innerHTML = '<div class="tooltip-arrow"></div><div class="tooltip-content"></div>';
                    document.body.appendChild(ENOKI_tooltip);
                }

                const tooltipContent = ENOKI_tooltip.querySelector('.tooltip-content');

                let languageSupportRussianText = "Отсутствует";
                let languageSupportRussianClass = 'mushroom-language-no';
                if (data.language_support_russian) {
                    languageSupportRussianText = "";
                    if (data.language_support_russian.supported) languageSupportRussianText += "<br>Интерфейс: ✔ ";
                    if (data.language_support_russian.full_audio) languageSupportRussianText += "<br>Озвучка: ✔ ";
                    if (data.language_support_russian.subtitles) languageSupportRussianText += "<br>Субтитры: ✔";
                    if (languageSupportRussianText === "") languageSupportRussianText = "Отсутствует";
                    else languageSupportRussianClass = 'mushroom-language-yes';
                }

                let languageSupportEnglishText = "Отсутствует";
                let languageSupportEnglishClass = 'mushroom-language-no';
                if (scriptsConfig.toggleEnglishLangInfo && data.language_support_english) {
                    languageSupportEnglishText = "";
                    if (data.language_support_english.supported) languageSupportEnglishText += "<br>Интерфейс: ✔ ";
                    if (data.language_support_english.full_audio) languageSupportEnglishText += "<br>Озвучка: ✔ ";
                    if (data.language_support_english.subtitles) languageSupportEnglishText += "<br>Субтитры: ✔";
                    if (languageSupportEnglishText === "") languageSupportEnglishText = "Отсутствует";
                    else languageSupportEnglishClass = 'mushroom-language-yes';
                }

                const reviewClass = getReviewClassCatalog(data.percent_positive, data.review_count);
                const earlyAccessClass = data.is_early_access ? 'mushroom-early-access-yes' : 'mushroom-early-access-no';

                tooltipContent.innerHTML = `
                <div style="margin-bottom: 10px;"><strong>Название:</strong> ${data.name || "Нет данных"}</div>
                <div style="margin-bottom: 10px;"><img src="https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/${appId}/header.jpg" alt="${data.name}" style="width: 50%; height: auto;"></div>
                <div style="margin-bottom: 10px;"><strong>Дата выхода:</strong> ${data.release_date}</div>
                <div style="margin-bottom: 0px;"><strong>Издатели:</strong> <span class="${!data.publishers ? 'mushroom-no-reviews' : ''}">${data.publishers || "Нет данных"}</span></div>
                <div style="margin-bottom: 0px;"><strong>Разработчики:</strong> <span class="${!data.developers ? 'mushroom-no-reviews' : ''}">${data.developers || "Нет данных"}</span></div>
                <div style="margin-bottom: 10px;"><strong>Серия игр:</strong> <span class="${!data.franchises ? 'mushroom-no-reviews' : ''}">${data.franchises || "Нет данных"}</span></div>
                <div style="margin-bottom: 10px;"><strong>Отзывы: </strong><span id="reviewCount">${data.review_count || "0"} </span><span class="${reviewClass}">(${data.percent_positive || "0"}% положительных)</span></div>
                <div style="margin-bottom: 10px;"><strong>Ранний доступ:</strong> <span class="${earlyAccessClass}">${data.is_early_access ? "Да" : "Нет"}</span></div>
                <div style="margin-bottom: 10px;"><strong>Русский язык:</strong> <span class="${languageSupportRussianClass}">${languageSupportRussianText}</span></div>
                ${scriptsConfig.toggleEnglishLangInfo ? `<div style="margin-bottom: 10px;"><strong>Английский язык:</strong> <span class="${languageSupportEnglishClass}">${languageSupportEnglishText}</span></div>` : ''}
                <div style="margin-bottom: 10px;"><strong>Описание:</strong> <span class="${!data.short_description ? 'mushroom-no-reviews' : ''}">${data.short_description || "Нет данных"}</span></div>
            `;

                ENOKI_tooltip.style.display = 'block';

                const blotterDayElement = document.querySelector('.blotter_day');
                if (blotterDayElement) {
                    const blotterRect = blotterDayElement.getBoundingClientRect();
                    const tooltipRect = ENOKI_tooltip.getBoundingClientRect();

                    ENOKI_tooltip.style.left = `${blotterRect.left - tooltipRect.width - 5}px`;
                    ENOKI_tooltip.style.top = `${element.getBoundingClientRect().top + window.scrollY - 35}px`;
                }

                ENOKI_tooltip.style.opacity = 0;
                ENOKI_tooltip.style.display = 'block';
                setTimeout(() => {
                    ENOKI_tooltip.style.opacity = 1;
                }, 10);

                element.addEventListener('mouseleave', () => {
                    clearTimeout(HEN_OF_THE_WOODS_hideTimer);
                    HEN_OF_THE_WOODS_hideTimer = setTimeout(() => {
                        ENOKI_tooltip.style.opacity = 0;
                        setTimeout(() => {
                            ENOKI_tooltip.style.display = 'none';
                        }, 300);
                    }, 200);
                }, {
                    once: true
                });

                element.addEventListener('mouseover', () => {
                    clearTimeout(HEN_OF_THE_WOODS_hideTimer);
                });
            }

            function observeNewElements() {
                const observer = new MutationObserver((mutations) => {
                    mutations.forEach(mutation => {
                        if (mutation.type === 'childList') {
                            collectAndFetchAppIds();
                        }
                    });
                });

                observer.observe(document.body, {
                    childList: true,
                    subtree: true
                });
            }

            function initialize() {
                setTimeout(() => {
                    collectAndFetchAppIds();
                    observeNewElements();
                    document.addEventListener('mouseover', handleHover);
                }, CHANTERELLE_WAIT_TIME);
            }

            initialize();

            const style = document.createElement('style');
            style.innerHTML = `
            .mushroom-tooltip {
                position: absolute;
                background: linear-gradient(to bottom, #e3eaef, #c7d5e0);
                color: #30455a;
                padding: 12px;
                border-radius: 0px;
                box-shadow: 0 0 12px #000;
                font-size: 12px;
                max-width: 300px;
                display: none;
                z-index: 1000;
                opacity: 0;
                transition: opacity 0.4s ease-in-out;
            }
            .tooltip-arrow {
                position: absolute;
                right: -9px;
                top: 32px;
                width: 0;
                height: 0;
                border-top: 10px solid transparent;
                border-bottom: 10px solid transparent;
                border-left: 10px solid #E1E8ED;
            }
            .mushroom-positive {
                color: #2B80E9;
            }
            .mushroom-mixed {
                color: #997a00;
            }
            .mushroom-negative {
                color: #E53E3E;
            }
            .mushroom-no-reviews {
                color: #929396;
            }
            .mushroom-language-yes {
                color: #2B80E9;
            }
            .mushroom-language-no {
                color: #E53E3E;
            }
            .mushroom-early-access-yes {
                color: #2B80E9;
            }
            .mushroom-early-access-no {
                color: #929396;
            }
        `;
            document.head.appendChild(style);
        })();
    }

    // Скрипт для страницы игры (ZOG; получение сведений о наличии русификаторов) | https://store.steampowered.com/app/*
    if (window.location.pathname.includes('/app/') && scriptsConfig.zogInfo) {
        (async function() {
            const ZOG_CACHE_KEY = 'ZoGRusekiEdrit';
            const ZOG_DATA_URL = 'https://gist.githubusercontent.com/0wn3dg0d/7baa8d9f42b0304fe303e903d44d2ada/raw/zogrusbase.json';

            const zogBlock = document.createElement('div');
            Object.assign(zogBlock.style, {
                position: 'absolute',
                left: '334px',
                width: '30px',
                height: '30px',
                background: 'rgba(27, 40, 56, 0.95)',
                padding: '15px',
                borderRadius: '4px',
                border: '1px solid #3c3c3c',
                boxShadow: '0 0 10px rgba(0, 0, 0, 0.5)',
                zIndex: '2',
                fontFamily: 'Arial, sans-serif',
                overflow: 'hidden',
                transition: 'all 0.3s ease'
            });

            let hltbBlock = null;
            let hltbObserver = null;
            let zogMap = null;
            let zogNameMap = null;

            const updatePosition = () => {
                if (hltbBlock) {
                    zogBlock.style.top = `${hltbBlock.offsetTop + hltbBlock.offsetHeight + 16}px`;
                } else {
                    const defaultBlock = document.querySelector('#gameHeaderImageCtn > div[style*="background: rgba(0, 0, 0, 0.1)"]');
                    zogBlock.style.top = defaultBlock ?
                        `${defaultBlock.offsetTop + defaultBlock.offsetHeight + 16}px` :
                        '232px';
                }
            };

            const initHltbObserver = () => {
                hltbBlock = document.querySelector('#gameHeaderImageCtn > div[style*="background: rgba(27, 40, 56, 0.95)"]');

                if (hltbBlock && !hltbObserver) {
                    hltbObserver = new ResizeObserver(updatePosition);
                    hltbObserver.observe(hltbBlock);

                    hltbBlock.addEventListener('transitionend', updatePosition);
                }
            };

            async function loadZogData() {
                const cached = GM_getValue(ZOG_CACHE_KEY);
                const lastUpdated = cached?.lastUpdated || '';

                try {
                    const metaResponse = await new Promise((resolve, reject) => {
                        GM_xmlhttpRequest({
                            method: 'GET',
                            url: 'https://api.github.com/gists/7baa8d9f42b0304fe303e903d44d2ada',
                            onload: resolve,
                            onerror: reject
                        });
                    });

                    const metaData = JSON.parse(metaResponse.responseText);

                    const newLastUpdated = metaData.updated_at;

                    if (newLastUpdated === lastUpdated) {
                        return cached.data;
                    }

                    const dataResponse = await new Promise((resolve, reject) => {
                        GM_xmlhttpRequest({
                            method: 'GET',
                            url: ZOG_DATA_URL,
                            onload: resolve,
                            onerror: reject
                        });
                    });

                    const newData = JSON.parse(dataResponse.responseText);

                    GM_setValue(ZOG_CACHE_KEY, {
                        lastUpdated: newLastUpdated,
                        data: newData,
                        timestamp: Date.now()
                    });

                    return newData;
                } catch (error) {
                    console.error('Ошибка загрузки данных ZOG:', error);
                    return cached?.data || [];
                }
            }

            async function initZogData() {
                try {
                    const data = await loadZogData();
                    zogMap = new Map(data.map(item => [item.app_id, item]));
                    zogNameMap = new Map(data.map(item => [
                        item.title
                        ?.normalize("NFD").replace(/[\u0300-\u036f]/g, "")
                        .replace(/[^a-zа-яё0-9 _'\-!]/gi, '')
                        .toLowerCase(),
                        item
                    ]));
                } catch (e) {
                    console.error('Ошибка инициализации данных ZOG:', e);
                    content.textContent = 'Ошибка загрузки базы';
                }
            }

            const title = document.createElement('div');
            Object.assign(title.style, {
                fontSize: '12px',
                fontWeight: 'bold',
                color: '#67c1f5',
                marginBottom: '10px',
                cursor: 'pointer'
            });
            title.textContent = 'ZOG';

            const content = document.createElement('div');
            Object.assign(content.style, {
                display: 'none',
                color: '#c6d4df',
                fontSize: '14px',
                maxWidth: '300px',
                overflowY: 'auto',
                whiteSpace: 'normal',
                lineHeight: '1.4',
                padding: '0 5px'
            });

            const arrow = createArrow();

            zogBlock.append(arrow, title, content);
            document.querySelector('#gameHeaderImageCtn').appendChild(zogBlock);

            initHltbObserver();
            updatePosition();
            await initZogData();

            title.onclick = () => toggleBlock(arrow);
            arrow.onclick = () => toggleBlock(arrow);

            async function toggleBlock(arrowElement) {
                if (content.style.display === 'none') {
                    await expandBlock(arrowElement);
                } else {
                    collapseBlock(arrowElement);
                }
            }

            async function expandBlock(arrowElement) {
                if (!zogMap || !zogNameMap) {
                    console.error('Данные ZOG не инициализированы');
                    return;
                }

                zogBlock.style.transition = 'width 0.3s ease, height 0.3s ease';
                zogBlock.style.width = '300px';
                zogBlock.style.height = '40px';

                arrowElement.style.transform = 'translateX(-50%) rotate(180deg)';
                await new Promise(resolve => setTimeout(resolve, 300));

                content.style.display = 'block';
                content.textContent = 'Ищем в базе...';

                await new Promise(resolve => requestAnimationFrame(resolve));

                const appId = getAppId();
                let entry = zogMap.get(appId);

                if (!entry) {
                    const gameName = getGameName()
                        .normalize("NFD").replace(/[\u0300-\u036f]/g, "")
                        .replace(/[^a-zа-яё0-9 _'\-!]/gi, '')
                        .toLowerCase();

                    content.textContent = 'Ищем углубленно...';
                    await new Promise(resolve => requestAnimationFrame(resolve));

                    entry = zogNameMap.get(gameName);

                    if (!entry && /[а-яё]/i.test(gameName)) {
                        content.textContent = 'Запрашиваем англ. название...';
                        await new Promise(resolve => requestAnimationFrame(resolve));

                        const steamApiUrl = `https://api.steampowered.com/IStoreBrowseService/GetItems/v1?input_json={"ids": [{"appid": ${appId}}], "context": {"language": "english", "country_code": "US", "steam_realm": 1}, "data_request": {"include_assets": true}}`;

                        try {
                            const steamResponse = await new Promise((resolve, reject) => {
                                GM_xmlhttpRequest({
                                    method: "GET",
                                    url: steamApiUrl,
                                    onload: resolve,
                                    onerror: reject
                                });
                            });

                            if (steamResponse.status === 200) {
                                const steamData = JSON.parse(steamResponse.responseText);
                                const englishName = steamData.response.store_items[0]?.name;

                                if (englishName) {
                                    const cleanEnglishName = englishName
                                        .normalize("NFD").replace(/[\u0300-\u036f]/g, "")
                                        .replace(/[^a-zа-яё0-9 _'\-!]/gi, '')
                                        .toLowerCase();

                                    content.textContent = 'Проверяем англ. название...';
                                    await new Promise(resolve => requestAnimationFrame(resolve));

                                    entry = zogNameMap.get(cleanEnglishName);

                                    if (!entry) {
                                        content.textContent = 'Проверяем возможные совпадения...';
                                        await new Promise(resolve => requestAnimationFrame(resolve));

                                        const possibleMatches = findPossibleMatches(cleanEnglishName, Array.from(zogNameMap.values()));
                                        if (possibleMatches.length > 0) {
                                            renderPossibleMatches(possibleMatches);
                                            zogBlock.style.height = `${content.scrollHeight + 30}px`;
                                            updatePosition();
                                            return;
                                        }
                                    }
                                }
                            }
                        } catch (error) {
                            console.error('Ошибка при запросе к Steam API:', error);
                        }
                    }
                }

                if (!entry) {
                    content.textContent = 'Проверяем возможные совпадения...';
                    await new Promise(resolve => requestAnimationFrame(resolve));
                    const possibleMatches = findPossibleMatches(getGameName(), Array.from(zogNameMap.values()));
                    if (possibleMatches.length > 0) {
                        renderPossibleMatches(possibleMatches);
                        zogBlock.style.height = `${content.scrollHeight + 30}px`;
                        updatePosition();
                        return;
                    }
                }

                renderContent(entry);
                zogBlock.style.height = `${content.scrollHeight + 30}px`;
                updatePosition();
            }

            function nextFrame() {
                return new Promise(resolve => requestAnimationFrame(resolve));
            }

            function collapseBlock(arrowElement) {
                zogBlock.style.transition = 'width 0.3s ease, height 0.3s ease';
                zogBlock.style.width = '30px';
                zogBlock.style.height = '30px';

                arrowElement.style.transform = 'translateX(-50%) rotate(0deg)';

                content.style.display = 'none';
                updatePosition();
            }

            function renderContent(entry) {
                content.innerHTML = '';

                if (!entry) {
                    content.textContent = 'Игра не найдена в базе ZOG';
                    return;
                }

                const titleLink = document.createElement('a');
                titleLink.href = `https://www.zoneofgames.ru/games/${entry.id}.html`;
                titleLink.target = '_blank';
                titleLink.textContent = entry.title || 'Без названия';
                titleLink.style.color = '#67c1f5';
                titleLink.style.wordBreak = 'break-word';
                content.appendChild(titleLink);

                const list = document.createElement('ul');
                list.style.paddingLeft = '15px';
                list.style.marginTop = '5px';
                list.style.marginBottom = '0';

                if (entry.localizations?.length > 0) {
                    entry.localizations.forEach(loc => {
                        const li = document.createElement('li');
                        li.style.marginBottom = '8px';

                        const link = document.createElement('a');
                        link.href = loc.link;
                        link.target = '_blank';
                        link.textContent = `${loc.name} ${loc.size || ''}`;
                        link.style.color = '#c6d4df';
                        link.style.wordBreak = 'break-word';
                        link.style.textDecoration = 'none';

                        li.appendChild(link);
                        list.appendChild(li);
                    });
                } else {
                    list.textContent = 'Русификаторы отсутствуют';
                    list.style.color = '#999';
                }

                content.appendChild(list);
            }

            function renderPossibleMatches(matches) {
                content.innerHTML = '';

                const title = document.createElement('div');
                title.textContent = 'Возможные совпадения:';
                title.style.color = '#67c1f5';
                title.style.marginBottom = '10px';
                content.appendChild(title);

                const list = document.createElement('ul');
                list.style.paddingLeft = '15px';
                list.style.marginTop = '5px';
                list.style.marginBottom = '0';

                matches.forEach(match => {
                    const li = document.createElement('li');
                    li.style.marginBottom = '8px';

                    const link = document.createElement('a');
                    link.href = `https://www.zoneofgames.ru/games/${match.id}.html`;
                    link.target = '_blank';
                    link.textContent = `${match.title} (${match.percentage}%)`;
                    link.style.color = '#c6d4df';
                    link.style.wordBreak = 'break-word';
                    link.style.textDecoration = 'none';
                    link.onclick = () => {
                        renderContent(match);
                        zogBlock.style.height = `${content.scrollHeight + 30}px`;
                        updatePosition();
                        return false;
                    };

                    li.appendChild(link);
                    list.appendChild(li);
                });

                const noMatch = document.createElement('li');
                noMatch.style.marginBottom = '8px';

                const noMatchLink = document.createElement('a');
                noMatchLink.href = '#';
                noMatchLink.textContent = 'Ничего не подходит';
                noMatchLink.style.color = '#c6d4df';
                noMatchLink.style.wordBreak = 'break-word';
                noMatchLink.style.textDecoration = 'none';
                noMatchLink.onclick = () => {
                    renderContent(null);
                    zogBlock.style.height = `${content.scrollHeight + 30}px`;
                    updatePosition();
                    return false;
                };

                noMatch.appendChild(noMatchLink);
                list.appendChild(noMatch);

                content.appendChild(list);
            }

            function findPossibleMatches(gameName, data) {
                const cleanGameName = gameName
                    .normalize("NFD").replace(/[\u0300-\u036f]/g, "")
                    .replace(/[^a-zа-яё0-9 _'\-!]/gi, '')
                    .toLowerCase();

                return data
                    .map(item => {
                        const cleanItemName = item.title
                            .normalize("NFD").replace(/[\u0300-\u036f]/g, "")
                            .replace(/[^a-zа-яё0-9 _'\-!]/gi, '')
                            .toLowerCase();

                        const similarity = calculateSimilarity(cleanGameName, cleanItemName);
                        const startsWith = cleanItemName.startsWith(cleanGameName);

                        return {
                            ...item,
                            percentage: similarity,
                            startsWith: startsWith
                        };
                    })
                    .filter(item => item.percentage > 50 || item.startsWith)
                    .sort((a, b) => {
                        if (a.startsWith && !b.startsWith) return -1;
                        if (!a.startsWith && b.startsWith) return 1;
                        return b.percentage - a.percentage;
                    })
                    .slice(0, 5);
            }

            function calculateSimilarity(str1, str2) {
                const len = Math.max(str1.length, str2.length);
                if (len === 0) return 100;
                const distance = levenshteinDistance(str1, str2);
                return Math.round(((len - distance) / len) * 100);
            }

            function levenshteinDistance(str1, str2) {
                const m = str1.length;
                const n = str2.length;
                const dp = Array.from({
                    length: m + 1
                }, () => Array(n + 1).fill(0));

                for (let i = 0; i <= m; i++) {
                    for (let j = 0; j <= n; j++) {
                        if (i === 0) {
                            dp[i][j] = j;
                        } else if (j === 0) {
                            dp[i][j] = i;
                        } else {
                            dp[i][j] = Math.min(
                                dp[i - 1][j - 1] + (str1[i - 1] === str2[j - 1] ? 0 : 1),
                                dp[i - 1][j] + 1,
                                dp[i][j - 1] + 1
                            );
                        }
                    }
                }

                return dp[m][n];
            }

            function createArrow() {
                const arrow = document.createElement('div');
                Object.assign(arrow.style, {
                    position: 'absolute',
                    bottom: '5px',
                    left: '50%',
                    width: '0',
                    height: '0',
                    borderLeft: '5px solid transparent',
                    borderRight: '5px solid transparent',
                    borderTop: '5px solid #67c1f5',
                    cursor: 'pointer',
                    transition: 'transform 0.3s ease',
                    transform: 'translateX(-50%)'
                });
                return arrow;
            }

            function getAppId() {
                return window.location.pathname.split('/')[2];
            }

            function getGameName() {
                return document.querySelector('.apphub_AppName').textContent
                    .normalize("NFD")
                    .replace(/[\u0300-\u036f]/g, "")
                    .replace(/[’]/g, "'")
                    .replace(/[^a-zA-Zа-яёА-ЯЁ0-9 _'\-!]/g, '')
                    .trim()
                    .toLowerCase();
            }
        })();
    }

    // Скрипт для получения уведомлений об изменении дат выхода игр из вашего списка желаемого Steam | https://steamcommunity.com/my/wishlist/
    if (scriptsConfig.wishlistTracker) {
        (function() {
            'use strict';

            const STORAGE_PREFIX = 'USE_Wishlist_';
            const STORAGE_KEYS = {
                NOTIFICATIONS: STORAGE_PREFIX + 'notifications',
                GAME_DATA: STORAGE_PREFIX + 'gameData',
                LAST_UPDATE: STORAGE_PREFIX + 'lastUpdate'
            };

            const BATCH_SIZE = 200;
            const MILLISECONDS_IN_HOUR = 60 * 60 * 1000;
            let notifications = GM_getValue(STORAGE_KEYS.NOTIFICATIONS, []);
            let isPanelOpen = false;

            GM_addStyle(`
            .wishlist-tracker-container {
				position: absolute;
				right: 180px;
				top: 6px;
				z-index: 999999;
			}

			.wishlist-tracker-button {
				color: #c6d4df;
				background: rgba(103, 193, 245, 0.1);
				padding: 7px 12px;
				border-radius: 2px;
				cursor: pointer;
				font-size: 13px;
				display: flex;
                align-items: center;
                gap: 4px;
				align-items: center;
				transition: all 0.2s ease;
			}

			.wishlist-tracker-button:hover {
				background: rgba(103, 193, 245, 0.2);
			}

			.notification-badge {
				background: #67c1f5;
				color: #1b2838;
				border-radius: 3px;
				padding: 3px 6px;
				font-size: 14px;
				font-weight: bold;
				margin-left: 8px;
				min-width: 20px;
				text-align: center;
				box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
			}

			.status-indicator {
			    background: #4a5562;
			    color: #c6d4df;
			    border-radius: 3px;
			    padding: 3px 6px;
			    font-size: 12px;
			    font-weight: bold;
			    margin-left: 5px;
			    min-width: 30px;
			    text-align: center;
			    transition: all 0.3s ease;
			    cursor: help;
			}

			    .status-ok { background: #4a5562; }
			    .status-warning { background: #4a5562; }
			    .status-alert1 { background: #665c3a; color: #ffd700; }
			    .status-alert2 { background: #804d4d; color: #ffb3b3; }
			    .status-critical { background: #e60000; color: #fff; }
			    .status-unknown { background: #1b2838; color: #8f98a0; }

			.wishlist-tracker-panel {
				position: fixed;
				right: 132px;
				top: 50px;
				background: #1b2838;
				border: 1px solid #67c1f5;
				width: 450px;
				max-height: 500px;
				overflow-y: auto;
				z-index: 9999;
				box-shadow: 0 0 15px rgba(0, 0, 0, 0.5);
				display: none;
			}

			.panel-header {
				padding: 15px;
				background: #171a21;
				display: flex;
				justify-content: space-between;
				align-items: center;
			}

			.panel-title {
				font-size: 17px;
				font-weight: 500;
				color: #67c1f5;
			}

            .panel-controls button {
                background: rgba(30, 45, 60, 0.7);
                border: none;
                color: #c6d4df;
                padding: 8px 14px;
                cursor: pointer;
                margin-left: 5px;
                border-radius: 2px;
                font-weight: 400;
                text-transform: uppercase;
                letter-spacing: 0.5px;
                box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
                transition: background 0.2s ease, box-shadow 0.2s ease;
            }

            .panel-controls button:hover {
                background: rgba(40, 60, 80, 0.9);
                box-shadow: 0 3px 6px rgba(0, 0, 0, 0.4);
            }

            .panel-controls button:active {
                background: rgba(30, 45, 60, 0.6);
                box-shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
            }

			.notification-item {
				padding: 15px;
				border-bottom: 1px solid #2a475e;
				position: relative;
				transition: opacity 0.3s;
			}

			.notification-content {
				display: flex;
				gap: 15px;
			}

			.notification-image {
				width: 80px;
				height: 45px;
				object-fit: cover;
			}

			.notification-text {
				flex-grow: 1;
				padding-right: 25px;
			}

			.notification-game-title {
				color: #66c0f4;
				font-weight: bold;
				text-decoration: none;
				display: block;
				margin-bottom: 5px;
			}

			.notification-date {
				font-size: 12px;
				color: #8f98a0;
			}

			.notification-dates {
				color: #c6d4df;
				font-size: 13px;
			}

			.unread {
				background: rgba(102, 192, 244, 0.15);
			}

			.notification-controls {
				position: absolute;
				right: 10px;
				top: 10px;
				display: flex;
				gap: 8px;
			}

			.notification-control {
				cursor: pointer;
				width: 18px;
				height: 18px;
				opacity: 0.7;
				transition: opacity 0.2s;
			}

			.notification-control:hover {
				opacity: 1;
			}

			.delete-btn {
			  width: 20px;
			  height: 20px;
			  display: flex;
			  align-items: center;
			  justify-content: center;
			  color: #6C7781;
			  font-size: 16px;
			  font-weight: bold;
			  line-height: 1;
			  border: none;
			  cursor: pointer;
			  transition: color 0.2s ease, transform 0.1s ease;
			}

			.delete-btn:hover {
			  color: #8F98A0;
			}

			.delete-btn:active {
			  color: #800000;
			  transform: scale(0.9);
			}

			.loading-indicator {
				color: #67c1f5;
				text-align: center;
				padding: 10px;
			}

			`);

            const envelopeIcons = {
                unread: `<svg width="20" height="16" viewBox="0 0 32 32" fill="#67c1f5" xmlns="http://www.w3.org/2000/svg">
        <path d="M16.015 18.861l-4.072-3.343-8.862 10.463h25.876l-8.863-10.567-4.079 3.447zM29.926 6.019h-27.815l13.908 11.698 13.907-11.698zM20.705 14.887l9.291 11.084v-18.952l-9.291 7.868zM2.004 7.019v18.952l9.291-11.084-9.291-7.868z"/>
    </svg>`,
                read: `<svg width="20" height="16" viewBox="0 0 32 32" fill="#8f98a0" xmlns="http://www.w3.org/2000/svg">
        <path d="M20.139 18.934l9.787-7.999-13.926-9.833-13.89 9.833 9.824 8.032 8.205-0.033zM12.36 19.936l-9.279 10.962h25.876l-9.363-10.9-7.234-0.062zM20.705 19.803l9.291 11.084v-18.952l-9.291 7.868zM2.004 11.935v18.952l9.291-11.084-9.291-7.868z"/>
    </svg>`
            };

            function createNotificationUI() {
                const container = $(`
    <div class="wishlist-tracker-container">
        <div class="wishlist-tracker-button">
            <span>Отслеживание вишлиста</span>
            <div class="status-indicator status-unknown">??</div>
            <div class="notification-badge">${getUnreadCount()}</div>
        </div>
                <div class="wishlist-tracker-panel">
                    <div class="panel-header">
                        <div class="panel-title">Уведомлений: (${notifications.length})</div>
                        <div class="panel-controls">
                            <button class="refresh-btn">⟳ Обновить</button>
                            <button class="clear-btn">× Очистить</button>
                        </div>
                    </div>
                </div>
            </div>
        `);

                const panel = container.find('.wishlist-tracker-panel');
                const button = container.find('.wishlist-tracker-button');

                button.click(function(e) {
                    e.stopPropagation();
                    togglePanel();
                });

                container.find('.refresh-btn').click((e) => {
                    e.stopPropagation();
                    updateData();
                });

                container.find('.clear-btn').click(() => {
                    notifications = [];
                    GM_setValue(STORAGE_KEYS.NOTIFICATIONS, notifications);
                    updateNotificationPanel();
                    updateBadge();
                });

                $('body').append(container);
                updateNotificationPanel();

                $(document).click(() => {
                    if (isPanelOpen) {
                        panel.hide();
                        isPanelOpen = false;
                    }
                });
            }

            function showLoadingIndicator() {
                const panel = $('.wishlist-tracker-panel');
                panel.find('.loading-indicator').remove();
                const loading = $(`<div class="loading-indicator">Обновление данных...</div>`);
                panel.append(loading);
            }

            function togglePanel() {
                updateStatusIndicator();
                const panel = $('.wishlist-tracker-panel');
                panel.toggle();
                isPanelOpen = !isPanelOpen;
                if (isPanelOpen) {
                    panel.css('display', 'block');
                }
            }

            function updateNotificationPanel() {
                const panel = $('.wishlist-tracker-panel');
                panel.find('.notification-item, .loading-indicator').remove();
                panel.find('.panel-title').text(`Уведомлений: (${notifications.length})`);

                notifications.slice(0, 5000).forEach((notification, index) => {
                    const item = $(`
            <div class="notification-item ${notification.read ? '' : 'unread'}">
                <div class="notification-controls">
                    <div class="toggle-read-btn notification-control">
                        ${notification.read ? envelopeIcons.read : envelopeIcons.unread}
                    </div>
                    <div class="delete-btn notification-control">X</div>
                </div>
                <div class="notification-content">
                    <a href="https://store.steampowered.com/app/${notification.appid}" target="_blank">
                        <img src="https://shared.cloudflare.steamstatic.com/store_item_assets/steam/apps/${notification.appid}/header.jpg"
                             class="notification-image">
                    </a>
                    <div class="notification-text">
                        <a href="https://store.steampowered.com/app/${notification.appid}"
                           class="notification-game-title" target="_blank">
                            ${notification.name}
                        </a>
                        <div class="notification-dates">
                            Дата выхода изменилась:<br>
                            <span class="old-date">${formatDate(notification.oldDate)}</span> →
                            <span class="new-date">${formatDate(notification.newDate)}</span>
                        </div>
                        <div class="notification-date">
                            Обнаружено: ${new Date(notification.timestamp).toLocaleString()}
                        </div>
                    </div>
                </div>
            </div>
        `);

                    item.find('.delete-btn').click((e) => {
                        e.stopPropagation();
                        notifications = notifications.filter((_, i) => i !== index);
                        GM_setValue(STORAGE_KEYS.NOTIFICATIONS, notifications);
                        item.fadeOut(300, () => {
                            updateNotificationPanel();
                            updateBadge();
                        });
                    });

                    item.find('.toggle-read-btn').click((e) => {
                        e.stopPropagation();
                        notifications[index].read = !notifications[index].read;
                        GM_setValue(STORAGE_KEYS.NOTIFICATIONS, notifications);
                        item.toggleClass('unread', !notifications[index].read);
                        item.find('.toggle-read-btn').html(notifications[index].read ? envelopeIcons.read : envelopeIcons.unread);
                        updateBadge();
                    });

                    panel.append(item);
                });
            }

            function formatDate(dateInfo) {
                if (!dateInfo || dateInfo.value === 'Не указана') return 'Не указано';

                const value = dateInfo.value;
                const displayType = dateInfo.displayType;

                if (typeof value === 'string' && isNaN(value)) {
                    return value;
                }

                const ts = formatTimestamp(value);
                const date = new Date(ts * 1000);

                const monthNames = ["январь", "февраль", "март", "апрель", "май", "июнь",
                    "июль", "август", "сентябрь", "октябрь", "ноябрь", "декабрь"
                ];
                const quarter = Math.floor(date.getMonth() / 3) + 1;

                if (displayType) {
                    switch (displayType) {
                        case 'date_month':
                            return `${monthNames[date.getMonth()]} ${date.getFullYear()}`;
                        case 'date_quarter':
                            return `Q${quarter} ${date.getFullYear()}`;
                        case 'date_year':
                            return `${date.getFullYear()}`;
                        case 'date_full':
                        default:
                            return date.toLocaleDateString('ru-RU', {
                                day: 'numeric',
                                month: 'long',
                                year: 'numeric'
                            });
                    }
                }

                return date.toLocaleDateString('ru-RU', {
                    day: 'numeric',
                    month: 'long',
                    year: 'numeric'
                });
            }

            function updateStatusIndicator() {
                const lastUpdate = GM_getValue(STORAGE_KEYS.LAST_UPDATE, 0);
                const hoursPassed = (Date.now() - lastUpdate) / MILLISECONDS_IN_HOUR;
                const indicator = $('.status-indicator');
                const days = Math.floor(hoursPassed / 24);
                const hours = Math.floor(hoursPassed % 24);

                indicator.attr('title', `Данные не обновлялись: ${days} д. и ${hours} ч.`);

                if (!lastUpdate) {
                    indicator.text('-').removeClass().addClass('status-indicator status-unknown');
                    return;
                }

                if (hoursPassed < 12) {
                    indicator.text('OK').removeClass().addClass('status-indicator status-ok');
                } else if (hoursPassed < 24) {
                    indicator.text('OK?').removeClass().addClass('status-indicator status-warning');
                } else if (hoursPassed < 48) {
                    indicator.text('!').removeClass().addClass('status-indicator status-alert1');
                } else if (hoursPassed < 72) {
                    indicator.text('!!').removeClass().addClass('status-indicator status-alert2');
                } else if (hoursPassed < 96) {
                    indicator.text('!!!').removeClass().addClass('status-indicator status-critical');
                } else {
                    indicator.text('???').removeClass().addClass('status-indicator status-critical');
                }
            }

            function updateBadge() {
                $('.notification-badge').text(getUnreadCount());
            }

            function getUnreadCount() {
                return notifications.filter(n => !n.read).length;
            }


            async function fetchWishlistAppIds() {
                return new Promise(resolve => {
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url: 'https://store.steampowered.com/dynamicstore/userdata/',
                        onload: function(response) {
                            const data = JSON.parse(response.responseText);
                            resolve(data.rgWishlist || []);
                        }
                    });
                });
            }

            async function fetchGameDetails(appIds) {
                const batches = [];
                for (let i = 0; i < appIds.length; i += BATCH_SIZE) {
                    batches.push(appIds.slice(i, i + BATCH_SIZE));
                }

                const allDetails = [];
                for (const batch of batches) {
                    const details = await fetchBatchDetails(batch);
                    allDetails.push(...details);
                    await new Promise(resolve => setTimeout(resolve, 1000));
                }

                return allDetails;
            }

            async function fetchBatchDetails(appIds) {
                const requestData = {
                    ids: appIds.map(appid => ({
                        appid
                    })),
                    context: {
                        language: 'russian',
                        country_code: 'RU',
                        steam_realm: 1
                    },
                    data_request: {
                        include_release: true,
                        include_basic_info: true
                    }
                };

                return new Promise(resolve => {
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url: `https://api.steampowered.com/IStoreBrowseService/GetItems/v1?input_json=${encodeURIComponent(JSON.stringify(requestData))}`,
                        onload: function(response) {
                            try {
                                const data = JSON.parse(response.responseText);
                                resolve(data.response?.store_items || []);
                            } catch (e) {
                                console.error('Error parsing response:', e);
                                resolve([]);
                            }
                        }
                    });
                });
            }

            function checkForChanges(currentData) {
                const previousData = GM_getValue(STORAGE_KEYS.GAME_DATA, {});
                const changes = [];

                currentData.forEach(game => {
                    const prevGame = previousData[game.appid];
                    const currentRelease = getReleaseInfo(game.release);
                    const prevRelease = prevGame ? getReleaseInfo(prevGame.rawRelease) : null;

                    if (prevGame && (
                            currentRelease.date !== prevRelease?.date ||
                            currentRelease.type !== prevRelease?.type ||
                            currentRelease.displayType !== prevRelease?.displayType
                        )) {
                        changes.push({
                            appid: game.appid,
                            name: game.name,
                            oldDate: {
                                value: prevRelease?.date || 'Не указана',
                                displayType: prevRelease?.displayType
                            },
                            newDate: {
                                value: currentRelease.date,
                                displayType: currentRelease.displayType
                            },
                            timestamp: Date.now(),
                            read: false
                        });
                    }
                });

                const newGameData = currentData.reduce((acc, game) => {
                    acc[game.appid] = {
                        name: game.name,
                        rawRelease: game.release,
                        releaseInfo: getReleaseInfo(game.release)
                    };
                    return acc;
                }, {});

                GM_setValue(STORAGE_KEYS.GAME_DATA, {
                    ...previousData,
                    ...newGameData
                });

                if (changes.length > 0) {
                    notifications = [...changes, ...notifications];
                    GM_setValue(STORAGE_KEYS.NOTIFICATIONS, notifications);
                    updateNotificationPanel();
                    updateBadge();
                }

                $('.wishlist-tracker-panel .loading-indicator').remove();
            }

            function getReleaseInfo(releaseData) {
                if (!releaseData) return {
                    date: 'Не указана',
                    type: 'unknown',
                    displayType: null
                };

                const displayType = releaseData.coming_soon_display || null;

                if (releaseData.steam_release_date) {
                    return {
                        date: releaseData.steam_release_date,
                        type: 'date',
                        displayType: displayType
                    };
                }

                if (releaseData.custom_release_date_message) {
                    return {
                        date: releaseData.custom_release_date_message,
                        type: 'custom',
                        displayType: null
                    };
                }

                return {
                    date: 'Не указана',
                    type: 'unknown',
                    displayType: null
                };
            }

            function formatTimestamp(ts) {
                if (!ts) return ts;
                if (typeof ts === 'string') {
                    if (/^\d{4}-\d{2}-\d{2}$/.test(ts)) {
                        return Math.floor(new Date(ts).getTime() / 1000);
                    }
                    return ts;
                }
                return typeof ts === 'number' ? ts : parseInt(ts);
            }

            async function updateData() {
                try {
                    showLoadingIndicator();
                    const indicator = $('.status-indicator');
                    indicator.text('...').removeClass().addClass('status-indicator status-unknown');
                    const appIds = await fetchWishlistAppIds();
                    const gameDetails = await fetchGameDetails(appIds);
                    checkForChanges(gameDetails);
                    GM_setValue(STORAGE_KEYS.LAST_UPDATE, Date.now());
                    updateStatusIndicator();
                } catch (e) {
                    console.error('Update error:', e);
                    showErrorIndicator();
                    updateStatusIndicator();
                } finally {
                    $('.wishlist-tracker-panel .loading-indicator').remove();
                }
            }

            function showErrorIndicator() {
                const panel = $('.wishlist-tracker-panel');
                const error = $(`
            <div class="notification-item" style="color: #ff4747;">
                Ошибка при обновлении данных
            </div>
        `);
                panel.prepend(error);
                setTimeout(() => error.remove(), 5000);
            }

            function initialize() {
                createNotificationUI();
                updateStatusIndicator();
            }

            $(document).ready(initialize);
        })();
    }

})();