Greasy Fork

Greasy Fork is available in English.

iwara & hanime1 视频比对

标题词干匹配 + 时长精确过滤 + 可调节秒数容差 + iwara池排序

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         iwara & hanime1 视频比对
// @namespace    http://tampermonkey.net/
// @version      1.7
// @description  标题词干匹配 + 时长精确过滤 + 可调节秒数容差 + iwara池排序
// @author       bydbot+trae,mimo 2 pro
// @match        https://www.iwara.tv/*
// @include      https://*.hanime*.*/search?*
// @include      https://hanime*.*/search?*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @license      GPL-3.0
// ==/UserScript==

(function() {
    'use strict';

    GM_addStyle(`
        #matcher-menu-btn {
            position: fixed; right: 20px; bottom: 20px;
            background: linear-gradient(135deg, #00ccff, #0066ff);
            color: #fff; width: 50px; height: 50px; border-radius: 50%;
            cursor: pointer; z-index: 99999; font-size: 24px; font-weight: bold;
            box-shadow: 0 4px 15px rgba(0,102,255,0.4);
            display: flex; align-items: center; justify-content: center;
            transition: all 0.3s; user-select: none;
            border: 2px solid rgba(255,255,255,0.2);
        }
        #matcher-menu-btn:hover {
            transform: scale(1.1);
            box-shadow: 0 6px 20px rgba(0,102,255,0.6);
            background: linear-gradient(135deg, #0066ff, #00ccff);
        }
        #matcher-dropdown {
            position: fixed; right: 20px; bottom: 80px;
            background: #1e1e1e; border: 1px solid #444; border-radius: 8px;
            z-index: 99998; display: none; overflow: hidden;
            box-shadow: 0 8px 24px rgba(0,0,0,0.6); min-width: 180px;
        }
        #matcher-dropdown.show { display: block; }
        .dropdown-item {
            padding: 12px 18px; cursor: pointer; font-size: 13px;
            color: #eee; transition: background 0.2s; white-space: nowrap;
            border-bottom: 1px solid #2a2a2a; font-family: sans-serif;
        }
        .dropdown-item:last-child { border-bottom: none; }
        .dropdown-item:hover { background: #2a2a2a; color: #00ccff; }
        #matcher-panel, #sort-panel {
            position: fixed; display: none; flex-direction: column;
            background: #1a1a1a; color: #eee; z-index: 10000;
            padding: 12px; border-radius: 10px; font-family: sans-serif;
            box-shadow: 0 10px 30px rgba(0,0,0,0.7); border: 1px solid #333;
            overflow: hidden; resize: both;
        }
        #matcher-panel {
            min-width: 900px; max-width: 95vw; min-height: 500px; max-height: 90vh;
        }
        #sort-panel {
            min-width: 900px; max-width: 95vw; min-height: 500px; max-height: 90vh;
        }
        .panel-header {
            border-bottom: 1px solid #333; padding: 8px 8px 8px 12px;
            margin: -12px -12px 10px -12px; background: #222;
            border-radius: 10px 10px 0 0; display: flex;
            justify-content: space-between; align-items: center;
        }
        .panel-header h3 { margin: 0; font-size: 14px; color: #00ccff; }
        .close-btn { cursor: pointer; color: #888; font-size: 18px; padding: 0 8px; }
        .close-btn:hover { color: #ff4444; }
        .stat-banner {
            display: flex; justify-content: space-around;
            background: #252525; padding: 6px; border-radius: 6px;
            margin-bottom: 10px; font-size: 11px; border: 1px solid #333;
        }
        .stat-item b { color: #00ffcc; }
        .progress-container {
            width: 100%; height: 4px; background: #333; border-radius: 2px;
            margin-bottom: 10px; overflow: hidden; display: none;
        }
        #progress-bar {
            width: 0%; height: 100%;
            background: linear-gradient(90deg, #00ccff, #00ffcc);
            transition: width 0.1s;
        }
        .filter-controls {
            background: #252525; border-radius: 6px; padding: 10px;
            margin-bottom: 10px; border: 1px solid #333;
            display: flex; justify-content: space-between; align-items: center;
        }
        .filter-item { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
        .filter-item label { width: 80px; font-size: 11px; color: #aaa; }
        .filter-item input[type=number] {
            width: 70px; background: #333; color: #fff;
            border: 1px solid #555; border-radius: 4px; padding: 4px 6px; font-size: 11px;
        }
        .filter-item span { font-size: 10px; color: #666; }
        .filter-hint { font-size: 10px; color: #ffaa00; text-align: center; margin-top: 5px; }
        .swap-mode-btn {
            background: #3a3a3a; color: #fff; border: 1px solid #444;
            padding: 6px 12px; border-radius: 5px; font-size: 11px;
            cursor: pointer; transition: 0.2s; white-space: nowrap; margin-left: 10px;
        }
        .swap-mode-btn:hover { background: #4a4a4a; border-color: #00ccff; }
        .swap-mode-btn.active {
            background: #00ccff; color: #000; border-color: #00ccff;
            box-shadow: 0 0 10px rgba(0,204,255,0.5);
        }
        .column-swap-ready { cursor: pointer; box-shadow: 0 0 15px rgba(0,204,255,0.6); border-color: #00ccff !important; }
        .column-swap-selected { cursor: pointer; box-shadow: 0 0 20px rgba(255,170,0,0.8); border-color: #ffaa00 !important; background: rgba(255,170,0,0.1); }
        .threshold-wrap { display: flex; align-items: center; gap: 10px; font-size: 12px; margin-bottom: 10px; color: #aaa; }
        input[type=range] { flex: 1; cursor: pointer; }
        .compare-container {
            display: flex; gap: 10px; margin-top: 10px; flex: 1;
            min-height: 200px; overflow: hidden;
        }
        .compare-column {
            flex: 1; display: flex; flex-direction: column;
            background: #000; border-radius: 6px; padding: 6px;
            border: 1px solid #222; overflow: hidden; min-width: 0; position: relative;
        }
        .compare-column-placeholder {
            flex: 1; min-height: 200px; background: #000;
            border-radius: 6px; border: 1px solid #222;
        }
        #matched-column { background: #0a1a1a; border: 1px solid #00ff88; }
        .column-header {
            font-size: 11px; font-weight: bold; color: #888;
            border-bottom: 1px solid #333; margin-bottom: 8px; padding: 4px;
            display: flex; justify-content: space-between; flex-shrink: 0;
            user-select: none; cursor: pointer;
        }
        .column-header:hover { color: #00ccff; background: rgba(0,204,255,0.1); border-radius: 4px; }
        .diff-count { color: #ff4444; font-size: 12px; }
        .list-content {
            flex: 1; overflow-y: auto; overflow-x: hidden; font-size: 11px; min-height: 100px;
            scrollbar-width: thin; scrollbar-color: #444 #1a1a1a;
        }
        .list-content::-webkit-scrollbar { width: 6px; }
        .list-content::-webkit-scrollbar-track { background: #1a1a1a; }
        .list-content::-webkit-scrollbar-thumb { background: #444; border-radius: 3px; }
        .list-content::-webkit-scrollbar-thumb:hover { background: #666; }
        .diff-item {
            border-bottom: 1px solid #1a1a1a; padding: 8px 5px; transition: 0.2s;
            display: flex; align-items: center; justify-content: space-between;
        }
        .diff-item:hover { background: #222; }
        .item-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
        .item-title a { color: #5cafff; text-decoration: none; }
        .duration-badge {
            font-size: 10px; margin-left: 10px; flex-shrink: 0; font-family: monospace;
            background: #333; padding: 2px 6px; border-radius: 12px;
        }
        .duration-iwara { color: #ffaa00; border-left: 2px solid #ffaa00; }
        .duration-hanime { color: #00ffaa; border-left: 2px solid #00ffaa; }
        .btn-row { display: flex; gap: 6px; margin-bottom: 10px; }
        button {
            flex: 1; cursor: pointer; background: #2a2a2a; color: #fff;
            border: 1px solid #444; padding: 8px; border-radius: 5px;
            font-size: 11px; transition: 0.2s;
        }
        button:hover { background: #3a3a3a; border-color: #00ccff; }
        .btn-capture { background: #004a80 !important; font-weight: bold; }
        .similarity-badge {
            display: inline-block; background: #333; color: #ffaa00;
            font-size: 10px; padding: 2px 5px; border-radius: 10px; margin-left: 5px;
        }
        #similarity-tooltip {
            position: fixed; background: #252525; border: 1px solid #444;
            border-radius: 6px; padding: 8px; z-index: 10001;
            min-width: 250px; box-shadow: 0 4px 15px rgba(0,0,0,0.5); pointer-events: none;
        }
        /* 排序面板样式 */
        .sort-controls {
            display: flex; gap: 10px; margin-bottom: 10px; align-items: center;
        }
        .sort-controls select, .sort-controls button {
            background: #333; color: #fff; border: 1px solid #555;
            border-radius: 4px; padding: 6px 10px; font-size: 12px; cursor: pointer;
        }
        .sort-controls select:focus { border-color: #00ccff; outline: none; }
        .sort-list { flex: 1; overflow-y: auto; min-height: 200px; }
        .sort-item {
            border-bottom: 1px solid #1a1a1a; padding: 8px 6px;
            display: flex; align-items: center; gap: 8px; font-size: 11px;
            transition: 0.2s;
        }
        .sort-item:hover { background: #222; }
        .sort-rank {
            width: 28px; height: 28px; border-radius: 50%;
            display: flex; align-items: center; justify-content: center;
            font-size: 11px; font-weight: bold; flex-shrink: 0;
        }
        .sort-rank.top1 { background: #ffd700; color: #000; }
        .sort-rank.top2 { background: #c0c0c0; color: #000; }
        .sort-rank.top3 { background: #cd7f32; color: #fff; }
        .sort-rank.normal { background: #333; color: #888; }
        .sort-title { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
        .sort-title a { color: #5cafff; text-decoration: none; }
        .sort-meta { display: flex; gap: 8px; flex-shrink: 0; align-items: center; }
        .sort-badge {
            font-size: 10px; padding: 2px 6px; border-radius: 10px;
            font-family: monospace; white-space: nowrap;
        }
        .sort-badge.view { background: #1a3a2a; color: #00ff88; }
        .sort-badge.like { background: #3a1a2a; color: #ff6b9d; }
        .sort-badge.ratio { background: #2a2a1a; color: #ffcc00; }
        .sort-badge.date { background: #1a2a3a; color: #6bc5ff; }
        .sort-level-row {
            display: flex; align-items: center; gap: 6px;
            padding: 4px 8px; background: #2a2a2a; border-radius: 4px;
            font-size: 11px;
        }
        .sort-level-row select {
            background: #333; color: #fff; border: 1px solid #555;
            border-radius: 3px; padding: 4px 6px; font-size: 11px;
        }
        .sort-level-row label { color: #888; font-size: 10px; white-space: nowrap; }
        .sort-levels { display: flex; flex-direction: column; gap: 4px; margin-bottom: 8px; }
        .sort-levels .level-label { font-size: 11px; color: #aaa; margin-bottom: 2px; font-weight: bold; }
        .sort-body {
            display: flex; gap: 12px; flex: 1; min-height: 0; overflow: hidden;
        }
        .sort-left {
            width: 320px; flex-shrink: 0; display: flex; flex-direction: column;
            overflow-y: auto;
        }
        .sort-right {
            flex: 1; display: none; flex-direction: column; min-width: 0;
            border-left: 1px solid #333; padding-left: 12px; overflow: hidden;
        }
        .sort-right.show { display: flex; }
        .sort-right-header {
            font-size: 12px; font-weight: bold; color: #00ccff;
            padding: 6px 0; border-bottom: 1px solid #333; margin-bottom: 6px;
            display: flex; justify-content: space-between; flex-shrink: 0;
        }
        .sort-col-toggles {
            display: flex; gap: 4px; flex-wrap: wrap; margin-bottom: 6px; flex-shrink: 0;
        }
        .sort-col-toggle {
            font-size: 10px; padding: 3px 8px; border-radius: 10px;
            cursor: pointer; user-select: none; border: 1px solid #555;
            background: #2a2a2a; color: #888; transition: 0.2s;
        }
        .sort-col-toggle.on { color: #fff; }
        .sort-col-toggle.on.view { background: #1a3a2a; border-color: #00ff88; color: #00ff88; }
        .sort-col-toggle.on.like { background: #3a1a2a; border-color: #ff6b9d; color: #ff6b9d; }
        .sort-col-toggle.on.ratio { background: #2a2a1a; border-color: #ffcc00; color: #ffcc00; }
        .sort-col-toggle.on.date { background: #1a2a3a; border-color: #6bc5ff; color: #6bc5ff; }
        .sort-col-toggle.on.duration { background: #3a2a1a; border-color: #ffaa00; color: #ffaa00; }
    `);

    // --- 工具函数 ---
    function parseDuration(s) {
        if (!s) return null;
        const m = s.match(/(\d+):(\d+)(?::(\d+))?/);
        if (!m) return null;
        const a = parseInt(m[1]), b = parseInt(m[2]), c = m[3] ? parseInt(m[3]) : 0;
        return c > 0 ? c * 3600 + a * 60 + b : a * 60 + b;
    }

    function formatDuration(sec) {
        if (sec == null) return '无时长';
        const h = Math.floor(sec / 3600), m = Math.floor((sec % 3600) / 60), s = sec % 60;
        const pad = n => String(n).padStart(2, '0');
        return h > 0 ? `${h}:${pad(m)}:${pad(s)}` : `${m}:${pad(s)}`;
    }

    function parseCompactNumber(s) {
        if (!s) return 0;
        s = s.trim().replace(/,/g, '');
        const m = s.match(/^([\d.]+)\s*([KkMmBb]?)$/);
        if (!m) return parseInt(s) || 0;
        const n = parseFloat(m[1]);
        const suf = m[2].toUpperCase();
        if (suf === 'K') return Math.round(n * 1000);
        if (suf === 'M') return Math.round(n * 1000000);
        if (suf === 'B') return Math.round(n * 1000000000);
        return Math.round(n);
    }

    function formatNumber(n) {
        if (n >= 1000000) return (n / 1000000).toFixed(1) + 'M';
        if (n >= 1000) return (n / 1000).toFixed(1) + 'K';
        return String(n);
    }

    function parseDate(s) {
        if (!s) return 0;
        const d = new Date(s);
        return isNaN(d.getTime()) ? 0 : d.getTime();
    }

    function getPoolSize() {
        const iwara = JSON.stringify(GM_getValue('iwara_pool', []) || []);
        const hanime = JSON.stringify(GM_getValue('hanime1me_pool', []) || []);
        return iwara.length + hanime.length;
    }

    function formatSize(bytes) {
        if (bytes < 1024) return bytes + ' B';
        if (bytes < 1048576) return (bytes / 1024).toFixed(1) + ' KB';
        return (bytes / 1048576).toFixed(2) + ' MB';
    }

    function stemWord(w) {
        if (/^\d+$/.test(w)) return w;
        return w.toLowerCase().replace(/(ing|ed|s|es|ies|ly|er|est|tion|ive|able|ible|al|y)$/, '').replace(/[^\w\u4e00-\u9fa5]/g, '');
    }

    function cleanTitle(t) {
        return (t || '').replace(/\[.*?\]|\(.*?\)|【.*?】/g, '').replace(/[::]/g, ' ')
            .replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]|\u200d|\uFE0F/g, '')
            .replace(/[^\w\s\u4e00-\u9fa5]/gi, ' ').replace(/\s+/g, ' ').trim().toLowerCase();
    }

    function editDist(a, b) {
        const c = [];
        for (let i = 0; i <= a.length; i++) {
            let last = i;
            for (let j = 0; j <= b.length; j++) {
                if (i === 0) c[j] = j;
                else if (j > 0) {
                    let nv = c[j - 1];
                    if (a[i - 1] !== b[j - 1]) nv = Math.min(nv, last, c[j]) + 1;
                    c[j - 1] = last; last = nv;
                }
            }
            if (i > 0) c[b.length] = last;
        }
        return c[b.length];
    }

    function charSim(a, b) {
        if (a.length < b.length) [a, b] = [b, a];
        return a.length === 0 ? 1 : (a.length - editDist(a, b)) / a.length;
    }

    function titleSim(s1, s2) {
        const w1 = s1.split(/\s+/).filter(Boolean).map(stemWord);
        const w2 = s2.split(/\s+/).filter(Boolean).map(stemWord);
        if (!w1.length || !w2.length) return charSim(s1, s2);
        const set1 = new Set(w1), set2 = new Set(w2);
        let inter = 0;
        for (const w of set1) if (set2.has(w)) inter++;
        const union = set1.size + set2.size - inter;
        const jaccard = union > 0 ? inter / union : 0;
        const cov1 = set1.size > 0 ? inter / set1.size : 0;
        const cov2 = set2.size > 0 ? inter / set2.size : 0;
        return jaccard * 0.4 + (cov1 + cov2) / 2 * 0.6;
    }

    function findTopSimilar(item, pool, thres, maxN = 3) {
        return pool.map(op => ({
            title: op.title, url: op.url, duration: op.duration, seconds: op.seconds,
            titleSim: titleSim(cleanTitle(item.title), cleanTitle(op.title)),
            durationDiff: Math.abs(item.seconds - op.seconds)
        })).filter(s => s.titleSim >= thres * 0.6).sort((a, b) => b.titleSim - a.titleSim).slice(0, maxN);
    }

    // --- UI ---
    let menuBtn, dropdown, panel, sortPanel, progressBar, progressContainer, thresholdInput, thresholdVal, toleranceInput;

    function createMenuButton() {
        menuBtn = document.createElement('div');
        menuBtn.id = 'matcher-menu-btn';
        menuBtn.innerHTML = '\u26A1';
        menuBtn.title = '打开菜单';
        document.body.appendChild(menuBtn);

        dropdown = document.createElement('div');
        dropdown.id = 'matcher-dropdown';
        dropdown.innerHTML = `
            <div class="dropdown-item" id="menu-save">\uD83D\uDCBE 保存当前页</div>
            <div class="dropdown-item" id="menu-clear" style="color:#ff6b6b">\uD83D\uDDD1\uFE0F 清除池</div>
            <div style="border-top:1px solid #333;margin:2px 0"></div>
            <div class="dropdown-item" id="menu-compare">\uD83D\uDD0D 对比器</div>
            <div class="dropdown-item" id="menu-sort">\uD83D\uDCCB iwara 池排序</div>
            <div style="border-top:1px solid #333;margin:2px 0"></div>
            <div class="dropdown-item" id="menu-size" style="font-size:11px;color:#888;cursor:default">\uD83D\uDCBE 存储占用: <span id="dropdown-size">-</span></div>`;
        document.body.appendChild(dropdown);

        menuBtn.addEventListener('click', (e) => {
            e.stopPropagation();
            dropdown.classList.toggle('show');
            if (dropdown.classList.contains('show')) {
                document.getElementById('dropdown-size').textContent = formatSize(getPoolSize());
            }
        });

        document.addEventListener('click', (e) => {
            if (!dropdown.contains(e.target) && e.target !== menuBtn) dropdown.classList.remove('show');
        });

        document.getElementById('menu-save').addEventListener('click', () => {
            dropdown.classList.remove('show');
            capturePage();
            updateStats();
            if (sortPanel?.style.display === 'flex') updateSortStats();
        });
        document.getElementById('menu-clear').addEventListener('click', () => {
            dropdown.classList.remove('show');
            clearPools();
        });
        document.getElementById('menu-compare').addEventListener('click', () => {
            dropdown.classList.remove('show');
            showComparePanel();
        });
        document.getElementById('menu-sort').addEventListener('click', () => {
            dropdown.classList.remove('show');
            showSortPanel();
            updateSortStats();
        });
    }

    function showComparePanel() {
        if (sortPanel) sortPanel.style.display = 'none';
        if (!panel) initPanel();
        const visible = panel.style.display === 'flex';
        panel.style.display = visible ? 'none' : 'flex';
        if (!visible) { setTimeout(() => centerEl(panel), 10); updateStats(); }
    }

    function showSortPanel() {
        if (panel) panel.style.display = 'none';
        if (!sortPanel) initSortPanel();
        const visible = sortPanel.style.display === 'flex';
        sortPanel.style.display = visible ? 'none' : 'flex';
        if (!visible) setTimeout(() => centerEl(sortPanel), 10);
    }

    function centerEl(el) {
        if (!el) return;
        const pw = el.offsetWidth || 600, ph = el.offsetHeight || 500;
        let left = (window.innerWidth - pw) / 2, top = (window.innerHeight - ph) / 2;
        left = Math.max(10, Math.min(left, window.innerWidth - pw - 10));
        top = Math.max(10, Math.min(top, window.innerHeight - ph - 10));
        Object.assign(el.style, { left: left + 'px', top: top + 'px', right: 'auto', bottom: 'auto' });
        if (ph > window.innerHeight - 20) { el.style.height = (window.innerHeight - 20) + 'px'; el.style.top = '10px'; }
    }

    function getColumnWidth() {
        const container = document.getElementById('compare-container');
        const vis = Array.from(container.querySelectorAll('.compare-column')).filter(c => c.style.display !== 'none');
        return (container.offsetWidth - (vis.length - 1) * 10) / vis.length;
    }

    function toggleColumnExpand(colId, listId) {
        const col = document.getElementById(colId);
        if (!col) return;
        if (col.classList.contains('expanded')) {
            col.classList.remove('expanded');
            document.getElementById(colId + '-placeholder')?.remove();
            col.style.cssText = '';
        } else {
            const w = getColumnWidth();
            const ph = document.createElement('div');
            ph.id = colId + '-placeholder';
            ph.className = 'compare-column-placeholder';
            ph.style.width = ph.style.minWidth = ph.style.maxWidth = w + 'px';
            col.parentNode.insertBefore(ph, col.nextSibling);
            col.classList.add('expanded');
        }
        updateColumnPositions();
    }

    function updateColumnPositions() {
        const container = document.getElementById('compare-container');
        const vis = Array.from(container.querySelectorAll('.compare-column')).filter(c => c.style.display !== 'none');
        const cw = getColumnWidth();
        const cwPercent = (cw / container.offsetWidth) * 100;
        vis.forEach(col => {
            const ph = document.getElementById(col.id + '-placeholder');
            if (ph) ph.style.width = ph.style.minWidth = ph.style.maxWidth = cw + 'px';
        });
        vis.forEach(col => {
            if (!col.classList.contains('expanded')) {
                col.style.position = col.style.left = col.style.right = col.style.top = col.style.height = col.style.zIndex = col.style.background = col.style.boxShadow = col.style.border = col.style.width = '';
            }
        });
        const borders = { 'iwara-column': '#ffffffff', 'hanime1me-column': '#ff0000ff', 'matched-column': '#00ff88' };
        vis.forEach((col, i) => {
            if (col.classList.contains('expanded')) {
                const leftPct = i * (cwPercent + (10 / container.offsetWidth) * 100);
                Object.assign(col.style, {
                    position: 'absolute', top: '45px', height: 'calc(100% - 57px)',
                    zIndex: '10', background: 'rgba(0,0,0,0.95)',
                    boxShadow: '0 0 30px rgba(0,204,255,0.3)',
                    border: '2px solid ' + (borders[col.id] || '#fff'),
                    left: leftPct + '%', width: cwPercent + '%'
                });
            }
        });
    }

    // --- 初始化对比面板 ---
    function initPanel() {
        panel = document.createElement('div');
        panel.id = 'matcher-panel';
        const th = GM_getValue('match_threshold', 0.5);
        const tol = GM_getValue('duration_tolerance', 10);

        panel.innerHTML = `
            <div class="panel-header" id="panel-header">
                <h3>iwara/hanime1 \u6BD4\u5BF9\u5668</h3>
                <div class="close-btn" id="close-panel">\u00D7</div>
            </div>
            <div class="stat-banner">
                <div class="stat-item">iwara \u6C60\uFF1A<b id="total-i">0</b></div>
                <div class="stat-item">hanime1 \u6C60\uFF1A<b id="total-h">0</b></div>
            </div>
            <div class="progress-container" id="p-container"><div id="progress-bar"></div></div>
            <div class="filter-controls">
                <div class="filter-item" style="margin-bottom:0">
                    <label>\u65F6\u957F\u5BB9\u5DEE:</label>
                    <input type="number" id="tolerance" min="0" max="300" step="5" value="${tol}">
                    <span>\u79D2</span>
                    <button class="swap-mode-btn" id="btn-swap-mode">\uD83D\uDD04 \u4EA4\u6362\u5217</button>
                </div>
                <div class="filter-hint" style="margin-top:5px;margin-left:10px">\u23F1\uFE0F \u81EA\u52A8\u820D\u5F03\u65E0\u65F6\u957F\u7684\u89C6\u9891\uFF0C\u65F6\u957F\u5DEE\u5F02 \u2264 \u5BB9\u5DEE\u79D2\u6570\u624D\u5339\u914D</div>
            </div>
            <div class="threshold-wrap">
                <span>\u6807\u9898\u5339\u914D\u9608\u503C:</span>
                <input type="range" id="threshold-range" min="0.1" max="1.0" step="0.05" value="${th}">
                <span id="threshold-val">${th}</span>
            </div>
            <div class="btn-row">
                <button id="btn-cap" class="btn-capture">\uD83D\uDCBE \u4FDD\u5B58\u5F53\u524D\u9875</button>
                <button id="btn-comp">\uD83D\uDD0D \u5F00\u59CB\u6BD4\u5BF9</button>
                <button id="btn-clr">\uD83D\uDDD1\uFE0F \u6E05\u7A7A\u6570\u636E</button>
                <button id="btn-toggle-matched">\uD83D\uDCCB \u663E\u793A\u5339\u914D\u6210\u529F\u5217</button>
            </div>
            <div class="compare-container" id="compare-container">
                <div class="compare-column" id="iwara-column" data-column="0">
                    <div class="column-header" id="iwara-header">iwara \u72EC\u6709 <span id="diff-i" class="diff-count">0</span></div>
                    <div id="iwara-only" class="list-content"></div>
                </div>
                <div class="compare-column" id="hanime1me-column" data-column="1">
                    <div class="column-header" id="hanime1me-header">hanime1 \u72EC\u6709 <span id="diff-h" class="diff-count">0</span></div>
                    <div id="hanime1me-only" class="list-content"></div>
                </div>
                <div class="compare-column" id="matched-column" data-column="2" style="display:none">
                    <div class="column-header" id="matched-header">\u5339\u914D\u6210\u529F <span id="diff-matched" class="diff-count" style="color:#00ff88">0</span></div>
                    <div id="matched-list" class="list-content"></div>
                </div>
            </div>`;

        document.body.appendChild(panel);
        progressBar = document.getElementById('progress-bar');
        progressContainer = document.getElementById('p-container');
        thresholdInput = document.getElementById('threshold-range');
        thresholdVal = document.getElementById('threshold-val');
        toleranceInput = document.getElementById('tolerance');

        let swapMode = false, selectedColumn = null;

        document.getElementById('btn-cap').onclick = () => capturePage();
        document.getElementById('btn-comp').onclick = performCompare;
        document.getElementById('btn-clr').onclick = clearPools;
        document.getElementById('btn-toggle-matched').onclick = () => {
            const mc = document.getElementById('matched-column');
            const btn = document.getElementById('btn-toggle-matched');
            const hidden = mc.style.display === 'none' || mc.style.display === '' || getComputedStyle(mc).display === 'none';
            mc.style.display = hidden ? 'flex' : 'none';
            btn.innerText = hidden ? '\uD83D\uDCCB \u9690\u85CF\u6210\u529F\u5217' : '\uD83D\uDCCB \u663E\u793A\u5339\u914D\u6210\u529F\u5217';
            if (hidden && swapMode) mc.classList.add('column-swap-ready');
            if (!hidden && selectedColumn?.id === 'matched-column') { mc.classList.remove('column-swap-selected'); selectedColumn = null; }
            centerEl(panel);
            setTimeout(updateColumnPositions, 100);
        };

        document.getElementById('btn-swap-mode').onclick = () => {
            swapMode = !swapMode;
            const btn = document.getElementById('btn-swap-mode');
            btn.classList.toggle('active', swapMode);
            btn.innerText = swapMode ? '\u2705 \u70B9\u51FB\u5217\u4EA4\u6362' : '\uD83D\uDD04 \u4EA4\u6362\u5217';
            if (swapMode) panel.querySelectorAll('.compare-column.expanded').forEach(c => {
                const ids = { 'iwara-column': 'iwara-only', 'hanime1me-column': 'hanime1me-only', 'matched-column': 'matched-list' };
                if (ids[c.id]) toggleColumnExpand(c.id, ids[c.id]);
            });
            panel.querySelectorAll('.compare-column').forEach(c => {
                if (c.style.display !== 'none') c.classList.toggle('column-swap-ready', swapMode);
                if (!swapMode) c.classList.remove('column-swap-selected');
            });
            if (!swapMode && selectedColumn) { selectedColumn.classList.remove('column-swap-selected'); selectedColumn = null; }
        };

        document.getElementById('close-panel').onclick = () => { panel.style.display = 'none'; };

        ['iwara', 'hanime1me', 'matched'].forEach(name => {
            const colId = name + '-column', listId = name === 'hanime1me' ? 'hanime1me-only' : name === 'matched' ? 'matched-list' : 'iwara-only';
            document.getElementById(name + '-header').onclick = (e) => {
                if (swapMode) { e.stopPropagation(); handleColumnSwap(colId); }
                else toggleColumnExpand(colId, listId);
            };
        });

        function handleColumnSwap(colId) {
            const clicked = document.getElementById(colId);
            if (!clicked || clicked.style.display === 'none') return;
            if (!selectedColumn) { selectedColumn = clicked; clicked.classList.add('column-swap-selected'); }
            else if (selectedColumn.id !== colId) {
                const container = document.getElementById('compare-container');
                const ph = document.createElement('div'); ph.style.display = 'none';
                container.insertBefore(ph, selectedColumn);
                container.insertBefore(selectedColumn, clicked);
                container.insertBefore(clicked, ph);
                container.removeChild(ph);
                const tmp = selectedColumn.getAttribute('data-column');
                selectedColumn.setAttribute('data-column', clicked.getAttribute('data-column'));
                clicked.setAttribute('data-column', tmp);
                selectedColumn.classList.remove('column-swap-selected');
                clicked.classList.remove('column-swap-selected');
                selectedColumn = null;
                centerEl(panel);
            } else { selectedColumn.classList.remove('column-swap-selected'); selectedColumn = null; }
        }

        thresholdInput.oninput = (e) => { thresholdVal.innerText = parseFloat(e.target.value).toFixed(2); GM_setValue('match_threshold', e.target.value); };
        toleranceInput.onchange = (e) => { GM_setValue('duration_tolerance', parseInt(e.target.value) || 30); };
        const ro = new ResizeObserver(() => { if (panel.style.display === 'flex') { centerEl(panel); updateColumnPositions(); } });
        ro.observe(panel);
        window.addEventListener('resize', () => { if (panel.style.display === 'flex') updateColumnPositions(); });
    }

    // --- 初始化排序面板 ---
    function initSortPanel() {
        sortPanel = document.createElement('div');
        sortPanel.id = 'sort-panel';
        sortPanel.innerHTML = `
            <div class="panel-header">
                <h3>\uD83D\uDCCB iwara \u6C60\u89C6\u9891\u6392\u5E8F</h3>
                <div class="close-btn" id="close-sort">\u00D7</div>
            </div>
            <div class="stat-banner" style="margin-bottom:8px">
                <div class="stat-item">\u6C60\u5185\u89C6\u9891\uFF1A<b id="sort-pool-count">0</b></div>
                <div class="stat-item">\u6709 views/likes\uFF1A<b id="sort-meta-count">0</b></div>
                <div class="stat-item">\u8FC7\u6EE4\u7ED3\u679C\uFF1A<b id="sort-filtered-count">0</b></div>
            </div>
            <div class="sort-body">
                <div class="sort-left">
                    <div class="btn-row" style="margin-bottom:8px">
                        <button id="sort-btn-cap" class="btn-capture">\uD83D\uDCBE \u4FDD\u5B58\u5F53\u524D\u9875</button>
                        <button id="sort-btn-clr" style="background:#6b2020">\uD83D\uDDD1\uFE0F \u6E05\u7A7A\u6C60</button>
                    </div>
                    <div class="sort-levels">
                        <div class="level-label">\u6392\u5E8F\u4F18\u5148\u7EA7\uFF08\u4ECE\u4E0A\u5230\u4E0B\uFF09</div>
                        <div class="sort-level-row">
                            <label>1\u7EA7:</label>
                            <select id="sort-field-1"><option value="views">\u64AD\u653E\u91CF</option><option value="likes">\u70B9\u8D5E</option><option value="ratio">\u70B9\u8D5E\u7387</option><option value="date">\u65E5\u671F</option></select>
                            <select id="sort-dir-1"><option value="desc">\u964D\u5E8F</option><option value="asc">\u5347\u5E8F</option></select>
                        </div>
                        <div class="sort-level-row">
                            <label>2\u7EA7:</label>
                            <select id="sort-field-2"><option value="">\u65E0</option><option value="views">\u64AD\u653E\u91CF</option><option value="likes">\u70B9\u8D5E</option><option value="ratio">\u70B9\u8D5E\u7387</option><option value="date">\u65E5\u671F</option></select>
                            <select id="sort-dir-2"><option value="desc">\u964D\u5E8F</option><option value="asc">\u5347\u5E8F</option></select>
                        </div>
                        <div class="sort-level-row">
                            <label>3\u7EA7:</label>
                            <select id="sort-field-3"><option value="">\u65E0</option><option value="views">\u64AD\u653E\u91CF</option><option value="likes">\u70B9\u8D5E</option><option value="ratio">\u70B9\u8D5E\u7387</option><option value="date">\u65E5\u671F</option></select>
                            <select id="sort-dir-3"><option value="desc">\u964D\u5E8F</option><option value="asc">\u5347\u5E8F</option></select>
                        </div>
                    </div>
                    <button id="btn-sort-run" style="background:#004a80;font-weight:bold;width:100%;margin-bottom:8px">\u5F00\u59CB\u6392\u5E8F</button>
                    <div style="background:#252525;border-radius:6px;padding:8px 10px;border:1px solid #333">
                        <div style="display:flex;gap:8px;margin-bottom:8px;align-items:center">
                            <label style="font-size:11px;color:#aaa;flex-shrink:0">\u5173\u952E\u5B57:</label>
                            <input type="text" id="sort-keyword" placeholder="\u6807\u988C\u5173\u952E\u5B57\u8FC7\u6EE4..." style="flex:1;background:#333;color:#fff;border:1px solid #555;border-radius:4px;padding:4px 8px;font-size:11px">
                        </div>
                        <div style="display:grid;grid-template-columns:1fr 1fr;gap:8px">
                            <div>
                                <div style="font-size:10px;color:#00ff88;margin-bottom:4px">\uD83D\uDC41 Views \u533A\u95F4</div>
                                <div style="display:flex;gap:4px">
                                    <input type="number" id="filter-view-min" placeholder="\u6700\u5C0F" min="0" style="width:100%;background:#333;color:#fff;border:1px solid #555;border-radius:3px;padding:4px 5px;font-size:11px">
                                    <input type="number" id="filter-view-max" placeholder="\u6700\u5927" min="0" style="width:100%;background:#333;color:#fff;border:1px solid #555;border-radius:3px;padding:4px 5px;font-size:11px">
                                </div>
                            </div>
                            <div>
                                <div style="font-size:10px;color:#ff6b9d;margin-bottom:4px">\u2764\uFE0F Likes \u533A\u95F4</div>
                                <div style="display:flex;gap:4px">
                                    <input type="number" id="filter-like-min" placeholder="\u6700\u5C0F" min="0" style="width:100%;background:#333;color:#fff;border:1px solid #555;border-radius:3px;padding:4px 5px;font-size:11px">
                                    <input type="number" id="filter-like-max" placeholder="\u6700\u5927" min="0" style="width:100%;background:#333;color:#fff;border:1px solid #555;border-radius:3px;padding:4px 5px;font-size:11px">
                                </div>
                            </div>
                            <div>
                                <div style="font-size:10px;color:#ffcc00;margin-bottom:4px">\uD83D\uDCCA \u8D5E\u7387(%)</div>
                                <div style="display:flex;gap:4px">
                                    <input type="number" id="filter-ratio-min" placeholder="\u6700\u5C0F" min="0" max="100" step="0.1" style="width:100%;background:#333;color:#fff;border:1px solid #555;border-radius:3px;padding:4px 5px;font-size:11px">
                                    <input type="number" id="filter-ratio-max" placeholder="\u6700\u5927" min="0" max="100" step="0.1" style="width:100%;background:#333;color:#fff;border:1px solid #555;border-radius:3px;padding:4px 5px;font-size:11px">
                                </div>
                            </div>
                            <div>
                                <div style="font-size:10px;color:#6bc5ff;margin-bottom:4px">\uD83D\uDCC5 \u65E5\u671F\u8303\u56F4</div>
                                <div style="display:flex;gap:4px">
                                    <input type="date" id="filter-date-from" style="width:100%;background:#333;color:#fff;border:1px solid #555;border-radius:3px;padding:4px 5px;font-size:11px">
                                    <input type="date" id="filter-date-to" style="width:100%;background:#333;color:#fff;border:1px solid #555;border-radius:3px;padding:4px 5px;font-size:11px">
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
                <div class="sort-right" id="sort-right">
                    <div class="sort-right-header">
                        <span>\u6392\u5E8F\u7ED3\u679C</span>
                        <span id="sort-result-info" style="font-size:11px;color:#888;font-weight:normal"></span>
                    </div>
                    <div class="sort-col-toggles" id="sort-col-toggles">
                        <span class="sort-col-toggle on view" data-col="view">\uD83D\uDC41 Views</span>
                        <span class="sort-col-toggle on like" data-col="like">\u2764\uFE0F Likes</span>
                        <span class="sort-col-toggle on ratio" data-col="ratio">\uD83D\uDCCA \u8D5E\u7387</span>
                        <span class="sort-col-toggle on date" data-col="date">\uD83D\uDCC5 \u65E5\u671F</span>
                        <span class="sort-col-toggle on duration" data-col="duration">\u23F1\uFE0F \u65F6\u957F</span>
                    </div>
                    <div class="sort-list" id="sort-list"></div>
                </div>`;
        document.body.appendChild(sortPanel);

        document.getElementById('close-sort').onclick = () => { sortPanel.style.display = 'none'; };
        document.getElementById('btn-sort-run').onclick = runSort;
        document.getElementById('sort-btn-cap').onclick = () => { capturePage(); updateSortStats(); };

        // 列显示切换
        document.getElementById('sort-col-toggles').addEventListener('click', (e) => {
            const t = e.target.closest('.sort-col-toggle');
            if (t) { t.classList.toggle('on'); runSort(); }
        });
        document.getElementById('sort-btn-clr').onclick = () => {
            if (confirm('\u6E05\u7A7A iwara \u6C60\u6240\u6709\u6570\u636E\uFF1F')) {
                GM_setValue('iwara_pool', []);
                updateStats(); updateSortStats();
                document.getElementById('sort-right').classList.remove('show');
            }
        };

        const ro = new ResizeObserver(() => { if (sortPanel.style.display === 'flex') centerEl(sortPanel); });
        ro.observe(sortPanel);
    }

    function updateSortStats() {
        const pool = GM_getValue('iwara_pool', []) || [];
        document.getElementById('sort-pool-count').innerText = pool.length;
        document.getElementById('sort-meta-count').innerText = pool.filter(v => v.views != null || v.likes != null).length;
    }

    function runSort() {
        const pool = GM_getValue('iwara_pool', []) || [];
        const list = document.getElementById('sort-list');
        const keyword = document.getElementById('sort-keyword').value.trim().toLowerCase();

        const viewMin = parseFloat(document.getElementById('filter-view-min').value);
        const viewMax = parseFloat(document.getElementById('filter-view-max').value);
        const likeMin = parseFloat(document.getElementById('filter-like-min').value);
        const likeMax = parseFloat(document.getElementById('filter-like-max').value);
        const ratioMin = parseFloat(document.getElementById('filter-ratio-min').value);
        const ratioMax = parseFloat(document.getElementById('filter-ratio-max').value);
        const dateFrom = document.getElementById('filter-date-from').value;
        const dateTo = document.getElementById('filter-date-to').value;
        const dateFromTs = dateFrom ? new Date(dateFrom).getTime() : 0;
        const dateToTs = dateTo ? new Date(dateTo + 'T23:59:59').getTime() : Infinity;

        const levels = [1, 2, 3].map(i => ({
            field: document.getElementById(`sort-field-${i}`).value,
            dir: document.getElementById(`sort-dir-${i}`).value
        })).filter(l => l.field);

        updateSortStats();
        if (!pool.length) { list.innerHTML = '<div style="padding:30px;text-align:center;color:#666">\u6C60\u4E3A\u7A7A</div>'; document.getElementById('sort-right').classList.add('show'); return; }

        const filtered = pool.map(v => {
            const views = v.views || 0, likes = v.likes || 0;
            const ratio = views > 0 ? likes / views : 0;
            const dateTs = parseDate(v.date);
            return { ...v, views, likes, ratio, dateTs };
        }).filter(v => {
            if (keyword && !v.title.toLowerCase().includes(keyword)) return false;
            if (!isNaN(viewMin) && v.views < viewMin) return false;
            if (!isNaN(viewMax) && v.views > viewMax) return false;
            if (!isNaN(likeMin) && v.likes < likeMin) return false;
            if (!isNaN(likeMax) && v.likes > likeMax) return false;
            const pct = v.ratio * 100;
            if (!isNaN(ratioMin) && pct < ratioMin) return false;
            if (!isNaN(ratioMax) && pct > ratioMax) return false;
            if (dateFromTs > 0 && v.dateTs > 0 && v.dateTs < dateFromTs) return false;
            if (dateToTs < Infinity && v.dateTs > 0 && v.dateTs > dateToTs) return false;
            return true;
        }).sort((a, b) => {
            for (const lv of levels) {
                const va = lv.field === 'date' ? a.dateTs : a[lv.field];
                const vb = lv.field === 'date' ? b.dateTs : b[lv.field];
                const diff = (vb || 0) - (va || 0);
                if (diff !== 0) return lv.dir === 'desc' ? diff : -diff;
            }
            return 0;
        });

        document.getElementById('sort-filtered-count').innerText = filtered.length;
        document.getElementById('sort-result-info').textContent = `${filtered.length} \u6761`;
        document.getElementById('sort-right').classList.add('show');

        // 读取列显示状态
        const showCol = {};
        document.querySelectorAll('#sort-col-toggles .sort-col-toggle').forEach(el => {
            showCol[el.dataset.col] = el.classList.contains('on');
        });

        list.innerHTML = '';
        if (!filtered.length) { list.innerHTML = '<div style="padding:30px;text-align:center;color:#666">\u65E0\u7B26\u5408\u6761\u4EF6</div>'; return; }

        filtered.forEach((item, idx) => {
            const div = document.createElement('div');
            div.className = 'sort-item';
            const rankCls = idx === 0 ? 'top1' : idx === 1 ? 'top2' : idx === 2 ? 'top3' : 'normal';
            let badges = '';
            if (showCol.view) badges += `<span class="sort-badge view">\uD83D\uDC41 ${formatNumber(item.views)}</span>`;
            if (showCol.like) badges += `<span class="sort-badge like">\u2764\uFE0F ${formatNumber(item.likes)}</span>`;
            if (showCol.ratio) badges += `<span class="sort-badge ratio">${(item.ratio * 100).toFixed(1)}%</span>`;
            if (showCol.date && item.date) badges += `<span class="sort-badge date">\uD83D\uDCC5 ${item.date}</span>`;
            if (showCol.duration) badges += `<span class="duration-badge duration-iwara">${formatDuration(item.seconds)}</span>`;
            div.innerHTML = `
                <div class="sort-rank ${rankCls}">${idx + 1}</div>
                <div class="sort-title"><a href="${item.url}" target="_blank">${item.title}</a></div>
                ${badges ? `<div class="sort-meta">${badges}</div>` : ''}`;
            list.appendChild(div);
        });
    }

    // --- 捕获数据 ---
    function captureiwara() {
        return [...document.querySelectorAll('.videoTeaser')].map(el => {
            try {
                const title = el.querySelector('.videoTeaser__title')?.innerText.trim() || '';
                const href = el.querySelector('.videoTeaser__thumbnail')?.getAttribute('href');
                const url = href ? 'https://www.iwara.tv' + href : '';
                const duration = el.querySelector('.duration .text')?.innerText.trim() || '';
                const viewsText = el.querySelector('.views .text')?.innerText.trim() || '';
                const likesText = el.querySelector('.likes .text')?.innerText.trim() || '';
                const dateText = el.querySelector('.byline .text')?.innerText.trim() || '';
                const views = viewsText ? parseCompactNumber(viewsText) : null;
                const likes = likesText ? parseCompactNumber(likesText) : null;
                return title && url ? { title, url, duration, seconds: parseDuration(duration), views, likes, date: dateText || null } : null;
            } catch { return null; }
        }).filter(Boolean);
    }

    function capturehanime1() {
        return [...document.querySelectorAll('.video-item-container')].map(el => {
            try {
                const title = el.getAttribute('title') || el.querySelector('.title')?.innerText.trim() || '';
                const url = el.querySelector('.video-link')?.getAttribute('href') || '';
                const duration = el.querySelector('.duration')?.innerText.trim() || '';
                return title && url ? { title, url, duration, seconds: parseDuration(duration) } : null;
            } catch { return null; }
        }).filter(Boolean);
    }

    function capturePage() {
        const host = window.location.host;
        let poolKey, videos;
        if (host.includes('iwara.tv')) { poolKey = 'iwara_pool'; videos = captureiwara(); }
        else if (host.includes('hanime')) { poolKey = 'hanime1me_pool'; videos = capturehanime1(); }
        else { showPanelMsg('\u26A0\uFE0F \u5F53\u524D\u9875\u9762\u4E0D\u652F\u6301', '#ff6b6b'); return; }

        let pool = GM_getValue(poolKey, []) || [];
        let added = 0, skipped = 0, duplicated = 0;
        videos.forEach(v => {
            if (!v.seconds) { skipped++; return; }
            if (pool.some(p => p.url === v.url)) { duplicated++; return; }
            pool.push(v); added++;
        });
        GM_setValue(poolKey, pool);
        updateStats();

        const msg = added === 0 && duplicated > 0
            ? `\u26A0\uFE0F \u5F53\u524D\u9875\u5168\u90E8 ${duplicated} \u6761\u5DF2\u5B58\u5728`
            : `\u2705 \u65B0\u589E ${added} \u6761\uFF0C\u603B\u8BA1 ${pool.length}\uFF08\u91CD\u590D ${duplicated}\uFF0C\u65E0\u65F6\u957F ${skipped}\uFF09`;
        const color = added === 0 && duplicated > 0 ? '#ffaa00' : '#00ff88';
        showPanelMsg(msg, color);
    }

    function showPanelMsg(msg, color = '#00ff88') {
        document.getElementById('_capToast')?.remove();
        const t = document.createElement('div');
        t.id = '_capToast';
        t.style.cssText = `position:fixed;top:20px;left:50%;transform:translateX(-50%);background:#1e1e1e;color:${color};padding:10px 24px;border-radius:8px;z-index:999999;font-size:13px;box-shadow:0 4px 16px rgba(0,0,0,0.6);border:1px solid #444;white-space:nowrap;transition:opacity 0.3s`;
        t.textContent = msg;
        document.body.appendChild(t);
        setTimeout(() => { t.style.opacity = '0'; setTimeout(() => t.remove(), 300); }, 3000);
    }

    // --- 核心比对 ---
    async function performCompare() {
        const iwara = GM_getValue('iwara_pool', []) || [];
        const hanime = GM_getValue('hanime1me_pool', []) || [];
        const thres = parseFloat(thresholdInput.value);
        const tolerance = parseInt(toleranceInput.value) || 30;
        const iDur = iwara.filter(v => v.seconds !== null);
        const hDur = hanime.filter(v => v.seconds !== null);

        const $i = document.getElementById('iwara-only');
        const $h = document.getElementById('hanime1me-only');
        const $m = document.getElementById('matched-list');
        $i.innerHTML = $h.innerHTML = $m.innerHTML = '<div style="padding:20px;text-align:center;color:#666">\u6BD4\u5BF9\u4E2D...</div>';

        progressContainer.style.display = 'block';
        progressBar.style.width = '0%';

        const total = iDur.length + hDur.length;
        const matchedPairs = [], iOnly = [], hOnly = [];
        const hMatched = new Set(), iMatched = new Set();

        for (let i = 0; i < iDur.length; i++) {
            const item = iDur[i];
            let maxSim = 0, best = null;
            for (const h of hDur) {
                if (Math.abs(item.seconds - h.seconds) > tolerance) continue;
                const s = titleSim(cleanTitle(item.title), cleanTitle(h.title));
                if (s > maxSim) { maxSim = s; best = h; }
            }
            if (maxSim >= thres && best) {
                matchedPairs.push({ iwara: item, hanime: best, similarity: maxSim });
                hMatched.add(best.url);
                iMatched.add(item.url);
            }
            if (i % 5 === 0) { progressBar.style.width = (i / total * 100) + '%'; await new Promise(r => setTimeout(r, 0)); }
        }

        for (const item of iDur) if (!iMatched.has(item.url)) {
            let maxSim = 0;
            for (const h of hDur) { if (Math.abs(item.seconds - h.seconds) > tolerance) continue; maxSim = Math.max(maxSim, titleSim(cleanTitle(item.title), cleanTitle(h.title))); }
            iOnly.push({ ...item, maxSimilarity: maxSim });
        }
        for (const item of hDur) if (!hMatched.has(item.url)) {
            let maxSim = 0;
            for (const i of iDur) { if (Math.abs(item.seconds - i.seconds) > tolerance) continue; maxSim = Math.max(maxSim, titleSim(cleanTitle(item.title), cleanTitle(i.title))); }
            hOnly.push({ ...item, maxSimilarity: maxSim });
        }

        progressBar.style.width = '100%';
        setTimeout(() => progressContainer.style.display = 'none', 500);
        renderResults(iOnly, hOnly, matchedPairs, iDur, hDur, thres, tolerance);
    }

    function renderResults(iOnly, hOnly, matchedPairs, iDur, hDur, thres, tolerance) {
        const $i = document.getElementById('iwara-only');
        const $h = document.getElementById('hanime1me-only');
        const $m = document.getElementById('matched-list');
        $i.innerHTML = $h.innerHTML = $m.innerHTML = '';
        matchedPairs.sort((a, b) => b.similarity - a.similarity);

        function renderDiffItem(item, pool, container, durClass) {
            const sims = findTopSimilar(item, pool, thres, 3);
            const maxSim = sims.length > 0 ? sims[0].titleSim : 0;
            const div = document.createElement('div');
            div.className = 'diff-item';
            div.innerHTML = `
                <div class="item-title">
                    <a href="${item.url}" target="_blank">${item.title}</a>
                    ${maxSim > 0 ? `<span class="similarity-badge">${(maxSim * 100).toFixed(0)}%</span>` : ''}
                </div>
                <div class="duration-badge ${durClass}">${formatDuration(item.seconds)}</div>`;
            div._similarities = sims;
            if (sims.length > 0) bindTooltip(div, sims, thres);
            container.appendChild(div);
        }

        iOnly.forEach(item => renderDiffItem(item, hDur, $i, 'duration-iwara'));
        hOnly.forEach(item => renderDiffItem(item, iDur, $h, 'duration-hanime'));

        matchedPairs.forEach(pair => {
            const div = document.createElement('div');
            div.className = 'diff-item';
            div.style.background = '#1a2a2a';
            const otherSim = [
                ...hDur.filter(h => h.url !== pair.hanime.url).map(h => ({ title: h.title, url: h.url, duration: h.duration, seconds: h.seconds, titleSim: titleSim(cleanTitle(pair.iwara.title), cleanTitle(h.title)), durationDiff: Math.abs(pair.iwara.seconds - h.seconds), type: 'hanime' })),
                ...iDur.filter(i => i.url !== pair.iwara.url).map(i => ({ title: i.title, url: i.url, duration: i.duration, seconds: i.seconds, titleSim: titleSim(cleanTitle(pair.hanime.title), cleanTitle(i.title)), durationDiff: Math.abs(pair.hanime.seconds - i.seconds), type: 'iwara' }))
            ].filter(s => s.titleSim >= thres * 0.6).sort((a, b) => b.titleSim - a.titleSim).slice(0, 4);

            div.innerHTML = `
                <div style="flex:1;min-width:0">
                    <div style="display:flex;align-items:center;gap:8px;margin-bottom:4px">
                        <a href="${pair.iwara.url}" target="_blank" style="color:#00ccff;font-size:11px;flex:1">${pair.iwara.title}</a>
                        <span class="duration-badge duration-iwara">${formatDuration(pair.iwara.seconds)}</span>
                    </div>
                    <div style="display:flex;align-items:center;gap:8px">
                        <a href="${pair.hanime.url}" target="_blank" style="color:#00ffaa;font-size:11px;flex:1">${pair.hanime.title}</a>
                        <span class="duration-badge duration-hanime">${formatDuration(pair.hanime.seconds)}</span>
                    </div>
                </div>
                <span class="similarity-badge" style="background:#00ff88;color:#000;font-weight:bold">${(pair.similarity * 100).toFixed(1)}%</span>`;
            if (otherSim.length > 0) { div._similarities = otherSim; bindTooltip(div, otherSim, thres); }
            $m.appendChild(div);
        });

        document.getElementById('diff-i').innerText = iOnly.length;
        document.getElementById('diff-h').innerText = hOnly.length;
        document.getElementById('diff-matched').innerText = matchedPairs.length;

        const iFilt = iDur.length, hFilt = hDur.length;
        const iTot = (GM_getValue('iwara_pool', []) || []).length;
        const hTot = (GM_getValue('hanime1me_pool', []) || []).length;
        if (iFilt < iTot || hFilt < hTot) {
            const n = document.createElement('div');
            n.style.cssText = 'text-align:center;font-size:10px;color:#ffaa00;margin-top:5px';
            n.innerHTML = `\u23F1\uFE0F \u5DF2\u8FC7\u6EE4\u65E0\u65F6\u957F\u89C6\u9891: iwara ${iTot - iFilt}\u6761, hanime1 ${hTot - hFilt}\u6761`;
            panel.querySelector('.compare-container').before(n);
            setTimeout(() => n.remove(), 5000);
        }

        if (!iOnly.length) $i.innerHTML = '<div style="padding:20px;text-align:center;color:#666">\u6CA1\u6709\u72EC\u6709\u9879</div>';
        if (!hOnly.length) $h.innerHTML = '<div style="padding:20px;text-align:center;color:#666">\u6CA1\u6709\u72EC\u6709\u9879</div>';
    }

    function bindTooltip(div, sims, thres) {
        div.addEventListener('mouseenter', () => {
            const tip = document.createElement('div');
            tip.id = 'similarity-tooltip';
            tip.innerHTML = `
                <div style="font-size:10px;color:#888;margin-bottom:4px">\u76F8\u4F3C\u9879\uFF1A</div>
                ${sims.map(s => `
                    <div style="margin:3px 0;padding:3px;background:#1a1a1a;border-radius:3px">
                        <a href="${s.url}" target="_blank" style="color:${s.type === 'iwara' ? '#00ccff' : s.type === 'hanime' ? '#00ffaa' : '#5cafff'};font-size:11px">${s.title}</a>
                        <span style="color:${s.titleSim >= thres ? '#00ff88' : '#ffaa00'};font-size:10px;float:right">${(s.titleSim * 100).toFixed(1)}%</span>
                        <div style="font-size:9px;color:#888;margin-top:2px">\u23F1\uFE0F ${s.duration} | \u76F8\u5DEE ${s.durationDiff}\u79D2</div>
                    </div>`).join('')}`;
            document.body.appendChild(tip);
            const r = div.getBoundingClientRect();
            let left = r.right + 10, top = r.top;
            if (r.right + 260 > window.innerWidth) left = r.left - 260;
            if (r.top + tip.offsetHeight + 10 > window.innerHeight) top = window.innerHeight - tip.offsetHeight - 10;
            tip.style.left = Math.max(10, left) + 'px';
            tip.style.top = Math.max(10, top) + 'px';
        });
        div.addEventListener('mouseleave', () => document.getElementById('similarity-tooltip')?.remove());
    }

    function clearPools() {
        if (confirm('\u6E05\u7A7A\u6240\u6709\u5B58\u50A8\u7684\u6570\u636E\uFF1F')) {
            GM_setValue('iwara_pool', []);
            GM_setValue('hanime1me_pool', []);
            updateStats();
            showPanelMsg('\u2705 \u6C60\u5DF2\u6E05\u7A7A');
            if (panel?.style.display === 'flex') performCompare();
            if (sortPanel?.style.display === 'flex') {
                updateSortStats();
                document.getElementById('sort-right')?.classList.remove('show');
            }
        }
    }

    function updateStats() {
        const ti = document.getElementById('total-i');
        const th = document.getElementById('total-h');
        if (ti) ti.innerText = (GM_getValue('iwara_pool', []) || []).length;
        if (th) th.innerText = (GM_getValue('hanime1me_pool', []) || []).length;
    }

    createMenuButton();
})();