Steam Currency Converter

Convert Steam prices between any currencies with commission support.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램을 설치해야 합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name             Steam Currency Converter
// @name:ru          Конвертер валют в Steam
// @version          0.8.9.5
// @namespace        https://store.steampowered.com/
// @description      Convert Steam prices between any currencies with commission support.
// @description:ru   Конвертирует цены Steam между любой валютой с поддержкой комиссии.
// @author           NeTan
// @license          MIT
// @match            https://store.steampowered.com/*
// @match            https://steamcommunity.com/*
// @match            https://checkout.steampowered.com/*
// @icon             https://icons.duckduckgo.com/ip3/steampowered.com.ico
// @grant            GM_xmlhttpRequest
// @grant            GM_getValue
// @grant            GM_setValue
// @grant            GM_deleteValue
// @grant            GM_addStyle
// @grant            GM_registerMenuCommand
// @connect          cdn.jsdelivr.net
// ==/UserScript==

// --- DEBUG LOGGER ---
// (чтобы включить подробный лог, измените false на true)
const DEBUG_MODE = false;
const log = (message, ...args) => {
    if (DEBUG_MODE) {
        console.log("SCC DEBUG:", message, ...args);
    }
};
const warn = (message, ...args) => {
    if (DEBUG_MODE) {
        console.warn("SCC WARN:", message, ...args);
    }
};
const error = (message, ...args) => {
    console.error("SCC ERROR:", message, ...args);
};
// --- END LOGGER ---

// Currency symbol to abbreviation mapping
const CURRENCY_SYMBOLS = {
    "₸": "KZT",
    "TL": "TRY",
    "€": "EUR",
    "£": "GBP",
    "ARS$": "ARS",
    "₴": "UAH",
    "$": "USD", // Примечание: $ используется для USD, CAD, AUD, MXN и др. USD является запасным вариантом.
    "₽": "RUB",
    "Br": "BYN",
    "฿": "THB",
    "zł": "PLN",
    "R$": "BRL",
    "¥": "JPY", // Примечание: JPY и CNY используют ¥. JPY более вероятен без префикса.
    "CNY ¥": "CNY",
    "₩": "KRW",
    "A$": "AUD",
    "CDN$": "CAD",
    "CHF": "CHF",
    "kr": "NOK", // Также используется DKK, SEK. NOK - базовый вариант.
    "MXN$": "MXN",
    "₹": "INR",
    "AED": "AED",
    "SAR": "SAR",
    "R": "ZAR",
    "CLP$": "CLP",
    "S/.": "PEN",
    "COL$": "COP",
    "$U": "UYU",
    "₡": "CRC",
    "₪": "ILS",
    "KWD": "KWD",
    "QR": "QAR",
    "HK$": "HKD",
    "NT$": "TWD",
    "S$": "SGD",
    "Rp": "IDR",
    "RM": "MYR",
    "₱": "PHP",
    "₫": "VND"
};
// Abbreviation to symbol mapping
const ABBREVIATION_SYMBOLS = {};
Object.entries(CURRENCY_SYMBOLS).forEach(([symbol, abbr]) => {
    // Не перезаписываем $ -> USD, если уже есть
    if (!ABBREVIATION_SYMBOLS[abbr]) {
        ABBREVIATION_SYMBOLS[abbr] = symbol;
    }
});
// Добавляем $ вручную для валют, которые могут его использовать
ABBREVIATION_SYMBOLS["USD"] = "$";
ABBREVIATION_SYMBOLS["CAD"] = "CDN$";
ABBREVIATION_SYMBOLS["AUD"] = "A$";
ABBREVIATION_SYMBOLS["MXN"] = "MXN$";

// Pre-sorted symbols for currency detection (longest first)
const SORTED_CURRENCY_SYMBOLS = Object.entries(CURRENCY_SYMBOLS)
    .sort((a, b) => b[0].length - a[0].length);

// Supported Steam currencies
const SUPPORTED_CURRENCIES = [
    // Основные (из старого списка)
    { id: 1, code: "USD", sign: "$", description: "United States Dollars" },
    { id: 5, code: "RUB", sign: "₽", description: "Russian Rouble" },
    { id: 17, code: "TRY", sign: "TL", description: "Turkish Lira" },
    { id: 18, code: "UAH", sign: "₴", description: "Ukrainian Hryvnia" },
    { id: 29, code: "THB", sign: "฿", description: "Thai Baht" },
    { id: 34, code: "ARS", sign: "ARS$", description: "Argentine Peso" },
    { id: 37, code: "KZT", sign: "₸", description: "Kazakhstani Tenge" },
    { id: 42, code: "BYN", sign: "Br", description: "Belarusian Ruble" },

    // Добавленные
    { id: 2, code: "GBP", sign: "£", description: "British Pound" },
    { id: 3, code: "EUR", sign: "€", description: "European Union Euro" },
    { id: 6, code: "PLN", sign: "zł", description: "Polish Złoty" },
    { id: 7, code: "BRL", sign: "R$", description: "Brazilian Real" },
    { id: 8, code: "JPY", sign: "¥", description: "Japanese Yen" },
    { id: 9, code: "NOK", sign: "kr", description: "Norwegian Krone" },
    { id: 10, code: "CAD", sign: "CDN$", description: "Canadian Dollar" },
    { id: 11, code: "AUD", sign: "A$", description: "Australian Dollar" },
    { id: 12, code: "CHF", sign: "CHF", description: "Swiss Franc" },
    { id: 19, code: "MXN", sign: "MXN$", description: "Mexican Peso" },
    { id: 20, code: "INR", sign: "₹", description: "Indian Rupee" },
    { id: 21, code: "CLP", sign: "CLP$", description: "Chilean Peso" },
    { id: 22, code: "PEN", sign: "S/.", description: "Peruvian Sol" },
    { id: 23, code: "KRW", sign: "₩", description: "South Korean Won" },
    { id: 24, code: "COP", sign: "COL$", description: "Colombian Peso" },
    { id: 25, code: "CNY", sign: "CNY ¥", description: "Chinese Yuan" },
    { id: 26, code: "ZAR", sign: "R", description: "South African Rand" },
    { id: 27, code: "AED", sign: "AED", description: "United Arab Emirates Dirham" },
    { id: 28, code: "SAR", sign: "SAR", description: "Saudi Riyal" },
    { id: 30, code: "UYU", sign: "$U", description: "Uruguayan Peso" },
    { id: 31, code: "CRC", sign: "₡", description: "Costa Rican Colón" },
    { id: 32, code: "ILS", sign: "₪", description: "Israeli New Shekel" },
    { id: 33, code: "KWD", sign: "KWD", description: "Kuwaiti Dinar" },
    { id: 35, code: "QAR", sign: "QR", description: "Qatari Riyal" },
    { id: 36, code: "HKD", sign: "HK$", description: "Hong Kong Dollar" },
    { id: 38, code: "TWD", sign: "NT$", description: "New Taiwan Dollar" },
    { id: 39, code: "SGD", sign: "S$", description: "Singapore Dollar" },
    { id: 40, code: "IDR", sign: "Rp", description: "Indonesian Rupiah" },
    { id: 41, code: "MYR", sign: "RM", description: "Malaysian Ringgit" },
    { id: 43, code: "PHP", sign: "₱", description: "Philippine Peso" },
    { id: 44, code: "VND", sign: "₫", description: "Vietnamese Đồng" }
];
// Currencies using COMMA as decimal separator
const COMMA_DECIMAL_CURRENCIES = [
    "KZT", "TRY", "EUR", "ARS", "UAH", "RUB", "BYN", "PLN", "BRL",
    "NOK", "CHF", "CLP", "PEN", "COP", "UYU", "CRC", "IDR", "VND"
];
const COMMA_DECIMAL_SET = new Set(COMMA_DECIMAL_CURRENCIES);

// Lookup maps for O(1) currency access
const CURRENCY_BY_ID = new Map(SUPPORTED_CURRENCIES.map(c => [c.id, c]));
const CURRENCY_BY_CODE = new Map(SUPPORTED_CURRENCIES.map(c => [c.code, c]));

// Application state
let activeCurrencyCode = null;
let activeCurrencySign = null;
let exchangeData = null;
let baseRateFromRub = null;
let targetCurrencies = {}; // { code: commission }
let suppressOriginal = false;
let userLanguage = 'ru';
let languageMode = 'auto';
let panelThemeMode = 'auto';
let userAddedSelectors = [];
let finalPriceSelectorString = "";
let conversionLogicStarted = false; // Флаг, что логика конвертации уже запущена
let targetCurrencyOrder = []; // Порядок отображения валют
let panelCurrencyOrder = []; // Временный порядок при редактировании в панели

// Storage identifiers
const RATE_EXPIRY_ID = "rate_expiry_v2";
const RATE_DATA_ID = "rate_data_v2";
const TARGETS_ID = "targets";
const SUPPRESS_ID = "suppress_original";
const LANG_ID = "user_lang";
const LANG_MODE_ID = "user_lang_mode_v1";
const THEME_MODE_ID = "ui_theme_mode_v1";
const USER_SELECTORS_ID = "user_selectors_v1";
const DISABLED_BASE_SELECTORS_ID = "disabled_base_selectors_v1";
const CURRENCY_ORDER_ID = "currency_order_v1";

// Network utilities
const fetchData = url => new Promise((resolve, reject) => {
    GM_xmlhttpRequest({
        method: "GET",
        url,
        headers: { "Content-Type": "application/json" },
        onload: response => resolve(response.responseText),
        onerror: reject,
    });
});
const refreshRates = async () => {
    const cacheBuster = Math.random();
    const endpoint = `https://cdn.jsdelivr.net/npm/@fawazahmed0/currency-api@latest/v1/currencies/rub.json?${cacheBuster}`;
    log("Refreshing rates from:", endpoint);
    const response = await fetchData(endpoint);
    const parsedRates = JSON.parse(response);
    const expiry = Date.now() + 6 * 60 * 60 * 1000; // 6 hours
    GM_setValue(RATE_EXPIRY_ID, expiry);
    GM_setValue(RATE_DATA_ID, parsedRates);
    log("Rates refreshed and stored.");
    return parsedRates;
};
const loadRates = async () => {
    if (exchangeData) return;
    const expiry = GM_getValue(RATE_EXPIRY_ID, null);
    const storedRates = GM_getValue(RATE_DATA_ID, null);
    const rateTimestamp = storedRates ? Date.parse(storedRates.date) : Date.now();
    const needsRefresh = !storedRates || !expiry || expiry <= Date.now() ||
        (rateTimestamp + 24 * 60 * 60 * 1000) <= Date.now();
    if (needsRefresh) {
        log("Rate cache expired or missing, fetching new rates...");
        exchangeData = await refreshRates();
    } else {
        log("Loading rates from cache.");
        exchangeData = storedRates;
    }
};
// Value formatting utilities
const renderValue = (value, code) => {
    const lowValue = value < 100;
    const adjustedValue = lowValue ?
        Math.ceil(value * 10) / 10 : Math.ceil(value);
    const region = code === "RUB" ?
        "ru-RU" : "en-US";
    const formatOptions = lowValue ? { maximumFractionDigits: 1 } : {};
    return adjustedValue.toLocaleString(region, formatOptions);
};

