Remanga

+Отображение открытых паков

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Remanga
// @namespace    http://tampermonkey.net/
// @version      8.0.0
// @description  +Отображение открытых паков
// @author       You
// @license      MIT
// @match        https://remanga.org/*
// @match        https://xn--80aaig9ahr.xn--c1avg/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=remanga.org
// @connect      xn--80aaig9ahr.xn--c1avg
// @connect      api.xn--80aaig9ahr.xn--c1avg
// @connect      remanga.org
// @connect      api.remanga.org
// @connect      update.greatest.deepsurf.us
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';

    const IS_MIRROR = location.hostname === 'xn--80aaig9ahr.xn--c1avg';
    const SITE_DOMAIN = IS_MIRROR ? 'https://xn--80aaig9ahr.xn--c1avg' : 'https://remanga.org';
    const API_DOMAIN = IS_MIRROR ? 'https://api.xn--80aaig9ahr.xn--c1avg' : 'https://api.remanga.org';


    const MY_ID_KEY = 'rem_user_id_v1';
    const MY_INV_KEY = 'rem_inventory_covers_v1';
    const MY_WISH_KEY = 'rem_wish_covers_v1';
    const STORAGE_FIX_KEY = 'rem_script_fix_active';

    const SETTINGS_KEYS = {
        onlineStatus: 'rem_set_online_status',
        ownBadge: 'rem_set_own_badge',
        cardStats: 'rem_set_card_stats',
        profileStats: 'rem_set_profile_stats',
        fixMode: 'rem_set_fix_mode',
        customBgEnabled: 'rem_set_bg_enabled',
        customBgOpacity: 'rem_set_bg_opacity',
        customBgBlur: 'rem_set_bg_blur',
        customBgFit: 'rem_set_bg_fit',
        themeAccent: 'rem_set_theme_accent',
        themeButtonBg: 'rem_set_theme_btn_bg',
        themeNameColor: 'rem_set_theme_name_color',
        themeNameFont: 'rem_set_theme_name_font',
        themeMenuBg: 'rem_set_theme_menu_bg',
        themeSiteBg: 'rem_set_theme_site_bg',
        scanButton: 'rem_set_scan_btn',
        deckStats: 'rem_set_deck_stats',
        wishBadge: 'rem_set_wish_badge',
    };
    const BG_URL_LS_KEY = 'rem_bg_url_data';

    const DEFAULTS = {
        onlineStatus: true,
        ownBadge: true,
        cardStats: true,
        profileStats: true,
        fixMode: false,
        customBgEnabled: false,
        customBgOpacity: 80,
        customBgBlur: 0,
        customBgFit: 'cover',
        themeAccent: '',
        themeButtonBg: '',
        themeNameColor: '',
        themeNameFont: '',
        themeMenuBg: '',
        themeSiteBg: '',
        scanButton: true,
        deckStats: false,
        wishBadge: true,
    };

    const THEME_PRESETS = [
        { name: 'По умолчанию', value: '' },
        { name: '🔵 Синий', value: '#3b82f6' },
        { name: '🟣 Фиолетовый', value: '#a855f7' },
        { name: '🟢 Зелёный', value: '#22c55e' },
        { name: '🔴 Красный', value: '#ef4444' },
        { name: '🟠 Оранжевый', value: '#f97316' },
        { name: '🌸 Розовый', value: '#ec4899' },
        { name: '🌊 Бирюзовый', value: '#06b6d4' },
        { name: '🌟 Золотой', value: '#eab308' },
    ];

    const cfg = {};
    function loadSettings() {
        for (const [k, dflt] of Object.entries(DEFAULTS)) {
            try {
                cfg[k] = (typeof GM_getValue !== 'undefined') ? GM_getValue(SETTINGS_KEYS[k], dflt) : JSON.parse(localStorage.getItem(SETTINGS_KEYS[k]) ?? JSON.stringify(dflt));
            } catch (e) {
                cfg[k] = dflt;
            }
        }
        cfg.customBgUrl = localStorage.getItem(BG_URL_LS_KEY) || '';
    }
    function saveSetting(key, value) {
        cfg[key] = value;
        if (key === 'customBgUrl') {
            try { localStorage.setItem(BG_URL_LS_KEY, value); } catch (e) { console.warn('REM BG: localStorage переполнен'); }
        } else {
            try {
                if (typeof GM_setValue !== 'undefined') GM_setValue(SETTINGS_KEYS[key], value);
                else localStorage.setItem(SETTINGS_KEYS[key], JSON.stringify(value));
            } catch (e) { }
        }
    }
    loadSettings();

    async function checkForUpdates() {
        const URL = 'https://update.greatest.deepsurf.us/scripts/570944/Remanga.meta.js';
        const current = (typeof GM_info !== 'undefined') ? GM_info.script.version : '7.2.5';
        Manager.showStatus('Проверка обновлений...', true);
        try {
            const resp = await new Promise((resolve, reject) => {
                GM_xmlhttpRequest({ method: 'GET', url: URL + '?t=' + Date.now(), onload: resolve, onerror: reject });
            });
            const m = resp.responseText.match(/@version\s+([\d.]+)/);
            if (m) {
                const latest = m[1];
                if (latest !== current) {
                    if (confirm(`Доступна новая версия: ${latest} (у вас ${current})\n\nПерейти на страницу обновления?`)) {
                        window.location.href = 'https://update.greatest.deepsurf.us/scripts/570944/Remanga.user.js';
                    }
                } else { alert('У вас установлена актуальная версия.'); }
            }
        } catch (e) { alert('Ошибка при проверке обновлений.'); }
        finally { Manager.showStatus('', false); }
    }

    let activeObserver = null;

    const ExState = {
        scanning: false,
        scanDone: 0,
        scanTotal: 0,
        results: [],
        currentRank: 'ALL',
        page: 0,
        perPage: 8,
        lastSource: '',
        searchQuery: '',
        searchUser: '',
    };
    const SCAN_LIST_KEY = 'rem_scan_list_v1';
    function getScanList() {
        try { return JSON.parse(localStorage.getItem(SCAN_LIST_KEY) || '[]'); } catch { return []; }
    }
    function saveScanList(list) {
        localStorage.setItem(SCAN_LIST_KEY, JSON.stringify(list));
    }
    function addToScanList(id, username) {
        const list = getScanList();
        id = String(id);
        if (list.find(u => String(u.id) === id)) return;
        list.push({ id, username: username || `User_${id}` });
        saveScanList(list);
    }
    function removeFromScanList(id) {
        const list = getScanList().filter(u => String(u.id) !== String(id));
        saveScanList(list);
    }
    function isInScanList(id) {
        return getScanList().some(u => String(u.id) === String(id));
    }
    const SCAN_HISTORY_KEY = 'rem_scan_history_v2';
    function getHistory() {
        let hist = localStorage.getItem(SCAN_HISTORY_KEY);
        if (!hist) {
            const old = localStorage.getItem('rem_scan_history_v1');
            if (old) {
                localStorage.setItem(SCAN_HISTORY_KEY, old);
                hist = old;
            }
        }
        try { return JSON.parse(hist || '[]'); } catch { return []; }
    }
    function saveToHistory(name, results, link = "") {
        let hist = getHistory();
        const exIdx = hist.findIndex(h => h.name === name || (link && h.link === link));
        let id;
        if (exIdx > -1) {
            id = hist[exIdx].id;
            hist[exIdx].time = Date.now();
            hist[exIdx].count = results.length;
            if (link) hist[exIdx].link = link;
            const it = hist.splice(exIdx, 1)[0];
            hist.unshift(it);
        } else {
            id = Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
            hist.unshift({ id, name, link, time: Date.now(), count: results.length });
        }
        if (hist.length > 20) {
            const old = hist.pop();
            try { if (typeof GM_deleteValue !== 'undefined') GM_deleteValue('rem_scan_data_' + old.id); } catch (e) { }
            localStorage.removeItem('rem_scan_data_' + old.id);
        }
        try {
            localStorage.setItem(SCAN_HISTORY_KEY, JSON.stringify(hist));
            if (typeof GM_setValue !== 'undefined') GM_setValue('rem_scan_data_' + id, JSON.stringify(results));
            else localStorage.setItem('rem_scan_data_' + id, JSON.stringify(results));
        } catch (e) {
            console.error('History save error:', e);
            try { localStorage.setItem('rem_scan_data_' + id, JSON.stringify(results)); } catch (e2) { }
        }
        return id;
    }
    function loadFromHistory(id) {
        try {
            let raw = null;
            if (typeof GM_getValue !== 'undefined') raw = GM_getValue('rem_scan_data_' + id);
            if (!raw) {
                raw = localStorage.getItem('rem_scan_data_' + id);
            }
            if (!raw) {
                console.warn('REM: Данные сканирования не найдены для', id);
                return null;
            }
            return JSON.parse(raw);
        } catch (e) {
            console.error('REM: Ошибка парсинга истории', e);
            return null;
        }
    }
    function deleteFromHistory(id) {
        const hist = getHistory().filter(h => h.id !== id);
        localStorage.setItem(SCAN_HISTORY_KEY, JSON.stringify(hist));
        try { if (typeof GM_deleteValue !== 'undefined') GM_deleteValue('rem_scan_data_' + id); } catch (e) { }
        localStorage.removeItem('rem_scan_data_' + id);
    }
    function captureVideoFrame(videoUrl, container, seekTime = 1) {
        const video = document.createElement('video');
        video.crossOrigin = 'anonymous';
        video.muted = true;
        video.preload = 'auto';
        video.src = videoUrl;
        video.addEventListener('loadeddata', () => {
            video.currentTime = Math.min(seekTime, video.duration || seekTime);
        });
        video.addEventListener('seeked', () => {
            try {
                const canvas = document.createElement('canvas');
                canvas.width = video.videoWidth || 120;
                canvas.height = video.videoHeight || 180;
                const ctx = canvas.getContext('2d');
                ctx.drawImage(video, 0, 0, canvas.width, canvas.height);
                const img = document.createElement('img');
                img.src = canvas.toDataURL('image/webp', 0.8);
                img.style.cssText = 'width:100%;height:100%;object-fit:cover;';
                container.innerHTML = '';
                container.appendChild(img);
            } catch (e) { /* CORS */ }
            video.remove();
        });
        video.addEventListener('error', () => { video.remove(); });
    }
    function isVideoUrl(url) {
        if (!url) return false;
        return /\.(webm|mp4)([?#]|$)/i.test(url);
    }
    function renderCardMedia(fullImg, container) {
        if (!fullImg) return;
        if (isVideoUrl(fullImg)) {
            captureVideoFrame(fullImg, container);
        }
    }

    const RANK_ORDER = ['rank_f', 'rank_e', 'rank_d', 'rank_c', 'rank_b', 'rank_a', 'rank_s', 'rank_re'];
    const RANK_MAP = {
        'rank_f': { name: 'F', color: '#9ca3af' },
        'rank_e': { name: 'E', color: '#9ca3af' },
        'rank_d': { name: 'D', color: '#ffffff' },
        'rank_c': { name: 'C', color: '#4ade80' },
        'rank_b': { name: 'B', color: '#60a5fa' },
        'rank_a': { name: 'A', color: '#c084fc' },
        'rank_s': { name: 'S', color: '#fbbf24' },
        'rank_re': { name: 'RE', color: '#f59e0b' },
    };

    const STYLES = `
        .rem-online-dot {
            position:absolute!important;top:2px!important;right:2px!important;
            width:11px;height:11px;border-radius:50%;border:2px solid #000;
            z-index:101;pointer-events:none;
        }
        .rem-online-dot.online{background-color:#22c55e!important;}
        .rem-online-dot.offline{background-color:#ef4444!important;}

        .rem-own-badge {
            position:absolute!important;bottom:5px!important;left:5px!important;z-index:50!important;
            width:22px;height:22px;border-radius:50%;
            background-color:#22c55e;color:#fff;
            display:flex;align-items:center;justify-content:center;
            border:2px solid #fff;pointer-events:none;
            opacity:0;animation:remFadeIn .3s forwards;box-shadow:none!important;
        }
        .rem-own-badge svg{width:14px;height:14px;stroke-width:3;}

        .rem-wish-badge {
            position:absolute!important;bottom:5px!important;right:5px!important;z-index:50!important;
            width:22px;height:22px;border-radius:50%;
            background-color:#3b82f6;color:#fff;
            display:flex;align-items:center;justify-content:center;
            border:2px solid #fff;pointer-events:none;
            opacity:0;animation:remFadeIn .3s forwards;box-shadow:none!important;
        }
        .rem-wish-badge svg{width:14px;height:14px;stroke-width:3;}

        @keyframes remFadeIn{from{opacity:0;transform:scale(.5)}to{opacity:1;transform:scale(1)}}

        .rem-toggle {
            display:inline-flex;align-items:center;justify-content:center;
            margin-left:10px;padding:2px 8px;border-radius:6px;
            font-size:11px;font-weight:800;text-transform:uppercase;
            cursor:pointer;border:1px solid #3f3f46;color:#fff;height:22px;vertical-align:middle;
            transition:all .2s;
        }
        .rem-toggle.on{background-color:#15803d;border-color:#22c55e;}
        .rem-toggle.off{background-color:transparent;color:#71717a;border-color:#3f3f46;}
        .rem-toggle:hover{filter:brightness(1.2);}

        #rem-loader-status {
            position:fixed;bottom:20px;left:70px;z-index:999999;
            background:#18181b;color:#fff;padding:8px 16px;border-radius:20px;
            border:1px solid #27272a;font-size:13px;font-weight:500;
            box-shadow:0 4px 15px rgba(0,0,0,.6);display:none;align-items:center;gap:10px;
        }
        #rem-loader-status.active{display:flex;animation:slideIn .3s;}
        @keyframes slideIn{from{opacity:0;transform:translateX(-20px)}to{opacity:1;transform:translateX(0)}}

        html.rem-checking body>*:not(#rem-loader){display:none!important;}
        #rem-loader{position:fixed;inset:0;background:#0c0c0c;z-index:999999;display:flex;align-items:center;justify-content:center;color:#666;font-family:sans-serif;}

        body.rem-active main>*:not(#rem-inject){display:none!important;}

        #rem-inject{width:100%;max-width:650px;margin:0 auto;min-height:80vh;font-family:system-ui,-apple-system,sans-serif;padding:10px;}

        .rem-grid{display:grid;grid-template-columns:repeat(4,1fr)!important;gap:10px;padding-bottom:40px;}

        .rem-card{position:relative;aspect-ratio:2/3;background:#18181b;border:1px solid #27272a;border-radius:6px;overflow:hidden;cursor:pointer;transition:.2s;display:flex;}
        .rem-card:hover{border-color:#71717a;}
        .rem-card img{width:100%;height:100%;object-fit:cover;}

        .rem-fix-header{margin-bottom:24px;padding-bottom:16px;border-bottom:1px solid #27272a;}
        .rem-fix-title{font-size:24px;font-weight:700;color:#fff;margin:0 0 4px 0;}
        .rem-fix-meta{color:#a1a1aa;font-size:13px;display:flex;align-items:center;margin-top:8px;}
        .rem-sep{margin:0 8px;opacity:.5;}

        .rem-over{position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,.85);display:flex;align-items:center;justify-content:center;backdrop-filter:blur(2px);animation:F .2s;}
        .rem-box{position:relative;width:90%;max-width:450px;background:#0c0c0c;border:1px solid #27272a;border-radius:16px;padding:24px;display:flex;flex-direction:column;gap:20px;animation:Z .2s;}
        @keyframes F{from{opacity:0}to{opacity:1}} @keyframes Z{from{transform:scale(.95)}to{transform:scale(1)}}
        .rem-box-img{width:180px;aspect-ratio:2/3;margin:10px auto 0;border-radius:12px;overflow:hidden;border:1px solid #27272a;}
        .rem-box-img img{width:100%;height:100%;object-fit:cover;}
        .rem-info{text-align:center;display:flex;flex-direction:column;gap:4px;}
        .rem-lnk-m{color:#fff;font-size:14px;font-weight:500;text-decoration:none;opacity:.9;display:inline-block;}
        .rem-lnk-c{color:#fff;font-size:20px;font-weight:700;text-decoration:none;display:inline-block;}
        .rem-acts{display:flex;justify-content:center;gap:12px;margin-top:5px;}
        .rem-pill{height:36px;padding:0 16px;border-radius:99px;background:#27272a;color:#fff;border:none;display:flex;align-items:center;gap:6px;font-size:14px;font-weight:500;}
        .rem-sub{display:flex;flex-wrap:wrap;justify-content:center;gap:10px;margin-top:5px;}
        .rem-s-btn{height:36px;padding:0 16px;border-radius:99px;background:#27272a;color:#fff;text-decoration:none;font-size:14px;font-weight:500;display:flex;align-items:center;}
        .rem-close{position:absolute;top:12px;right:12px;width:32px;height:32px;border-radius:50%;background:#27272a;color:#fff;border:none;display:flex;align-items:center;justify-content:center;cursor:pointer;}

        #rem-cfg-btn {
            position:fixed;bottom:20px;left:20px;z-index:999999;
            width:40px;height:40px;border-radius:50%;
            background:#18181b;border:1px solid #27272a;color:#a1a1aa;
            display:flex;align-items:center;justify-content:center;
            cursor:pointer;transition:.2s;box-shadow:0 4px 10px rgba(0,0,0,.5);
            font-size:18px;
        }
        #rem-cfg-btn:hover{color:#fff;border-color:#71717a;transform:rotate(45deg);}

        #rem-settings-panel {
            position:fixed;bottom:70px;left:20px;z-index:999998;
            width:300px;background:#18181b;border:1px solid #27272a;
            border-radius:16px;padding:0;overflow:hidden;
            box-shadow:0 8px 32px rgba(0,0,0,.7);
            transform:scale(.95) translateY(10px);opacity:0;
            transition:transform .2s, opacity .2s;
            pointer-events:none;
        }
        #rem-settings-panel.open {
            transform:scale(1) translateY(0);opacity:1;pointer-events:all;
        }
        .rem-panel-header {
            padding:14px 16px 12px;border-bottom:1px solid #27272a;
            display:flex;align-items:center;justify-content:space-between;
        }
        .rem-panel-title {
            font-size:15px;font-weight:700;color:#fff;display:flex;align-items:center;gap:8px;
        }
        .rem-panel-title span.badge {
            font-size:10px;background:#27272a;color:#71717a;
            padding:2px 6px;border-radius:4px;font-weight:600;
        }
        .rem-panel-body { padding:8px 0; max-height:70vh; overflow-y:auto; scrollbar-width:thin; scrollbar-color:#3f3f46 transparent; }
        .rem-panel-body::-webkit-scrollbar { width:4px; }
        .rem-panel-body::-webkit-scrollbar-thumb { background:#3f3f46; border-radius:2px; }

        .rem-section-label {
            font-size:10px;font-weight:700;color:#52525b;text-transform:uppercase;
            letter-spacing:.08em;padding:8px 16px 4px;
        }

        .rem-row {
            display:flex;align-items:center;justify-content:space-between;
            padding:9px 16px;cursor:default;transition:background .15s;
            gap:10px;
        }
        .rem-row:hover{ background:rgba(255,255,255,.04); }
        .rem-row-info { display:flex;flex-direction:column;gap:1px;flex:1;min-width:0; }
        .rem-row-name { font-size:13px;color:#e4e4e7;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis; }
        .rem-row-desc { font-size:11px;color:#71717a;white-space:nowrap;overflow:hidden;text-overflow:ellipsis; }

        .rem-sw { position:relative;width:36px;height:20px;flex-shrink:0;cursor:pointer; }
        .rem-sw input { opacity:0;width:0;height:0;position:absolute; }
        .rem-sw-track {
            position:absolute;inset:0;border-radius:10px;background:#3f3f46;
            transition:background .2s;
        }
        .rem-sw input:checked + .rem-sw-track { background:#16a34a; }
        .rem-sw-thumb {
            position:absolute;width:14px;height:14px;border-radius:50%;background:#fff;
            top:3px;left:3px;transition:left .2s;pointer-events:none;
        }
        .rem-sw input:checked ~ .rem-sw-thumb { left:19px; }

        .rem-sub-row{padding:4px 16px 8px;display:flex;flex-direction:column;gap:4px;}
        .rem-sub-row-label{font-size:10px;color:#52525b;font-weight:600;}
        .rem-url-input{width:100%;height:30px;border-radius:6px;border:1px solid #27272a;background:#18181b;color:#e4e4e7;font-size:11px;padding:0 8px;outline:none;transition:.15s;}
        .rem-url-input:focus{border-color:#3b82f6;}
        .rem-url-input::placeholder{color:#3f3f46;}

        .rem-ex-cloud{margin-bottom:16px;}
        .rem-ex-cloud-item{display:flex;align-items:center;justify-content:space-between;padding:8px 12px;background:#18181b;border:1px solid #1e1e21;border-radius:8px;transition:.15s;cursor:pointer;}
        .rem-ex-cloud-item:hover{border-color:#3b82f6;background:rgba(59,130,246,.06);}
        .rem-ex-cloud-item-left{display:flex;align-items:center;gap:8px;min-width:0;}
        .rem-ex-cloud-item-name{font-size:13px;color:#e4e4e7;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
        .rem-ex-cloud-item-meta{font-size:11px;color:#52525b;white-space:nowrap;}
        .rem-ex-cloud-item-badge{font-size:9px;padding:2px 6px;border-radius:4px;background:rgba(96,165,250,.15);color:#60a5fa;font-weight:700;flex-shrink:0;}

        .rem-panel-footer {
            border-top:1px solid #27272a;padding:10px 16px;
            display:flex;gap:8px;flex-wrap:wrap;
        }
        .rem-action-btn {
            flex:1;min-width:0;height:32px;border-radius:8px;border:1px solid #3f3f46;
            background:transparent;color:#a1a1aa;font-size:11px;font-weight:600;
            cursor:pointer;transition:all .15s;white-space:nowrap;
            padding:0 4px;overflow:hidden;text-overflow:ellipsis;
        }
        .rem-action-btn:hover{ background:#27272a;color:#fff;border-color:#52525b; }
        .rem-action-btn.danger:hover{ background:#450a0a;color:#f87171;border-color:#7f1d1d; }

        .rem-profile-stat-badge {
            margin-top:6px;display:inline-flex;align-items:center;justify-content:center;gap:6px;
            background-color:#18181b;border:1px solid #27272a;padding:4px 10px;border-radius:6px;
            color:#a1a1aa;font-size:12px;font-weight:600;width:fit-content;align-self:center;
        }
        .rem-profile-stat-badge strong{color:#22c55e;margin-left:4px;}
        .rem-counting strong{color:#eab308;}

        .rem-progress-box{width:100%;margin:15px 0;background:#18181b;border:1px solid #27272a;border-radius:8px;padding:12px;display:flex;flex-direction:column;gap:8px;}
        .rem-progress-header{display:flex;justify-content:space-between;align-items:center;color:#fff;font-size:14px;font-weight:600;}
        .rem-progress-text span{color:#22c55e;}
        .rem-progress-track{width:100%;height:8px;background:#27272a;border-radius:4px;overflow:hidden;margin-bottom:4px;}
        .rem-progress-fill{height:100%;background:linear-gradient(90deg,#22c55e,#4ade80);border-radius:4px;width:0%;transition:width .5s ease-out;}
        .rem-rank-stats{display:flex;flex-wrap:nowrap;overflow-x:auto;gap:6px;margin-top:8px;padding-bottom:2px;-webkit-overflow-scrolling:touch;scrollbar-width:none;}
        .rem-rank-stats::-webkit-scrollbar{display:none;}
        .rem-rank-badge{display:inline-flex;align-items:center;border-radius:4px;padding:2px 8px;font-size:11px;font-weight:700;border:1px solid;background:rgba(0,0,0,.3);flex-shrink:0;white-space:nowrap;}

        #rem-inject{width:100%;max-width:650px;margin:0 auto;min-height:80vh;font-family:system-ui,-apple-system,sans-serif;padding:10px;}
        .rem-grid{display:grid;grid-template-columns:repeat(4,1fr)!important;gap:10px;padding-bottom:40px;}
        .rem-card{position:relative;aspect-ratio:2/3;background:#18181b;border:1px solid #27272a;border-radius:6px;overflow:hidden;cursor:pointer;transition:.2s;display:flex;}
        .rem-card:hover{border-color:#71717a;}
        .rem-card img{width:100%;height:100%;object-fit:cover;}

        .rem-fix-header{margin-bottom:24px;padding-bottom:16px;border-bottom:1px solid #27272a;}
        .rem-fix-title{font-size:24px;font-weight:700;color:#fff;margin:0 0 4px 0;}
        .rem-fix-meta{color:#a1a1aa;font-size:13px;display:flex;align-items:center;margin-top:8px;}
        .rem-sep{margin:0 8px;opacity:.5;}

        .rem-over{position:fixed;inset:0;z-index:99999;background:rgba(0,0,0,.85);display:flex;align-items:center;justify-content:center;backdrop-filter:blur(2px);animation:F .2s;}
        .rem-box{position:relative;width:90%;max-width:450px;background:#0c0c0c;border:1px solid #27272a;border-radius:16px;padding:24px;display:flex;flex-direction:column;gap:20px;animation:Z .2s;}
        @keyframes F{from{opacity:0}to{opacity:1}} @keyframes Z{from{transform:scale(.95)}to{transform:scale(1)}}
        .rem-box-img{width:180px;aspect-ratio:2/3;margin:10px auto 0;border-radius:12px;overflow:hidden;border:1px solid #27272a;}
        .rem-box-img img{width:100%;height:100%;object-fit:cover;}
        .rem-info{text-align:center;display:flex;flex-direction:column;gap:4px;}
        .rem-lnk-m{color:#fff;font-size:14px;font-weight:500;text-decoration:none;opacity:.9;display:inline-block;}
        .rem-lnk-c{color:#fff;font-size:20px;font-weight:700;text-decoration:none;display:inline-block;}
        .rem-acts{display:flex;justify-content:center;gap:12px;margin-top:5px;}
        .rem-pill{height:36px;padding:0 16px;border-radius:99px;background:#27272a;color:#fff;border:none;display:flex;align-items:center;gap:6px;font-size:14px;font-weight:500;}
        .rem-sub{display:flex;flex-wrap:wrap;justify-content:center;gap:10px;margin-top:5px;}
        .rem-s-btn{height:36px;padding:0 16px;border-radius:99px;background:#27272a;color:#fff;text-decoration:none;font-size:14px;font-weight:500;display:flex;align-items:center;}
        .rem-close{position:absolute;top:12px;right:12px;width:32px;height:32px;border-radius:50%;background:#27272a;color:#fff;border:none;display:flex;align-items:center;justify-content:center;cursor:pointer;}

        .rem-card-stats-row{display:flex;gap:8px;margin-top:10px;justify-content:center;width:100%;flex-wrap:wrap;}
        .rem-stat-bubble{background:#18181b;border:1px solid #27272a;border-radius:10px;padding:6px 10px;display:flex;flex-direction:column;align-items:center;min-width:75px;transition:.2s;}
        .rem-stat-bubble:hover{border-color:#3f3f46;}
        .rem-stat-num{font-size:16px;font-weight:800;line-height:1;}
        .rem-stat-lab{font-size:9px;color:#71717a;text-transform:uppercase;margin-top:4px;font-weight:600;}
        .rem-stat-num.loading{color:#3f3f46;}
        .rem-stat-bubble.owners .rem-stat-num{color:#22c55e;}
        .rem-stat-bubble.wants .rem-stat-num{color:#3b82f6;}
        .rem-stat-bubble.sales .rem-stat-num{color:#f59e0b;}


        #rem-exchange-panel{position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:999998;width:680px;max-width:94vw;max-height:88vh;background:#0f0f11;border:1px solid #27272a;border-radius:20px;display:none;flex-direction:column;overflow:hidden;box-shadow:0 24px 80px rgba(0,0,0,.7),0 0 0 1px rgba(255,255,255,.03);animation:Z .25s;}
        #rem-exchange-panel.open{display:flex;}
        .rem-ex-backdrop{position:fixed;inset:0;z-index:999997;background:rgba(0,0,0,.6);backdrop-filter:blur(3px);display:none;}
        .rem-ex-backdrop.open{display:block;}

        .rem-ex-header{padding:20px 24px 16px;display:flex;align-items:center;justify-content:space-between;border-bottom:1px solid #1e1e21;}
        .rem-ex-title{font-size:18px;font-weight:700;color:#fff;display:flex;align-items:center;gap:8px;}
        .rem-ex-close{width:32px;height:32px;border-radius:50%;background:#1e1e21;border:1px solid #2e2e33;color:#a1a1aa;display:flex;align-items:center;justify-content:center;cursor:pointer;transition:.2s;font-size:16px;}
        .rem-ex-close:hover{background:#27272a;color:#fff;border-color:#3f3f46;}

        .rem-ex-body{flex:1;overflow-y:auto;padding:16px 24px 24px;scrollbar-width:thin;scrollbar-color:#27272a transparent;}
        .rem-ex-body::-webkit-scrollbar{width:6px;}
        .rem-ex-body::-webkit-scrollbar-thumb{background:#27272a;border-radius:3px;}

        .rem-ex-input-row{display:flex;gap:8px;margin-bottom:16px;}
        .rem-ex-input{flex:1;background:#18181b;border:1px solid #27272a;border-radius:10px;color:#e4e4e7;font-size:13px;padding:10px 14px;outline:none;transition:border .15s;}
        .rem-ex-input:focus{border-color:#3b82f6;}
        .rem-ex-input::placeholder{color:#52525b;}
        .rem-ex-btn{padding:0 20px;border-radius:10px;border:none;font-size:13px;font-weight:600;cursor:pointer;transition:.15s;display:flex;align-items:center;gap:6px;}
        .rem-ex-btn.primary{background:linear-gradient(135deg,#3b82f6,#2563eb);color:#fff;}
        .rem-ex-btn.primary:hover{filter:brightness(1.1);transform:translateY(-1px);}
        .rem-ex-btn.secondary{background:#1e1e21;color:#a1a1aa;border:1px solid #27272a;}
        .rem-ex-btn.secondary:hover{background:#27272a;color:#fff;}
        .rem-ex-btn:disabled{opacity:.5;cursor:default;transform:none!important;filter:none!important;}

        .rem-ex-progress{width:100%;margin:12px 0 16px;text-align:center;}
        .rem-ex-pbar-track{width:100%;height:6px;background:#1e1e21;border-radius:3px;overflow:hidden;margin:8px 0;}
        .rem-ex-pbar-fill{height:100%;background:linear-gradient(90deg,#3b82f6,#60a5fa);border-radius:3px;width:0%;transition:width .3s;}
        .rem-ex-ptext{font-size:12px;color:#71717a;}
        .rem-ex-ptext strong{color:#a1a1aa;}

        .rem-ex-rank-bar{display:flex;gap:6px;flex-wrap:wrap;margin-bottom:16px;}
        .rem-ex-rank-btn{padding:6px 14px;border-radius:8px;background:#18181b;border:1px solid #27272a;color:#a1a1aa;font-size:12px;font-weight:600;cursor:pointer;transition:.15s;}
        .rem-ex-rank-btn:hover{border-color:#3f3f46;color:#fff;}
        .rem-ex-rank-btn.active{background:#3b82f6;border-color:#3b82f6;color:#fff;}
        .rem-ex-rank-count{font-size:10px;color:#71717a;margin-left:2px;}

        .rem-ex-summary{background:#18181b;border:1px solid #27272a;border-radius:12px;padding:14px 18px;margin-bottom:16px;display:flex;gap:20px;justify-content:center;flex-wrap:wrap;}
        .rem-ex-s-item{text-align:center;}
        .rem-ex-s-num{font-size:22px;font-weight:800;line-height:1;}
        .rem-ex-s-lab{font-size:10px;color:#71717a;text-transform:uppercase;font-weight:600;margin-top:2px;}

        .rem-ex-results{display:flex;flex-direction:column;gap:10px;}
        .rem-ex-card{background:#18181b;border:1px solid #27272a;border-radius:12px;overflow:hidden;transition:.15s;cursor:pointer;}
        .rem-ex-card:hover{border-color:#3f3f46;transform:translateY(-1px);box-shadow:0 4px 12px rgba(0,0,0,.3);}
        .rem-ex-card-inner{display:flex;gap:14px;padding:14px;}
        .rem-ex-card-img{width:60px;height:90px;border-radius:8px;overflow:hidden;flex-shrink:0;background:#27272a;}
        .rem-ex-card-img img{width:100%;height:100%;object-fit:cover;}
        .rem-ex-card-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px;}
        .rem-ex-card-name{font-size:14px;font-weight:700;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
        .rem-ex-card-rank{font-size:11px;font-weight:600;padding:2px 8px;border-radius:4px;display:inline-block;width:fit-content;}
        .rem-ex-card-traders{display:flex;flex-wrap:wrap;gap:6px;margin-top:4px;}
        .rem-ex-card-tag{font-size:11px;padding:3px 8px;border-radius:6px;font-weight:600;display:flex;align-items:center;gap:4px;}
        .rem-ex-card-tag.buyers{background:rgba(59,130,246,.15);color:#60a5fa;}
        .rem-ex-card-tag.sellers{background:rgba(249,115,22,.15);color:#fb923c;}
        .rem-ex-card-tag.owners{background:rgba(34,197,94,.15);color:#4ade80;}

        .rem-ex-detail{animation:F .2s;}
        .rem-ex-detail-header{display:flex;gap:16px;margin-bottom:16px;}
        .rem-ex-detail-img{width:100px;height:150px;border-radius:10px;overflow:hidden;flex-shrink:0;}
        .rem-ex-detail-img img{width:100%;height:100%;object-fit:cover;}
        .rem-ex-detail-meta{flex:1;display:flex;flex-direction:column;gap:6px;}
        .rem-ex-detail-name{font-size:18px;font-weight:700;color:#fff;}
        .rem-ex-detail-id{font-size:12px;color:#71717a;}
        .rem-ex-user-list{margin-top:8px;}
        .rem-ex-user-list h4{font-size:12px;font-weight:700;text-transform:uppercase;color:#71717a;margin:12px 0 6px;letter-spacing:.05em;}
        .rem-ex-user{display:flex;align-items:center;gap:8px;padding:6px 10px;border-radius:8px;transition:.1s;text-decoration:none;}
        .rem-ex-user:hover{background:#1e1e21;}
        .rem-ex-user-name{font-size:13px;color:#e4e4e7;font-weight:500;}
        .rem-ex-user-id{font-size:11px;color:#52525b;}

        .rem-ex-empty{text-align:center;padding:40px 20px;color:#52525b;font-size:14px;}
        .rem-ex-nav{display:flex;justify-content:center;gap:8px;margin-top:16px;}

        .rem-scan-btn{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:8px;border:1px solid #27272a;background:#18181b;color:#a1a1aa;font-size:11px;font-weight:600;cursor:pointer;transition:.2s;margin-top:6px;align-self:center;max-width:130px;min-height:36px;flex-shrink:1;}
        .rem-scan-btn:hover{border-color:#3f3f46;color:#fff;}
        .rem-scan-btn.active{background:rgba(59,130,246,.15);border-color:#3b82f6;color:#60a5fa;}
        .rem-scan-btn .rem-scan-label{white-space:normal;text-align:left;line-height:1.2;flex:1;}
        .rem-scan-btn .rem-scan-check{width:16px;height:16px;border-radius:4px;border:2px solid #3f3f46;display:flex;align-items:center;justify-content:center;transition:.2s;flex-shrink:0;}
        .rem-scan-btn.active .rem-scan-check{background:#3b82f6;border-color:#3b82f6;}
        .rem-scan-btn.active .rem-scan-check svg{display:block;}
        .rem-scan-btn .rem-scan-check svg{display:none;width:10px;height:10px;}

        .rem-ex-scanlist{margin-bottom:16px;}
        .rem-ex-scanlist-title{font-size:13px;font-weight:700;color:#a1a1aa;margin-bottom:8px;display:flex;align-items:center;gap:8px;}
        .rem-ex-scanlist-count{font-size:11px;color:#52525b;font-weight:500;}
        .rem-ex-scanlist-items{display:flex;flex-direction:column;gap:4px;max-height:200px;overflow-y:auto;scrollbar-width:thin;scrollbar-color:#27272a transparent;}
        .rem-ex-scanlist-items::-webkit-scrollbar{width:5px;}
        .rem-ex-scanlist-items::-webkit-scrollbar-thumb{background:#27272a;border-radius:3px;}
        .rem-ex-scanlist-item{display:flex;align-items:center;justify-content:space-between;padding:6px 10px;background:#18181b;border:1px solid #1e1e21;border-radius:8px;transition:.1s;}
        .rem-ex-scanlist-item:hover{border-color:#27272a;}
        .rem-ex-scanlist-item-info{display:flex;align-items:center;gap:8px;min-width:0;}
        .rem-ex-scanlist-item-name{font-size:13px;color:#e4e4e7;font-weight:500;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;}
        .rem-ex-scanlist-item-id{font-size:11px;color:#52525b;flex-shrink:0;}
        .rem-ex-scanlist-rm{width:24px;height:24px;border-radius:6px;border:none;background:transparent;color:#52525b;font-size:14px;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:.15s;flex-shrink:0;}
        .rem-ex-scanlist-rm:hover{background:#27272a;color:#ef4444;}
        .rem-ex-scanlist-actions{display:flex;gap:6px;margin-top:8px;}

        #rem-custom-bg {
            position:fixed;top:0;left:0;width:100%;height:100vh;
            object-fit:cover;object-position:center center;
            z-index:-9;pointer-events:none;
            will-change:transform;transform:translateZ(0);
        }

        .rem-slider {
            -webkit-appearance:none;appearance:none;
            width:100%;height:4px;border-radius:2px;
            background:#3f3f46;outline:none;cursor:pointer;
        }
        .rem-slider::-webkit-slider-thumb {
            -webkit-appearance:none;appearance:none;
            width:14px;height:14px;border-radius:50%;background:#22c55e;cursor:pointer;
        }
        .rem-slider::-moz-range-thumb {
            width:14px;height:14px;border-radius:50%;background:#22c55e;cursor:pointer;border:none;
        }
        .rem-url-input {
            width:100%;background:#0c0c0c;border:1px solid #3f3f46;border-radius:8px;
            color:#e4e4e7;font-size:12px;padding:6px 10px;box-sizing:border-box;
            outline:none;transition:border-color .15s;
        }
        .rem-url-input:focus { border-color:#22c55e; }
        .rem-url-input::placeholder { color:#52525b; }

        .rem-sub-row {
            padding:4px 16px 8px;
            display:flex;flex-direction:column;gap:6px;
        }
        .rem-sub-row-label {
            font-size:11px;color:#71717a;
            display:flex;justify-content:space-between;align-items:center;
        }
        .rem-upload-btn {
            width:100%;height:30px;border-radius:8px;border:1px dashed #3f3f46;
            background:transparent;color:#71717a;font-size:12px;font-weight:600;
            cursor:pointer;transition:all .15s;
        }
        .rem-upload-btn:hover { border-color:#52525b;color:#a1a1aa;background:#27272a; }
        .rem-select {
            background:#0c0c0c;border:1px solid #3f3f46;border-radius:8px;
            color:#e4e4e7;font-size:12px;padding:4px 8px;
            outline:none;cursor:pointer;
        }

        .rem-deck-count-badge {
            position:absolute!important;top:6px!important;left:6px!important;z-index:50!important;
            min-width:24px;height:24px;border-radius:12px;
            background:rgba(0,0,0,.75);color:#fff;
            display:flex;align-items:center;justify-content:center;
            padding:0 6px;font-size:12px;font-weight:700;
            border:1.5px solid rgba(255,255,255,.25);pointer-events:none;
            opacity:0;animation:remFadeIn .3s forwards;
            backdrop-filter:blur(4px);-webkit-backdrop-filter:blur(4px);
            letter-spacing:.3px;line-height:1;
        }
        .rem-deck-count-badge.has-count {
            background:rgba(34,197,94,.85);border-color:rgba(255,255,255,.4);
        }

        .rem-deck-inner-count {
            display:flex;align-items:center;justify-content:center;gap:6px;
            margin-bottom:8px;font-size:14px;font-weight:600;color:#a1a1aa;
        }
        .rem-deck-inner-count strong {
            color:#22c55e;font-size:16px;
        }
    `;
    const styleEl = document.createElement('style');
    styleEl.textContent = STYLES;
    (document.head || document.documentElement).appendChild(styleEl);

    const Manager = {
        covers: new Set(),
        wishCovers: new Set(),
        userId: null,
        loaded: false,
        wishLoaded: false,
        cacheMap: new Map(),
        onlineCache: new Map(),
        onlineProcessing: new Set(),

        async init() {
            this.createUI();
            this.userId = localStorage.getItem(MY_ID_KEY);

            const cache = localStorage.getItem(MY_INV_KEY);
            if (cache) {
                try {
                    const d = JSON.parse(cache);
                    this.covers = new Set(d.data);
                    this.loaded = true;
                    setTimeout(() => scanVisual(), 500);
                } catch (e) { }
            }

            const wishCache = localStorage.getItem(MY_WISH_KEY);
            if (wishCache) {
                try {
                    const wd = JSON.parse(wishCache);
                    this.wishCovers = new Set(wd.data);
                    this.wishLoaded = true;
                } catch (e) { }
            }

            if (!this.userId) { this.promptId(); return; }

            const cacheData = cache ? JSON.parse(cache) : null;
            if (!cacheData || !cacheData.time || Date.now() - cacheData.time > 1200000) {
                this.sync(true);
            }

            const wishCacheData = wishCache ? JSON.parse(wishCache) : null;
            if (!wishCacheData || !wishCacheData.time || Date.now() - wishCacheData.time > 1200000) {
                this.syncWishes(true);
            }

            setInterval(() => {
                this.sync(true);
                this.syncWishes(true);
            }, 1200000);
        },

        getFilename(url) { if (!url) return null; return url.split('/').pop().split('?')[0].replace(/\.(jpg|jpeg|png|webp|gif|webm|mp4)$/i, ''); },

        async sync(silent = false) {
            if (!this.userId) return;
            if (this.syncing) return;
            this.syncing = true;
            if (!silent) this.showStatus("Обновление...", true);
            let page = 1, run = true;
            const temp = new Set();
            while (run) {
                const url = `${API_DOMAIN}/api/v2/inventory/${this.userId}/?count=100&ordering=rank&page=${page}&type=cards`;
                if (!silent) this.showStatus(`Загрузка стр. ${page} (Найдено: ${temp.size})...`, true);
                const data = await this.req(url);
                if (!data) break;
                const list = data.results || [];
                if (!list.length) break;
                list.forEach(item => {
                    if (item.card && item.card.cover) {
                        if (item.card.cover.high) temp.add(this.getFilename(item.card.cover.high));
                        if (item.card.cover.mid) temp.add(this.getFilename(item.card.cover.mid));
                    }
                });
                if (!data.next) run = false;
                else { page++; await new Promise(r => setTimeout(r, 100)); }
            }
            this.covers = temp;
            this.loaded = true;
            localStorage.setItem(MY_INV_KEY, JSON.stringify({ time: Date.now(), data: Array.from(temp) }));
            if (!silent) {
                this.showStatus(`Готово!`, true);
                setTimeout(() => this.showStatus("", false), 2000);
            }
            scanVisual();
            this.syncing = false;
        },

        async syncWishes(silent = true) {
            if (!this.userId) return;
            if (this.syncingWishes) return;
            this.syncingWishes = true;
            let page = 1, run = true;
            const temp = new Set();
            while (run) {
                const url = `${SITE_DOMAIN}/api/v2/inventory/wishes/users/${this.userId}/?wish_type=1&page=${page}`;
                const data = await this.req(url);
                if (!data) break;
                const list = data.results || [];
                if (!list.length) break;
                list.forEach(item => {
                    if (item.card && item.card.cover) {
                        if (item.card.cover.high) temp.add(this.getFilename(item.card.cover.high));
                        if (item.card.cover.mid) temp.add(this.getFilename(item.card.cover.mid));
                    }
                });
                if (!data.next) run = false;
                else { page++; await new Promise(r => setTimeout(r, 100)); }
            }
            this.wishCovers = temp;
            this.wishLoaded = true;
            localStorage.setItem(MY_WISH_KEY, JSON.stringify({ time: Date.now(), data: Array.from(temp) }));
            scanVisual();
            this.syncingWishes = false;
        },

        req(url) {
            const finalUrl = url.startsWith('http') ? url : API_DOMAIN + '/' + url.replace(/^\//, '');
            return new Promise(resolve => {
                const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) || (navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1);

                if (isIOS || typeof GM_xmlhttpRequest === 'undefined') {
                    fetch(finalUrl, { headers: { "Accept": "application/json" }, credentials: "omit" })
                        .then(r => r.ok ? r.json() : null)
                        .then(resolve)
                        .catch(() => fallbackGM(finalUrl, resolve));
                } else {
                    fallbackGM(finalUrl, resolve);
                }

                function fallbackGM(u, res) {
                    if (typeof GM_xmlhttpRequest !== 'undefined') {
                        GM_xmlhttpRequest({
                            method: "GET", url: u, headers: { "Accept": "application/json" },
                            onload: r => { if (r.status == 200) try { res(JSON.parse(r.responseText)) } catch { res(null) } else res(null) },
                            onerror: () => res(null)
                        });
                    } else {
                        res(null);
                    }
                }
            });
        },

        async getUserInfo(uid) {
            if (this.cacheMap.has(uid)) return this.cacheMap.get(uid);
            const data = await this.req(`${API_DOMAIN}/api/v2/users/${uid}/`);
            if (data) this.cacheMap.set(uid, data);
            return data;
        },

        has(filename) { return this.loaded && filename && this.covers.has(filename); },

        hasWish(filename) { return this.wishLoaded && filename && this.wishCovers.has(filename) && !this.has(filename); },

        promptId() {
            const link = prompt("Remanga Fix: Вставьте ссылку на профиль:", "");
            if (link) {
                const m = link.match(/user\/(\d+)/);
                if (m && m[1]) { localStorage.setItem(MY_ID_KEY, m[1]); this.userId = m[1]; this.sync(); }
            }
        },

        showStatus(text, show) {
            const el = document.getElementById('rem-loader-status');
            if (!el) return;
            if (show) { el.innerText = text; el.classList.add('active'); } else el.classList.remove('active');
        },

        createUI() {
            if (document.getElementById('rem-cfg-btn')) return;
            applyCustomBackground();
            applyTheme();
            const btn = document.createElement('div');
            btn.id = 'rem-cfg-btn';
            btn.innerHTML = '⚙️';
            btn.title = 'Настройки расширения';
            btn.onclick = (e) => { e.stopPropagation(); togglePanel(); };
            document.body.appendChild(btn);

            const stat = document.createElement('div');
            stat.id = 'rem-loader-status';
            document.body.appendChild(stat);
            document.body.appendChild(buildSettingsPanel());
            document.addEventListener('click', (e) => {
                const panel = document.getElementById('rem-settings-panel');
                const cfgBtn = document.getElementById('rem-cfg-btn');
                if (panel && !panel.contains(e.target) && e.target !== cfgBtn) {
                    panel.classList.remove('open');
                }
            });
        }
    };

    function makeSwitchRow(key, name, desc) {
        const row = document.createElement('div');
        row.className = 'rem-row';

        const info = document.createElement('div');
        info.className = 'rem-row-info';
        info.innerHTML = `<div class="rem-row-name">${name}</div>${desc ? `<div class="rem-row-desc">${desc}</div>` : ''}`;

        const label = document.createElement('label');
        label.className = 'rem-sw';
        label.title = cfg[key] ? 'Включено' : 'Выключено';
        const input = document.createElement('input');
        input.type = 'checkbox';
        input.checked = cfg[key];
        const track = document.createElement('div');
        track.className = 'rem-sw-track';
        const thumb = document.createElement('div');
        thumb.className = 'rem-sw-thumb';
        label.appendChild(input);
        label.appendChild(track);
        label.appendChild(thumb);

        input.addEventListener('change', () => {
            saveSetting(key, input.checked);
            label.title = input.checked ? 'Включено' : 'Выключено';
        });

        row.appendChild(info);
        row.appendChild(label);
        return row;
    }

    function buildSettingsPanel() {
        const panel = document.createElement('div');
        panel.id = 'rem-settings-panel';

        panel.innerHTML = `
            <div class="rem-panel-header">
                <div class="rem-panel-title">⚙️ Настройки <span class="badge" style="font-size:10px;color:#71717a">v8.0.0</span></div>
            </div>
        `;

        const body = document.createElement('div');
        body.className = 'rem-panel-body';

        const lbl1 = document.createElement('div');
        lbl1.className = 'rem-section-label';
        lbl1.textContent = 'Пользователи';
        body.appendChild(lbl1);
        body.appendChild(makeSwitchRow('onlineStatus', '🟢 Онлайн-статус', 'Точка на аватарке пользователя'));
        body.appendChild(makeSwitchRow('profileStats', '🎴 Статистика профиля', 'Кол-во созданных карт на профиле'));
        body.appendChild(makeSwitchRow('deckStats', '📦 Статистика кейсов', 'Число открытых паков на профиле'));

        const lbl2 = document.createElement('div');
        lbl2.className = 'rem-section-label';
        lbl2.textContent = 'Карты';
        body.appendChild(lbl2);
        body.appendChild(makeSwitchRow('ownBadge', '✅ Метка коллекции', 'Галочка на картах из вашей коллекции'));
        body.appendChild(makeSwitchRow('wishBadge', '💙 Метка желаемого', 'Синяя метка на картах из списка "Хочу"'));
        body.appendChild(makeSwitchRow('cardStats', '📊 Статистика карты', 'Владельцы / Хотят / Продают в диалоге'));

        const lblSystem = document.createElement('div');
        lblSystem.className = 'rem-section-label';
        lblSystem.textContent = 'Система';
        body.appendChild(lblSystem);

        const upBtn = document.createElement('button');
        upBtn.className = 'rem-ex-btn primary';
        upBtn.style.cssText = 'width:100%; margin-bottom:12px;';
        upBtn.innerText = '🔄 Проверить обновление';
        upBtn.onclick = () => checkForUpdates();
        body.appendChild(upBtn);

        const lbl2b = document.createElement('div');
        lbl2b.className = 'rem-section-label';
        lbl2b.textContent = 'Обмены';
        body.appendChild(lbl2b);
        body.appendChild(makeSwitchRow('scanButton', '➕ Кнопка сканирования', 'Чекбокс добавления в профилях'));

        const exBtnRow = document.createElement('div');
        exBtnRow.className = 'rem-row';
        exBtnRow.style.cursor = 'pointer';
        const exBtnInfo = document.createElement('div');
        exBtnInfo.className = 'rem-row-info';
        exBtnInfo.innerHTML = `<div class="rem-row-name">🔥 Сканер обменов</div><div class="rem-row-desc">Поиск обменов среди пользователей / гильдии</div>`;
        exBtnRow.appendChild(exBtnInfo);
        const exArrow = document.createElement('span');
        exArrow.style.cssText = 'color:#52525b;font-size:16px;';
        exArrow.textContent = '→';
        exBtnRow.appendChild(exArrow);
        exBtnRow.onclick = () => { document.getElementById('rem-settings-panel')?.classList.remove('open'); openExchangePanel(); };
        body.appendChild(exBtnRow);

        const lbl3 = document.createElement('div');
        lbl3.className = 'rem-section-label';
        lbl3.textContent = 'Фон сайта';
        body.appendChild(lbl3);

        const bgToggleRow = makeSwitchRow('customBgEnabled', '🖼️ Кастомный фон', 'Своё видео, гифка или фото');
        body.appendChild(bgToggleRow);

        const bgSub = document.createElement('div');
        bgSub.className = 'rem-sub-row';
        bgSub.id = 'rem-bg-sub';
        bgSub.style.display = cfg.customBgEnabled ? 'flex' : 'none';

        const urlLabel = document.createElement('div');
        urlLabel.className = 'rem-sub-row-label';
        urlLabel.textContent = 'URL (видео .webm/.mp4, гифка, фото)';
        bgSub.appendChild(urlLabel);

        const urlInput = document.createElement('input');
        urlInput.type = 'text';
        urlInput.className = 'rem-url-input';
        urlInput.placeholder = 'https://... или вставьте ссылку';
        urlInput.value = cfg.customBgUrl || '';
        urlInput.addEventListener('change', () => {
            saveSetting('customBgUrl', urlInput.value.trim());
            applyCustomBackground();
        });
        bgSub.appendChild(urlInput);

        const uploadBtn = document.createElement('button');
        uploadBtn.className = 'rem-upload-btn';
        uploadBtn.textContent = '📂 Загрузить файл с компьютера';
        const fileInput = document.createElement('input');
        fileInput.type = 'file';
        fileInput.accept = 'image/*,video/*,.gif,.webm,.mp4,.webp';
        fileInput.style.display = 'none';
        fileInput.addEventListener('change', () => {
            const file = fileInput.files[0];
            if (!file) return;
            const reader = new FileReader();
            reader.onload = (e) => {
                const dataUrl = e.target.result;
                saveSetting('customBgUrl', dataUrl);
                urlInput.value = '(локальный файл: ' + file.name + ')';
                applyCustomBackground();
            };
            reader.readAsDataURL(file);
        });
        uploadBtn.onclick = () => fileInput.click();
        bgSub.appendChild(fileInput);
        bgSub.appendChild(uploadBtn);

        const opacLabel = document.createElement('div');
        opacLabel.className = 'rem-sub-row-label';
        const opacVal = document.createElement('span');
        opacVal.textContent = cfg.customBgOpacity + '%';
        opacLabel.innerHTML = 'Яркость фона: ';
        opacLabel.appendChild(opacVal);
        bgSub.appendChild(opacLabel);

        const opacSlider = document.createElement('input');
        opacSlider.type = 'range';
        opacSlider.className = 'rem-slider';
        opacSlider.min = 10; opacSlider.max = 100; opacSlider.step = 5;
        opacSlider.value = cfg.customBgOpacity;
        opacSlider.addEventListener('input', () => {
            opacVal.textContent = opacSlider.value + '%';
            saveSetting('customBgOpacity', Number(opacSlider.value));
            applyCustomBackground();
        });
        bgSub.appendChild(opacSlider);

        const blurLabel = document.createElement('div');
        blurLabel.className = 'rem-sub-row-label';
        const blurVal = document.createElement('span');
        blurVal.textContent = cfg.customBgBlur + 'px';
        blurLabel.innerHTML = 'Размытие: ';
        blurLabel.appendChild(blurVal);
        bgSub.appendChild(blurLabel);

        const blurSlider = document.createElement('input');
        blurSlider.type = 'range';
        blurSlider.className = 'rem-slider';
        blurSlider.min = 0; blurSlider.max = 20; blurSlider.step = 1;
        blurSlider.value = cfg.customBgBlur;
        blurSlider.addEventListener('input', () => {
            blurVal.textContent = blurSlider.value + 'px';
            saveSetting('customBgBlur', Number(blurSlider.value));
            applyCustomBackground();
        });
        bgSub.appendChild(blurSlider);

        const fitLabel = document.createElement('div');
        fitLabel.className = 'rem-sub-row-label';
        fitLabel.textContent = 'Масштаб:';
        const fitSelect = document.createElement('select');
        fitSelect.className = 'rem-select';
        [['cover', 'Заполнить'], ['contain', 'Вписать'], ['fill', 'Растянуть']].forEach(([v, t]) => {
            const opt = document.createElement('option');
            opt.value = v; opt.textContent = t;
            if (cfg.customBgFit === v) opt.selected = true;
            fitSelect.appendChild(opt);
        });
        fitSelect.addEventListener('change', () => {
            saveSetting('customBgFit', fitSelect.value);
            applyCustomBackground();
        });
        fitLabel.appendChild(fitSelect);
        bgSub.appendChild(fitLabel);

        const resetBgBtn = document.createElement('button');
        resetBgBtn.className = 'rem-upload-btn';
        resetBgBtn.style.borderStyle = 'solid';
        resetBgBtn.textContent = '🗑️ Убрать фон';
        resetBgBtn.onclick = () => {
            saveSetting('customBgUrl', '');
            saveSetting('customBgEnabled', false);
            urlInput.value = '';
            const sw = bgToggleRow.querySelector('input[type=checkbox]');
            if (sw) sw.checked = false;
            document.getElementById('rem-bg-sub').style.display = 'none';
            applyCustomBackground();
        };
        bgSub.appendChild(resetBgBtn);

        body.appendChild(bgSub);

        bgToggleRow.querySelector('input').addEventListener('change', function () {
            document.getElementById('rem-bg-sub').style.display = this.checked ? 'flex' : 'none';
            if (this.checked) applyCustomBackground();
            else removeCustomBackground();
        });

        const lblTheme = document.createElement('div');
        lblTheme.className = 'rem-section-label';
        lblTheme.textContent = 'Оформление сайта';
        body.appendChild(lblTheme);

        const themeRow = document.createElement('div');
        themeRow.style.cssText = 'padding:4px 16px 8px;display:flex;flex-direction:column;gap:8px;';

        const themeLabel = document.createElement('div');
        themeLabel.style.cssText = 'font-size:11px;color:#71717a;';
        themeLabel.textContent = 'Цвет кнопок (Основа):';
        themeRow.appendChild(themeLabel);

        const presetGrid = document.createElement('div');
        presetGrid.style.cssText = 'display:flex;flex-wrap:wrap;gap:6px;';
        THEME_PRESETS.forEach(preset => {
            const btn = document.createElement('button');
            btn.title = preset.name;
            btn.style.cssText = `
                width:22px;height:22px;border-radius:50%;cursor:pointer;
                border:2px solid ${cfg.themeButtonBg === preset.value ? '#fff' : 'transparent'};
                background:${preset.value || '#3f3f46'};
                transition:all .15s;outline:none;flex-shrink:0;
            `;
            if (!preset.value) {
                btn.textContent = '✕';
                btn.style.fontSize = '10px';
                btn.style.color = '#a1a1aa';
            }
            btn.onclick = () => {
                saveSetting('themeButtonBg', preset.value);
                applyTheme();
                presetGrid.querySelectorAll('button').forEach((b, i) => {
                    b.style.borderColor = THEME_PRESETS[i].value === preset.value ? '#fff' : 'transparent';
                });
                customBtnInput.value = preset.value || '#3b82f6';
            };
            presetGrid.appendChild(btn);
        });
        themeRow.appendChild(presetGrid);

        const customBtnRow = document.createElement('div');
        customBtnRow.style.cssText = 'display:flex;align-items:center;gap:8px;';
        const customBtnLabel = document.createElement('span');
        customBtnLabel.style.cssText = 'font-size:11px;color:#71717a;white-space:nowrap;width:100px;';
        customBtnLabel.textContent = 'Свой цвет кнопок:';
        const customBtnInput = document.createElement('input');
        customBtnInput.type = 'color';
        customBtnInput.value = cfg.themeButtonBg || '#3b82f6';
        customBtnInput.style.cssText = 'width:32px;height:24px;border:1px solid #3f3f46;border-radius:6px;background:#0c0c0c;cursor:pointer;padding:2px;';
        const customBtnReset = document.createElement('button');
        customBtnReset.innerHTML = '✕';
        customBtnReset.style.cssText = 'background:none;border:none;color:#a1a1aa;cursor:pointer;font-size:12px;outline:none;';
        customBtnReset.title = 'Сбросить цвет кнопок';
        customBtnReset.onclick = () => { saveSetting('themeButtonBg', ''); customBtnInput.value = '#3b82f6'; applyTheme(); presetGrid.querySelectorAll('button').forEach(b => b.style.borderColor = 'transparent'); };
        customBtnInput.addEventListener('input', () => {
            saveSetting('themeButtonBg', customBtnInput.value);
            applyTheme();
            presetGrid.querySelectorAll('button').forEach((b, i) => {
                b.style.borderColor = THEME_PRESETS[i].value === customBtnInput.value ? '#fff' : 'transparent';
            });
        });
        customBtnRow.appendChild(customBtnLabel);
        customBtnRow.appendChild(customBtnInput);
        customBtnRow.appendChild(customBtnReset);
        themeRow.appendChild(customBtnRow);

        const customAccentRow = document.createElement('div');
        customAccentRow.style.cssText = 'display:flex;align-items:center;gap:8px;margin-top:4px;';
        const customAccentLabel = document.createElement('span');
        customAccentLabel.style.cssText = 'font-size:11px;color:#71717a;white-space:nowrap;width:100px;';
        customAccentLabel.textContent = 'Цвет акцентов:';
        const customAccentInput = document.createElement('input');
        customAccentInput.type = 'color';
        customAccentInput.value = cfg.themeAccent || '#3b82f6';
        customAccentInput.style.cssText = 'width:32px;height:24px;border:1px solid #3f3f46;border-radius:6px;background:#0c0c0c;cursor:pointer;padding:2px;';
        const customAccentReset = document.createElement('button');
        customAccentReset.innerHTML = '✕';
        customAccentReset.style.cssText = 'background:none;border:none;color:#a1a1aa;cursor:pointer;font-size:12px;outline:none;';
        customAccentReset.title = 'Сбросить цвет акцентов';
        customAccentReset.onclick = () => { saveSetting('themeAccent', ''); customAccentInput.value = '#3b82f6'; applyTheme(); };
        customAccentInput.addEventListener('input', () => {
            saveSetting('themeAccent', customAccentInput.value);
            applyTheme();
        });
        customAccentRow.appendChild(customAccentLabel);
        customAccentRow.appendChild(customAccentInput);
        customAccentRow.appendChild(customAccentReset);
        themeRow.appendChild(customAccentRow);

        const nameColorRow = document.createElement('div');
        nameColorRow.style.cssText = 'display:flex;align-items:center;gap:8px;margin-top:4px;';
        const nameColorLabel = document.createElement('span');
        nameColorLabel.style.cssText = 'font-size:11px;color:#71717a;white-space:nowrap;width:100px;';
        nameColorLabel.textContent = 'Цвет имени:';
        const nameColorInput = document.createElement('input');
        nameColorInput.type = 'color';
        nameColorInput.value = cfg.themeNameColor || '#ffffff';
        nameColorInput.style.cssText = 'width:32px;height:24px;border:1px solid #3f3f46;border-radius:6px;background:#0c0c0c;cursor:pointer;padding:2px;';
        const nameColorReset = document.createElement('button');
        nameColorReset.innerHTML = '✕';
        nameColorReset.style.cssText = 'background:none;border:none;color:#a1a1aa;cursor:pointer;font-size:12px;outline:none;';
        nameColorReset.title = 'Сбросить цвет имени';
        nameColorReset.onclick = () => { saveSetting('themeNameColor', ''); nameColorInput.value = '#ffffff'; applyTheme(); };
        nameColorInput.addEventListener('input', () => { saveSetting('themeNameColor', nameColorInput.value); applyTheme(); });
        nameColorRow.appendChild(nameColorLabel);
        nameColorRow.appendChild(nameColorInput);
        nameColorRow.appendChild(nameColorReset);
        themeRow.appendChild(nameColorRow);

        const nameFontRow = document.createElement('div');
        nameFontRow.style.cssText = 'display:flex;align-items:center;gap:8px;margin-top:4px;';
        const nameFontLabel = document.createElement('span');
        nameFontLabel.style.cssText = 'font-size:11px;color:#71717a;white-space:nowrap;width:100px;';
        nameFontLabel.textContent = 'Шрифт имени:';

        const nameFontInput = document.createElement('select');
        nameFontInput.style.cssText = 'flex:1;height:24px;border:1px solid #3f3f46;border-radius:6px;background:#0c0c0c;color:#fff;padding:0 6px;font-size:12px;outline:none;cursor:pointer;';
        const fonts = [
            { v: '', t: 'По умолчанию' },
            { v: 'Comic Sans MS', t: 'Comic Sans' },
            { v: 'Caveat', t: 'Caveat (Рукописный)' },
            { v: 'Comfortaa', t: 'Comfortaa (Округлый)' },
            { v: 'Lobster', t: 'Lobster (Объемный)' },
            { v: 'Pacifico', t: 'Pacifico (Винтаж)' },
            { v: 'Oswald', t: 'Oswald (Строгий)' },
            { v: 'Press Start 2P', t: 'Пиксельный (8-bit)' },
            { v: 'Marmelad', t: 'Marmelad (Плавный)' },
            { v: 'Russo One', t: 'Russo One (Жирный/Квадратный)' },
            { v: 'Jura', t: 'Jura (Техно)' },
            { v: 'Marck Script', t: 'Marck Script (Каллиграфия)' },
            { v: 'Philosopher', t: 'Philosopher (Изящный)' },
            { v: 'Amatic SC', t: 'Amatic SC (Рисованный узкий)' },
            { v: 'Neucha', t: 'Neucha (Веселый)' },
            { v: 'Underdog', t: 'Underdog (Необычный)' }
        ];
        fonts.forEach(f => {
            const opt = document.createElement('option');
            opt.value = opt.textContent = f.v;
            opt.textContent = f.t;
            if (cfg.themeNameFont === f.v) opt.selected = true;
            nameFontInput.appendChild(opt);
        });

        const nameFontReset = document.createElement('button');
        nameFontReset.innerHTML = '✕';
        nameFontReset.style.cssText = 'background:none;border:none;color:#a1a1aa;cursor:pointer;font-size:12px;outline:none;';
        nameFontReset.title = 'Сбросить шрифт имени';
        nameFontReset.onclick = () => { saveSetting('themeNameFont', ''); nameFontInput.value = ''; applyTheme(); };
        nameFontInput.addEventListener('change', () => { saveSetting('themeNameFont', nameFontInput.value); applyTheme(); });
        nameFontRow.appendChild(nameFontLabel);
        nameFontRow.appendChild(nameFontInput);
        nameFontRow.appendChild(nameFontReset);
        themeRow.appendChild(nameFontRow);

        const menuBgRow = document.createElement('div');
        menuBgRow.style.cssText = 'display:flex;align-items:center;gap:8px;margin-top:4px;';
        const menuBgLabel = document.createElement('span');
        menuBgLabel.style.cssText = 'font-size:11px;color:#71717a;white-space:nowrap;flex:1;';
        menuBgLabel.textContent = 'Фон меню и карточек:';
        const menuBgInput = document.createElement('input');
        menuBgInput.type = 'color';
        menuBgInput.value = cfg.themeMenuBg || '#18181b';
        menuBgInput.style.cssText = 'width:32px;height:24px;border:1px solid #3f3f46;border-radius:6px;background:#0c0c0c;cursor:pointer;padding:2px;';
        const menuBgReset = document.createElement('button');
        menuBgReset.innerHTML = '✕';
        menuBgReset.style.cssText = 'background:none;border:none;color:#a1a1aa;cursor:pointer;font-size:12px;outline:none;';
        menuBgReset.title = 'Сбросить фон меню';
        menuBgReset.onclick = () => { saveSetting('themeMenuBg', ''); menuBgInput.value = '#18181b'; applyTheme(); };
        menuBgInput.addEventListener('input', () => { saveSetting('themeMenuBg', menuBgInput.value); applyTheme(); });
        menuBgRow.appendChild(menuBgLabel);
        menuBgRow.appendChild(menuBgInput);
        menuBgRow.appendChild(menuBgReset);
        themeRow.appendChild(menuBgRow);

        const siteBgRow = document.createElement('div');
        siteBgRow.style.cssText = 'display:flex;align-items:center;gap:8px;margin-top:4px;';
        const siteBgLabel = document.createElement('span');
        siteBgLabel.style.cssText = 'font-size:11px;color:#71717a;white-space:nowrap;flex:1;';
        siteBgLabel.textContent = 'Фон сайта (без картинки):';
        const siteBgInput = document.createElement('input');
        siteBgInput.type = 'color';
        siteBgInput.value = cfg.themeSiteBg || '#09090b';
        siteBgInput.style.cssText = 'width:32px;height:24px;border:1px solid #3f3f46;border-radius:6px;background:#0c0c0c;cursor:pointer;padding:2px;';
        const siteBgReset = document.createElement('button');
        siteBgReset.innerHTML = '✕';
        siteBgReset.style.cssText = 'background:none;border:none;color:#a1a1aa;cursor:pointer;font-size:12px;outline:none;';
        siteBgReset.title = 'Сбросить фон сайта';
        siteBgReset.onclick = () => { saveSetting('themeSiteBg', ''); siteBgInput.value = '#09090b'; applyTheme(); };
        siteBgInput.addEventListener('input', () => { saveSetting('themeSiteBg', siteBgInput.value); applyTheme(); });
        siteBgRow.appendChild(siteBgLabel);
        siteBgRow.appendChild(siteBgInput);
        siteBgRow.appendChild(siteBgReset);
        themeRow.appendChild(siteBgRow);

        body.appendChild(themeRow);

        const lbl4 = document.createElement('div');
        lbl4.className = 'rem-section-label';
        lbl4.textContent = 'Исправления';
        body.appendChild(lbl4);
        body.appendChild(makeSwitchRow('fixMode', '🔧 FIX: переход на тайтл', 'Перейти на карты тайтла вместо простой ссылки'));

        panel.appendChild(body);

        const footer = document.createElement('div');
        footer.className = 'rem-panel-footer';

        const btnRefresh = document.createElement('button');
        btnRefresh.className = 'rem-action-btn';
        btnRefresh.textContent = '🔄 Обновить';
        btnRefresh.title = 'Принудительно перезагрузить инвентарь';
        btnRefresh.onclick = () => {
            localStorage.removeItem(MY_INV_KEY);
            localStorage.removeItem(MY_WISH_KEY);
            Manager.loaded = false;
            Manager.wishLoaded = false;
            Manager.covers = new Set();
            Manager.wishCovers = new Set();
            Manager.sync();
            Manager.syncWishes();
            document.getElementById('rem-settings-panel')?.classList.remove('open');
        };

        const btnProfile = document.createElement('button');
        btnProfile.className = 'rem-action-btn';
        btnProfile.textContent = '👤 Профиль';
        btnProfile.title = 'Ввести ссылку на другой профиль';
        btnProfile.onclick = () => {
            localStorage.removeItem(MY_INV_KEY);
            localStorage.removeItem(MY_WISH_KEY);
            localStorage.removeItem(MY_ID_KEY);
            document.getElementById('rem-settings-panel')?.classList.remove('open');
            Manager.userId = null;
            Manager.loaded = false;
            Manager.wishLoaded = false;
            Manager.covers = new Set();
            Manager.wishCovers = new Set();
            Manager.promptId();
        };

        const btnReset = document.createElement('button');
        btnReset.className = 'rem-action-btn danger';
        btnReset.textContent = '🗑️ Сброс';
        btnReset.title = 'Удалить все данные расширения и перезагрузить страницу';
        btnReset.onclick = () => {
            if (confirm('Сбросить все данные расширения?')) {
                localStorage.removeItem(MY_INV_KEY);
                localStorage.removeItem(MY_WISH_KEY);
                localStorage.removeItem(MY_ID_KEY);
                location.reload();
            }
        };

        footer.appendChild(btnRefresh);
        footer.appendChild(btnProfile);
        footer.appendChild(btnReset);
        panel.appendChild(footer);

        return panel;
    }

    function togglePanel() {
        const panel = document.getElementById('rem-settings-panel');
        if (!panel) return;
        panel.classList.toggle('open');
    }

    function applyCustomBackground() {
        if (!cfg.customBgEnabled || !cfg.customBgUrl) { removeCustomBackground(); return; }

        const url = cfg.customBgUrl;
        const opacity = (cfg.customBgOpacity ?? 80) / 100;
        const blur = cfg.customBgBlur ?? 0;
        const fit = cfg.customBgFit || 'cover';

        removeCustomBackground();

        const isVideo = /\.(webm|mp4|ogg|ogv)(\?.*)?$/.test(url) || url.startsWith('data:video');

        let bgStyle = document.getElementById('rem-bg-override');
        if (!bgStyle) {
            bgStyle = document.createElement('style');
            bgStyle.id = 'rem-bg-override';
            (document.head || document.documentElement).appendChild(bgStyle);
        }
        bgStyle.textContent = `
            body, #__next,
            [data-theme="light"] body, [data-theme="dark"] body,
            [data-sentry-element="AppLayoutRoot"],
            [data-sentry-component="AppLayoutRoot"],
            [data-sentry-element="AppLayoutContent"],
            [data-sentry-element="EntityLayoutRoot"] {
                 background: transparent !important;
                 background-color: transparent !important;
                 background-image: none !important;
            }

            .cs-layout-root::before {
                 display: none !important;
                 background: none !important;
            }

            [data-sentry-component="Header"], header {
                background-color: var(--chakra-colors-gray-800, #18181b) !important;
                backdrop-filter: none !important;
            }
            [data-theme="light"] [data-sentry-component="Header"],
            [data-theme="light"] header {
                background-color: var(--chakra-colors-white, #ffffff) !important;
            }

            [data-sentry-component="WallpaperBackground"],
            .custom-background-video, video.custom-background-video {
                z-index: -2 !important;
            }
        `;

        let el;
        if (isVideo) {
            el = document.createElement('video');
            el.src = url;
            el.autoplay = true;
            el.loop = true;
            el.muted = true;
            el.playsInline = true;
            el.play().catch(() => { });
        } else {
            el = document.createElement('img');
            el.src = url;
            el.onerror = () => console.warn('REM BG: не удалось загрузить фон:', url.substring(0, 80));
        }

        el.id = 'rem-custom-bg';
        el.style.cssText = [
            'position:fixed',
            'top:0', 'left:0',
            'width:100%', 'height:100vh',
            `object-fit:${fit}`,
            'object-position:center center',
            'z-index:-1',
            'pointer-events:none',
            'will-change:transform',
            'transform:translateZ(0)',
            `opacity:${opacity}`,
            `filter:${blur > 0 ? 'blur(' + blur + 'px)' : 'none'}`,
            'transition:opacity .3s',
        ].join(';');

        document.body.insertBefore(el, document.body.firstChild);
    }

    function removeCustomBackground() {
        const old = document.getElementById('rem-custom-bg');
        if (old) old.remove();
        const bgStyle = document.getElementById('rem-bg-override');
        if (bgStyle) bgStyle.remove();
    }

    function hexToHsl(hex) {
        let r = parseInt(hex.slice(1, 3), 16) / 255,
            g = parseInt(hex.slice(3, 5), 16) / 255,
            b = parseInt(hex.slice(5, 7), 16) / 255;
        const max = Math.max(r, g, b), min = Math.min(r, g, b);
        let h, s, l = (max + min) / 2;
        if (max === min) { h = s = 0; } else {
            const d = max - min; s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
            switch (max) { case r: h = (g - b) / d + (g < b ? 6 : 0); break; case g: h = (b - r) / d + 2; break; default: h = (r - g) / d + 4; }
            h /= 6;
        }
        return `${Math.round(h * 360)} ${Math.round(s * 100)}% ${Math.round(l * 100)}%`;
    }

    function applyTheme() {
        let themeEl = document.getElementById('rem-theme-override');
        if (!themeEl) {
            themeEl = document.createElement('style');
            themeEl.id = 'rem-theme-override';
            (document.head || document.documentElement).appendChild(themeEl);
        }

        let css = '';

        if (cfg.themeNameFont && cfg.themeNameFont !== '') {
            if (cfg.themeNameFont !== 'Comic Sans MS') {
                const fontNameUrl = cfg.themeNameFont.replace(/ /g, '+');
                css += `@import url('https://fonts.googleapis.com/css2?family=${fontNameUrl}&display=swap');\n`;
            }
        }

        if (cfg.themeAccent) {
            const accent = cfg.themeAccent;
            let darker = accent;
            const darkerMatch = accent.match(/#([0-9a-f]{6})/i);
            if (darkerMatch) {
                const dr = Math.max(0, parseInt(darkerMatch[1].slice(0, 2), 16) - 30),
                    dg = Math.max(0, parseInt(darkerMatch[1].slice(2, 4), 16) - 30),
                    db = Math.max(0, parseInt(darkerMatch[1].slice(4, 6), 16) - 30);
                darker = '#' + [dr, dg, db].map(v => v.toString(16).padStart(2, '0')).join('');
            }
            css += `
                input:checked + .rem-sw-track { background: ${accent} !important; }
                .rem-progress-fill { background: linear-gradient(90deg, ${accent}, ${darker}) !important; }
            `;
        }

        if (cfg.themeButtonBg) {
            const btnBg = cfg.themeButtonBg;
            let darker = btnBg;
            const darkerMatch = btnBg.match(/#([0-9a-f]{6})/i);
            if (darkerMatch) {
                const dr = Math.max(0, parseInt(darkerMatch[1].slice(0, 2), 16) - 30),
                    dg = Math.max(0, parseInt(darkerMatch[1].slice(2, 4), 16) - 30),
                    db = Math.max(0, parseInt(darkerMatch[1].slice(4, 6), 16) - 30);
                darker = '#' + [dr, dg, db].map(v => v.toString(16).padStart(2, '0')).join('');
            }
            css += `
                .bg-primary,
                [data-state="active"][class*="bg-primary"],
                [data-state="open"][class*="bg-primary"],
                [aria-selected="true"][class*="bg-primary"] {
                    background-color: ${btnBg} !important;
                    border-color: ${btnBg} !important;
                    color: #fff !important;
                }
                .bg-primary:hover,
                [data-state="active"][class*="bg-primary"]:hover,
                [data-state="open"][class*="bg-primary"]:hover,
                [aria-selected="true"][class*="bg-primary"]:hover {
                    background-color: ${darker} !important;
                    border-color: ${darker} !important;
                }
            `;
        }

        if (cfg.themeNameColor || cfg.themeNameFont) {
            css += `
                .cs-layout-title-text, .cs-layout-title .cs-text {
                    ${cfg.themeNameColor ? `color: ${cfg.themeNameColor} !important;` : ''}
                    ${cfg.themeNameFont ? `font-family: "${cfg.themeNameFont}", sans-serif !important;` : ''}
                }
            `;
        }

        if (cfg.themeMenuBg) {
            const mBg = hexToHsl(cfg.themeMenuBg);
            css += `
                :root {
                    --popover: ${mBg} !important;
                    --card: ${mBg} !important;
                    --secondary: ${mBg} !important;
                    --muted: ${mBg} !important;
                }
                .bg-popover, .bg-secondary, .bg-card, .bg-muted, .cs-account-menu, [data-radix-menu-content] {
                    background-color: ${cfg.themeMenuBg} !important;
                }
                .rem-settings-panel, .rem-box, .rem-progress-box {
                    background-color: ${cfg.themeMenuBg} !important;
                    border-color: rgba(255,255,255,0.1) !important;
                }
            `;
        }

        if (cfg.themeSiteBg && !cfg.customBgEnabled) {
            const sBg = hexToHsl(cfg.themeSiteBg);
            css += `
                :root {
                    --background: ${sBg} !important;
                }
                body, #__next, .bg-background {
                    background-color: ${cfg.themeSiteBg} !important;
                }
            `;
        }

        themeEl.textContent = css;
    }

    function removeTheme() {
        const el = document.getElementById('rem-theme-override');
        if (el) el.textContent = '';
    }

    const HOT_RANKS_EX = ['RE', 'S', 'A', 'B', 'C', 'D', 'E', 'F'];
    function getCardRankLetter(card) {
        let rk = card.rank || card.card_rank || '';
        if (typeof rk === 'object') rk = rk.name || rk.rank || '';
        rk = String(rk).trim().toUpperCase();
        if (!rk) return '?';
        const m = rk.match(/RANK_([A-Z]+)/i);
        if (m) return m[1].toUpperCase();
        for (const l of HOT_RANKS_EX) if (rk.includes(l)) return l;
        return rk.slice(0, 2) || '?';
    }
    function getCardCharName(card) {
        if (card.character && typeof card.character === 'object' && card.character.name) return card.character.name;
        return card.name || '';
    }

    function isBetterCardInfo(newC, oldC) {
        if (!oldC) return true;
        const newN = getCardCharName(newC);
        const oldN = getCardCharName(oldC);
        const isGeneric = (n) => !n || n.startsWith('Карта #');
        if (!isGeneric(newN) && isGeneric(oldN)) return true;
        if (newC.character && !oldC.character) return true;
        return false;
    }

    async function fetchUserTradeData(userId) {
        const DOMAIN = SITE_DOMAIN;
        const fetchPages = async (type) => {
            let items = [], pg = 1, run = true;
            while (run) {
                const url = `${DOMAIN}/api/v2/inventory/wishes/users/${userId}/?wish_type=${type}&page=${pg}`;
                const data = await Manager.req(url);
                if (!data) break;
                const list = data.results || [];
                if (!list.length) break;
                list.forEach(it => { if (it.card) items.push(it.card); });
                if (!data.next) run = false; else { pg++; await new Promise(r => setTimeout(r, 80)); }
            }
            return items;
        };
        const fetchInv = async () => {
            let items = [], pg = 1, run = true;
            while (run) {
                const url = `${API_DOMAIN}/api/v2/inventory/${userId}/?type=cards&count=50&page=${pg}`;
                const data = await Manager.req(url);
                if (!data) break;
                const list = data.results || [];
                if (!list.length) break;
                list.forEach(it => { if (it.card) items.push(it.card); });
                if (!data.next) run = false; else { pg++; await new Promise(r => setTimeout(r, 50)); }
            }
            return items;
        };
        let username = `User_${userId}`;
        let sex = 0;
        const udata = await Manager.req(`${API_DOMAIN}/api/v2/users/${userId}/`);
        if (udata) {
            username = udata.username || (udata.content && udata.content.username) || username;
            const maybeSex = udata.sex !== undefined ? udata.sex : (udata.content && udata.content.sex !== undefined ? udata.content.sex : 0);
            sex = parseInt(maybeSex) || 0;
        }
        const [wants, offers, inventory] = await Promise.all([fetchPages(1), fetchPages(2), fetchInv()]);
        return { profile: { username, id: userId, sex }, wants, offers, inventory };
    }

    async function gatherTradesEx(userIds, progressCb) {
        const usersData = {};
        let done = 0;
        const total = userIds.length;
        const queue = [...userIds];
        const workers = [];
        for (let i = 0; i < Math.min(4, queue.length); i++) {
            workers.push((async () => {
                while (queue.length > 0) {
                    const uid = queue.shift();
                    try {
                        const data = await fetchUserTradeData(uid);
                        if (data) usersData[uid] = data;
                    } catch (e) { }
                    done++;
                    if (progressCb) progressCb(done, total);
                }
            })());
        }
        await Promise.all(workers);
        const cardsDb = {};
        for (const [uid, data] of Object.entries(usersData)) {
            const prof = data.profile;
            for (const c of data.wants) {
                const cid = String(c.id);
                if (!cid) continue;
                if (!cardsDb[cid]) cardsDb[cid] = { card: c, wants: [], offers: [], inventory: [] };
                else if (isBetterCardInfo(c, cardsDb[cid].card)) cardsDb[cid].card = c;
                cardsDb[cid].wants.push(prof);
            }
            for (const c of data.offers) {
                const cid = String(c.id);
                if (!cid) continue;
                if (!cardsDb[cid]) cardsDb[cid] = { card: c, wants: [], offers: [], inventory: [] };
                else if (isBetterCardInfo(c, cardsDb[cid].card)) cardsDb[cid].card = c;
                cardsDb[cid].offers.push(prof);
            }
            for (const c of (data.inventory || [])) {
                const cid = String(c.id);
                if (!cid) continue;
                if (!cardsDb[cid]) cardsDb[cid] = { card: c, wants: [], offers: [], inventory: [] };
                else if (isBetterCardInfo(c, cardsDb[cid].card)) cardsDb[cid].card = c;
                cardsDb[cid].inventory.push(prof);
            }
        }
        const validTrades = [];
        for (const [cid, info] of Object.entries(cardsDb)) {
            const wList = info.wants, oList = info.offers, invList = info.inventory;
            const wFiltered = [], oFiltered = [], invFiltered = [];

            for (const w of wList) if (!wFiltered.find(x => String(x.id) === String(w.id))) wFiltered.push(w);
            for (const o of oList) if (!oFiltered.find(x => String(x.id) === String(o.id))) oFiltered.push(o);

            const oIds = new Set(oFiltered.map(o => String(o.id)));
            for (const inv of invList) {
                if (!oIds.has(String(inv.id))) {
                    if (!invFiltered.find(x => String(x.id) === String(inv.id))) invFiltered.push(inv);
                }
            }

            if (wFiltered.length > 0 || oFiltered.length > 0) {
                validTrades.push({ card_id: cid, card: info.card, buyers: wFiltered, sellers: oFiltered, owners: invFiltered });
            }
        }
        validTrades.sort((a, b) => parseInt(a.card_id) - parseInt(b.card_id));
        return validTrades;
    }

    async function fetchClubMembers(slug) {
        const users = [];
        let pg = 1, run = true;
        const escSlug = encodeURIComponent(slug);
        while (run) {
            const url = `${API_DOMAIN}/api/v2/clubs/${escSlug}/members/?count=100&page=${pg}`;
            const data = await Manager.req(url);
            if (!data) break;
            const list = data.results || [];
            if (!list.length) break;
            list.forEach(it => { if (it.user && it.user.id) users.push(it.user.id); });
            if (!data.next) run = false; else { pg++; await new Promise(r => setTimeout(r, 80)); }
        }
        return users;
    }

    function openExchangePanel() {
        let panel = document.getElementById('rem-exchange-panel');
        let backdrop = document.querySelector('.rem-ex-backdrop');
        if (!panel) {
            backdrop = document.createElement('div');
            backdrop.className = 'rem-ex-backdrop';
            backdrop.onclick = () => closeExchangePanel();
            document.body.appendChild(backdrop);
            panel = document.createElement('div');
            panel.id = 'rem-exchange-panel';
            panel.innerHTML = `
                <div class="rem-ex-header">
                    <div class="rem-ex-title">🔥 Сканер обменов</div>
                    <div class="rem-ex-close" id="rem-ex-close-btn">✕</div>
                </div>
                <div class="rem-ex-body" id="rem-ex-body"></div>
            `;
            document.body.appendChild(panel);
            panel.querySelector('#rem-ex-close-btn').onclick = () => closeExchangePanel();
        }
        backdrop.classList.add('open');
        panel.classList.add('open');
        renderExchangeMain();
    }
    function closeExchangePanel() {
        document.getElementById('rem-exchange-panel')?.classList.remove('open');
        document.querySelector('.rem-ex-backdrop')?.classList.remove('open');
    }

    function fmtAgo(ts) {
        const ago = Math.round((Date.now() - ts) / 60000);
        if (ago < 1) return 'только что';
        if (ago < 60) return `${ago} мин. назад`;
        if (ago < 1440) return `${Math.round(ago / 60)} ч. назад`;
        return `${Math.round(ago / 1440)} дн. назад`;
    }

    function renderExchangeMain() {
        const body = document.getElementById('rem-ex-body');
        if (!body) return;
        const hasResults = ExState.results.length > 0;
        const scanList = getScanList();
        const history = getHistory();
        let scanListHtml = '';
        if (scanList.length > 0) {
            scanListHtml = `
                <div class="rem-ex-scanlist">
                    <div class="rem-ex-scanlist-title">👥 Список на сканирование <span class="rem-ex-scanlist-count">(${scanList.length} чел.)</span></div>
                    <div class="rem-ex-scanlist-items" id="rem-ex-scanlist-items">
                        ${scanList.map(u => `
                            <div class="rem-ex-scanlist-item" data-uid="${u.id}">
                                <div class="rem-ex-scanlist-item-info">
                                    <span class="rem-ex-scanlist-item-name">${u.username}</span>
                                    <span class="rem-ex-scanlist-item-id">ID: ${u.id}</span>
                                </div>
                                <button class="rem-ex-scanlist-rm" data-rm-uid="${u.id}" title="Убрать">✕</button>
                            </div>
                        `).join('')}
                    </div>
                    <div class="rem-ex-scanlist-actions">
                        <button class="rem-ex-btn primary" id="rem-ex-scan-list-btn" style="height:36px;">🔍 Сканировать список (${scanList.length})</button>
                        <button class="rem-ex-btn secondary" id="rem-ex-clear-list-btn" style="height:36px;">🗑️ Очистить</button>
                    </div>
                </div>
            `;
        }
        let histHtml = '';
        if (history.length > 0) {
            histHtml = `
                <div class="rem-ex-scanlist" style="margin-bottom:${hasResults ? '0' : '16'}px;">
                    <div class="rem-ex-scanlist-title">📋 История сканирований <span class="rem-ex-scanlist-count">(${history.length})</span></div>
                    <div class="rem-ex-scanlist-items">
                        ${history.map(h => `
                            <div class="rem-ex-scanlist-item" data-hist-id="${h.id}" style="cursor:pointer;">
                                <div class="rem-ex-scanlist-item-info">
                                    <span class="rem-ex-scanlist-item-name" style="color:${ExState.activeHistId === h.id ? '#60a5fa' : '#e4e4e7'};">${h.name}</span>
                                    <span class="rem-ex-scanlist-item-id">${h.count} обменов · ${fmtAgo(h.time)}</span>
                                </div>
                                <button class="rem-ex-scanlist-rm" data-del-hist="${h.id}" title="Удалить">✕</button>
                            </div>
                        `).join('')}
                    </div>
                </div>
            `;
        }
        let cacheBarHtml = '';
        if (hasResults && ExState.lastSource) {
            const h = history.find(x => x.id === ExState.activeHistId);
            const canUpdate = h && h.link && !ExState.scanning;
            cacheBarHtml = `<div style="display:flex;flex-direction:column;gap:12px;margin-bottom:12px;padding:12px;background:#18181b;border:1px solid #27272a;border-radius:10px;">
                <div style="display:flex;align-items:center;justify-content:space-between;">
                    <div style="font-size:12px;color:#71717a;">📦 <strong style="color:#a1a1aa;">${ExState.lastSource}</strong> · Всего: ${ExState.results.length}</div>
                    <div style="display:flex;gap:8px;">
                        ${canUpdate ? `<button class="rem-ex-btn secondary" id="rem-ex-update-btn" style="height:28px;padding:0 12px;font-size:11px;">🔄 Обновить</button>` : ''}
                        <button class="rem-ex-btn secondary" id="rem-ex-close-results" style="height:28px;padding:0 12px;font-size:11px;">✕ Закрыть</button>
                    </div>
                </div>
                <div style="display:flex;gap:8px;">
                    <input type="text" id="rem-search-card" class="rem-ex-input" style="height:32px;font-size:12px;flex:1;" placeholder="🔍 Поиск карты (Название, ID или ссылка...)" value="${(ExState.searchQuery || '').replace(/"/g, '&quot;')}">
                    <input type="text" id="rem-search-user" class="rem-ex-input" style="height:32px;font-size:12px;flex:1;" placeholder="👤 Поиск человека (Имя или ID...)" value="${(ExState.searchUser || '').replace(/"/g, '&quot;')}">
                </div>
            </div>`;
        }
        let progressHtml = '';
        if (ExState.scanning) {
            const pct = ExState.scanTotal ? Math.round((ExState.scanDone / ExState.scanTotal) * 100) : 0;
            progressHtml = `<div class="rem-ex-progress"><div class="rem-ex-ptext">Сканирование <strong>${ExState.scanDone}</strong> / <strong>${ExState.scanTotal}</strong> пользователей... (${pct}%)</div><div class="rem-ex-pbar-track"><div class="rem-ex-pbar-fill" id="rem-ex-pbar" style="width:${pct}%"></div></div></div>`;
        }
        body.innerHTML = `
            <div class="rem-ex-input-row">
                <input class="rem-ex-input" id="rem-ex-input" placeholder="Ссылка на гильдию или ID пользователей" />
                <button class="rem-ex-btn primary" id="rem-ex-scan-btn" style="height:40px;" ${ExState.scanning ? 'disabled' : ''}>${ExState.scanning ? '⏳ Загрузка...' : '🔍 Скан'}</button>
            </div>
            ${scanListHtml}
            ${!hasResults ? histHtml : ''}
            <div id="rem-ex-status">${progressHtml}</div>
            ${hasResults ? cacheBarHtml + '<div id="rem-ex-results-container"></div>' : (history.length === 0 && scanList.length === 0 && !ExState.scanning ? `
                <div class="rem-ex-empty">
                    <div style="font-size:32px;margin-bottom:12px;">🔥</div>
                    <div>Введите ссылку на гильдию<br/>или добавляйте пользователей через профили</div>
                    <div style="margin-top:8px;font-size:12px;color:#3f3f46;">Пример: ${SITE_DOMAIN}/guild/fairy-tail-68c526d1</div>
                    <div style="margin-top:4px;font-size:12px;color:#3f3f46;">или: 12345 67890 11111</div>
                </div>
            ` : '')}
        `;
        if (hasResults) renderExchangeResults();
        document.getElementById('rem-ex-scan-btn').onclick = () => startExchangeScan();
        document.getElementById('rem-ex-input').addEventListener('keypress', e => { if (e.key === 'Enter') startExchangeScan(); });
        document.querySelectorAll('[data-rm-uid]').forEach(btn => {
            btn.onclick = (e) => { e.stopPropagation(); removeFromScanList(btn.getAttribute('data-rm-uid')); renderExchangeMain(); };
        });
        const scanListBtn = document.getElementById('rem-ex-scan-list-btn');
        if (scanListBtn) { scanListBtn.onclick = () => startExchangeScanFromList(); }
        const clearListBtn = document.getElementById('rem-ex-clear-list-btn');
        if (clearListBtn) { clearListBtn.onclick = () => { saveScanList([]); renderExchangeMain(); }; }
        document.querySelectorAll('[data-hist-id]').forEach(row => {
            const handleHistClick = () => {
                const hid = row.getAttribute('data-hist-id');
                const h = history.find(x => x.id === hid);
                const statusEl = document.getElementById('rem-ex-status');
                if (statusEl) statusEl.innerHTML = `<div class="rem-ex-ptext">📂 Загрузка истории <strong>${h ? h.name : ''}</strong>...</div>`;

                setTimeout(() => {
                    const data = loadFromHistory(hid);
                    if (!data || !data.length) {
                        if (statusEl) statusEl.innerHTML = `<div class="rem-ex-ptext" style="color:#ef4444;">❌ Не удалось загрузить данные (возможно, они были удалены или слишком велики)</div>`;
                        return;
                    }
                    ExState.results = data;
                    ExState.lastSource = h ? h.name : 'Из истории';
                    ExState.activeHistId = hid;
                    ExState.currentRank = 'ALL';
                    ExState.page = 0;
                    renderExchangeMain();
                }, 50);
            };
            row.onclick = handleHistClick;
            row.onpointerdown = (e) => {
                if (e.pointerType === 'touch') row._remTouch = true;
            };
            row.onpointerup = (e) => {
                if (e.pointerType === 'touch' && row._remTouch) {
                    row._remTouch = false;
                    handleHistClick();
                }
            };
        });
        document.querySelectorAll('[data-del-hist]').forEach(btn => {
            btn.onclick = (e) => { e.stopPropagation(); deleteFromHistory(btn.getAttribute('data-del-hist')); renderExchangeMain(); };
        });
        const closeBtn = document.getElementById('rem-ex-close-results');
        if (closeBtn) { closeBtn.onclick = () => { ExState.results = []; ExState.lastSource = ''; ExState.activeHistId = null; renderExchangeMain(); }; }
        const updateBtn = document.getElementById('rem-ex-update-btn');
        if (updateBtn) {
            updateBtn.onclick = () => {
                const h = history.find(x => x.id === ExState.activeHistId);
                if (h && h.link) {
                    const input = document.getElementById('rem-ex-input');
                    if (input) input.value = h.link;
                    startExchangeScan();
                }
            };
        }
        const searchCard = document.getElementById('rem-search-card');
        const searchUser = document.getElementById('rem-search-user');
        if (searchCard) searchCard.addEventListener('input', (e) => { ExState.searchQuery = e.target.value.trim(); ExState.page = 0; renderExchangeResults(); });
        if (searchUser) searchUser.addEventListener('input', (e) => { ExState.searchUser = e.target.value.trim(); ExState.page = 0; renderExchangeResults(); });
    }

    async function startExchangeScanFromList() {
        const scanList = getScanList();
        if (!scanList.length) return;
        const statusEl = document.getElementById('rem-ex-status');
        if (!statusEl) return;
        if (ExState.scanning) return;
        ExState.scanning = true;
        const scanBtn = document.getElementById('rem-ex-scan-list-btn');
        if (scanBtn) { scanBtn.disabled = true; scanBtn.textContent = '⏳ Загрузка...'; }
        const userIds = scanList.map(u => parseInt(u.id));
        ExState.scanDone = 0;
        ExState.scanTotal = userIds.length;
        renderExchangeMain();

        const results = await gatherTradesEx(userIds, (done, total, msg) => {
            ExState.scanDone = done;
            ExState.scanTotal = total;
            const pct = Math.round((done / total) * 100);
            const pbar = document.getElementById('rem-ex-pbar');
            const ptext = document.querySelector('.rem-ex-ptext');
            if (pbar) pbar.style.width = pct + '%';
            if (ptext) ptext.innerHTML = msg || `Сканирование <strong>${done}</strong> / <strong>${total}</strong> пользователей... (${pct}%)`;
        });
        const srcName = `Список (${scanList.length} чел.)`;
        ExState.results = results;
        ExState.lastSource = srcName;
        ExState.currentRank = 'ALL';
        ExState.page = 0;
        ExState.scanning = false;
        ExState.activeHistId = saveToHistory(srcName, results, "list");
        renderExchangeMain();
    }

    async function fetchGuildName(slug) {
        try {
            const data = await Manager.req(`${API_DOMAIN}/api/v2/clubs/${encodeURIComponent(slug)}/`);
            if (data) {
                const name = data.name || (data.content && data.content.name);
                if (name) return name;
            }
        } catch (e) { }
        return slug;
    }

    async function startExchangeScan() {
        const input = document.getElementById('rem-ex-input');
        const statusEl = document.getElementById('rem-ex-status');
        if (!input || !statusEl) return;
        const val = input.value.trim();
        if (!val) return;
        if (ExState.scanning) return;
        ExState.scanning = true;
        const scanBtn = document.getElementById('rem-ex-scan-btn');
        if (scanBtn) { scanBtn.disabled = true; scanBtn.textContent = '⏳ Загрузка...'; }
        let userIds = [];
        let srcName = '';
        const clubMatch = val.match(/(?:clubs|guild)\/([^\/\?\s]+)/);
        if (clubMatch) {
            statusEl.innerHTML = `<div class="rem-ex-progress"><div class="rem-ex-ptext">Загрузка гильдии <strong>${clubMatch[1]}</strong>...</div><div class="rem-ex-pbar-track"><div class="rem-ex-pbar-fill" id="rem-ex-pbar" style="width:5%"></div></div></div>`;
            const [members, guildName] = await Promise.all([fetchClubMembers(clubMatch[1]), fetchGuildName(clubMatch[1])]);
            userIds = members;
            srcName = guildName;
            if (!userIds.length) {
                statusEl.innerHTML = `<div class="rem-ex-ptext" style="color:#ef4444;">❌ Не удалось загрузить участников гильдии</div>`;
                ExState.scanning = false;
                if (scanBtn) { scanBtn.disabled = false; scanBtn.textContent = '🔍 Скан'; }
                return;
            }
        } else {
            const links = val.match(/(?:remanga\.org|xn--80aaig9ahr\.xn--c1avg)\/user\/(\d+)/g) || [];
            links.forEach(l => { const m = l.match(/(\d+)/); if (m) userIds.push(parseInt(m[1])); });
            const rawText = val.replace(/https?:\/\/\S+/g, '');
            const nums = rawText.match(/\b(\d+)\b/g) || [];
            nums.forEach(n => userIds.push(parseInt(n)));
            userIds = [...new Set(userIds)];
            srcName = `${userIds.length} пользователей`;
            if (!userIds.length) {
                statusEl.innerHTML = `<div class="rem-ex-ptext" style="color:#ef4444;">❌ Не найдено ID пользователей</div>`;
                ExState.scanning = false;
                if (scanBtn) { scanBtn.disabled = false; scanBtn.textContent = '🔍 Скан'; }
                return;
            }
        }

        ExState.scanDone = 0;
        ExState.scanTotal = userIds.length;
        renderExchangeMain();

        const results = await gatherTradesEx(userIds, (done, total, msg) => {
            ExState.scanDone = done;
            ExState.scanTotal = total;
            const pct = Math.round((done / total) * 100);
            const pbar = document.getElementById('rem-ex-pbar');
            const ptext = document.querySelector('.rem-ex-ptext');
            if (pbar) pbar.style.width = pct + '%';
            if (ptext) ptext.innerHTML = msg || `Сканирование <strong>${done}</strong> / <strong>${total}</strong> пользователей... (${pct}%)`;
        });
        ExState.results = results;
        ExState.lastSource = srcName;
        ExState.currentRank = 'ALL';
        ExState.page = 0;
        ExState.scanning = false;
        ExState.activeHistId = saveToHistory(srcName, results, val);
        renderExchangeMain();
    }

    function filterByRankEx(results, rank) {
        if (rank === 'ALL') return results;
        return results.filter(r => getCardRankLetter(r.card) === rank);
    }

    function getFilteredExchangeResults() {
        let results = ExState.results;
        if (ExState.searchQuery) {
            const sq = ExState.searchQuery.toLowerCase();
            results = results.filter(r => {
                const cardName = (getCardCharName(r.card) || '').toLowerCase();
                const cardId = String(r.card_id);
                let queryId = sq;
                if (sq.includes('/card/')) queryId = sq.match(/\/card\/(\d+)/)?.[1] || sq;
                return cardName.includes(sq) || cardId === queryId || cardId.includes(queryId);
            });
        }
        if (ExState.searchUser) {
            const su = ExState.searchUser.toLowerCase();
            results = results.filter(r => {
                const matchUser = (u) => String(u.id) === su || String(u.id).includes(su) || (u.username && u.username.toLowerCase().includes(su));
                return r.sellers.some(matchUser) || r.buyers.some(matchUser) || r.owners.some(matchUser);
            });
        }
        return results;
    }

    function renderExchangeResults() {
        const container = document.getElementById('rem-ex-results-container');
        if (!container) return;
        const results = getFilteredExchangeResults();
        if (!results.length && ExState.results.length) {
            container.innerHTML = `<div class="rem-ex-empty"><div style="font-size:32px;margin-bottom:12px;">🕵️‍♂️</div><div>По вашему запросу ничего не найдено</div></div>`;
            return;
        } else if (!results.length) {
            container.innerHTML = `<div class="rem-ex-empty"><div style="font-size:32px;margin-bottom:12px;">🤷</div><div>Обмены не найдены</div></div>`;
            return;
        }
        const rankCounts = {};
        results.forEach(r => {
            const rk = getCardRankLetter(r.card);
            rankCounts[rk] = (rankCounts[rk] || 0) + 1;
        });
        let html = `<div class="rem-ex-summary">
            <div class="rem-ex-s-item"><div class="rem-ex-s-num" style="color:#60a5fa;">${results.length}</div><div class="rem-ex-s-lab">Обменов</div></div>
            <div class="rem-ex-s-item"><div class="rem-ex-s-num" style="color:#fb923c;">${results.reduce((s, r) => s + r.sellers.length, 0)}</div><div class="rem-ex-s-lab">Продают</div></div>
            <div class="rem-ex-s-item"><div class="rem-ex-s-num" style="color:#3b82f6;">${results.reduce((s, r) => s + r.buyers.length, 0)}</div><div class="rem-ex-s-lab">Хотят</div></div>
            <div class="rem-ex-s-item"><div class="rem-ex-s-num" style="color:#4ade80;">${results.reduce((s, r) => s + r.owners.length, 0)}</div><div class="rem-ex-s-lab">Есть</div></div>
        </div>`;
        html += `<div class="rem-ex-rank-bar">`;
        html += `<div class="rem-ex-rank-btn ${ExState.currentRank === 'ALL' ? 'active' : ''}" data-rank="ALL">💎 Все <span class="rem-ex-rank-count">(${results.length})</span></div>`;
        HOT_RANKS_EX.forEach(rk => {
            const cnt = rankCounts[rk] || 0;
            if (cnt > 0) {
                html += `<div class="rem-ex-rank-btn ${ExState.currentRank === rk ? 'active' : ''}" data-rank="${rk}">${rk} <span class="rem-ex-rank-count">(${cnt})</span></div>`;
            }
        });
        html += `</div>`;
        const pool = filterByRankEx(results, ExState.currentRank);
        const totalPages = Math.max(1, Math.ceil(pool.length / ExState.perPage));
        if (ExState.page >= totalPages) ExState.page = 0;
        const start = ExState.page * ExState.perPage;
        const batch = pool.slice(start, start + ExState.perPage);
        html += `<div class="rem-ex-results">`;
        batch.forEach((item, i) => {
            const card = item.card;
            const name = getCardCharName(card) || `Карта #${item.card_id}`;
            const rank = getCardRankLetter(card);
            const RANK_COLORS = { RE: '#f59e0b', S: '#fbbf24', A: '#c084fc', B: '#60a5fa', C: '#4ade80', D: '#fff', E: '#9ca3af', F: '#9ca3af' };
            const color = RANK_COLORS[rank] || '#a1a1aa';
            const cov = card.cover || {};
            const imgUrl = typeof cov === 'object' ? (cov.high || cov.mid || '') : '';
            const fullImg = imgUrl.startsWith('http') ? imgUrl : (imgUrl ? `${SITE_DOMAIN}${imgUrl}` : '');
            const isVid = isVideoUrl(fullImg);
            html += `
                <div class="rem-ex-card" data-trade-idx="${start + i}">
                    <div class="rem-ex-card-inner">
                        <div class="rem-ex-card-img"${isVid ? ` data-video-src="${fullImg}"` : ''}>${fullImg && !isVid ? `<img src="${fullImg}" loading="lazy">` : (!fullImg ? '' : '')}</div>
                        <div class="rem-ex-card-info">
                            <div class="rem-ex-card-name">${name}</div>
                            <div class="rem-ex-card-rank" style="color:${color};border:1px solid ${color};background:rgba(0,0,0,.3);">${rank}</div>
                            <div class="rem-ex-card-traders">
                                ${item.sellers.length ? `<span class="rem-ex-card-tag sellers">🔶 Отдают: ${item.sellers.length}</span>` : ''}
                                ${item.buyers.length ? `<span class="rem-ex-card-tag buyers">🔷 Хотят: ${item.buyers.length}</span>` : ''}
                                ${item.owners.length ? `<span class="rem-ex-card-tag owners">🟢 Есть: ${item.owners.length}</span>` : ''}
                            </div>
                        </div>
                    </div>
                </div>
            `;
        });
        html += `</div>`;
        if (totalPages > 1) {
            html += `<div class="rem-ex-nav">`;
            if (ExState.page > 0) html += `<button class="rem-ex-btn secondary" data-page="${ExState.page - 1}">⬅️</button>`;
            html += `<span style="color:#71717a;font-size:12px;display:flex;align-items:center;">Стр. ${ExState.page + 1} / ${totalPages}</span>`;
            if (ExState.page < totalPages - 1) html += `<button class="rem-ex-btn secondary" data-page="${ExState.page + 1}">➡️</button>`;
            html += `</div>`;
        }
        container.innerHTML = html;
        container.querySelectorAll('[data-video-src]').forEach(el => {
            renderCardMedia(el.getAttribute('data-video-src'), el);
        });
        container.querySelectorAll('.rem-ex-rank-btn').forEach(btn => {
            btn.onclick = () => {
                ExState.currentRank = btn.getAttribute('data-rank');
                ExState.page = 0;
                renderExchangeResults();
            };
        });
        container.querySelectorAll('.rem-ex-card').forEach(card => {
            card.onclick = () => {
                const idx = parseInt(card.getAttribute('data-trade-idx'));
                renderExchangeDetail(idx);
            };
        });
        container.querySelectorAll('[data-page]').forEach(btn => {
            btn.onclick = (e) => {
                e.stopPropagation();
                ExState.page = parseInt(btn.getAttribute('data-page'));
                renderExchangeResults();
            };
        });
    }

    function renderExchangeDetail(idx) {
        const container = document.getElementById('rem-ex-results-container');
        if (!container) return;
        const results = getFilteredExchangeResults();
        const pool = filterByRankEx(results, ExState.currentRank);
        if (idx < 0 || idx >= pool.length) return;
        const data = pool[idx];
        const card = data.card;
        const name = getCardCharName(card) || `Карта #${data.card_id}`;
        const rank = getCardRankLetter(card);
        const cov = card.cover || {};
        const imgUrl = typeof cov === 'object' ? (cov.high || cov.mid || '') : '';
        const fullImg = imgUrl.startsWith('http') ? imgUrl : (imgUrl ? `${SITE_DOMAIN}${imgUrl}` : '');
        const isVid = isVideoUrl(fullImg);
        const RANK_COLORS = { RE: '#f59e0b', S: '#fbbf24', A: '#c084fc', B: '#60a5fa', C: '#4ade80', D: '#fff', E: '#9ca3af', F: '#9ca3af' };
        const color = RANK_COLORS[rank] || '#a1a1aa';

        const formatUsers = (users) => {
            if (!users.length) return '<div style="color:#52525b;font-size:12px;padding:4px 10px;">—</div>';
            return users.map(u => {
                const emoji = u.sex === 1 ? '👨' : (u.sex === 2 ? '👩' : '👤');
                return `
                <a href="${SITE_DOMAIN}/user/${u.id}/about" target="_blank" class="rem-ex-user">
                    <span>${emoji}</span>
                    <span class="rem-ex-user-name">${u.username}</span>
                    <span class="rem-ex-user-id">ID: ${u.id}</span>
                </a>
            `}).join('');
        };

        container.innerHTML = `
            <div class="rem-ex-detail">
                <div style="margin-bottom:16px;">
                    <button class="rem-ex-btn secondary" id="rem-ex-back-btn">⬅️ Назад к списку</button>
                </div>
                <div class="rem-ex-detail-header">
                    <div class="rem-ex-detail-img"${isVid ? ` data-video-src="${fullImg}"` : ''}>${fullImg && !isVid ? `<img src="${fullImg}">` : (!fullImg ? '<div style="width:100%;height:100%;background:#27272a;display:flex;align-items:center;justify-content:center;color:#52525b;">?</div>' : '')}</div>
                    <div class="rem-ex-detail-meta">
                        <div class="rem-ex-detail-name">${name}</div>
                        <div class="rem-ex-card-rank" style="color:${color};border:1px solid ${color};background:rgba(0,0,0,.3);">${rank}</div>
                        <div class="rem-ex-detail-id">ID: ${data.card_id}</div>
                        <a href="${SITE_DOMAIN}/card/${data.card_id}" target="_blank" style="color:#3b82f6;font-size:12px;text-decoration:none;">Открыть на сайте ↗</a>
                    </div>
                </div>
                <div class="rem-ex-user-list">
                    <h4>🔶 Отдают (${data.sellers.length})</h4>
                    ${formatUsers(data.sellers)}
                    <h4>🔷 Хотят (${data.buyers.length})</h4>
                    ${formatUsers(data.buyers)}
                    ${data.owners.length ? `<h4>🟢 Просто есть (${data.owners.length})</h4>${formatUsers(data.owners)}` : ''}
                </div>
            </div>
        `;
        container.querySelector('#rem-ex-back-btn').onclick = () => renderExchangeResults();
        container.querySelectorAll('[data-video-src]').forEach(el => { renderCardMedia(el.getAttribute('data-video-src'), el); });
    }

    function createCheck() {
        const div = document.createElement('div');
        div.className = 'rem-own-badge';
        div.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"></polyline></svg>`;
        return div;
    }

    function createWishCheck() {
        const div = document.createElement('div');
        div.className = 'rem-wish-badge';
        div.innerHTML = `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"></path></svg>`;
        return div;
    }

    function processCardElement(mediaEl, src) {
        if (mediaEl.hasAttribute('data-rem-checked')) return;
        mediaEl.setAttribute('data-rem-checked', '1');
        const fname = Manager.getFilename(src);
        const have = Manager.has(fname);
        const wish = Manager.hasWish(fname);

        // If the element is a source, we want to attach the badge to the picture or video wrapper
        let wrapper = mediaEl.parentElement;
        if (mediaEl.tagName.toLowerCase() === 'source' && wrapper) {
            wrapper = wrapper.parentElement;
        }

        if (!wrapper) return;
        if (window.getComputedStyle(wrapper).position === 'static') wrapper.style.position = 'relative';

        const isTitleCardsPage = !!location.pathname.match(/^\/manga\/([\w-]+)\/cards/);
        const isDeletedTitle = new URLSearchParams(location.search).has('fix_id');
        const isUserProfilePage = !!location.pathname.match(/\/user\/(?:\d+\/)?(about|inventory|create\/exchange|inventory\/cards-upgrade)/);
        const hideOwnBadge = (isTitleCardsPage && !isDeletedTitle) || isUserProfilePage;

        if (cfg.ownBadge && have && !hideOwnBadge && !wrapper.querySelector('.rem-own-badge')) {
            wrapper.appendChild(createCheck());
        }
        if (cfg.wishBadge && wish && !have && !wrapper.querySelector('.rem-wish-badge')) {
            wrapper.appendChild(createWishCheck());
        }
    }

    function scanVisual() {
        if (!Manager.loaded) return;

        document.querySelectorAll('img[src*="/media/card-item/"]:not([data-rem-checked])').forEach(img => {
            processCardElement(img, img.src);
        });

        document.querySelectorAll('video[src*="/media/card-item/"]:not([data-rem-checked])').forEach(vid => {
            processCardElement(vid, vid.src);
        });

        document.querySelectorAll('video:not([data-rem-checked]), picture:not([data-rem-checked])').forEach(media => {
            const source = media.querySelector('source[src*="/media/card-item/"], source[srcset*="/media/card-item/"]');
            if (source) {
                const src = source.srcset ? source.srcset.split(' ')[0] : source.src;
                processCardElement(source, src);
            }
        });

        document.querySelectorAll('[style*="/media/card-item/"]:not([data-rem-checked])').forEach(el => {
            const style = el.getAttribute('style');
            const match = style.match(/\/media\/card-item\/[^)"']+/);
            if (match) {
                processCardElement(el, match[0]);
            }
        });

        const isTitleCardsPage = !!location.pathname.match(/^\/manga\/([\w-]+)\/cards/);
        if (isTitleCardsPage && typeof applyTitleMissingMode === 'function') {
            applyTitleMissingMode();
        }
    }

    async function checkOnline() {
        if (!cfg.onlineStatus) return;
        const candidates = document.querySelectorAll('a[href*="/user/"]:not([data-rem-online])');
        for (const el of candidates) {
            if (el.textContent.includes("Показать") || el.closest('.cs-comments-section, [data-sentry-component="ActivityItemCard"]')) {
                el.setAttribute('data-rem-online', 'skip'); continue;
            }
            const m = el.getAttribute('href').match(/\/user\/(\d+)/);
            if (!m) { el.setAttribute('data-rem-online', 'skip'); continue; }
            const uid = m[1], av = el.querySelector('[data-slot="avatar"], .relative.shrink-0');
            if (!av) { el.setAttribute('data-rem-online', 'skip'); continue; }
            el.setAttribute('data-rem-online', '1');
            if (Manager.onlineCache.has(uid)) {
                const c = Manager.onlineCache.get(uid);
                if (Date.now() - c.time < 600000) { drawDot(av, c.online); continue; }
            }
            if (Manager.onlineProcessing.has(uid)) continue;
            Manager.onlineProcessing.add(uid);
            Manager.req(`${API_DOMAIN}/api/v2/users/${uid}/`).then(data => {
                const status = data ? !!data.is_online : false;
                Manager.onlineCache.set(uid, { online: status, time: Date.now() });
                Manager.onlineProcessing.delete(uid);
                drawDot(av, status);
            });
        }
    }

    function drawDot(container, isOnline) {
        if (container.querySelector('.rem-online-dot')) return;
        const dot = document.createElement('div');
        dot.className = `rem-online-dot ${isOnline ? 'online' : 'offline'}`;
        if (window.getComputedStyle(container).position === 'static') container.style.position = 'relative';
        container.appendChild(dot);
    }

    async function injectCardStats(dialog, cardId) {
        if (!cfg.cardStats) return;
        if (dialog.querySelector('.rem-card-stats-row')) return;
        const row = document.createElement('div');
        row.className = 'rem-card-stats-row';
        row.innerHTML = `<div class="rem-stat-bubble owners"><span class="rem-stat-num loading">...</span><span class="rem-stat-lab">Владельцы</span></div><div class="rem-stat-bubble wants"><span class="rem-stat-num loading">...</span><span class="rem-stat-lab">Хотят</span></div><div class="rem-stat-bubble sales"><span class="rem-stat-num loading">...</span><span class="rem-stat-lab">Продают</span></div>`;
        const target = dialog.querySelector('.flex.flex-wrap.items-center.justify-center.gap-3');
        if (target) target.parentNode.insertBefore(row, target);

        const fetchCount = async (url) => {
            let total = 0, page = 1;
            while (true) {
                const data = await Manager.req(`${url}${url.includes('?') ? '&' : '?'}page=${page}`);
                if (!data || !data.results) break;
                total += data.results.length;
                if (!data.next || page >= 40) break;
                page++;
                await new Promise(r => setTimeout(r, 100));
            }
            return total;
        };

        Promise.all([
            fetchCount(`${API_DOMAIN}/api/v2/users/have-card/${cardId}/`),
            fetchCount(`${API_DOMAIN}/api/v2/inventory/wishes/${cardId}/?wish_type=1`),
            fetchCount(`${API_DOMAIN}/api/v2/inventory/wishes/${cardId}/?wish_type=2`),
        ]).then(([o, w, s]) => {
            const oN = row.querySelector('.owners .rem-stat-num'),
                wN = row.querySelector('.wants .rem-stat-num'),
                sN = row.querySelector('.sales .rem-stat-num');
            if (oN) { oN.innerText = o; oN.classList.remove('loading'); }
            if (wN) { wN.innerText = w; wN.classList.remove('loading'); }
            if (sN) { sN.innerText = s; sN.classList.remove('loading'); }
        });
    }

    let titleMissingMode = false;
    let titleCacheOwned = 0, titleCacheTotal = 0, titleCacheStats = null;
    let isCalculating = false;
    let lastSlug = '';

    function applyTitleMissingMode() {
        let grid = document.querySelector('.grid.gap-3');
        if (!grid) return;

        let ph = document.getElementById('rem-all-collected-ph');

        if (titleMissingMode && titleCacheOwned > 0 && titleCacheOwned === titleCacheTotal) {
            grid.style.display = 'none';
            if (!ph) {
                ph = document.createElement('div');
                ph.id = 'rem-all-collected-ph';
                ph.innerHTML = '✨ Все карты собраны! ✨';
                ph.style.cssText = 'height: 50vh; display: flex; align-items: center; justify-content: center; font-size: 24px; color: #71717a;';
                grid.parentElement.insertBefore(ph, grid.nextSibling);
            }
            ph.style.display = 'flex';
        } else {
            grid.style.display = '';

            if (titleMissingMode) {
                grid.style.minHeight = '100vh';
                grid.style.alignContent = 'start';
            } else {
                grid.style.minHeight = '';
                grid.style.alignContent = '';
            }

            if (ph) ph.style.display = 'none';

            document.querySelectorAll('img[src*="/media/card-item/"]').forEach(img => {
                const fname = Manager.getFilename(img.src);
                const have = Manager.has(fname);
                const cardCont = img.closest('.grid > div') || img.closest('[data-sentry-component="CardItem"]') || img.parentElement.parentElement;
                if (cardCont) {
                    if (titleMissingMode && have) {
                        cardCont.style.display = 'none';
                    } else if (cardCont.style.display === 'none') {
                        cardCont.style.display = '';
                    }
                }
            });
        }
    }

    function buildProgressHtml(owned, total, rankStats) {
        if (titleMissingMode) {
            let badgesHtml = '';
            if (rankStats) {
                RANK_ORDER.forEach(rankKey => {
                    const st = rankStats[rankKey];
                    if (st && st.total > 0) {
                        const r = RANK_MAP[rankKey] || { name: rankKey.toUpperCase(), color: '#a1a1aa' };
                        const miss = st.total - st.owned;
                        if (miss > 0) {
                            badgesHtml += `<div class="rem-rank-badge" style="color:${r.color};border-color:${r.color};">${r.name}: <span style="color:#fff;margin-left:3px;">-${miss}</span></div>`;
                        }
                    }
                });
            }
            return `
                <div class="rem-progress-header" style="justify-content:space-between;align-items:center;">
                    <div class="rem-progress-text">Недостающие карты: <span>${total - owned}</span></div>
                    <button class="rem-ex-btn secondary" id="rem-missing-toggle" style="height:28px;padding:0 12px;font-size:11px;">🔍 Показать все</button>
                </div>
                <div class="rem-rank-stats">${badgesHtml || '<div style="color:#71717a;font-size:12px;">Все карты собраны! 🎉</div>'}</div>
            `;
        } else {
            let percentNum = 0, percentStr = "0%";
            if (total > 0 && owned > 0) {
                const raw = (owned / total) * 100;
                if (owned === total) { percentNum = 100; percentStr = "100%"; }
                else if (raw < 1) { percentStr = raw.toFixed(1) + "%"; percentNum = raw; }
                else if (raw > 99) { percentStr = raw.toFixed(1) + "%"; percentNum = raw; }
                else { percentNum = Math.floor(raw); percentStr = percentNum + "%"; }
            }
            let badgesHtml = '';
            if (rankStats) {
                RANK_ORDER.forEach(rankKey => {
                    if (rankStats[rankKey] && rankStats[rankKey].total > 0) {
                        const r = RANK_MAP[rankKey] || { name: rankKey.toUpperCase(), color: '#a1a1aa' };
                        const st = rankStats[rankKey];
                        badgesHtml += `<div class="rem-rank-badge" style="color:${r.color};border-color:${r.color};">${r.name}: <span style="color:#fff;margin-left:3px;">${st.owned} / ${st.total}</span></div>`;
                    }
                });
            }
            return `
                <div class="rem-progress-header">
                    <div class="rem-progress-text">Собрано карт: <span>${owned}</span> / ${total}</div>
                    <div style="display:flex;align-items:center;gap:12px;">
                        <button class="rem-ex-btn secondary" id="rem-missing-toggle" style="height:28px;padding:0 12px;font-size:11px;">👁️ Скрыть полученные</button>
                        <div id="rem-percent">${percentStr}</div>
                    </div>
                </div>
                <div class="rem-progress-track"><div class="rem-progress-fill" style="width:${percentNum}%"></div></div>
                <div class="rem-rank-stats">${badgesHtml}</div>
            `;
        }
    }

    function createProgressBox() {
        const box = document.createElement('div');
        box.id = 'rem-progress-box';
        box.className = 'rem-progress-box';
        box.innerHTML = buildProgressHtml(0, 0, null);
        return box;
    }

    function updateProgressBox(box, owned, total, rankStats) {
        titleCacheOwned = owned;
        titleCacheTotal = total;
        titleCacheStats = rankStats;
        box.innerHTML = buildProgressHtml(owned, total, rankStats);
        const btn = box.querySelector('#rem-missing-toggle');
        if (btn) {
            btn.onclick = () => {
                titleMissingMode = !titleMissingMode;
                updateProgressBox(box, titleCacheOwned, titleCacheTotal, titleCacheStats);
                applyTitleMissingMode();
            };
        }
    }

    async function injectTitleProgress() {
        if (!cfg.titleProgress) return;
        const match = location.pathname.match(/^\/manga\/([\w-]+)\/cards/);
        if (!match) {
            const oldBox = document.getElementById('rem-progress-box');
            if (oldBox) oldBox.remove();
            isCalculating = false; lastSlug = '';
            return;
        }
        if (!Manager.loaded || isCalculating) return;
        const grid = document.querySelector('.grid.gap-3');
        if (!grid) return;
        const slug = match[1];
        if (lastSlug !== slug) {
            const oldBox = document.getElementById('rem-progress-box');
            if (oldBox) oldBox.remove();
            lastSlug = slug;
            titleMissingMode = false;
        }
        if (document.getElementById('rem-progress-box')) return;
        isCalculating = true;
        const box = createProgressBox();
        grid.parentNode.insertBefore(box, grid);
        try {
            const titleData = await Manager.req(`${API_DOMAIN}/api/titles/${slug}/`);
            if (!titleData || !titleData.content || !titleData.content.id) { isCalculating = false; return; }
            const titleId = titleData.content.id;
            let totalCards = 0, ownedCards = 0, rankStats = {}, page = 1, run = true;
            while (run) {
                if (!location.pathname.includes(slug)) { isCalculating = false; return; }
                const data = await Manager.req(`${API_DOMAIN}/api/inventory/${titleId}/cards/?count=100&ordering=rank&page=${page}`);
                if (!data) { run = false; break; }
                const list = data.results || data.content || [];
                if (!list.length) { run = false; break; }
                list.forEach(item => {
                    totalCards++;
                    const c = item.card || item;
                    const rank = c.rank || 'unknown';
                    if (!rankStats[rank]) rankStats[rank] = { total: 0, owned: 0 };
                    rankStats[rank].total++;
                    if (c.cover) {
                        const h = Manager.getFilename(c.cover.high);
                        const m = Manager.getFilename(c.cover.mid);
                        if ((h && Manager.has(h)) || (m && Manager.has(m))) {
                            ownedCards++;
                            rankStats[rank].owned++;
                        }
                    }
                });
                if (!data.next) run = false; else page++;
                await new Promise(r => setTimeout(r, 100));
            }
            const finalBox = document.getElementById('rem-progress-box');
            if (finalBox) updateProgressBox(finalBox, ownedCards, totalCards, rankStats);
        } catch (e) { console.error(e); }
        isCalculating = false;
    }

    function scanForDialog() {
        const dialog = document.querySelector('[role="dialog"]');
        if (!dialog) return;

        const cardLink = dialog.querySelector('a[href^="/card/"]');
        if (cardLink && !dialog.dataset.statsInjected) {
            const cid = cardLink.getAttribute('href').match(/card\/(\d+)/)?.[1];
            if (cid) { dialog.dataset.statsInjected = cid; injectCardStats(dialog, cid); }
        }

        const mangaLink = dialog.querySelector('a[href^="/manga/"]');
        if (mangaLink && !mangaLink.dataset.fixHook) {
            mangaLink.dataset.fixHook = "true";
            mangaLink.dataset.origHref = mangaLink.getAttribute('href');
            const isActive = cfg.fixMode;
            const btn = document.createElement('span');
            btn.className = `rem-toggle ${isActive ? 'on' : 'off'}`;
            btn.innerText = `FIX: ${isActive ? 'ON' : 'OFF'}`;

            if (mangaLink.nextSibling) mangaLink.parentNode.insertBefore(btn, mangaLink.nextSibling);
            else mangaLink.parentNode.appendChild(btn);

            btn.addEventListener('click', (e) => {
                e.preventDefault(); e.stopPropagation();
                const newState = !cfg.fixMode;
                saveSetting('fixMode', newState);
                btn.className = `rem-toggle ${newState ? 'on' : 'off'}`;
                btn.innerText = `FIX: ${newState ? 'ON' : 'OFF'}`;
                if (!newState) mangaLink.href = mangaLink.dataset.origHref;
            });

            mangaLink.addEventListener('click', async (e) => {
                if (cfg.fixMode) {
                    e.preventDefault(); e.stopPropagation();
                    if (!cardLink) return;
                    const m = cardLink.getAttribute('href').match(/card\/(\d+)/);
                    if (!m) return;
                    btn.innerText = "⏳";
                    const cardData = await Manager.req(`${API_DOMAIN}/api/inventory/cards/${m[1]}/`);
                    if (cardData && cardData.title && cardData.title.id) {
                        const titleId = cardData.title.id;
                        const titleName = cardData.title.main_name || "Тайтл";
                        const slug = mangaLink.dataset.origHref.split('/')[2];
                        btn.innerText = "FIX: ON";
                        window.location.href = `${SITE_DOMAIN}/manga/${slug}?fix_id=${titleId}&fix_name=${encodeURIComponent(titleName)}`;
                    } else { btn.innerText = "ERR"; }
                }
            });
        }
    }

    async function runFix(fid, rawName) {
        if (activeObserver) { activeObserver.disconnect(); activeObserver = null; }
        document.documentElement.classList.add('rem-checking');
        const l = document.createElement('div'); l.id = 'rem-loader'; l.innerText = "Загрузка данных (0%)...";
        if (!document.body) await new Promise(r => addEventListener('DOMContentLoaded', r));
        document.body.appendChild(l);
        const slug = location.pathname.split('/')[2];
        const status = await Manager.req(`${API_DOMAIN}/api/titles/${slug}/`);
        l.remove(); document.documentElement.classList.remove('rem-checking');
        if (status && status.content) {
            const url = new URL(window.location);
            url.searchParams.delete('fix_id'); url.searchParams.delete('fix_name');
            window.history.replaceState({}, '', url);
            return;
        }
        const tName = rawName ? decodeURIComponent(rawName) : "Title";
        let main = document.querySelector('main');
        let attempts = 0;
        while (!main && attempts < 30) { await new Promise(r => setTimeout(r, 50)); main = document.querySelector('main'); attempts++; }
        if (!main) main = document.body;
        document.body.classList.add('rem-active');
        const box = document.createElement('div');
        box.id = 'rem-inject'; box.className = "container mx-auto px-4 py-6";
        box.innerHTML = `<div style="height:50vh;display:flex;align-items:center;justify-content:center;color:#888;">Загрузка карт...</div>`;
        main.appendChild(box);
        activeObserver = new MutationObserver(() => {
            if (!document.body.classList.contains('rem-active')) document.body.classList.add('rem-active');
            if (!document.getElementById('rem-inject')) { const m = document.querySelector('main') || document.body; m.appendChild(box); }
        });
        activeObserver.observe(document.body, { attributes: true, attributeFilter: ['class'] });
        activeObserver.observe(main, { childList: true });

        let allCards = [];
        let page = 1, run = true;
        while (run) {
            box.innerHTML = `<div style="height:50vh;display:flex;align-items:center;justify-content:center;color:#888;">Загрузка стр. ${page}...</div>`;
            const data = await Manager.req(`${API_DOMAIN}/api/inventory/${fid}/cards/?count=100&ordering=rank&page=${page}`);
            if (!data) { run = false; break; }
            const list = data.results || data.content || [];
            if (!list.length) { run = false; break; }
            allCards = allCards.concat(list);

            if (data.next) {
                page++;
            } else if (data.props && data.props.total_pages && page < data.props.total_pages) {
                page++;
            } else if (data.count && page * 100 < data.count) {
                page++;
            } else {
                run = false;
            }

            if (run) await new Promise(r => setTimeout(r, 20));
        }
        if (!allCards.length) { box.innerHTML = `<div style="text-align:center;padding:50px">Пусто</div>`; return; }

        let ownedInTitle = 0, rankStats = {};
        const grid = document.createElement('div');
        grid.className = 'rem-grid';

        allCards.forEach(item => {
            const c = item.card || item;
            const rank = c.rank || 'unknown';
            if (!rankStats[rank]) rankStats[rank] = { total: 0, owned: 0 };
            rankStats[rank].total++;
            let have = false;
            if (c.cover) {
                const h = Manager.getFilename(c.cover.high);
                const m = Manager.getFilename(c.cover.mid);
                if ((h && Manager.has(h)) || (m && Manager.has(m))) have = true;
            }
            if (have) { ownedInTitle++; rankStats[rank].owned++; }
            const imgUrl = c.cover?.high ? `${SITE_DOMAIN}${c.cover.high}` : (c.cover?.mid ? `${SITE_DOMAIN}${c.cover.mid}` : null);
            const el = document.createElement('div');
            el.className = 'rem-card';
            if (imgUrl) {
                if (imgUrl.endsWith('.webm') || imgUrl.endsWith('.mp4')) {
                    el.innerHTML = `<video src="${imgUrl}#t=1.0" muted playsinline preload="metadata" style="width:100%;height:100%;object-fit:cover;pointer-events:none;"></video>`;
                } else {
                    el.innerHTML = `<img src="${imgUrl}" loading="lazy">`;
                }
            } else {
                el.innerHTML = '';
            }
            el.onclick = () => showModal(c, tName);
            if (have && cfg.ownBadge) {
                if (window.getComputedStyle(el).position === 'static') el.style.position = 'relative';
                el.appendChild(createCheck());
            }
            grid.appendChild(el);
        });

        const progressBox = createProgressBox();
        updateProgressBox(progressBox, ownedInTitle, allCards.length, rankStats);
        progressBox.style.marginBottom = '20px';
        box.innerHTML = `
            <div class="rem-fix-header">
                <h1 class="rem-fix-title">${tName}</h1>
                <div class="rem-fix-meta">
                    <span>ID: ${fid}</span><span class="rem-sep">|</span>
                    <span>${allCards.length} шт</span><span class="rem-sep">|</span>
                    <span style="color:#22c55e;">● Fix Active</span>
                </div>
            </div>
        `;
        box.appendChild(progressBox);
        box.appendChild(grid);
    }

    function checkNavigation() {
        const p = new URLSearchParams(location.search);
        if (!p.get('fix_id')) {
            if (activeObserver) { activeObserver.disconnect(); activeObserver = null; }
            if (document.body.classList.contains('rem-active')) document.body.classList.remove('rem-active');
            const inj = document.getElementById('rem-inject'); if (inj) inj.remove();
            const load = document.getElementById('rem-loader'); if (load) load.remove();
            document.documentElement.classList.remove('rem-checking');
        } else if (!document.body.classList.contains('rem-active') && !document.getElementById('rem-loader')) {
            runFix(p.get('fix_id'), p.get('fix_name'));
        }
    }

    function getAuthorInfo(c) {
        const u = c.author || c.user || c.upload_user || c.owner || c.publisher || c.creator;
        if (u && (u.username || u.name)) return { name: u.username || u.name, link: u.id ? `/user/${u.id}/about` : '#' };
        return { name: "Неизвестен", link: "#" };
    }

    function showModal(c, tName) {
        const imgUrl = c.cover?.high ? `${SITE_DOMAIN}${c.cover.high}` : '';
        const isVid = imgUrl && (imgUrl.endsWith('.webm') || imgUrl.endsWith('.mp4'));
        const imgHtml = isVid
            ? `<video src="${imgUrl}#t=1.0" muted playsinline preload="metadata" style="width:100%;height:100%;object-fit:cover;pointer-events:none;"></video>`
            : `<img src="${imgUrl}">`;

        const name = c.name || c.character?.name || "?";
        const cid = c.character?.id || 0;
        const uInfo = getAuthorInfo(c);
        const likes = c.likes_count || 0;
        const cLink = c.id ? `/card/${c.id}` : '#';
        const over = document.createElement('div'); over.className = 'rem-over';
        over.onclick = e => { if (e.target === over) over.remove(); };
        let titleUrl = `/manga/${location.pathname.split('/')[2]}`;
        const p = new URLSearchParams(location.search);
        if (p.get('fix_id')) titleUrl += `?fix_id=${p.get('fix_id')}&fix_name=${p.get('fix_name')}`;
        else { const slug = c.title ? c.title.dir : '#'; if (slug !== '#') titleUrl = `/manga/${slug}`; }
        over.innerHTML = `
            <div class="rem-box">
                <button class="rem-close" id="x"><svg width="20" height="20" viewBox="0 0 20 20" stroke="currentColor" stroke-width="1.5" fill="none"><path d="M6 14L14 6M14 14L6 6" stroke-linecap="round" stroke-linejoin="round"/></svg></button>
                <div class="rem-box-img">${imgHtml}</div>
                <div class="rem-info">
                    <div><a href="${titleUrl}" class="rem-lnk-m" target="_blank">${tName} ↗</a>${p.get('fix_id') ? '<span class="rem-toggle on" style="font-size:10px;padding:2px 4px;margin-left:5px">FIX: ON</span>' : ''}</div>
                    <a href="/character/${cid}" class="rem-lnk-c" target="_blank">${name} ↗</a>
                </div>
                <div class="rem-acts"><button class="rem-pill">Like ${likes}</button></div>
                <div class="rem-sub"><a href="${uInfo.link}" target="_blank" class="rem-s-btn">Автор: ${uInfo.name}</a><a href="${cLink}" target="_blank" class="rem-s-btn">Пользователи</a></div>
            </div>`;
        document.body.appendChild(over);
        document.getElementById('x').onclick = () => over.remove();
    }

    function getProfileInjectionPoint() {
        const isMobile = window.innerWidth <= 768;
        const cont = document.querySelector('.cs-layout-avatar-container');

        if (isMobile) {
            const mobileRow = document.querySelector('.flex.flex-row.gap-2.max-sm\\:w-full');
            if (mobileRow) return { targetCol: mobileRow.parentElement, refNode: mobileRow };
            if (cont) {
                const guild = cont.nextElementSibling;
                const refNode = (guild && !guild.classList.contains('mt-auto')) ? guild : cont;
                return { targetCol: cont.parentElement, refNode: refNode };
            }
        } else {
            if (cont) {
                const guild = cont.nextElementSibling;
                const refNode = (guild && !guild.classList.contains('mt-auto')) ? guild : cont;
                return { targetCol: cont.parentElement, refNode: refNode };
            }
            const desktopRow = document.querySelector('.flex.flex-row.gap-2.max-sm\\:w-full');
            if (desktopRow) return { targetCol: desktopRow.parentElement, refNode: desktopRow };
        }

        const btns = Array.from(document.querySelectorAll('button'));
        const fbtn = btns.find(b => b.textContent && (b.textContent.includes('друзья') || b.textContent.includes('сообщение')));
        if (fbtn && fbtn.parentElement) return { targetCol: fbtn.parentElement.parentElement, refNode: fbtn.parentElement };

        return null;
    }

    async function injectScanButton() {
        if (!cfg.scanButton) return;
        const m = location.pathname.match(/^\/user\/(\d+)/);
        if (!m) return;
        const uid = m[1];
        if (uid === Manager.userId) return;

        const pt = getProfileInjectionPoint();
        if (!pt) return;
        const { targetCol, refNode } = pt;
        if (document.querySelector('.rem-scan-btn')) return;

        const inList = isInScanList(uid);
        const btn = document.createElement('button');
        btn.className = `rem-scan-btn ${inList ? 'active' : ''}`;
        btn.innerHTML = `
            <span class="rem-scan-check">
                <svg viewBox="0 0 12 12" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polyline points="10 3 5 9 2 6"></polyline></svg>
            </span>
            <span class="rem-scan-label">${inList ? 'В списке сканирования' : 'Добавить в сканирование'}</span>
        `;

        let customWrap = targetCol.querySelector('.rem-custom-actions-wrap');
        if (!customWrap) {
            customWrap = document.createElement('div');
            customWrap.className = 'rem-custom-actions-wrap';
            customWrap.style.cssText = 'width:100%; display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-top:8px; margin-bottom:8px;';
            if (refNode && refNode.nextSibling) targetCol.insertBefore(customWrap, refNode.nextSibling);
            else targetCol.appendChild(customWrap);
        }
        customWrap.appendChild(btn);

        btn.onclick = () => {
            const currentlyInList = isInScanList(uid);
            if (currentlyInList) {
                removeFromScanList(uid);
                btn.classList.remove('active');
                btn.querySelector('.rem-scan-label').textContent = 'Добавить в сканирование';
            } else {
                let username = `User_${uid}`;
                const titleText = document.title.split(/ [|—\-/] /)[0].trim();
                const nameEl = document.querySelector('h1, .text-2xl, [class*="username"]');

                const _isSiteTitle = (t) => t === 'Remanga' || /реманга/i.test(t) || /xn--80aaig9ahr/i.test(t);
                if (titleText && !_isSiteTitle(titleText) && !titleText.includes('Ошибка')) {
                    username = titleText.replace(/^Профиль\s+/i, '').replace(/#\d+$/, '').trim();
                } else if (nameEl) {
                    const txt = nameEl.textContent.trim();
                    if (txt && txt.length < 60) username = txt.replace(/#\d+$/, '').trim();
                }

                addToScanList(uid, username);
                btn.classList.add('active');
                btn.querySelector('.rem-scan-label').textContent = 'В списке сканирования';

                Manager.req(`${API_DOMAIN}/api/v2/users/${uid}/`).then(udata => {
                    if (!udata) return;
                    let apiName = udata.username || (udata.content && udata.content.username) || (udata.user && udata.user.username);
                    if (apiName) apiName = apiName.replace(/#\d+$/, '').trim();
                    if (apiName && apiName !== username) {
                        const list = getScanList();
                        const entry = list.find(u => String(u.id) === String(uid));
                        if (entry) { entry.username = apiName; saveScanList(list); }
                    }
                }).catch(() => { });
            }
        };
    }

    async function injectStats() {
        if (!cfg.profileStats && !cfg.deckStats) return;
        const m = location.pathname.match(/^\/user\/(\d+)/); if (!m) return;
        const uid = m[1];

        const pt = getProfileInjectionPoint();
        if (!pt) return;
        const { targetCol, refNode } = pt;
        if (document.querySelector('.rem-profile-stat-badge')) return;

        const badge = document.createElement('div');
        badge.className = 'rem-profile-stat-badge rem-counting';
        badge.innerHTML = cfg.profileStats ? `🎴 Создал карт: <strong>...</strong>` : `📦 Загрузка...`;

        let customWrap = targetCol.querySelector('.rem-custom-actions-wrap');
        if (!customWrap) {
            customWrap = document.createElement('div');
            customWrap.className = 'rem-custom-actions-wrap';
            customWrap.style.cssText = 'width:100%; display:flex; flex-wrap:wrap; gap:8px; align-items:center; margin-top:8px; margin-bottom:8px;';
            if (refNode && refNode.nextSibling) targetCol.insertBefore(customWrap, refNode.nextSibling);
            else targetCol.appendChild(customWrap);
        }
        badge.style.marginTop = '0';
        customWrap.appendChild(badge);

        let cVerb = "Создал";
        let oVerb = "Открыл";
        try {
            const userRes = await Manager.req(`${API_DOMAIN}/api/v2/users/${uid}/`);
            if (userRes) {
                let s = undefined;
                if (userRes.sex !== undefined) s = userRes.sex;
                else if (userRes.content && userRes.content.sex !== undefined) s = userRes.content.sex;
                else if (userRes.user && userRes.user.sex !== undefined) s = userRes.user.sex;
                if (s !== undefined) {
                    const sex = Number(s);
                    if (sex === 0) { cVerb = "Создало"; oVerb = "Открыло"; }
                    if (sex === 1) { cVerb = "Создал"; oVerb = "Открыл"; }
                    if (sex === 2) { cVerb = "Создала"; oVerb = "Открыла"; }
                }
            }
        } catch (e) { }

        let deckPart = "";
        if (cfg.deckStats) {
            try {
                const res = await Manager.req(`${API_DOMAIN}/api/v2/inventory/decks/??is_opened=false&user_id=${uid}&count=1`);
                if (res) {
                    const count = res.count || 0;
                    deckPart = ` | 📦 ${oVerb} паков: <strong>${count}</strong>`;
                }
            } catch (e) { }
        }

        if (!cfg.profileStats) {
            badge.classList.remove('rem-counting');
            badge.innerHTML = deckPart ? deckPart.replace(/^ \| /, '') : `📦 ${oVerb} паков: 0`;
            return;
        }

        let total = 0, page = 1, run = true;
        while (run) {
            badge.innerHTML = `🎴 ${cVerb} карт: <strong>${total}...</strong>${deckPart}`;
            const r = await Manager.req(`${API_DOMAIN}/api/inventory/catalog/?author=${uid}&count=30&ordering=-rank&page=${page}`);
            if (!r) break;
            const list = r.results || [];
            if (!list.length) break;
            total += list.length;
            if (!r.next) run = false; else { page++; await new Promise(r => setTimeout(r, 150)); }
        }
        badge.classList.remove('rem-counting');
        badge.innerHTML = `🎴 ${cVerb} карт: <strong>${total}</strong>${deckPart}`;
    }

    let filterMapCache = null;
    async function getFilterMap() {
        if (filterMapCache) return filterMapCache;
        const res = await Manager.req('api/forms/titles/?is_without_ex=true');
        const map = { genres: {}, categories: {} };
        if (res && res.content) {
            if (res.content.genres) res.content.genres.forEach(g => map.genres[g.name.toLowerCase()] = g.id);
            if (res.content.categories) res.content.categories.forEach(c => map.categories[c.name.toLowerCase()] = c.id);
        }

        const fallbackCategories = {
            "веб": 5, "в цвете": 6, "зомби": 14, "демоны": 15, "кулинария": 16, "медицина": 17, "культивация": 18, "зверолюди": 19,
            "магия": 22, "горничные": 23, "средневековье": 25, "антигерой": 26,
            "ниндзя": 30, "офисные работники": 31, "полиция": 32, "самураи": 33,
            "видеоигры": 35, "криминал": 36, "обратный гарем": 40,
            "выживание": 41, "путешествия во времени": 43, "боги": 45, "алхимия": 47, "ангелы": 48,
            "антиутопия": 49, "апокалипсис": 50, "армия": 51, "артефакты": 52, "борьба за власть": 54,
            "будущее": 55, "владыка демонов": 57, "волшебные существа": 59, "воспоминания из другого мира": 60,
            "геймеры": 61, "гильдии": 62, "гг женщина": 63, "гг мужчина": 64, "дружба": 67, "ранги силы": 68,
            "жестокий мир": 69, "животные компаньоны": 70, "игровые элементы": 73, "космос": 76,
            "магическая академия": 78, "месть": 79, "навыки": 80, "наёмники": 81, "насилие / жестокость": 82,
            "нежить": 83, "подземелья": 86, "политика": 87, "разумные расы": 88, "рыцари": 90, "система": 91,
            "скрытие личности": 93, "спасение мира": 94, "супер герои": 95, "учитель / ученик": 96,
            "шантаж": 99, "тупой гг": 109, "гг имба": 110, "умный гг": 111, "управление территорией": 114,
            "исекай": 115, "аристократия": 117, "амнезия": 121, "бои на мечах": 122, "гг не человек": 123,
            "упоротость": 124, "грузовик-сан": 125, "учебное заведение": 126, "аниме": 127,
            "лоли": 108, "хикикомори": 21, "реинкарнация": 13, "монстры": 38
        };
        const fallbackGenres = {
            "экшен": 2, "боевые искусства": 3, "гарем": 5, "героическое фэнтези": 7, "детектив": 8,
            "дзёсэй": 9, "додзинси": 10, "драма": 11, "история": 13, "киберпанк": 14,
            "элементы юмора": 16, "меха": 18, "мистика": 19, "научная фантастика": 20,
            "повседневность": 21, "постапокалиптика": 22, "приключения": 23, "психология": 24,
            "романтика": 25, "сверхъестественное": 27, "сёдзё": 28, "сёнэн": 30,
            "спорт": 32, "сэйнэн": 33, "трагедия": 34, "триллер": 35, "ужасы": 36,
            "фантастика": 37, "фэнтези": 38, "школьники": 39, "этти": 40, "эротика": 42,
            "комедия": 50, "мурим": 51
        };
        Object.assign(map.categories, fallbackCategories);
        Object.assign(map.genres, fallbackGenres);

        try {
            const pagesToCrawl = [
                'api/v2/search/catalog/?count=30&ordering=-score&page=1&unstrict_search_fields=categories',
                'api/v2/search/catalog/?categories=47&count=30&ordering=-score&page=1',
                'api/v2/search/catalog/?categories=127&count=30&ordering=-score&page=1'
            ];
            for (const p of pagesToCrawl) {
                const data = await Manager.req(p);
                if (data && data.results) {
                    data.results.forEach(m => {
                        if (m.genres) m.genres.forEach(g => map.genres[g.name.toLowerCase()] = g.id);
                        if (m.categories) m.categories.forEach(c => map.categories[c.name.toLowerCase()] = c.id);
                    });
                }
            }
        } catch (e) { }

        filterMapCache = map;
        return map;
    }

    function ruToSlug(str) {
        const m = { 'а': 'a', 'б': 'b', 'в': 'v', 'г': 'g', 'д': 'd', 'е': 'e', 'ё': 'e', 'ж': 'zh', 'з': 'z', 'и': 'i', 'й': 'y', 'к': 'k', 'л': 'l', 'м': 'm', 'н': 'n', 'о': 'o', 'п': 'p', 'р': 'r', 'с': 's', 'т': 't', 'у': 'u', 'ф': 'f', 'х': 'kh', 'ц': 'ts', 'ч': 'ch', 'ш': 'sh', 'щ': 'sch', 'ъ': '', 'ы': '', 'ь': '', 'э': 'e', 'ю': 'yu', 'я': 'ya' };
        return str.toLowerCase().replace(/[^а-яёa-z0-9]/g, '').split('').map(c => m[c] !== undefined ? m[c] : c).join('');
    }

    async function executeBPTask(card) {
        const t = card.textContent.toLowerCase();
        const uid = Manager.userId || '';
        const go = (path) => {
            Manager.showStatus('⏳ Перенаправление...', true);
            setTimeout(() => { Manager.showStatus('', false); window.open((path.startsWith('http') ? path : SITE_DOMAIN + path), '_blank'); }, 500);
        };
        const goX = async (fn) => {
            Manager.showStatus('⏳ Подготовка редиректа...', true);
            try {
                const path = await fn();
                go(path);
            } catch (e) { go('/'); }
        };

        if (t.includes('разнообразие жанров')) go('/manga/?count_chapters=1-&ordering=-score&unstrict_search_fields=genres%2Ccategories');
        else if (t.includes('новые карточки')) go('/deck/10/open');
        else if (t.includes('путешествие по мирам')) go('/manga?count_chapters=300-&ordering=-score&unstrict_search_fields=genres%2Ccategories');
        else if (t.includes('шпион') || t.includes('чужой профиль') || t.includes('дружеский жест')) {
            const rndId = Math.floor(Math.random() * 2000000) + 1;
            go(`/user/${rndId}/about`);
        }
        else if (t.includes('личное')) go(`/user/${uid}/about`);
        else if (
            t.includes('волна лайков') ||
            t.includes('читательский марафон') || t.includes('погружение в истории') ||
            t.includes('даритель любви')
        ) go('/manga?count_chapters=300-&ordering=-score&unstrict_search_fields=genres%2Ccategories');

        else if (t.includes('вперёд за покупками') || t.includes('вперед за покупками')) go('/customization/feed/frame?ordering=cost');
        else if (t.includes('купи её') || t.includes('коллекционер историй') || t.includes('потрать тикеты')) go('/manga/one_hundred_to_make_god/chapters?ordering=index');
        else if (t.includes('оценка эксперта')) go('/manga/?count_chapters=10-&exclude_bookmarks=51891154%2C51891155%2C51891156%2C51891160&ordering=-score&unstrict_search_fields=genres%2Ccategories');
        else if (t.includes('оно похоже')) {
            const similarLinks = [
                '/manga/pinaty/similar',
                '/manga/the-greatest-estate-designer_/similar',
                '/manga/trash-of-the-counts-family/similar',
                '/manga/one_punch_man_/similar',
                '/manga/omniscient-reader_/similar',
                '/manga/solo-leveling_/similar',
                '/manga/sss-level-hunter_/similar'
            ];
            go(similarLinks[Math.floor(Math.random() * similarLinks.length)]);
        }

        else if (t.includes('одобрительный отклик') || t.includes('оценка мнения')) go('/manga/pinaty/comments');

        else if (t.includes('моё мнение') || t.includes('мое мнение') || t.includes('ответ на мнение')) {
            const commentLinks = [
                '/manga/pinaty/comments',
                '/manga/god-of-ad-libs/comments',
                '/manga/i-was-immediately-mistaken-for-a-genius-actor_/comments',
                '/manga/omniscient-reader_/comments',
                '/manga/regression-instruction_/comments',
                '/manga/the-pick-me-up_/comments'
            ];
            go(commentLinks[Math.floor(Math.random() * commentLinks.length)]);
        }

        else if (t.includes('новый образ')) go(`/user/${uid}/inventory?type=customization&shopType=frames`);

        else if (t.includes('найди пару')) go(`/user/${uid}/mini-games`);

        else if (t.includes('целое') || t.includes('гильди')) go('/guild');
        else if (t.includes('улучши') || t.includes('апгрейд') || t.includes('создай')) go(`/user/inventory/cards-upgrade?ordering=-id&card__rank=rank_f&is_favorite=false`);
        else if (t.includes('обменяемся') || t.includes('обмен')) {
            goX(async () => {
                let allFriends = [];
                const p1 = await Manager.req(`${API_DOMAIN}/api/v2/users/${uid}/friends/?count=20&page=1`);
                if (p1 && p1.results) allFriends.push(...p1.results);
                const p2 = await Manager.req(`${API_DOMAIN}/api/v2/users/${uid}/friends/?count=20&page=2`);
                if (p2 && p2.results) allFriends.push(...p2.results);

                if (allFriends.length > 0) {
                    const rnd = allFriends[Math.floor(Math.random() * allFriends.length)];
                    return `/user/${rnd.id}/create/exchange`;
                }
                const rndId = Math.floor(Math.random() * 2000000) + 1;
                return `/user/${rndId}/create/exchange`;
            });
        }
        else if (t.includes('золот') || t.includes('баланс')) go(`/user/${uid}/balance`);

        else if (t.includes('категори') || t.includes('жанр')) {
            goX(async () => {
                const map = await getFilterMap();
                if (!map) return '/manga';

                const allBpButtons = Array.from(document.querySelectorAll('button'))
                    .filter(b => b.textContent && b.textContent.trim().toLowerCase() === 'получить');

                const taskButtons = allBpButtons.filter((b, idx) => {
                    let card = b;
                    for (let i = 0; i < 6; i++) {
                        if (!card.parentElement) break;
                        card = card.parentElement;
                        if (card.textContent.length > 50 && card.querySelector('.font-bold, .font-semibold, h1, h2, h3, h4, h5, b, strong')) break;
                    }
                    if (!card) return false;
                    const t = card.textContent.toLowerCase();
                    return t.includes('категори') || t.includes('жанр');
                });

                const knownTags = [...Object.keys(map.genres), ...Object.keys(map.categories)]
                    .filter(t => t.length >= 2)
                    .sort((a, b) => b.length - a.length);

                let taskRequirements = [];
                taskButtons.forEach((b, idx) => {
                    let card = b;
                    for (let i = 0; i < 6; i++) {
                        if (!card.parentElement) break;
                        card = card.parentElement;
                        if (card.textContent.length > 50 && card.querySelector('.font-bold, .font-semibold, h1, h2, h3, h4, h5, b, strong')) break;
                    }

                    let taskText = '';
                    if (card) {
                        const t = card.textContent.toLowerCase();
                        if (t.includes('категори') || t.includes('жанр')) {
                            const progressMatch = card.textContent.replace(/\s+/g, '').match(/(\d+)\/(\d+)/);
                            const done = progressMatch && parseInt(progressMatch[1], 10) >= parseInt(progressMatch[2], 10);
                            if (done) {
                            } else {
                                taskText = t;
                            }
                        }
                    }

                    if (taskText) {
                        const foundItems = knownTags.filter(tag => taskText.includes(tag));
                        if (foundItems.length > 0) {
                            taskRequirements.push(foundItems);
                        }
                    }
                });

                if (taskRequirements.length === 0) {
                    if (t.includes('разнообразие жанров')) return '/manga/?count_chapters=1-&ordering=-score&unstrict_search_fields=genres%2Ccategories';
                    return '/manga';
                }

                let validReqs = taskRequirements.map(reqList => reqList.filter(item => map.genres[item] || map.categories[item])).filter(list => list.length > 0);

                const selectedTags = new Set();
                const selectedCategoryIds = [];
                const selectedGenreIds = [];

                validReqs.forEach(reqList => {
                    const shuffled = [...reqList];
                    for (let i = shuffled.length - 1; i > 0; i--) {
                        const j = Math.floor(Math.random() * (i + 1));
                        [shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
                    }

                    let item = shuffled.find(tag => {
                        const id = map.genres[tag] || map.categories[tag];
                        return id && !selectedCategoryIds.includes(id) && !selectedGenreIds.includes(id);
                    });
                    if (!item) item = shuffled[0];

                    if (item) {
                        selectedTags.add(item);
                        if (map.genres[item]) selectedGenreIds.push(map.genres[item]);
                        else if (map.categories[item]) selectedCategoryIds.push(map.categories[item]);
                    }
                });

                let query = '?ordering=-score&unstrict_search_fields=';
                if (selectedCategoryIds.length) query += `&categories=${selectedCategoryIds.join(',')}`;
                if (selectedGenreIds.length) query += `&genres=${selectedGenreIds.join(',')}`;
                query += '&count_chapters=1-';

                return `/manga/${query}`;
            });
        }
        else if (t.includes('похоже на триплет')) go('/user/inventory/cards-upgrade?ordering=-id&card__rank=rank_f&is_favorite=false');

        else go('/');
    }

    function injectBattlepassExecuteButtons() {
        if (!location.pathname.includes('/user/battlepass/tasks')) return;

        document.querySelectorAll('.rem-bp-exec-btn').forEach(execBtn => {
            let card = execBtn;
            for (let i = 0; i < 6; i++) {
                if (!card.parentElement) break;
                card = card.parentElement;
                if (card.textContent.length > 50 && card.querySelector('.font-bold, .font-semibold, h1, h2, h3, h4, h5, b, strong')) break;
            }
            if (!card) { execBtn.remove(); return; }

            let hasGetBtn = Array.from(card.querySelectorAll('button')).some(b => b.textContent && b.textContent.trim().toLowerCase() === 'получить');

            const textContent = card.textContent.replace(/\s+/g, '');
            const progressMatch = textContent.match(/(\d+)\/(\d+)/);
            if (!hasGetBtn || (progressMatch && parseInt(progressMatch[1], 10) >= parseInt(progressMatch[2], 10))) {
                execBtn.remove();
                delete card.dataset.remExecuteInjected;
            }
        });

        const btns = Array.from(document.querySelectorAll('button')).filter(b => b.textContent && b.textContent.trim().toLowerCase() === 'получить');
        btns.forEach(btn => {
            let card = btn;
            for (let i = 0; i < 6; i++) {
                if (!card.parentElement) break;
                card = card.parentElement;
                if (card.textContent.length > 50 && card.querySelector('.font-bold, .font-semibold, h1, h2, h3, h4, h5, b, strong')) break;
            }
            if (!card) return;

            const textContent = card.textContent.replace(/\s+/g, '');
            const progressMatch = textContent.match(/(\d+)\/(\d+)/);
            if (progressMatch && parseInt(progressMatch[1], 10) >= parseInt(progressMatch[2], 10)) {
                return;
            }

            if (card.dataset.remExecuteInjected) return;

            const cardText = card.textContent.toLowerCase();
            const noButton = [
                'давний знакомый', 'я мы одно целое', 'мы одно целое',
                'больше золота', 'найди его', 'найди отличие',
                'я всё знаю', 'я все знаю', 'они разные'
            ];
            if (noButton.some(kw => cardText.includes(kw))) return;

            card.dataset.remExecuteInjected = "1";

            const btnContainer = btn.parentNode;
            const executeBtn = document.createElement('button');
            executeBtn.className = 'rem-bp-exec-btn cs-button z-0 cursor-pointer group relative inline-flex items-center justify-center box-border appearance-none select-none whitespace-nowrap font-medium subpixel-antialiased transition-all tap-highlight-transparent transform-gpu data-[pressed=true]:scale-[0.97] opacity-100 outline-none focus-visible:outline-hidden ring-0 rounded-full px-4 h-9 min-w-9 text-sm bg-primary text-primary-foreground hover:bg-primary/90';
            executeBtn.style.marginRight = '8px';
            executeBtn.innerHTML = 'Выполнить';
            executeBtn.onclick = (e) => {
                e.preventDefault(); e.stopPropagation();
                executeBPTask(card);
            };
            btnContainer.style.display = 'flex';
            btnContainer.style.alignItems = 'center';
            btnContainer.insertBefore(executeBtn, btn);
        });
    }

    async function solveMemoryGame() {
        const DELAY_CLICK = 400;
        const DELAY_PAIR = 900;
        const delay = ms => new Promise(r => setTimeout(r, ms));

        const buttons = [...document.querySelectorAll('button.relative.flex')]
            .filter(btn => btn.querySelector('img[alt="card"]'));

        if (buttons.length < 16) return false;

        const fiberKey = Object.keys(buttons[0]).find(k =>
            k.startsWith('__reactFiber$') || k.startsWith('__reactInternalInstance$')
        );
        if (!fiberKey) return false;

        let fiber = buttons[0][fiberKey];
        let cardsData = null;
        while (fiber && !cardsData) {
            let hook = fiber.memoizedState;
            while (hook) {
                const val = hook.memoizedState;
                if (Array.isArray(val) && val.length === 16 && val[0] && typeof val[0] === 'object' && val[0].symbol !== undefined) {
                    cardsData = val;
                    break;
                }
                if (hook.queue && hook.queue.lastRenderedState) {
                    const qs = hook.queue.lastRenderedState;
                    if (Array.isArray(qs) && qs.length === 16 && qs[0] && typeof qs[0] === 'object' && qs[0].symbol !== undefined) {
                        cardsData = qs;
                        break;
                    }
                }
                hook = hook.next;
            }
            fiber = fiber.return;
        }

        if (!cardsData) return false;

        const groups = {};
        cardsData.forEach((card, i) => {
            const key = String(card.symbol);
            if (!groups[key]) groups[key] = [];
            groups[key].push(i);
        });

        for (const indices of Object.values(groups)) {
            if (indices.length >= 2) {
                buttons[indices[0]].click();
                await delay(DELAY_CLICK);
                buttons[indices[1]].click();
                await delay(DELAY_PAIR);
            }
        }
        return true;
    }

    function injectMemoryGameButton() {
        if (!location.pathname.includes('/user/battlepass/games/memory')) return;
        if (document.querySelector('.rem-bp-exec-btn[data-rem-memory]')) return;

        const headers = document.querySelectorAll('p.cs-text');
        let descEl = null;
        for (const h of headers) {
            if (h.textContent.trim() === 'Игра на память') {
                descEl = h.nextElementSibling;
                break;
            }
        }
        if (!descEl || descEl.dataset.remMemoryInjected) return;
        descEl.dataset.remMemoryInjected = '1';

        const btn = document.createElement('button');
        btn.className = 'rem-bp-exec-btn cs-button z-0 cursor-pointer group relative inline-flex items-center justify-center box-border appearance-none select-none whitespace-nowrap font-medium subpixel-antialiased transition-all tap-highlight-transparent transform-gpu data-[pressed=true]:scale-[0.97] opacity-100 outline-none focus-visible:outline-hidden ring-0 rounded-full px-4 h-9 min-w-9 text-sm bg-primary text-primary-foreground hover:bg-primary/90';
        btn.style.marginRight = '8px';
        btn.dataset.remMemory = '1';
        btn.textContent = 'Выполнить';
        btn.onclick = async (e) => {
            e.preventDefault();
            e.stopPropagation();
            btn.disabled = true;
            btn.textContent = '⏳ Решаю...';
            const ok = await solveMemoryGame().catch(() => false);
            btn.textContent = ok ? '✅ Решено!' : '⚠️ Ошибка';
            setTimeout(() => {
                btn.textContent = 'Выполнить';
                btn.disabled = false;
            }, 3000);
        };

        descEl.textContent = '';
        descEl.appendChild(btn);
    }

    async function solvePuzzleGame() {
        const delay = ms => new Promise(r => setTimeout(r, ms));
        const grid = document.querySelector('div.grid[style*="grid-template-columns:repeat(4, 1fr)"]');
        if (!grid) return false;

        const parseId = (el) => {
            const img = el.querySelector('img');
            let src = img ? img.src : el.style.backgroundImage;
            if (!src || src === 'none') return 0;
            const url = src.replace(/url\(["']?|["']?\)/g, "");
            const filename = Manager.getFilename(url);
            const m = filename.match(/(\d+)/) || url.match(/\/(\d+)\./);
            return m ? parseInt(m[1]) : 0;
        };

        const getSortedCells = () => {
            return Array.from(grid.children).slice(0, 16).sort((a, b) => {
                const ra = a.getBoundingClientRect(), rb = b.getBoundingClientRect();
                if (Math.abs(ra.top - rb.top) > 5) return ra.top - rb.top;
                return ra.left - rb.left;
            });
        };

        let state = [];
        for (let attempt = 0; attempt < 20; attempt++) {
            state = getSortedCells().map(cell => parseId(cell));
            if (new Set(state.filter(v => v !== 0)).size === 15 && state.length === 16) break;
            await delay(150);
        }

        if (new Set(state).size < 16) return false;

        const goal = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 0];
        const gPos = new Array(16); goal.forEach((v, i) => gPos[v] = [i >> 2, i & 3]);

        const solve = async (start) => {
            const weight = 5.0;
            const startTime = performance.now();
            const getH = (s) => {
                let d = 0;
                for (let i = 0; i < 16; i++) {
                    if (s[i] === 0) continue;
                    const p = gPos[s[i]];
                    d += Math.abs(p[0] - (i >> 2)) + Math.abs(p[1] - (i & 3));
                }
                return d;
            };

            let heap = [{ s: start, p: [], h: getH(start), g: 0 }];
            const push = (v) => {
                heap.push(v); let i = heap.length - 1;
                while (i > 0) {
                    let p = (i - 1) >> 1;
                    if (heap[i].g + heap[i].h * weight < heap[p].g + heap[p].h * weight) {
                        [heap[i], heap[p]] = [heap[p], heap[i]]; i = p;
                    } else break;
                }
            };
            const pop = () => {
                if (heap.length === 0) return null;
                const top = heap[0], last = heap.pop();
                if (heap.length > 0) {
                    heap[0] = last; let i = 0;
                    while (true) {
                        let l = (i << 1) + 1, r = (i << 1) + 2, s = i;
                        if (l < heap.length && heap[l].g + heap[l].h * weight < heap[s].g + heap[s].h * weight) s = l;
                        if (r < heap.length && heap[r].g + heap[r].h * weight < heap[s].g + heap[s].h * weight) s = r;
                        if (s === i) break;
                        [heap[i], heap[s]] = [heap[s], heap[i]]; i = s;
                    }
                }
                return top;
            };

            let v = new Set([start.join(',')]);
            while (heap.length > 0) {
                if (v.size % 2000 === 0 && performance.now() - startTime > 3000) break;
                const c = pop();
                if (c.h === 0) return c.p;
                const z = c.s.indexOf(0);
                [z - 4, z + 4, z - 1, z + 1].forEach(ni => {
                    if (ni >= 0 && ni < 16 && (Math.abs(ni - z) !== 1 || (ni >> 2) === (z >> 2))) {
                        const ns = [...c.s];[ns[z], ns[ni]] = [ns[ni], ns[z]];
                        const key = ns.join(',');
                        if (!v.has(key)) { v.add(key); push({ s: ns, p: [...c.p, ni], h: getH(ns), g: c.g + 1 }); }
                    }
                });
            }
            return null;
        };

        if (state.every((v, i) => v === goal[i])) return true;
        const path = await solve(state);
        if (!path) return false;

        for (const idx of path) {
            const cells = getSortedCells();
            if (cells[idx]) cells[idx].click();
            await delay(150);
        }
        return true;
    }

    function injectPuzzleGameButton() {
        if (!location.pathname.includes('/user/battlepass/games/puzzle')) return;
        if (document.querySelector('.rem-bp-exec-btn[data-rem-puzzle]')) return;
        const headers = document.querySelectorAll('h1.cs-text');
        let descEl = null;
        for (const h of headers) {
            if (h.textContent.trim() === 'Собери пазл') {
                descEl = h.nextElementSibling;
                break;
            }
        }
        if (!descEl || descEl.dataset.remPuzzleInjected) return;
        descEl.dataset.remPuzzleInjected = '1';
        const btn = document.createElement('button');
        btn.className = 'rem-bp-exec-btn cs-button z-0 cursor-pointer group relative inline-flex items-center justify-center box-border appearance-none select-none whitespace-nowrap font-medium subpixel-antialiased transition-all tap-highlight-transparent transform-gpu data-[pressed=true]:scale-[0.97] opacity-100 outline-none focus-visible:outline-hidden ring-0 rounded-full px-4 h-9 min-w-9 text-sm bg-primary text-primary-foreground hover:bg-primary/90';
        btn.style.marginTop = '12px';
        btn.dataset.remPuzzle = '1';
        btn.textContent = 'Собрать пазл';
        btn.onclick = async (e) => {
            e.preventDefault(); e.stopPropagation();
            btn.disabled = true;
            btn.textContent = '⏳ Собираю...';
            const ok = await solvePuzzleGame().catch(() => false);
            btn.textContent = ok ? '✅ Готово!' : '⚠️ Обнови страницу';
            setTimeout(() => { btn.textContent = 'Собрать пазл'; btn.disabled = false; }, 3000);
        };
        descEl.parentNode.appendChild(btn);
    }

    function injectDifferenceGameMod() {
        if (!location.pathname.includes('/user/battlepass/games/difference')) return;
        const img1 = document.querySelector('img[alt="Найди отличия 1"]');
        const img2 = document.querySelector('img[alt="Найди отличия 2"]');
        if (!img1 || !img2) return;
        const c1 = img1.closest('.relative.cursor-crosshair');
        const c2 = img2.closest('.relative.cursor-crosshair');
        if (!c1 || !c2) return;
        const targetContainer = c1.parentElement;
        if (!targetContainer || targetContainer.dataset.remDiffInjected) return;
        targetContainer.dataset.remDiffInjected = '1';
        targetContainer.style.display = 'block';
        targetContainer.style.position = 'relative';
        targetContainer.style.maxWidth = '600px';
        targetContainer.style.margin = '-25px auto';
        c1.style.width = '100%';
        c1.style.height = 'auto';
        c2.style.position = 'absolute';
        c2.style.top = '0';
        c2.style.left = '0';
        c2.style.width = '100%';
        c2.style.height = '100%';
        c2.style.opacity = '0';
        c2.style.pointerEvents = 'none';
        const btn = document.createElement('button');
        btn.className = 'rem-bp-exec-btn cs-button z-0 cursor-pointer group relative inline-flex items-center justify-center box-border appearance-none select-none whitespace-nowrap font-medium subpixel-antialiased transition-all tap-highlight-transparent transform-gpu data-[pressed=true]:scale-[0.97] opacity-100 outline-none focus-visible:outline-hidden ring-0 rounded-full px-4 h-9 min-w-9 text-sm bg-primary text-primary-foreground hover:bg-primary/90';
        btn.style.marginTop = '2px';
        btn.style.marginLeft = 'auto';
        btn.style.marginRight = 'auto';
        btn.style.display = 'flex';
        btn.textContent = 'Поменять';
        let showFirst = true;
        btn.onclick = (e) => {
            e.preventDefault();
            showFirst = !showFirst;
            c2.style.opacity = showFirst ? '0' : '1';
            c2.style.pointerEvents = showFirst ? 'none' : 'auto';
        };
        const textContainer = targetContainer.previousElementSibling;
        if (textContainer && textContainer.classList.contains('text-center')) {
            textContainer.appendChild(btn);
        } else {
            targetContainer.parentNode.insertBefore(btn, targetContainer);
        }
    }

    const p = new URLSearchParams(location.search);
    if (p.get('fix_id')) {
        const run = () => runFix(p.get('fix_id'), p.get('fix_name'));
        if (document.readyState === 'loading') addEventListener('DOMContentLoaded', run); else run();
    }

    window.addEventListener('load', () => Manager.init());

    const deckCountCache = new Map();
    const deckCountPending = new Set();

    async function fetchDeckOpenedCount(deckId) {
        if (!Manager.userId) return 0;
        if (deckCountCache.has(deckId)) return deckCountCache.get(deckId);
        if (deckCountPending.has(deckId)) return -1;
        deckCountPending.add(deckId);
        try {
            const data = await Manager.req(`${API_DOMAIN}/api/v2/inventory/decks/?is_opened=true&user_id=${Manager.userId}&deck_id=${deckId}&page=1`);
            const count = data && data.count !== undefined ? data.count : 0;
            deckCountCache.set(deckId, count);
            return count;
        } catch (e) {
            deckCountCache.set(deckId, 0);
            return 0;
        } finally {
            deckCountPending.delete(deckId);
        }
    }

    function isDeckShopPage() {
        return /\/customization\/feed\/deck/.test(location.pathname + location.search);
    }

    function isDeckInventoryPage() {
        return /\/user\/\d+\/inventory/.test(location.pathname) && /type=decks/.test(location.search);
    }

    function isDeckInnerPage() {
        return /\/deck\/\d+\/open/.test(location.pathname);
    }

    let lastDeckScanUrl = '';

    async function scanDeckCards() {
        if (!Manager.userId) return;
        if (!isDeckShopPage() && !isDeckInventoryPage()) return;

        const currentUrl = location.pathname + location.search;
        if (lastDeckScanUrl !== currentUrl) {
            lastDeckScanUrl = currentUrl;
            document.querySelectorAll('.rem-deck-count-badge').forEach(b => b.remove());
            document.querySelectorAll('[data-rem-deck-counted]').forEach(el => el.removeAttribute('data-rem-deck-counted'));
        }

        const deckLinks = document.querySelectorAll('a[href*="/deck/"][href*="/open"]:not([data-rem-deck-counted])');
        for (const link of deckLinks) {
            const m = link.getAttribute('href').match(/\/deck\/(\d+)\/open/);
            if (!m) continue;
            const deckId = m[1];
            link.setAttribute('data-rem-deck-counted', '1');

            const cardEl = link.querySelector('.cs-item-card, .relative') || link.firstElementChild;
            if (!cardEl) continue;
            if (window.getComputedStyle(cardEl).position === 'static') cardEl.style.position = 'relative';
            if (cardEl.querySelector('.rem-deck-count-badge')) continue;

            const badge = document.createElement('div');
            badge.className = 'rem-deck-count-badge';
            badge.textContent = '...';
            cardEl.appendChild(badge);

            fetchDeckOpenedCount(deckId).then(count => {
                if (count < 0) return;
                badge.textContent = count;
                if (count > 0) badge.classList.add('has-count');
            });
        }
    }

    async function scanDeckInnerPage() {
        if (!Manager.userId) return;
        if (!isDeckInnerPage()) return;
        if (document.querySelector('.rem-deck-inner-count')) return;

        const m = location.pathname.match(/\/deck\/(\d+)\/open/);
        if (!m) return;
        const deckId = m[1];

        const headings = document.querySelectorAll('h2, h3, h4, p, span, div');
        let targetEl = null;
        for (const el of headings) {
            const txt = el.textContent.trim();
            if (txt === 'Содержимое пака' && !el.querySelector('.rem-deck-inner-count')) {
                targetEl = el;
                break;
            }
        }
        if (!targetEl) return;

        const countEl = document.createElement('div');
        countEl.className = 'rem-deck-inner-count';
        countEl.innerHTML = '📦 Открыто: <strong>...</strong>';
        targetEl.parentNode.insertBefore(countEl, targetEl);

        const count = await fetchDeckOpenedCount(deckId);
        countEl.innerHTML = `📦 Открыто: <strong>${count >= 0 ? count : '...'}</strong>`;
    }

    setInterval(() => {
        checkNavigation();
        scanVisual();
        if (typeof injectTitleProgress === 'function') injectTitleProgress();
        scanForDialog();
        injectBattlepassExecuteButtons();
        injectMemoryGameButton();
        injectPuzzleGameButton();
        injectDifferenceGameMod();
        scanQuiz();
        scanDeckCards();
        scanDeckInnerPage();
        if (cfg.onlineStatus) checkOnline();
        if (location.pathname.startsWith('/user/')) { injectStats(); injectScanButton(); }
    }, 400);

    function scanQuiz() {
        if (!location.pathname.includes('/user/battlepass/games/quiz')) return;

        const qElem = document.querySelector('h2.cs-text');
        if (!qElem) return;

        const question = qElem.textContent.toLowerCase().replace(/\s+/g, ' ').trim();
        const authorLink = document.querySelector('a[title="Автор"]');
        if (!authorLink) return;

        if (authorLink.dataset.remAnswerInjected && authorLink.dataset.remAnswerInjected !== question) {
            const oldBadge = authorLink.parentNode.querySelector('.rem-quiz-answer');
            if (oldBadge) oldBadge.textContent = '';
        }

        if (authorLink.dataset.remAnswerInjected === question) return;

        const answer = quizDb[question] || quizDb[question.replace('?', '')];
        if (answer) {
            let badge = authorLink.parentNode.querySelector('.rem-quiz-answer');
            if (!badge) {
                badge = document.createElement('span');
                badge.className = 'rem-quiz-answer';
                badge.style.marginLeft = '12px';
                badge.style.color = '#22c55e';
                badge.style.fontWeight = 'bold';
                badge.style.fontSize = '14px';
                authorLink.parentNode.appendChild(badge);
            }
            badge.textContent = `✅ Ответ: ${answer}`;
        }
        authorLink.dataset.remAnswerInjected = question;
    }

    const quizDb = {
        "какой тайтл не является исекаем?": "Тетрадь Смерти",
        "в \"death note\" лайт ягами использует свою тетрадь с определённой целью. какую?": "Создать мир без преступности",
        "в каком тайтле герой переносится в игровой мир, где становится правителем великой гробницы назарик?": "Повелитель",
        "в какой манге действия происходят в постапокалиптическом мире с гигантскими стенами?": "Атака Титанов",
        "какой из этих тайтлов основан на реальных мифологических концепциях и персонажах?": "Повесть о Конце Света",
        "во сколько прибыл годжо сатору в сибуя?": "20:31",
        "какой фонд поддерживал семью джостаров?": "Фонд Спидвагона",
        "реально научиться по видео драться ? а ещё и удачно создать ютуб канал ? думаю эта манхва и аниме даст вам ответ.": "Борьба в прямом эфире",
        "в какой манге используется так называемый нен?": "Хантер х Хантер",
        "сколько процентов населения убил эрен йегер?": "80",
        "как погиб римуру темпест в своей прошлой жизни": "Зарезан грабителем",
        "какая сила гиасса у лелуша в манге \"код гиасс\"?": "Способность подчинять людей",
        "какой вид кагуне получил канеки после операции?": "Ринкаку",
        "как зовут штормового дракона, в манге \"о моем перерождении в слизь\"?": "Вельдора",
        "как звали главного героя манхвы \"поднятие уровня в одиночку\"": "Сон Джин У",
        "что из перечисленного не является сэйнэном?": "Выбери меня",
        "чего добился под конец своей жизни корол пиратов, голд роджер?": "Голд Роджер, Король Пиратов, добился всего, что может предложить этот мир",
        "раз во сколько лет происходит солнечное затмение в берсерке?": "216",
        "в каком году происходят события атаки титанов?": "845",
        "что такое remanga?": "Платформа для чтения манги, манхвы, маньхуа, комиксов",
        "что такое \"сёнэн\"?": "Жанр аниме для мальчиков.",
        "сколько стоит создание гильдии?": "2940",
        "мало кто помнит, но во втором repass изменили названия получаемых бейджей. какой первый бейдж получали пользователи?": "Читатель FFF ранга",
        "уникальный пак карт, не просто новый, а особенный, созданный в коллабе с крутым художником!": "Кацузавр",
        "в наше время с чем только не поднимают уровень.... а ведь правда, с чем из перечисленного ещё не поднимали уровень прямо в названии?": "С фамильярами",
        "что за манга \"дневник...\"?": "Будущего",
        "на сколько молний можно обменять 1 тикет?": "75",
        "сколько молний получает пользователь за вход на сайт 1 раз за день? (без бонусов гульдий и премиум аккаунта)": "20",
        "сколько лайков нужно поставить для получения ачивки \"сердце читателя\" xr ранга?": "20 000",
        "назовите какого класса не существует в «ассоциации героев» из манги \"ванпачмен\"?": "F-класс",
        "из которого аниме эта фраза \"выньте жесткий диск из моего домашнего компьютера… поместите его в ванну и убедитесь, что он полностью стерт\"": "О моём перерождении в слизь",
        "какое самое кассовое аниме в истории?": "Истребитель демонов: Поезд „Бесконечный“",
        "кто из перечисленных не состоял в акацуки?": "Чиё",
        "первое аниме, показанное в ссср": "Летающий корабль-призрак",
        "кто из перечисленных не относится к муравьям-химерам из манги \"охотник х охотник\"?": "Хисока",
        "какой стенд у дио брандо?": "The world",
        "кто является главным антагонистом первой арки \"re:zero?\"": "Эльза",
        "кто является первым убийцей драконов в \"хвосте фей\"": "Акнология",
        "сколько существует школьных тайн в японии?": "7",
        "какой цвет волос часто ассоциируется с цундере-персонажами?": "Розовый или красный",
        "в каком аниме главные герои участвуют в смертельной игре с телефонами?": "Дневник будущего",
        "кто из этих персонажей использует тетрадь смерти?": "Лайт Ягами",
        "какой хвостатый зверь был первым, кто подружился с наруто?": "Сон Гоку (Четырёххвостый)",
        "в каком аниме главный герой превращается в титана?": "Атака титанов",
        "что будет, если написать имя человека в тетради смерти без указания причины смерти?": "Он умрёт от сердечного приступа",
        "сколько отжимыний каждый день делал сайтама?": "100",
        "что будет, если в «re:zero» главный герой погибает?": "Он возвращается в предыдущую точку",
        "как зовут старшую сестру тандзиро из \"истребителя демонов\"?": "Незуко",
        "как называется магический предмет, с помощью которого лайт ягами может убивать людей?": "Тетрадь смерти",
        "чем отличается манга от манхвы?": "Направлением чтения",
        "сколько типов тайтлов можно выбрать в фильтре каталога?": "7",
        "сколько карт в паке читай город?": "5 карт",
        "кто первый создал s ранговую карту?": "muzeek0",
        "какой максимальный ранг может быть у ачивки?": "XR",
        "сколько разделов в магазине?": "6",
        "сколько карт было в паке на 7-ое день рождение?": "10",
        "за какую активность нельзя получить молнии на сайте?": "Комментарии при обмене",
        "сколько рангов карт существует на реманге?": "8",
        "как называется карточка что может эволюционировать и сколько у неё этапов эволюции?": "Загадочный горшок. 8 этапов эволюции.",
        "как в начале манхвы «поднятие уровня в одиночку» называли джин ву?": "Слабейшее оружие человечества",
        "кто оставил старку шрам на лице": "Айзен",
        "из за чего хината захотел играть в волейбол? из \"волейбол!!\"": "Увидел по телевизору",
        "\"всеведующий читатель\" манхва о том как вокруг главного героя начинают происходить события книги, которую он читал на протяжении пол своей жизни, её название \"три способа выжить в разрушенном мире\". сколько в ней глав?": "3149",
        "сколько раз убил себя ким кон чжа из охотник sss-уровня что бы вернутся на 4050 дней?": "4080",
        "какой был игровой ник у главного персонажа \"выбери меня!\"?": "Локи",
        "кто является главным героем в \"моб психо 100\"?": "Сигэо Кагэяма",
        "как называется гримуар асты в \"чёрный клевер\"?": "Пятилистный Гримуар",
        "как называется организация супергероев у сайтамы в \"one punch man\"?": "Ассоциация Героев",
        "что в начале истории случилось с главным героем произведения \"убийца педро\"?": "Его предали,после чего на грани смерти он заново обрёл молодость",
        "как зовут главного героя в манхве \"башня бога\"?": "Двадцать Пятый Бэм",
        "как зовут демона-бензопилу в \"человек-бензопила\", с которым заключил контракт денджи?": "Почита",
        "какая конвертация монет к молниям?": "1 к 5",
        "кем был артур из \"начало после конца\" в своей прошлой жизни?": "Королём",
        "от кого можно получить наилучший любовный совет?": "Герцог Ада",
        "каким номером в своём отряде был главный героя произведения \"прирождённый наёмник\"?": "001",
        "какой организации начинает служить денджи после своей смерти и перерождения в \"человек-бензопила\"?": "Специальный отряд безопасности",
        "в какой отряд рыцарей-чародеев присоединился аста из произведения \"чёрный клевер\"?": "Черный Бык",
        "какой навык позволил ким докче пережить свой первый контакт с \"точкой разрушения\"?": "Читательская стойкость",
        "где находится \"клеймо жертвы\" гатса из манги \"берсерк\"?": "Правая сторона шеи",
        "в какой манге/аниме-исекае главный герой, увлеченный идеей стать “скрытым владыкой тьмы”, тренируется в лесу и внезапно погибает, что приводит к его перерождению в другом мире?": "Восхождение в Тени",
        "какое имя было у артура лейвина из комикса \"начало после конца\" в прошлой жизни?": "Грей",
        "сколько листков клевера было на гримуаре главного героя из аниме \"чёрный клевер\"?": "5",
        "сколько глав в новелле \"боевой пик\"?": "Более шести тысяч",
        "что из этого является произведением про боевые исскуства?": "Безупречный отец",
        "кае называется любительское произведение, чаще всего написанная с использованием персонажей известного художественного произведения, но обладающая самостоятельным сюжетом?": "Додзинси",
        "карточку какого гг дают при создании аккаунта на реманге?": "Не Ли",
        "как зовут истинную форму алукарда, освобожденную после снятия ограничителей?": "Владыка Ночи",
        "как звали первого джостара из джо джо?": "Джонатан",
        "сколько лет сайту \"реманга\" на момент 2025г?": "7",
        "сколько горячих новинок вмещает слайдер на главной странице сайта?": "20",
        "на какого паблишера нужно подписаться, чтобы следить за актуальными новостями сайта?": "Реманга вещает!",
        "сколько нужно поставить лайков на сайте \"реманга\", чтобы получить ачивку максимального ранга \"сердце читателя xr ранга\"?": "20.000",
        "с какого количества гильдий, в которых ты состоишь, можно получить бонус?": "Одна",
        "какой бонус можно получить за подписку на \"реманга\"?": "Бонус 2.000 молний",
        "небесный демон в старшей школе - это...": "Манхва",
        "регрессия - это...": "Сюжет, где главный герой возвращается в прошлое с сохранением воспоминаний и опыта из будущего.",
        "куда писать, если у тебя баг?": "В форму обратной связи на сайте и модерацию сайта через официальный паблик вк.",
        "можно ли исключить теги на форуме, чтобы не видеть неинтересные посты?": "Да",
        "как маомао попала во дворец из манги \"монолог фармацевта\"?": "Её похитили и продали во дворец",
        "в каком произведении главный герой, будучи обычным студентом, случайно обнаруживает, что его сосед по комнате является древним демоном? при этом демон вынужден подчиняться приказам главного героя из-за древнего магического контракта, который они заключили по ошибке.": "Очень приятно, Бог",
        "кем хочет стать руби хошино из аниме и манги \"звёздное дитя\"?": "Айдолом",
        "главный герой какой известной манхы зовут артур лейвин?": "Начало после конца",
        "какой вид произведений рисуют в корее?": "Манхва",
        "какие из произведений были выпущены в один год?": "Хоримия и Сквозь бальный зал",
        "самый популярный законченный тайтл?": "Милый дом",
        "что больше всего любит ллойд из \"система всемогущего дизайнера\"": "Деньги",
        "имя главной героини \"провожающей в последний путь\"?": "Фрирен",
        "какая особенность у главной героини юки из манги \"любовь с кончиков пальцев\"?": "Она глухая",
        "пиццу какой сети постоянно ели персонажи в \"коде гиасе\"?": "Pizza Hut",
        "как зовут моба из \"моб психо 100\"": "Кагэяма Шигео",
        "в каком разделе можно найти самые популярные произведения?": "В топе",
        "какой единственный режиссер аниме, который удостоился премии оскар?": "Хаяо Миядаки",
        "в каком формате нельзя читать мангу на сайте?": "В формате PDF",
        "что такое \"таймскип\" в манге?": "Прыжок во времени в сюжете",
        "какое имя получила элейна из аниме \"путешествие элейны\", когда стала ведьмой?": "Пепельная ведьма",
        "что такое \"фанбук\" в манге?": "Книга с дополнительными материалами от автора",
        "как зовут синигами, которому принадлежала тетрадь смерти, найденная лайтом ягами?": "Рюк",
        "главный герой аниме \"van pis\"": "Луффи",
        "какая манхва является предысторией милого дома?": "Мальчик с ружьём",
        "кто был главным героем в лукизме?": "Даниэль Пак",
        "сколько жён было у тенгена из манги \"клинок, рассекающий демона\"?": "3",
        "чем мэш защищается от магии?": "Физической силой",
        "где обычно культиваторы собирают и хранят свою ци?": "В Даньтяне",
        "укажите лишний тайтл": "Бакуман",
        "как называлась главная подпольная, преступная организация из манги «великий из бродячих псов»?": "Портовая мафия",
        "в каком аниме героиня получает магическую силу от геометрической фигуры, связанной со спутником земли?": "Сейлор Мун",
        "как называется меч гатса?": "Драконоборец",
        "какой фрукт дьявола съел луффи?": "Гому-Гому но Ми",
        "кто главный герой манхвы «башня бога»?": "Баам",
        "как зовут дракона, связанного с главным героем артуром лейвином в манхве «начало после конца»?": "Сильви",
        "как называется меч танджиро в \"клинке, рассекающем демонов\"?": "Ничирин",
        "какой из этих специалистов не реинкарнировал?": "Никто, все реинкарнировали",
        "сколько хвостов было у демона-лисы по имени курама?": "9",
        "как итодори юди избавляется от пальцев сукуна?": "Съедает их",
        "в манге \"паразит\" как зовут паразита, захватившего руку главного героя синъити идзуми?": "Миги",
        "какое настоящее имя персонажа \"l\" из манги тетрадь смерти?": "Эл Лоулайт",
        "сколько всего эмодзи-паков в магазине реманги?": "12",
        "сколько нужно прочитать глав чтоб получить ачивку \"погружённый в чтение xr ранга\"?": "40.000",
        "как называется текстовая манга?": "Ранобэ",
        "первая карта а ранга": "Хитори Гото \"Одинокий Рокер\"",
        "когда день рождения remanga?": "25 мая",
        "сколько существует базовых ачивок на сайте?": "9",
        "месяц, когда очень хочется солгать": "Апрель",
        "аниме это что?": "Японский мультик",
        "когда можно было получить бейдж «адепт темной стороны» и «адепт светлой стороны»?": "Ивент «недрочабрь» в 2022г.",
        "кто читает этот вопрос?": "Лучший человек на свете",
        "имя главного героя из тайтла \"элисед \"": "Со Джи У",
        "кем хотел стать наруто узумаки в аниме \"наруто\"?": "Хокаге",
        "что означает термин \"сэйнэн\"?": "Манга для взрослой аудитории",
        "лучший пту для тех кто хочет стать героем в манге \"моя геройская академии\"?": "Академия Шинро",
        "что такое \"фансервис\" в манге?": "Элементы для привлечения внимания аудитории",
        "какая раса первая подчинилась римуру, в манге \"о моём перерождении в слизь\"?": "Гоблины",
        "сколько действующих капитанов в готей 13 в аниме \"блич\"?": "13",
        "какой тайтл не был выпущен recomics.org?": "Город гоблинов",
        "что обозначает термин \"сабу\"?": "Наставник",
        "какой жанр чаще всего включает в себя перерождение главного героя?": "Исекай",
        "какая самая первая s ранговая карта?": "Токийский гуль",
        "какой персонаж не вошёл в пак «читай город»?": "Марк Твен",
        "сколько существует вкладок в магазине?": "6",
        "какой самый популярный тайтл по количеству оценок?": "Поднятие уровня в одиночку",
        "сколько дают тикетов бонусом при пополнении 1к рублей?": "7",
        "когда нужно начинать пропаганду тюленей?": "Пока админы спят",
        "сколько лет реманге?": "7",
        "сколько даст 1 тикет молнии?": "75",
        "как называется категория аниме, где гг переносится в другой мир?": "Исекай",
        "Сколько существует фильтров по поиску тайтла, не считая расширенный фильтр и исключения?": "5",
        "Как называется организация супергероев у Сайтамы в One Punch Man?": "Ассоциация Героев",
        "Кто является главным антагонистом первой арки \"Re:Zero\"\"?": "Эльза",
        "Назовите какого класса НЕ существует в \"Ассоциации героев\" из манги Ванпачмен?": "F-класс",
        "Как в начале манхвы \"Поднятие уровня в одиночку\" называли Джин Ву?": "Слабейшее оружие человечества",
        "В манхве \"Лукизм\" какую особенность имеет главный герой Пак Хён Сок?": "У него два тела — одно красивое, другое обычное"
    };
})();