Remanga

////

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

// ==UserScript==
// @name         Remanga
// @namespace    http://tampermonkey.net/
// @version      6.1.4
// @description  ////
// @author       You
// @license MIT
// @match        https://remanga.org/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=remanga.org
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @run-at       document-start
// ==/UserScript==

(function () {
    'use strict';

    /* ===== КЛЮЧИ ХРАНИЛИЩА ===== */
    const MY_ID_KEY = 'rem_user_id_v1';
    const MY_INV_KEY = 'rem_inventory_covers_v1';
    const STORAGE_FIX_KEY = 'rem_script_fix_active';

    /* ===== КЛЮЧИ НАСТРОЕК (GM_getValue/GM_setValue) ===== */
    const SETTINGS_KEYS = {
        onlineStatus: 'rem_set_online_status',
        ownBadge: 'rem_set_own_badge',
        cardStats: 'rem_set_card_stats',
        titleProgress: 'rem_set_title_progress',
        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',
        tradeChecker: 'rem_set_trade_checker',
        tradeAutoScan: 'rem_set_trade_autoscan',
        scanButton: 'rem_set_scan_btn',
    };
    /* URL фона храним в localStorage (может быть очень большим base64) */
    const BG_URL_LS_KEY = 'rem_bg_url_data';

    /* Значения по умолчанию */
    const DEFAULTS = {
        onlineStatus: true,
        ownBadge: true,
        cardStats: true,
        titleProgress: true,
        profileStats: true,
        fixMode: false,
        customBgEnabled: false,
        customBgOpacity: 80,
        customBgBlur: 0,
        customBgFit: 'cover',
        themeAccent: '',
        themeButtonBg: '',
        themeNameColor: '',
        themeNameFont: '',
        themeMenuBg: '',
        themeSiteBg: '',
        tradeChecker: true,
        tradeAutoScan: false,
        scanButton: 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)) {
            cfg[k] = GM_getValue(SETTINGS_KEYS[k], dflt);
        }
        /* URL читаем из localStorage */
        cfg.customBgUrl = localStorage.getItem(BG_URL_LS_KEY) || '';
    }
    function saveSetting(key, value) {
        cfg[key] = value;
        if (key === 'customBgUrl') {
            /* Большие base64 храним в localStorage */
            try { localStorage.setItem(BG_URL_LS_KEY, value); } catch (e) { console.warn('REM BG: localStorage переполнен'); }
        } else {
            GM_setValue(SETTINGS_KEYS[key], value);
        }
    }
    loadSettings();

    /* ===== ПРОЧИЕ КОНСТАНТЫ ===== */
    let activeObserver = null;
    let isCalculating = false;
    let lastSlug = '';

    /* Trade Checker: cached wish lists for current user */
    const TRADE_CACHE_KEY = 'rem_trade_cache_v1';
    const TradeData = {
        wants: [],       /* cards user wants (wish_type=1) */
        offers: [],      /* cards user sells (wish_type=2) */
        wantIds: new Set(),
        offerIds: new Set(),
        loaded: false,
        loading: false,
        lastSync: 0,
    };
    /* Exchange scanner state */
    const ExState = {
        scanning: false,
        scanDone: 0,
        scanTotal: 0,
        results: [],
        currentRank: 'ALL',
        page: 0,
        perPage: 8,
        lastSource: '',
        searchQuery: '',
        searchUser: '',
    };
    /* Scan list: users saved for batch scanning */
    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));
    }
    /* Scan history: multiple saved scans */
    const SCAN_HISTORY_KEY = 'rem_scan_history_v2';
    function getHistory() {
        try { return JSON.parse(localStorage.getItem(SCAN_HISTORY_KEY) || '[]'); } 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();
            GM_deleteValue('rem_scan_data_' + old.id);
        }
        try {
            localStorage.setItem(SCAN_HISTORY_KEY, JSON.stringify(hist));
            GM_setValue('rem_scan_data_' + id, JSON.stringify(results));
        } catch (e) { console.error('History save error:', e); }
        return id;
    }
    function loadFromHistory(id) {
        try {
            const raw = GM_getValue('rem_scan_data_' + id);
            return raw ? JSON.parse(raw) : null;
        } catch { return null; }
    }
    function deleteFromHistory(id) {
        const hist = getHistory().filter(h => h.id !== id);
        localStorage.setItem(SCAN_HISTORY_KEY, JSON.stringify(hist));
        GM_deleteValue('rem_scan_data_' + id);
    }
    /* Video frame capture for animated card covers */
    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;}
        @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; }

        /* Toggle switch */
        .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-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;}

        /* === TRADE CHECKER BADGES === */
        .rem-trade-badge{position:absolute;top:4px;left:4px;z-index:100;padding:2px 6px;border-radius:4px;font-size:9px;font-weight:700;letter-spacing:.3px;text-transform:uppercase;pointer-events:none;backdrop-filter:blur(4px);line-height:1.3;text-shadow:0 1px 2px rgba(0,0,0,.6);}
        .rem-trade-badge.sell{background:rgba(249,115,22,.85);color:#fff;box-shadow:0 0 8px rgba(249,115,22,.4);}
        .rem-trade-badge.want{background:rgba(59,130,246,.85);color:#fff;box-shadow:0 0 8px rgba(59,130,246,.4);}
        .rem-trade-badge.have{background:rgba(34,197,94,.85);color:#fff;box-shadow:0 0 8px rgba(34,197,94,.4);}
        .rem-trade-multi{display:flex;flex-direction:column;gap:2px;position:absolute;top:4px;left:4px;z-index:100;}

        /* === EXCHANGE SCANNER PANEL === */
        #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;}

        /* Scan list styles */
        .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;
        }
    `;
    const styleEl = document.createElement('style');
    styleEl.textContent = STYLES;
    (document.head || document.documentElement).appendChild(styleEl);

    /* ===== MANAGER ===== */
    const Manager = {
        covers: new Set(),
        userId: null,
        loaded: false,
        cacheMap: new Map(),
        onlineCache: new Map(),
        onlineProcessing: new Set(),

        async init() {
            this.createUI();
            this.userId = localStorage.getItem(MY_ID_KEY);
            if (!this.userId) { this.promptId(); return; }

            const cache = localStorage.getItem(MY_INV_KEY);
            if (cache) {
                const d = JSON.parse(cache);
                this.covers = new Set(d.data);
                this.loaded = true;
                loadTradeData(true);
                if (Date.now() - d.time > 1200000) {
                    this.sync(true); // Запускаем тихое обновление, если кэш старше 20 минут
                }
            } else {
                await this.sync();
                loadTradeData(true);
            }

            setInterval(() => {
                this.sync(true);
                loadTradeData(true);
            }, 1200000); // 20 минут
        },

        getFilename(url) { if (!url) return null; return url.split('/').pop().split('?')[0]; },

        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 = `https://api.remanga.org/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;
        },

        req(url) {
            return new Promise(resolve => {
                GM_xmlhttpRequest({
                    method: "GET", url, headers: { "Accept": "application/json" },
                    onload: r => { if (r.status == 200) try { resolve(JSON.parse(r.responseText)) } catch { resolve(null) } else resolve(null) },
                    onerror: () => resolve(null)
                });
            });
        },

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

        has(filename) { return this.loaded && filename && this.covers.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">v6.1.4</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', '🎴 Статистика профиля', 'Кол-во созданных карт на профиле'));

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

        /* --- Раздел: Обмены --- */
        const lbl2b = document.createElement('div');
        lbl2b.className = 'rem-section-label';
        lbl2b.textContent = 'Обмены';
        body.appendChild(lbl2b);
        body.appendChild(makeSwitchRow('tradeChecker', '🔄 Метки обмена', 'Бейджи «Продаю» / «Хочу» на картах'));
        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';

        /* URL ввод */
        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);

        /* --- Раздел: FIX режим --- */
        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);
            Manager.loaded = false;
            Manager.covers = new Set();
            Manager.sync();
            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_ID_KEY);
            document.getElementById('rem-settings-panel')?.classList.remove('open');
            Manager.userId = null;
            Manager.loaded = false;
            Manager.covers = 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_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');

        /* CSS-оверрайд скрывает оригинальный фон сайта и делает html/body прозрачным
           чтобы наш элемент был виден сквозь них */
        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;
            }

            /* Опускаем фоны сайта, чтобы наш кастомный был выше (z-index -1) */
            [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',  /* выше чем -9, но ниже контента страницы */
            '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 = '';
    }

    /* ===== TRADE CHECKER: ЗАГРУЗКА ВИШЛИСТОВ ===== */
    async function loadTradeData(silent = false) {
        if (!Manager.userId || TradeData.loading) return;
        /* Пробуем из кэша */
        if (!TradeData.loaded) {
            try {
                const raw = localStorage.getItem(TRADE_CACHE_KEY);
                if (raw) {
                    const c = JSON.parse(raw);
                    if (c && Date.now() - c.time < 3600000) {
                        TradeData.wants = c.wants || [];
                        TradeData.offers = c.offers || [];
                        TradeData.wantIds = new Set(c.wants.map(x => String(x.id)));
                        TradeData.offerIds = new Set(c.offers.map(x => String(x.id)));
                        TradeData.loaded = true;
                        TradeData.lastSync = c.time;
                    }
                }
            } catch (e) { }
        }
        /* Фоновое обновление */
        if (TradeData.loaded && Date.now() - TradeData.lastSync < 600000) return;
        TradeData.loading = true;
        if (!silent) Manager.showStatus('Загрузка вишлистов...', true);
        const uid = Manager.userId;
        const fetchWish = async (type) => {
            let items = [], pg = 1, run = true;
            while (run) {
                const url = `https://remanga.org/api/v2/inventory/wishes/users/${uid}/?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;
        };
        try {
            const [wants, offers] = await Promise.all([fetchWish(1), fetchWish(2)]);
            TradeData.wants = wants;
            TradeData.offers = offers;
            TradeData.wantIds = new Set(wants.map(x => String(x.id)));
            TradeData.offerIds = new Set(offers.map(x => String(x.id)));
            TradeData.loaded = true;
            TradeData.lastSync = Date.now();
            localStorage.setItem(TRADE_CACHE_KEY, JSON.stringify({
                time: Date.now(), wants, offers
            }));
        } catch (e) { console.warn('REM Trade: ошибка загрузки', e); }
        TradeData.loading = false;
        if (!silent) { Manager.showStatus('Вишлисты загружены!', true); setTimeout(() => Manager.showStatus('', false), 1500); }
    }

    /* ===== TRADE CHECKER: РАЗМЕТКА КАРТ ===== */
    function scanTradeMarks() {
        if (!cfg.tradeChecker || !TradeData.loaded) return;
        document.querySelectorAll('img[src*="/media/card-item/"]:not([data-rem-trade])').forEach(img => {
            img.setAttribute('data-rem-trade', '1');
            const wrapper = img.parentElement;
            if (!wrapper) return;
            /* Извлекаем card ID из ссылки на карту рядом */
            const link = wrapper.closest('a[href*="/card/"]') || wrapper.querySelector('a[href*="/card/"]');
            let cardId = null;
            if (link) {
                const m = link.getAttribute('href').match(/\/card\/(\d+)/);
                if (m) cardId = m[1];
            }
            /* Ищем card ID из src через API структуру */
            if (!cardId) {
                const src = img.src || '';
                const fname = Manager.getFilename(src);
                /* Сканируем data-атрибуты контейнеров */
                const parent = wrapper.closest('[data-card-id]');
                if (parent) cardId = parent.getAttribute('data-card-id');
            }
            if (!cardId) return;
            if (window.getComputedStyle(wrapper).position === 'static') wrapper.style.position = 'relative';
            const isSelling = TradeData.offerIds.has(cardId);
            const isWanting = TradeData.wantIds.has(cardId);
            const hasIt = Manager.has(Manager.getFilename(img.src));
            if (!isSelling && !isWanting && !hasIt) return;
            const badges = [];
            if (isSelling) badges.push({ cls: 'sell', text: 'Продаю' });
            if (isWanting) badges.push({ cls: 'want', text: 'Хочу' });
            if (badges.length === 0 && hasIt) return; /* уже есть галочка от ownBadge */
            if (badges.length === 1) {
                const b = document.createElement('div');
                b.className = `rem-trade-badge ${badges[0].cls}`;
                b.textContent = badges[0].text;
                wrapper.appendChild(b);
            } else if (badges.length > 1) {
                const cont = document.createElement('div');
                cont.className = 'rem-trade-multi';
                badges.forEach(bg => {
                    const b = document.createElement('div');
                    b.className = `rem-trade-badge ${bg.cls}`;
                    b.style.position = 'static';
                    b.textContent = bg.text;
                    cont.appendChild(b);
                });
                wrapper.appendChild(cont);
            }
        });
    }

    /* ===== TRADE CHECKER: РАЗМЕТКА НА СТРАНИЦАХ WISHES ===== */
    function scanWishesPage() {
        if (!cfg.tradeChecker || !TradeData.loaded) return;
        /* На страницах /user/{id}/about - показываем карты из инвентаря текущего пользователя */
        const m = location.pathname.match(/^\/user\/(\d+)/);
        if (!m) return;
        const pageUserId = m[1];
        if (pageUserId === Manager.userId) return; /* Свой профиль - не нужно */
        /* Ищем все карты на странице и помечаем */
        document.querySelectorAll('a[href*="/card/"]:not([data-rem-wish-checked])').forEach(el => {
            el.setAttribute('data-rem-wish-checked', '1');
            const cm = el.getAttribute('href').match(/\/card\/(\d+)/);
            if (!cm) return;
            const cid = cm[1];
            const imgEl = el.querySelector('img[src*="/media/card-item/"]');
            if (!imgEl) return;
            const wrapper = imgEl.parentElement || el;
            if (window.getComputedStyle(wrapper).position === 'static') wrapper.style.position = 'relative';
            const isSelling = TradeData.offerIds.has(cid);
            const isWanting = TradeData.wantIds.has(cid);
            const hasIt = Manager.has(Manager.getFilename(imgEl.src));
            const badges = [];
            if (isSelling) badges.push({ cls: 'sell', text: 'Продаю' });
            if (isWanting) badges.push({ cls: 'want', text: 'Хочу' });
            if (hasIt && !isSelling) badges.push({ cls: 'have', text: 'Есть' });
            if (!badges.length) return;
            if (wrapper.querySelector('.rem-trade-badge, .rem-trade-multi')) return;
            if (badges.length === 1) {
                const b = document.createElement('div');
                b.className = `rem-trade-badge ${badges[0].cls}`;
                b.textContent = badges[0].text;
                wrapper.appendChild(b);
            } else {
                const cont = document.createElement('div');
                cont.className = 'rem-trade-multi';
                badges.forEach(bg => {
                    const b = document.createElement('div');
                    b.className = `rem-trade-badge ${bg.cls}`;
                    b.style.position = 'static';
                    b.textContent = bg.text;
                    cont.appendChild(b);
                });
                wrapper.appendChild(cont);
            }
        });
    }

    /* ===== EXCHANGE SCANNER: CORE LOGIC ===== */
    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 = 'https://remanga.org';
        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 = `https://api.remanga.org/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;
        };
        /* Profile */
        let username = `User_${userId}`;
        let sex = 0;
        const udata = await Manager.req(`https://api.remanga.org/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;
        /* 4 concurrent fetches */
        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);
        /* Build cards_db */
        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);
            }
        }
        /* Compile all active trades without omitting un-matched pieces */
        const validTrades = [];
        for (const [cid, info] of Object.entries(cardsDb)) {
            const wList = info.wants, oList = info.offers, invList = info.inventory;
            const wFiltered = [], oFiltered = [], invFiltered = [];

            // Deduplicate lists just in case
            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)));
            // Add to owners only if they don't already sell it
            for (const inv of invList) {
                if (!oIds.has(String(inv.id))) {
                    if (!invFiltered.find(x => String(x.id) === String(inv.id))) invFiltered.push(inv);
                }
            }

            // 100% Scan: Keep card if there is ANY demand or supply for it
            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 = `https://api.remanga.org/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;
    }

    /* ===== EXCHANGE PANEL UI ===== */
    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();
        /* Scan list */
        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>
            `;
        }
        /* History */
        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>
            `;
        }
        /* Active results info */
        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;">Пример: https://remanga.org/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(); });
        /* Scan list events */
        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(); }; }
        /* History events */
        document.querySelectorAll('[data-hist-id]').forEach(row => {
            row.onclick = () => {
                const hid = row.getAttribute('data-hist-id');
                const data = loadFromHistory(hid);
                if (!data || !data.length) return;
                const h = history.find(x => x.id === hid);
                ExState.results = data;
                ExState.lastSource = h ? h.name : 'Из истории';
                ExState.activeHistId = hid;
                ExState.currentRank = 'ALL';
                ExState.page = 0;
                renderExchangeMain();
            };
        });
        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(`https://api.remanga.org/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 = '';
        /* Detect club / guild link */
        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 {
            /* Parse IDs */
            const links = val.match(/remanga\.org\/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;
        }
        /* Rank counts */
        const rankCounts = {};
        results.forEach(r => {
            const rk = getCardRankLetter(r.card);
            rankCounts[rk] = (rankCounts[rk] || 0) + 1;
        });
        /* Summary */
        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>`;
        /* Rank filter buttons */
        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>`;
        /* Filtered list */
        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 ? `https://remanga.org${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>`;
        /* Pagination */
        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;
        /* Capture video frames */
        container.querySelectorAll('[data-video-src]').forEach(el => {
            renderCardMedia(el.getAttribute('data-video-src'), el);
        });
        /* Bind events */
        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 ? `https://remanga.org${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="https://remanga.org/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="https://remanga.org/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 scanVisual() {
        if (!Manager.loaded) return;
        if (location.pathname.match(/\/user\/\d+\/(about|inventory|create\/exchange)/)) return;

        const isTitleCardsPage = !!location.pathname.match(/^\/manga\/([\w-]+)\/cards/);

        document.querySelectorAll('img[src*="/media/card-item/"]:not([data-rem-checked])').forEach(img => {
            img.setAttribute('data-rem-checked', '1');
            const fname = Manager.getFilename(img.src);
            const have = Manager.has(fname);
            const wrapper = img.parentElement;
            if (!wrapper) return;
            if (window.getComputedStyle(wrapper).position === 'static') wrapper.style.position = 'relative';

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

        if (isTitleCardsPage) {
            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(`https://api.remanga.org/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(`https://api.remanga.org/api/v2/users/have-card/${cardId}/`),
            fetchCount(`https://api.remanga.org/api/v2/inventory/wishes/${cardId}/?wish_type=1`),
            fetchCount(`https://api.remanga.org/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;

    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];
                        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], 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(`https://api.remanga.org/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(`https://api.remanga.org/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;
    }

    /* ===== FIX РЕЖИМ / ДИАЛОГ ===== */
    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(`https://api.remanga.org/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 = `https://remanga.org/manga/${slug}/cards?fix_id=${titleId}&fix_name=${encodeURIComponent(titleName)}`;
                    } else { btn.innerText = "ERR"; }
                }
            });
        }
    }

    /* ===== runFix ===== */
    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(`https://api.remanga.org/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(`https://api.remanga.org/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 ? `https://remanga.org${c.cover.high}` : (c.cover?.mid ? `https://remanga.org${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 ? `https://remanga.org${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 += `/cards?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; // Mobile breakpoint
        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 {
            // Desktop: prioritize injecting into the left avatar column!
            if (cont) {
                const guild = cont.nextElementSibling;
                const refNode = (guild && !guild.classList.contains('mt-auto')) ? guild : cont;
                return { targetCol: cont.parentElement, refNode: refNode };
            }
            // fallback
            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"]');

                if (titleText && titleText !== 'Remanga' && !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();
                }

                /* Мгновенно добавляем и обновляем UI */
                addToScanList(uid, username);
                btn.classList.add('active');
                btn.querySelector('.rem-scan-label').textContent = 'В списке сканирования';

                /* Фоновое обновление ника через API (с правильными путями данных) */
                Manager.req(`https://api.remanga.org/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) 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 = `🎴 Создал карт: <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 verb = "Создал";
        try {
            const userRes = await Manager.req(`https://api.remanga.org/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) verb = "Создало";
                    if (sex === 1) verb = "Создал";
                    if (sex === 2) verb = "Создала";
                }
            }
        } catch (e) { }

        let total = 0, page = 1, run = true;
        while (run) {
            badge.innerHTML = `🎴 ${verb} карт: <strong>${total}...</strong>`;
            const r = await Manager.req(`https://api.remanga.org/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 = `🎴 ${verb} карт: <strong>${total}</strong>`;
    }

    /* ===== СТАРТ ===== */
    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());

    setInterval(() => {
        checkNavigation();
        scanVisual();
        scanForDialog();
        injectTitleProgress();
        if (cfg.onlineStatus) checkOnline();
        if (location.pathname.startsWith('/user/')) { injectStats(); injectScanButton(); }
        if (cfg.tradeChecker) { scanTradeMarks(); scanWishesPage(); }
    }, 400);

})();