// String/number helpers
const escapeRegex = value => value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const normalizePriceString = (raw, useCommaDecimal) => {
    let cleaned = raw.replace(/USD/gi, '');
    cleaned = cleaned.replace(/[\s\u00A0]/g, '');
    if (useCommaDecimal) {
        cleaned = cleaned.replace(/\./g, "").replace(/,/g, ".");
    } else {
        cleaned = cleaned.replace(/,/g, "");
    }
    return cleaned;
};

const cleanupDiscountedPrice = element => {
    const crossedOut = element.querySelector("span > strike");
    if (!crossedOut) return;
    crossedOut.remove();
    element.classList.remove("discounted");
    element.style.color = "#BEEE11";
};

// Regex cache for price parsing
const PRICE_TAG_REGEX_CACHE = new Map();
const getPriceTagRegex = sign => {
    const cached = PRICE_TAG_REGEX_CACHE.get(sign);
    if (cached) return cached;
    const escapedSign = escapeRegex(sign);
    const regex = new RegExp(
        `(<[a-z]+[^>]*>)?` +
        `(\\s*` +
            `(${escapedSign}\\s*[\\d.,]+(?:\\s*USD)?|[\\d.,\\s\u00A0]+\\s*${escapedSign})` +
        `\\s*)` +
        `(<\\/[a-z]+>)?`,
        'gi'
    );
    PRICE_TAG_REGEX_CACHE.set(sign, regex);
    return regex;
};

// *** ИСПРАВЛЕННАЯ ФУНКЦИЯ: Price modification routine (v1.0.15) ***
const applyConversion = element => {
    const classList = element.classList;

    // --- ИСПРАВЛЕНИЕ v0.8.15: Предотвращение бесконечного цикла React ---
    // Если React перезаписывает наш .done, проверяем, не содержит ли элемент уже нашу конвертацию.
    const elementText = element.textContent || "";
    if (classList.contains("done") || elementText.includes("≈")) {
        return;
    }
    // --- КОНЕЦ ИСПРАВЛЕНИЯ v0.8.15 ---

    const isWalletBalance = element.id === "header_wallet_balance";

    // Ранние проверки на выход
    if (!activeCurrencySign ||
        // Проверяем наличие символа валюты, только если это НЕ баланс кошелька
        (!isWalletBalance && !elementText.includes(activeCurrencySign)) ||
        classList.contains("es-regprice") || classList.contains("es-converted") ||
        classList.contains("discount_original_price")) {
        classList.add("done"); // Помечаем, чтобы не проверять снова
        return;
    }

    // Порядок валют: сначала по сохранённому порядку, затем остальные
    const orderedCodes = [];
    const seen = new Set();
    for (const code of targetCurrencyOrder) {
        if (code in targetCurrencies) { orderedCodes.push(code); seen.add(code); }
    }
    for (const code of Object.keys(targetCurrencies)) {
        if (!seen.has(code)) orderedCodes.push(code);
    }
    const targetEntries = orderedCodes.map(code => [code, targetCurrencies[code]]);
    if (targetEntries.length === 0 || !baseRateFromRub || !exchangeData) {
        if (DEBUG_MODE) {
            warn("Bailed applyConversion. Reason: missing targets, rate, or data.", {
                targets: targetEntries.length,
                baseRate: baseRateFromRub,
                data: !!exchangeData,
                element: element.cloneNode(true)
            });
        }
        return;
    }

    classList.add("done");
    if (DEBUG_MODE) {
        log("applyConversion running on element:", element.cloneNode(true));
    }

    // Предварительная чистка (для зачеркнутых цен)
    cleanupDiscountedPrice(element);

    let originalHTML = element.innerHTML;
    const escapedSign = escapeRegex(activeCurrencySign);
    const useCommaDecimal = COMMA_DECIMAL_SET.has(activeCurrencyCode);
    const priceTagPattern = getPriceTagRegex(activeCurrencySign);


    let replaced = false;

    const newHTML = originalHTML.replace(priceTagPattern, (match, openTag, priceTextWithSign, innerPrice, closeTag) => {
        log(`Match found: '${match}'`);

        const rawPriceString = priceTextWithSign.trim(); // e.g., "$1.79 USD" или "26 155,13₸"
        let cleanedText = rawPriceString.replace(new RegExp(escapedSign, 'g'), '');
        cleanedText = normalizePriceString(cleanedText, useCommaDecimal);

        const originalValue = parseFloat(cleanedText);

        log(`Parsed Value: ${originalValue} (from '${cleanedText}')`);

        if (isNaN(originalValue)) {
            warn("Parsing failed (NaN) for:", cleanedText, "from:", rawPriceString);
            return match; // Возвращаем оригинальное совпадение, если парсинг не удался
        }

        const rubEquivalent = originalValue / baseRateFromRub;
        log(`RUB Equivalent: ${rubEquivalent} (${originalValue} / ${baseRateFromRub})`);

        const currentConvertedDisplays = [];

        for (const [targetCode, fee] of targetEntries) {
            if (targetCode === activeCurrencyCode) {
                log(`Skipping conversion: Target (${targetCode}) is same as Active (${activeCurrencyCode})`);
                continue;
            }

            let rateToTarget = exchangeData.rub[targetCode.toLowerCase()];
            if (rateToTarget === undefined) {
                if (targetCode.toLowerCase() === 'rub') {
                    rateToTarget = 1;
                } else {
                    warn(`No rate found for target currency: ${targetCode}`);
                    continue;
                }
            }
            const convertedValue = rubEquivalent * rateToTarget * (1 + fee / 100);
            const displayedValue = renderValue(convertedValue, targetCode);
            const sign = getCurrencySign(targetCode);
            currentConvertedDisplays.push(`${displayedValue}${sign}`);
        }

        if (currentConvertedDisplays.length === 0) {
            warn("No converted displays generated (all targets might be same as active or no rates found).");
            if (!suppressOriginal) {
                 return match;
            }
        }

        replaced = true;
        const newPrice = currentConvertedDisplays.join(" ≈ ");
        log(`Converted Strings: ${newPrice}`);

        // Используем захваченные теги или пустую строку, чтобы сохранить форматирование
        const tagToUse = openTag ? openTag : '';
        const closingTagToUse = closeTag ? closeTag : '';

        let contentToInsert;

        if (suppressOriginal) {
            contentToInsert = (newPrice.length > 0) ? newPrice : "";
        } else {
            // Используем 'innerPrice' (Group 3), который содержит только цену (напр. $1.79 USD или 26 155,13₸)
            const displayPrice = innerPrice.trim();
            contentToInsert = (newPrice.length > 0) ? `${displayPrice} ≈ ${newPrice}` : displayPrice;
        }

        return `${tagToUse}${contentToInsert}${closingTagToUse}`;
    });

    if (replaced) {
        if (DEBUG_MODE) {
            log("Replacing HTML for element:", element);
        }
        element.innerHTML = newHTML;
    }
};

// *** ИСПРАВЛЕННАЯ ФУНКЦИЯ: Currency identification (v1.0.14 fix) ***
const identifyActiveCurrency = () => {
    if (activeCurrencyCode) return; // Не запускать дважды

    // 1. GStoreItemData (Надежно, если есть)
    try {
        const formattedSample = GStoreItemData.fnFormatCurrency(12345); // "$123.45 USD" or "123,45 ₸"

        if (formattedSample.includes('USD')) {
            activeCurrencyCode = "USD";
            activeCurrencySign = "$"; // Используем базовый символ $
            log(`Currency via GStoreItemData (USD Fix): ${activeCurrencyCode} (${activeCurrencySign})`);
            return;
        }

        const cleanedSample = formattedSample
            .replace("123,45", "").replace("123.45", "").trim(); // "₸"

        activeCurrencyCode = CURRENCY_SYMBOLS[cleanedSample];
        activeCurrencySign = cleanedSample;

        if (activeCurrencyCode && !activeCurrencySign) {
             activeCurrencySign = ABBREVIATION_SYMBOLS[activeCurrencyCode];
        }

        log(`Currency via GStoreItemData (Legacy): ${activeCurrencyCode} (${activeCurrencySign})`);
        return;
    } catch (err) {}

    // 2. Wallet Info (Надежно, если залогинен)
    try {
        const walletCode = getCurrencyById(g_rgWalletInfo?.wallet_currency);
        if (walletCode) {
            activeCurrencyCode = walletCode.code;
            activeCurrencySign = walletCode.sign;
            log(`Currency via Wallet Info: ${activeCurrencyCode} (${activeCurrencySign})`);
            return;
        }
    } catch (err) {}

    // 3. Meta tag (Надежный, но не всегда есть)
    const priceMeta = document.querySelector('meta[itemprop="priceCurrency"]');
    if (priceMeta?.content) {
        activeCurrencyCode = priceMeta.content;
        activeCurrencySign = ABBREVIATION_SYMBOLS[activeCurrencyCode];
        if (!activeCurrencySign) {
            warn(`Found currency code ${activeCurrencyCode} from meta, but no matching SIGN in ABBREVIATION_SYMBOLS.`);
            const curr = getCurrencyByCode(activeCurrencyCode);
            if (curr) activeCurrencySign = curr.sign;
        }
        log(`Currency via Meta Tag: ${activeCurrencyCode} (${activeCurrencySign})`);
        return;
    }


    // 4. Page Scrape (Резервный метод для динамических страниц)
    log("Using Page Scrape fallback to find currency...");
    const sortedSymbols = SORTED_CURRENCY_SYMBOLS;

    // Сканируем *только* селекторы цен, а не все div
    const candidateElements = document.querySelectorAll(BASE_PRICE_TARGETS.join(','));
    if (candidateElements.length === 0) {
        log("Page Scrape: No price elements found yet.");
        return; // Не найдено, выходим (повторный запуск будет из observe)
    }

    for (const [sign, code] of sortedSymbols) {
        for (const candidate of candidateElements) {
            if (sign === "$" && candidate.innerText.includes("USD")) {
                 activeCurrencySign = "$";
                 activeCurrencyCode = "USD";
                 log(`Currency via Page Scrape (USD Fix): ${activeCurrencyCode} (${activeCurrencySign})`);
                 return;
            }

            if (candidate.innerText.includes(sign)) {
                activeCurrencySign = sign;
                activeCurrencyCode = code;
                log(`Currency via Page Scrape (Legacy): ${activeCurrencyCode} (${activeCurrencySign})`);
                return;
            }
        }
    }

    warn("Could not identify active currency.");
};

// *** ИСПРАВЛЕННЫЙ СПИСОК: Price element selectors (v0.8.15) ***
const BASE_PRICE_TARGETS = [
    "#header_wallet_balance",
    "div[class*=StoreSalePriceBox]",
    "div[class*='StoreSalePriceWidget_']",
    "div[class*='StoreOriginalPrice_']",
    ".game_purchase_price",
    // --- Селекторы Списка желаемого ---
    "div._3j4dI1yA7cRfCvK8h406OB",
    "div.DOnsaVcV0Is-",
    "div._79DIT7RUQ5g-",
    // --- Селекторы Корзины (Новые) ---
    "._2WLaY5TxjBGVyuWe_6KS3N", // Итого (total)
    ".k2r-13oII_503_1b0Bf",     // Подытог (subtotal)
    "._38-m1g-YVkf-nCAcHJ1NbP", // Цена элемента (item price)
    // ---
    ".steamdb_prices_top",
    ".discount_final_price:not(:has(> .your_price_label))",
    ".discount_final_price > div:not([class])",
    ".search_price",
    ".price:not(.spotlight_body):not(.similar_grid_price)",
    ".match_subtitle",
    ".game_area_dlc_price:not(:has(> *))",
    ".savings.bundle_savings",
    ".wallet_column",
    ".wht_total",
    ".normal_price:not(.market_table_value)",
    ".sale_price",
    ".StoreSalePriceWidgetContainer:not(.Discounted) div",
    ".StoreSalePriceWidgetContainer.Discounted div:nth-child(2) > div:nth-child(2)",
    "#marketWalletBalanceAmount",
    ".market_commodity_order_summary > span:nth-child(2)",
    ".market_commodity_orders_table tr > td:first-child",
    ".market_listing_price_with_fee",
    ".market_activity_price",
    ".item_market_actions > div > div:nth-child(2)",
    ".wishlist_row .discount_final_price",
    ".wishlist_row .discount_original_price",
    ".wishlist_row .normal_price",
    ".wishlist_row .price",
    ".ws_row_bold .price",
    "[class*='wishlist'] .price",
    ".wishlist_row div",
];
// Price application handler
const processPrices = elements => {
    if (!elements || elements.length === 0) return;
    for (const elem of elements) {
        applyConversion(elem);
    }
};

// DOM mutation watcher
const initializeWatcher = () => {
    log("Initializing MutationObserver...");
    const pendingNodes = new Set();
    let scheduled = false;

    const scheduleProcess = () => {
        if (scheduled) return;
        scheduled = true;
        const run = () => {
            scheduled = false;
            if (pendingNodes.size === 0) return;
            const nodes = Array.from(pendingNodes);
            pendingNodes.clear();

            // Повторное определение, если валюта не найдена
            if (!activeCurrencyCode) {
                log("Watcher trying to find active currency...");
                identifyActiveCurrency(); // Повторный вызов
                if (activeCurrencyCode) {
                    // Валюта найдена, запускаем логику конвертации
                    startConversionLogic();
                } else {
                    return;
                }
            }

            const foundPriceNodes = new Set();
            for (const added of nodes) {
                if (added.nodeType !== Node.ELEMENT_NODE) continue;
                if (added.matches && added.matches(finalPriceSelectorString)) {
                    foundPriceNodes.add(added);
                }
                if (added.querySelectorAll) {
                    const matches = added.querySelectorAll(finalPriceSelectorString);
                    for (const match of matches) {
                        foundPriceNodes.add(match);
                    }
                }
            }
            if (foundPriceNodes.size > 0) {
                processPrices(Array.from(foundPriceNodes));
            }
        };
        if (typeof requestAnimationFrame === "function") {
            requestAnimationFrame(run);
        } else {
            setTimeout(run, 0);
        }
    };

    const watcher = new MutationObserver(mutations => {
        try {
            for (const change of mutations) {
                if (!change.addedNodes || change.addedNodes.length === 0) continue;
                for (const node of change.addedNodes) {
                    pendingNodes.add(node);
                }
            }
            if (pendingNodes.size > 0) {
                scheduleProcess();
            }
        } catch (err) {
            error("Error in MutationObserver:", err);
        }
    });
    watcher.observe(document.body, { childList: true, subtree: true });
};

// CSS selector generation
const generateSelectorForElement = (el) => {
    if ((!el.classList || el.classList.length === 0) && el.parentElement) {
        el = el.parentElement;
    }
    if (!el || el === document.body) return null;
    if (el.id) {
        const selector = `#${el.id}`;
        if (document.querySelectorAll(selector).length === 1) {
            return selector;
        }
    }
    const classes = Array.from(el.classList).filter(c => c !== 'done');
    if (classes.length > 0) {
        return `${el.tagName.toLowerCase()}.${classes.join('.')}`;
    }
    if (el.parentElement && el.parentElement !== document.body) {
        const parentClasses = Array.from(el.parentElement.classList).filter(c => c !== 'done');
        if (parentClasses.length > 0) {
            return `${el.parentElement.tagName.toLowerCase()}.${parentClasses.join('.')} > ${el.tagName.toLowerCase()}`;
        }
    }
    return null;
};
// Add selector by click
const findAndAddSelector = (callbacks = {}) => {
    const { onStatus, onDone } = callbacks;
    if (typeof onStatus === "function") {
        onStatus(getSettingsText("addMissingInstructions"));
    }
    const clickHandler = (e) => {
        e.preventDefault();
        e.stopPropagation();
        document.body.removeEventListener('click', clickHandler, true);
        const targetElement = e.target;
        const targetText = targetElement?.textContent || "";
        if (!activeCurrencySign || !targetText.includes(activeCurrencySign)) {
            error("Invalid click", { activeSign: activeCurrencySign, text: targetText });
            if (typeof onStatus === "function") {
                onStatus(getSettingsText("invalidPriceClick"));
            }
            if (typeof onDone === "function") onDone(false);
            return;
        }
        const newSelector = generateSelectorForElement(targetElement);
        if (newSelector) {
            let currentSelectors = GM_getValue(USER_SELECTORS_ID, []);
            if (currentSelectors.includes(newSelector)) {
                if (typeof onStatus === "function") {
                    onStatus(getSettingsText("selectorExists"));
                }
                if (typeof onDone === "function") onDone(false);
                return;
            }
            currentSelectors.push(newSelector);
            GM_setValue(USER_SELECTORS_ID, currentSelectors);
            userAddedSelectors = currentSelectors;
            buildFinalSelectorString();
            if (typeof onStatus === "function") {
                onStatus(getSettingsText("addedSelector", newSelector));
            }
            if (typeof onDone === "function") onDone(true);
        } else {
            if (typeof onStatus === "function") {
                onStatus(getSettingsText("priceNotFound"));
            }
            if (typeof onDone === "function") onDone(false);
        }
    };
    document.body.addEventListener('click', clickHandler, true);
};

const SETTINGS_PANEL_ID = "scc-settings-panel";
const SETTINGS_BACKDROP_ID = "scc-settings-backdrop";

const SETTINGS_I18N = {
    ru: {
        menuSettings: "Настройки",
        panelTitle: "Steam Currency Converter - Настройки",
        actions: "Действия",
        runNow: "Запустить вручную",
        addByClick: "Добавить селектор кликом",
        export: "Экспорт",
        import: "Импорт",
        configuration: "Конфигурация",
        language: "Язык",
        languageAuto: "Auto",
        themeToggleTitle: "Тема панели",
        themeToggleAuto: "Авто",
        themeToggleLight: "Светлая",
        themeToggleDark: "Темная",
        currencyColumn: "Валюта",
        feeColumn: "Комиссия",
        hideOriginal: "Скрывать оригинальную цену",
        targetCurrencies: "Целевые валюты",
        customSelectors: "Пользовательские селекторы",
        add: "Добавить",
        baseSelectorToggles: "Базовые селекторы (по умолчанию ВКЛ)",
        save: "Сохранить",
        remove: "Удалить",
        noSelectors: "Нет добавленных селекторов.",
        selectorPlaceholder: ".wishlist_row .price",
        removedSelector: selector => `Удален селектор: ${selector}`,
        runningManual: "Запускаю конвертацию вручную...",
        manualDone: "Ручной запуск завершен.",
        selectorExists: "Селектор уже существует.",
        addedSelector: selector => `Добавлен селектор: ${selector}`,
        exported: "Настройки экспортированы.",
        imported: "Настройки импортированы.",
        importFailed: err => `Ошибка импорта: ${err}`,
        savedReloading: "Сохранено. Перезагрузка страницы...",
        tipActions: "Быстрые действия: ручной запуск, добавление селектора и перенос настроек.",
        tipConfig: "Основные параметры отображения и конвертации цен.",
        tipTargets: "Включите нужные валюты, задайте комиссию в процентах.",
        tipSelectors: "Добавляйте селекторы, если на странице пропускаются цены.",
        tipSave: "Сохраняет изменения и перезагружает страницу.",
        displayOrder: "Порядок отображения",
        tipOrder: "Используйте стрелки для изменения порядка конвертированных цен.",
        orderEmpty: "Включите валюты выше.",
        addMissingInstructions: "Нажмите на неконвертированную цену. Страница перезагрузится.",
        invalidPriceClick: "Выбранный элемент не содержит цену с символом активной валюты.",
        priceNotFound: "Не удалось создать полезный селектор для этого элемента."
    },
    en: {
        menuSettings: "Settings",
        panelTitle: "Steam Currency Converter - Settings",
        actions: "Actions",
        runNow: "Run manually",
        addByClick: "Add selector by click",
        export: "Export",
        import: "Import",
        configuration: "Configuration",
        language: "Language",
        languageAuto: "Auto",
        themeToggleTitle: "Panel theme",
        themeToggleAuto: "Auto",
        themeToggleLight: "Light",
        themeToggleDark: "Dark",
        currencyColumn: "Currency",
        feeColumn: "Fee",
        hideOriginal: "Hide original price",
        targetCurrencies: "Target currencies",
        customSelectors: "Custom selectors",
        add: "Add",
        baseSelectorToggles: "Base selector toggles (default ON)",
        save: "Save",
        remove: "Remove",
        noSelectors: "No custom selectors added.",
        selectorPlaceholder: ".wishlist_row .price",
        removedSelector: selector => `Removed selector: ${selector}`,
        runningManual: "Running conversion manually...",
        manualDone: "Manual run finished.",
        selectorExists: "Selector already exists.",
        addedSelector: selector => `Added selector: ${selector}`,
        exported: "Settings exported.",
        imported: "Settings imported.",
        importFailed: err => `Import failed: ${err}`,
        savedReloading: "Saved. Reloading page...",
        tipActions: "Quick actions: manual run, selector capture, import/export settings.",
        tipConfig: "Main parameters for displaying and converting prices.",
        tipTargets: "Enable target currencies and set fee percent.",
        tipSelectors: "Add selectors when some prices are not detected.",
        tipSave: "Saves changes and reloads the page.",
        displayOrder: "Display order",
        tipOrder: "Use arrows to change display order of converted prices.",
        orderEmpty: "Enable currencies above.",
        addMissingInstructions: "Click on the unconverted price. The page will reload.",
        invalidPriceClick: "The selected element does not contain a price with the active currency symbol.",
        priceNotFound: "Could not generate a useful selector for this element."
    }
};

const detectBrowserLanguage = () => {
    const navLanguage = (navigator.language || navigator.userLanguage || "en").toLowerCase();
    return navLanguage.startsWith("ru") ? "ru" : "en";
};

const parseCssColorToRgb = (value) => {
    if (!value) return null;
    const normalized = value.trim().toLowerCase();
    if (normalized === "transparent" || normalized === "rgba(0, 0, 0, 0)") return null;

    const rgbMatch = normalized.match(/^rgba?\(([^)]+)\)$/);
    if (rgbMatch) {
        const parts = rgbMatch[1].split(",").map(part => parseFloat(part.trim()));
        if (parts.length >= 3 && parts.slice(0, 3).every(Number.isFinite)) {
            return { r: parts[0], g: parts[1], b: parts[2] };
        }
        return null;
    }

    const hexMatch = normalized.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
    if (hexMatch) {
        const hex = hexMatch[1];
        if (hex.length === 3) {
            return {
                r: parseInt(hex[0] + hex[0], 16),
                g: parseInt(hex[1] + hex[1], 16),
                b: parseInt(hex[2] + hex[2], 16)
            };
        }
        return {
            r: parseInt(hex.slice(0, 2), 16),
            g: parseInt(hex.slice(2, 4), 16),
            b: parseInt(hex.slice(4, 6), 16)
        };
    }

    return null;
};

const getColorLuminance = (rgb) => {
    if (!rgb) return 1;
    return (0.2126 * rgb.r + 0.7152 * rgb.g + 0.0722 * rgb.b) / 255;
};

const isColorDark = (colorValue) => {
    const rgb = parseCssColorToRgb(colorValue);
    if (!rgb) return false;
    return getColorLuminance(rgb) < 0.45;
};

const detectPageDarkTheme = () => {
    const html = document.documentElement;
    const body = document.body;
    if (!html || !body) return false;

    const classMarkers = ["night-theme", "dark", "theme-dark", "dark-mode"];
    for (const marker of classMarkers) {
        if (html.classList.contains(marker) || body.classList.contains(marker)) {
            return true;
        }
    }

    const attrMarkers = ["data-theme", "theme", "color-theme"];
    for (const attr of attrMarkers) {
        const htmlTheme = (html.getAttribute(attr) || "").toLowerCase();
        const bodyTheme = (body.getAttribute(attr) || "").toLowerCase();
        if (htmlTheme.includes("dark") || bodyTheme.includes("dark")) {
            return true;
        }
    }

    const metaThemeColor = document.querySelector('meta[name="theme-color"]');
    if (metaThemeColor && isColorDark(metaThemeColor.getAttribute("content"))) {
        return true;
    }

    const probeSelectors = [
        "body",
        "html",
        "#responsive_page_frame",
        "#global_header",
        ".responsive_page_frame",
        ".responsive_page_content"
    ];
    let darkVotes = 0;
    let checked = 0;
    for (const selector of probeSelectors) {
        const node = selector === "body" ? body : (selector === "html" ? html : document.querySelector(selector));
        if (!node) continue;
        const bg = window.getComputedStyle(node).backgroundColor;
        const rgb = parseCssColorToRgb(bg);
        if (!rgb) continue;
        checked += 1;
        if (getColorLuminance(rgb) < 0.45) {
            darkVotes += 1;
        }
    }

    if (checked === 0) return false;
    return darkVotes >= Math.ceil(checked / 2);
};

const resolveLanguage = (mode) => {
    if (mode === "ru" || mode === "en") return mode;
    return detectBrowserLanguage();
};

const getSettingsText = (key, ...args) => {
    const dict = SETTINGS_I18N[userLanguage] || SETTINGS_I18N.en;
    const value = dict[key];
    if (typeof value === "function") return value(...args);
    return value || key;
};

const loadLanguagePreference = () => {
    const storedMode = GM_getValue(LANG_MODE_ID, null);
    if (storedMode === "auto" || storedMode === "ru" || storedMode === "en") {
        languageMode = storedMode;
    } else {
        const legacyLang = GM_getValue(LANG_ID, null);
        languageMode = legacyLang === "ru" || legacyLang === "en" ? legacyLang : "auto";
        GM_setValue(LANG_MODE_ID, languageMode);
    }
    userLanguage = resolveLanguage(languageMode);
    GM_setValue(LANG_ID, userLanguage);
};

const loadThemePreference = () => {
    const storedThemeMode = GM_getValue(THEME_MODE_ID, "auto");
    panelThemeMode = ["auto", "light", "dark"].includes(storedThemeMode) ? storedThemeMode : "auto";
};

const resolvePanelTheme = (mode) => {
    if (mode === "dark" || mode === "light") return mode;
    return detectPageDarkTheme() ? "dark" : "light";
};

const applyPanelTheme = (panel, mode) => {
    const resolvedTheme = resolvePanelTheme(mode);
    panel.classList.remove("scc-theme-light", "scc-theme-dark");
    panel.classList.add(`scc-theme-${resolvedTheme}`);
};

let settingsTooltipEl = null;
const ensureSettingsTooltip = () => {
    if (settingsTooltipEl && document.body.contains(settingsTooltipEl)) {
        return settingsTooltipEl;
    }
    settingsTooltipEl = document.createElement("div");
    settingsTooltipEl.id = "scc-help-tooltip";
    settingsTooltipEl.className = "scc-help-tooltip";
    document.body.append(settingsTooltipEl);
    return settingsTooltipEl;
};

const positionSettingsTooltip = (tooltip, anchor) => {
    const anchorRect = anchor.getBoundingClientRect();
    const gap = 8;
    let top = anchorRect.bottom + gap;
    let left = anchorRect.left;
    let preferAbove = false;

    const viewportWidth = window.innerWidth;
    const viewportHeight = window.innerHeight;
    const safeMaxWidth = Math.max(160, viewportWidth - (gap * 2));
    tooltip.style.maxWidth = `${safeMaxWidth}px`;
    tooltip.style.left = "0px";
    tooltip.style.top = "0px";
    const tooltipWidth = tooltip.offsetWidth;
    const tooltipHeight = tooltip.offsetHeight;

    if (left + tooltipWidth > viewportWidth - gap) {
        left = viewportWidth - tooltipWidth - gap;
    }
    if (left < gap) {
        left = gap;
    }

    if (top + tooltipHeight > viewportHeight - gap) {
        top = anchorRect.top - tooltipHeight - gap;
        preferAbove = true;
    }
    if (top < gap) {
        top = gap;
    }

    tooltip.classList.toggle("is-above", preferAbove);
    tooltip.style.left = `${left}px`;
    tooltip.style.top = `${top}px`;
};

const attachHelpTooltipHandlers = (panel) => {
    const tooltip = ensureSettingsTooltip();
    let activeHelp = null;
    let showTimer = null;

    const hideTooltip = () => {
        if (showTimer) {
            clearTimeout(showTimer);
            showTimer = null;
        }
        activeHelp = null;
        tooltip.classList.remove("is-visible");
        tooltip.classList.remove("is-above");
    };

    panel.addEventListener("mouseover", event => {
        const help = event.target.closest(".scc-help");
        if (!help || !panel.contains(help)) return;
        const text = help.getAttribute("data-tooltip") || "";
        if (!text) return;
        activeHelp = help;
        tooltip.textContent = text;
        if (showTimer) clearTimeout(showTimer);
        showTimer = setTimeout(() => {
            if (activeHelp !== help) return;
            tooltip.classList.add("is-visible");
            positionSettingsTooltip(tooltip, help);
        }, 180);
    });

    panel.addEventListener("mousemove", event => {
        if (!activeHelp || !tooltip.classList.contains("is-visible")) return;
        const help = event.target.closest(".scc-help");
        if (!help || help !== activeHelp) return;
        positionSettingsTooltip(tooltip, activeHelp);
    });

    panel.addEventListener("mouseout", event => {
        if (!activeHelp) return;
        const fromHelp = event.target.closest(".scc-help");
        if (!fromHelp) return;
        if (event.relatedTarget && fromHelp.contains(event.relatedTarget)) return;
        hideTooltip();
    });

    panel.addEventListener("mouseleave", hideTooltip);
    window.addEventListener("scroll", hideTooltip, true);
};

const getNextThemeMode = (current) => {
    if (current === "auto") return "light";
    if (current === "light") return "dark";
    return "auto";
};

const getThemeToggleMeta = (mode, langCode) => {
    const dict = SETTINGS_I18N[langCode] || SETTINGS_I18N.en;
    if (mode === "light") return { icon: "🔆", label: dict.themeToggleLight };
    if (mode === "dark") return { icon: "🌙", label: dict.themeToggleDark };
    return { icon: "🌓", label: dict.themeToggleAuto };
};

const updateThemeToggleButton = (panel, mode, langCode) => {
    const btn = panel.querySelector("#scc-theme-toggle-btn");
    if (!btn) return;
    const dict = SETTINGS_I18N[langCode] || SETTINGS_I18N.en;
    const meta = getThemeToggleMeta(mode, langCode);
    btn.textContent = meta.icon;
    btn.title = `${dict.themeToggleTitle}: ${meta.label}`;
    btn.setAttribute("aria-label", `${dict.themeToggleTitle}: ${meta.label}`);
};

const applyPanelLocalization = (panel, langCode) => {
    const dict = SETTINGS_I18N[langCode] || SETTINGS_I18N.en;
    const setText = (selector, value) => {
        const el = panel.querySelector(selector);
        if (el) el.textContent = value;
    };
    const setTip = (selector, value) => {
        const el = panel.querySelector(selector);
        if (el) {
            el.setAttribute("data-tooltip", value);
            el.removeAttribute("title");
            el.setAttribute("aria-label", value);
        }
    };

    setText("#scc-panel-title", dict.panelTitle);
    setText("#scc-actions-title", dict.actions);
    if (DEBUG_MODE) setText("#scc-run-now", dict.runNow);
    setText("#scc-add-selector-click", dict.addByClick);
    setText("#scc-export-settings", dict.export);
    setText("#scc-import-settings-btn", dict.import);
    setText("#scc-config-title", dict.configuration);
    setText("#scc-currency-col-label", dict.currencyColumn);
    setText("#scc-fee-col-label", dict.feeColumn);
    setText("#scc-hide-original-label", dict.hideOriginal);
    setText("#scc-target-currencies-title", dict.targetCurrencies);
    setText("#scc-currency-order-title", dict.displayOrder);
    setText("#scc-custom-selectors-title", dict.customSelectors);
    setText("#scc-add-selector-manual", dict.add);
    setText("#scc-base-selectors-title", dict.baseSelectorToggles);
    setText("#scc-save-settings", dict.save);

    const selectorInput = panel.querySelector("#scc-new-selector");
    if (selectorInput) selectorInput.placeholder = dict.selectorPlaceholder;

    const autoOption = panel.querySelector('#scc-lang-mode option[value="auto"]');
    if (autoOption) autoOption.textContent = dict.languageAuto;
    setTip("#scc-actions-help", dict.tipActions);
    setTip("#scc-config-help", dict.tipConfig);
    setTip("#scc-targets-help", dict.tipTargets);
    setTip("#scc-order-help", dict.tipOrder);
    setTip("#scc-selectors-help", dict.tipSelectors);
    setTip("#scc-save-help", dict.tipSave);
    updateThemeToggleButton(panel, panelThemeMode, langCode);
};

const buildFinalSelectorString = () => {
    const disabledBaseSelectors = new Set(GM_getValue(DISABLED_BASE_SELECTORS_ID, []));
    const enabledBaseSelectors = BASE_PRICE_TARGETS.filter(sel => !disabledBaseSelectors.has(sel));
    const uniqueSelectors = Array.from(new Set([...enabledBaseSelectors, ...userAddedSelectors]));
    finalPriceSelectorString = uniqueSelectors.map(sel => `${sel}:not(.done)`).join(", ");
    return finalPriceSelectorString;
};


const renderCurrencyList = (container, targetsSnapshot) => {
    container.innerHTML = "";
    for (const currency of SUPPORTED_CURRENCIES) {
        const row = document.createElement("div");
        row.className = "scc-list-row";

        const left = document.createElement("div");
        left.className = "scc-row-left";
        left.textContent = `${currency.code} (${currency.sign})`;

        const controls = document.createElement("div");
        controls.className = "scc-row-controls";

        const switchWrap = document.createElement("label");
        switchWrap.className = "scc-switch";
        const enabledInput = document.createElement("input");
        enabledInput.type = "checkbox";
        enabledInput.dataset.code = currency.code;
        enabledInput.className = "scc-currency-enabled";
        enabledInput.checked = Object.prototype.hasOwnProperty.call(targetsSnapshot, currency.code);
        const slider = document.createElement("span");
        slider.className = "scc-slider";
        switchWrap.append(enabledInput, slider);

        const feeInput = document.createElement("input");
        feeInput.type = "number";
        feeInput.min = "0";
        feeInput.step = "0.1";
        feeInput.className = "scc-input scc-fee-input";
        feeInput.dataset.code = currency.code;
        feeInput.value = (targetsSnapshot[currency.code] ?? 0).toString();
        feeInput.disabled = !enabledInput.checked;

        enabledInput.addEventListener("change", () => {
            feeInput.disabled = !enabledInput.checked;
        });

        controls.append(feeInput, switchWrap);
        row.append(left, controls);
        container.append(row);
    }
};

const renderCurrencyOrder = (container, order, langCode = userLanguage) => {
    const dict = SETTINGS_I18N[langCode] || SETTINGS_I18N.en;
    container.innerHTML = "";
    if (order.length === 0) {
        const empty = document.createElement("div");
        empty.className = "scc-empty";
        empty.textContent = dict.orderEmpty;
        container.append(empty);
        return;
    }
    order.forEach((code, index) => {
        const currency = CURRENCY_BY_CODE.get(code);
        if (!currency) return;
        const row = document.createElement("div");
        row.className = "scc-list-row scc-order-row";

        const label = document.createElement("span");
        label.className = "scc-row-left";
        label.textContent = `${currency.code} (${currency.sign})`;

        const controls = document.createElement("div");
        controls.className = "scc-row-controls";

        const upBtn = document.createElement("button");
        upBtn.type = "button";
        upBtn.className = "scc-btn scc-btn-secondary scc-order-btn";
        upBtn.textContent = "▲";
        upBtn.dataset.code = code;
        upBtn.dataset.dir = "up";
        upBtn.disabled = index === 0;

        const downBtn = document.createElement("button");
        downBtn.type = "button";
        downBtn.className = "scc-btn scc-btn-secondary scc-order-btn";
        downBtn.textContent = "▼";
        downBtn.dataset.code = code;
        downBtn.dataset.dir = "down";
        downBtn.disabled = index === order.length - 1;

        controls.append(upBtn, downBtn);
        row.append(label, controls);
        container.append(row);
    });
};

const renderSelectorList = (container, selectors, langCode = userLanguage) => {
    const dict = SETTINGS_I18N[langCode] || SETTINGS_I18N.en;
    container.innerHTML = "";
    if (!selectors.length) {
        const empty = document.createElement("div");
        empty.className = "scc-empty";
        empty.textContent = dict.noSelectors;
        container.append(empty);
        return;
    }
    selectors.forEach((selector, index) => {
        const row = document.createElement("div");
        row.className = "scc-list-row";

        const text = document.createElement("div");
        text.className = "scc-selector-text";
        text.textContent = `${index + 1}. ${selector}`;

        const removeBtn = document.createElement("button");
        removeBtn.type = "button";
        removeBtn.className = "scc-btn scc-btn-secondary";
        removeBtn.textContent = dict.remove;
        removeBtn.dataset.removeSelector = selector;
        row.append(text, removeBtn);
        container.append(row);
    });
};

const renderBaseSelectorToggleList = (container, disabledSelectors) => {
    container.innerHTML = "";
    const disabledSet = new Set(disabledSelectors);
    for (const selector of BASE_PRICE_TARGETS) {
        const row = document.createElement("div");
        row.className = "scc-list-row";

        const text = document.createElement("div");
        text.className = "scc-selector-text";
        text.textContent = selector;

        const switchWrap = document.createElement("label");
        switchWrap.className = "scc-switch";

        const toggle = document.createElement("input");
        toggle.type = "checkbox";
        toggle.className = "scc-base-selector-toggle";
        toggle.dataset.selector = selector;
        toggle.checked = !disabledSet.has(selector);
        const slider = document.createElement("span");
        slider.className = "scc-slider";
        switchWrap.append(toggle, slider);

        row.append(text, switchWrap);
        container.append(row);
    }
};

const loadSettingsToPanel = (panel) => {
    const languageInput = panel.querySelector("#scc-lang-mode");
    const suppressInput = panel.querySelector("#scc-suppress-original");
    const currencyList = panel.querySelector("#scc-currency-list");
    const selectorsList = panel.querySelector("#scc-custom-selectors-list");
    const baseSelectorsList = panel.querySelector("#scc-base-selectors-list");

    const storedTargets = GM_getValue(TARGETS_ID, {});
    const storedLangMode = GM_getValue(LANG_MODE_ID, languageMode);
    const storedThemeMode = GM_getValue(THEME_MODE_ID, panelThemeMode);
    const storedSuppress = GM_getValue(SUPPRESS_ID, false);
    const storedCustomSelectors = GM_getValue(USER_SELECTORS_ID, []);
    const disabledBaseSelectors = GM_getValue(DISABLED_BASE_SELECTORS_ID, []);

    languageInput.value = ["auto", "ru", "en"].includes(storedLangMode) ? storedLangMode : "auto";
    panelThemeMode = ["auto", "light", "dark"].includes(storedThemeMode) ? storedThemeMode : "auto";
    const panelLang = resolveLanguage(languageInput.value);
    applyPanelTheme(panel, panelThemeMode);
    updateThemeToggleButton(panel, panelThemeMode, panelLang);
    applyPanelLocalization(panel, panelLang);
    suppressInput.checked = storedSuppress;
    renderCurrencyList(currencyList, storedTargets);

    // Загружаем порядок валют и синхронизируем с включёнными
    const storedOrder = GM_getValue(CURRENCY_ORDER_ID, []);
    const enabledCodes = Object.keys(storedTargets);
    panelCurrencyOrder = storedOrder.filter(c => enabledCodes.includes(c));
    for (const code of enabledCodes) {
        if (!panelCurrencyOrder.includes(code)) panelCurrencyOrder.push(code);
    }
    const orderList = panel.querySelector("#scc-currency-order-list");
    if (orderList) renderCurrencyOrder(orderList, panelCurrencyOrder, panelLang);

    renderSelectorList(selectorsList, storedCustomSelectors, panelLang);
    if (baseSelectorsList) {
        renderBaseSelectorToggleList(baseSelectorsList, disabledBaseSelectors);
    }
};

const saveSettingsFromPanel = (panel) => {
    const selectedLanguageMode = panel.querySelector("#scc-lang-mode").value;
    const nextLanguageMode = ["auto", "ru", "en"].includes(selectedLanguageMode) ? selectedLanguageMode : "auto";
    const language = resolveLanguage(nextLanguageMode);
    const suppress = panel.querySelector("#scc-suppress-original").checked;

    const nextTargets = {};
    panel.querySelectorAll(".scc-currency-enabled").forEach(toggle => {
        const code = toggle.dataset.code;
        if (!toggle.checked) return;
        const feeInput = panel.querySelector(`.scc-fee-input[data-code="${code}"]`);
        const fee = parseFloat(feeInput?.value ?? "0");
        nextTargets[code] = Number.isFinite(fee) && fee >= 0 ? fee : 0;
    });

    let disabledBaseSelectors = GM_getValue(DISABLED_BASE_SELECTORS_ID, []);
    if (panel.querySelector("#scc-base-selectors-list")) {
        disabledBaseSelectors = [];
        panel.querySelectorAll(".scc-base-selector-toggle").forEach(toggle => {
            if (!toggle.checked) {
                disabledBaseSelectors.push(toggle.dataset.selector);
            }
        });
    }

    GM_setValue(LANG_MODE_ID, nextLanguageMode);
    GM_setValue(THEME_MODE_ID, panelThemeMode);
    GM_setValue(LANG_ID, language);
    GM_setValue(SUPPRESS_ID, suppress);
    GM_setValue(TARGETS_ID, nextTargets);
    GM_setValue(CURRENCY_ORDER_ID, panelCurrencyOrder);
    GM_setValue(DISABLED_BASE_SELECTORS_ID, disabledBaseSelectors);

    languageMode = nextLanguageMode;
    userLanguage = language;
    suppressOriginal = suppress;
    targetCurrencies = nextTargets;
    targetCurrencyOrder = [...panelCurrencyOrder];

    userAddedSelectors = GM_getValue(USER_SELECTORS_ID, []);
    buildFinalSelectorString();
};

const openSettingsPanel = () => {
    let backdrop = document.getElementById(SETTINGS_BACKDROP_ID);
    let panel = document.getElementById(SETTINGS_PANEL_ID);

    if (!backdrop || !panel) {
        backdrop = document.createElement("div");
        backdrop.id = SETTINGS_BACKDROP_ID;
        backdrop.className = "scc-settings-backdrop";

        const baseSelectorsSection = DEBUG_MODE ? `
                    <h4>
                        <span id="scc-base-selectors-title">Base selector toggles (default ON)</span>
                    </h4>
                    <div id="scc-base-selectors-list" class="scc-scroll-list"></div>
        ` : "";

        panel = document.createElement("aside");
        panel.id = SETTINGS_PANEL_ID;
        panel.className = "scc-settings-panel";
        panel.innerHTML = `
            <div class="scc-panel-header">
                <h2 class="scc-panel-title" id="scc-panel-title">Steam Currency Converter - Settings</h2>
                <div class="scc-header-actions">
                    <select id="scc-lang-mode" class="scc-input scc-lang-compact" aria-label="Language">
                        <option value="auto">Auto</option>
                        <option value="ru">RU</option>
                        <option value="en">EN</option>
                    </select>
                    <button type="button" class="scc-theme-toggle-btn" id="scc-theme-toggle-btn" title="Panel theme">A</button>
                    <button type="button" class="scc-close-btn" id="scc-close-panel">✖</button>
                </div>
            </div>
            <div class="scc-panel-content">
                <section class="scc-section">
                    <h3 class="scc-title-row">
                        <span id="scc-actions-title">Actions</span>
                        <span class="scc-help" id="scc-actions-help" data-tooltip="Actions help">?</span>
                    </h3>
                    <div class="scc-actions-grid">
                        ${DEBUG_MODE ? '<button type="button" class="scc-btn scc-btn-secondary" id="scc-run-now">Run manually</button>' : ''}
                        <button type="button" class="scc-btn scc-btn-secondary" id="scc-add-selector-click">Add selector by click</button>
                        <button type="button" class="scc-btn scc-btn-secondary" id="scc-export-settings">Export</button>
                        <button type="button" class="scc-btn scc-btn-secondary" id="scc-import-settings-btn">Import</button>
                        <input type="file" id="scc-import-file" accept="application/json" style="display:none">
                    </div>
                    <div id="scc-action-status" class="scc-status"></div>
                </section>

                <section class="scc-section">
                    <h3 class="scc-title-row">
                        <span id="scc-config-title">Configuration</span>
                        <span class="scc-help" id="scc-config-help" data-tooltip="Configuration help">?</span>
                    </h3>

                    <label class="scc-field scc-inline">
                        <span id="scc-hide-original-label">Hide original price</span>
                        <label class="scc-switch">
                            <input type="checkbox" id="scc-suppress-original" checked>
                            <span class="scc-slider"></span>
                        </label>
                    </label>

                    <h4 class="scc-title-row">
                        <span id="scc-target-currencies-title">Target currencies</span>
                        <span class="scc-help" id="scc-targets-help" data-tooltip="Target currencies help">?</span>
                    </h4>
                    <div class="scc-currency-head">
                        <span id="scc-currency-col-label">Currency</span>
                        <span id="scc-fee-col-label">Fee</span>
                        <span></span>
                    </div>
                    <div id="scc-currency-list" class="scc-scroll-list"></div>

                    <h4 class="scc-title-row">
                        <span id="scc-currency-order-title">Display order</span>
                        <span class="scc-help" id="scc-order-help" data-tooltip="Order help">?</span>
                    </h4>
                    <div id="scc-currency-order-list" class="scc-scroll-list scc-order-list"></div>

                    <h4 class="scc-title-row">
                        <span id="scc-custom-selectors-title">Custom selectors</span>
                        <span class="scc-help" id="scc-selectors-help" data-tooltip="Custom selectors help">?</span>
                    </h4>
                    <div class="scc-add-row">
                        <input type="text" id="scc-new-selector" class="scc-input" placeholder=".wishlist_row .price">
                        <button type="button" class="scc-btn scc-btn-secondary" id="scc-add-selector-manual">Add</button>
                    </div>
                    <div id="scc-custom-selectors-list" class="scc-scroll-list"></div>
                    ${baseSelectorsSection}
                </section>

                <section class="scc-save-area">
                    <span class="scc-help" id="scc-save-help" data-tooltip="Save help">?</span>
                    <button type="button" class="scc-btn scc-btn-primary" id="scc-save-settings">Save</button>
                    <div id="scc-save-status" class="scc-status"></div>
                </section>
            </div>
        `;

        document.body.append(backdrop, panel);

        const closePanel = () => {
            backdrop.classList.remove("is-open");
            panel.classList.remove("is-open");
        };

        backdrop.addEventListener("click", closePanel);
        panel.querySelector("#scc-close-panel").addEventListener("click", closePanel);

        const setActionStatus = (message) => {
            panel.querySelector("#scc-action-status").textContent = message;
        };
        const setSaveStatus = (message) => {
            panel.querySelector("#scc-save-status").textContent = message;
        };
        attachHelpTooltipHandlers(panel);

        const exportBtn = panel.querySelector("#scc-export-settings");
        const importBtn = panel.querySelector("#scc-import-settings-btn");
        const importFile = panel.querySelector("#scc-import-file");
        const langModeSelect = panel.querySelector("#scc-lang-mode");
        const themeToggleBtn = panel.querySelector("#scc-theme-toggle-btn");
        const addManualSelectorBtn = panel.querySelector("#scc-add-selector-manual");
        const runNowBtn = panel.querySelector("#scc-run-now");
        const addByClickBtn = panel.querySelector("#scc-add-selector-click");
        const saveBtn = panel.querySelector("#scc-save-settings");

        document.addEventListener("keydown", event => {
            if (event.key !== "Escape") return;
            if (!panel.classList.contains("is-open")) return;
            closePanel();
        });

        const syncThemeInAutoMode = () => {
            if (!panel.classList.contains("is-open")) return;
            if (panelThemeMode !== "auto") return;
            const panelLang = resolveLanguage(langModeSelect.value);
            applyPanelTheme(panel, panelThemeMode);
            updateThemeToggleButton(panel, panelThemeMode, panelLang);
        };
        const themeObserver = new MutationObserver(syncThemeInAutoMode);
        themeObserver.observe(document.documentElement, {
            attributes: true,
            attributeFilter: ["class", "style", "data-theme", "theme", "color-theme"]
        });
        if (document.body) {
            themeObserver.observe(document.body, {
                attributes: true,
                attributeFilter: ["class", "style", "data-theme", "theme", "color-theme"]
            });
        }

        langModeSelect.addEventListener("change", () => {
            const previewLang = resolveLanguage(langModeSelect.value);
            applyPanelLocalization(panel, previewLang);
            renderSelectorList(panel.querySelector("#scc-custom-selectors-list"), GM_getValue(USER_SELECTORS_ID, []), previewLang);
            const orderListEl = panel.querySelector("#scc-currency-order-list");
            if (orderListEl) renderCurrencyOrder(orderListEl, panelCurrencyOrder, previewLang);
        });
        themeToggleBtn.addEventListener("click", () => {
            panelThemeMode = getNextThemeMode(panelThemeMode);
            const panelLang = resolveLanguage(langModeSelect.value);
            applyPanelTheme(panel, panelThemeMode);
            updateThemeToggleButton(panel, panelThemeMode, panelLang);
        });

        // Синхронизация порядка при включении/выключении валют
        panel.addEventListener("change", (e) => {
            const toggle = e.target.closest(".scc-currency-enabled");
            if (!toggle) return;
            const code = toggle.dataset.code;
            if (toggle.checked) {
                if (!panelCurrencyOrder.includes(code)) panelCurrencyOrder.push(code);
            } else {
                panelCurrencyOrder = panelCurrencyOrder.filter(c => c !== code);
            }
            const orderListEl = panel.querySelector("#scc-currency-order-list");
            if (orderListEl) renderCurrencyOrder(orderListEl, panelCurrencyOrder, resolveLanguage(langModeSelect.value));
        });

        // Кнопки ▲/▼ для изменения порядка
        panel.addEventListener("click", (e) => {
            const orderBtn = e.target.closest(".scc-order-btn");
            if (orderBtn) {
                const code = orderBtn.dataset.code;
                const dir = orderBtn.dataset.dir;
                const idx = panelCurrencyOrder.indexOf(code);
                if (idx === -1) return;
                if (dir === "up" && idx > 0) {
                    [panelCurrencyOrder[idx - 1], panelCurrencyOrder[idx]] =
                        [panelCurrencyOrder[idx], panelCurrencyOrder[idx - 1]];
                } else if (dir === "down" && idx < panelCurrencyOrder.length - 1) {
                    [panelCurrencyOrder[idx + 1], panelCurrencyOrder[idx]] =
                        [panelCurrencyOrder[idx], panelCurrencyOrder[idx + 1]];
                }
                const orderListEl = panel.querySelector("#scc-currency-order-list");
                if (orderListEl) renderCurrencyOrder(orderListEl, panelCurrencyOrder, resolveLanguage(langModeSelect.value));
                return;
            }

            const removeSelectorBtn = e.target.closest("[data-remove-selector]");
            if (!removeSelectorBtn) return;
            const panelLang = resolveLanguage(langModeSelect.value);
            const selectorToRemove = removeSelectorBtn.dataset.removeSelector;
            const currentSelectors = GM_getValue(USER_SELECTORS_ID, []);
            const nextSelectors = currentSelectors.filter(sel => sel !== selectorToRemove);
            GM_setValue(USER_SELECTORS_ID, nextSelectors);
            userAddedSelectors = nextSelectors;
            renderSelectorList(panel.querySelector("#scc-custom-selectors-list"), nextSelectors, panelLang);
            setActionStatus(getSettingsText("removedSelector", selectorToRemove));
        });

        if (runNowBtn) {
            runNowBtn.addEventListener("click", async () => {
                setActionStatus(getSettingsText("runningManual"));
                conversionLogicStarted = false;
                await startConversionLogic();
                processPrices(document.querySelectorAll(finalPriceSelectorString));
                setActionStatus(getSettingsText("manualDone"));
            });
        }

        addByClickBtn.addEventListener("click", () => {
            findAndAddSelector({
                onStatus: setActionStatus,
                onDone: () => {
                    const panelLang = resolveLanguage(langModeSelect.value);
                    const selectors = GM_getValue(USER_SELECTORS_ID, []);
                    renderSelectorList(panel.querySelector("#scc-custom-selectors-list"), selectors, panelLang);
                }
            });
        });

        addManualSelectorBtn.addEventListener("click", () => {
            const input = panel.querySelector("#scc-new-selector");
            const value = (input.value || "").trim();
            if (!value) return;
            const currentSelectors = GM_getValue(USER_SELECTORS_ID, []);
            if (currentSelectors.includes(value)) {
                setActionStatus(getSettingsText("selectorExists"));
                return;
            }
            currentSelectors.push(value);
            GM_setValue(USER_SELECTORS_ID, currentSelectors);
            userAddedSelectors = currentSelectors;
            renderSelectorList(panel.querySelector("#scc-custom-selectors-list"), currentSelectors, resolveLanguage(langModeSelect.value));
            input.value = "";
            setActionStatus(getSettingsText("addedSelector", value));
        });

        exportBtn.addEventListener("click", () => {
            const data = {
                targets: GM_getValue(TARGETS_ID, {}),
                suppressOriginal: GM_getValue(SUPPRESS_ID, false),
                language: GM_getValue(LANG_ID, "ru"),
                languageMode: GM_getValue(LANG_MODE_ID, "auto"),
                themeMode: GM_getValue(THEME_MODE_ID, "auto"),
                userSelectors: GM_getValue(USER_SELECTORS_ID, []),
                disabledBaseSelectors: GM_getValue(DISABLED_BASE_SELECTORS_ID, []),
                currencyOrder: GM_getValue(CURRENCY_ORDER_ID, [])
            };
            const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" });
            const link = document.createElement("a");
            link.href = URL.createObjectURL(blob);
            link.download = "steam-currency-converter-settings.json";
            link.click();
            URL.revokeObjectURL(link.href);
            setActionStatus(getSettingsText("exported"));
        });

        importBtn.addEventListener("click", () => importFile.click());
        importFile.addEventListener("change", () => {
            const file = importFile.files?.[0];
            if (!file) return;
            const reader = new FileReader();
            reader.onload = () => {
                try {
                    const parsed = JSON.parse(String(reader.result || "{}"));
                    if (parsed.targets && typeof parsed.targets === "object") GM_setValue(TARGETS_ID, parsed.targets);
                    if (typeof parsed.suppressOriginal === "boolean") GM_setValue(SUPPRESS_ID, parsed.suppressOriginal);
                    if (parsed.languageMode === "auto" || parsed.languageMode === "ru" || parsed.languageMode === "en") {
                        GM_setValue(LANG_MODE_ID, parsed.languageMode);
                    } else if (parsed.language === "ru" || parsed.language === "en") {
                        GM_setValue(LANG_MODE_ID, parsed.language);
                    }
                    if (parsed.themeMode === "auto" || parsed.themeMode === "light" || parsed.themeMode === "dark") {
                        GM_setValue(THEME_MODE_ID, parsed.themeMode);
                    }
                    if (parsed.language === "ru" || parsed.language === "en") GM_setValue(LANG_ID, parsed.language);
                    if (Array.isArray(parsed.userSelectors)) GM_setValue(USER_SELECTORS_ID, parsed.userSelectors);
                    if (Array.isArray(parsed.disabledBaseSelectors)) GM_setValue(DISABLED_BASE_SELECTORS_ID, parsed.disabledBaseSelectors);
                    if (Array.isArray(parsed.currencyOrder)) GM_setValue(CURRENCY_ORDER_ID, parsed.currencyOrder);
                    loadLanguagePreference();
                    loadThemePreference();
                    loadSettingsToPanel(panel);
                    setActionStatus(getSettingsText("imported"));
                } catch (err) {
                    setActionStatus(getSettingsText("importFailed", String(err)));
                }
            };
            reader.readAsText(file);
        });

        saveBtn.addEventListener("click", () => {
            saveSettingsFromPanel(panel);
            setSaveStatus(getSettingsText("savedReloading"));
            setTimeout(() => location.reload(), 600);
        });
    }

    loadSettingsToPanel(panel);
    requestAnimationFrame(() => {
        requestAnimationFrame(() => {
            backdrop.classList.add("is-open");
            panel.classList.add("is-open");
        });
    });
};

const configureMenus = () => {
    GM_registerMenuCommand(getSettingsText("menuSettings"), openSettingsPanel);
};
const applyStyles = () => {
    const styles = `
    :root {
      --scc-panel-bg-light: #f8fafc;
      --scc-panel-text-light: #0f172a;
      --scc-panel-border-light: #cbd5e1;
      --scc-input-bg-light: #ffffff;
      --scc-input-text-light: #0f172a;
      --scc-input-border-light: #94a3b8;
      --scc-btn-primary-light: #1d4ed8;
      --scc-btn-primary-hover-light: #1e40af;
      --scc-btn-secondary-light: #334155;
      --scc-btn-secondary-hover-light: #1e293b;

      --scc-panel-bg-dark: #0f172a;
      --scc-panel-text-dark: #e2e8f0;
      --scc-panel-border-dark: #334155;
      --scc-input-bg-dark: #111827;
      --scc-input-text-dark: #f1f5f9;
      --scc-input-border-dark: #475569;
      --scc-btn-primary-dark: #2563eb;
      --scc-btn-primary-hover-dark: #1d4ed8;
      --scc-btn-secondary-dark: #475569;
      --scc-btn-secondary-hover-dark: #334155;
    }

    .scc-settings-backdrop {
      position: fixed;
      inset: 0;
      background: rgba(2, 6, 23, 0.45);
      opacity: 0;
      pointer-events: none;
      transition: opacity .2s ease;
      z-index: 99998;
    }
    .scc-settings-backdrop.is-open {
      opacity: 1;
      pointer-events: auto;
    }
    .scc-settings-panel {
      position: fixed;
      top: 12px;
      right: 0;
      transform: translateX(calc(100% + 24px));
      width: min(440px, 94vw);
      max-height: 90vh;
      overflow-y: auto;
      overflow-x: hidden;
      border-radius: 12px 0 0 12px;
      border: 1px solid var(--scc-panel-border);
      background: var(--scc-panel-bg);
      color: var(--scc-panel-text);
      box-shadow: var(--scc-panel-shadow, 0 10px 28px rgba(0,0,0,0.22));
      z-index: 99999;
      transition: transform .35s ease;
      padding: 16px;
      font-family: Arial, sans-serif;
      line-height: 1.35;
    }
    .scc-settings-panel.is-open { transform: translateX(0); }

    .scc-settings-panel,
    .scc-settings-panel.scc-theme-light {
      --scc-panel-bg: var(--scc-panel-bg-light);
      --scc-panel-text: var(--scc-panel-text-light);
      --scc-panel-border: var(--scc-panel-border-light);
      --scc-input-bg: var(--scc-input-bg-light);
      --scc-input-text: var(--scc-input-text-light);
      --scc-input-border: var(--scc-input-border-light);
      --scc-btn-primary: var(--scc-btn-primary-light);
      --scc-btn-primary-hover: var(--scc-btn-primary-hover-light);
      --scc-btn-secondary: var(--scc-btn-secondary-light);
      --scc-btn-secondary-hover: var(--scc-btn-secondary-hover-light);
      --scc-panel-shadow: 0 10px 28px rgba(0,0,0,0.22);
    }
    .scc-settings-panel.scc-theme-dark {
      --scc-panel-bg: var(--scc-panel-bg-dark);
      --scc-panel-text: var(--scc-panel-text-dark);
      --scc-panel-border: var(--scc-panel-border-dark);
      --scc-input-bg: var(--scc-input-bg-dark);
      --scc-input-text: var(--scc-input-text-dark);
      --scc-input-border: var(--scc-input-border-dark);
      --scc-btn-primary: var(--scc-btn-primary-dark);
      --scc-btn-primary-hover: var(--scc-btn-primary-hover-dark);
      --scc-btn-secondary: var(--scc-btn-secondary-dark);
      --scc-btn-secondary-hover: var(--scc-btn-secondary-hover-dark);
      --scc-panel-shadow: 0 10px 28px rgba(0,0,0,0.65);
    }

    .scc-panel-header {
      display: flex;
      justify-content: space-between;
      align-items: center;
      margin-bottom: 10px;
    }
    .scc-header-actions {
      display: flex;
      align-items: center;
      gap: 8px;
    }
    .scc-lang-compact {
      width: 74px;
      min-width: 74px;
      height: 34px;
      padding: 4px 8px;
      font-size: 12px;
    }
    .scc-panel-title {
      font-size: 18px;
      margin: 0;
      color: var(--scc-panel-text) !important;
      text-shadow: none !important;
      opacity: 1 !important;
    }
    .scc-theme-toggle-btn {
      border: 1px solid var(--scc-input-border);
      background: var(--scc-btn-secondary);
      color: #fff !important;
      border-radius: 10px;
      width: 34px;
      height: 34px;
      cursor: pointer;
      font-weight: 700;
      line-height: 1;
      display: inline-flex;
      align-items: center;
      justify-content: center;
    }
    .scc-close-btn {
      border: 1px solid var(--scc-input-border);
      background: var(--scc-input-bg);
      color: var(--scc-input-text);
      border-radius: 10px;
      width: 34px;
      height: 34px;
      cursor: pointer;
    }
    .scc-panel-content {
      display: flex;
      flex-direction: column;
      gap: 12px;
      padding-bottom: 72px;
    }
    .scc-section {
      border: 1px solid var(--scc-panel-border);
      border-radius: 12px;
      padding: 10px;
    }
    .scc-section h3, .scc-section h4 {
      margin: 0 0 8px 0;
      color: var(--scc-panel-text) !important;
      text-shadow: none !important;
      opacity: 1 !important;
    }
    .scc-title-row {
      display: flex;
      align-items: center;
      justify-content: space-between;
      gap: 8px;
    }
    .scc-help {
      display: inline-flex;
      align-items: center;
      justify-content: center;
      width: 16px;
      height: 16px;
      border-radius: 50%;
      border: 1px solid var(--scc-input-border);
      background: var(--scc-input-bg);
      color: var(--scc-input-text) !important;
      font-size: 11px;
      cursor: help;
      position: relative;
      flex: 0 0 auto;
    }
    .scc-help-tooltip {
      position: fixed;
      display: none;
      min-width: 160px;
      max-width: 260px;
      background: var(--scc-input-bg-light);
      color: var(--scc-input-text-light);
      border: 1px solid var(--scc-input-border-light);
      border-radius: 8px;
      padding: 6px 8px;
      font-size: 11px;
      line-height: 1.3;
      white-space: normal;
      z-index: 100001;
      box-shadow: 0 8px 20px rgba(0, 0, 0, .3);
      pointer-events: none;
    }
    .scc-help-tooltip.is-visible {
      display: block;
    }
    .scc-help-tooltip.is-above {
      transform-origin: bottom right;
    }
    @media (max-width: 560px) {
      .scc-help-tooltip {
        min-width: 140px;
        max-width: calc(100vw - 16px);
        font-size: 10px;
      }
    }
    body.night-theme .scc-help-tooltip,
    html.night-theme .scc-help-tooltip,
    .night-theme .scc-help-tooltip {
      background: var(--scc-input-bg-dark);
      color: var(--scc-input-text-dark);
      border-color: var(--scc-input-border-dark);
    }
    .scc-settings-panel .scc-field span,
    .scc-settings-panel .scc-row-left,
    .scc-settings-panel .scc-selector-text,
    .scc-settings-panel .scc-empty,
    .scc-settings-panel .scc-status {
      color: var(--scc-panel-text) !important;
      text-shadow: none !important;
      opacity: 1 !important;
    }
    .scc-actions-grid {
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 8px;
    }
    .scc-field {
      display: flex;
      flex-direction: column;
      gap: 6px;
      margin-bottom: 8px;
    }
    .scc-field.scc-inline {
      flex-direction: row;
      align-items: center;
      justify-content: space-between;
    }
    .scc-input {
      width: 100%;
      box-sizing: border-box;
      border: 1px solid var(--scc-input-border) !important;
      background: var(--scc-input-bg) !important;
      color: var(--scc-input-text) !important;
      border-radius: 10px;
      padding: 8px 10px;
      font-size: 13px;
    }
    .scc-scroll-list {
      max-height: 180px;
      overflow-y: auto;
      overflow-x: hidden;
      border: 1px solid var(--scc-panel-border);
      border-radius: 10px;
      padding: 6px;
      margin-bottom: 8px;
    }
    .scc-currency-head {
      display: grid;
      grid-template-columns: 1fr 88px 44px;
      gap: 8px;
      padding: 0 6px 6px 6px;
      margin-bottom: 4px;
      border-bottom: 1px solid var(--scc-panel-border);
      color: var(--scc-panel-text) !important;
      font-size: 11px;
      font-weight: 700;
      letter-spacing: .2px;
    }
    .scc-currency-head span:nth-child(2) {
      text-align: center;
    }
    .scc-list-row {
      display: flex;
      justify-content: space-between;
      gap: 8px;
      align-items: center;
      padding: 6px 4px;
      border-bottom: 1px solid var(--scc-panel-border);
    }
    .scc-list-row:last-child { border-bottom: none; }
    .scc-row-left {
      font-size: 12px;
      white-space: nowrap;
    }
    .scc-row-controls {
      display: flex;
      align-items: center;
      gap: 8px;
      justify-content: flex-end;
    }
    .scc-fee-input {
      width: 80px;
      padding: 6px 8px;
    }
    .scc-selector-text {
      font-size: 12px;
      overflow: hidden;
      text-overflow: ellipsis;
      white-space: nowrap;
      max-width: 285px;
    }
    .scc-empty {
      font-size: 12px;
      opacity: .8;
      padding: 6px;
    }
    .scc-order-list {
      max-height: 120px;
    }
    .scc-order-btn {
      padding: 2px 8px !important;
      font-size: 10px !important;
      min-width: 28px;
      line-height: 1;
    }
    .scc-order-btn:disabled {
      opacity: .3;
      cursor: default;
    }
    .scc-add-row {
      display: grid;
      grid-template-columns: 1fr auto;
      gap: 8px;
      margin-bottom: 8px;
    }
    .scc-btn {
      border: none;
      color: #fff !important;
      border-radius: 10px;
      padding: 8px 10px;
      font-size: 12px;
      cursor: pointer;
      transition: background .15s ease;
    }
    .scc-btn-primary { background: var(--scc-btn-primary); }
    .scc-btn-primary:hover { background: var(--scc-btn-primary-hover); }
    .scc-btn-secondary { background: var(--scc-btn-secondary); }
    .scc-btn-secondary:hover { background: var(--scc-btn-secondary-hover); }
    .scc-theme-toggle-btn:hover { background: var(--scc-btn-secondary-hover); }
    .scc-status {
      margin-top: 8px;
      min-height: 18px;
      font-size: 12px;
      opacity: .95;
    }
    .scc-save-area {
      position: sticky;
      bottom: 8px;
      display: flex;
      align-items: center;
      gap: 8px;
      z-index: 3;
      background: transparent;
      border: none;
      padding: 0;
      margin-top: 2px;
    }

    .scc-switch {
      position: relative;
      display: inline-block;
      width: 44px;
      height: 24px;
    }
    .scc-switch input {
      opacity: 0;
      width: 0;
      height: 0;
    }
    .scc-slider {
      position: absolute;
      cursor: pointer;
      inset: 0;
      background: #64748b;
      transition: .2s;
      border-radius: 999px;
    }
    .scc-slider:before {
      position: absolute;
      content: "";
      height: 18px;
      width: 18px;
      left: 3px;
      bottom: 3px;
      background: #fff;
      transition: .2s;
      border-radius: 50%;
    }
    .scc-switch input:checked + .scc-slider {
      background: #22c55e;
    }
    .scc-switch input:checked + .scc-slider:before {
      transform: translateX(20px);
    }

    .tab_item_discount { width: 160px !important; }
    .tab_item_discount .discount_prices { width: 100% !important; }
    .tab_item_discount .discount_final_price { padding: 0 !important; }
    .home_marketing_message.small .discount_block { height: auto !important; }
    .discount_block_inline { white-space: nowrap !important; }
    .curator #RecommendationsRows .store_capsule.price_inline .discount_block { min-width: 200px !important; }
    .market_listing_their_price { min-width: 130px !important; }
  `;
    GM_addStyle(styles);
};
const getCurrencyById = id => CURRENCY_BY_ID.get(id);
const getCurrencyByCode = code => CURRENCY_BY_CODE.get(code);
const getCurrencySign = code => {
    const currency = CURRENCY_BY_CODE.get(code);
    return currency ? (currency.sign || code) : code;
};

// Вынесено в отдельную функцию
const startConversionLogic = async () => {
    if (conversionLogicStarted) {
        log("Conversion logic already started, skipping.");
        return;
    }
    conversionLogicStarted = true;
    log("Starting conversion logic...");

    if (Object.keys(targetCurrencies).length === 0) {
        warn("No target currencies set. Exiting conversion logic.");
        return;
    }

    await loadRates();
    log("Exchange rates loaded.");

    if (!activeCurrencyCode) {
         error("Conversion logic started but activeCurrencyCode is missing!");
         return;
    }

    baseRateFromRub = exchangeData.rub[activeCurrencyCode.toLowerCase()];
    if (activeCurrencyCode.toLowerCase() === 'rub') {
        baseRateFromRub = 1;
    }

    log(`Base Rate (1 RUB to ${activeCurrencyCode}): ${baseRateFromRub}`);

    if (!exchangeData || !baseRateFromRub) {
        error(`FAILED to find base rate for ${activeCurrencyCode}. Exiting.`);
        return;
    }

    // Первоначальный прогон
    processPrices(document.querySelectorAll(finalPriceSelectorString));

    // Дополнительные проверки для динамического контента
    for (const delay of [2000, 5000, 10000]) {
        setTimeout(() => {
            log(`Running delayed scan (${delay / 1000}s)...`);
            processPrices(document.querySelectorAll(finalPriceSelectorString));
        }, delay);
    }
}


// Core initialization
const bootstrap = async () => {
    "use strict";
    log("Bootstrapping script...");
    // Restore persistent state
    targetCurrencies = GM_getValue(TARGETS_ID, {});
    targetCurrencyOrder = GM_getValue(CURRENCY_ORDER_ID, []);
    suppressOriginal = GM_getValue(SUPPRESS_ID, false);
    loadLanguagePreference();
    loadThemePreference();
    userAddedSelectors = GM_getValue(USER_SELECTORS_ID, []);

    // Собираем финальную строку селекторов
    buildFinalSelectorString();
    log("Final selector string:", finalPriceSelectorString);

    // Определение валюты (Методы 1, 2, 3 и безопасный 4)
    identifyActiveCurrency();
    log(`Active Currency: ${activeCurrencyCode} (Sign: ${activeCurrencySign})`);

    // Настраиваем меню и стили в любом случае
    configureMenus();
    applyStyles();

    if (!activeCurrencyCode) {
        warn("No currency found on initial load. Initializing watcher for dynamic detection...");
        initializeWatcher(); // Запускаем наблюдатель, который будет *ждать* появления валюты
        return;
    }

    // Валюта найдена сразу, запускаем основную логику
    await startConversionLogic();
    initializeWatcher(); // Запускаем наблюдатель для отслеживания *будущих* изменений
};

// Start on page load
window.addEventListener("load", bootstrap);