LDStatus Pro

在 Linux.do 和 IDCFlare 页面显示信任级别进度,支持历史趋势、里程碑通知、阅读时间统计、排行榜系统、我的活动查看。两站点均支持排行榜和云同步功能

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

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

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

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

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

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

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

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

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

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

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

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

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

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

 // ==UserScript==
    // @name         LDStatus Pro
    // @namespace    http://tampermonkey.net/
    // @version      3.5.4.16
    // @description  在 Linux.do 和 IDCFlare 页面显示信任级别进度,支持历史趋势、里程碑通知、阅读时间统计、排行榜系统、我的活动查看。两站点均支持排行榜和云同步功能
    // @author       JackLiii
    // @license      MIT
    // @match        https://linux.do/*
    // @match        https://idcflare.com/*
    // @match        https://cdk.linux.do/*
    // @match        https://credit.linux.do/*
    // @run-at       document-start
    // @grant        GM_xmlhttpRequest
    // @grant        GM_setValue    
    // @grant        GM_getValue
    // @grant        GM_info
    // @grant        GM_notification
    // @connect      connect.linux.do
    // @connect      linux.do
    // @connect      credit.linux.do
    // @connect      cdk.linux.do
    // @connect      connect.idcflare.com
    // @connect      idcflare.com
    // @connect      github.com
    // @connect      raw.githubusercontent.com
    // @connect      api.ldspro.qzz.io
    // @icon         https://linux.do/uploads/default/optimized/4X/6/a/6/6a6affc7b1ce8140279e959d32671304db06d5ab_2_180x180.png
    // ==/UserScript==

    (function() {
        'use strict';

        // 提前定义跨域白名单 & 调试开关(桥接逻辑需要)
        const ALLOWED_ORIGINS = ['https://linux.do', 'https://www.linux.do', 'https://idcflare.com', 'https://www.idcflare.com'];
        const DEBUG = {
            bridgeLogs: (typeof GM_getValue === 'function' && GM_getValue('ldsp_debug_bridge', false)) || false
        };
        const debugBridgeLog = (...args) => {
            if (DEBUG.bridgeLogs) console.debug('[LDSP][bridge]', ...args);
        };

        // ==================== CDK/LDC 桥接页面 ====================
        // 在 cdk.linux.do 或 credit.linux.do 运行时作为数据桥接,用原生 fetch 请求 API(同域自动带 cookie)
        if (location.hostname === 'cdk.linux.do') {
            window.addEventListener('message', async (e) => {
                if (!ALLOWED_ORIGINS.includes(e.origin) || e.data?.type !== 'ldsp-cdk-request') {
                    debugBridgeLog('CDK bridge reject', { origin: e.origin, type: e.data?.type });
                    return;
                }
                const { requestId, url } = e.data;
                try {
                    const res = await fetch(url, { credentials: 'include' });
                    let data;
                    try { data = await res.json(); } catch { data = { _error: '解析失败' }; }
                    window.parent?.postMessage({ type: 'ldsp-cdk-response', requestId, status: res.status, data }, e.origin);
                } catch (err) {
                    debugBridgeLog('CDK bridge error', err?.message || err);
                    window.parent?.postMessage({ type: 'ldsp-cdk-response', requestId, status: 0, data: { _error: '网络错误' } }, e.origin);
                }
            });
            return; // 桥接页面不执行主程序
        }
        
        // LDC (credit.linux.do) 桥接
        if (location.hostname === 'credit.linux.do') {
            window.addEventListener('message', async (e) => {
                if (!ALLOWED_ORIGINS.includes(e.origin) || e.data?.type !== 'ldsp-ldc-request') {
                    debugBridgeLog('LDC bridge reject', { origin: e.origin, type: e.data?.type });
                    return;
                }
                const { requestId, url, method = 'GET', data } = e.data;
                try {
                    const res = await fetch(url, {
                        method,
                        credentials: 'include',
                        headers: { 'Accept': 'application/json', 'Content-Type': 'application/json' },
                        body: data ? JSON.stringify(data) : undefined
                    });
                    let resData;
                    try { resData = await res.json(); } catch { resData = { _error: '解析失败' }; }
                    window.parent?.postMessage({ type: 'ldsp-ldc-response', requestId, status: res.status, data: resData }, e.origin);
                } catch (err) {
                    debugBridgeLog('LDC bridge error', err?.message || err);
                    window.parent?.postMessage({ type: 'ldsp-ldc-response', requestId, status: 0, data: { _error: '网络错误' } }, e.origin);
                }
            });
            return; // 桥接页面不执行主程序
        }

        // ==================== 尽早捕获 OAuth 登录结果 =====================
        // 由于 Discourse 路由可能会处理掉 URL hash,需要在脚本最开始就提取
        let _pendingOAuthData = null;
        try {
            const hash = window.location.hash;
            console.log('[OAuth] Initial hash check:', hash ? hash.substring(0, 100) + '...' : '(empty)');
            if (hash) {
                const match = hash.match(/ldsp_oauth=([^&]+)/);
                if (match) {
                    console.log('[OAuth] Found ldsp_oauth in hash, decoding...');
                    const encoded = match[1];
                    const decoded = JSON.parse(decodeURIComponent(atob(encoded)));
                    console.log('[OAuth] Decoded data:', { hasToken: !!decoded.t, hasUser: !!decoded.u, ts: decoded.ts });
                    // 检查时效性(5分钟内有效)
                    if (decoded.ts && Date.now() - decoded.ts < 5 * 60 * 1000) {
                        _pendingOAuthData = {
                            success: true,
                            token: decoded.t,
                            user: decoded.u,
                            isJoined: decoded.j === 1
                        };
                        console.log('[OAuth] ✅ Captured login data from URL hash, user:', decoded.u?.username);
                    } else {
                        console.log('[OAuth] ⚠️ Login data expired, age:', Date.now() - decoded.ts, 'ms');
                    }
                    // 立即清除 URL 中的登录参数
                    let newHash = hash.replace(/[#&]?ldsp_oauth=[^&]*/, '').replace(/^[#&]+/, '').replace(/[#&]+$/, '');
                    const newUrl = window.location.pathname + window.location.search + (newHash ? '#' + newHash : '');
                    history.replaceState(null, '', newUrl);
                }
            }
        } catch (e) {
            console.warn('[OAuth] Failed to capture OAuth data:', e);
        }

        // ==================== 浏览器兼容性检查 ====================
        // 检测必需的 API 是否存在
        if (typeof Map === 'undefined' || typeof Set === 'undefined' || typeof Promise === 'undefined') {
            console.error('[LDStatus Pro] 浏览器版本过低,请升级浏览器');
            return;
        }

        // 兼容性:requestIdleCallback polyfill(Firefox 和旧版浏览器)
        const requestIdleCallback = window.requestIdleCallback || function(cb) {
            const start = Date.now();
            return setTimeout(() => cb({ didTimeout: false, timeRemaining: () => Math.max(0, 50 - (Date.now() - start)) }), 1);
        };
        const _cancelIdleCallback = window.cancelIdleCallback || clearTimeout;

        // ==================== 网站配置 ====================
        const SITE_CONFIGS = {
            'linux.do': {
                name: 'Linux.do',
                icon: 'https://linux.do/uploads/default/optimized/4X/6/a/6/6a6affc7b1ce8140279e959d32671304db06d5ab_2_180x180.png',
                apiUrl: 'https://connect.linux.do',
                supportsLeaderboard: true
            },
            'idcflare.com': {
                name: 'IDCFlare',
                icon: 'https://idcflare.com/uploads/default/optimized/1X/8746f94a48ddc8140e8c7a52084742f38d3f5085_2_180x180.png',
                apiUrl: 'https://connect.idcflare.com',
                supportsLeaderboard: true
            }
        };

        const CURRENT_SITE = (() => {
            const hostname = window.location.hostname;
            for (const [domain, config] of Object.entries(SITE_CONFIGS)) {
                if (hostname === domain || hostname.endsWith(`.${domain}`)) {
                    return { domain, prefix: domain.replace('.', '_'), ...config };
                }
            }
            return null;
        })();

        if (!CURRENT_SITE) {
            console.warn('[LDStatus Pro] 不支持的网站');
            return;
        }

        // ==================== Discourse 鉴权头工具 ====================
        const getCsrfToken = () => document.querySelector('meta[name="csrf-token"]')?.content || '';
        const buildAuthHeaders = (url, extra = {}) => {
            const headers = { ...extra };
            let host = '';
            try { host = new URL(url, location.href).hostname; } catch {}
            const needDiscourseHeaders = host && (host.endsWith('linux.do') || host.endsWith('idcflare.com'));
            if (needDiscourseHeaders) {
                if (!headers['X-Requested-With'] && !headers['x-requested-with']) headers['X-Requested-With'] = 'XMLHttpRequest';
                if (!headers['Discourse-Logged-In']) headers['Discourse-Logged-In'] = 'true';
                if (!headers['Discourse-Present']) headers['Discourse-Present'] = 'true';
                const csrf = getCsrfToken();
                if (csrf && !headers['X-CSRF-Token'] && !headers['x-csrf-token']) headers['X-CSRF-Token'] = csrf;
                if (!headers['Referer']) headers['Referer'] = `${location.origin}/`;
            }
            return headers;
        };

        // ==================== 事件总线(跨模块通信) ====================
        const EventBus = {
            _listeners: new Map(),
            
            // 订阅事件
            on(event, callback) {
                if (!this._listeners.has(event)) {
                    this._listeners.set(event, new Set());
                }
                this._listeners.get(event).add(callback);
                // 返回取消订阅函数
                return () => this.off(event, callback);
            },
            
            // 取消订阅
            off(event, callback) {
                this._listeners.get(event)?.delete(callback);
            },
            
            // 发布事件
            emit(event, data) {
                this._listeners.get(event)?.forEach(cb => {
                    try { cb(data); } catch (e) { /* 静默失败 */ }
                });
            },
            
            // 一次性订阅
            once(event, callback) {
                const wrapper = (data) => {
                    this.off(event, wrapper);
                    callback(data);
                };
                return this.on(event, wrapper);
            },
            
            // 清理所有监听器
            clear() {
                this._listeners.clear();
            }
        };

        // ==================== 跨标签页领导者管理器(全局单例) ====================
        // 确保同一时间只有一个标签页执行定时任务(阅读计时、同步、刷新等)
        const TabLeader = {
            LEADER_KEY: `ldsp_tab_leader_${CURRENT_SITE.prefix}`,
            HEARTBEAT: 5000,    // 5秒心跳
            TIMEOUT: 10000,     // 10秒超时(减少陈旧数据导致的等待时间)
            _tabId: Date.now().toString(36) + Math.random().toString(36).slice(2, 8),
            _isLeader: false,
            _initialized: false,
            _interval: null,
            _callbacks: [],     // 领导者状态变化回调
            
            init() {
                if (this._initialized) return;
                this._initialized = true;
                
                this._tryBecomeLeader();
                this._interval = setInterval(() => this._tryBecomeLeader(), this.HEARTBEAT);
                
                // 监听其他标签页的变化
                this._storageHandler = (e) => {
                    if (e.key === this.LEADER_KEY) this._tryBecomeLeader();
                };
                window.addEventListener('storage', this._storageHandler);
                
                // 页面卸载时释放领导者
                this._unloadHandler = () => this._release();
                window.addEventListener('beforeunload', this._unloadHandler);
            },
            
            _tryBecomeLeader() {
                const now = Date.now();
                let data = {};
                
                // 检查 localStorage 是否可用(隐私模式、存储满等情况)
                if (!this._storageAvailable) {
                    if (this._storageAvailable === undefined) {
                        try {
                            const testKey = '__ldsp_test__';
                            localStorage.setItem(testKey, '1');
                            localStorage.removeItem(testKey);
                            this._storageAvailable = true;
                        } catch (e) {
                            this._storageAvailable = false;
                            Logger.log('localStorage not available, becoming sole leader');
                        }
                    }
                    // localStorage 不可用,直接成为领导者
                    if (!this._storageAvailable) {
                        if (!this._isLeader) {
                            this._isLeader = true;
                            this._notifyCallbacks(true);
                            EventBus.emit('leader:change', { isLeader: true, tabId: this._tabId });
                        }
                        return;
                    }
                }
                
                try {
                    const stored = localStorage.getItem(this.LEADER_KEY);
                    if (stored) data = JSON.parse(stored);
                } catch (e) { /* 解析失败视为无数据 */ }
                
                const expired = !data.timestamp || (now - data.timestamp) > this.TIMEOUT;
                const iAmLeader = data.tabId === this._tabId;
                
                if (expired || iAmLeader) {
                    const wasLeader = this._isLeader;
                    this._isLeader = true;
                    try {
                        localStorage.setItem(this.LEADER_KEY, JSON.stringify({
                            tabId: this._tabId,
                            timestamp: now
                        }));
                    } catch (e) { /* 存储失败不影响逻辑 */ }
                    if (!wasLeader) {
                        this._notifyCallbacks(true);
                        EventBus.emit('leader:change', { isLeader: true, tabId: this._tabId });
                    }
                } else if (this._isLeader) {
                    this._isLeader = false;
                    this._notifyCallbacks(false);
                    EventBus.emit('leader:change', { isLeader: false, tabId: this._tabId });
                }
            },
            
            _release() {
                if (this._isLeader) {
                    try {
                        const stored = localStorage.getItem(this.LEADER_KEY);
                        if (stored) {
                            const data = JSON.parse(stored);
                            if (data.tabId === this._tabId) {
                                localStorage.removeItem(this.LEADER_KEY);
                            }
                        }
                    } catch (e) { /* 静默失败 */ }
                }
            },
            
            _notifyCallbacks(isLeader) {
                this._callbacks.forEach(cb => {
                    try { cb(isLeader); } catch (e) { /* 静默失败 */ }
                });
            },
            
            // 公开方法:检查是否是领导者
            isLeader() {
                return this._isLeader;
            },
            
            // 公开方法:获取当前标签页 ID
            getTabId() {
                return this._tabId;
            },
            
            // 公开方法:注册领导者状态变化回调
            onLeaderChange(callback) {
                if (typeof callback === 'function') {
                    this._callbacks.push(callback);
                }
            },
            
            // 公开方法:销毁
            destroy() {
                if (this._interval) {
                    clearInterval(this._interval);
                    this._interval = null;
                }
                if (this._storageHandler) {
                    window.removeEventListener('storage', this._storageHandler);
                }
                if (this._unloadHandler) {
                    window.removeEventListener('beforeunload', this._unloadHandler);
                }
                this._release();
                this._callbacks = [];
                this._initialized = false;
            }
        };

        // ==================== 常量配置 ====================
        const CONFIG = {
            // 时间间隔(毫秒)- 优化版:减少请求频率
            INTERVALS: {
                REFRESH: 300000,           // 数据刷新间隔
                READING_TRACK: 10000,      // 阅读追踪间隔
                READING_SAVE: 30000,       // 阅读保存间隔
                READING_IDLE: 60000,       // 空闲阈值
                STORAGE_DEBOUNCE: 1000,    // 存储防抖
                READING_UPDATE: 2000,      // 阅读时间UI更新(2秒,减少更新频率避免动画闪烁)
                LEADERBOARD_SYNC: 900000,  // 排行榜同步(15分钟,原10分钟)
                CLOUD_UPLOAD: 3600000,     // 云同步上传(60分钟,原30分钟)
                CLOUD_DOWNLOAD: 43200000,  // 云同步下载(12小时,原6小时)
                CLOUD_CHECK: 600000,       // 云同步检查(10分钟,原5分钟)
                REQ_SYNC_INCREMENTAL: 3600000, // 升级要求增量同步(1小时)
                REQ_SYNC_FULL: 43200000,   // 升级要求全量同步(12小时,与reading同步间隔一致)
                SYNC_RETRY_DELAY: 60000    // 同步失败后重试延迟(1分钟)
            },
            // 缓存配置
            CACHE: {
                MAX_HISTORY_DAYS: 365,
                LRU_SIZE: 50,
                VALUE_TTL: 5000,
                SCREEN_TTL: 100,
                YEAR_DATA_TTL: 5000,
                HISTORY_TTL: 1000,
                LEADERBOARD_DAILY_TTL: 600000,     // 日榜缓存 10 分钟(减少请求频率)
                LEADERBOARD_WEEKLY_TTL: 7200000,   // 周榜缓存 2 小时
                LEADERBOARD_MONTHLY_TTL: 21600000  // 月榜缓存 6 小时
            },
            // 网络配置
            NETWORK: {
                RETRY_COUNT: 3,
                RETRY_DELAY: 1000,
                TIMEOUT: 15000
            },
            // 里程碑配置
            MILESTONES: {
                '浏览话题': [100, 500, 1000, 2000, 5000],
                '已读帖子': [500, 1000, 5000, 10000, 20000],
                '获赞': [10, 50, 100, 500, 1000],
                '送出赞': [50, 100, 500, 1000, 2000],
                '回复': [10, 50, 100, 500, 1000]
            },
            // 趋势字段配置
            TREND_FIELDS: [
                { key: '浏览话题', search: '浏览的话题', label: '浏览话题' },
                { key: '已读帖子', search: '已读帖子', label: '已读帖子' },
                { key: '点赞', search: '送出赞', label: '点赞' },
                { key: '回复', search: '回复', label: '回复' },
                { key: '获赞', search: '获赞', label: '获赞' }
            ],
            // 阅读等级预设样式(图标、颜色、背景色固定,按索引匹配,共10级)
            READING_LEVEL_PRESETS: [
                { icon: '🌱', color: '#94a3b8', bg: 'rgba(148,163,184,0.15)' },  // 0: 灰色 - 刚起步
                { icon: '📖', color: '#60a5fa', bg: 'rgba(96,165,250,0.15)' },  // 1: 蓝色 - 热身中
                { icon: '📚', color: '#34d399', bg: 'rgba(52,211,153,0.15)' },  // 2: 绿色 - 渐入佳境
                { icon: '🔥', color: '#fbbf24', bg: 'rgba(251,191,36,0.15)' },  // 3: 黄色 - 沉浸阅读
                { icon: '⚡', color: '#f97316', bg: 'rgba(249,115,22,0.15)' },  // 4: 橙色 - 深度学习
                { icon: '🏆', color: '#a855f7', bg: 'rgba(168,85,247,0.15)' },  // 5: 紫色 - LD达人
                { icon: '👑', color: '#ec4899', bg: 'rgba(236,72,153,0.15)' },  // 6: 粉色 - 超级水怪
                { icon: '💎', color: '#06b6d4', bg: 'rgba(6,182,212,0.15)' },   // 7: 青色 - 钻石级
                { icon: '🌟', color: '#eab308', bg: 'rgba(234,179,8,0.15)' },   // 8: 金色 - 传奇级
                { icon: '🚀', color: '#ef4444', bg: 'rgba(239,68,68,0.15)' }    // 9: 红色 - 神话级
            ],
            // 阅读等级默认阈值和标签(与 PRESETS 索引对应)
            READING_LEVELS_DEFAULT: [
                { min: 0, label: '刚起步' },
                { min: 30, label: '热身中' },
                { min: 90, label: '渐入佳境' },
                { min: 180, label: '沉浸阅读' },
                { min: 300, label: '深度学习' },
                { min: 450, label: 'LD达人' },
                { min: 600, label: '超级水怪' }
            ],
            // 动态阅读等级配置(运行时从服务器加载)
            READING_LEVELS: null,
            // 阅读等级配置刷新间隔(24小时)
            READING_LEVELS_REFRESH: 24 * 60 * 60 * 1000,
            // 名称替换映射
            NAME_MAP: new Map([
                ['已读帖子(所有时间)', '已读帖子'],
                ['浏览的话题(所有时间)', '浏览话题'],
                ['获赞:点赞用户数量', '点赞用户'],
                ['获赞:单日最高数量', '获赞天数'],
                ['被禁言(过去 6 个月)', '禁言'],
                ['被封禁(过去 6 个月)', '封禁'],
                ['发帖数量', '发帖'],
                ['回复数量', '回复'],
                ['被举报的帖子(过去 6 个月)', '被举报帖子'],
                ['发起举报的用户(过去 6 个月)', '发起举报']
            ]),
            // 存储键
            STORAGE_KEYS: {
                position: 'position', collapsed: 'collapsed', theme: 'theme',
                trendTab: 'trend_tab', history: 'history', milestones: 'milestones',
                lastNotify: 'last_notify', lastVisit: 'last_visit', todayData: 'today_data',
                userAvatar: 'user_avatar', readingTime: 'reading_time', currentUser: 'current_user',
                lastCloudSync: 'last_cloud_sync', lastDownloadSync: 'last_download_sync',
                lastUploadHash: 'last_upload_hash', leaderboardToken: 'leaderboard_token',
                leaderboardUser: 'leaderboard_user', leaderboardJoined: 'leaderboard_joined',
                leaderboardTab: 'leaderboard_tab',
                readingLevels: 'reading_levels', readingLevelsTime: 'reading_levels_time',
                websiteUrl: 'website_url', websiteUrlDate: 'website_url_date'
            },
            // 用户特定的存储键
            USER_KEYS: new Set(['history', 'milestones', 'lastVisit', 'todayData', 'userAvatar', 'readingTime']),
            // 周和月名称
            WEEKDAYS: ['周日', '周一', '周二', '周三', '周四', '周五', '周六'],
            MONTHS: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
            // API地址(使用自定义域名,启用 Cloudflare 边缘防护)
            LEADERBOARD_API: 'https://api.ldspro.qzz.io'
        };

        // 预编译正则
        const PATTERNS = {
            REVERSE: /被举报|发起举报|禁言|封禁/,
            USERNAME: /\/u\/([^/]+)/,
            TRUST_LEVEL: /(.*) - 信任级别 (\d+)/,
            TRUST_LEVEL_H1: /你好,.*?\(([^)]+)\)\s*(\d+)级用户/,  // 匹配 h1 中的 "你好,XX (username) X级用户"
            VERSION: /@version\s+([\d.]+)/,
            AVATAR_SIZE: /\/\d+\//,
            NUMBER: /(\d+)/
        };

        // ==================== 调试与日志 ====================
        const Logger = {
            _enabled: false,  // 生产环境默认关闭详细日志
            _prefix: '[LDSP]',
            
            enable() { this._enabled = true; },
            disable() { this._enabled = false; },
            
            log(...args) {
                if (this._enabled) console.log(this._prefix, ...args);
            },
            warn(...args) {
                console.warn(this._prefix, ...args);
            },
            error(...args) {
                console.error(this._prefix, ...args);
            },
            // 带标签的日志(用于追踪特定模块)
            tag(tag) {
                return {
                    log: (...args) => this._enabled && console.log(`${this._prefix}[${tag}]`, ...args),
                    warn: (...args) => console.warn(`${this._prefix}[${tag}]`, ...args),
                    error: (...args) => console.error(`${this._prefix}[${tag}]`, ...args)
                };
            }
        };

        // ==================== 工具函数 ====================
        const Utils = {
            _nameCache: new Map(),
            _htmlEntities: { '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#x27;' },

            // HTML 转义(防止 XSS)
            escapeHtml(str) {
                if (!str || typeof str !== 'string') return '';
                return str.replace(/[&<>"']/g, c => this._htmlEntities[c]);
            },

            // 清理用户输入(移除控制字符,限制长度)
            sanitize(str, maxLen = 100) {
                if (!str || typeof str !== 'string') return '';
                return str.replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, '').substring(0, maxLen).trim();
            },
            
            // 安全的数值转换(防止 NaN 和 Infinity)
            toSafeNumber(val, defaultVal = 0) {
                const num = Number(val);
                return Number.isFinite(num) ? num : defaultVal;
            },
            
            // 安全的整数转换
            toSafeInt(val, defaultVal = 0) {
                const num = parseInt(val, 10);
                return Number.isFinite(num) ? num : defaultVal;
            },
            
            // 深度冻结对象(防止意外修改)
            deepFreeze(obj) {
                if (obj && typeof obj === 'object') {
                    Object.keys(obj).forEach(key => this.deepFreeze(obj[key]));
                    return Object.freeze(obj);
                }
                return obj;
            },

            // 版本比较
            compareVersion(v1, v2) {
                if (!v1 || !v2) return 0;
                const [p1, p2] = [v1, v2].map(v => String(v).split('.').map(n => this.toSafeInt(n)));
                const len = Math.max(p1.length, p2.length);
                for (let i = 0; i < len; i++) {
                    const diff = (p1[i] || 0) - (p2[i] || 0);
                    if (diff !== 0) return diff > 0 ? 1 : -1;
                }
                return 0;
            },

            // 颜色工具:hex -> rgb/rgba
            hexToRgb(hex) {
                if (!hex) return null;
                let h = String(hex).trim().replace('#', '');
                if (h.length === 3) h = h.split('').map(c => c + c).join('');
                if (!/^[0-9a-fA-F]{6}$/.test(h)) return null;
                const num = parseInt(h, 16);
                return { r: (num >> 16) & 255, g: (num >> 8) & 255, b: num & 255 };
            },
            hexToRgba(hex, alpha = 1) {
                const rgb = this.hexToRgb(hex);
                if (!rgb) return '';
                const a = Math.max(0, Math.min(1, Number(alpha)));
                return `rgba(${rgb.r},${rgb.g},${rgb.b},${a})`;
            },

            // 简化名称
            simplifyName(name) {
                if (this._nameCache.has(name)) return this._nameCache.get(name);
                let result = CONFIG.NAME_MAP.get(name);
                if (!result) {
                    for (const [from, to] of CONFIG.NAME_MAP) {
                        if (name.includes(from.split('(')[0])) {
                            result = name.replace(from, to);
                            break;
                        }
                    }
                }
                result = result || name;
                this._nameCache.set(name, result);
                return result;
            },

            // 格式化日期
            formatDate(ts, format = 'short') {
                const d = new Date(ts);
                const [m, day] = [d.getMonth() + 1, d.getDate()];
                if (format === 'short') return `${m}/${day}`;
                if (format === 'time') return `${d.getHours()}:${String(d.getMinutes()).padStart(2, '0')}`;
                return `${m}月${day}日`;
            },

            // 格式化相对时间(将UTC时间转为本地时间并显示为xx前)
            formatRelativeTime(utcStr) {
                if (!utcStr) return '';
                const d = new Date(utcStr); // 自动转换UTC到本地时区
                const now = new Date();
                const diff = (now - d) / 1000;
                if (diff < 60) return '刚刚';
                if (diff < 3600) return `${Math.floor(diff / 60)}分钟前`;
                if (diff < 86400) return `${Math.floor(diff / 3600)}小时前`;
                if (diff < 2592000) return `${Math.floor(diff / 86400)}天前`;
                if (diff < 31536000) return `${d.getMonth() + 1}月${d.getDate()}日`;
                return `${d.getFullYear()}年${d.getMonth() + 1}月`;
            },

            // 格式化完整日期时间(年月日时分)
            formatDateTime(utcStr) {
                if (!utcStr) return '';
                const d = new Date(utcStr);
                const year = d.getFullYear();
                const month = String(d.getMonth() + 1).padStart(2, '0');
                const day = String(d.getDate()).padStart(2, '0');
                const hour = String(d.getHours()).padStart(2, '0');
                const minute = String(d.getMinutes()).padStart(2, '0');
                return `${year}-${month}-${day} ${hour}:${minute}`;
            },

            // 获取今日键
            getTodayKey: () => new Date().toDateString(),

            // 格式化阅读时间
            formatReadingTime(minutes) {
                if (minutes < 1) return '< 1分钟';
                if (minutes < 60) return `${Math.round(minutes)}分钟`;
                const h = Math.floor(minutes / 60);
                const m = Math.round(minutes % 60);
                return m > 0 ? `${h}小时${m}分` : `${h}小时`;
            },

            // 获取阅读等级(合并服务端配置和预设样式)
            getReadingLevel(minutes) {
                const levels = CONFIG.READING_LEVELS || CONFIG.READING_LEVELS_DEFAULT;
                const presets = CONFIG.READING_LEVEL_PRESETS;
                
                for (let i = levels.length - 1; i >= 0; i--) {
                    if (minutes >= levels[i].min) {
                        const level = levels[i];
                        const preset = presets[i] || presets[presets.length - 1];
                        // 合并:使用服务端的 min/label,预设的 icon/color/bg
                        return {
                            min: level.min,
                            label: level.label,
                            icon: preset.icon,
                            color: preset.color,
                            bg: preset.bg
                        };
                    }
                }
                const first = levels[0];
                const preset = presets[0];
                return {
                    min: first.min,
                    label: first.label,
                    icon: preset.icon,
                    color: preset.color,
                    bg: preset.bg
                };
            },

            // 获取热力图等级
            getHeatmapLevel(minutes) {
                if (minutes < 1) return 0;
                if (minutes < 60) return 1;
                if (minutes < 180) return 2;
                if (minutes < 300) return 3;
                return 4;
            },

            // 重排需求项(将举报相关项移到禁言前)
            reorderRequirements(reqs) {
                const reports = [], others = [];
                reqs.forEach(r => {
                    (r.name.includes('被举报') || r.name.includes('发起举报') ? reports : others).push(r);
                });
                const banIdx = others.findIndex(r => r.name.includes('禁言'));
                if (banIdx >= 0) others.splice(banIdx, 0, ...reports);
                else others.push(...reports);
                return others;
            },

            // 防抖(带取消功能)
            debounce(fn, wait) {
                let timer = null;
                const debounced = function(...args) {
                    if (timer !== null) clearTimeout(timer);
                    timer = setTimeout(() => {
                        timer = null;
                        fn.apply(this, args);
                    }, wait);
                };
                debounced.cancel = () => {
                    if (timer !== null) {
                        clearTimeout(timer);
                        timer = null;
                    }
                };
                return debounced;
            },

            // 节流(保证首次立即执行,后续按间隔执行)
            throttle(fn, limit) {
                let lastTime = 0;
                return function(...args) {
                    const now = Date.now();
                    if (now - lastTime >= limit) {
                        lastTime = now;
                        fn.apply(this, args);
                    }
                };
            },

            // 安全执行(捕获异常)
            safeCall(fn, fallback = null) {
                try {
                    return fn();
                } catch (e) {
                    return fallback;
                }
            },
            
            // 安全的异步执行(捕获 Promise 异常)
            async safeAsync(fn, fallback = null) {
                try {
                    return await fn();
                } catch (e) {
                    Logger.warn('Async operation failed:', e.message);
                    return fallback;
                }
            },
            
            // 生成唯一 ID
            uid() {
                return Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
            },
            
            // 检查是否为有效的 URL
            isValidUrl(str) {
                if (!str || typeof str !== 'string') return false;
                try {
                    const url = new URL(str);
                    return url.protocol === 'http:' || url.protocol === 'https:';
                } catch {
                    return false;
                }
            },
            
            // 安全获取嵌套对象属性
            get(obj, path, defaultVal = undefined) {
                if (!obj || typeof path !== 'string') return defaultVal;
                const keys = path.split('.');
                let result = obj;
                for (const key of keys) {
                    if (result == null || typeof result !== 'object') return defaultVal;
                    result = result[key];
                }
                return result !== undefined ? result : defaultVal;
            },
            
            // 克隆对象(浅拷贝,用于配置等)
            clone(obj) {
                if (!obj || typeof obj !== 'object') return obj;
                return Array.isArray(obj) ? [...obj] : { ...obj };
            }
        };

        // ==================== 屏幕工具 ====================
        const Screen = {
            _cache: null,
            _cacheTime: 0,
            _cacheVw: 0,
            _cacheVh: 0,

            // 动态计算面板配置 - 基于视口尺寸的相对计算
            // 确保面板始终在窗口内显示,针对移动设备优化
            getConfig() {
                const now = Date.now();
                const { innerWidth: vw, innerHeight: vh } = window;
                if (this._cache && this._cacheVw === vw && this._cacheVh === vh &&
                    (now - this._cacheTime) < CONFIG.CACHE.SCREEN_TTL) {
                    return this._cache;
                }
                const isMobile = vw < 500 || vh < 700;
                const isVerySmall = vw < 360 || vh < 500;
                
                // 面板宽度:移动设备使用更小的基础宽度
                // 桌面: 视口宽度的18%,限制在260-360px
                // 移动: 视口宽度的65%,限制在220-280px
                let width;
                if (isVerySmall) {
                    width = Math.max(200, Math.min(240, Math.round(vw * 0.7)));
                } else if (isMobile) {
                    width = Math.max(220, Math.min(280, Math.round(vw * 0.65)));
                } else {
                    width = Math.max(260, Math.min(360, Math.round(vw * 0.18)));
                }
                
                // 面板最大高度:视口高度减去边距
                // 移动设备预留更小的边距以获得更多显示空间
                const topMargin = isMobile 
                    ? Math.max(15, Math.round(vh * 0.03))
                    : Math.max(30, Math.round(vh * 0.06));
                const bottomMargin = isMobile ? 10 : 20;
                const maxHeight = vh - topMargin - bottomMargin;
                
                // 字体大小:移动设备使用较小字号
                // 桌面: 10-13px,移动: 9-11px
                const fontSize = isMobile
                    ? Math.max(9, Math.min(11, Math.round(vh / 70)))
                    : Math.max(10, Math.min(13, Math.round(vh / 70)));
                
                // 内边距:根据宽度缩放
                // 桌面: 10-14px,移动: 6-10px
                const padding = isMobile
                    ? Math.max(6, Math.min(10, Math.round(width / 28)))
                    : Math.max(10, Math.min(14, Math.round(width / 24)));
                
                // 头像大小:移动设备使用较小头像
                // 桌面: 40-52px,移动: 32-42px
                const avatarSize = isMobile
                    ? Math.max(32, Math.min(42, Math.round(width / 7)))
                    : Math.max(40, Math.min(52, Math.round(width / 6.5)));
                
                // 环形图大小:根据高度缩放
                // 桌面: 65-85px,移动: 50-65px
                const ringSize = isMobile
                    ? Math.max(50, Math.min(65, Math.round(vh / 12)))
                    : Math.max(65, Math.min(85, Math.round(vh / 11)));
                
                const config = {
                    width,
                    maxHeight,
                    fontSize,
                    padding,
                    avatarSize,
                    ringSize,
                    top: topMargin,
                    vw,
                    vh,
                    isMobile,
                    isVerySmall
                };
                this._cache = config;
                this._cacheTime = now;
                this._cacheVw = vw;
                this._cacheVh = vh;
                return config;
            }
        };

        // ==================== LRU 缓存 ====================
        class LRUCache {
            constructor(maxSize = CONFIG.CACHE.LRU_SIZE) {
                this.maxSize = maxSize;
                this.cache = new Map();
            }

            get(key) {
                if (!this.cache.has(key)) return undefined;
                const value = this.cache.get(key);
                this.cache.delete(key);
                this.cache.set(key, value);
                return value;
            }

            set(key, value) {
                this.cache.has(key) && this.cache.delete(key);
                if (this.cache.size >= this.maxSize) {
                    this.cache.delete(this.cache.keys().next().value);
                }
                this.cache.set(key, value);
            }

            has(key) { return this.cache.has(key); }
            clear() { this.cache.clear(); }
        }

        // ==================== 存储管理器 ====================
        class Storage {
            constructor() {
                this._pending = new Map();
                this._timer = null;
                this._user = null;
                this._keyCache = new Map();
                this._valueCache = new Map();
                this._valueCacheTime = new Map();
            }

            // 获取当前用户
            getUser() {
                if (this._user) return this._user;
                const link = document.querySelector('.current-user a[href^="/u/"]');
                if (link) {
                    const match = link.getAttribute('href').match(PATTERNS.USERNAME);
                    if (match) {
                        this._user = match[1];
                        GM_setValue(this._globalKey('currentUser'), this._user);
                        return this._user;
                    }
                }
                return this._user = GM_getValue(this._globalKey('currentUser'), null);
            }

            setUser(username) {
                if (this._user !== username) {
                    this._user = username;
                    this._keyCache.clear();  // 用户变化时清除 key 缓存
                    GM_setValue(this._globalKey('currentUser'), username);
                }
            }

            // 生成全局键
            _globalKey(key) {
                return `ldsp_${CURRENT_SITE.prefix}_${CONFIG.STORAGE_KEYS[key] || key}`;
            }

            // 生成用户键
            _userKey(key) {
                const cacheKey = `${key}_${this._user || ''}`;
                if (this._keyCache.has(cacheKey)) return this._keyCache.get(cacheKey);
                
                const base = CONFIG.STORAGE_KEYS[key] || key;
                const user = this.getUser();
                const result = user && CONFIG.USER_KEYS.has(key) 
                    ? `ldsp_${CURRENT_SITE.prefix}_${base}_${user}`
                    : `ldsp_${CURRENT_SITE.prefix}_${base}`;
                
                this._keyCache.set(cacheKey, result);
                return result;
            }

            // 获取用户数据
            get(key, defaultValue = null) {
                const storageKey = this._userKey(key);
                const now = Date.now();
                
                if (this._valueCache.has(storageKey)) {
                    const cacheTime = this._valueCacheTime.get(storageKey);
                    if ((now - cacheTime) < CONFIG.CACHE.VALUE_TTL) {
                        return this._valueCache.get(storageKey);
                    }
                }
                
                const value = GM_getValue(storageKey, defaultValue);
                this._valueCache.set(storageKey, value);
                this._valueCacheTime.set(storageKey, now);
                return value;
            }

            // 设置用户数据(带防抖)
            set(key, value) {
                const storageKey = this._userKey(key);
                this._valueCache.set(storageKey, value);
                this._valueCacheTime.set(storageKey, Date.now());
                this._pending.set(storageKey, value);
                this._scheduleWrite();
            }

            // 立即设置用户数据
            setNow(key, value) {
                const storageKey = this._userKey(key);
                this._valueCache.set(storageKey, value);
                this._valueCacheTime.set(storageKey, Date.now());
                GM_setValue(storageKey, value);
            }

            // 获取全局数据
            getGlobal(key, defaultValue = null) {
                return GM_getValue(this._globalKey(key), defaultValue);
            }

            // 设置全局数据(带防抖)
            setGlobal(key, value) {
                this._pending.set(this._globalKey(key), value);
                this._scheduleWrite();
            }

            // 立即设置全局数据
            setGlobalNow(key, value) {
                GM_setValue(this._globalKey(key), value);
            }

            // 调度写入
            _scheduleWrite() {
                if (this._timer) return;
                this._timer = setTimeout(() => {
                    this.flush();
                    this._timer = null;
                }, CONFIG.INTERVALS.STORAGE_DEBOUNCE);
            }

            // 刷新所有待写入数据
            flush() {
                this._pending.forEach((value, key) => {
                    try { GM_setValue(key, value); } catch (e) { console.error('[Storage]', key, e); }
                });
                this._pending.clear();
            }

            // 清除缓存
            invalidateCache(key) {
                if (key) {
                    const storageKey = this._userKey(key);
                    this._valueCache.delete(storageKey);
                    this._valueCacheTime.delete(storageKey);
                } else {
                    this._valueCache.clear();
                    this._valueCacheTime.clear();
                }
            }

            // 迁移旧数据
            migrate(username) {
                const flag = `ldsp_migrated_v3_${username}`;
                if (GM_getValue(flag, false)) return;

                CONFIG.USER_KEYS.forEach(key => {
                    const oldKey = CONFIG.STORAGE_KEYS[key];
                    const newKey = `ldsp_${CURRENT_SITE.prefix}_${oldKey}_${username}`;
                    const oldData = GM_getValue(oldKey, null);
                    if (oldData !== null && GM_getValue(newKey, null) === null) {
                        GM_setValue(newKey, oldData);
                    }
                });

                this._migrateReadingTime(username);
                GM_setValue(flag, true);
            }

            // 迁移阅读时间数据
            _migrateReadingTime(username) {
                const key = `ldsp_${CURRENT_SITE.prefix}_reading_time_${username}`;
                const data = GM_getValue(key, null);
                if (!data || typeof data !== 'object') return;

                if (data.date && data.minutes !== undefined && !data.dailyData) {
                    GM_setValue(key, {
                        version: 3,
                        dailyData: { [data.date]: { totalMinutes: data.minutes || 0, lastActive: data.lastActive || Date.now(), sessions: [] } },
                        monthlyCache: {},
                        yearlyCache: {}
                    });
                } else if (data.version === 2) {
                    data.version = 3;
                    data.monthlyCache = data.monthlyCache || {};
                    data.yearlyCache = data.yearlyCache || {};
                    if (data.dailyData) {
                        Object.entries(data.dailyData).forEach(([dateKey, dayData]) => {
                            try {
                                const d = new Date(dateKey);
                                const monthKey = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
                                const yearKey = `${d.getFullYear()}`;
                                const minutes = dayData.totalMinutes || 0;
                                data.monthlyCache[monthKey] = (data.monthlyCache[monthKey] || 0) + minutes;
                                data.yearlyCache[yearKey] = (data.yearlyCache[yearKey] || 0) + minutes;
                            } catch (e) {}
                        });
                    }
                    GM_setValue(key, data);
                }
            }
        }

        // ==================== 自定义错误类型 ====================
        class NetworkError extends Error {
            constructor(message, code = 'NETWORK_ERROR', status = 0) {
                super(message);
                this.name = 'NetworkError';
                this.code = code;
                this.status = status;
            }
            
            get isTimeout() { return this.code === 'TIMEOUT'; }
            get isAuth() { return this.code === 'UNAUTHORIZED' || this.status === 401; }
            get isNotFound() { return this.status === 404; }
            get isServerError() { return this.status >= 500; }
        }
        
        // ==================== 错误消息格式化工具 ====================
        // v3.5.2.9: 统一的用户友好错误消息
        const ErrorFormatter = {
            // 错误码到友好消息的映射
            _codeMap: {
                'NETWORK_ERROR': '网络连接失败,请检查网络',
                'TIMEOUT': '请求超时,请稍后重试',
                'IP_BANNED': 'IP 已被封禁,请更换网络节点',
                'IP_PERMANENTLY_BANNED': 'IP 已被永久封禁,请联系管理员',
                'RATE_LIMITED': '请求过于频繁,请稍后重试',
                'NOT_LOGGED_IN': '请先登录',
                'AUTH_EXPIRED': '登录已过期,请重新登录',
                'INVALID_TOKEN': '登录已失效,请重新登录',
                'TOKEN_EXPIRED': '登录已过期,请重新登录',
                'NOT_JOINED': '请先加入排行榜',
                'REGISTRATION_PAUSED': '已暂停新用户注册',
                'UNAUTHORIZED': '没有访问权限',
                'NOT_FOUND': '请求的资源不存在',
                'SERVER_ERROR': '服务器暂时不可用,请稍后重试',
                'VALIDATION_FAILED': '数据验证失败',
                'SYNC_FAILED': '同步失败,请稍后重试'
            },
            
            // 将错误对象转换为用户友好的消息
            format(error) {
                if (!error) return '未知错误';
                
                // 字符串直接返回
                if (typeof error === 'string') return this._simplify(error);
                
                // 提取错误信息
                const msg = error.message || '';
                const code = error.code || '';
                const status = error.status || 0;
                
                // 1. 优先匹配错误码
                if (code && this._codeMap[code]) {
                    return this._codeMap[code];
                }
                
                // 2. 检查消息中的关键词
                const lowerMsg = msg.toLowerCase();
                
                // 网络相关
                if (lowerMsg.includes('network') || lowerMsg.includes('网络')) {
                    return '网络连接失败,请检查网络';
                }
                if (lowerMsg.includes('timeout') || lowerMsg.includes('超时')) {
                    return '请求超时,请稍后重试';
                }
                
                // 认证相关
                if (lowerMsg.includes('登录') || lowerMsg.includes('login') || 
                    lowerMsg.includes('token') || lowerMsg.includes('auth')) {
                    if (lowerMsg.includes('过期') || lowerMsg.includes('expire') || lowerMsg.includes('失效')) {
                        return '登录已过期,请重新登录';
                    }
                    return '请先登录';
                }
                
                // IP 封禁
                if (lowerMsg.includes('ip') && (lowerMsg.includes('ban') || lowerMsg.includes('封禁') || lowerMsg.includes('block'))) {
                    return 'IP 已被封禁,请更换网络节点';
                }
                
                // 频率限制
                if (lowerMsg.includes('频繁') || lowerMsg.includes('rate') || lowerMsg.includes('429')) {
                    // 尝试提取等待时间
                    const waitMatch = msg.match(/(\d+)\s*(秒|分钟|second|minute)/i);
                    if (waitMatch) {
                        return `请求过于频繁,请等待 ${waitMatch[1]} ${waitMatch[2]}后重试`;
                    }
                    return '请求过于频繁,请稍后重试';
                }
                
                // 服务器错误
                if (status >= 500 || lowerMsg.includes('server') || lowerMsg.includes('服务器')) {
                    return '服务器暂时不可用,请稍后重试';
                }
                
                // 3. 简化并返回原始消息
                return this._simplify(msg) || '操作失败,请稍后重试';
            },
            
            // 简化错误消息(移除技术细节)
            _simplify(msg) {
                if (!msg) return '';
                
                // 移除 HTTP 状态码细节
                msg = msg.replace(/\s*\(HTTP \d+\)|\s*HTTP \d+:?/gi, '');
                // 移除错误码前缀
                msg = msg.replace(/^[A-Z_]+:\s*/i, '');
                // 移除 "Error:" 前缀
                msg = msg.replace(/^Error:\s*/i, '');
                // 限制长度
                if (msg.length > 50) msg = msg.substring(0, 47) + '...';
                
                return msg.trim();
            },
            
            // 获取带图标的错误消息
            withIcon(error) {
                const msg = this.format(error);
                // 根据错误类型选择图标
                if (msg.includes('网络') || msg.includes('连接')) return `📡 ${msg}`;
                if (msg.includes('登录') || msg.includes('权限')) return `🔐 ${msg}`;
                if (msg.includes('封禁')) return `🚫 ${msg}`;
                if (msg.includes('频繁') || msg.includes('等待')) return `⏳ ${msg}`;
                if (msg.includes('服务器')) return `🔧 ${msg}`;
                if (msg.includes('超时')) return `⏱️ ${msg}`;
                return `❌ ${msg}`;
            }
        };

        // ==================== 网络管理器 ====================
        class Network {
            // 公共接口429限流冷却时间(2分钟)
            static RATE_LIMIT_COOLDOWN = 2 * 60 * 1000;
            // 429限流记录(记录上次收到429的时间戳)
            static _rateLimitedAt = 0;
            // v3.5.2.9: 全局请求队列 - 防止并发请求导致429
            static _requestQueue = [];
            static _isProcessingQueue = false;
            static _lastRequestTime = 0;
            static MIN_REQUEST_INTERVAL = 300; // 最小请求间隔 300ms
            
            constructor() {
                this._pending = new Map();
                this._apiCache = new Map();
                this._apiCacheTime = new Map();
            }
            
            // v3.5.2.9: 将请求加入队列,确保请求间隔
            static async queueRequest(requestFn) {
                return new Promise((resolve, reject) => {
                    Network._requestQueue.push({ requestFn, resolve, reject });
                    Network._processQueue();
                });
            }
            
            // v3.5.2.9: 处理请求队列
            static async _processQueue() {
                if (Network._isProcessingQueue || Network._requestQueue.length === 0) return;
                Network._isProcessingQueue = true;
                
                while (Network._requestQueue.length > 0) {
                    const { requestFn, resolve, reject } = Network._requestQueue.shift();
                    
                    // 确保请求间隔
                    const now = Date.now();
                    const elapsed = now - Network._lastRequestTime;
                    if (elapsed < Network.MIN_REQUEST_INTERVAL) {
                        await new Promise(r => setTimeout(r, Network.MIN_REQUEST_INTERVAL - elapsed));
                    }
                    
                    Network._lastRequestTime = Date.now();
                    
                    try {
                        const result = await requestFn();
                        resolve(result);
                    } catch (e) {
                        reject(e);
                    }
                }
                
                Network._isProcessingQueue = false;
            }
            
            // 检查是否处于429限流冷却期
            static isRateLimited() {
                if (!Network._rateLimitedAt) return false;
                const elapsed = Date.now() - Network._rateLimitedAt;
                if (elapsed >= Network.RATE_LIMIT_COOLDOWN) {
                    Network._rateLimitedAt = 0; // 冷却期结束,清除记录
                    return false;
                }
                return true;
            }
            
            // 获取剩余冷却时间(秒)
            static getRateLimitRemaining() {
                if (!Network._rateLimitedAt) return 0;
                const remaining = Network.RATE_LIMIT_COOLDOWN - (Date.now() - Network._rateLimitedAt);
                return remaining > 0 ? Math.ceil(remaining / 1000) : 0;
            }
            
            // 记录收到429响应
            static recordRateLimit() {
                Network._rateLimitedAt = Date.now();
                Logger.warn('API rate limited, cooling down for 2 minutes');
            }
            
            // 创建统一的错误对象
            static createError(message, code = 'UNKNOWN', status = 0) {
                return new NetworkError(message, code, status);
            }

            // 静态方法:加载阅读等级配置(从服务端获取,本地缓存24小时)
            static async loadReadingLevels() {
                const storageKey = `ldsp_reading_levels`;
                const timeKey = `ldsp_reading_levels_time`;
                
                try {
                    // 检查本地缓存是否过期(24小时刷新一次)
                    const cachedTime = GM_getValue(timeKey, 0);
                    const now = Date.now();
                    
                    if (cachedTime && (now - cachedTime) < CONFIG.READING_LEVELS_REFRESH) {
                        // 缓存未过期,使用本地数据
                        const cached = GM_getValue(storageKey, null);
                        if (cached && Array.isArray(cached) && cached.length > 0) {
                            CONFIG.READING_LEVELS = cached;
                            return;
                        }
                    }
                    
                    // 需要从服务端获取
                    const response = await new Promise((resolve, reject) => {
                        GM_xmlhttpRequest({
                            method: 'GET',
                            url: `${CONFIG.LEADERBOARD_API}/api/config/reading-levels`,
                            headers: { 'Content-Type': 'application/json' },
                            timeout: 10000,
                            onload: res => {
                                if (res.status >= 200 && res.status < 300) {
                                    try {
                                        resolve(JSON.parse(res.responseText));
                                    } catch (e) {
                                        reject(new Error('Parse error'));
                                    }
                                } else {
                                    reject(new Error(`HTTP ${res.status}`));
                                }
                            },
                            onerror: () => reject(new Error('Network error')),
                            ontimeout: () => reject(new Error('Timeout'))
                        });
                    });
                    
                    if (response.success && response.data?.levels && Array.isArray(response.data.levels)) {
                        const levels = response.data.levels;
                        CONFIG.READING_LEVELS = levels;
                        GM_setValue(storageKey, levels);
                        GM_setValue(timeKey, now);
                    } else {
                        throw new Error('Invalid response format');
                    }
                } catch (e) {
                    // 尝试使用本地缓存(即使过期也比没有好)
                    const cached = GM_getValue(storageKey, null);
                    if (cached && Array.isArray(cached) && cached.length > 0) {
                        CONFIG.READING_LEVELS = cached;
                    } else {
                        // 使用默认配置
                        CONFIG.READING_LEVELS = CONFIG.READING_LEVELS_DEFAULT;
                    }
                }
            }

            async fetch(url, options = {}) {
                const key = this._buildPendingKey(url, options);
                if (this._pending.has(key)) return this._pending.get(key);
                
                const promise = this._fetchWithRetry(url, options);
                this._pending.set(key, promise);
                
                try {
                    return await promise;
                } finally {
                    this._pending.delete(key);
                }
            }

            _buildPendingKey(url, options = {}) {
                const method = (options.method || 'GET').toUpperCase();
                // 归一化 headers,避免对象引用导致的重复命中
                let headersSig = '';
                const headers = options.headers;
                if (headers) {
                    const normalized = {};
                    if (headers instanceof Headers) {
                        headers.forEach((v, k) => { normalized[k] = v; });
                    } else if (typeof headers === 'object') {
                        Object.entries(headers).forEach(([k, v]) => { normalized[k] = v; });
                    }
                    headersSig = JSON.stringify(Object.keys(normalized).sort().reduce((acc, k) => { acc[k] = normalized[k]; return acc; }, {}));
                }
                let bodySig = '';
                const body = options.body;
                if (body) {
                    if (typeof body === 'string') bodySig = body.slice(0, 128);
                    else if (typeof body === 'object') { try { bodySig = JSON.stringify(body).slice(0, 128); } catch {} }
                }
                return `${method}:${url}#${headersSig}:${bodySig}`;
            }

            // 清除 API 缓存
            clearApiCache(endpoint) {
                if (endpoint) {
                    this._apiCache.delete(endpoint);
                    this._apiCacheTime.delete(endpoint);
                } else {
                    this._apiCache.clear();
                    this._apiCacheTime.clear();
                }
            }

            async _fetchWithRetry(url, options = {}) {
                const { maxRetries = CONFIG.NETWORK.RETRY_COUNT, timeout = CONFIG.NETWORK.TIMEOUT, headers = {}, method = 'GET', body = undefined } = options;
                const finalHeaders = this._buildHeaders(url, { 'Accept': 'application/json, text/html, */*', ...headers });
                
                for (let i = 0; i < maxRetries; i++) {
                    try {
                        return await this._doFetch(url, { timeout, headers: finalHeaders, method, body });
                    } catch (e) {
                        if (i === maxRetries - 1) throw e;
                        await new Promise(r => setTimeout(r, CONFIG.NETWORK.RETRY_DELAY * Math.pow(2, i)));
                    }
                }
            }

            async _doFetch(url, { timeout, headers = {}, method = 'GET', body = undefined }) {
                // 检测是否为同源请求
                const isSameOrigin = this._isSameOrigin(url);
                
                // 同源请求优先使用原生 fetch(可以正确携带 cookie)
                // v3.5.4.9: 修复0/1级用户获取升级要求数据时需要携带cookie的问题
                if (isSameOrigin) {
                    try {
                        const controller = new AbortController();
                        const timeoutId = setTimeout(() => controller.abort(), timeout);
                        const resp = await fetch(url, { 
                            credentials: 'include',
                            method,
                            body,
                            headers,
                            signal: controller.signal
                        });
                        clearTimeout(timeoutId);
                        if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
                        return await resp.text();
                    } catch (e) {
                        if (e.name === 'AbortError') throw new Error('Timeout');
                        // 原生 fetch 失败,fallback 到 GM(可能是扩展环境问题)
                    }
                }
                
                // 检测 GM_xmlhttpRequest 是否可用
                const hasGM = typeof GM_xmlhttpRequest === 'function';
                
                // GM_xmlhttpRequest(可绕过跨域,并携带 cookie)
                if (hasGM) {
                    const result = await new Promise((resolve, reject) => {
                        let settled = false;
                        const timeoutId = setTimeout(() => {
                            if (!settled) {
                                settled = true;
                                reject(new Error('Timeout'));
                            }
                        }, timeout);
                        
                        try {
                            GM_xmlhttpRequest({
                                method,
                                url,
                                timeout,
                                // v3.5.4.9: 添加 withCredentials 携带 cookie
                                withCredentials: true,
                                headers,
                                data: body && typeof body === 'object' && !(body instanceof FormData) ? JSON.stringify(body) : body,
                                onload: res => {
                                    if (settled) return;
                                    settled = true;
                                    clearTimeout(timeoutId);
                                    if (res.status >= 200 && res.status < 300) {
                                        resolve(res.responseText);
                                    } else {
                                        reject(new Error(`HTTP ${res.status}`));
                                    }
                                },
                                onerror: () => {
                                    if (settled) return;
                                    settled = true;
                                    clearTimeout(timeoutId);
                                    reject(new Error('Network error'));
                                },
                                ontimeout: () => {
                                    if (settled) return;
                                    settled = true;
                                    clearTimeout(timeoutId);
                                    reject(new Error('GM Timeout'));
                                }
                            });
                        } catch (gmCallError) {
                            if (settled) return;
                            settled = true;
                            clearTimeout(timeoutId);
                            reject(gmCallError);
                        }
                    });
                    return result;
                }
                
                // 没有 GM 且非同源请求,尝试原生 fetch(可能会失败)
                const controller = new AbortController();
                const timeoutId = setTimeout(() => controller.abort(), timeout);
                const resp = await fetch(url, { 
                    credentials: 'include',
                    method,
                    body,
                    headers,
                    signal: controller.signal
                });
                clearTimeout(timeoutId);
                if (!resp.ok) throw new Error(`HTTP ${resp.status}`);
                return await resp.text();
            }

            _buildHeaders(url, headers = {}) {
                const merged = { ...headers };
                if (!merged['Accept'] && !merged['accept']) merged['Accept'] = 'application/json, text/html, */*';
                return buildAuthHeaders(url, merged);
            }

            // API 请求(带认证和缓存)
            async api(endpoint, options = {}) {
                const method = options.method || 'GET';
                const cacheTtl = options.cacheTtl || 0;
                
                // 检查是否处于429限流冷却期(公共接口保护)
                if (Network.isRateLimited()) {
                    const remaining = Network.getRateLimitRemaining();
                    throw new Error(`请求过于频繁,请等待 ${remaining} 秒后重试`);
                }
                
                // GET 请求支持缓存
                if (method === 'GET' && cacheTtl > 0) {
                    const now = Date.now();
                    const cacheKey = `${endpoint}_${options.token || ''}`;
                    if (this._apiCache.has(cacheKey)) {
                        const cacheTime = this._apiCacheTime.get(cacheKey);
                        if (now - cacheTime < cacheTtl) {
                            return this._apiCache.get(cacheKey);
                        }
                    }
                }

                // v3.5.2.9: 使用全局请求队列,防止并发请求导致429
                return Network.queueRequest(() => this._doApiRequest(endpoint, options, method, cacheTtl));
            }
            
            // v3.5.2.9: 实际执行 API 请求
            _doApiRequest(endpoint, options, method, cacheTtl) {
                return new Promise((resolve, reject) => {
                    // 确保 body 是字符串
                    let bodyData = options.body;
                    if (bodyData && typeof bodyData === 'object') {
                        bodyData = JSON.stringify(bodyData);
                    }
                    
                    GM_xmlhttpRequest({
                        method,
                        url: `${CONFIG.LEADERBOARD_API}${endpoint}`,
                        headers: {
                            'Content-Type': 'application/json',
                            'X-Client-Version': GM_info.script.version || 'unknown',
                            ...(options.token ? { 'Authorization': `Bearer ${options.token}` } : {})
                        },
                        data: bodyData || undefined,
                        timeout: CONFIG.NETWORK.TIMEOUT,
                        onload: res => {
                            try {
                                const data = JSON.parse(res.responseText);
                                if (res.status >= 200 && res.status < 300) {
                                    // 缓存成功响应
                                    if (method === 'GET' && cacheTtl > 0) {
                                        const cacheKey = `${endpoint}_${options.token || ''}`;
                                        this._apiCache.set(cacheKey, data);
                                        this._apiCacheTime.set(cacheKey, Date.now());
                                    }
                                    resolve(data);
                                } else {
                                    // 检测特殊错误码并提供友好提示
                                    const errorCode = data.error?.code || '';
                                    
                                    // 429 限速:记录并触发冷却
                                    if (res.status === 429) {
                                        Network.recordRateLimit();
                                        const retryAfter = data.error?.retryAfter || 120;
                                        reject(new Error(`请求过于频繁,请等待 ${retryAfter} 秒后重试`));
                                        return;
                                    }
                                    
                                    // IP 被封禁:提供友好提示
                                    if (errorCode === 'IP_BANNED' || errorCode === 'IP_PERMANENTLY_BANNED') {
                                        const isPermanent = errorCode === 'IP_PERMANENTLY_BANNED';
                                        const banMsg = isPermanent 
                                            ? 'IP 已被永久封禁,请更换网络节点或联系管理员'
                                            : `IP 已被临时封禁,请稍后重试或更换网络节点`;
                                        reject(new Error(banMsg));
                                        return;
                                    }
                                    
                                    // 其他错误:构建错误消息
                                    const errorMsg = data.error?.message || data.error || `HTTP ${res.status}`;
                                    const err = new Error(errorCode ? `${errorCode}: ${errorMsg}` : errorMsg);
                                    if (errorCode) err.code = errorCode;
                                    err.status = res.status;
                                    reject(err);
                                }
                            } catch (e) {
                                // JSON 解析失败,可能是服务器返回非 JSON 响应
                                // 检查常见的 HTTP 状态码
                                if (res.status === 429) {
                                    Network.recordRateLimit();
                                    reject(new Error('请求过于频繁,请等待 2 分钟后重试'));
                                } else if (res.status === 403) {
                                    reject(new Error('IP 可能被封禁,请更换网络节点或联系管理员'));
                                } else if (res.status >= 500) {
                                    reject(new Error('服务器暂时不可用,请稍后重试'));
                                } else {
                                    reject(new Error(`请求失败 (${res.status})`));
                                }
                            }
                        },
                        onerror: () => reject(new Error('网络错误,请检查网络连接')),
                        ontimeout: () => reject(new Error('请求超时,请稍后重试'))
                    });
                });
            }

            // 获取 JSON 数据(带 cookie 同源请求)
            // iOS Safari 中 GM_xmlhttpRequest 的 withCredentials 可能无法正确传递 cookie
            // 因此对同源请求优先使用原生 fetch,可以正确携带 cookie
            async fetchJson(url, options = {}) {
                const timeout = options.timeout || CONFIG.NETWORK.TIMEOUT;
                const headers = this._buildHeaders(url, { 'Accept': 'application/json', ...(options.headers || {}) });
                
                // 检查是否为同源请求
                const isSameOrigin = this._isSameOrigin(url);
                
                // 同源请求优先使用原生 fetch(iOS Safari 兼容性更好)
                if (isSameOrigin) {
                    return this._fetchJsonNative(url, timeout, headers);
                }
                
                // 跨域请求使用 GM_xmlhttpRequest
                return this._fetchJsonGM(url, timeout, headers);
            }
            
            // 检查 URL 是否与当前页面同源
            _isSameOrigin(url) {
                try {
                    const urlObj = new URL(url, location.href);
                    return urlObj.origin === location.origin;
                } catch {
                    return false;
                }
            }
            
            // 使用原生 fetch 获取 JSON(同源请求,更好的 cookie 支持)
            async _fetchJsonNative(url, timeout, headers = {}) {
                const controller = new AbortController();
                const timeoutId = setTimeout(() => controller.abort(), timeout);
                
                try {
                    const resp = await fetch(url, {
                        method: 'GET',
                        headers,
                        credentials: 'include',
                        signal: controller.signal
                    });
                    
                    clearTimeout(timeoutId);
                    
                    if (resp.status >= 200 && resp.status < 300) {
                        return await resp.json();
                    } else if (resp.status === 403 || resp.status === 401) {
                        throw new Error('需要登录后访问');
                    } else {
                        throw new Error(`HTTP ${resp.status}`);
                    }
                } catch (e) {
                    clearTimeout(timeoutId);
                    if (e.name === 'AbortError') {
                        throw new Error('请求超时');
                    }
                    throw e;
                }
            }
            
            // 使用 GM_xmlhttpRequest 获取 JSON(跨域请求)
            _fetchJsonGM(url, timeout, headers = {}) {
                return new Promise((resolve, reject) => {
                    const timeoutId = setTimeout(() => reject(new Error('Timeout')), timeout);
                    
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url,
                        headers,
                        timeout,
                        withCredentials: true,
                        onload: res => {
                            clearTimeout(timeoutId);
                            try {
                                if (res.status >= 200 && res.status < 300) {
                                    resolve(JSON.parse(res.responseText));
                                } else if (res.status === 403 || res.status === 401) {
                                    reject(new Error('需要登录后访问'));
                                } else {
                                    reject(new Error(`HTTP ${res.status}`));
                                }
                            } catch (e) {
                                reject(new Error('解析响应失败'));
                            }
                        },
                        onerror: () => {
                            clearTimeout(timeoutId);
                            reject(new Error('网络错误'));
                        },
                        ontimeout: () => {
                            clearTimeout(timeoutId);
                            reject(new Error('请求超时'));
                        }
                    });
                });
            }
        }

        // ==================== 历史数据管理器 ====================
        class HistoryManager {
            constructor(storage) {
                this.storage = storage;
                this.cache = new LRUCache();
                this._history = null;
                this._historyTime = 0;
            }

            getHistory() {
                const now = Date.now();
                if (this._history && (now - this._historyTime) < CONFIG.CACHE.HISTORY_TTL) {
                    return this._history;
                }
                
                const history = this.storage.get('history', []);
                const cutoff = now - CONFIG.CACHE.MAX_HISTORY_DAYS * 86400000;
                this._history = history.filter(h => h.ts > cutoff);
                this._historyTime = now;
                return this._history;
            }

            addHistory(data, readingTime = 0) {
                const history = this.getHistory();
                const now = Date.now();
                const today = new Date().toDateString();
                const record = { ts: now, data, readingTime };

                const idx = history.findIndex(h => new Date(h.ts).toDateString() === today);
                idx >= 0 ? history[idx] = record : history.push(record);

                this.storage.set('history', history);
                this._history = history;
                this._historyTime = now;
                this.cache.clear();
                return history;
            }

            // 聚合每日增量
            aggregateDaily(history, reqs, maxDays) {
                const cacheKey = `daily_${maxDays}_${history.length}`;
                if (this.cache.has(cacheKey)) return this.cache.get(cacheKey);

                const byDay = new Map();
                history.forEach(h => {
                    const day = new Date(h.ts).toDateString();
                    byDay.has(day) ? byDay.get(day).push(h) : byDay.set(day, [h]);
                });

                const sortedDays = [...byDay.keys()].sort((a, b) => new Date(a) - new Date(b));
                const result = new Map();
                let prevData = null;

                sortedDays.forEach(day => {
                    const latest = byDay.get(day).at(-1);
                    const dayData = {};
                    reqs.forEach(r => {
                        dayData[r.name] = (latest.data[r.name] || 0) - (prevData?.[r.name] || 0);
                    });
                    result.set(day, dayData);
                    prevData = { ...latest.data };
                });

                this.cache.set(cacheKey, result);
                return result;
            }

            // 聚合每周增量
            aggregateWeekly(history, reqs) {
                const cacheKey = `weekly_${history.length}`;
                if (this.cache.has(cacheKey)) return this.cache.get(cacheKey);

                const now = new Date();
                const [year, month] = [now.getFullYear(), now.getMonth()];
                const weeks = this._getWeeksInMonth(year, month);
                const result = new Map();
                const byWeek = new Map(weeks.map((_, i) => [i, []]));

                history.forEach(h => {
                    const d = new Date(h.ts);
                    if (d.getFullYear() === year && d.getMonth() === month) {
                        weeks.forEach((week, i) => {
                            if (d >= week.start && d <= week.end) byWeek.get(i).push(h);
                        });
                    }
                });

                let prevData = null;
                const lastMonth = history.filter(h => new Date(h.ts) < new Date(year, month, 1));
                if (lastMonth.length) prevData = { ...lastMonth.at(-1).data };

                weeks.forEach((week, i) => {
                    const records = byWeek.get(i);
                    const weekData = {};
                    if (records.length) {
                        const latest = records.at(-1);
                        reqs.forEach(r => {
                            weekData[r.name] = (latest.data[r.name] || 0) - (prevData?.[r.name] || 0);
                        });
                        prevData = { ...latest.data };
                    } else {
                        reqs.forEach(r => weekData[r.name] = 0);
                    }
                    result.set(i, { weekNum: i + 1, start: week.start, end: week.end, label: `第${i + 1}周`, data: weekData });
                });

                this.cache.set(cacheKey, result);
                return result;
            }

            // 聚合每月增量
            aggregateMonthly(history, reqs) {
                const cacheKey = `monthly_${history.length}`;
                if (this.cache.has(cacheKey)) return this.cache.get(cacheKey);

                const byMonth = new Map();
                history.forEach(h => {
                    const d = new Date(h.ts);
                    const key = new Date(d.getFullYear(), d.getMonth(), 1).toDateString();
                    byMonth.has(key) ? byMonth.get(key).push(h) : byMonth.set(key, [h]);
                });

                const sortedMonths = [...byMonth.keys()].sort((a, b) => new Date(a) - new Date(b));
                const result = new Map();
                let prevData = null;

                sortedMonths.forEach(month => {
                    const latest = byMonth.get(month).at(-1);
                    const monthData = {};
                    reqs.forEach(r => {
                        monthData[r.name] = (latest.data[r.name] || 0) - (prevData?.[r.name] || 0);
                    });
                    result.set(month, monthData);
                    prevData = { ...latest.data };
                });

                this.cache.set(cacheKey, result);
                return result;
            }

            _getWeeksInMonth(year, month) {
                const weeks = [];
                const now = new Date();
                const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
                const lastDay = new Date(year, month + 1, 0);
                // v3.5.2.9: 只生成到当前日期为止的周,不显示未来的周
                const effectiveLastDay = (year === now.getFullYear() && month === now.getMonth()) 
                    ? (today < lastDay ? today : lastDay)
                    : lastDay;
                let start = new Date(year, month, 1);
                
                while (start <= effectiveLastDay) {
                    let end = new Date(start);
                    end.setDate(end.getDate() + 6);
                    if (end > effectiveLastDay) end = new Date(effectiveLastDay);
                    weeks.push({ start: new Date(start), end });
                    start = new Date(end);
                    start.setDate(start.getDate() + 1);
                }
                return weeks;
            }
        }

        // ==================== 阅读时间追踪器 ====================
        class ReadingTracker {
            constructor(storage) {
                this.storage = storage;
                this.isActive = true;
                this.lastActivity = Date.now();
                this.lastSave = Date.now();
                this._intervals = [];
                this._initialized = false;
                this._yearCache = null;
                this._yearCacheTime = 0;
            }

            init(username) {
                if (this._initialized) return;
                this.storage.migrate(username);
                this._bindEvents();
                
                // 始终启动活动状态追踪(用于 UI 显示)
                // 但只有领导者才会执行数据保存(避免多标签页重复写入)
                this._startTracking();
                
                this._initialized = true;
            }
            
            _stopTracking() {
                this._intervals.forEach(id => clearInterval(id));
                this._intervals = [];
                this._tracking = false;
                // 停止前保存当前数据
                this.save();
            }

            _bindEvents() {
                try {
                    // 使用节流的活动处理器
                    // 普通事件:每秒最多触发一次
                    this._activityHandler = Utils.throttle(() => this._onActivity(), 1000);
                    // 高频事件(如 mousemove):每 3 秒最多触发一次
                    this._highFreqHandler = Utils.throttle(() => this._onActivity(), 3000);
                    
                    // 监听用户活动事件
                    // 使用 capture: true 确保在事件捕获阶段就能获取,避免被其他脚本阻止
                    // 普通频率事件:点击、按键、触摸开始
                    this._normalEvents = ['mousedown', 'keydown', 'click', 'touchstart', 'pointerdown'];
                    this._normalEvents.forEach(e => {
                        document.addEventListener(e, this._activityHandler, { passive: true, capture: true });
                    });
                    
                    // 高频事件:移动、滚动(使用更长的节流时间)
                    this._highFreqEvents = ['mousemove', 'scroll', 'wheel', 'touchmove', 'pointermove'];
                    this._highFreqEvents.forEach(e => {
                        document.addEventListener(e, this._highFreqHandler, { passive: true, capture: true });
                    });

                    // 页面可见性变化
                    this._visibilityHandler = () => {
                        if (document.hidden) {
                            this.save();
                            this.isActive = false;
                        } else {
                            // 页面恢复可见时,假定用户正在查看,恢复活动状态
                            // 如果用户60秒内无任何操作,定时器会自动设为 inactive
                            this.lastActivity = Date.now();
                            this.isActive = true;
                        }
                    };
                    document.addEventListener('visibilitychange', this._visibilityHandler);
                    
                    // Safari/iOS 兼容:pageshow/pagehide 事件比 visibilitychange 更可靠
                    this._pageShowHandler = (e) => {
                        // e.persisted 表示页面从 bfcache 恢复
                        this.lastActivity = Date.now();
                        this.isActive = true;
                    };
                    this._pageHideHandler = () => {
                        this.save();
                        this.isActive = false;
                    };
                    window.addEventListener('pageshow', this._pageShowHandler);
                    window.addEventListener('pagehide', this._pageHideHandler);
                    
                    // 窗口获得焦点时更新活动状态(同时监听 window 和 document)
                    this._focusHandler = () => {
                        this.lastActivity = Date.now();
                        // Safari 上 focus 事件更可靠,直接设置 active
                        this.isActive = true;
                    };
                    this._blurHandler = () => {
                        // 窗口失去焦点时保存数据(Safari 上 visibilitychange 可能不触发)
                        this.save();
                    };
                    window.addEventListener('focus', this._focusHandler);
                    window.addEventListener('blur', this._blurHandler);
                    document.addEventListener('focus', this._focusHandler);

                    // 页面卸载前保存
                    this._beforeUnloadHandler = () => this.save();
                    window.addEventListener('beforeunload', this._beforeUnloadHandler);
                    
                } catch (e) {
                    Logger.log('Failed to bind events:', e);
                    // 降级:即使事件绑定失败,也尝试启动基本功能
                }
            }

            _onActivity() {
                const now = Date.now();
                if (!this.isActive) this.isActive = true;
                this.lastActivity = now;
            }

            _startTracking() {
                // 防止重复启动
                if (this._tracking) return;
                this._tracking = true;
                
                // 用于检测系统休眠/恢复
                let lastCheckTime = Date.now();
                
                // 记录定时器启动时间,用于健康检查
                this._trackingStartTime = Date.now();
                
                this._intervals.push(
                    setInterval(() => {
                        const now = Date.now();
                        const checkGap = now - lastCheckTime;
                        
                        // 检测系统休眠:如果两次检查间隔超过预期的 3 倍,说明可能休眠过
                        // 例如:READING_TRACK=10秒,如果间隔超过 30 秒,说明系统暂停过
                        if (checkGap > CONFIG.INTERVALS.READING_TRACK * 3) {
                            // 系统刚从休眠恢复,重置状态避免累积错误时间
                            this.isActive = false;
                            this.lastActivity = now;
                            this.lastSave = now;
                            lastCheckTime = now;
                            Logger.log('System resume detected, reset tracking state');
                            return; // 跳过本次空闲检测,等待用户新活动
                        }
                        lastCheckTime = now;
                        
                        // 空闲检测逻辑:只负责将 active 状态设为 false
                        // isActive = true 只能通过用户活动事件触发(_onActivity)
                        const idle = now - this.lastActivity;
                        if (this.isActive && idle > CONFIG.INTERVALS.READING_IDLE) {
                            this.isActive = false;
                        }
                        // 注意:不再自动将 isActive 设为 true
                        // 用户必须有新活动才能恢复记录状态
                    }, CONFIG.INTERVALS.READING_TRACK),
                    setInterval(() => this.save(), CONFIG.INTERVALS.READING_SAVE)
                );
                
                // 健康检查:每 60 秒检查定时器是否还存活
                // 如果定时器意外被清除,尝试重新启动
                this._healthCheckId = setInterval(() => {
                    if (this._tracking && this._intervals.length === 0) {
                        Logger.log('Tracking timers died, restarting...');
                        this._tracking = false;
                        this._startTracking();
                    }
                }, 60000);
            }

            save() {
                if (!this.storage.getUser()) return;

                const todayKey = Utils.getTodayKey();
                const now = Date.now();
                
                // 计算这次应该加的时间(基于本标签页的活动状态)
                const elapsed = (now - this.lastSave) / 1000;
                const idle = now - this.lastActivity;
                
                // 防护:检测异常数据
                // 1. elapsed 为负数(系统时间被调整)
                // 2. elapsed 过大(超过 2 分钟,可能是休眠恢复)
                // 3. idle 为负数(系统时间被调整)
                if (elapsed < 0 || elapsed > 120 || idle < 0) {
                    // 重置状态,不记录这段异常时间
                    this.lastSave = now;
                    this.lastActivity = now;
                    this.isActive = false;
                    return;
                }
                
                let toAdd = 0;
                if (elapsed > 0) {
                    // 计算有效的活动时间
                    // 如果用户一直活跃(idle <= 60秒),记录全部 elapsed 时间
                    // 如果用户空闲了,减去超出空闲阈值的部分
                    toAdd = idle <= CONFIG.INTERVALS.READING_IDLE 
                        ? elapsed 
                        : Math.max(0, elapsed - (idle - CONFIG.INTERVALS.READING_IDLE) / 1000);
                    
                    // 额外防护:单次保存不能超过保存间隔的 1.5 倍(正常约45秒)
                    const maxToAdd = CONFIG.INTERVALS.READING_SAVE / 1000 * 1.5;
                    toAdd = Math.min(toAdd, maxToAdd);
                }
                
                // 无论是否是领导者,都更新 lastSave(避免时间累积)
                this.lastSave = now;
                
                // 只有领导者才写入 storage
                if (!TabLeader.isLeader()) return;

                let stored = this.storage.get('readingTime', null);

                if (!stored?.dailyData) {
                    stored = { version: 3, dailyData: {}, monthlyCache: {}, yearlyCache: {} };
                }

                let today = stored.dailyData[todayKey] || { totalMinutes: 0, lastActive: now, sessions: [] };

                const minutes = toAdd / 60;
                if (minutes > 0.1) {
                    today.totalMinutes += minutes;
                    today.lastActive = now;
                    today.sessions = (today.sessions || []).slice(-20); // 限制会话数量
                    today.sessions.push({ time: now, added: minutes });

                    stored.dailyData[todayKey] = today;
                    this._updateCache(stored, todayKey, minutes);
                    this._cleanOld(stored);
                    this.storage.set('readingTime', stored);
                    this._yearCache = null;
                }
            }

            _updateCache(stored, dateKey, minutes) {
                try {
                    const d = new Date(dateKey);
                    const monthKey = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
                    const yearKey = `${d.getFullYear()}`;
                    stored.monthlyCache[monthKey] = (stored.monthlyCache[monthKey] || 0) + minutes;
                    stored.yearlyCache[yearKey] = (stored.yearlyCache[yearKey] || 0) + minutes;
                } catch (e) {}
            }

            _cleanOld(stored) {
                const cutoff = new Date();
                cutoff.setDate(cutoff.getDate() - CONFIG.CACHE.MAX_HISTORY_DAYS);

                Object.keys(stored.dailyData).forEach(key => {
                    if (new Date(key) < cutoff) delete stored.dailyData[key];
                });

                Object.keys(stored.monthlyCache || {}).forEach(key => {
                    const [y, m] = key.split('-');
                    if (new Date(+y, +m - 1, 1) < cutoff) delete stored.monthlyCache[key];
                });
            }

            getTodayTime() {
                if (!this.storage.getUser()) return 0;
                
                const stored = this.storage.get('readingTime', null);
                const saved = stored?.dailyData?.[Utils.getTodayKey()]?.totalMinutes || 0;
                
                const now = Date.now();
                const elapsed = (now - this.lastSave) / 1000;
                const idle = now - this.lastActivity;
                
                let unsaved = 0;
                if (idle <= CONFIG.INTERVALS.READING_IDLE) {
                    unsaved = elapsed / 60;
                } else {
                    unsaved = Math.max(0, elapsed - (idle - CONFIG.INTERVALS.READING_IDLE) / 1000) / 60;
                }

                return saved + Math.max(0, unsaved);
            }

            getTimeForDate(dateKey) {
                return this.storage.get('readingTime', null)?.dailyData?.[dateKey]?.totalMinutes || 0;
            }

            getWeekHistory() {
                const result = [];
                const now = new Date();
                
                for (let i = 6; i >= 0; i--) {
                    const d = new Date(now);
                    d.setDate(d.getDate() - i);
                    const key = d.toDateString();
                    result.push({
                        date: key,
                        label: Utils.formatDate(d.getTime()),
                        day: CONFIG.WEEKDAYS[d.getDay()],
                        minutes: i === 0 ? this.getTodayTime() : this.getTimeForDate(key),
                        isToday: i === 0
                    });
                }
                return result;
            }

            getYearData() {
                const now = Date.now();
                if (this._yearCache && (now - this._yearCacheTime) < CONFIG.CACHE.YEAR_DATA_TTL) {
                    return this._yearCache;
                }

                const today = new Date();
                const year = today.getFullYear();
                const stored = this.storage.get('readingTime', null);
                const daily = stored?.dailyData || {};
                const result = new Map();

                Object.entries(daily).forEach(([key, data]) => {
                    if (new Date(key).getFullYear() === year) {
                        result.set(key, data.totalMinutes || 0);
                    }
                });
                result.set(Utils.getTodayKey(), this.getTodayTime());

                this._yearCache = result;
                this._yearCacheTime = now;
                return result;
            }

            getTotalTime() {
                const stored = this.storage.get('readingTime', null);
                if (!stored?.dailyData) return this.getTodayTime();

                const todayKey = Utils.getTodayKey();
                let total = 0;
                Object.entries(stored.dailyData).forEach(([key, data]) => {
                    total += key === todayKey ? this.getTodayTime() : (data.totalMinutes || 0);
                });
                return total;
            }
            
            destroy() {
                // 清除计时定时器
                this._intervals.forEach(id => clearInterval(id));
                this._intervals = [];
                this._tracking = false;
                
                // 清除健康检查定时器
                if (this._healthCheckId) {
                    clearInterval(this._healthCheckId);
                    this._healthCheckId = null;
                }
                
                // 移除普通事件监听器(注意:capture 必须与添加时一致)
                if (this._activityHandler && this._normalEvents) {
                    this._normalEvents.forEach(e => {
                        document.removeEventListener(e, this._activityHandler, { passive: true, capture: true });
                    });
                }
                // 移除高频事件监听器
                if (this._highFreqHandler && this._highFreqEvents) {
                    this._highFreqEvents.forEach(e => {
                        document.removeEventListener(e, this._highFreqHandler, { passive: true, capture: true });
                    });
                }
                if (this._visibilityHandler) {
                    document.removeEventListener('visibilitychange', this._visibilityHandler);
                }
                // 移除 Safari 兼容事件
                if (this._pageShowHandler) {
                    window.removeEventListener('pageshow', this._pageShowHandler);
                }
                if (this._pageHideHandler) {
                    window.removeEventListener('pagehide', this._pageHideHandler);
                }
                // 移除焦点事件
                if (this._focusHandler) {
                    window.removeEventListener('focus', this._focusHandler);
                    document.removeEventListener('focus', this._focusHandler);
                }
                if (this._blurHandler) {
                    window.removeEventListener('blur', this._blurHandler);
                }
                if (this._beforeUnloadHandler) {
                    window.removeEventListener('beforeunload', this._beforeUnloadHandler);
                }
                
                // 保存数据
                this.save();
            }
        }

        // ==================== 通知管理器 ====================
        class Notifier {
            constructor(storage) {
                this.storage = storage;
            }

            check(reqs) {
                const achieved = this.storage.get('milestones', {});
                const newMilestones = [];

                reqs.forEach(r => {
                    Object.entries(CONFIG.MILESTONES).forEach(([key, thresholds]) => {
                        if (r.name.includes(key)) {
                            thresholds.forEach(t => {
                                const k = `${key}_${t}`;
                                if (r.currentValue >= t && !achieved[k]) {
                                    newMilestones.push({ name: key, threshold: t });
                                    achieved[k] = true;
                                }
                            });
                        }
                    });

                    const reqKey = `req_${r.name}`;
                    if (r.isSuccess && !achieved[reqKey]) {
                        newMilestones.push({ name: r.name, type: 'req' });
                        achieved[reqKey] = true;
                    }
                });

                if (newMilestones.length) {
                    this.storage.set('milestones', achieved);
                    this._notify(newMilestones);
                }
            }

            _notify(milestones) {
                const last = this.storage.get('lastNotify', 0);
                if (Date.now() - last < 60000) return;
                
                this.storage.set('lastNotify', Date.now());
                const msg = milestones.slice(0, 3).map(m => 
                    m.type === 'req' ? `✅ ${m.name}` : `🏆 ${m.name} → ${m.threshold}`
                ).join('\n');

                typeof GM_notification !== 'undefined' && GM_notification({
                    title: '🎉 达成里程碑!',
                    text: msg,
                    timeout: 5000
                });
            }
        }

        // ==================== OAuth 管理器 ====================
        class OAuthManager {
            constructor(storage, network) {
                this.storage = storage;
                this.network = network;
            }

            getToken() { return this.storage.getGlobal('leaderboardToken', null); }
            setToken(token) { this.storage.setGlobalNow('leaderboardToken', token); }
            
            getUserInfo() { return this.storage.getGlobal('leaderboardUser', null); }
            setUserInfo(user) { this.storage.setGlobalNow('leaderboardUser', user); }
            
            /**
             * 检查是否已登录且 Token 未过期
             */
            isLoggedIn() {
                const token = this.getToken();
                const user = this.getUserInfo();
                if (!token || !user) return false;
                
                // 检查 token 是否过期
                if (this._isTokenExpired(token)) {
                    Logger.log('Token expired, logging out');
                    this.logout();
                    return false;
                }
                return true;
            }
            
            /**
             * 解析 JWT Token 检查是否过期
             */
            _isTokenExpired(token) {
                try {
                    const parts = token.split('.');
                    if (parts.length !== 3) return true;
                    
                    // 解析 payload (base64url)
                    const payload = parts[1].replace(/-/g, '+').replace(/_/g, '/');
                    const decoded = JSON.parse(atob(payload));
                    
                    // 检查过期时间 (exp 是秒级时间戳)
                    if (!decoded.exp) return false; // 无过期时间则认为有效
                    
                    const now = Math.floor(Date.now() / 1000);
                    // 提前 10 分钟判断为过期,增加容错时间避免边界情况
                    return decoded.exp < (now + 600);
                } catch (e) {
                    console.error('[LDStatus Pro] Token parse error:', e);
                    return true; // 解析失败视为过期
                }
            }
            
            isJoined() { return this.storage.getGlobal('leaderboardJoined', false); }
            setJoined(v) { this.storage.setGlobalNow('leaderboardJoined', v); }

            /**
             * 检查 URL hash 中的登录结果
             * 统一同窗口登录模式:回调后通过 URL hash 传递登录结果
             */
            _checkUrlHashLogin() {
                try {
                    const hash = window.location.hash;
                    if (!hash) return null;
                    
                    // 查找 ldsp_oauth 参数
                    const match = hash.match(/ldsp_oauth=([^&]+)/);
                    if (!match) return null;
                    
                    const encoded = match[1];
                    // 解码 base64
                    const decoded = JSON.parse(decodeURIComponent(atob(encoded)));
                    
                    // 检查时效性(5分钟内有效)
                    if (decoded.ts && Date.now() - decoded.ts > 5 * 60 * 1000) {
                        console.log('[OAuth] URL login result expired');
                        this._clearUrlHash();
                        return null;
                    }
                    
                    // 转换为标准格式
                    const result = {
                        success: true,
                        token: decoded.t,
                        user: decoded.u,
                        isJoined: decoded.j === 1
                    };
                    
                    // 清除 URL 中的登录参数,保持 URL 干净
                    this._clearUrlHash();
                    
                    return result;
                } catch (e) {
                    console.error('[OAuth] Failed to parse URL hash login:', e);
                    this._clearUrlHash();
                    return null;
                }
            }
            
            /**
             * 清除 URL 中的 OAuth 登录参数
             */
            _clearUrlHash() {
                try {
                    const hash = window.location.hash;
                    if (!hash || !hash.includes('ldsp_oauth=')) return;
                    
                    // 移除 ldsp_oauth 参数
                    let newHash = hash.replace(/[#&]?ldsp_oauth=[^&]*/, '');
                    // 清理多余的 # 和 &
                    newHash = newHash.replace(/^[#&]+/, '').replace(/[#&]+$/, '');
                    
                    // 更新 URL(不触发页面刷新)
                    const newUrl = window.location.pathname + window.location.search + (newHash ? '#' + newHash : '');
                    history.replaceState(null, '', newUrl);
                } catch (e) {
                    console.warn('[OAuth] Failed to clear URL hash:', e);
                }
            }

            /**
             * 统一同窗口登录
             * 所有环境都使用同窗口跳转方式,避免弹窗拦截和跨窗口通信问题
             */
            async login() {
                // 检查是否有待处理的登录结果(从 URL hash 中获取)
                const pendingResult = this._checkUrlHashLogin();
                if (pendingResult?.success && pendingResult.token && pendingResult.user) {
                    this.setToken(pendingResult.token);
                    this.setUserInfo(pendingResult.user);
                    this.setJoined(pendingResult.isJoined || false);
                    return pendingResult.user;
                }

                // 获取授权链接并跳转(同窗口模式)
                const siteParam = encodeURIComponent(CURRENT_SITE.domain);
                // 使用不带 hash 的 URL 作为返回地址
                const returnUrl = encodeURIComponent(window.location.origin + window.location.pathname + window.location.search);
                
                try {
                    const result = await this.network.api(`/api/auth/init?site=${siteParam}&return_url=${returnUrl}`);
                    
                    if (result.success && result.data?.auth_url) {
                        // 跳转到授权页面
                        window.location.href = result.data.auth_url;
                        // 返回一个永不 resolve 的 Promise(页面会跳转,不会执行后续代码)
                        return new Promise(() => {});
                    } else {
                        throw new Error(result.error?.message || '获取授权链接失败');
                    }
                } catch (e) {
                    throw new Error(e.message || '登录请求失败');
                }
            }

            logout() {
                this.setToken(null);
                this.setUserInfo(null);
                this.setJoined(false);
            }

            /**
             * 发起 API 请求,自动处理 Token 过期
             * @param {string} endpoint - API 端点
             * @param {Object} options - 请求选项
             * @param {boolean} options.requireAuth - 是否需要登录(默认 true)
             */
            async api(endpoint, options = {}) {
                const { requireAuth = true, ...restOptions } = options;
                
                // 需要登录的接口,先检查登录状态(包含 Token 过期检测)
                if (requireAuth) {
                    const token = this.getToken();
                    // 无 Token 或 Token 已过期,直接返回错误
                    if (!token || this._isTokenExpired(token)) {
                        // 清理过期状态
                        if (token) this.logout();
                        return { success: false, error: { code: 'NOT_LOGGED_IN', message: 'Not logged in or token expired' } };
                    }
                }
                
                try {
                    const result = await this.network.api(endpoint, { ...restOptions, token: this.getToken() });
                    return result;
                } catch (e) {
                    // 检查是否是 Token 过期错误
                    const errMsg = e.message || '';
                    const isAuthError = errMsg.includes('expired') || errMsg.includes('TOKEN_EXPIRED') || 
                        errMsg.includes('INVALID_TOKEN') || errMsg.includes('401') ||
                        errMsg.includes('Unauthorized') || (e instanceof NetworkError && e.isAuth);
                    
                    if (isAuthError) {
                        this.logout();
                        // 通过事件总线通知(替代全局 window 事件)
                        EventBus.emit('auth:expired', { endpoint });
                    }
                    throw e;
                }
            }
        }

        // ==================== 排行榜管理器 ====================
        class LeaderboardManager {
            constructor(oauth, readingTracker, storage) {
                this.oauth = oauth;
                this.tracker = readingTracker;
                this.storage = storage;  // v3.2.7: 用于智能同步缓存
                this.cache = new Map();
                this._syncTimer = null;
                this._lastSync = 0;
                this._manualRefreshTime = new Map(); // 记录每种榜的手动刷新时间
            }

            // 手动刷新冷却时间 5 分钟
            static MANUAL_REFRESH_COOLDOWN = 5 * 60 * 1000;

            async getLeaderboard(type = 'daily') {
                const key = `lb_${type}`;
                const cached = this.cache.get(key);
                const now = Date.now();
                const ttlMap = {
                    daily: CONFIG.CACHE.LEADERBOARD_DAILY_TTL,
                    weekly: CONFIG.CACHE.LEADERBOARD_WEEKLY_TTL,
                    monthly: CONFIG.CACHE.LEADERBOARD_MONTHLY_TTL
                };
                const ttl = ttlMap[type] || CONFIG.CACHE.LEADERBOARD_DAILY_TTL;

                if (cached && (now - cached.time) < ttl) return cached.data;

                try {
                    // oauth.api() 内置登录检查,未登录时返回 { success: false }
                    const result = await this.oauth.api(`/api/leaderboard/${type}`);
                    if (result.success) {
                        const data = {
                            rankings: result.data.rankings || [],
                            period: result.data.period,
                            myRank: result.data.myRank
                        };
                        this.cache.set(key, { data, time: now });
                        return data;
                    }
                    throw new Error(result.error || '获取排行榜失败');
                } catch (e) {
                    if (cached) return cached.data;
                    throw e;
                }
            }

            // 手动刷新排行榜(有5分钟冷却时间)
            async forceRefresh(type = 'daily') {
                const key = `lb_${type}`;
                const now = Date.now();
                const lastRefresh = this._manualRefreshTime.get(type) || 0;

                // 检查冷却时间
                if (now - lastRefresh < LeaderboardManager.MANUAL_REFRESH_COOLDOWN) {
                    // 冷却中,返回缓存
                    const cached = this.cache.get(key);
                    if (cached) return { data: cached.data, fromCache: true };
                    throw new Error('刷新冷却中');
                }

                try {
                    const result = await this.oauth.api(`/api/leaderboard/${type}`);
                    if (result.success) {
                        const data = {
                            rankings: result.data.rankings || [],
                            period: result.data.period,
                            myRank: result.data.myRank
                        };
                        this.cache.set(key, { data, time: now });
                        this._manualRefreshTime.set(type, now);
                        return { data, fromCache: false };
                    }
                    throw new Error(result.error || '获取排行榜失败');
                } catch (e) {
                    const cached = this.cache.get(key);
                    if (cached) return { data: cached.data, fromCache: true };
                    throw e;
                }
            }

            // 获取手动刷新剩余冷却时间(秒)
            getRefreshCooldown(type = 'daily') {
                const lastRefresh = this._manualRefreshTime.get(type) || 0;
                const elapsed = Date.now() - lastRefresh;
                const remaining = LeaderboardManager.MANUAL_REFRESH_COOLDOWN - elapsed;
                return remaining > 0 ? Math.ceil(remaining / 1000) : 0;
            }

            async join() {
                const result = await this.oauth.api('/api/user/register', { method: 'POST' });
                if (result.success) {
                    this.oauth.setJoined(true);
                    return true;
                }
                // 检测登录失效情况
                const errCode = result.error?.code;
                if (errCode === 'NOT_LOGGED_IN' || errCode === 'AUTH_EXPIRED' || errCode === 'INVALID_TOKEN') {
                    throw new Error('登录已失效,请重新登录');
                }
                throw new Error(result.error?.message || result.error || '加入失败');
            }

            async quit() {
                const result = await this.oauth.api('/api/user/quit', { method: 'POST' });
                if (result.success) {
                    this.oauth.setJoined(false);
                    return true;
                }
                // 检测登录失效情况
                const errCode = result.error?.code;
                if (errCode === 'NOT_LOGGED_IN' || errCode === 'AUTH_EXPIRED' || errCode === 'INVALID_TOKEN') {
                    throw new Error('登录已失效,请重新登录');
                }
                throw new Error(result.error?.message || result.error || '退出失败');
            }

            async syncReadingTime() {
                if (!this.oauth.isLoggedIn() || !this.oauth.isJoined()) return;
                // 只有领导者标签页才执行同步,避免多标签页重复请求
                if (!TabLeader.isLeader()) return;
                if (Date.now() - this._lastSync < 60000) return;

                try {
                    const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
                    const currentMinutes = this.tracker.getTodayTime();
                    
                    // v3.2.7 优化(方案E):智能同步 - 只在数据变化时才发送请求
                    // 节省约 30% 的 D1 写入额度
                    const lastSyncedKey = `lastSynced_${today}`;
                    const lastSyncedMinutes = this.storage?.getGlobal(lastSyncedKey, -1) ?? -1;
                    
                    if (currentMinutes === lastSyncedMinutes) {
                        // 数据没变化,跳过同步
                        return;
                    }
                    
                    const result = await this.oauth.api('/api/reading/sync', {
                        method: 'POST',
                        body: { 
                            date: today,
                            minutes: currentMinutes,
                            client_timestamp: Date.now()
                        }
                    });
                    
                    // 登录失效或请求失败,不更新本地状态
                    if (!result?.success && !result?.server_minutes) {
                        return;
                    }
                    
                    this._lastSync = Date.now();
                    
                    // v3.4.2 修复:渐进同步 - 处理服务器截断响应
                    // 服务器防刷机制会限制单次增量,需要多次同步才能完成大幅增量
                    if (result && result.server_minutes !== undefined) {
                        // 以服务器实际接受的分钟数为准
                        const serverAccepted = result.server_minutes;
                        this.storage?.setGlobal(lastSyncedKey, serverAccepted);
                        
                        if (result.truncated && serverAccepted < currentMinutes) {
                            // 服务器截断了数据,需要继续同步
                            Logger.log(`Leaderboard sync truncated: server=${serverAccepted}, client=${currentMinutes}, will retry`);
                            // 35秒后再次尝试同步剩余数据(服务器限制是30秒)
                            setTimeout(() => {
                                this._lastSync = 0; // 重置冷却时间
                                this.syncReadingTime();
                            }, 35000);
                        } else if (result.rateLimited) {
                            // 被服务器限速,稍后重试
                            Logger.log('Leaderboard rate limited, will retry later');
                            setTimeout(() => {
                                this._lastSync = 0;
                                this.syncReadingTime();
                            }, 35000);
                        }
                    } else {
                        // 兼容旧版响应格式
                        this.storage?.setGlobal(lastSyncedKey, currentMinutes);
                    }
                } catch (e) {
                    console.warn('[Leaderboard] Sync failed:', e.message || e);
                }
            }

            startSync() {
                if (this._syncTimer) return;
                // 延迟5秒后首次同步,避免与页面加载时的其他请求并发
                setTimeout(() => this.syncReadingTime(), 5000);
                this._syncTimer = setInterval(() => this.syncReadingTime(), CONFIG.INTERVALS.LEADERBOARD_SYNC);
            }

            stopSync() {
                this._syncTimer && clearInterval(this._syncTimer);
                this._syncTimer = null;
            }

            clearCache() { this.cache.clear(); }
            
            destroy() {
                this.stopSync();
                this.clearCache();
            }
        }

        // ==================== 云同步管理器 ====================
        class CloudSyncManager {
            constructor(storage, oauth, tracker) {
                this.storage = storage;
                this.oauth = oauth;
                this.tracker = tracker;
                this._timer = null;
                this._syncing = false;
                this._lastUpload = storage.getGlobal('lastCloudSync', 0);
                this._lastDownload = storage.getGlobal('lastDownloadSync', 0);
                this._lastHash = storage.getGlobal('lastUploadHash', '');
                this._onSyncStateChange = null;  // 同步状态变化回调
                
                // 失败重试机制
                this._failureCount = { reading: 0, requirements: 0 };
                this._lastFailure = { reading: 0, requirements: 0 };
                
                // trust_level 缓存(避免重复调用 requirements 接口)
                this._trustLevelCache = storage.getGlobal('trustLevelCache', null);
                this._trustLevelCacheTime = storage.getGlobal('trustLevelCacheTime', 0);
            }
            
            // 计算退避延迟(指数退避,最大 30 分钟)
            _getBackoffDelay(type) {
                const failures = this._failureCount[type] || 0;
                if (failures === 0) return 0;
                const baseDelay = CONFIG.INTERVALS.SYNC_RETRY_DELAY || 60000;
                return Math.min(baseDelay * Math.pow(2, failures - 1), 30 * 60 * 1000);
            }
            
            // 检查是否可以重试
            _canRetry(type) {
                const lastFail = this._lastFailure[type] || 0;
                const backoff = this._getBackoffDelay(type);
                return Date.now() - lastFail >= backoff;
            }
            
            // 记录失败
            _recordFailure(type) {
                this._failureCount[type] = Math.min((this._failureCount[type] || 0) + 1, 6);
                this._lastFailure[type] = Date.now();
            }
            
            // 记录成功(重置失败计数)
            _recordSuccess(type) {
                this._failureCount[type] = 0;
                this._lastFailure[type] = 0;
            }
            
            // 检查用户 trust_level 是否足够
            // 优先从 OAuth 用户信息获取,其次使用缓存
            _hasSufficientTrustLevel() {
                // 1. 优先从 OAuth 用户信息获取 trust_level(最准确)
                // v3.4.7: 兼容 trust_level 和 trustLevel 两种命名格式
                const userInfo = this.oauth.getUserInfo();
                const trustLevel = userInfo?.trust_level ?? userInfo?.trustLevel;
                if (userInfo && typeof trustLevel === 'number') {
                    const hasTrust = trustLevel >= 2;
                    // 更新缓存以便其他地方使用
                    if (this._trustLevelCache !== hasTrust) {
                        this._updateTrustLevelCache(hasTrust);
                    }
                    return hasTrust;
                }
                
                // 2. 使用缓存(24小时有效)
                const now = Date.now();
                const cacheAge = now - this._trustLevelCacheTime;
                if (this._trustLevelCache !== null && cacheAge < 24 * 60 * 60 * 1000) {
                    return this._trustLevelCache;
                }
                
                // 3. 无法确定,返回 null(需要从 API 获取)
                return null;
            }
            
            // 更新 trust_level 缓存(兼容性保留)
            _updateTrustLevelCache(hasTrust) {
                // v3.4.8: 移除等级限制,始终缓存为 true
                this._trustLevelCache = true;
                this._trustLevelCacheTime = Date.now();
                this.storage.setGlobalNow('trustLevelCache', true);
                this.storage.setGlobalNow('trustLevelCacheTime', this._trustLevelCacheTime);
            }

            // 设置同步状态变化回调
            setSyncStateCallback(callback) {
                this._onSyncStateChange = callback;
            }

            // 更新同步状态
            _setSyncing(syncing) {
                this._syncing = syncing;
                this._onSyncStateChange?.(syncing);
            }

            // 获取同步状态
            isSyncing() {
                return this._syncing;
            }

            _getDataHash() {
                const data = this.storage.get('readingTime', null);
                if (!data?.dailyData) return '';
                const days = Object.keys(data.dailyData).length;
                const total = Object.values(data.dailyData).reduce((s, d) => s + (d.totalMinutes || 0), 0);
                return `${days}:${Math.round(total)}`;
            }

            async download() {
                // 检查退避延迟
                if (!this._canRetry('reading')) {
                    return null;
                }

                try {
                    const result = await this.oauth.api('/api/reading/history?days=365');
                    if (!result.success) {
                        this._recordFailure('reading');
                        return null;
                    }
                    
                    this._recordSuccess('reading');

                    const cloud = result.data.dailyData || {};
                    let local = this.storage.get('readingTime', null);

                    if (!local?.dailyData) {
                        local = { version: 3, dailyData: cloud, monthlyCache: {}, yearlyCache: {} };
                        this._rebuildCache(local);
                        this.storage.setNow('readingTime', local);
                        // 通知 UI 阅读数据已更新(新设备首次同步)
                        EventBus.emit('reading:synced', { merged: Object.keys(cloud).length, source: 'cloud' });
                        return { merged: Object.keys(cloud).length, source: 'cloud' };
                    }

                    let merged = 0;
                    Object.entries(cloud).forEach(([key, cloudDay]) => {
                        const localMinutes = local.dailyData[key]?.totalMinutes || 0;
                        const cloudMinutes = cloudDay.totalMinutes || 0;
                        if (cloudMinutes > localMinutes) {
                            local.dailyData[key] = {
                                totalMinutes: cloudMinutes,
                                lastActive: cloudDay.lastActive || Date.now(),
                                sessions: local.dailyData[key]?.sessions || []
                            };
                            merged++;
                        }
                    });

                    if (merged > 0) {
                        this._rebuildCache(local);
                        this.storage.setNow('readingTime', local);
                        // 通知 UI 阅读数据已更新
                        EventBus.emit('reading:synced', { merged, source: 'merge' });
                    }
                    return { merged, source: 'merge' };
                } catch (e) {
                    console.error('[CloudSync] Download failed:', e);
                    this._recordFailure('reading');
                    return null;
                }
            }

            async upload() {
                // 前置检查:登录状态 + 同步状态 + 数据有效性
                if (!this.oauth.isLoggedIn() || this._syncing) return null;
                
                const local = this.storage.get('readingTime', null);
                if (!local?.dailyData || Object.keys(local.dailyData).length === 0) {
                    return null;
                }
                
                // 检查退避延迟
                if (!this._canRetry('reading')) {
                    return null;
                }

                try {
                    this._setSyncing(true);

                    // 优化:只上传最近 90 天的数据,减少请求大小
                    const cutoffDate = new Date();
                    cutoffDate.setDate(cutoffDate.getDate() - 90);
                    const _cutoff = cutoffDate.toDateString();
                    
                    const recentData = {};
                    let count = 0;
                    for (const [key, value] of Object.entries(local.dailyData)) {
                        // 只保留最近90天的数据
                        try {
                            const date = new Date(key);
                            if (date >= cutoffDate && count < 100) { // 最多100条
                                recentData[key] = value;
                                count++;
                            }
                        } catch (e) {}
                    }
                    
                    if (Object.keys(recentData).length === 0) {
                        this._setSyncing(false);
                        return null;
                    }

                    const result = await this.oauth.api('/api/reading/sync-full', {
                        method: 'POST',
                        body: { dailyData: recentData, lastSyncTime: Date.now() }
                    });

                    if (result.success) {
                        this._lastUpload = Date.now();
                        this.storage.setGlobalNow('lastCloudSync', this._lastUpload);
                        this._recordSuccess('reading');
                        return result.data;
                    }
                    this._recordFailure('reading');
                    throw new Error(result.error || '上传失败');
                } catch (e) {
                    console.error('[CloudSync] Upload failed:', e);
                    this._recordFailure('reading');
                    return null;
                } finally {
                    this._setSyncing(false);
                }
            }

            async onPageLoad() {
                if (!this.oauth.isLoggedIn()) return;

                const now = Date.now();
                const local = this.storage.get('readingTime', null);
                const hasLocal = local?.dailyData && Object.keys(local.dailyData).length > 0;
                const isNew = !hasLocal || this._lastDownload === 0;

                // 串行执行同步请求,避免并发压力
                // 1. 下载检查(优先级最高)
                if (isNew || (now - this._lastDownload) > CONFIG.INTERVALS.CLOUD_DOWNLOAD) {
                    const result = await this.download();
                    if (result) {
                        this._lastDownload = now;
                        this.storage.setGlobalNow('lastDownloadSync', now);
                        if (isNew && result.merged > 0) this.tracker._yearCache = null;
                    }
                }

                // 2. 上传检查(仅在数据变化时)
                const hash = this._getDataHash();
                if (hash && hash !== this._lastHash && (now - this._lastUpload) > 5 * 60 * 1000) {
                    // 至少间隔 5 分钟才上传
                    const result = await this.upload();
                    if (result) {
                        this._lastHash = hash;
                        this.storage.setGlobalNow('lastUploadHash', hash);
                    }
                }

                this._startPeriodicSync();
            }

            async fullSync() {
                // 前置登录检查
                if (!this.oauth.isLoggedIn() || this._syncing) return;
                
                try {
                    this._setSyncing(true);
                    
                    await this.download();
                    this._lastDownload = Date.now();
                    this.storage.setGlobalNow('lastDownloadSync', this._lastDownload);

                    // 上传本地数据(只上传最近 90 天,减少请求大小)
                    const local = this.storage.get('readingTime', null);
                    if (local?.dailyData && Object.keys(local.dailyData).length > 0) {
                        const cutoffDate = new Date();
                        cutoffDate.setDate(cutoffDate.getDate() - 90);
                        
                        const recentData = {};
                        let count = 0;
                        for (const [key, value] of Object.entries(local.dailyData)) {
                            try {
                                const date = new Date(key);
                                if (date >= cutoffDate && count < 100) {
                                    recentData[key] = value;
                                    count++;
                                }
                            } catch (e) {}
                        }
                        
                        if (Object.keys(recentData).length > 0) {
                            const result = await this.oauth.api('/api/reading/sync-full', {
                                method: 'POST',
                                body: { dailyData: recentData, lastSyncTime: Date.now() }
                            });
                            if (result?.success) {
                                this._lastUpload = Date.now();
                                this.storage.setGlobalNow('lastCloudSync', this._lastUpload);
                            }
                        }
                    }
                    this._lastHash = this._getDataHash();
                    this.storage.setGlobalNow('lastUploadHash', this._lastHash);

                    this._startPeriodicSync();
                } finally {
                    this._setSyncing(false);
                }
            }

            _startPeriodicSync() {
                if (this._timer) return;
                this._timer = setInterval(async () => {
                    if (!this.oauth.isLoggedIn()) return;
                    // 只有领导者标签页才执行定期同步,避免多标签页重复请求
                    if (!TabLeader.isLeader()) return;
                    if (this._syncing) return; // 避免并发

                    const now = Date.now();
                    const hash = this._getDataHash();

                    // 上传检查:数据变化 + 间隔足够 + 不在退避期
                    if (hash !== this._lastHash && 
                        (now - this._lastUpload) > CONFIG.INTERVALS.CLOUD_UPLOAD &&
                        this._canRetry('reading')) {
                        const result = await this.upload();
                        if (result) {
                            this._lastHash = hash;
                            this.storage.setGlobalNow('lastUploadHash', hash);
                        }
                    }

                    // 下载检查:间隔足够 + 不在退避期
                    if ((now - this._lastDownload) > CONFIG.INTERVALS.CLOUD_DOWNLOAD &&
                        this._canRetry('reading')) {
                        const result = await this.download();
                        if (result) {
                            this._lastDownload = now;
                            this.storage.setGlobalNow('lastDownloadSync', now);
                        }
                    }
                }, CONFIG.INTERVALS.CLOUD_CHECK);
            }

            _rebuildCache(data) {
                data.monthlyCache = {};
                data.yearlyCache = {};
                Object.entries(data.dailyData).forEach(([key, day]) => {
                    try {
                        const d = new Date(key);
                        if (isNaN(d.getTime())) return;
                        const monthKey = `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}`;
                        const yearKey = `${d.getFullYear()}`;
                        const minutes = day.totalMinutes || 0;
                        data.monthlyCache[monthKey] = (data.monthlyCache[monthKey] || 0) + minutes;
                        data.yearlyCache[yearKey] = (data.yearlyCache[yearKey] || 0) + minutes;
                    } catch (e) {}
                });
            }

            // ==================== 升级要求历史同步 (trust_level >= 2) ====================

            /**
             * 设置 HistoryManager 引用(用于升级要求同步)
             */
            setHistoryManager(historyMgr) {
                this._historyMgr = historyMgr;
                // 兼容旧版本存储 key
                this._reqLastDownload = this.storage.getGlobal('lastReqDownload', 0);
                this._reqLastFullSync = this.storage.getGlobal('lastReqFullSync', 0) || 
                                        this.storage.getGlobal('lastReqSync', 0); // 兼容旧 key
                this._reqLastIncrementalSync = this.storage.getGlobal('lastReqIncrementalSync', 0);
            }

            /**
             * 获取升级要求历史数据的 hash
             */
            _getReqHash() {
                if (!this._historyMgr) return '';
                const history = this._historyMgr.getHistory();
                if (!history.length) return '';
                return `${history.length}:${history[history.length - 1].ts}`;
            }

            /**
             * 为单日升级要求记录生成稳定哈希,用于请求去重
             * @param {{data: object, readingTime?: number}} record
             * @returns {string}
             */
            _getReqRecordHash(record) {
                if (!record?.data) return '';
                const sorted = Object.entries(record.data).sort(([a], [b]) => a.localeCompare(b));
                const dataStr = sorted.map(([k, v]) => `${k}:${v ?? 0}`).join('|');
                const reading = Math.round(record.readingTime || 0);
                return `${dataStr}|rt:${reading}`;
            }

            /**
             * 下载升级要求历史数据
             */
            async downloadRequirements() {
                // 前置检查:登录状态 + 只有领导者标签页执行
                if (!this.oauth.isLoggedIn() || !this._historyMgr) return null;
                if (!TabLeader.isLeader()) return null;
                
                // 检查退避延迟
                if (!this._canRetry('requirements')) {
                    return null;
                }

                try {
                    // 减少请求数据量:从 100 天减少到 60 天
                    const result = await this.oauth.api('/api/requirements/history?days=60');
                    
                    if (!result.success) {
                        // 权限不足(trust_level < 2)是正常情况,缓存结果避免重复请求
                        if (result.error?.code === 'INSUFFICIENT_TRUST_LEVEL') {
                            this._updateTrustLevelCache(false);
                            return null;
                        }
                        this._recordFailure('requirements');
                        return null;
                    }
                    
                    // 请求成功,说明有足够权限
                    this._updateTrustLevelCache(true);
                    this._recordSuccess('requirements');

                    const cloudHistory = result.data.history || [];
                    if (!cloudHistory.length) return { merged: 0, source: 'empty' };

                    let localHistory = this._historyMgr.getHistory();
                    const localByDay = new Map();
                    localHistory.forEach(h => {
                        const day = new Date(h.ts).toDateString();
                        localByDay.set(day, h);
                    });

                    let merged = 0;
                    cloudHistory.forEach(cloudRecord => {
                        const day = new Date(cloudRecord.ts).toDateString();
                        const localRecord = localByDay.get(day);

                        if (!localRecord) {
                            // 本地没有,添加云端数据
                            localHistory.push(cloudRecord);
                            merged++;
                        } else {
                            // 本地有,合并数据(取每个字段的较大值)
                            let changed = false;
                            for (const [key, cloudValue] of Object.entries(cloudRecord.data)) {
                                if (typeof cloudValue === 'number') {
                                    const localValue = localRecord.data[key] || 0;
                                    if (cloudValue > localValue) {
                                        localRecord.data[key] = cloudValue;
                                        changed = true;
                                    }
                                }
                            }
                            if (cloudRecord.readingTime > (localRecord.readingTime || 0)) {
                                localRecord.readingTime = cloudRecord.readingTime;
                                changed = true;
                            }
                            if (changed) merged++;
                        }
                    });

                    if (merged > 0) {
                        // 按时间排序
                        localHistory.sort((a, b) => a.ts - b.ts);
                        this.storage.set('history', localHistory);
                        this._historyMgr._history = localHistory;
                        this._historyMgr._historyTime = Date.now();
                        this._historyMgr.cache.clear();
                    }

                    return { merged, source: 'merge' };
                } catch (e) {
                    console.error('[CloudSync] Requirements download failed:', e);
                    this._recordFailure('requirements');
                    return null;
                }
            }

            /**
             * 增量同步当天的升级要求数据
             * @param {Object} todayRecord - 今天的历史记录 {ts, data, readingTime}
             */
            async syncTodayRequirements(todayRecord, { force = false } = {}) {
                // 前置检查:登录状态 + 数据有效性
                if (!this.oauth.isLoggedIn() || !this._historyMgr) return null;
                if (!todayRecord?.data || Object.keys(todayRecord.data).length === 0) return null;
                // 仅标签页主节点执行,避免多标签重复请求
                if (!TabLeader.isLeader()) return null;
                
                const INCREMENTAL_INTERVAL = CONFIG.INTERVALS.REQ_SYNC_INCREMENTAL || 3600000; // 1小时
                const now = Date.now();
                const today = new Date().toISOString().split('T')[0]; // YYYY-MM-DD
                const lastIncremental = this._reqLastIncrementalSync || 0;

                // 数据去重:相同内容不重复同步
                const hashKey = `lastReqHash_${today}`;
                const currentHash = this._getReqRecordHash(todayRecord);
                const lastHash = this.storage.getGlobal(hashKey, '');

                if (!force) {
                    if (currentHash && currentHash === lastHash) {
                        return null;
                    }
                    if ((now - lastIncremental) < INCREMENTAL_INTERVAL) {
                        return null;
                    }
                }

                // 检查退避延迟
                if (!this._canRetry('requirements')) {
                    return null;
                }

                try {
                    const result = await this.oauth.api('/api/requirements/sync', {
                        method: 'POST',
                        body: { 
                            date: today,
                            requirements: todayRecord.data,
                            readingTime: todayRecord.readingTime || 0
                        }
                    });

                    if (result.success) {
                        this._reqLastIncrementalSync = Date.now();
                        this.storage.setGlobalNow('lastReqIncrementalSync', this._reqLastIncrementalSync);
                        if (currentHash) {
                            this.storage.setGlobalNow(hashKey, currentHash);
                        }
                        this._updateTrustLevelCache(true);
                        this._recordSuccess('requirements');
                        return result.data;
                    }
                    
                    // 权限不足是正常情况,缓存结果
                    if (result.error?.code === 'INSUFFICIENT_TRUST_LEVEL') {
                        this._updateTrustLevelCache(false);
                        return null;
                    }
                    
                    this._recordFailure('requirements');
                    return null;
                } catch (e) {
                    console.error('[CloudSync] Requirements incremental sync failed:', e);
                    this._recordFailure('requirements');
                    return null;
                }
            }

            /**
             * 全量上传升级要求历史数据(仅在需要时调用)
             */
            async uploadRequirementsFull() {
                // 前置检查:登录状态 + 数据有效性
                if (!this.oauth.isLoggedIn() || !this._historyMgr || this._syncing) return null;
                
                const history = this._historyMgr.getHistory();
                if (!history.length) return null;
                
                // 检查退避延迟
                if (!this._canRetry('requirements')) {
                    return null;
                }

                try {
                    // 限制上传数据量,最多 60 天
                    const recentHistory = history.slice(-60);
                    
                    const result = await this.oauth.api('/api/requirements/sync-full', {
                        method: 'POST',
                        body: { history: recentHistory, lastSyncTime: Date.now() }
                    });

                    if (result.success) {
                        this._reqLastFullSync = Date.now();
                        this.storage.setGlobalNow('lastReqFullSync', this._reqLastFullSync);
                        this._updateTrustLevelCache(true);
                        this._recordSuccess('requirements');
                        return result.data;
                    }
                    
                    // 权限不足是正常情况,缓存结果
                    if (result.error?.code === 'INSUFFICIENT_TRUST_LEVEL') {
                        this._updateTrustLevelCache(false);
                        return null;
                    }
                    
                    this._recordFailure('requirements');
                    throw new Error(result.error?.message || '上传失败');
                } catch (e) {
                    console.error('[CloudSync] Requirements full upload failed:', e);
                    this._recordFailure('requirements');
                    return null;
                }
            }

            /**
             * 兼容旧调用 - 重定向到增量同步
             * @deprecated 使用 syncTodayRequirements 或 uploadRequirementsFull
             */
            async uploadRequirements() {
                // 获取今天的记录并进行增量同步
                const history = this._historyMgr?.getHistory() || [];
                const today = new Date().toDateString();
                const todayRecord = history.find(h => new Date(h.ts).toDateString() === today);
                return this.syncTodayRequirements(todayRecord);
            }

            /**
             * 页面加载时同步升级要求数据
             * 仅 trust_level >= 2 的用户可用
             * 
             * 优化策略(v3.3.1):
             * 1. 增量同步:默认只同步当天数据(1小时间隔)
             * 2. 全量同步:仅在以下情况触发(12小时间隔):
             *    - 首次登录(从未下载过云端数据)
             *    - 本地数据天数与云端不一致
             */
            async syncRequirementsOnLoad() {
                // 前置检查:登录状态 + 只有领导者标签页执行
                if (!this.oauth.isLoggedIn() || !this._historyMgr) return;
                if (!TabLeader.isLeader()) return;

                const now = Date.now();
                const localHistory = this._historyMgr.getHistory();
                const INCREMENTAL_INTERVAL = CONFIG.INTERVALS.REQ_SYNC_INCREMENTAL || 3600000; // 1小时
                const FULL_INTERVAL = CONFIG.INTERVALS.REQ_SYNC_FULL || 43200000; // 12小时
                
                // ========== 判断是否需要全量同步 ==========
                const isFirstTime = this._reqLastDownload === 0;
                const needFullSync = isFirstTime || (now - (this._reqLastFullSync || 0)) > FULL_INTERVAL;
                
                if (needFullSync) {
                    // 1. 先下载云端数据
                    const downloadResult = await this.downloadRequirements();
                    if (downloadResult) {
                        this._reqLastDownload = now;
                        this.storage.setGlobalNow('lastReqDownload', now);
                        
                        // 2. 如果本地有数据且云端数据较少,上传本地数据
                        const cloudDays = downloadResult.merged || 0;
                        const localDays = localHistory.length;
                        
                        if (localDays > 0 && (isFirstTime || localDays > cloudDays)) {
                            const uploadResult = await this.uploadRequirementsFull();
                            if (uploadResult) {
                                this._reqLastFullSync = now;
                                this.storage.setGlobalNow('lastReqFullSync', now);
                            }
                        } else {
                            this._reqLastFullSync = now;
                            this.storage.setGlobalNow('lastReqFullSync', now);
                        }
                    }
                    return;
                }
                
                // ========== 增量同步:只同步当天数据 ==========
                const lastIncremental = this._reqLastIncrementalSync || 0;
                if ((now - lastIncremental) < INCREMENTAL_INTERVAL) {
                    return;
                }
                
                // 获取今天的记录
                const today = new Date().toDateString();
                const todayRecord = localHistory.find(h => new Date(h.ts).toDateString() === today);
                
                if (todayRecord) {
                    await this.syncTodayRequirements(todayRecord);
                }
            }

            /**
             * 获取系统公告(公开接口,不需要登录)
             * @returns {Promise<{enabled: boolean, content: string, type: string}|null>}
             */
            async getAnnouncement() {
                try {
                    const response = await fetch(`${CONFIG.LEADERBOARD_API}/api/config/announcement`);
                    if (!response.ok) return null;
                    const result = await response.json();
                    if (result.success && result.data) {
                        return result.data;
                    }
                    return null;
                } catch (e) {
                    console.error('[CloudSync] Get announcement failed:', e);
                    return null;
                }
            }

            /**
             * 获取官网URL(公开接口,不需要登录)
             * 每天最多请求一次,使用本地缓存
             * @returns {Promise<string>} 官网URL
             */
            async getWebsiteUrl() {
                const DEFAULT_URL = 'https://ldspro.qzz.io/';
                const today = new Date().toISOString().slice(0, 10); // YYYY-MM-DD
                
                try {
                    // 检查今日是否已请求过
                    const cachedDate = GM_getValue('ldsp_website_url_date', null);
                    const cachedUrl = GM_getValue('ldsp_website_url', null);
                    
                    if (cachedDate === today && cachedUrl) {
                        return cachedUrl;
                    }
                    
                    // 今日未请求,发起请求
                    const response = await fetch(`${CONFIG.LEADERBOARD_API}/api/config/website-url`);
                    if (!response.ok) {
                        // 请求失败,返回缓存或默认值
                        return cachedUrl || DEFAULT_URL;
                    }
                    
                    const result = await response.json();
                    if (result.success && result.data?.url) {
                        // 更新缓存
                        GM_setValue('ldsp_website_url', result.data.url);
                        GM_setValue('ldsp_website_url_date', today);
                        return result.data.url;
                    }
                    
                    return cachedUrl || DEFAULT_URL;
                } catch (e) {
                    console.error('[CloudSync] Get website URL failed:', e);
                    // 出错时返回缓存或默认值
                    const cachedUrl = GM_getValue('ldsp_website_url', null);
                    return cachedUrl || DEFAULT_URL;
                }
            }

            destroy() {
                this._timer && clearInterval(this._timer);
                this._timer = null;
            }
        }

        // ==================== 样式管理器 ====================
        const Styles = {
            _injected: false,

            inject() {
                if (this._injected) return;
                const cfg = Screen.getConfig();
                const style = document.createElement('style');
                style.id = 'ldsp-styles';
                style.textContent = this._css(cfg);
                document.head.appendChild(style);
                this._injected = true;
            },

            _css(c) {
                return `
    #ldsp-panel{--dur-fast:120ms;--dur:200ms;--dur-slow:350ms;--ease:cubic-bezier(.22,1,.36,1);--ease-circ:cubic-bezier(.85,0,.15,1);--ease-spring:cubic-bezier(.175,.885,.32,1.275);--ease-out:cubic-bezier(0,.55,.45,1);--bg:#12131a;--bg-card:rgba(24,26,36,.92);--bg-hover:rgba(38,42,56,.95);--bg-el:rgba(32,35,48,.88);--bg-glass:rgba(255,255,255,.02);--txt:#e4e6ed;--txt-sec:#9499ad;--txt-mut:#5d6275;--accent:#6b8cef;--accent-light:#8aa4f4;--accent2:#5bb5a6;--accent2-light:#7cc9bc;--accent3:#e07a8d;--grad:linear-gradient(135deg,#5a7de0 0%,#4a6bc9 100%);--grad-accent:linear-gradient(135deg,#4a6bc9,#3d5aaa);--grad-warm:linear-gradient(135deg,#e07a8d,#c9606e);--grad-gold:linear-gradient(135deg,#d4a853 0%,#c49339 100%);--ok:#5bb5a6;--ok-light:#7cc9bc;--ok-bg:rgba(91,181,166,.12);--err:#e07a8d;--err-light:#ea9aa8;--err-bg:rgba(224,122,141,.12);--warn:#d4a853;--warn-bg:rgba(212,168,83,.12);--border:rgba(255,255,255,.06);--border2:rgba(255,255,255,.1);--border-accent:rgba(107,140,239,.3);--border-panel:rgba(0,0,0,.25);--shadow:0 1.25rem 3rem rgba(0,0,0,.4);--shadow-lg:0 1.5rem 4rem rgba(0,0,0,.5),0 0 2rem rgba(107,140,239,.06);--shadow-glow:0 0 1.25rem rgba(107,140,239,.15);--glow-accent:0 0 1rem rgba(107,140,239,.2);--scrollbar:rgba(140,150,175,.5);--scrollbar-hover:rgba(140,150,175,.7);--r-xs:0.25em;--r-sm:0.5em;--r-md:0.75em;--r-lg:1em;--r-xl:1.25em;--w:${c.width}px;--h:${c.maxHeight}px;--fs:${c.fontSize}px;--pd:${c.padding}px;--av:${c.avatarSize}px;--ring:${c.ringSize}px;--min-w:220px;--max-w:420px;--min-h:300px;display:flex;flex-direction:column;position:fixed;left:0.5vw;top:${c.top}px;right:auto;width:var(--w);max-height:var(--h);min-width:var(--min-w);max-width:var(--max-w);min-height:var(--min-h);background:var(--bg);border-radius:var(--r-lg);font-family:'Inter',-apple-system,BlinkMacSystemFont,'Segoe UI','PingFang SC','Noto Sans SC',sans-serif;font-size:var(--fs);color:var(--txt);box-shadow:var(--shadow);z-index:99999;overflow:hidden;border:none;backdrop-filter:blur(20px);-webkit-backdrop-filter:blur(20px)}
    #ldsp-panel,#ldsp-panel *{transition:opacity var(--dur) var(--ease),transform var(--dur) var(--ease);user-select:none;-webkit-font-smoothing:antialiased}
    #ldsp-panel{transform:translateZ(0);backface-visibility:hidden}
    #ldsp-panel input,#ldsp-panel textarea{cursor:text;user-select:text}
    #ldsp-panel [data-clickable],#ldsp-panel [data-clickable] *,#ldsp-panel button,#ldsp-panel a,#ldsp-panel .ldsp-tab,#ldsp-panel .ldsp-subtab,#ldsp-panel .ldsp-ring-lvl,#ldsp-panel .ldsp-rd-day-bar,#ldsp-panel .ldsp-year-cell:not(.empty),#ldsp-panel .ldsp-rank-item,#ldsp-panel .ldsp-ticket-item,#ldsp-panel .ldsp-ticket-type,#ldsp-panel .ldsp-ticket-tab,#ldsp-panel .ldsp-ticket-close,#ldsp-panel .ldsp-ticket-back,#ldsp-panel .ldsp-lb-refresh,#ldsp-panel .ldsp-modal-btn,#ldsp-panel .ldsp-lb-btn,#ldsp-panel .ldsp-update-bubble-close{cursor:pointer}
    #ldsp-panel.no-trans,#ldsp-panel.no-trans *{transition:none!important;animation-play-state:paused!important}
    #ldsp-panel.anim{transition:width var(--dur-slow) var(--ease),height var(--dur-slow) var(--ease),min-width var(--dur-slow) var(--ease),min-height var(--dur-slow) var(--ease),max-width var(--dur-slow) var(--ease),max-height var(--dur-slow) var(--ease),top var(--dur-slow) var(--ease),border-radius var(--dur-slow) var(--ease);transform:none!important}
    #ldsp-panel.light{--bg:rgba(250,251,254,.97);--bg-card:rgba(245,247,252,.94);--bg-hover:rgba(238,242,250,.96);--bg-el:rgba(255,255,255,.94);--bg-glass:rgba(0,0,0,.012);--txt:#1e2030;--txt-sec:#4a5068;--txt-mut:#8590a6;--accent:#5070d0;--accent-light:#6b8cef;--accent2:#4a9e8f;--accent2-light:#5bb5a6;--ok:#4a9e8f;--ok-light:#5bb5a6;--ok-bg:rgba(74,158,143,.08);--err:#d45d6e;--err-light:#e07a8d;--err-bg:rgba(212,93,110,.08);--warn:#c49339;--warn-bg:rgba(196,147,57,.08);--border:rgba(0,0,0,.08);--border2:rgba(0,0,0,.1);--border-accent:rgba(80,112,208,.2);--border-panel:rgba(0,0,0,.1);--shadow:0 1.25rem 3rem rgba(0,0,0,.08);--shadow-lg:0 1.5rem 4rem rgba(0,0,0,.12);--glow-accent:0 0 1rem rgba(80,112,208,.1);--scrollbar:var(--accent);--scrollbar-hover:var(--accent-light)}
    #ldsp-panel.collapsed{width:48px!important;height:48px!important;min-width:48px!important;min-height:48px!important;max-height:48px!important;border-radius:var(--r-md);cursor:pointer;touch-action:none;background:linear-gradient(135deg,#7a9bf5 0%,#5a7de0 50%,#5bb5a6 100%);border:none;box-shadow:var(--shadow),0 0 20px rgba(107,140,239,.35)}
    #ldsp-panel.collapsed .ldsp-hdr{padding:0;justify-content:center;align-items:center;height:100%;background:0 0;min-height:0}
    #ldsp-panel.collapsed .ldsp-hdr-info{opacity:0;visibility:hidden;pointer-events:none;position:absolute;transform:translateX(-10px)}
    #ldsp-panel .ldsp-body{transition:max-height var(--dur-slow) var(--ease),opacity var(--dur-fast) var(--ease);opacity:1;max-height:calc(var(--h) - 120px);}
    #ldsp-panel.collapsed .ldsp-body{max-height:0!important;opacity:0;pointer-events:none;overflow:hidden}
    #ldsp-panel.collapsed .ldsp-hdr-btns>button:not(.ldsp-toggle){opacity:0;visibility:hidden;pointer-events:none;transform:scale(0.8);position:absolute}
    #ldsp-panel.collapsed .ldsp-hdr-btns{justify-content:center;width:100%;height:100%;margin-left:0}
    #ldsp-panel.collapsed,#ldsp-panel.collapsed *{cursor:pointer!important}
    #ldsp-panel.collapsed .ldsp-toggle{width:100%;height:100%;font-size:18px;background:0 0;display:flex;align-items:center;justify-content:center;color:#fff;position:absolute;inset:0;margin:0;padding:0;box-sizing:border-box}
    #ldsp-panel.collapsed .ldsp-toggle .ldsp-toggle-arrow{display:none}
    #ldsp-panel.collapsed .ldsp-toggle .ldsp-toggle-logo{display:block;width:24px;height:24px;filter:brightness(1.05) drop-shadow(0 0 2px rgba(140,180,255,.2));transition:filter .2s var(--ease),transform .2s var(--ease)}
    #ldsp-panel:not(.collapsed) .ldsp-toggle .ldsp-toggle-logo{display:none}
    @media (hover:hover){#ldsp-panel.collapsed:hover{transform:scale(1.08);box-shadow:var(--shadow-lg),0 0 35px rgba(120,160,255,.6)}#ldsp-panel.collapsed:hover .ldsp-toggle-logo{filter:brightness(1.6) drop-shadow(0 0 12px rgba(160,200,255,1)) drop-shadow(0 0 20px rgba(140,180,255,.8));transform:scale(1.15) rotate(360deg);transition:filter .3s var(--ease),transform .6s var(--ease-spring)}}
    #ldsp-panel.collapsed:active .ldsp-toggle-logo{filter:brightness(2) drop-shadow(0 0 16px rgba(200,230,255,1)) drop-shadow(0 0 30px rgba(160,200,255,1));transform:scale(0.92)}
    #ldsp-panel.collapsed.no-hover-effect{transform:none!important}#ldsp-panel.collapsed.no-hover-effect .ldsp-toggle-logo{filter:brightness(1.05) drop-shadow(0 0 2px rgba(140,180,255,.2))!important;transform:none!important}
    .ldsp-hdr{display:flex;align-items:center;padding:10px 12px;background:var(--grad);cursor:move;user-select:none;touch-action:none;position:relative;gap:8px;min-height:52px;box-sizing:border-box;flex-shrink:0}
    .ldsp-hdr::before{content:'';position:absolute;inset:0;background:linear-gradient(180deg,rgba(255,255,255,.1) 0%,transparent 100%);pointer-events:none}
    .ldsp-hdr::after{content:'';position:absolute;top:-50%;left:-50%;width:200%;height:200%;background:radial-gradient(circle,rgba(255,255,255,.1) 0%,transparent 60%);opacity:0;transition:opacity .5s;pointer-events:none}
    .ldsp-hdr:hover::after{opacity:1}
    .ldsp-hdr-info{display:flex;align-items:center;gap:8px;min-width:0;flex:1 1 auto;position:relative;z-index:1;transition:opacity .25s var(--ease),visibility .25s,transform .25s var(--ease);overflow:hidden}
    .ldsp-site-wrap{display:flex;flex-direction:column;align-items:center;gap:3px;flex-shrink:0;position:relative}
    .ldsp-site-icon{width:26px;height:26px;border-radius:7px;border:2px solid rgba(255,255,255,.25);flex-shrink:0;box-shadow:0 2px 8px rgba(0,0,0,.2)}
    .ldsp-hdr-text{display:flex;flex-direction:column;align-items:flex-start;gap:1px;min-width:0;flex:1 1 0;overflow:hidden}
    .ldsp-title{font-weight:800;font-size:14px;color:#fff;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.2;letter-spacing:-.02em;text-shadow:0 1px 2px rgba(0,0,0,.2);max-width:100%}
    .ldsp-ver{font-size:10px;color:rgba(255,255,255,.6);line-height:1.2;display:flex;align-items:center;gap:4px;overflow:hidden;max-width:100%}
    .ldsp-learn-trust{display:block;text-align:center;margin-top:8px;font-size:10px;color:var(--txt-dim);text-decoration:none;opacity:.6;transition:opacity .15s,color .15s}
    .ldsp-learn-trust:hover{opacity:1;color:var(--txt-sec)}
    .ldsp-app-name{font-size:10px;font-weight:700;white-space:nowrap;background:linear-gradient(90deg,#a8c0f8,#7a9eef,#7cc9bc,#7a9eef,#a8c0f8);background-size:200% auto;-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;animation:gradient-shift 6s ease infinite;will-change:background-position}
    @keyframes gradient-shift{0%{background-position:0% center}50%{background-position:100% center}100%{background-position:0% center}}
    .ldsp-ver-num{background:rgba(255,255,255,.2);padding:2px 8px;border-radius:10px;color:#fff;font-weight:600;font-size:9px;backdrop-filter:blur(4px)}
    .ldsp-site-ver{font-size:9px;color:#fff;text-align:center;font-weight:700;background:rgba(0,0,0,.25);padding:1px 5px;border-radius:5px;letter-spacing:.02em}
    .ldsp-hdr-btns{display:flex;gap:4px;flex-shrink:0;position:relative;z-index:1;margin-left:auto}
    .ldsp-hdr-btns button{width:28px;height:28px;border:none;background:rgba(255,255,255,.12);color:#fff;border-radius:var(--r-sm);font-size:12px;display:flex;align-items:center;justify-content:center;flex-shrink:0;outline:none;-webkit-tap-highlight-color:transparent;backdrop-filter:blur(4px);transition:transform .25s var(--ease),background .15s,box-shadow .2s,opacity .2s,visibility .2s}
    .ldsp-hdr-btns button:hover{background:rgba(255,255,255,.25);transform:translateY(-2px) scale(1.05);box-shadow:0 4px 12px rgba(0,0,0,.2)}
    .ldsp-hdr-btns button:active{transform:translateY(0) scale(.95)}
    .ldsp-hdr-btns button:focus{outline:none}
    .ldsp-hdr-btns button:disabled{opacity:.5;cursor:not-allowed;transform:none!important}
    .ldsp-hdr-btns button.has-update{background:linear-gradient(135deg,var(--ok),var(--ok-light));animation:pulse-update 3s ease-in-out infinite;position:relative;box-shadow:0 0 15px rgba(16,185,129,.4)}
    .ldsp-hdr-btns button.has-update::after{content:'';position:absolute;top:-3px;right:-3px;width:10px;height:10px;background:var(--err);border-radius:50%;border:2px solid rgba(0,0,0,.2);animation:pulse-dot 2.5s ease infinite}
    @keyframes pulse-update{0%,100%{transform:scale(1)}50%{transform:scale(1.05)}}
    @keyframes pulse-dot{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.15);opacity:.8}}
    .ldsp-update-bubble{position:absolute;top:52px;left:50%;transform:translateX(-50%) translateY(-10px);background:var(--bg-card);border:1px solid var(--border-accent);border-radius:var(--r-md);padding:16px 18px;text-align:center;z-index:100;box-shadow:var(--shadow-lg),var(--glow-accent);opacity:0;pointer-events:none;transition:transform .3s var(--ease-spring),opacity .3s var(--ease);max-width:calc(100% - 24px);width:220px;backdrop-filter:blur(16px);will-change:transform,opacity}
    .ldsp-update-bubble::before{content:'';position:absolute;top:-7px;left:50%;transform:translateX(-50%) rotate(45deg);width:12px;height:12px;background:var(--bg-card);border-left:1px solid var(--border-accent);border-top:1px solid var(--border-accent)}
    .ldsp-update-bubble.show{opacity:1;transform:translateX(-50%) translateY(0);pointer-events:auto}
    .ldsp-update-bubble-close{position:absolute;top:8px;right:10px;font-size:16px;color:var(--txt-mut);transition:color .15s,background .15s;line-height:1;width:20px;height:20px;display:flex;align-items:center;justify-content:center;border-radius:50%}
    .ldsp-update-bubble-close:hover{color:var(--txt);background:var(--bg-hover)}
    .ldsp-update-bubble-icon{font-size:28px;margin-bottom:8px;animation:bounce-in .5s var(--ease-spring)}
    @keyframes bounce-in{0%{transform:scale(0)}50%{transform:scale(1.2)}100%{transform:scale(1)}}
    .ldsp-update-bubble-title{font-size:13px;font-weight:700;margin-bottom:6px;color:var(--txt);letter-spacing:-.01em}
    .ldsp-update-bubble-ver{font-size:11px;margin-bottom:12px;color:var(--txt-sec)}
    .ldsp-update-bubble-btn{background:var(--grad);color:#fff;border:none;padding:8px 20px;border-radius:20px;font-size:12px;font-weight:600;transition:transform .2s var(--ease),box-shadow .2s;box-shadow:0 4px 15px rgba(107,140,239,.3)}
    .ldsp-update-bubble-btn:hover{transform:translateY(-2px) scale(1.02);box-shadow:0 6px 20px rgba(107,140,239,.4)}
    .ldsp-update-bubble-btn:active{transform:translateY(0) scale(.98)}
    .ldsp-update-bubble-btn:disabled{opacity:.6;cursor:not-allowed;transform:none!important}
    .ldsp-body{background:var(--bg);position:relative;overflow:hidden;display:flex;flex-direction:column;flex:1;min-height:0}
    .ldsp-announcement{overflow:hidden;background:linear-gradient(90deg,rgba(59,130,246,.1),rgba(107,140,239,.1));border-bottom:1px solid var(--border);padding:0;height:0;opacity:0;transition:height .3s var(--ease),opacity .3s,padding .3s;flex-shrink:0}
    .ldsp-announcement.active{height:24px;min-height:24px;opacity:1;padding:0 10px}
    .ldsp-announcement.warning{background:linear-gradient(90deg,rgba(245,158,11,.15),rgba(239,68,68,.08))}
    .ldsp-announcement.success{background:linear-gradient(90deg,rgba(16,185,129,.12),rgba(34,197,94,.08))}
    .ldsp-announcement-inner{display:flex;align-items:center;height:24px;white-space:nowrap;animation:marquee var(--marquee-duration,20s) linear var(--marquee-iteration,infinite)}
    .ldsp-announcement-inner:hover{animation-play-state:paused}
    .ldsp-announcement-text{font-size:11px;font-weight:500;color:var(--txt-sec);display:flex;align-items:center;gap:6px;padding-right:50px}
    .ldsp-announcement-text::before{content:'📢';font-size:12px}
    .ldsp-announcement.warning .ldsp-announcement-text::before{content:'⚠️'}
    .ldsp-announcement.success .ldsp-announcement-text::before{content:'🎉'}
    @keyframes marquee{0%{transform:translateX(var(--start-x,100%))}100%{transform:translateX(var(--end-x,-100%))}}
    .ldsp-user{display:flex;align-items:stretch;gap:10px;padding:10px var(--pd) 24px;background:var(--bg-card);border-bottom:1px solid var(--border);position:relative;overflow:hidden;flex-shrink:0}
    .ldsp-user::before{content:'';position:absolute;top:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,var(--accent),transparent);opacity:.3}
    .ldsp-user-left{display:flex;flex-direction:column;flex:1;min-width:0;gap:8px;justify-content:center}
    .ldsp-user-row{display:flex;align-items:center;gap:10px}
    .ldsp-user-actions{display:flex;flex-wrap:wrap;gap:6px;margin-top:2px;position:relative}
    .ldsp-user-actions.collapsed{max-height:24px;overflow:hidden}
    .ldsp-user-actions-wrap{display:flex;flex-direction:column;gap:4px;flex:1;min-width:0}
    .ldsp-user-actions-toggle{display:none;align-items:center;justify-content:center;gap:3px;padding:4px 10px;background:transparent;border:none;border-radius:6px;font-size:9px;color:var(--txt-mut);cursor:pointer;transition:all .15s;opacity:.7}
    .ldsp-user-actions-toggle:hover{background:var(--bg-hover);color:var(--accent);opacity:1}
    .ldsp-user-actions-toggle.show{display:inline-flex}
    .ldsp-user-actions-toggle svg{width:10px;height:10px;stroke-width:2;transition:transform .2s}
    .ldsp-user-actions-toggle.expanded{color:var(--accent);opacity:1}
    .ldsp-user-actions-toggle.expanded svg{transform:rotate(180deg)}
    .ldsp-avatar{width:var(--av);height:var(--av);border-radius:12px;border:2px solid var(--accent);flex-shrink:0;background:var(--bg-el);position:relative;box-shadow:0 4px 12px rgba(107,140,239,.2);transition:transform .3s var(--ease),box-shadow .3s,border-color .2s}
    .ldsp-avatar:hover{transform:scale(1.08) rotate(-3deg);border-color:var(--accent-light);box-shadow:0 6px 20px rgba(107,140,239,.35),var(--glow-accent)}
    .ldsp-avatar-ph{width:var(--av);height:var(--av);border-radius:12px;background:var(--grad);display:flex;align-items:center;justify-content:center;font-size:18px;color:#fff;flex-shrink:0;transition:transform .3s var(--ease),box-shadow .3s;position:relative;box-shadow:0 4px 12px rgba(107,140,239,.25)}
    .ldsp-avatar-ph:hover{transform:scale(1.08) rotate(-3deg);box-shadow:0 6px 20px rgba(107,140,239,.4)}
    .ldsp-avatar-wrap{position:relative;flex-shrink:0}
    .ldsp-avatar-wrap::after{content:'🔗 GitHub';position:absolute;bottom:-20px;left:50%;transform:translateX(-50%) translateY(4px);background:var(--bg-el);color:var(--txt-sec);padding:3px 8px;border-radius:6px;font-size:8px;white-space:nowrap;opacity:0;pointer-events:none;transition:transform .2s var(--ease),opacity .2s;border:1px solid var(--border2);box-shadow:0 4px 12px rgba(0,0,0,.2)}
    .ldsp-avatar-wrap:hover::after{opacity:1;transform:translateX(-50%) translateY(0)}
    .ldsp-user-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
    .ldsp-user-display-name{font-weight:700;font-size:16px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;line-height:1.3;letter-spacing:-.01em;background:linear-gradient(135deg,var(--txt) 0%,var(--txt-sec) 100%);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text}
    .ldsp-user-handle{font-size:12px;color:var(--txt-mut);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:500}
    .ldsp-user.not-logged .ldsp-avatar,.ldsp-user.not-logged .ldsp-avatar-ph{border:2px dashed var(--warn);animation:pulse-border 3s ease infinite}
    @keyframes pulse-border{0%,100%{border-color:var(--warn)}50%{border-color:rgba(245,158,11,.5)}}
    @keyframes pulse-border-red{0%,100%{border-color:#ef4444}50%{border-color:rgba(239,68,68,.4)}}
    .ldsp-user.not-logged .ldsp-user-display-name{color:var(--warn);-webkit-text-fill-color:var(--warn)}
    .ldsp-login-hint{font-size:9px;color:var(--warn);margin-left:4px;animation:blink 2.5s ease-in-out infinite;background:var(--warn-bg);padding:2px 6px;border-radius:8px;font-weight:500}
    @keyframes blink{0%,100%{opacity:1}50%{opacity:.7}}
    .ldsp-user-meta{display:flex;align-items:center;gap:8px;margin-top:3px}

    .ldsp-reading{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:8px;border-radius:var(--r-md);aspect-ratio:0.92/1;min-width:100px;max-width:250px;align-self:stretch;position:relative;overflow:visible;border:1px solid var(--border);transition:background .2s,border-color .2s,box-shadow .3s;flex-shrink:0;box-sizing:border-box}
    .ldsp-reading::before{content:'';position:absolute;inset:0;border-radius:inherit;background:linear-gradient(180deg,rgba(255,255,255,.05) 0%,transparent 100%);pointer-events:none}
    .ldsp-reading-icon{font-size:20px;margin-bottom:3px;animation:bounce 3s ease-in-out infinite;filter:drop-shadow(0 2px 4px rgba(0,0,0,.2));will-change:transform}
    @keyframes bounce{0%,100%{transform:translateY(0)}50%{transform:translateY(-3px)}}
    .ldsp-reading-time{font-size:13px;font-weight:800;letter-spacing:-.02em}
    .ldsp-reading-label{font-size:9px;opacity:.85;margin-top:2px;font-weight:600;letter-spacing:.02em}
    .ldsp-reading{--rc:#94a3b8}
    .ldsp-reading::after{content:'未活动 已停止记录';position:absolute;bottom:-16px;left:50%;transform:translateX(-50%);font-size:8px;color:var(--err);white-space:nowrap;font-weight:600;letter-spacing:.02em;opacity:.8}
    .ldsp-reading.tracking{animation:reading-glow 3.5s ease-in-out infinite;will-change:box-shadow}
    .ldsp-reading.tracking::after{content:'阅读时间记录中...';color:var(--rc);opacity:1}
    @keyframes reading-glow{0%,100%{box-shadow:0 0 8px color-mix(in srgb,var(--rc) 40%,transparent),0 0 16px color-mix(in srgb,var(--rc) 20%,transparent),0 0 24px color-mix(in srgb,var(--rc) 10%,transparent)}50%{box-shadow:0 0 16px color-mix(in srgb,var(--rc) 60%,transparent),0 0 32px color-mix(in srgb,var(--rc) 35%,transparent),0 0 48px color-mix(in srgb,var(--rc) 15%,transparent)}}
    .ldsp-reading-ripple{position:absolute;inset:-2px;border-radius:inherit;pointer-events:none;z-index:-1;opacity:0}
    .ldsp-reading.tracking .ldsp-reading-ripple{opacity:1}
    .ldsp-reading.tracking .ldsp-reading-ripple::before,.ldsp-reading.tracking .ldsp-reading-ripple::after{content:'';position:absolute;inset:0;border-radius:inherit;border:2px solid var(--rc);opacity:.5;animation:ripple-expand 4s ease-out infinite;will-change:transform,opacity}
    .ldsp-reading.tracking .ldsp-reading-ripple::after{animation-delay:2s}
    @keyframes ripple-expand{0%{transform:scale(1);opacity:.5;border-width:2px}100%{transform:scale(1.4);opacity:0;border-width:1px}}
    .ldsp-reading.hi{box-shadow:0 0 20px rgba(249,115,22,.2)}
    .ldsp-reading.hi .ldsp-reading-icon{animation:fire 1.2s ease-in-out infinite;will-change:transform}
    @keyframes fire{0%,100%{transform:scale(1)}50%{transform:scale(1.1)}}
    .ldsp-reading.max{box-shadow:0 0 25px rgba(236,72,153,.25)}
    .ldsp-reading.max .ldsp-reading-icon{animation:crown 2s ease-in-out infinite;will-change:transform}
    @keyframes crown{0%,100%{transform:rotate(-5deg) scale(1)}50%{transform:rotate(5deg) scale(1.1)}}

    .ldsp-tabs{display:flex;padding:8px 10px;gap:6px;background:var(--bg);border-bottom:1px solid var(--border);flex-shrink:0;container-type:inline-size}
    .ldsp-tab{flex:1;padding:7px 8px;border:none;background:var(--bg-card);color:var(--txt-sec);border-radius:var(--r-sm);font-size:11px;font-weight:600;transition:background .15s,color .15s,border-color .15s,box-shadow .2s;border:1px solid transparent;white-space:nowrap;display:flex;align-items:center;justify-content:center;gap:4px;min-width:0;overflow:hidden}
    .ldsp-tab .ldsp-tab-icon{flex-shrink:0;font-size:12px}
    .ldsp-tab .ldsp-tab-text{overflow:hidden;text-overflow:ellipsis;min-width:1em}
    .ldsp-tab:hover{background:var(--bg-hover);color:var(--txt);border-color:var(--border2);transform:translateY(-1px)}
    .ldsp-tab.active{background:var(--grad);color:#fff;box-shadow:0 4px 15px rgba(107,140,239,.3);border-color:transparent}
    @container (max-width:260px){.ldsp-tab{font-size:10px;padding:6px 5px;gap:2px}.ldsp-tab .ldsp-tab-icon{display:none}}
    @container (max-width:200px){.ldsp-tab{font-size:9px;padding:5px 3px}}
    @media (max-width:340px){.ldsp-tabs{padding:6px 8px;gap:4px}.ldsp-tab{font-size:10px;padding:6px 6px;gap:2px}.ldsp-tab .ldsp-tab-icon{display:none}}
    @media (max-width:280px){.ldsp-tabs{padding:5px 6px;gap:3px}.ldsp-tab{font-size:9px;padding:5px 4px}}
    .ldsp-content{flex:1 1 auto;min-height:0;max-height:calc(var(--h) - 180px);overflow-y:auto;scrollbar-width:thin;scrollbar-color:transparent transparent}
    .ldsp-content.scrolling{scrollbar-color:var(--scrollbar) transparent}
    .ldsp-content::-webkit-scrollbar{width:6px;background:transparent}
    .ldsp-content::-webkit-scrollbar-track{background:transparent}
    .ldsp-content::-webkit-scrollbar-thumb{background:transparent;border-radius:4px;transition:background .3s}
    .ldsp-content.scrolling::-webkit-scrollbar-thumb{background:var(--scrollbar)}
    .ldsp-content::-webkit-scrollbar-button{width:0;height:0;display:none}
    .ldsp-section{display:none;padding:10px}
    .ldsp-section.active{display:block;animation:enter var(--dur) var(--ease-out)}
    @keyframes enter{from{opacity:0;transform:translateY(12px)}to{opacity:1;transform:none}}
    .ldsp-ring{display:flex;align-items:center;justify-content:space-between;padding:14px 16px;background:var(--bg-card);border-radius:var(--r-md);margin-bottom:10px;position:relative;overflow:hidden;border:1px solid var(--border);gap:12px}
    .ldsp-ring::before{content:'';position:absolute;inset:0;background:radial-gradient(circle at 50% 0%,rgba(107,140,239,.08) 0%,transparent 70%);pointer-events:none}
    .ldsp-ring-stat{display:flex;flex-direction:column;align-items:center;justify-content:center;min-width:50px;gap:4px;z-index:1}
    .ldsp-ring-stat-val{font-size:18px;font-weight:800;letter-spacing:-.02em}
    .ldsp-ring-stat-val.ok{color:var(--ok)}
    .ldsp-ring-stat-val.fail{color:var(--err)}
    .ldsp-ring-stat-lbl{font-size:9px;color:var(--txt-mut);font-weight:500;white-space:nowrap}
    .ldsp-ring-center{display:flex;flex-direction:column;align-items:center;position:relative}
    .ldsp-ring-wrap{position:relative;width:var(--ring);height:var(--ring)}
    .ldsp-ring-wrap svg{transform:rotate(-90deg);width:100%;height:100%;overflow:visible}
    .ldsp-ring-bg{fill:none;stroke:var(--bg-el);stroke-width:7}
    .ldsp-ring-fill{fill:none;stroke:url(#ldsp-grad);stroke-width:7;stroke-linecap:round;transition:stroke-dashoffset 1s var(--ease)}
    .ldsp-ring-fill.anim{animation:ring 1.5s var(--ease) forwards}
    @keyframes ring{from{stroke-dashoffset:var(--circ)}to{stroke-dashoffset:var(--off)}}
    .ldsp-ring-txt{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);text-align:center}
    .ldsp-ring-val{font-size:clamp(12px,calc(var(--ring) * 0.2),18px);font-weight:800;background:var(--grad);-webkit-background-clip:text;-webkit-text-fill-color:transparent;background-clip:text;letter-spacing:-.02em}
    .ldsp-ring-val.anim{animation:val 1s var(--ease-spring) .5s forwards;opacity:0}
    @keyframes val{from{opacity:0;transform:scale(.6)}60%{transform:scale(1.1)}to{opacity:1;transform:scale(1)}}
    .ldsp-ring-lbl{font-size:9px;color:var(--txt-mut);margin-top:2px;font-weight:500}
    .ldsp-ring-lvl{font-size:12px;font-weight:700;margin-top:8px;padding:4px 14px;border-radius:12px;background-image:linear-gradient(90deg,#64748b 0%,#94a3b8 50%,#64748b 100%);background-size:200% 100%;background-position:0% 50%;color:#fff;box-shadow:0 2px 10px rgba(100,116,139,.35);letter-spacing:.03em;text-shadow:0 1px 2px rgba(0,0,0,.2);transition:transform 2s ease;transform-style:preserve-3d;animation:lvl-shimmer 6s ease-in-out infinite;will-change:background-position}
    .ldsp-ring-lvl:hover{transform:rotateY(360deg);animation-play-state:paused}
    .ldsp-ring-lvl.lv1{background-image:linear-gradient(90deg,#64748b 0%,#94a3b8 50%,#64748b 100%);box-shadow:0 2px 10px rgba(100,116,139,.35);animation-duration:4s}
    .ldsp-ring-lvl.lv2{background-image:linear-gradient(90deg,#3b82f6 0%,#60a5fa 50%,#3b82f6 100%);box-shadow:0 2px 10px rgba(59,130,246,.4);animation-duration:3.5s}
    .ldsp-ring-lvl.lv3{background-image:linear-gradient(90deg,#5070d0 0%,#8aa4f4 30%,#5bb5a6 70%,#5070d0 100%);box-shadow:0 2px 12px rgba(107,140,239,.45);animation-duration:3s}
    .ldsp-ring-lvl.lv4{background-image:linear-gradient(90deg,#f59e0b 0%,#fbbf24 25%,#f97316 50%,#ef4444 75%,#f59e0b 100%);box-shadow:0 2px 15px rgba(245,158,11,.5),0 0 20px rgba(249,115,22,.3);animation-duration:2.5s;animation-name:lvl-shimmer-gold}
    @keyframes lvl-shimmer{0%,100%{background-position:0% 50%}50%{background-position:100% 50%}}
    @keyframes lvl-shimmer-gold{0%,100%{background-position:0% 50%;filter:brightness(1)}50%{background-position:100% 50%;filter:brightness(1.2)}}
    .ldsp-confetti{position:absolute;width:100%;height:100%;top:0;left:0;pointer-events:none;overflow:visible;z-index:10}
    .ldsp-confetti-piece{position:absolute;font-size:12px;opacity:0;top:42%;left:50%;transform-origin:center center;text-shadow:0 1px 3px rgba(0,0,0,.3)}
    .ldsp-ring.complete.anim-done .ldsp-confetti-piece{animation:confetti-burst 2s cubic-bezier(.15,.8,.3,1) forwards}
    @keyframes confetti-burst{0%{opacity:1;transform:translate(-50%,-50%) scale(0)}5%{opacity:1;transform:translate(-50%,-50%) scale(1.5)}25%{opacity:1;transform:translate(calc(var(--tx) * 1.2),calc(var(--ty) * 1.2)) rotate(calc(var(--rot) * 0.4)) scale(1.1)}100%{opacity:0;transform:translate(calc(var(--tx) + var(--drift)),calc(var(--ty) + 110px)) rotate(var(--rot)) scale(0.2)}}
    .ldsp-ring-tip{font-size:11px;text-align:center;margin:12px 0 16px;padding:8px 14px;border-radius:20px;font-weight:600;letter-spacing:.02em}
    .ldsp-ring-tip.ok{color:var(--ok);background:linear-gradient(135deg,var(--ok-bg),rgba(16,185,129,.05));border:1px solid rgba(16,185,129,.2)}
    .ldsp-ring-tip.progress{color:var(--accent);background:linear-gradient(135deg,rgba(107,140,239,.1),rgba(6,182,212,.05));border:1px solid rgba(107,140,239,.2)}
    .ldsp-ring-tip.max{color:var(--warn);background:linear-gradient(135deg,rgba(251,191,36,.1),rgba(249,115,22,.05));border:1px solid rgba(251,191,36,.25)}
    .ldsp-item{display:flex;align-items:center;padding:8px 10px;margin-bottom:6px;background:var(--bg-card);border-radius:var(--r-sm);border-left:3px solid var(--border2);animation:item var(--dur) var(--ease-out) backwards;transition:background .15s,border-color .15s,transform .2s var(--ease);border:1px solid var(--border);border-left-width:3px}
    .ldsp-item:nth-child(1){animation-delay:0ms}.ldsp-item:nth-child(2){animation-delay:25ms}.ldsp-item:nth-child(3){animation-delay:50ms}.ldsp-item:nth-child(4){animation-delay:75ms}.ldsp-item:nth-child(5){animation-delay:100ms}.ldsp-item:nth-child(6){animation-delay:125ms}.ldsp-item:nth-child(7){animation-delay:150ms}.ldsp-item:nth-child(8){animation-delay:175ms}.ldsp-item:nth-child(9){animation-delay:200ms}.ldsp-item:nth-child(10){animation-delay:225ms}.ldsp-item:nth-child(11){animation-delay:250ms}.ldsp-item:nth-child(12){animation-delay:275ms}
    @keyframes item{from{opacity:0;transform:translateX(-15px)}to{opacity:1;transform:none}}
    .ldsp-item:hover{background:var(--bg-hover);transform:translateX(4px);box-shadow:0 4px 12px rgba(0,0,0,.1)}
    .ldsp-item.ok{border-left-color:var(--ok);background:linear-gradient(135deg,var(--ok-bg) 0%,transparent 100%)}
    .ldsp-item.fail{border-left-color:var(--err);background:linear-gradient(135deg,var(--err-bg) 0%,transparent 100%)}
    .ldsp-item-icon{font-size:12px;margin-right:8px;width:18px;height:18px;display:flex;align-items:center;justify-content:center;border-radius:50%;background:var(--bg-el)}
    .ldsp-item.ok .ldsp-item-icon{background:var(--ok-bg);color:var(--ok)}
    .ldsp-item.fail .ldsp-item-icon{background:var(--err-bg);color:var(--err)}
    .ldsp-item-name{flex:1;font-size:11px;color:var(--txt-sec);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:500}
    .ldsp-item.ok .ldsp-item-name{color:var(--ok)}
    .ldsp-item-vals{display:flex;align-items:center;gap:3px;font-size:12px;font-weight:700;margin-left:8px}
    .ldsp-item-cur{color:var(--txt);transition:color .2s}
    .ldsp-item-cur.upd{animation:upd .7s var(--ease-spring)}
    @keyframes upd{0%{transform:scale(1)}30%{transform:scale(1.3);background:var(--accent);color:#fff;border-radius:6px;padding:0 4px}100%{transform:scale(1)}}
    .ldsp-item.ok .ldsp-item-cur{color:var(--ok)}
    .ldsp-item.fail .ldsp-item-cur{color:var(--err)}
    .ldsp-item-sep{color:var(--txt-mut);font-weight:400;opacity:.6}
    .ldsp-item-req{color:var(--txt-mut);font-weight:500}
    .ldsp-item-chg{font-size:10px;padding:2px 6px;border-radius:6px;font-weight:700;margin-left:6px;animation:pop var(--dur) var(--ease-spring)}
    @keyframes pop{from{transform:scale(0) rotate(-10deg);opacity:0}to{transform:scale(1) rotate(0);opacity:1}}
    .ldsp-item-chg.up{background:var(--ok-bg);color:var(--ok);box-shadow:0 2px 8px rgba(16,185,129,.2)}
    .ldsp-item-chg.down{background:var(--err-bg);color:var(--err);box-shadow:0 2px 8px rgba(244,63,94,.2)}
    .ldsp-subtabs{display:flex;align-items:center;gap:6px;padding:6px 10px;overflow-x:auto;scrollbar-width:thin;scrollbar-color:var(--scrollbar) transparent;-webkit-overflow-scrolling:touch}
    .ldsp-subtabs::-webkit-scrollbar{height:6px;background:var(--bg-el);border-radius:3px}
    .ldsp-subtabs::-webkit-scrollbar-track{background:var(--bg-el);border-radius:3px}
    .ldsp-subtabs::-webkit-scrollbar-thumb{background:var(--scrollbar);border-radius:3px;transition:background .3s}
    .ldsp-subtabs::-webkit-scrollbar-thumb:hover{background:var(--accent)}
    .ldsp-subtabs::-webkit-scrollbar-button{width:0;height:0;display:none}
    .ldsp-subtab{padding:6px 12px;border:1px solid var(--border2);background:var(--bg-card);color:var(--txt-sec);border-radius:20px;font-size:10px;font-weight:600;white-space:nowrap;flex-shrink:0;transition:background .15s,color .15s,border-color .15s}
    .ldsp-subtab:hover{border-color:var(--accent);color:var(--accent);background:rgba(107,140,239,.08);transform:translateY(-1px)}
    .ldsp-subtab.active{background:var(--grad);border-color:transparent;color:#fff;box-shadow:0 4px 12px rgba(107,140,239,.25)}
    .ldsp-chart{background:var(--bg-card);border-radius:var(--r-md);padding:12px;margin-bottom:10px;border:1px solid var(--border);position:relative;overflow:hidden}
    .ldsp-chart::before{content:'';position:absolute;top:0;left:0;right:0;height:1px;background:linear-gradient(90deg,transparent,var(--accent),transparent);opacity:.2}
    .ldsp-chart:last-child{margin-bottom:0}
    .ldsp-chart-title{font-size:12px;font-weight:700;margin-bottom:12px;display:flex;align-items:center;gap:6px;color:var(--txt)}
    .ldsp-chart-sub{font-size:10px;color:var(--txt-mut);font-weight:500;margin-left:auto}
    .ldsp-spark-row{display:flex;align-items:center;gap:8px;margin-bottom:10px}
    .ldsp-spark-row:last-child{margin-bottom:0}
    .ldsp-spark-lbl{width:55px;font-size:10px;color:var(--txt-sec);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:600}
    .ldsp-spark-bars{flex:1;display:flex;align-items:flex-end;gap:3px;height:24px}
    .ldsp-spark-bar{flex:1;background:linear-gradient(180deg,var(--accent),var(--accent2));border-radius:3px 3px 0 0;min-height:3px;opacity:.35;position:relative;transition:opacity .2s,height .2s var(--ease)}
    .ldsp-spark-bar:last-child,.ldsp-spark-bar.ldsp-spark-current{opacity:1}
    .ldsp-spark-bar:hover{opacity:1;transform:scaleY(1.15);box-shadow:0 -4px 12px rgba(107,140,239,.3)}
    .ldsp-spark-bar::after{content:attr(data-v);position:absolute;bottom:100%;left:50%;transform:translateX(-50%) translateY(5px);font-size:9px;background:var(--bg-card);color:var(--txt);padding:4px 8px;border-radius:6px;opacity:0;visibility:hidden;white-space:nowrap;pointer-events:none;border:1px solid var(--border);box-shadow:0 4px 12px rgba(0,0,0,.15);transition:transform .15s var(--ease),opacity .15s,visibility .15s;z-index:100;font-weight:500}
    .ldsp-spark-bar:hover::after{opacity:1;visibility:visible;transform:translateX(-50%) translateY(-4px)}
    .ldsp-spark-val{font-size:11px;font-weight:700;min-width:35px;text-align:right;color:var(--accent)}
    .ldsp-date-labels{display:flex;justify-content:space-between;padding:8px 0 0 60px;margin-right:40px}
    .ldsp-date-lbl{font-size:9px;color:var(--txt-mut);text-align:center;font-weight:500}
    .ldsp-changes{margin-top:8px}
    .ldsp-chg-row{display:flex;justify-content:space-between;align-items:center;padding:8px 0;border-bottom:1px solid var(--border);transition:background .15s}
    .ldsp-chg-row:hover{background:var(--bg-glass);margin:0 -6px;padding:8px 6px;border-radius:var(--r-xs)}
    .ldsp-chg-row:last-child{border-bottom:none}
    .ldsp-chg-name{font-size:11px;color:var(--txt-sec);flex:1;font-weight:500}
    .ldsp-chg-cur{font-size:10px;color:var(--txt-mut);margin-right:8px}
    .ldsp-chg-val{font-size:11px;font-weight:700;padding:3px 8px;border-radius:6px}
    .ldsp-chg-val.up{background:var(--ok-bg);color:var(--ok)}
    .ldsp-chg-val.down{background:var(--err-bg);color:var(--err)}
    .ldsp-chg-val.neu{background:var(--bg-el);color:var(--txt-mut)}
    .ldsp-rd-stats{border-radius:var(--r-md);padding:14px;margin-bottom:10px;display:flex;align-items:center;gap:12px;border:1px solid var(--border)}
    .ldsp-rd-stats-icon{font-size:32px;flex-shrink:0;filter:drop-shadow(0 2px 8px rgba(0,0,0,.2))}
    .ldsp-rd-stats-info{flex:1}
    .ldsp-rd-stats-val{font-size:18px;font-weight:800;background:var(--grad);-webkit-background-clip:text;-webkit-text-fill-color:transparent;letter-spacing:-.02em}
    .ldsp-rd-stats-lbl{font-size:10px;color:var(--txt-mut);margin-top:3px;font-weight:500}
    .ldsp-rd-stats-badge{padding:5px 12px;border-radius:12px;font-size:10px;font-weight:700;transform:translateY(-1px);transition:transform .15s,box-shadow .15s}
    .ldsp-track{display:flex;align-items:center;gap:8px;padding:8px 10px;background:var(--bg-card);border-radius:var(--r-sm);margin-bottom:10px;font-size:10px;color:var(--txt-mut);border:1px solid var(--border);font-weight:500}
    .ldsp-track-dot{width:8px;height:8px;border-radius:50%;background:var(--ok);animation:pulse 3s ease-in-out infinite;box-shadow:0 0 10px rgba(16,185,129,.4);will-change:opacity,transform}
    @keyframes pulse{0%,100%{opacity:1;transform:scale(1);box-shadow:0 0 10px rgba(16,185,129,.4)}50%{opacity:.7;transform:scale(.9);box-shadow:0 0 5px rgba(16,185,129,.2)}}
    @keyframes gradient-shift{0%{background-position:0% center}50%{background-position:100% center}100%{background-position:0% center}}
    .ldsp-rd-prog{background:var(--bg-card);border-radius:var(--r-md);padding:12px;margin-bottom:10px;border:1px solid var(--border)}
    .ldsp-rd-prog-hdr{display:flex;justify-content:space-between;align-items:center;margin-bottom:8px}
    .ldsp-rd-prog-title{font-size:11px;color:var(--txt-sec);font-weight:600}
    .ldsp-rd-prog-val{font-size:12px;font-weight:700;color:var(--accent)}
    .ldsp-rd-prog-bar{height:8px;background:var(--bg-el);border-radius:4px;overflow:hidden;box-shadow:inset 0 1px 3px rgba(0,0,0,.1)}
    .ldsp-rd-prog-fill{height:100%;border-radius:4px;transition:width .5s var(--ease);position:relative}
    .ldsp-rd-prog-fill::after{content:'';position:absolute;top:0;left:0;right:0;height:50%;background:linear-gradient(180deg,rgba(255,255,255,.2) 0%,transparent 100%);border-radius:4px 4px 0 0}
    .ldsp-rd-week{display:flex;justify-content:space-between;align-items:flex-end;height:55px;padding:0 4px;margin:12px 0 8px;gap:4px}
    .ldsp-rd-day{flex:1;display:flex;flex-direction:column;align-items:center;gap:4px;min-width:0}
    .ldsp-rd-day-bar{width:100%;max-width:18px;background:linear-gradient(180deg,var(--accent) 0%,var(--accent2) 100%);border-radius:4px 4px 0 0;min-height:3px;position:relative;transition:opacity .2s,height .2s var(--ease)}
    .ldsp-rd-day-bar:hover{transform:scaleX(1.2);box-shadow:0 -4px 15px rgba(91,181,166,.35)}
    .ldsp-rd-day-bar::after{content:attr(data-t);position:absolute;bottom:100%;left:50%;transform:translateX(-50%) translateY(5px);background:var(--bg-card);color:var(--txt);padding:4px 8px;border-radius:6px;font-size:9px;font-weight:500;white-space:nowrap;opacity:0;visibility:hidden;pointer-events:none;margin-bottom:4px;border:1px solid var(--border);box-shadow:0 4px 12px rgba(0,0,0,.15);transition:transform .15s var(--ease),opacity .15s,visibility .15s;z-index:100}
    .ldsp-rd-day-bar:hover::after{opacity:1;visibility:visible;transform:translateX(-50%) translateY(-4px)}
    .ldsp-rd-day-lbl{font-size:9px;color:var(--txt-mut);line-height:1;font-weight:500}
    .ldsp-today-stats{display:grid;grid-template-columns:repeat(2,1fr);gap:8px;margin-bottom:10px}
    .ldsp-today-stat{background:var(--bg-card);border-radius:var(--r-md);padding:12px 10px;text-align:center;border:1px solid var(--border);position:relative;overflow:hidden;transition:background .15s,border-color .15s}
    .ldsp-today-stat:hover{transform:translateY(-2px);box-shadow:0 8px 20px rgba(0,0,0,.1)}
    .ldsp-today-stat::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:var(--grad)}
    .ldsp-today-stat-val{font-size:18px;font-weight:800;background:var(--grad);-webkit-background-clip:text;-webkit-text-fill-color:transparent;letter-spacing:-.02em}
    .ldsp-today-stat-lbl{font-size:10px;color:var(--txt-mut);margin-top:4px;font-weight:500}
    .ldsp-time-info{font-size:10px;color:var(--txt-mut);text-align:center;padding:8px 10px;background:var(--bg-card);border-radius:var(--r-sm);margin-bottom:10px;border:1px solid var(--border);font-weight:500}
    .ldsp-time-info span{color:var(--accent);font-weight:700}
    .ldsp-year-heatmap{padding:10px 14px 10px 0;overflow-x:hidden;overflow-y:auto;max-height:320px;scrollbar-width:thin;scrollbar-color:transparent transparent}
    .ldsp-year-heatmap.scrolling{scrollbar-color:var(--scrollbar) transparent}
    .ldsp-year-heatmap::-webkit-scrollbar{width:6px;background:transparent}
    .ldsp-year-heatmap::-webkit-scrollbar-track{background:transparent}
    .ldsp-year-heatmap::-webkit-scrollbar-thumb{background:transparent;border-radius:4px;transition:background .3s}
    .ldsp-year-heatmap.scrolling::-webkit-scrollbar-thumb{background:var(--scrollbar)}
    .ldsp-year-heatmap::-webkit-scrollbar-button{width:0;height:0;display:none}
    .ldsp-year-wrap{display:flex;flex-direction:column;gap:3px;width:100%;padding-right:6px}
    .ldsp-year-row{display:flex;align-items:center;gap:4px;width:100%;position:relative}
    .ldsp-year-month{width:28px;font-size:8px;font-weight:600;color:var(--txt-mut);text-align:right;flex-shrink:0;line-height:1;position:absolute;left:0;top:50%;transform:translateY(-50%)}
    .ldsp-year-cells{display:grid;grid-template-columns:repeat(14,minmax(9px,1fr));gap:3px;width:100%;align-items:center;margin-left:32px}
    .ldsp-year-cell{width:100%;aspect-ratio:1;border-radius:3px;background:var(--bg-card);border:1px solid var(--border);position:relative;transition:transform .15s var(--ease),box-shadow .15s}
    .ldsp-year-cell:hover{transform:scale(1.6);box-shadow:0 4px 15px rgba(107,140,239,.4);border-color:var(--accent);z-index:10}
    .ldsp-year-cell.l0{background:rgba(107,140,239,.1);border-color:rgba(107,140,239,.18)}
    .ldsp-year-cell.l1{background:rgba(180,230,210,.35);border-color:rgba(180,230,210,.45)}
    .ldsp-year-cell.l2{background:rgba(130,215,180,.5);border-color:rgba(130,215,180,.6)}
    .ldsp-year-cell.l3{background:rgba(90,195,155,.65);border-color:rgba(90,195,155,.75)}
    .ldsp-year-cell.l4{background:linear-gradient(135deg,#6dcfa5,#50c090);border-color:#6dcfa5;box-shadow:0 0 8px rgba(109,207,165,.4)}
    .ldsp-year-cell.empty{background:0 0;border-color:transparent;cursor:default}
    .ldsp-year-cell.empty:hover{transform:none;box-shadow:none}
    .ldsp-year-tip{position:absolute;left:50%;transform:translateX(-50%);background:var(--bg-card);color:var(--txt);padding:4px 8px;border-radius:6px;font-size:9px;white-space:nowrap;opacity:0;visibility:hidden;pointer-events:none;border:1px solid var(--border);z-index:1000;line-height:1.3;box-shadow:0 4px 12px rgba(0,0,0,.15);font-weight:500;transition:opacity .15s,visibility .15s}
    .ldsp-year-cell:hover .ldsp-year-tip{opacity:1;visibility:visible}
    .ldsp-year-cell .ldsp-year-tip{bottom:100%;margin-bottom:4px}
    .ldsp-year-row:nth-child(-n+3) .ldsp-year-tip{bottom:auto;top:100%;margin-top:4px;margin-bottom:0}
    .ldsp-year-heatmap.few-rows .ldsp-year-tip{bottom:auto;top:100%;margin-top:4px;margin-bottom:0}
    .ldsp-year-heatmap.few-rows{overflow:visible;padding-bottom:40px}
    .ldsp-year-cell:nth-child(13) .ldsp-year-tip,.ldsp-year-cell:nth-child(14) .ldsp-year-tip{left:auto;right:0;transform:translateX(0)}
    .ldsp-heatmap-legend{display:flex;align-items:center;gap:6px;justify-content:center;font-size:9px;color:var(--txt-mut);padding:8px 0;font-weight:500}
    .ldsp-heatmap-legend-cell{width:10px;height:10px;border-radius:2px;border:1px solid var(--border)}
    .ldsp-empty,.ldsp-loading{text-align:center;padding:30px 16px;color:var(--txt-mut)}
    .ldsp-empty-icon{font-size:36px;margin-bottom:12px;filter:drop-shadow(0 2px 8px rgba(0,0,0,.1))}
    .ldsp-empty-txt{font-size:12px;line-height:1.7;font-weight:500}
    .ldsp-spinner{width:28px;height:28px;border:3px solid var(--border2);border-top-color:var(--accent);border-radius:50%;animation:spin 1s linear infinite;margin:0 auto 10px;will-change:transform}
    @keyframes spin{to{transform:rotate(360deg)}}
    .ldsp-mini-loader{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:50px 20px;color:var(--txt-mut)}
    .ldsp-mini-spin{width:32px;height:32px;border:3px solid var(--border2);border-top-color:var(--accent);border-radius:50%;animation:spin 1s linear infinite;margin-bottom:14px;will-change:transform}
    .ldsp-mini-txt{font-size:11px;font-weight:500}
    .ldsp-toast{position:absolute;bottom:-55px;left:50%;transform:translateX(-50%) translateY(15px);background:var(--grad);color:#fff;padding:10px 18px;border-radius:20px;font-size:12px;font-weight:600;box-shadow:0 8px 30px rgba(107,140,239,.4);opacity:0;white-space:nowrap;display:flex;align-items:center;gap:8px;z-index:100000;transition:transform .3s var(--ease-spring),opacity .3s;will-change:transform,opacity}
    .ldsp-toast.show{opacity:1;transform:translateX(-50%) translateY(0)}
    .ldsp-modal-overlay{position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.7);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);display:flex;align-items:center;justify-content:center;z-index:100001;opacity:0;transition:opacity .3s var(--ease)}
    .ldsp-modal-overlay.show{opacity:1}
    .ldsp-modal{background:var(--bg-card);border-radius:var(--r-xl);padding:24px;max-width:340px;width:90%;box-shadow:var(--shadow-lg),var(--glow-accent);transform:scale(.9) translateY(30px);transition:transform .35s var(--ease-spring);border:1px solid var(--border);backdrop-filter:blur(20px)}
    .ldsp-modal-overlay.show .ldsp-modal{transform:scale(1) translateY(0)}
    .ldsp-modal-hdr{display:flex;align-items:center;gap:12px;margin-bottom:18px}
    .ldsp-modal-icon{font-size:28px;filter:drop-shadow(0 2px 8px rgba(0,0,0,.2))}
    .ldsp-modal-title{font-size:17px;font-weight:700;letter-spacing:-.02em}
    .ldsp-modal-body{font-size:13px;color:var(--txt-sec);line-height:1.7;margin-bottom:20px}
    .ldsp-modal-body p{margin:0 0 10px}
    .ldsp-modal-body ul{margin:10px 0;padding-left:0;list-style:none}
    .ldsp-modal-body li{margin:6px 0;padding-left:24px;position:relative}
    .ldsp-modal-body li::before{content:'';position:absolute;left:0;top:6px;width:6px;height:6px;background:var(--accent);border-radius:50%}
    .ldsp-modal-body strong{color:var(--accent);font-weight:600}
    .ldsp-modal-footer{display:flex;gap:12px}
    .ldsp-modal-btn{flex:1;padding:12px 18px;border:none;border-radius:var(--r-md);font-size:13px;font-weight:600;transition:background .15s,transform .2s var(--ease)}
    .ldsp-modal-btn.primary{background:var(--grad);color:#fff;box-shadow:0 4px 15px rgba(107,140,239,.3)}
    .ldsp-modal-btn.primary:hover{transform:translateY(-2px);box-shadow:0 8px 25px rgba(107,140,239,.4)}
    .ldsp-modal-btn.primary:active{transform:translateY(0)}
    .ldsp-modal-btn.secondary{background:var(--bg-el);color:var(--txt-sec);border:1px solid var(--border2)}
    .ldsp-modal-btn.secondary:hover{background:var(--bg-hover);border-color:var(--border-accent)}
    .ldsp-modal-btn.danger{background:var(--grad-warm);color:#fff;box-shadow:0 4px 15px rgba(224,122,141,.3)}
    .ldsp-modal-btn.danger:hover{transform:translateY(-2px);box-shadow:0 8px 25px rgba(224,122,141,.4)}
    .ldsp-modal-btn.danger:active{transform:translateY(0)}
    .ldsp-modal-note{margin-top:14px;font-size:11px;color:var(--txt-mut);text-align:center;font-weight:500}
    .ldsp-confirm-overlay{position:absolute;top:0;left:0;right:0;bottom:0;width:100%;height:100%;box-sizing:border-box;background:rgba(18,19,26,.92);backdrop-filter:blur(12px);-webkit-backdrop-filter:blur(12px);display:flex;align-items:center;justify-content:center;z-index:20;opacity:0;pointer-events:none;transition:opacity .3s var(--ease);border-radius:inherit;margin:0;padding:0 20px}
    .ldsp-confirm-overlay.show{opacity:1;pointer-events:auto}
    .ldsp-confirm-box{background:linear-gradient(145deg,var(--bg-card),var(--bg));border-radius:var(--r-lg);padding:24px 20px;width:100%;max-width:260px;box-shadow:var(--shadow-lg),0 0 40px rgba(224,122,141,.1);transform:scale(.92) translateY(20px);transition:transform .35s var(--ease-spring);border:1px solid var(--border2);position:relative;overflow:hidden;margin:0 auto}
    .ldsp-confirm-box::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:var(--grad-warm);opacity:.8}
    .ldsp-confirm-overlay.show .ldsp-confirm-box{transform:scale(1) translateY(0)}
    .ldsp-confirm-icon{text-align:center;font-size:40px;margin-bottom:14px;filter:drop-shadow(0 4px 8px rgba(224,122,141,.3));animation:confirm-icon-bounce .5s var(--ease-spring) .1s both}
    @keyframes confirm-icon-bounce{0%{transform:scale(0) rotate(-20deg);opacity:0}60%{transform:scale(1.15) rotate(5deg)}100%{transform:scale(1) rotate(0);opacity:1}}
    .ldsp-confirm-title{text-align:center;font-size:16px;font-weight:700;margin-bottom:10px;color:#ef4444;letter-spacing:-.02em}
    .ldsp-confirm-msg{text-align:center;font-size:12px;color:var(--txt-sec);line-height:1.7;margin-bottom:20px;padding:0 4px}
    .ldsp-confirm-btns{display:flex;gap:10px}
    .ldsp-confirm-btn{flex:1;padding:11px 14px;border:none;border-radius:var(--r-sm);font-size:12px;font-weight:600;transition:background .15s,transform .15s,box-shadow .15s,border-color .15s;-webkit-tap-highlight-color:transparent;touch-action:manipulation}
    .ldsp-confirm-btn.cancel{background:var(--bg-el);color:var(--txt-sec);border:1px solid var(--border2)}
    @media (hover:hover){.ldsp-confirm-btn.cancel:hover{background:var(--bg-hover);border-color:var(--border-accent)}}
    .ldsp-confirm-btn.confirm{background:var(--grad-warm);color:#fff;box-shadow:0 4px 15px rgba(224,122,141,.3);border:none}
    @media (hover:hover){.ldsp-confirm-btn.confirm:hover{transform:translateY(-2px);box-shadow:0 8px 22px rgba(224,122,141,.4)}}
    .ldsp-confirm-btn:active{transform:scale(.96)}
    @media (max-width:480px){.ldsp-confirm-box{padding:20px 16px;max-width:240px}.ldsp-confirm-icon{font-size:36px;margin-bottom:12px}.ldsp-confirm-title{font-size:14px}.ldsp-confirm-msg{font-size:11px;margin-bottom:16px}.ldsp-confirm-btn{padding:10px 12px;font-size:11px}.ldsp-update-bubble{width:200px;padding:14px 16px}.ldsp-update-bubble-icon{font-size:24px;margin-bottom:6px}.ldsp-update-bubble-title{font-size:12px;margin-bottom:5px}.ldsp-update-bubble-ver{font-size:10px;margin-bottom:10px}.ldsp-update-bubble-btn{padding:7px 16px;font-size:11px}}
    @media (max-width:380px){.ldsp-confirm-overlay{padding:0 10px}.ldsp-confirm-box{padding:18px 14px;max-width:230px}.ldsp-confirm-icon{font-size:34px;margin-bottom:10px}.ldsp-confirm-title{font-size:13px}.ldsp-confirm-msg{font-size:10px;margin-bottom:14px}.ldsp-confirm-btn{padding:9px 10px;font-size:10px}}
    @media (max-width:320px){.ldsp-confirm-overlay{padding:0 8px}.ldsp-confirm-box{padding:14px 12px;max-width:200px}.ldsp-confirm-icon{font-size:28px;margin-bottom:8px}.ldsp-confirm-title{font-size:12px}.ldsp-confirm-msg{font-size:9px;margin-bottom:12px;line-height:1.5}.ldsp-confirm-btns{gap:6px}.ldsp-confirm-btn{padding:8px 8px;font-size:9px;border-radius:6px}.ldsp-update-bubble{width:180px;padding:12px 14px}.ldsp-update-bubble-icon{font-size:20px;margin-bottom:5px}.ldsp-update-bubble-title{font-size:11px}.ldsp-update-bubble-ver{font-size:9px;margin-bottom:8px}.ldsp-update-bubble-btn{padding:6px 12px;font-size:10px}}
    .ldsp-no-chg{text-align:center;padding:18px;color:var(--txt-mut);font-size:11px;font-weight:500}
    .ldsp-lb-hdr{display:flex;align-items:center;justify-content:space-between;padding:12px;background:var(--bg-card);border-radius:var(--r-md);margin-bottom:10px;border:1px solid var(--border)}
    .ldsp-lb-status{display:flex;align-items:center;gap:10px}
    .ldsp-lb-dot{width:10px;height:10px;border-radius:50%;background:var(--txt-mut);transition:background .2s}
    .ldsp-lb-dot.joined{background:var(--ok);box-shadow:0 0 10px rgba(16,185,129,.4)}
    .ldsp-lb-btn{padding:8px 14px;border:none;border-radius:20px;font-size:11px;font-weight:600;transition:background .15s,color .15s,transform .2s var(--ease)}
    .ldsp-lb-btn.primary{background:var(--grad);color:#fff;box-shadow:0 4px 12px rgba(107,140,239,.25)}
    .ldsp-lb-btn.primary:hover{transform:translateY(-2px);box-shadow:0 6px 18px rgba(107,140,239,.4)}
    .ldsp-lb-btn.primary:active{transform:translateY(0)}
    .ldsp-lb-btn.secondary{background:var(--bg-el);color:var(--txt-sec);border:1px solid var(--border2)}
    .ldsp-lb-btn.secondary:hover{background:var(--bg-hover);border-color:var(--border-accent)}
    .ldsp-lb-btn.danger{background:var(--err-bg);color:var(--err);border:1px solid rgba(244,63,94,.3)}
    .ldsp-lb-btn.danger:hover{background:var(--err);color:#fff;box-shadow:0 4px 12px rgba(244,63,94,.3)}
    .ldsp-lb-btn:disabled{opacity:.5;cursor:not-allowed;transform:none!important}
    .ldsp-rank-list{display:flex;flex-direction:column;gap:6px}
    .ldsp-rank-item{display:flex;align-items:center;gap:10px;padding:10px 12px;background:var(--bg-card);border-radius:var(--r-md);animation:item var(--dur) var(--ease-out) backwards;border:1px solid var(--border);transition:background .15s,border-color .15s,transform .2s var(--ease)}
    .ldsp-rank-item:hover{background:var(--bg-hover);transform:translateX(4px);box-shadow:0 4px 15px rgba(0,0,0,.1)}
    .ldsp-rank-item.t1{background:linear-gradient(135deg,rgba(255,215,0,.12) 0%,rgba(255,185,0,.05) 100%);border:1px solid rgba(255,215,0,.35);box-shadow:0 4px 20px rgba(255,215,0,.15)}
    .ldsp-rank-item.t2{background:linear-gradient(135deg,rgba(192,192,192,.12) 0%,rgba(160,160,160,.05) 100%);border:1px solid rgba(192,192,192,.35)}
    .ldsp-rank-item.t3{background:linear-gradient(135deg,rgba(205,127,50,.12) 0%,rgba(181,101,29,.05) 100%);border:1px solid rgba(205,127,50,.35)}
    .ldsp-rank-item.me{border-left:3px solid var(--accent);box-shadow:0 0 15px rgba(107,140,239,.1)}
    .ldsp-rank-num{width:28px;height:28px;border-radius:10px;background:var(--bg-el);display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;color:var(--txt-sec);flex-shrink:0}
    .ldsp-rank-item.t1 .ldsp-rank-num{background:linear-gradient(135deg,#ffd700 0%,#ffb700 100%);color:#1a1a1a;font-size:14px;box-shadow:0 4px 12px rgba(255,215,0,.4)}
    .ldsp-rank-item.t2 .ldsp-rank-num{background:linear-gradient(135deg,#e0e0e0 0%,#b0b0b0 100%);color:#1a1a1a;box-shadow:0 4px 12px rgba(192,192,192,.4)}
    .ldsp-rank-item.t3 .ldsp-rank-num{background:linear-gradient(135deg,#cd7f32 0%,#b5651d 100%);color:#fff;box-shadow:0 4px 12px rgba(205,127,50,.4)}
    .ldsp-rank-avatar{width:32px;height:32px;border-radius:10px;border:2px solid var(--border2);flex-shrink:0;background:var(--bg-el);transition:transform .2s var(--ease),border-color .15s}
    .ldsp-rank-item:hover .ldsp-rank-avatar{transform:scale(1.05)}
    .ldsp-rank-item.t1 .ldsp-rank-avatar{border-color:#ffd700;box-shadow:0 0 12px rgba(255,215,0,.3)}
    .ldsp-rank-item.t2 .ldsp-rank-avatar{border-color:#c0c0c0}
    .ldsp-rank-item.t3 .ldsp-rank-avatar{border-color:#cd7f32}
    .ldsp-rank-info{flex:1;min-width:0;display:flex;flex-wrap:wrap;align-items:baseline;gap:3px 5px}
    .ldsp-rank-name{font-size:12px;font-weight:600;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
    .ldsp-rank-display-name{font-size:12px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:85px}
    .ldsp-rank-username{font-size:10px;color:var(--txt-mut);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font-weight:500}
    .ldsp-rank-name-only{font-size:12px;font-weight:700;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
    .ldsp-rank-me-tag{font-size:10px;color:var(--accent);margin-left:3px;font-weight:600;background:rgba(107,140,239,.1);padding:1px 6px;border-radius:8px}
    .ldsp-rank-time{font-size:13px;font-weight:800;color:var(--accent);white-space:nowrap;letter-spacing:-.02em}
    .ldsp-rank-item.t1 .ldsp-rank-time{color:#ffc107;text-shadow:0 0 10px rgba(255,193,7,.3)}
    .ldsp-rank-item.t2 .ldsp-rank-time{color:#b8b8b8}
    .ldsp-rank-item.t3 .ldsp-rank-time{color:#cd7f32}
    .ldsp-lb-empty{text-align:center;padding:40px 20px;color:var(--txt-mut)}
    .ldsp-lb-empty-icon{font-size:48px;margin-bottom:14px;filter:drop-shadow(0 2px 10px rgba(0,0,0,.1))}
    .ldsp-lb-empty-txt{font-size:12px;line-height:1.7;font-weight:500}
    .ldsp-lb-login{text-align:center;padding:40px 20px}
    .ldsp-lb-login-icon{font-size:56px;margin-bottom:16px;filter:drop-shadow(0 4px 15px rgba(0,0,0,.15))}
    .ldsp-lb-login-title{font-size:15px;font-weight:700;margin-bottom:8px;letter-spacing:-.01em}
    .ldsp-lb-login-desc{font-size:11px;color:var(--txt-mut);margin-bottom:20px;line-height:1.7;font-weight:500}
    .ldsp-lb-period{font-size:10px;color:var(--txt-mut);text-align:center;padding:8px 10px;background:var(--bg-card);border-radius:var(--r-sm);margin-bottom:10px;display:flex;justify-content:center;align-items:center;gap:10px;flex-wrap:wrap;border:1px solid var(--border);font-weight:500}
    .ldsp-lb-period span{color:var(--accent);font-weight:700}
    .ldsp-lb-period .ldsp-update-rule{font-size:9px;opacity:.8}
    /* 排行榜响应式 */
    @media (max-width:380px){.ldsp-lb-hdr{padding:10px}.ldsp-lb-btn{padding:6px 10px;font-size:10px}.ldsp-lb-period{padding:6px 8px;font-size:9px;gap:6px}.ldsp-lb-empty{padding:30px 15px}.ldsp-lb-empty-icon{font-size:40px;margin-bottom:10px}.ldsp-lb-empty-txt{font-size:11px}.ldsp-lb-login{padding:30px 15px}.ldsp-lb-login-icon{font-size:48px;margin-bottom:12px}.ldsp-lb-login-title{font-size:13px}.ldsp-lb-login-desc{font-size:10px;margin-bottom:16px}.ldsp-rank-item{padding:10px;gap:8px}.ldsp-rank-num{width:24px;height:24px;font-size:10px}.ldsp-rank-avatar{width:34px;height:34px}.ldsp-rank-name{font-size:12px}.ldsp-rank-time{font-size:12px}.ldsp-my-rank{padding:10px}.ldsp-my-rank-lbl{font-size:10px}.ldsp-my-rank-val{font-size:18px}}
    @media (max-width:320px){.ldsp-lb-hdr{padding:8px}.ldsp-lb-status{gap:6px}.ldsp-lb-dot{width:8px;height:8px}.ldsp-lb-btn{padding:5px 8px;font-size:9px}.ldsp-lb-period{padding:5px 6px;font-size:8px;gap:4px}.ldsp-lb-empty{padding:24px 12px}.ldsp-lb-empty-icon{font-size:32px}.ldsp-lb-empty-txt{font-size:10px}.ldsp-lb-login{padding:24px 12px}.ldsp-lb-login-icon{font-size:40px;margin-bottom:10px}.ldsp-lb-login-title{font-size:12px}.ldsp-lb-login-desc{font-size:9px;margin-bottom:12px}.ldsp-rank-list{gap:4px}.ldsp-rank-item{padding:8px;gap:6px}.ldsp-rank-num{width:20px;height:20px;font-size:9px}.ldsp-rank-avatar{width:28px;height:28px}.ldsp-rank-name{font-size:10px}.ldsp-rank-time{font-size:10px}.ldsp-my-rank{padding:8px}.ldsp-my-rank-lbl{font-size:9px}.ldsp-my-rank-val{font-size:16px}}
    .ldsp-lb-refresh{background:var(--bg-el);border:none;font-size:11px;padding:4px 8px;border-radius:6px;transition:background .15s,opacity .2s;opacity:.8}
    .ldsp-lb-refresh:hover{opacity:1;background:var(--bg-hover);transform:scale(1.05)}
    .ldsp-lb-refresh:active{transform:scale(.95)}
    .ldsp-lb-refresh.spinning{animation:ldsp-spin 1s linear infinite}
    .ldsp-lb-refresh:disabled{opacity:.4;cursor:not-allowed;transform:none!important}
    @keyframes ldsp-spin{from{transform:rotate(0deg)}to{transform:rotate(360deg)}}
    .ldsp-my-rank{display:flex;align-items:center;justify-content:space-between;padding:14px;background:var(--grad);border-radius:var(--r-md);margin-bottom:10px;color:#fff;position:relative;overflow:hidden;box-shadow:0 8px 25px rgba(107,140,239,.3)}
    .ldsp-my-rank::before{content:'';position:absolute;top:-50%;right:-20%;width:100px;height:100px;background:radial-gradient(circle,rgba(255,255,255,.15) 0%,transparent 70%);pointer-events:none}
    .ldsp-my-rank.not-in-top{background:linear-gradient(135deg,#52525b 0%,#3f3f46 100%);box-shadow:0 8px 25px rgba(0,0,0,.2)}
    .ldsp-my-rank-lbl{font-size:11px;opacity:.9;font-weight:500}
    .ldsp-my-rank-val{font-size:20px;font-weight:800;letter-spacing:-.02em;text-shadow:0 2px 8px rgba(0,0,0,.2)}
    .ldsp-my-rank-time{font-size:12px;opacity:.95;font-weight:600}
    .ldsp-not-in-top-hint{font-size:10px;opacity:.7;margin-left:5px}
    .ldsp-join-prompt{background:var(--bg-card);border-radius:var(--r-md);padding:24px 20px;text-align:center;margin-bottom:10px;border:1px solid var(--border);position:relative;overflow:hidden}
    .ldsp-join-prompt::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:var(--grad)}
    .ldsp-join-prompt-icon{font-size:44px;margin-bottom:12px;filter:drop-shadow(0 2px 10px rgba(0,0,0,.15))}
    .ldsp-join-prompt-title{font-size:14px;font-weight:700;margin-bottom:6px;letter-spacing:-.01em}
    .ldsp-join-prompt-desc{font-size:11px;color:var(--txt-mut);line-height:1.7;margin-bottom:16px;font-weight:500}
    .ldsp-join-prompt.paused{border-style:dashed;background:linear-gradient(135deg,rgba(244,63,94,.08),rgba(249,115,22,.06))}
    .ldsp-join-prompt.paused::before{background:linear-gradient(90deg,#f97316,#f43f5e)}
    .ldsp-join-prompt.paused .ldsp-join-prompt-title{color:#f97316}
    .ldsp-privacy-note{font-size:9px;color:var(--txt-mut);margin-top:12px;display:flex;align-items:center;justify-content:center;gap:5px;font-weight:500}
    /* 面板手动调整大小 - 仅桌面端 */
    @media (hover:hover) and (pointer:fine){
    #ldsp-panel:not(.collapsed){resize:none}
    .ldsp-resize-handle{position:absolute;z-index:15;opacity:0;transition:opacity .2s}
    #ldsp-panel:hover .ldsp-resize-handle{opacity:1}
    .ldsp-resize-e{right:0;top:1em;bottom:1em;width:6px;cursor:e-resize}
    .ldsp-resize-s{bottom:0;left:1em;right:1em;height:6px;cursor:s-resize}
    .ldsp-resize-se{right:0;bottom:0;width:14px;height:14px;cursor:se-resize}
    .ldsp-resize-se::before{content:'';position:absolute;right:3px;bottom:3px;width:8px;height:8px;border-right:2px solid var(--txt-mut);border-bottom:2px solid var(--txt-mut);opacity:.5;transition:opacity .2s}
    #ldsp-panel:hover .ldsp-resize-se::before{opacity:.8}
    .ldsp-resize-handle:hover::before{opacity:1}
    #ldsp-panel.resizing .ldsp-resize-handle{opacity:1}
    #ldsp-panel.resizing,#ldsp-panel.resizing *{transition:none!important;user-select:none!important}
    }
    @media (prefers-reduced-motion:reduce){#ldsp-panel,#ldsp-panel *{animation:none!important;transition:none!important}#ldsp-panel .ldsp-spinner,#ldsp-panel .ldsp-mini-spin,#ldsp-panel .ldsp-lb-refresh.spinning{animation:spin 1.5s linear infinite!important}}
    /* ========== 响应式布局系统 ========== */
    /* 内容区最大高度 */
    .ldsp-content{max-height:calc(var(--h) - 160px)}
    /* --- 高度响应式 --- */
    /* 矮屏优化 (高度<700px): 减小间距 */
    @media (max-height:700px){.ldsp-user{padding:8px var(--pd) 20px}.ldsp-reading{padding:6px}.ldsp-reading-icon{font-size:18px}.ldsp-reading-time{font-size:12px}.ldsp-reading::after{font-size:7px;bottom:-12px}.ldsp-ring{padding:10px 12px}.ldsp-ring-stat-val{font-size:16px}.ldsp-ring-stat-lbl{font-size:8px}}
    /* 矮屏优化 (高度<600px): 进一步压缩 */
    @media (max-height:600px){.ldsp-hdr{padding:8px 10px;min-height:44px;gap:6px}.ldsp-user{padding:6px var(--pd) 16px;gap:8px}.ldsp-user-actions{gap:4px;margin-top:0}.ldsp-action-btn{padding:4px 6px;font-size:9px}.ldsp-tabs{padding:6px 8px}.ldsp-section{padding:8px}.ldsp-reading{padding:5px}.ldsp-reading-icon{font-size:16px;margin-bottom:2px}.ldsp-reading-time{font-size:11px}.ldsp-reading-label{font-size:8px}.ldsp-reading::after{font-size:6px;bottom:-10px}}
    /* 矮屏优化 (高度<500px): 隐藏次要元素 */
    @media (max-height:500px){.ldsp-user{padding:5px var(--pd) 14px}.ldsp-reading{padding:4px}.ldsp-reading-icon{font-size:14px;margin-bottom:1px}.ldsp-reading-time{font-size:10px}.ldsp-reading-label{font-size:7px}.ldsp-tabs{padding:5px 6px;gap:4px}.ldsp-tab{padding:5px 6px;font-size:9px}.ldsp-section{padding:5px}.ldsp-ring{padding:8px 10px;margin-bottom:8px}.ldsp-ring-wrap{--ring:55px}.ldsp-announcement.active{height:20px;min-height:20px}.ldsp-announcement-text{font-size:9px}}
    /* 极矮屏 (高度<420px): 隐藏操作按钮 */
    @media (max-height:420px){.ldsp-user-actions{display:none}.ldsp-reading::after{display:none}}
    /* --- 宽度响应式 --- */
    /* 窄屏优化 (宽度<380px): 手机竖屏 */
    @media (max-width:380px){#ldsp-panel{--w:260px}#ldsp-panel.collapsed{width:42px!important;height:42px!important;min-width:42px!important;min-height:42px!important;max-height:42px!important}#ldsp-panel.collapsed .ldsp-toggle-logo{width:20px;height:20px}.ldsp-hdr{padding:8px 10px;gap:6px;min-height:46px}.ldsp-hdr-info{gap:5px}.ldsp-site-icon{width:22px;height:22px}.ldsp-site-ver{font-size:7px}.ldsp-title{font-size:11px}.ldsp-app-name{font-size:9px}.ldsp-hdr-btns{gap:3px}.ldsp-hdr-btns button{width:24px;height:24px;font-size:10px}.ldsp-user{gap:8px}.ldsp-avatar,.ldsp-avatar-ph{width:40px;height:40px;border-radius:10px}.ldsp-user-display-name{font-size:14px}.ldsp-user-handle{font-size:10px}.ldsp-reading{padding:5px;max-width:85px}.ldsp-reading-icon{font-size:16px}.ldsp-reading-time{font-size:11px}.ldsp-reading-label{font-size:8px}.ldsp-user-actions{gap:4px}.ldsp-action-btn{font-size:9px;padding:4px 6px;gap:3px}.ldsp-action-icon{font-size:11px}.ldsp-tabs{padding:6px 8px;gap:4px}.ldsp-tab{font-size:9px;padding:5px 6px}.ldsp-section{padding:8px}.ldsp-ring{padding:10px;gap:8px}.ldsp-ring-stat-val{font-size:14px}.ldsp-ring-stat-lbl{font-size:8px}}
    .ldsp-action-btn{display:inline-flex;align-items:center;gap:4px;padding:5px 10px;background:linear-gradient(135deg,rgba(107,140,239,.08),rgba(90,125,224,.12));border:1px solid rgba(107,140,239,.2);border-radius:8px;font-size:10px;color:var(--accent);transition:background .15s,border-color .15s,transform .15s,box-shadow .15s;font-weight:600;white-space:nowrap;flex:1 1 auto;min-width:fit-content;justify-content:center;-webkit-tap-highlight-color:transparent;touch-action:manipulation}
    @media (hover:hover){.ldsp-action-btn:hover{background:linear-gradient(135deg,rgba(107,140,239,.15),rgba(90,125,224,.2));border-color:var(--accent);box-shadow:0 4px 12px rgba(107,140,239,.18)}}
    .ldsp-action-btn:active{background:linear-gradient(135deg,rgba(107,140,239,.18),rgba(90,125,224,.24));transform:scale(.97)}
    .ldsp-action-btn:only-child{flex:0 1 auto}
    .ldsp-action-btn .ldsp-action-icon{flex-shrink:0}
    .ldsp-action-btn .ldsp-action-text{overflow:hidden;text-overflow:ellipsis}
    /* 极窄屏优化 (宽度<320px): 小屏手机 */
    @media (max-width:320px){#ldsp-panel{--w:230px;--min-w:200px}#ldsp-panel.collapsed{width:36px!important;height:36px!important;min-width:36px!important;min-height:36px!important;max-height:36px!important;border-radius:8px}#ldsp-panel.collapsed .ldsp-toggle-logo{width:18px;height:18px}.ldsp-hdr{padding:6px 8px;gap:4px;min-height:38px}.ldsp-hdr-info{gap:4px}.ldsp-site-icon{width:18px;height:18px;border-radius:5px;border-width:1px}.ldsp-site-ver{display:none}.ldsp-title{font-size:10px}.ldsp-app-name{display:none}.ldsp-ver-num{font-size:8px;padding:1px 5px}.ldsp-hdr-btns{gap:2px}.ldsp-hdr-btns button{width:22px;height:22px;font-size:9px;border-radius:5px}.ldsp-user{padding:6px 8px 14px;gap:6px}.ldsp-avatar,.ldsp-avatar-ph{width:36px;height:36px;border-radius:8px;font-size:14px}.ldsp-user-display-name{font-size:13px}.ldsp-user-handle{font-size:9px}.ldsp-reading{padding:4px;max-width:70px}.ldsp-reading-icon{font-size:13px;margin-bottom:1px}.ldsp-reading-time{font-size:9px}.ldsp-reading-label{font-size:6px}.ldsp-user-actions{flex-direction:column;gap:3px}.ldsp-action-btn{flex:1 1 100%;min-width:0;font-size:8px;padding:4px 5px;gap:2px}.ldsp-action-icon{font-size:10px}.ldsp-tabs{padding:4px 6px;gap:3px}.ldsp-tab{font-size:8px;padding:4px 5px;gap:2px}.ldsp-tab .ldsp-tab-icon{display:none}.ldsp-section{padding:6px}.ldsp-ring{padding:8px;gap:6px;margin-bottom:6px}.ldsp-ring-stat{min-width:40px;gap:2px}.ldsp-ring-stat-val{font-size:13px}.ldsp-ring-stat-lbl{font-size:7px}.ldsp-ring-wrap{--ring:50px}}
    /* 小屏手机组合优化 (宽度<380px 且 高度<700px) */
    @media (max-width:380px) and (max-height:700px){.ldsp-user{padding:5px var(--pd) 12px;gap:6px}.ldsp-avatar,.ldsp-avatar-ph{width:36px;height:36px}.ldsp-reading{padding:4px;max-width:65px}.ldsp-reading-icon{font-size:13px;margin-bottom:1px}.ldsp-reading-time{font-size:9px}.ldsp-reading-label{font-size:6px}.ldsp-reading::after{font-size:6px;bottom:-9px}.ldsp-user-actions{gap:3px}.ldsp-action-btn{padding:3px 5px;font-size:8px}.ldsp-ring{padding:8px;margin-bottom:6px}.ldsp-ring-stat-val{font-size:12px}.ldsp-ring-wrap{--ring:48px}}
    /* 超小屏手机 (宽度<280px) */
    @media (max-width:280px){#ldsp-panel{--w:210px}.ldsp-hdr{padding:5px 6px;min-height:34px}.ldsp-site-icon{width:16px;height:16px}.ldsp-title{font-size:9px}.ldsp-hdr-btns button{width:20px;height:20px;font-size:8px}.ldsp-user{padding:4px 6px 10px;gap:5px}.ldsp-avatar,.ldsp-avatar-ph{width:32px;height:32px;border-radius:6px}.ldsp-user-display-name{font-size:12px}.ldsp-user-handle{font-size:8px}.ldsp-reading{padding:3px;max-width:55px}.ldsp-reading-icon{font-size:11px;margin-bottom:0}.ldsp-reading-time{font-size:8px}.ldsp-reading-label{font-size:5px;margin-top:1px}.ldsp-reading::after{display:none}.ldsp-action-btn{padding:3px 4px;font-size:7px;gap:2px}.ldsp-tabs{padding:3px 5px;gap:2px}.ldsp-tab{font-size:7px;padding:3px 4px}.ldsp-section{padding:4px}.ldsp-ring{padding:6px;gap:4px}.ldsp-ring-stat-val{font-size:11px}.ldsp-ring-stat-lbl{font-size:6px}.ldsp-ring-wrap{--ring:42px}}
    /* ========== 响应式布局系统结束 ========== */
    .ldsp-logout-btn,.ldsp-ticket-btn,.ldsp-melon-btn,.ldsp-ldc-btn{padding:5px 8px}
    .ldsp-logout-btn{background:linear-gradient(135deg,rgba(239,68,68,.06),rgba(220,38,38,.08));border-color:rgba(239,68,68,.15);color:rgba(239,68,68,.7)}
    .ldsp-logout-btn:hover{background:linear-gradient(135deg,rgba(239,68,68,.12),rgba(220,38,38,.16));border-color:rgba(239,68,68,.3);color:#ef4444}
    .ldsp-login-btn{flex:1 1 100%;background:linear-gradient(135deg,rgba(212,168,83,.15),rgba(196,147,57,.2));border-color:rgba(212,168,83,.3);color:var(--warn);animation:login-pulse 2.5s ease-in-out infinite}
    .ldsp-login-btn:hover{background:linear-gradient(135deg,rgba(212,168,83,.25),rgba(196,147,57,.3));border-color:var(--warn)}
    @keyframes login-pulse{0%,100%{box-shadow:0 0 0 0 rgba(212,168,83,.3)}50%{box-shadow:0 0 12px 2px rgba(212,168,83,.2)}}
    .ldsp-ticket-btn .ldsp-ticket-badge{background:var(--err);color:#fff;font-size:8px;padding:2px 5px;border-radius:8px;margin-left:2px;font-weight:700;animation:pulse 3s ease infinite}
    .ldsp-ticket-overlay{position:absolute;top:0;left:0;right:0;bottom:0;background:var(--bg);border-radius:0 0 var(--r-lg) var(--r-lg);z-index:10;display:none;flex-direction:column;overflow:hidden}
    .ldsp-ticket-overlay.show{display:flex}
    .ldsp-ticket-header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;background:var(--bg-card);border-bottom:1px solid var(--border);flex-shrink:0}
    .ldsp-ticket-title{font-size:13px;font-weight:700;display:flex;align-items:center;gap:6px;color:var(--txt)}
    .ldsp-ticket-close{width:24px;height:24px;display:flex;align-items:center;justify-content:center;background:var(--bg-el);border:1px solid var(--border);border-radius:6px;font-size:12px;color:var(--txt-sec);transition:background .15s,color .15s}
    .ldsp-ticket-close:hover{background:var(--err-bg);color:var(--err);border-color:var(--err)}
    .ldsp-ticket-tabs{display:flex;border-bottom:1px solid var(--border);padding:0 10px;background:var(--bg-card);flex-shrink:0}
    .ldsp-ticket-tab{padding:8px 12px;font-size:10px;font-weight:600;color:var(--txt-mut);border-bottom:2px solid transparent;transition:color .15s,border-color .15s}
    .ldsp-ticket-tab.active{color:var(--accent);border-color:var(--accent)}
    .ldsp-ticket-tab:hover:not(.active){color:var(--txt-sec)}
    .ldsp-ticket-body{flex:1;overflow-y:auto;padding:12px;background:var(--bg);display:flex;flex-direction:column}
    .ldsp-ticket-body.detail-mode{padding:0;overflow:hidden}
    .ldsp-ticket-empty{text-align:center;padding:30px 16px;color:var(--txt-mut)}
    .ldsp-ticket-empty-icon{font-size:36px;margin-bottom:10px}
    .ldsp-ticket-list{display:flex;flex-direction:column;gap:8px}
    .ldsp-ticket-item{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:10px;cursor:pointer;transition:background .15s,border-color .15s}
    .ldsp-ticket-item:hover{background:var(--bg-hover);transform:translateX(3px)}
    .ldsp-ticket-item.has-reply{border-left:3px solid #ef4444;animation:pulse-border-red 3s ease infinite;background:rgba(239,68,68,.05)}
    .ldsp-ticket-item-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:5px}
    .ldsp-ticket-item-type{font-size:10px;color:var(--txt-sec)}
    .ldsp-ticket-item-status{font-size:9px;padding:2px 5px;border-radius:4px}
    .ldsp-ticket-item-status.open{background:var(--ok-bg);color:var(--ok)}
    .ldsp-ticket-item-status.closed{background:var(--bg-el);color:var(--txt-mut)}
    .ldsp-ticket-item-title{font-size:11px;font-weight:600;color:var(--txt);margin-bottom:5px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .ldsp-ticket-item-meta{font-size:9px;color:var(--txt-mut);display:flex;gap:6px}
    .ldsp-ticket-form{display:flex;flex-direction:column;gap:10px}
    .ldsp-ticket-form-group{display:flex;flex-direction:column;gap:5px}
    .ldsp-ticket-label{font-size:10px;font-weight:600;color:var(--txt-sec)}
    .ldsp-ticket-types{display:flex;gap:6px;flex-wrap:wrap}
    .ldsp-ticket-type{flex:1;min-width:80px;padding:8px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-sm);text-align:center;cursor:pointer;transition:background .15s,border-color .15s}
    .ldsp-ticket-type:hover{border-color:var(--accent)}
    .ldsp-ticket-type.selected{border-color:var(--accent);background:rgba(107,140,239,.1)}
    .ldsp-ticket-type-icon{font-size:16px;display:block;margin-bottom:3px}
    .ldsp-ticket-type-label{font-size:10px;color:var(--txt)}
    .ldsp-ticket-input{padding:8px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:11px;color:var(--txt)}
    .ldsp-ticket-input:focus{border-color:var(--accent);outline:none}
    .ldsp-ticket-textarea{padding:8px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:11px;color:var(--txt);min-height:80px;resize:vertical}
    .ldsp-ticket-textarea:focus{border-color:var(--accent);outline:none}
    .ldsp-ticket-submit{padding:10px;background:var(--grad);color:#fff;border:none;border-radius:var(--r-sm);font-size:11px;font-weight:600;cursor:pointer;transition:opacity .15s,transform .2s}
    .ldsp-ticket-submit:hover{box-shadow:0 4px 12px rgba(107,140,239,.3)}
    .ldsp-ticket-submit:disabled{opacity:.5;cursor:not-allowed}
    .ldsp-ticket-detail{display:flex;flex-direction:column;flex:1;min-height:0;background:var(--bg)}
    .ldsp-ticket-detail-top{padding:10px 12px;border-bottom:1px solid var(--border);background:var(--bg-card);flex-shrink:0}
    .ldsp-ticket-back{display:inline-flex;align-items:center;gap:4px;padding:5px 8px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:10px;color:var(--txt-sec);transition:background .15s,color .15s}
    .ldsp-ticket-back:hover{background:var(--bg-hover);color:var(--txt)}
    .ldsp-ticket-detail-header{margin-top:6px}
    .ldsp-ticket-detail-title{font-size:12px;font-weight:600;color:var(--txt);line-height:1.4;word-break:break-word}
    .ldsp-ticket-detail-meta{display:flex;flex-wrap:wrap;gap:5px;font-size:9px;color:var(--txt-mut);margin-top:5px}
    .ldsp-ticket-detail-meta span{background:var(--bg-el);padding:2px 5px;border-radius:3px}
    .ldsp-ticket-messages{flex:1;overflow-y:auto;padding:10px 12px;display:flex;flex-direction:column;gap:8px;min-height:0}
    .ldsp-ticket-reply{max-width:85%;padding:8px 10px;border-radius:var(--r-sm);font-size:11px;line-height:1.4;word-break:break-word}
    .ldsp-ticket-reply.user{background:linear-gradient(135deg,rgba(107,140,239,.12),rgba(90,125,224,.08));border:1px solid rgba(107,140,239,.2);margin-left:auto;border-bottom-right-radius:3px}
    .ldsp-ticket-reply.admin{background:var(--bg-card);border:1px solid var(--border);margin-right:auto;border-bottom-left-radius:3px}
    .ldsp-ticket-reply-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:4px;font-size:9px;color:var(--txt-mut)}
    .ldsp-ticket-reply-author{font-weight:600}
    .ldsp-ticket-reply.admin .ldsp-ticket-reply-author{color:var(--ok)}
    .ldsp-ticket-reply-content{color:var(--txt);white-space:pre-wrap}
    .ldsp-ticket-input-area{border-top:1px solid var(--border);padding:10px 12px;background:var(--bg-card);flex-shrink:0}
    .ldsp-ticket-reply-form{display:flex;gap:6px;align-items:center}
    .ldsp-ticket-reply-input{flex:1;padding:6px 8px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:11px;resize:none;min-height:32px;max-height:50px;color:var(--txt)}
    .ldsp-ticket-reply-input:focus{border-color:var(--accent);outline:none}
    .ldsp-ticket-reply-btn{padding:6px 12px;background:var(--grad);color:#fff;border:none;border-radius:var(--r-sm);font-size:10px;font-weight:600;transition:opacity .15s,transform .2s;flex-shrink:0;height:32px}
    .ldsp-ticket-reply-btn:hover{box-shadow:0 4px 12px rgba(107,140,239,.3)}
    .ldsp-ticket-reply-btn:disabled{opacity:.5;cursor:not-allowed}
    .ldsp-ticket-closed-hint{text-align:center;color:var(--txt-mut);font-size:10px;padding:10px}
    /* 吃瓜助手样式 */
    .ldsp-melon-btn{background:linear-gradient(135deg,rgba(74,222,128,.08),rgba(34,197,94,.12));border-color:rgba(74,222,128,.2);color:rgba(34,197,94,.85)}
    .ldsp-melon-btn:hover{background:linear-gradient(135deg,rgba(74,222,128,.15),rgba(34,197,94,.2));border-color:rgba(74,222,128,.35);color:#22c55e}
    /* 导出帖子按钮样式 - 使用橙色调 */
    .ldsp-export-btn{background:linear-gradient(135deg,rgba(251,146,60,.08),rgba(234,88,12,.12));border-color:rgba(251,146,60,.2);color:rgba(234,88,12,.85)}
    .ldsp-export-btn:hover{background:linear-gradient(135deg,rgba(251,146,60,.15),rgba(234,88,12,.2));border-color:rgba(251,146,60,.35);color:#ea580c}
    /* 导出帖子面板样式 */
    .ldsp-export-overlay{position:absolute;top:0;left:0;right:0;bottom:0;background:var(--bg);border-radius:0 0 var(--r-lg) var(--r-lg);z-index:10;display:none;flex-direction:column;overflow:hidden}
    .ldsp-export-overlay.show{display:flex}
    .ldsp-export-header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;background:var(--bg-card);border-bottom:1px solid var(--border);flex-shrink:0}
    .ldsp-export-title{font-size:13px;font-weight:700;display:flex;align-items:center;gap:6px;color:var(--txt)}
    .ldsp-export-header-actions{display:flex;align-items:center;gap:6px}
    .ldsp-export-refresh{padding:4px 10px;display:flex;align-items:center;justify-content:center;gap:4px;background:linear-gradient(135deg,rgba(107,140,239,.08),rgba(90,125,224,.12));border:1px solid rgba(107,140,239,.2);border-radius:6px;font-size:10px;color:var(--accent);cursor:pointer;transition:all .15s;white-space:nowrap}
    .ldsp-export-refresh:hover{background:linear-gradient(135deg,rgba(107,140,239,.15),rgba(90,125,224,.2));border-color:rgba(107,140,239,.4);color:#5a7de0}
    .ldsp-export-close{width:24px;height:24px;display:flex;align-items:center;justify-content:center;background:var(--bg-el);border:1px solid var(--border);border-radius:6px;font-size:12px;color:var(--txt-sec);cursor:pointer;transition:background .15s,color .15s}
    .ldsp-export-close:hover{background:var(--err-bg);color:var(--err);border-color:var(--err)}
    .ldsp-export-body{flex:1;overflow-y:auto;padding:12px;background:var(--bg);display:flex;flex-direction:column;gap:10px}
    .ldsp-export-info{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:12px;font-size:11px}
    .ldsp-export-info-title{font-weight:600;color:var(--txt);margin-bottom:8px;display:flex;align-items:flex-start;gap:5px;line-height:1.5;font-size:12px}
    .ldsp-export-info-row{display:flex;align-items:center;gap:6px;color:var(--txt-sec);font-size:10px;margin-top:4px}
    .ldsp-export-info-label{color:var(--txt-mut);min-width:50px}
    .ldsp-export-info-value{color:var(--txt);font-weight:500}
    .ldsp-export-info-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:6px}
    .ldsp-export-info-category{display:inline-flex;align-items:center;gap:3px;padding:2px 8px;background:var(--accent);color:#fff;border-radius:10px;font-size:9px;font-weight:600}
    .ldsp-export-info-tag{display:inline-flex;align-items:center;padding:2px 8px;background:var(--bg-el);color:var(--txt-sec);border-radius:10px;font-size:9px;font-weight:500;border:1px solid var(--border)}
    .ldsp-export-range{display:flex;align-items:center;gap:8px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:10px;flex-wrap:wrap}
    .ldsp-export-range-label{font-size:10px;color:var(--txt-sec);white-space:nowrap}
    .ldsp-export-range-input{width:70px;padding:5px 8px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:11px;color:var(--txt);text-align:center}
    .ldsp-export-range-input:focus{border-color:var(--accent);outline:none}
    .ldsp-export-range-sep{color:var(--txt-mut);font-size:10px}
    .ldsp-export-range-hint{font-size:9px;color:var(--txt-mut);margin-left:auto}
    .ldsp-export-options{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:10px}
    .ldsp-export-options-title{font-size:10px;color:var(--txt-sec);margin-bottom:8px}
    .ldsp-export-option{display:flex;align-items:center;gap:8px;padding:6px 0}
    .ldsp-export-option input[type="checkbox"]{width:16px;height:16px;accent-color:var(--accent)}
    .ldsp-export-option label{font-size:11px;color:var(--txt);cursor:pointer}
    .ldsp-export-format-selector{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:10px}
    .ldsp-export-format-title{font-size:10px;color:var(--txt-sec);margin-bottom:8px}
    .ldsp-export-format-cards{display:flex;gap:8px;flex-wrap:wrap}
    .ldsp-export-format-card{flex:1 1 90px;min-width:90px;display:flex;flex-direction:column;align-items:center;padding:12px 8px;background:var(--bg-el);border:2px solid var(--border);border-radius:var(--r-md);cursor:pointer;transition:all .2s}
    .ldsp-export-format-card:hover{border-color:var(--txt-mut);background:var(--bg-hover);transform:translateY(-1px)}
    .ldsp-export-format-card.active{border-color:var(--accent);background:rgba(107,140,239,.1);box-shadow:0 2px 8px rgba(107,140,239,.15)}
    .ldsp-export-format-card input{display:none}
    .ldsp-export-format-card-icon{width:28px;height:28px;margin-bottom:6px;display:flex;align-items:center;justify-content:center}
    .ldsp-export-format-card-icon svg{width:24px;height:24px}
    .ldsp-export-format-card-name{font-size:11px;font-weight:600;color:var(--txt)}
    .ldsp-export-format-card.active .ldsp-export-format-card-name{color:var(--accent)}
    .ldsp-export-actions{display:flex;gap:8px}
    .ldsp-export-btn-start{flex:1;padding:12px;border:none;border-radius:var(--r-md);font-size:13px;font-weight:600;cursor:pointer;transition:all .2s;display:flex;align-items:center;justify-content:center;gap:6px;background:linear-gradient(135deg,#22c55e,#16a34a);color:#fff;box-shadow:0 2px 8px rgba(34,197,94,.2)}
    .ldsp-export-btn-start:hover{box-shadow:0 4px 16px rgba(34,197,94,.35);transform:translateY(-2px)}
    .ldsp-export-btn-start:disabled{opacity:.5;cursor:not-allowed;transform:none;box-shadow:none}
    .ldsp-export-btn-stop{flex:1;padding:12px;border:none;border-radius:var(--r-md);font-size:13px;font-weight:600;cursor:pointer;transition:all .2s;display:flex;align-items:center;justify-content:center;gap:6px;background:linear-gradient(135deg,#ef4444,#dc2626);color:#fff;box-shadow:0 2px 8px rgba(239,68,68,.2)}
    .ldsp-export-btn-stop:hover{box-shadow:0 4px 16px rgba(239,68,68,.35);transform:translateY(-2px)}
    .ldsp-export-status{text-align:center;padding:14px;color:var(--txt-sec);font-size:11px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md)}
    .ldsp-export-status-icon{font-size:24px;margin-bottom:8px;display:block}
    .ldsp-export-not-topic{text-align:center;padding:40px 20px;color:var(--txt-mut)}
    .ldsp-export-not-topic-icon{font-size:36px;margin-bottom:10px}
    .ldsp-export-not-topic-text{font-size:12px;line-height:1.6}
    /* LDC 积分按钮样式 - 使用与主题一致的蓝紫色调 */
    .ldsp-ldc-btn{background:linear-gradient(135deg,rgba(139,92,246,.08),rgba(124,58,237,.12));border-color:rgba(139,92,246,.2);color:rgba(139,92,246,.85)}
    .ldsp-ldc-btn:hover{background:linear-gradient(135deg,rgba(139,92,246,.15),rgba(124,58,237,.2));border-color:rgba(139,92,246,.35);color:#8b5cf6}
    /* CDK 按钮样式 - 使用青色调 */
    .ldsp-cdk-btn{background:linear-gradient(135deg,rgba(6,182,212,.08),rgba(14,165,233,.12));border-color:rgba(6,182,212,.2);color:rgba(6,182,212,.85)}
    .ldsp-cdk-btn:hover{background:linear-gradient(135deg,rgba(6,182,212,.15),rgba(14,165,233,.2));border-color:rgba(6,182,212,.35);color:#06b6d4}
    /* CDK 面板样式 */
    .ldsp-cdk-overlay{position:absolute;top:0;left:0;right:0;bottom:0;background:var(--bg);border-radius:0 0 var(--r-lg) var(--r-lg);z-index:10;display:none;flex-direction:column;overflow:hidden}
    .ldsp-cdk-overlay.show{display:flex}
    .ldsp-cdk-header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;background:var(--bg-card);border-bottom:1px solid var(--border);flex-shrink:0}
    .ldsp-cdk-title{font-size:13px;font-weight:700;display:flex;align-items:center;gap:6px;color:var(--txt)}
    .ldsp-cdk-header-actions{display:flex;align-items:center;gap:8px}
    .ldsp-cdk-link{color:var(--accent)!important;text-decoration:none!important;font-size:10px!important;font-weight:600!important;opacity:.9;transition:opacity .15s}
    .ldsp-cdk-link:hover{opacity:1}
    .ldsp-cdk-refresh{width:24px;height:24px;display:flex;align-items:center;justify-content:center;background:var(--bg-el);border:1px solid var(--border);border-radius:6px;color:var(--txt-sec);cursor:pointer;transition:background .15s,border-color .15s,color .15s;-webkit-tap-highlight-color:transparent;touch-action:manipulation}
    .ldsp-cdk-refresh:hover{background:var(--bg-hover);border-color:var(--accent);color:var(--accent)}
    .ldsp-cdk-refresh.spinning svg{animation:ldsp-spin .8s linear infinite}
    .ldsp-cdk-refresh svg{width:12px;height:12px;fill:currentColor}
    .ldsp-cdk-close{width:24px;height:24px;display:flex;align-items:center;justify-content:center;background:var(--bg-el);border:1px solid var(--border);border-radius:6px;font-size:12px;color:var(--txt-sec);cursor:pointer;transition:background .15s,color .15s;-webkit-tap-highlight-color:transparent;touch-action:manipulation}
    .ldsp-cdk-close:hover{background:var(--err-bg);color:var(--err);border-color:var(--err)}
    /* CDK Tabs */
    .ldsp-cdk-tabs{display:flex;background:var(--bg-card);border-bottom:1px solid var(--border);flex-shrink:0}
    .ldsp-cdk-tab{flex:1;padding:10px 8px;font-size:11px;font-weight:600;color:var(--txt-mut);text-align:center;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s;-webkit-tap-highlight-color:transparent}
    .ldsp-cdk-tab:hover{color:var(--txt-sec);background:var(--bg-hover)}
    .ldsp-cdk-tab.active{color:#06b6d4;border-bottom-color:#06b6d4;background:transparent}
    .ldsp-cdk-body{flex:1;overflow-y:auto;padding:12px;background:var(--bg);display:flex;flex-direction:column;gap:10px;container-type:inline-size}
    .ldsp-cdk-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;color:var(--txt-mut);flex:1}
    .ldsp-cdk-loading .ldsp-spinner{margin-bottom:10px}
    .ldsp-cdk-error{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:30px 20px;text-align:center;color:var(--err);flex:1}
    .ldsp-cdk-error-icon{font-size:32px;margin-bottom:10px}
    .ldsp-cdk-error-msg{font-size:11px;margin-bottom:12px;color:var(--txt-sec)}
    .ldsp-cdk-login-btn{display:inline-block;padding:10px 20px;background:linear-gradient(135deg,#06b6d4,#0ea5e9);color:#fff!important;text-decoration:none!important;border-radius:var(--r-sm);font-size:12px;font-weight:600;transition:all .2s;box-shadow:0 2px 8px rgba(6,182,212,.3)}
    .ldsp-cdk-login-btn:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(6,182,212,.4)}
    .ldsp-cdk-retry-btn{margin-top:8px;padding:8px 16px;background:#06b6d4;color:#fff;border:none;border-radius:var(--r-sm);font-size:11px;cursor:pointer;transition:opacity .15s}
    .ldsp-cdk-retry-btn:hover{opacity:.85}
    /* CDK 用户卡片 */
    .ldsp-cdk-user-card{display:flex;align-items:center;gap:12px;padding:14px;background:linear-gradient(135deg,var(--bg-card),var(--bg-hover));border:1px solid var(--border);border-radius:var(--r-md);position:relative;overflow:hidden}
    .ldsp-cdk-user-card::before{content:'';position:absolute;top:-50%;right:-30%;width:80%;height:200%;background:radial-gradient(circle,rgba(6,182,212,.08) 0%,transparent 70%);pointer-events:none}
    .ldsp-cdk-user-avatar{width:52px;height:52px;border-radius:50%;border:2px solid rgba(6,182,212,.3);object-fit:cover;box-shadow:0 4px 12px rgba(0,0,0,.1)}
    .ldsp-cdk-user-info{flex:1;min-width:0}
    .ldsp-cdk-user-name{font-size:15px;font-weight:700;color:var(--txt);margin-bottom:2px}
    .ldsp-cdk-user-username{font-size:10px;color:var(--txt-mut)}
    .ldsp-cdk-user-level{display:inline-flex;align-items:center;gap:4px;padding:3px 10px;background:linear-gradient(135deg,rgba(251,191,36,.18),rgba(245,158,11,.1));border:1px solid rgba(251,191,36,.25);border-radius:999px;font-size:10px;color:#f59e0b;font-weight:600;margin-top:6px}
    .ldsp-cdk-score-card{display:flex;flex-direction:column;align-items:center;padding:12px 18px;background:linear-gradient(135deg,rgba(6,182,212,.15),rgba(14,165,233,.08));border:1px solid rgba(6,182,212,.25);border-radius:var(--r-md);min-width:80px;box-shadow:0 4px 12px rgba(6,182,212,.1)}
    .ldsp-cdk-score-label{font-size:9px;color:var(--txt-sec);margin-bottom:3px;text-transform:uppercase;letter-spacing:.3px}
    .ldsp-cdk-score-value{font-size:26px;font-weight:800;color:#06b6d4;text-shadow:0 2px 8px rgba(6,182,212,.25)}
    /* CDK 用户卡片 - 窄宽度响应式 */
    @container (max-width:280px){.ldsp-cdk-user-card{gap:10px;padding:12px}.ldsp-cdk-user-card::before{display:none}.ldsp-cdk-user-avatar{width:36px;height:36px;flex-shrink:0}.ldsp-cdk-user-info{flex:1;min-width:0}.ldsp-cdk-user-name{font-size:13px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.ldsp-cdk-user-username{font-size:9px}.ldsp-cdk-user-level{font-size:9px;padding:2px 8px;margin-top:4px}.ldsp-cdk-score-card{min-width:65px;padding:10px 12px;flex-shrink:0}.ldsp-cdk-score-label{font-size:8px}.ldsp-cdk-score-value{font-size:18px}}
    @container (max-width:220px){.ldsp-cdk-user-card{gap:8px;padding:10px}.ldsp-cdk-user-avatar{width:32px;height:32px}.ldsp-cdk-user-name{font-size:12px}.ldsp-cdk-user-username{font-size:8px}.ldsp-cdk-user-level{font-size:8px;padding:2px 6px;margin-top:3px}.ldsp-cdk-score-card{min-width:55px;padding:8px}.ldsp-cdk-score-label{font-size:7px}.ldsp-cdk-score-value{font-size:16px}}
    /* CDK 搜索框 */
    .ldsp-cdk-search{display:flex;align-items:center;gap:8px;padding:8px 12px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);transition:border-color .15s}
    .ldsp-cdk-search:focus-within{border-color:var(--accent)}
    .ldsp-cdk-search-icon{color:var(--txt-mut);font-size:14px;flex-shrink:0}
    .ldsp-cdk-search input{flex:1;border:none;background:transparent;font-size:12px;color:var(--txt);outline:none;min-width:0}
    .ldsp-cdk-search input::placeholder{color:var(--txt-mut)}
    .ldsp-cdk-search-clear{width:18px;height:18px;display:flex;align-items:center;justify-content:center;background:var(--bg-el);border:none;border-radius:50%;cursor:pointer;color:var(--txt-mut);font-size:10px;transition:all .15s;flex-shrink:0;opacity:0;pointer-events:none}
    .ldsp-cdk-search-clear.show{opacity:1;pointer-events:auto}
    .ldsp-cdk-search-clear:hover{background:var(--err-bg);color:var(--err)}
    /* CDK 领取记录列表 */
    .ldsp-cdk-list{display:flex;flex-direction:column;gap:8px}
    .ldsp-cdk-item{display:flex;flex-direction:column;gap:8px;padding:12px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);cursor:pointer;transition:all .2s ease;position:relative;overflow:hidden}
    .ldsp-cdk-item::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;background:#06b6d4;opacity:0;transition:opacity .2s}
    .ldsp-cdk-item:hover{border-color:rgba(6,182,212,.4);background:var(--bg-hover);transform:translateX(2px)}
    .ldsp-cdk-item:hover::before{opacity:1}
    .ldsp-cdk-item:active{transform:translateX(2px) scale(.99)}
    .ldsp-cdk-item-header{display:flex;justify-content:space-between;align-items:center;gap:8px}
    .ldsp-cdk-item-name{font-size:13px;font-weight:600;color:var(--txt);flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .ldsp-cdk-item-time{font-size:9px;color:var(--txt-mut);flex-shrink:0;padding:2px 6px;background:var(--bg-hover);border-radius:4px}
    .ldsp-cdk-item-creator{font-size:10px;color:var(--txt-sec);display:flex;align-items:center;gap:4px}
    .ldsp-cdk-item-content{font-size:11px;color:#06b6d4;font-family:monospace;background:rgba(6,182,212,.08);padding:8px 10px;border-radius:var(--r-sm);word-break:break-all;display:flex;align-items:center;justify-content:space-between;gap:8px}
    .ldsp-cdk-item-content-text{flex:1;min-width:0;overflow:hidden;text-overflow:ellipsis}
    .ldsp-cdk-item-copy{display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;width:28px;height:28px;background:rgba(6,182,212,.15);border:none;border-radius:6px;color:#06b6d4;font-size:12px;cursor:pointer;transition:all .15s}
    .ldsp-cdk-item-copy:hover{background:rgba(6,182,212,.25);transform:scale(1.05)}
    /* CDK 数量卡片 */
    .ldsp-cdk-qty-card{display:flex;gap:0;padding:0;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);overflow:hidden}
    .ldsp-cdk-qty-item{flex:1;text-align:center;padding:12px 8px}
    .ldsp-cdk-qty-item.remain{background:linear-gradient(135deg,rgba(34,197,94,.12),rgba(16,185,129,.06));border-right:1px solid var(--border)}
    .ldsp-cdk-qty-item .num{font-size:24px;font-weight:700;line-height:1.2}
    .ldsp-cdk-qty-item.remain .num{color:#22c55e}
    .ldsp-cdk-qty-item.total .num{color:var(--txt)}
    .ldsp-cdk-qty-item .lbl{font-size:9px;color:var(--txt-mut);margin-top:4px}
    /* CDK 详情页 */
    .ldsp-cdk-detail{display:flex;flex-direction:column;gap:12px;flex:1}
    .ldsp-cdk-detail-header{display:flex;align-items:center;gap:8px;margin-bottom:4px}
    .ldsp-cdk-back-btn{padding:6px 12px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:11px;color:var(--txt-sec);cursor:pointer;transition:all .15s}
    .ldsp-cdk-back-btn:hover{background:var(--bg-hover);border-color:var(--accent);color:var(--accent)}
    .ldsp-cdk-detail-title{font-size:14px;font-weight:700;color:var(--txt)}
    .ldsp-cdk-detail-meta{font-size:10px;color:var(--txt-sec);margin-bottom:4px}
    .ldsp-cdk-detail-desc{font-size:11px;color:var(--txt-sec);line-height:1.6;padding:10px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md)}
    .ldsp-cdk-detail-content{padding:12px;background:linear-gradient(135deg,rgba(6,182,212,.08),rgba(14,165,233,.05));border:1px solid rgba(6,182,212,.2);border-radius:var(--r-md)}
    .ldsp-cdk-detail-content-label{font-size:10px;color:var(--txt-mut);margin-bottom:6px}
    .ldsp-cdk-detail-content-value{font-size:14px;color:#06b6d4;font-family:monospace;word-break:break-all;display:flex;align-items:center;gap:8px;flex-wrap:wrap}
    .ldsp-cdk-detail-copy{padding:4px 10px;background:rgba(6,182,212,.12);border:1px solid rgba(6,182,212,.25);border-radius:var(--r-sm);color:#06b6d4;font-size:10px;cursor:pointer;transition:all .15s}
    .ldsp-cdk-detail-copy:hover{background:rgba(6,182,212,.2);border-color:rgba(6,182,212,.4)}
    .ldsp-cdk-detail-info{display:flex;flex-direction:column;gap:6px}
    .ldsp-cdk-detail-row{display:flex;justify-content:space-between;padding:8px 10px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-sm)}
    .ldsp-cdk-detail-row .label{font-size:10px;color:var(--txt-mut)}
    .ldsp-cdk-detail-row .value{font-size:10px;color:var(--txt);font-weight:500}
    .ldsp-cdk-detail-tags{display:flex;flex-wrap:wrap;gap:4px;margin-top:4px}
    .ldsp-cdk-detail-tag{padding:2px 8px;background:rgba(6,182,212,.1);border:1px solid rgba(6,182,212,.2);border-radius:999px;font-size:9px;color:#06b6d4}
    .ldsp-cdk-detail-section{margin-top:4px}
    .ldsp-cdk-detail-section-title{font-size:11px;font-weight:600;color:var(--txt-sec);margin-bottom:8px;display:flex;align-items:center;gap:4px}
    .ldsp-cdk-qty-card{display:flex;gap:12px;padding:14px;background:linear-gradient(135deg,var(--bg-card),var(--bg-hover));border:1px solid var(--border);border-radius:var(--r-md)}
    .ldsp-cdk-qty-item{flex:1;text-align:center}
    .ldsp-cdk-qty-item.remain{background:linear-gradient(135deg,rgba(34,197,94,.1),rgba(16,185,129,.05));border:1px solid rgba(34,197,94,.25);border-radius:var(--r-sm);padding:10px 8px}
    .ldsp-cdk-qty-item .num{font-size:22px;font-weight:700;line-height:1.2}
    .ldsp-cdk-qty-item.remain .num{color:#22c55e}
    .ldsp-cdk-qty-item.total .num{color:var(--txt-sec)}
    .ldsp-cdk-qty-item .lbl{font-size:9px;color:var(--txt-mut);margin-top:4px}
    .ldsp-cdk-qty-divider{width:1px;background:var(--border);margin:0 4px}
    /* CDK 列表头部 */
    .ldsp-cdk-list-header{display:flex;flex-direction:column;gap:8px;margin-bottom:10px}
    .ldsp-cdk-stats-row{display:flex;align-items:center;justify-content:space-between;font-size:10px;color:var(--txt-sec)}
    .ldsp-cdk-stats-row .total{display:flex;align-items:center;gap:4px}
    .ldsp-cdk-stats-row .total strong{color:#06b6d4;font-size:14px;font-weight:700}
    .ldsp-cdk-stats-row .loaded{color:var(--txt-mut)}
    .ldsp-cdk-load-trigger{padding:14px;text-align:center;font-size:10px;color:var(--txt-mut);background:var(--bg-card);border:1px dashed var(--border);border-radius:var(--r-sm);margin-top:8px;cursor:pointer;transition:all .15s}
    .ldsp-cdk-load-trigger:hover{border-color:var(--accent);color:var(--accent)}
    .ldsp-cdk-load-trigger.loading{display:flex;align-items:center;justify-content:center;gap:8px;cursor:default;border-style:solid;border-color:var(--accent)}
    .ldsp-cdk-load-trigger .ldsp-mini-spin{width:14px;height:14px;border-width:2px}
    .ldsp-cdk-loaded-all{padding:10px;text-align:center;font-size:10px;color:var(--txt-mut);background:rgba(34,197,94,.06);border:1px solid rgba(34,197,94,.15);border-radius:var(--r-sm);margin-top:8px}
    .ldsp-cdk-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;text-align:center;flex:1}
    .ldsp-cdk-empty-icon{font-size:36px;margin-bottom:10px}
    .ldsp-cdk-empty-text{font-size:11px;color:var(--txt-mut)}
    /* 自定义弹出框样式 - Toast/Alert/Confirm */
    #ldsp-panel .ldsp-toast-container{position:absolute;top:50px;left:0;right:0;z-index:9999;display:flex;flex-direction:column;align-items:center;gap:8px;pointer-events:none;padding:0 12px;box-sizing:border-box}
    #ldsp-panel .ldsp-toast{display:flex;align-items:flex-start;gap:10px;padding:10px 14px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);box-shadow:0 4px 16px rgba(0,0,0,.2);animation:ldsp-toast-in .3s cubic-bezier(.4,0,.2,1);pointer-events:auto;word-break:break-word;max-width:280px;box-sizing:border-box}
    #ldsp-panel .ldsp-toast.hiding{animation:ldsp-toast-out .25s cubic-bezier(.4,0,.2,1) forwards}
    #ldsp-panel .ldsp-toast-icon{font-size:16px;flex-shrink:0;line-height:1}
    #ldsp-panel .ldsp-toast-content{flex:1;min-width:0}
    #ldsp-panel .ldsp-toast-title{font-size:11px;font-weight:600;color:var(--txt);margin-bottom:2px}
    #ldsp-panel .ldsp-toast-message{font-size:10px;color:var(--txt-sec);line-height:1.5}
    #ldsp-panel .ldsp-toast-close{flex-shrink:0;width:18px;height:18px;display:flex;align-items:center;justify-content:center;background:transparent;border:none;color:var(--txt-mut);cursor:pointer;border-radius:4px;font-size:14px;transition:all .15s;margin:-2px -4px -2px 0}
    #ldsp-panel .ldsp-toast-close:hover{background:var(--bg-el);color:var(--txt-sec)}
    #ldsp-panel .ldsp-toast.success{border-left:3px solid var(--ok)}
    #ldsp-panel .ldsp-toast.success .ldsp-toast-icon{color:var(--ok)}
    #ldsp-panel .ldsp-toast.error{border-left:3px solid var(--err)}
    #ldsp-panel .ldsp-toast.error .ldsp-toast-icon{color:var(--err)}
    #ldsp-panel .ldsp-toast.warning{border-left:3px solid var(--warn)}
    #ldsp-panel .ldsp-toast.warning .ldsp-toast-icon{color:var(--warn)}
    #ldsp-panel .ldsp-toast.info{border-left:3px solid var(--accent)}
    #ldsp-panel .ldsp-toast.info .ldsp-toast-icon{color:var(--accent)}
    @keyframes ldsp-toast-in{from{opacity:0;transform:translateY(-10px) scale(.95)}to{opacity:1;transform:translateY(0) scale(1)}}
    @keyframes ldsp-toast-out{from{opacity:1;transform:translateY(0) scale(1)}to{opacity:0;transform:translateY(-10px) scale(.95)}}
    .ldsp-dialog-overlay{position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;z-index:9999;border-radius:var(--r-lg)}
    .ldsp-dialog{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-lg);padding:18px 20px;width:85%;max-width:280px;text-align:center;box-shadow:0 8px 32px rgba(0,0,0,.25)}
    .ldsp-dialog-icon{font-size:32px;margin-bottom:12px}
    .ldsp-dialog-title{font-size:13px;font-weight:700;color:var(--txt);margin-bottom:6px}
    .ldsp-dialog-message{font-size:11px;color:var(--txt-sec);line-height:1.6;margin-bottom:16px;word-break:break-word}
    .ldsp-dialog-actions{display:flex;gap:8px}
    .ldsp-dialog-btn{flex:1;padding:9px 14px;border:none;border-radius:var(--r-sm);font-size:11px;font-weight:600;cursor:pointer;transition:background .15s,opacity .15s}
    .ldsp-dialog-btn-cancel{background:var(--bg-el);color:var(--txt-sec);border:1px solid var(--border)}
    .ldsp-dialog-btn-cancel:hover{background:var(--bg-hover);border-color:var(--txt-mut)}
    .ldsp-dialog-btn-ok{background:var(--accent);color:#fff}
    .ldsp-dialog-btn-ok:hover{opacity:.9;box-shadow:0 4px 12px rgba(107,140,239,.3)}
    .ldsp-dialog-btn-danger{background:var(--err);color:#fff}
    .ldsp-dialog-btn-danger:hover{opacity:.9;box-shadow:0 4px 12px rgba(239,68,68,.3)}
    /* 禁用全局transition对弹窗的影响 - 使用高权重选择器 */
    #ldsp-panel .ldsp-toast-container,#ldsp-panel .ldsp-toast-container *{transition:none!important;animation:none!important}
    #ldsp-panel .ldsp-toast{transition:none!important;animation:none!important}
    #ldsp-panel .ldsp-dialog-overlay,#ldsp-panel .ldsp-dialog-overlay *{transition:none!important}
    #ldsp-panel .ldsp-dialog{transition:none!important}
    @keyframes ldsp-fade-in{from{opacity:0}to{opacity:1}}
    /* LDC 积分面板样式 - 与客户端主题风格统一 */
    .ldsp-ldc-overlay{position:absolute;top:0;left:0;right:0;bottom:0;background:var(--bg);border-radius:0 0 var(--r-lg) var(--r-lg);z-index:10;display:none;flex-direction:column;overflow:hidden}
    .ldsp-ldc-overlay.show{display:flex}
    .ldsp-ldc-header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;background:var(--bg-card);border-bottom:1px solid var(--border);flex-shrink:0}
    .ldsp-ldc-title{font-size:13px;font-weight:700;display:flex;align-items:center;gap:6px;color:var(--txt)}
    .ldsp-ldc-header-actions{display:flex;align-items:center;gap:8px}
    .ldsp-ldc-link{color:var(--accent)!important;text-decoration:none!important;font-size:10px!important;font-weight:600!important;opacity:.9;transition:opacity .15s}
    .ldsp-ldc-link:hover{opacity:1}
    .ldsp-ldc-refresh{width:24px;height:24px;display:flex;align-items:center;justify-content:center;background:var(--bg-el);border:1px solid var(--border);border-radius:6px;color:var(--txt-sec);cursor:pointer;transition:background .15s,border-color .15s,color .15s;-webkit-tap-highlight-color:transparent;touch-action:manipulation}
    .ldsp-ldc-refresh:hover{background:var(--bg-hover);border-color:var(--accent);color:var(--accent)}
    .ldsp-ldc-refresh.spinning svg{animation:ldsp-spin .8s linear infinite}
    .ldsp-ldc-refresh svg{width:12px;height:12px;fill:currentColor}
    .ldsp-ldc-close{width:24px;height:24px;display:flex;align-items:center;justify-content:center;background:var(--bg-el);border:1px solid var(--border);border-radius:6px;font-size:12px;color:var(--txt-sec);cursor:pointer;transition:background .15s,color .15s;-webkit-tap-highlight-color:transparent;touch-action:manipulation}
    .ldsp-ldc-close:hover{background:var(--err-bg);color:var(--err);border-color:var(--err)}
    /* LDC Tabs */
    .ldsp-ldc-tabs{display:flex;background:var(--bg-card);border-bottom:1px solid var(--border);flex-shrink:0}
    .ldsp-ldc-tab{flex:1;padding:10px 8px;font-size:11px;font-weight:600;color:var(--txt-mut);text-align:center;cursor:pointer;border-bottom:2px solid transparent;transition:all .15s;-webkit-tap-highlight-color:transparent}
    .ldsp-ldc-tab:hover{color:var(--txt-sec);background:var(--bg-hover)}
    .ldsp-ldc-tab.active{color:var(--accent);border-bottom-color:var(--accent);background:transparent}
    .ldsp-ldc-body{flex:1;overflow-y:auto;padding:12px;background:var(--bg);display:flex;flex-direction:column;gap:10px;min-height:0}
    .ldsp-ldc-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;color:var(--txt-mut);flex:1}
    .ldsp-ldc-loading .ldsp-spinner{margin-bottom:10px}
    .ldsp-ldc-error{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:30px 20px;text-align:center;color:var(--err);flex:1}
    .ldsp-ldc-error-icon{font-size:32px;margin-bottom:10px}
    .ldsp-ldc-error-msg{font-size:11px;margin-bottom:12px;color:var(--txt-sec)}
    .ldsp-ldc-login-btn{display:inline-block;padding:10px 20px;background:var(--grad);color:#fff!important;text-decoration:none!important;border-radius:var(--r-sm);font-size:12px;font-weight:600;transition:all .2s;box-shadow:0 2px 8px rgba(107,140,239,.3)}
    .ldsp-ldc-login-btn:hover{transform:translateY(-1px);box-shadow:0 4px 12px rgba(107,140,239,.4)}
    .ldsp-ldc-retry-btn{margin-top:8px;padding:8px 16px;background:var(--accent);color:#fff;border:none;border-radius:var(--r-sm);font-size:11px;cursor:pointer;transition:opacity .15s}
    .ldsp-ldc-retry-btn:hover{opacity:.85}
    /* iOS Safari 限制提示 */
    .ldsp-ldc-ios-guide{display:block;padding:12px;text-align:center;overflow-y:auto;-webkit-overflow-scrolling:touch}
    .ldsp-ldc-ios-icon{font-size:28px;margin-bottom:6px;display:block}
    .ldsp-ldc-ios-title{font-size:13px;font-weight:700;color:var(--txt);margin-bottom:8px}
    .ldsp-ldc-ios-desc{font-size:10px;color:var(--txt-sec);margin-bottom:12px;line-height:1.5;padding:0 4px}
    .ldsp-ldc-ios-desc strong{color:var(--txt);font-weight:600}
    .ldsp-ldc-ios-solutions{margin-bottom:10px}
    .ldsp-ldc-ios-solution{padding:10px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);text-align:left;margin-bottom:8px}
    .ldsp-ldc-ios-solution.alt{background:var(--bg-el);border-style:dashed;margin-bottom:0}
    .ldsp-ldc-ios-solution-title{font-size:11px;font-weight:600;color:var(--txt);margin-bottom:4px}
    .ldsp-ldc-ios-solution-desc{font-size:10px;color:var(--txt-sec);margin-bottom:8px;line-height:1.4}
    .ldsp-ldc-ios-solution.alt .ldsp-ldc-ios-solution-desc{margin-bottom:0}
    .ldsp-ldc-ios-btn{display:block;padding:10px 16px;border-radius:var(--r-sm);font-size:11px;font-weight:600;cursor:pointer;text-decoration:none!important;border:none;width:100%;text-align:center;box-sizing:border-box;-webkit-appearance:none;-webkit-tap-highlight-color:transparent}
    .ldsp-ldc-ios-btn.primary{background:var(--grad);color:#fff!important;box-shadow:0 2px 6px rgba(107,140,239,.25)}
    .ldsp-ldc-ios-tip{font-size:9px;color:var(--txt-mut);padding:8px;background:var(--bg-el);border-radius:var(--r-sm);line-height:1.4}
    /* 概览页 - 余额卡片 */
    .ldsp-ldc-balance-card{position:relative;background:linear-gradient(145deg,rgba(139,92,246,.14) 0%,rgba(107,140,239,.16) 50%,rgba(59,130,246,.1) 100%);border:1px solid rgba(139,92,246,.2);border-radius:var(--r-lg);padding:16px 18px 12px;box-shadow:0 4px 16px rgba(139,92,246,.1),0 2px 4px rgba(0,0,0,.05);overflow:hidden;flex-shrink:0}
    .ldsp-ldc-balance-card::before{content:'';position:absolute;top:0;left:0;right:0;height:2px;background:linear-gradient(90deg,transparent,rgba(139,92,246,.6) 30%,rgba(107,140,239,.8) 50%,rgba(139,92,246,.6) 70%,transparent)}
    .ldsp-ldc-balance-card::after{content:'';position:absolute;top:-50%;right:-30%;width:60%;height:150%;background:radial-gradient(ellipse,rgba(255,255,255,.04) 0%,transparent 70%);pointer-events:none}
    .ldsp-ldc-balance-main{display:flex;align-items:center;justify-content:space-between;gap:12px;position:relative;z-index:1;flex-wrap:wrap}
    .ldsp-ldc-balance-left{flex:1;min-width:80px}
    .ldsp-ldc-balance-label{font-size:10px;color:var(--txt-mut);margin-bottom:2px}
    .ldsp-ldc-balance-value{font-size:28px;font-weight:700;color:var(--accent);line-height:1.2}
    .ldsp-ldc-balance-right{text-align:right;flex-shrink:0;display:flex;flex-direction:column;gap:4px;min-width:0}
    .ldsp-ldc-balance-sub{font-size:10px;color:var(--txt-mut);display:flex;align-items:center;gap:4px;justify-content:flex-end;flex-wrap:wrap}
    .ldsp-ldc-balance-sub-value{font-weight:600;color:var(--txt-sec)}
    .ldsp-ldc-balance-footer{display:flex;justify-content:flex-end;margin-top:10px;padding-top:8px;border-top:1px solid rgba(139,92,246,.1);position:relative;z-index:1}
    .ldsp-ldc-balance-time{font-size:9px;color:var(--txt-mut);opacity:.6}
    .ldsp-ldc-balance-estimate{display:flex;align-items:center;gap:4px;font-size:10px;color:var(--txt-mut);justify-content:flex-end;flex-wrap:nowrap}
    .ldsp-ldc-balance-estimate-value{font-weight:600}
    .ldsp-ldc-balance-estimate-value.positive{color:var(--ok)}
    .ldsp-ldc-balance-estimate-value.negative{color:var(--err)}
    .ldsp-ldc-balance-estimate-value.neutral{color:var(--txt-mut)}
    .ldsp-ldc-balance-estimate-tip{display:inline-flex;align-items:center;justify-content:center;width:12px;height:12px;border-radius:50%;background:transparent;border:1px solid var(--txt-mut);opacity:.5;cursor:help;font-size:8px;color:var(--txt-mut);transition:opacity .15s;flex-shrink:0}
    .ldsp-ldc-balance-estimate-tip:hover{opacity:.8}
    /* 概览页 - 统计网格 */
    .ldsp-ldc-stats-grid{display:grid;grid-template-columns:1fr 1fr;gap:8px;flex-shrink:0}
    .ldsp-ldc-stat-card{display:flex;align-items:center;gap:10px;padding:12px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md)}
    .ldsp-ldc-stat-card.income{border-left:3px solid var(--ok)}
    .ldsp-ldc-stat-card.expense{border-left:3px solid var(--err)}
    .ldsp-ldc-stat-icon{font-size:18px}
    .ldsp-ldc-stat-info{flex:1;min-width:0}
    .ldsp-ldc-stat-label{font-size:10px;color:var(--txt-mut)}
    .ldsp-ldc-stat-num{font-size:14px;font-weight:700;color:var(--txt)}
    .ldsp-ldc-stat-card.income .ldsp-ldc-stat-num{color:var(--ok)}
    .ldsp-ldc-stat-card.expense .ldsp-ldc-stat-num{color:var(--err)}
    /* 概览页 - 图表区 */
    .ldsp-ldc-section{display:flex;flex-direction:column;gap:8px;flex-shrink:0}
    .ldsp-ldc-section-header{display:flex;justify-content:space-between;align-items:center}
    .ldsp-ldc-section-title{font-size:11px;font-weight:600;color:var(--txt-sec)}
    .ldsp-ldc-section-summary{font-size:10px;display:flex;gap:8px}
    .ldsp-ldc-section-summary em{font-style:normal;font-weight:600}
    .ldsp-ldc-section-summary em.income{color:var(--ok)}
    .ldsp-ldc-section-summary em.expense{color:var(--err)}
    .ldsp-ldc-chart{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:12px}
    .ldsp-ldc-chart-bars{display:flex;justify-content:space-around;align-items:flex-end;height:80px;gap:4px;margin-bottom:8px}
    .ldsp-ldc-chart-col{display:flex;flex-direction:column;align-items:center;flex:1;height:100%}
    .ldsp-ldc-chart-bar-group{flex:1;display:flex;align-items:flex-end;gap:2px;width:100%}
    .ldsp-ldc-chart-bar{width:45%;min-height:2px;border-radius:2px 2px 0 0;transition:height .3s}
    .ldsp-ldc-chart-bar.income{background:linear-gradient(to top,var(--ok),rgba(74,222,128,.6))}
    .ldsp-ldc-chart-bar.expense{background:linear-gradient(to top,var(--err),rgba(248,113,113,.6))}
    .ldsp-ldc-chart-label{font-size:9px;color:var(--txt-mut);margin-top:4px}
    .ldsp-ldc-chart-legend{display:flex;justify-content:center;gap:16px;padding-top:8px;border-top:1px solid var(--border)}
    .ldsp-ldc-legend-item{font-size:9px;color:var(--txt-sec);display:flex;align-items:center;gap:4px}
    .ldsp-ldc-legend-item i{width:8px;height:8px;border-radius:2px}
    .ldsp-ldc-legend-item.income i{background:var(--ok)}
    .ldsp-ldc-legend-item.expense i{background:var(--err)}
    .ldsp-ldc-footer{display:flex;justify-content:center;padding-top:8px;border-top:1px solid var(--border)}
    .ldsp-ldc-update-time{font-size:9px;color:var(--txt-mut)}
    .ldsp-ldc-empty{text-align:center;padding:16px;color:var(--txt-mut);font-size:11px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md)}
    /* 概览页 - 信息卡片区 */
    .ldsp-ldc-info-section{display:flex;flex-direction:column;gap:8px;margin-top:4px;flex-shrink:0}
    .ldsp-ldc-info-card{display:flex;align-items:center;gap:12px;padding:12px 14px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);cursor:pointer;text-decoration:none!important;transition:all .2s ease;position:relative;overflow:hidden}
    .ldsp-ldc-info-card::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--info-accent,var(--accent));opacity:.6;transition:opacity .2s}
    .ldsp-ldc-info-card:hover{border-color:var(--info-accent,var(--accent));background:linear-gradient(90deg,rgba(var(--info-accent-rgb,107,140,239),.06),transparent);transform:translateX(2px)}
    .ldsp-ldc-info-card:hover::before{opacity:1}
    .ldsp-ldc-info-card:active{transform:translateX(1px);transition-duration:.1s}
    .ldsp-ldc-info-card.faq{--info-accent:#8b5cf6;--info-accent-rgb:139,92,246}
    .ldsp-ldc-info-icon{font-size:20px;flex-shrink:0;filter:drop-shadow(0 2px 4px rgba(0,0,0,.1))}
    .ldsp-ldc-info-content{flex:1;min-width:0}
    .ldsp-ldc-info-title{font-size:11px;font-weight:600;color:var(--txt);margin-bottom:2px;display:flex;align-items:center;gap:6px}
    .ldsp-ldc-info-desc{font-size:9px;color:var(--txt-mut);line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .ldsp-ldc-info-arrow{font-size:12px;color:var(--txt-mut);transition:transform .2s,color .2s;flex-shrink:0}
    .ldsp-ldc-info-card:hover .ldsp-ldc-info-arrow{transform:translateX(3px);color:var(--info-accent,var(--accent))}
    /* 支持页面 */
    .ldsp-ldc-support{display:flex;flex-direction:column;gap:14px;flex:1;min-height:200px}
    .ldsp-ldc-support-header{text-align:center;padding:12px 10px;background:linear-gradient(135deg,rgba(239,68,68,.06),rgba(249,115,22,.04),rgba(107,140,239,.06));border-radius:var(--r-md);border:1px solid rgba(239,68,68,.1)}
    .ldsp-ldc-support-title{font-size:15px;font-weight:700;color:var(--txt);margin-bottom:6px;display:flex;align-items:center;justify-content:center;gap:8px}
    .ldsp-ldc-support-desc{font-size:11px;color:var(--txt-sec);line-height:1.6}
    .ldsp-ldc-support-grid{display:grid;grid-template-columns:1fr 1fr;gap:10px}
    .ldsp-ldc-support-card{display:flex;flex-direction:column;align-items:center;padding:18px 14px 16px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);cursor:pointer;transition:all .3s cubic-bezier(.4,0,.2,1);text-decoration:none!important;position:relative;overflow:hidden}
    .ldsp-ldc-support-card::before{content:'';position:absolute;top:0;left:0;right:0;height:3px;background:linear-gradient(90deg,transparent,var(--card-accent,var(--border)),transparent);transition:all .3s cubic-bezier(.4,0,.2,1)}
    .ldsp-ldc-support-card::after{content:'';position:absolute;inset:0;background:radial-gradient(circle at 50% 0%,var(--card-accent,transparent) 0%,transparent 70%);opacity:0;transition:opacity .3s}
    .ldsp-ldc-support-card:hover{border-color:var(--card-accent,var(--accent));transform:translateY(-4px) scale(1.02);box-shadow:0 12px 28px var(--card-glow,rgba(0,0,0,.15))}
    .ldsp-ldc-support-card:hover::before{height:4px;background:var(--card-accent,var(--accent))}
    .ldsp-ldc-support-card:hover::after{opacity:.08}
    .ldsp-ldc-support-card:active{transform:translateY(-2px) scale(1.01);transition-duration:.1s}
    .ldsp-ldc-support-card.tier-1{--card-accent:#10b981;--card-glow:rgba(16,185,129,.25)}
    .ldsp-ldc-support-card.tier-2{--card-accent:#3b82f6;--card-glow:rgba(59,130,246,.25)}
    .ldsp-ldc-support-card.tier-3{--card-accent:#f59e0b;--card-glow:rgba(245,158,11,.28)}
    .ldsp-ldc-support-card.tier-4{--card-accent:#ef4444;--card-glow:rgba(239,68,68,.28);background:linear-gradient(145deg,var(--bg-card),rgba(239,68,68,.05))}
    .ldsp-ldc-support-card.tier-3::before,.ldsp-ldc-support-card.tier-4::before{animation:ldsp-shimmer 2s ease-in-out infinite}
    @keyframes ldsp-shimmer{0%,100%{opacity:.8}50%{opacity:1}}
    .ldsp-ldc-support-icon{font-size:34px;margin-bottom:10px;filter:drop-shadow(0 3px 8px var(--card-glow,rgba(0,0,0,.1)));transition:transform .3s cubic-bezier(.4,0,.2,1),filter .3s;will-change:transform}
    .ldsp-ldc-support-card:hover .ldsp-ldc-support-icon{animation:ldsp-icon-breath 1.8s cubic-bezier(.45,.05,.55,.95) infinite}
    @keyframes ldsp-icon-breath{0%,100%{transform:scale(1.12) translateY(-2px)}50%{transform:scale(1.18) translateY(-4px)}}
    .ldsp-ldc-support-name{font-size:12px;font-weight:600;color:var(--txt);margin-bottom:8px;transition:color .3s;letter-spacing:.3px}
    .ldsp-ldc-support-card:hover .ldsp-ldc-support-name{color:var(--card-accent,var(--txt))}
    .ldsp-ldc-support-amount{font-size:20px;font-weight:700;color:var(--card-accent,var(--accent));display:flex;align-items:baseline;gap:4px;text-shadow:0 1px 2px var(--card-glow,transparent)}
    .ldsp-ldc-support-amount span{font-size:11px;font-weight:500;color:var(--txt-mut);text-shadow:none}
    .ldsp-ldc-support-badge{position:absolute;top:8px;right:8px;padding:3px 8px;background:linear-gradient(135deg,var(--card-accent,var(--accent)),color-mix(in srgb,var(--card-accent,var(--accent)) 70%,#000));color:#fff;font-size:9px;font-weight:600;border-radius:10px;box-shadow:0 2px 8px var(--card-glow,rgba(0,0,0,.2));animation:ldsp-badge-float 3s ease-in-out infinite;transform-origin:center center;transition:all .2s cubic-bezier(.4,0,.2,1)}
    .ldsp-ldc-support-card:hover .ldsp-ldc-support-badge{animation:ldsp-badge-wiggle .5s ease-in-out;transform:scale(1.1);box-shadow:0 4px 12px var(--card-glow,rgba(0,0,0,.3))}
    @keyframes ldsp-badge-float{0%,100%{transform:translateY(0) rotate(0deg)}25%{transform:translateY(-2px) rotate(2deg)}75%{transform:translateY(1px) rotate(-1deg)}}
    @keyframes ldsp-badge-wiggle{0%{transform:scale(1.1) rotate(0deg)}15%{transform:scale(1.15) rotate(-12deg)}30%{transform:scale(1.12) rotate(10deg)}45%{transform:scale(1.15) rotate(-8deg)}60%{transform:scale(1.12) rotate(6deg)}75%{transform:scale(1.13) rotate(-3deg)}100%{transform:scale(1.1) rotate(0deg)}}
    .ldsp-ldc-support-footer{text-align:center;padding:12px;background:linear-gradient(135deg,var(--bg-card),var(--bg-el));border:1px solid var(--border);border-radius:var(--r-md)}
    .ldsp-ldc-support-footer-text{font-size:10px;color:var(--txt-mut);line-height:1.7}
    .ldsp-ldc-support-footer-text em{font-style:normal;color:var(--accent);font-weight:600}
    .ldsp-ldc-support-heart{display:inline-block;animation:ldsp-heartbeat 1.2s ease-in-out infinite;filter:drop-shadow(0 0 4px rgba(239,68,68,.4))}
    @keyframes ldsp-heartbeat{0%,100%{transform:scale(1)}14%{transform:scale(1.2)}28%{transform:scale(1)}42%{transform:scale(1.15)}70%{transform:scale(1)}}
    /* GitHub Star 卡片 - 深色模式(默认) */
    .ldsp-github-star-card{display:flex;align-items:center;gap:14px;padding:14px 16px;background:linear-gradient(135deg,#161b22 0%,#0d1117 100%);border:1px solid #30363d;border-radius:var(--r-md);cursor:pointer;text-decoration:none;transition:all .25s cubic-bezier(.4,0,.2,1);position:relative;overflow:hidden}
    .ldsp-github-star-card::before{content:'';position:absolute;inset:0;background:linear-gradient(135deg,rgba(88,166,255,.1) 0%,rgba(255,215,0,.1) 100%);opacity:0;transition:opacity .25s}
    .ldsp-github-star-card:hover{border-color:#58a6ff;transform:translateY(-2px);box-shadow:0 8px 24px rgba(88,166,255,.2)}
    .ldsp-github-star-card:hover::before{opacity:1}
    .ldsp-github-star-card:hover .ldsp-github-icon{transform:rotate(-8deg) scale(1.08)}
    .ldsp-github-star-card:hover .ldsp-github-star-icon{animation:ldsp-star-bounce .6s ease-in-out}
    .ldsp-github-icon-wrap{flex-shrink:0;width:36px;height:36px;display:flex;align-items:center;justify-content:center}
    .ldsp-github-icon{width:32px;height:32px;fill:#f0f6fc;transition:all .25s}
    .ldsp-github-star-icon{font-size:16px;line-height:1;filter:drop-shadow(0 0 4px rgba(255,215,0,.4))}
    .ldsp-github-content{flex:1;min-width:0;position:relative;z-index:1}
    .ldsp-github-title{font-size:13px;font-weight:600;color:#f0f6fc;margin-bottom:3px;display:flex;align-items:center;gap:6px}
    .ldsp-github-desc{font-size:10px;color:#8b949e;line-height:1.5}
    .ldsp-github-arrow{flex-shrink:0;font-size:16px;color:#58a6ff;transition:all .25s;opacity:.7}
    .ldsp-github-star-card:hover .ldsp-github-arrow{transform:translateX(4px);opacity:1}
    @keyframes ldsp-star-bounce{0%,100%{transform:scale(1) rotate(0deg)}25%{transform:scale(1.3) rotate(-15deg)}50%{transform:scale(1) rotate(10deg)}75%{transform:scale(1.15) rotate(-5deg)}}
    /* GitHub Star 卡片 - 浅色模式 */
    #ldsp-panel.light .ldsp-github-star-card{background:linear-gradient(135deg,#ffffff 0%,#f6f8fa 100%);border-color:#d0d7de}
    #ldsp-panel.light .ldsp-github-star-card:hover{border-color:#0969da;box-shadow:0 8px 24px rgba(9,105,218,.15)}
    #ldsp-panel.light .ldsp-github-star-card::before{background:linear-gradient(135deg,rgba(88,166,255,.08) 0%,rgba(255,215,0,.08) 100%)}
    #ldsp-panel.light .ldsp-github-icon{fill:#24292f}
    #ldsp-panel.light .ldsp-github-title{color:#24292f}
    #ldsp-panel.light .ldsp-github-desc{color:#656d76}
    #ldsp-panel.light .ldsp-github-arrow{color:#0969da}
    #ldsp-panel.light .ldsp-github-star-icon{filter:drop-shadow(0 1px 2px rgba(0,0,0,.1))}
    /* 交易记录 */
    /* 交易筛选器 */
    .ldsp-ldc-filter-section{display:flex;flex-direction:column;gap:8px;padding-bottom:10px;border-bottom:1px solid var(--border);flex-shrink:0}
    .ldsp-ldc-filter-row{display:flex;align-items:flex-start;gap:8px}
    .ldsp-ldc-filter-label{font-size:10px;color:var(--txt-mut);min-width:28px;padding-top:5px;flex-shrink:0}
    .ldsp-ldc-filter-chips{display:flex;gap:5px;flex-wrap:nowrap;overflow-x:auto;-webkit-overflow-scrolling:touch;scrollbar-width:none}
    .ldsp-ldc-filter-chips::-webkit-scrollbar{display:none}
    .ldsp-ldc-filter-chips-wrap{flex-wrap:wrap}
    .ldsp-ldc-filter-chip{padding:5px 10px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:10px;color:var(--txt-sec);cursor:pointer;transition:all .15s;white-space:nowrap;flex-shrink:0;-webkit-tap-highlight-color:transparent}
    .ldsp-ldc-filter-chip:hover{border-color:var(--txt-mut);color:var(--txt)}
    .ldsp-ldc-filter-chip.active{background:rgba(107,140,239,.12);border-color:var(--accent);color:var(--accent);font-weight:600}
    /* 交易列表 */
    .ldsp-ldc-trans-content{display:flex;flex-direction:column;gap:8px;flex:1;min-height:0}
    .ldsp-ldc-trans-summary{font-size:10px;color:var(--txt-mut)}
    .ldsp-ldc-trans-list{display:flex;flex-direction:column;gap:6px;overflow-y:auto;flex:1;min-height:120px}
    .ldsp-ldc-trans-item{display:flex;align-items:center;gap:10px;padding:10px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);cursor:pointer;transition:all .15s;-webkit-tap-highlight-color:transparent;flex-shrink:0}
    .ldsp-ldc-trans-item:hover{border-color:var(--accent);background:var(--bg-hover)}
    .ldsp-ldc-trans-item:active{transform:scale(.98)}
    .ldsp-ldc-trans-icon{font-size:16px;flex-shrink:0;width:24px;height:24px;display:flex;align-items:center;justify-content:center;background:var(--bg-el);border-radius:6px}
    .ldsp-ldc-trans-info{flex:1;min-width:0}
    .ldsp-ldc-trans-name{font-size:12px;font-weight:600;color:var(--txt);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .ldsp-ldc-trans-meta{font-size:10px;color:var(--txt-mut);margin-top:2px;display:flex;gap:6px;align-items:center}
    .ldsp-ldc-trans-type{padding:2px 6px;border-radius:3px;font-size:9px;font-weight:500}
    /* 交易类型颜色 - 收益绿/消耗红/转移蓝/社区紫/在线青 */
    .ldsp-ldc-trans-type.type-receive{background:rgba(74,222,128,.15);color:#22c55e}
    .ldsp-ldc-trans-type.type-payment{background:rgba(248,113,113,.15);color:#ef4444}
    .ldsp-ldc-trans-type.type-transfer{background:rgba(96,165,250,.15);color:#3b82f6}
    .ldsp-ldc-trans-type.type-community{background:rgba(192,132,252,.15);color:#a855f7}
    .ldsp-ldc-trans-type.type-online{background:rgba(45,212,191,.15);color:#14b8a6}
    .ldsp-ldc-trans-type.type-default{background:var(--bg-el);color:var(--txt-sec)}
    .ldsp-ldc-trans-amount{font-size:14px;font-weight:700;flex-shrink:0}
    .ldsp-ldc-trans-amount.income{color:var(--ok)}
    .ldsp-ldc-trans-amount.expense{color:var(--err)}
    .ldsp-ldc-load-more{width:100%;padding:10px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-md);font-size:11px;font-weight:600;color:var(--txt-sec);cursor:pointer;transition:all .15s;flex-shrink:0;display:flex;align-items:center;justify-content:center;gap:6px}
    .ldsp-ldc-load-more:hover{background:var(--bg-hover);border-color:var(--accent);color:var(--accent)}
    .ldsp-ldc-load-more:disabled{opacity:.6;cursor:not-allowed}
    .ldsp-ldc-load-more.loading{pointer-events:none}
    .ldsp-ldc-load-sentinel{width:100%;min-height:40px;display:flex;align-items:center;justify-content:center;flex-shrink:0}
    .ldsp-ldc-loading-more{display:flex;align-items:center;justify-content:center;gap:8px;padding:10px;font-size:11px;color:var(--txt-mut)}
    .ldsp-ldc-empty-state{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:30px 20px;flex:1;background:var(--bg-card);border:1px dashed var(--border);border-radius:var(--r-md)}
    .ldsp-ldc-empty-icon{font-size:36px;margin-bottom:10px;opacity:.8}
    .ldsp-ldc-empty-text{font-size:12px;color:var(--txt-mut);text-align:center}
    .ldsp-ldc-empty-hint{font-size:10px;color:var(--txt-mut);margin-top:6px;opacity:.7}
    /* 交易详情 */
    .ldsp-ldc-detail{display:flex;flex-direction:column;gap:12px}
    .ldsp-ldc-detail-header{display:flex;align-items:center;gap:10px;padding-bottom:10px;border-bottom:1px solid var(--border)}
    .ldsp-ldc-back-btn{padding:6px 10px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:11px;color:var(--txt-sec);cursor:pointer;transition:all .15s}
    .ldsp-ldc-back-btn:hover{background:var(--bg-hover);border-color:var(--accent);color:var(--accent)}
    .ldsp-ldc-detail-header span{font-size:13px;font-weight:600;color:var(--txt)}
    .ldsp-ldc-detail-amount{text-align:center;padding:16px;border-radius:var(--r-md)}
    .ldsp-ldc-detail-amount.income-bg{background:linear-gradient(135deg,rgba(74,222,128,.08),rgba(34,197,94,.12));border:1px solid rgba(74,222,128,.2)}
    .ldsp-ldc-detail-amount.expense-bg{background:linear-gradient(135deg,rgba(248,113,113,.08),rgba(239,68,68,.12));border:1px solid rgba(248,113,113,.2)}
    .ldsp-ldc-detail-amount-value{font-size:28px;font-weight:700}
    .ldsp-ldc-detail-amount-value.income{color:var(--ok)}
    .ldsp-ldc-detail-amount-value.expense{color:var(--err)}
    .ldsp-ldc-detail-amount-label{font-size:11px;color:var(--txt-mut);margin-top:4px}
    .ldsp-ldc-detail-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);overflow:hidden}
    .ldsp-ldc-detail-row{display:flex;justify-content:space-between;align-items:flex-start;padding:10px 12px;border-bottom:1px solid var(--border)}
    .ldsp-ldc-detail-row:last-child{border-bottom:none}
    .ldsp-ldc-detail-row .label{font-size:11px;color:var(--txt-mut);flex-shrink:0}
    .ldsp-ldc-detail-row .value{font-size:11px;color:var(--txt);text-align:right;word-break:break-all;margin-left:10px}
    .ldsp-ldc-detail-row .value.mono{font-family:monospace;font-size:10px}
    .ldsp-ldc-detail-row .value.status-success{color:var(--ok);font-weight:600}
    .ldsp-ldc-detail-row .value.link{color:var(--accent);text-decoration:none;max-width:160px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;display:block}
    .ldsp-ldc-detail-row .value.link:hover{text-decoration:underline}
    /* 小卖部样式 */
    .ldsp-shop{display:flex;flex-direction:column;gap:10px;flex:1;min-height:0}
    .ldsp-shop-header{display:flex;align-items:center;justify-content:space-between;gap:6px;flex-shrink:0}
    .ldsp-shop-tabs{display:flex;gap:3px;flex:1;min-width:0}
    .ldsp-shop-tab{padding:5px 8px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:10px;font-weight:600;color:var(--txt-sec);cursor:pointer;transition:all .15s;white-space:nowrap;flex:1;text-align:center;min-width:0}
    .ldsp-shop-tab:hover{border-color:var(--txt-mut);color:var(--txt)}
    .ldsp-shop-tab.active{background:rgba(107,140,239,.12);border-color:var(--accent);color:var(--accent)}
    .ldsp-shop-add-btn{padding:6px 12px;background:var(--grad);border:none;border-radius:var(--r-sm);font-size:10px;font-weight:600;color:#fff;cursor:pointer;transition:all .15s;display:flex;align-items:center;gap:4px}
    .ldsp-shop-add-btn:hover{opacity:.9;transform:translateY(-1px)}
    .ldsp-shop-filter{display:flex;gap:6px;flex-wrap:wrap;padding:8px 0;flex-shrink:0;border-bottom:1px solid var(--border);margin-bottom:4px}
    .ldsp-shop-filter-chip{padding:5px 10px;background:var(--bg-el);border:1px solid var(--border);border-radius:999px;font-size:10px;color:var(--txt-sec);cursor:pointer;transition:all .15s;white-space:nowrap}
    .ldsp-shop-filter-chip:hover{border-color:var(--txt-mut);color:var(--txt);background:var(--bg-hover)}
    .ldsp-shop-filter-chip.active{background:rgba(107,140,239,.15);border-color:var(--accent);color:var(--accent);font-weight:600}
    .ldsp-shop-count{font-size:10px;color:var(--txt-mut);padding:4px 0 6px;flex-shrink:0}
    .ldsp-shop-count strong{color:var(--accent);font-weight:600}
    .ldsp-shop-grid{display:flex;flex-wrap:wrap;gap:8px;overflow-y:auto;flex:1;min-height:0;padding-bottom:4px;align-content:flex-start}
    .ldsp-shop-load-sentinel{width:100%;flex-shrink:0;min-height:20px}
    .ldsp-shop-loading-more{width:100%;display:flex;align-items:center;justify-content:center;gap:6px;padding:12px 0;color:var(--txt-sec);font-size:11px}
    .ldsp-shop-loading-more .ldsp-spinner{width:16px;height:16px;border-width:2px}
    .ldsp-shop-load-more-hint{width:100%;text-align:center;padding:8px 0;color:var(--txt-mut);font-size:10px}
    .ldsp-shop-loaded-all{width:100%;text-align:center;padding:10px 0;color:var(--txt-mut);font-size:10px}
    .ldsp-shop-no-more{width:100%;text-align:center;padding:12px 0;color:var(--txt-mut);font-size:10px}
    .ldsp-shop-card{width:calc(50% - 4px);background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);overflow:hidden;cursor:pointer;transition:all .2s;position:relative;box-sizing:border-box}
    .ldsp-shop-card:hover{border-color:var(--accent);transform:translateY(-2px);box-shadow:0 4px 12px rgba(0,0,0,.1)}
    .ldsp-shop-card-cover{width:100%;height:70px;display:flex;align-items:center;justify-content:center;font-size:28px;overflow:hidden}
    .ldsp-shop-card-cover img{width:100%;height:100%;object-fit:cover}
    .ldsp-shop-card-body{padding:8px;display:flex;flex-direction:column;gap:4px}
    .ldsp-shop-card-name{font-size:11px;font-weight:600;color:var(--txt);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .ldsp-shop-card-meta{display:flex;align-items:center;gap:6px}
    .ldsp-shop-card-category{font-size:8px;color:#fff;padding:2px 5px;background:linear-gradient(135deg,var(--accent),#8b5cf6);border-radius:3px;font-weight:500}
    .ldsp-shop-card-time{font-size:8px;color:var(--txt-mut);margin-left:auto}
    .ldsp-shop-card-seller{font-size:9px;color:var(--txt-sec);display:flex;align-items:center;gap:3px;overflow:hidden}
    .ldsp-shop-card-seller-avatar{width:14px;height:14px;border-radius:50%;background:var(--bg-el);flex-shrink:0;object-fit:cover}
    .ldsp-shop-card-seller-name{flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .ldsp-shop-card-sold{margin-left:auto;font-size:8px;color:#f97316;font-weight:600;flex-shrink:0}
    .ldsp-shop-card-footer{display:flex;justify-content:space-between;align-items:center;padding-top:6px;border-top:1px solid var(--border)}
    .ldsp-shop-card-price{font-size:13px;font-weight:700;color:var(--accent);display:flex;align-items:baseline;gap:1px}
    .ldsp-shop-card-price span{font-size:8px;font-weight:500;color:var(--txt-mut)}
    .ldsp-shop-card-price.discounted{color:var(--ok)}
    .ldsp-shop-card-original{font-size:8px;color:var(--txt-mut);text-decoration:line-through;margin-left:3px;opacity:.7}
    .ldsp-shop-card-discount{position:absolute;top:5px;left:4px;min-width:34px;height:28px;padding:0 5px;display:flex;align-items:center;justify-content:center;font-size:9px;font-weight:800;color:#fff;background:linear-gradient(135deg,rgba(255,75,75,.88),rgba(235,60,60,.82));border-radius:14px;box-shadow:0 2px 6px rgba(235,60,60,.3);z-index:3;transform:rotate(-8deg);text-shadow:0 1px 1px rgba(0,0,0,.15);letter-spacing:-.2px}
    .ldsp-shop-card-discount::before{content:'';position:absolute;inset:-3px;background:linear-gradient(135deg,rgba(255,90,90,.6),rgba(235,60,60,.45));border-radius:17px;clip-path:polygon(8% 35%,0% 50%,8% 65%,20% 85%,35% 95%,50% 100%,65% 95%,80% 85%,92% 65%,100% 50%,92% 35%,80% 15%,65% 5%,50% 0%,35% 5%,20% 15%);z-index:-1}
    .ldsp-shop-card-discount::after{content:'';position:absolute;top:4px;left:7px;width:10px;height:5px;background:linear-gradient(180deg,rgba(255,255,255,.3),transparent);border-radius:50%;transform:rotate(-15deg)}
    .ldsp-shop-card-views{font-size:8px;color:var(--txt-mut)}
    .ldsp-shop-card-status{position:absolute;top:6px;left:6px;padding:2px 6px;font-size:9px;font-weight:600;border-radius:4px}
    .ldsp-shop-card-status.active{background:rgba(34,197,94,.15);color:#22c55e}
    .ldsp-shop-card-status.inactive{background:rgba(239,68,68,.15);color:#ef4444}
    .ldsp-shop-load-more{width:100%;padding:12px;text-align:center;font-size:11px;color:var(--txt-mut);cursor:pointer;transition:color .15s}
    .ldsp-shop-load-more:hover{color:var(--accent)}
    .ldsp-shop-load-more.loading{pointer-events:none;opacity:.6}
    .ldsp-shop-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;flex:1;text-align:center}
    .ldsp-shop-empty-icon{font-size:40px;margin-bottom:10px;opacity:.7}
    .ldsp-shop-empty-text{font-size:12px;color:var(--txt-mut);margin-bottom:6px}
    .ldsp-shop-empty-hint{font-size:10px;color:var(--txt-mut);opacity:.7}
    /* 小卖部详情页 */
    .ldsp-shop-detail{display:flex;flex-direction:column;gap:10px;flex:1;min-height:0;overflow-y:auto;padding-bottom:4px}
    .ldsp-shop-detail-header{display:flex;align-items:center;gap:8px;flex-shrink:0}
    .ldsp-shop-back-btn{padding:5px 10px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:11px;color:var(--txt-sec);cursor:pointer;transition:all .15s;flex-shrink:0}
    .ldsp-shop-back-btn:hover{background:var(--bg-hover);border-color:var(--accent);color:var(--accent)}
    .ldsp-shop-detail-category{font-size:9px;color:#fff;padding:2px 8px;background:linear-gradient(135deg,var(--accent),#8b5cf6);border-radius:4px;font-weight:500}
    .ldsp-shop-detail-img-wrap{width:100%;aspect-ratio:16/9;max-height:180px;flex-shrink:0;position:relative;border-radius:var(--r-md);overflow:hidden}
    .ldsp-shop-detail-img{width:100%;height:100%;object-fit:cover;background:var(--bg-el)}
    .ldsp-shop-detail-placeholder{width:100%;height:100%;max-height:120px;display:flex;align-items:center;justify-content:center;font-size:36px;background:linear-gradient(135deg,var(--bg-el),var(--bg-card))}
    .ldsp-shop-detail-content{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:12px 14px}
    .ldsp-shop-detail-name{font-size:15px;font-weight:700;color:var(--txt);margin-bottom:8px;line-height:1.4}
    .ldsp-shop-detail-desc{font-size:11px;color:var(--txt-sec);line-height:1.6;white-space:pre-wrap;word-break:break-word;max-height:100px;overflow-y:auto}
    .ldsp-shop-detail-desc.store-desc{max-height:160px;font-size:12px;line-height:1.7;color:var(--txt);padding:10px 12px;background:var(--bg-el);border-radius:var(--r-sm);margin-top:8px}
    .ldsp-shop-detail-store-owner{display:flex;align-items:center;gap:4px;font-size:11px;color:var(--txt-sec);padding:6px 10px;background:linear-gradient(135deg,rgba(6,182,212,.08),rgba(8,145,178,.05));border:1px solid rgba(6,182,212,.15);border-radius:var(--r-sm);margin-bottom:4px}
    .ldsp-shop-detail-store-owner span{font-weight:600;color:#06b6d4}
    .ldsp-shop-detail.store-detail .ldsp-shop-detail-content{padding:14px 16px}
    .ldsp-shop-detail-price-row{display:flex;align-items:center;gap:6px;padding:10px 12px;background:linear-gradient(135deg,rgba(107,140,239,.1),rgba(139,92,246,.08));border:1px solid rgba(107,140,239,.2);border-radius:var(--r-md)}
    .ldsp-shop-detail-price{font-size:20px;font-weight:700;color:var(--accent)}
    .ldsp-shop-detail-price span{font-size:10px;font-weight:500;color:var(--txt-mut);margin-left:2px}
    .ldsp-shop-detail-price.discounted{color:#22c55e}
    .ldsp-shop-detail-original{font-size:11px;color:var(--txt-mut);text-decoration:line-through}
    .ldsp-shop-detail-discount-badge{padding:2px 6px;background:linear-gradient(135deg,var(--err),#f43f5e);color:#fff;font-size:9px;font-weight:600;border-radius:3px;margin-left:auto}
    .ldsp-shop-detail-info{display:flex;flex-wrap:wrap;gap:6px;font-size:9px;color:var(--txt-mut)}
    .ldsp-shop-detail-info-item{display:flex;align-items:center;gap:3px;padding:3px 6px;background:var(--bg-el);border-radius:var(--r-sm)}
    .ldsp-shop-detail-info-item.stock{background:rgba(34,197,94,.1);border:1px solid rgba(34,197,94,.2);font-weight:600}
    .ldsp-shop-detail-info-item.stock .available{color:#22c55e;font-weight:700}
    .ldsp-shop-detail-info-item.stock .total{color:var(--txt-mut)}
    .ldsp-shop-detail-info-item.stock.low{background:rgba(239,68,68,.1);border-color:rgba(239,68,68,.2)}
    .ldsp-shop-detail-info-item.stock.low .available{color:#ef4444}
    .ldsp-shop-detail-info-item.sold{background:rgba(251,146,60,.1);border:1px solid rgba(251,146,60,.2);color:#fb923c}
    .ldsp-shop-seller{display:flex;align-items:center;gap:8px;padding:8px 10px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);cursor:pointer;transition:all .15s}
    .ldsp-shop-seller:hover{border-color:var(--accent);background:var(--bg-hover)}
    .ldsp-shop-seller-avatar{width:28px;height:28px;border-radius:50%;border:1px solid var(--border);background:var(--bg-el);object-fit:cover}
    .ldsp-shop-seller-info{flex:1;min-width:0}
    .ldsp-shop-seller-name{font-size:11px;font-weight:600;color:var(--txt)}
    .ldsp-shop-seller-label{font-size:8px;color:var(--txt-mut)}
    .ldsp-shop-seller-arrow{font-size:12px;color:var(--txt-mut);transition:transform .15s}
    .ldsp-shop-seller:hover .ldsp-shop-seller-arrow{transform:translateX(3px);color:var(--accent)}
    .ldsp-shop-purchase-action{padding:10px;background:#4f7cef !important;border:none !important;border-radius:var(--r-md);font-size:13px !important;font-weight:600 !important;color:#fff !important;cursor:pointer;transition:all .2s;display:block;text-align:center;flex-shrink:0;text-decoration:none !important;box-sizing:border-box}
    .ldsp-shop-purchase-action:hover{background:#3d6be0 !important;box-shadow:0 4px 12px rgba(79,124,239,.35);color:#fff !important}
    /* 小卖部表单 */
    .ldsp-shop-form{display:flex;flex-direction:column;gap:12px;flex:1;min-height:0;overflow-y:auto}
    .ldsp-shop-form-header{display:flex;align-items:center;gap:10px;flex-shrink:0}
    .ldsp-shop-form-title{font-size:14px;font-weight:700;color:var(--txt)}
    .ldsp-shop-form-group{display:flex;flex-direction:column;gap:4px}
    .ldsp-shop-form-label{font-size:10px;font-weight:600;color:var(--txt-sec);display:flex;align-items:center;gap:4px}
    .ldsp-shop-form-label .required{color:var(--err)}
    .ldsp-shop-form-input{padding:10px 12px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:12px;color:var(--txt);transition:border-color .15s;width:100%;box-sizing:border-box}
    .ldsp-shop-form-input:focus{border-color:var(--accent);outline:none}
    .ldsp-shop-form-input::placeholder{color:var(--txt-mut)}
    .ldsp-shop-form-textarea{min-height:80px;resize:vertical;font-family:inherit;line-height:1.5}
    .ldsp-shop-form-select{padding:10px 12px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:12px;color:var(--txt);cursor:pointer;width:100%;box-sizing:border-box}
    .ldsp-shop-form-select:focus{border-color:var(--accent);outline:none}
    .ldsp-shop-form-row{display:grid;grid-template-columns:1fr 1fr;gap:10px}
    .ldsp-shop-form-hint{font-size:9px;color:var(--txt-mut);margin-top:2px}
    .ldsp-shop-form-hint-selectable{user-select:text!important;-webkit-user-select:text!important}
    .ldsp-shop-form-hint a{color:var(--link);text-decoration:underline;user-select:text!important}
    .ldsp-shop-form-optional{font-size:9px;font-weight:400;color:var(--txt-mut)}
    .ldsp-shop-form-actions{display:flex;gap:8px;margin-top:auto;padding-top:10px;flex-shrink:0}
    .ldsp-shop-form-btn{flex:1;padding:10px;border:none;border-radius:var(--r-sm);font-size:12px;font-weight:600;cursor:pointer;transition:all .15s}
    .ldsp-shop-form-btn.primary{background:var(--grad);color:#fff}
    .ldsp-shop-form-btn.primary:hover{opacity:.9}
    .ldsp-shop-form-btn.secondary{background:var(--bg-el);color:var(--txt-sec);border:1px solid var(--border)}
    .ldsp-shop-form-btn.secondary:hover{border-color:var(--txt-mut);color:var(--txt)}
    .ldsp-shop-form-btn:disabled{opacity:.5;cursor:not-allowed}
    /* Shop v2.0 商品类型选择 */
    .ldsp-shop-form-types{display:flex;gap:8px}
    .ldsp-shop-type-option{flex:1;display:flex;flex-direction:column;align-items:center;padding:10px 8px;background:var(--bg-el);border:2px solid var(--border);border-radius:var(--r-md);cursor:pointer;transition:all .2s}
    .ldsp-shop-type-option:hover{border-color:var(--txt-mut);background:var(--bg-hover)}
    .ldsp-shop-type-option.active{border-color:var(--accent);background:rgba(107,140,239,.08)}
    .ldsp-shop-type-option input{display:none}
    .ldsp-shop-type-icon{font-size:20px;margin-bottom:4px}
    .ldsp-shop-type-name{font-size:11px;font-weight:600;color:var(--txt)}
    .ldsp-shop-type-desc{font-size:9px;color:var(--txt-mut);text-align:center}
    .ldsp-shop-type-link.hidden,.ldsp-shop-type-cdk.hidden{display:none}
    .ldsp-shop-cdk-notice{display:flex;gap:8px;padding:10px;background:linear-gradient(135deg,rgba(234,179,8,.08),rgba(245,158,11,.05));border:1px solid rgba(234,179,8,.2);border-radius:var(--r-sm)}
    .ldsp-shop-cdk-notice-icon{font-size:18px;flex-shrink:0}
    .ldsp-shop-cdk-notice-text{font-size:10px;color:var(--txt-sec);line-height:1.5}
    .ldsp-shop-cdk-notice-text p{margin:0 0 4px}
    .ldsp-shop-cdk-notice-text p:last-child{margin-bottom:0}
    .ldsp-shop-cdk-notice-text strong{color:var(--accent);font-weight:600}
    /* Shop v2.0 CDK 类型标签和库存 */
    .ldsp-shop-card-type{position:absolute;top:0;right:0;padding:4px 8px;font-size:9px;font-weight:700;border-radius:0 var(--r-md) 0 8px;z-index:2;letter-spacing:.5px;text-transform:uppercase}
    .ldsp-shop-card-type.cdk{background:linear-gradient(135deg,#fbbf24 0%,#f59e0b 100%);color:#1c1917;box-shadow:0 2px 8px rgba(251,191,36,.4)}
    .ldsp-shop-card-type.store{background:linear-gradient(135deg,#06b6d4 0%,#0891b2 100%);color:#fff;box-shadow:0 2px 8px rgba(6,182,212,.4)}
    /* 小店特殊卡片样式 */
    .ldsp-shop-card.store-card{border-color:rgba(6,182,212,.3)}
    .ldsp-shop-card.store-card:hover{border-color:#06b6d4}
    .ldsp-shop-card.store-card .ldsp-shop-card-footer{border-top:none;padding-top:0}
    .ldsp-shop-card-store-owner{font-size:10px;color:var(--txt-sec);font-weight:500;display:flex;align-items:center;gap:2px;max-width:100px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .ldsp-shop-card-stock{display:inline-flex;align-items:center;gap:3px;padding:2px 6px;border-radius:4px;font-size:9px;font-weight:600;background:rgba(34,197,94,.1);border:1px solid rgba(34,197,94,.2)}
    .ldsp-shop-card-stock .available{color:#22c55e;font-weight:700}
    .ldsp-shop-card-stock .divider{color:var(--txt-mut);font-weight:400}
    .ldsp-shop-card-stock .total{color:var(--txt-mut);font-weight:500}
    .ldsp-shop-card-stock.low{background:rgba(239,68,68,.1);border-color:rgba(239,68,68,.2)}
    .ldsp-shop-card-stock.low .available{color:#ef4444}
    .ldsp-shop-card-stock.out{background:rgba(107,114,128,.1);border-color:rgba(107,114,128,.2)}
    .ldsp-shop-card-stock.out .available{color:#6b7280}
    .ldsp-shop-card.out-of-stock{opacity:.6;filter:grayscale(.3)}
    .ldsp-shop-card.out-of-stock::after{content:'已售罄';position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);padding:6px 16px;background:rgba(0,0,0,.7);color:#fff;font-size:12px;font-weight:700;border-radius:20px;z-index:5}
    .ldsp-shop-detail-type-badge{padding:4px 10px;font-size:10px;font-weight:700;border-radius:6px;margin-left:auto;letter-spacing:.5px}
    .ldsp-shop-detail-type-badge.cdk{background:linear-gradient(135deg,#fbbf24 0%,#f59e0b 100%);color:#1c1917;box-shadow:0 2px 6px rgba(251,191,36,.3)}
    .ldsp-shop-detail-type-badge.store{background:linear-gradient(135deg,#06b6d4 0%,#0891b2 100%);color:#fff;box-shadow:0 2px 6px rgba(6,182,212,.3)}
    /* Shop v2.0 订单列表样式 */
    .ldsp-order-list{display:flex;flex-direction:column;gap:10px;overflow-y:auto;flex:1;min-height:0;padding:2px}
    .ldsp-order-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:12px;cursor:pointer;transition:all .2s}
    .ldsp-order-card:hover{border-color:var(--accent);transform:translateY(-1px);box-shadow:0 4px 12px rgba(0,0,0,.1)}
    .ldsp-order-card-header{display:flex;justify-content:space-between;align-items:center;margin-bottom:10px}
    .ldsp-order-card-id{font-size:11px;color:var(--txt-mut);font-family:monospace}
    .ldsp-order-status{padding:3px 8px;font-size:9px;font-weight:600;border-radius:4px}
    .ldsp-order-status.pending{background:rgba(234,179,8,.15);color:#eab308}
    .ldsp-order-status.paid{background:rgba(59,130,246,.15);color:#3b82f6}
    .ldsp-order-status.delivered{background:rgba(34,197,94,.15);color:#22c55e}
    .ldsp-order-status.cancelled{background:rgba(107,114,128,.15);color:#6b7280}
    .ldsp-order-status.refunded{background:rgba(239,68,68,.15);color:#ef4444}
    .ldsp-order-status.expired{background:rgba(156,163,175,.15);color:#9ca3af}
    .ldsp-order-product{display:flex;gap:10px;align-items:center}
    .ldsp-order-product-img{width:48px;height:48px;object-fit:cover;border-radius:var(--r-sm);background:var(--bg-el)}
    .ldsp-order-product-img-placeholder{width:48px;height:48px;border-radius:var(--r-sm);display:flex;align-items:center;justify-content:center;font-size:18px;background:var(--bg-el)}
    .ldsp-order-product-info{flex:1;min-width:0}
    .ldsp-order-product-name{font-size:13px;font-weight:600;color:var(--txt);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .ldsp-order-product-seller{font-size:10px;color:var(--txt-mut);margin-top:2px}
    .ldsp-order-product-price{font-size:14px;font-weight:700;color:var(--accent);white-space:nowrap}
    .ldsp-order-card-footer{display:flex;justify-content:space-between;align-items:center;margin-top:10px;padding-top:10px;border-top:1px dashed var(--border)}
    .ldsp-order-card-time{font-size:10px;color:var(--txt-mut)}
    .ldsp-order-card-actions{display:flex;gap:6px}
    .ldsp-order-btn{padding:5px 12px;border:1px solid var(--border);border-radius:var(--r-sm);font-size:10px;color:var(--txt-sec);background:var(--bg-el);cursor:pointer;transition:all .15s}
    .ldsp-order-btn:hover{border-color:var(--accent);color:var(--accent)}
    .ldsp-order-btn.primary{border-color:var(--accent);background:var(--accent);color:#fff}
    .ldsp-order-btn.primary:hover{filter:brightness(1.1)}
    .ldsp-order-btn.danger{border-color:var(--err);color:var(--err)}
    .ldsp-order-btn.danger:hover{background:var(--err);color:#fff}
    .ldsp-order-btn:disabled{opacity:.6;cursor:not-allowed}
    .ldsp-order-no{font-size:10px;color:var(--txt-mut);font-family:monospace}
    .ldsp-order-card-body{display:flex;flex-direction:column;gap:8px}
    .ldsp-order-quantity{font-size:11px;color:var(--txt-mut);margin-left:auto}
    .ldsp-order-info{display:flex;justify-content:space-between;font-size:10px;color:var(--txt-mut)}
    .ldsp-order-footer{display:flex;justify-content:space-between;align-items:center;padding-top:8px;margin-top:4px;border-top:1px dashed var(--border)}
    .ldsp-order-amount{font-size:13px;font-weight:600;color:var(--accent)}
    .ldsp-order-actions{display:flex;gap:6px}
    .ldsp-order-back-btn{padding:6px 12px;border:1px solid var(--border);border-radius:var(--r-sm);font-size:11px;color:var(--txt-sec);background:var(--bg-el);cursor:pointer;transition:all .15s}
    .ldsp-order-back-btn:hover{border-color:var(--accent);color:var(--accent)}
    .ldsp-order-detail-status{padding:8px 12px;border-radius:var(--r-md);border:1px solid;text-align:center;font-size:12px;font-weight:600}
    .ldsp-order-detail-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:10px 12px;overflow:hidden}
    .ldsp-order-logs{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:12px;display:flex;flex-direction:column;gap:8px;max-height:150px;overflow-y:auto}
    .ldsp-order-logs-title{font-size:11px;font-weight:600;color:var(--txt-sec);padding-bottom:8px;border-bottom:1px dashed var(--border)}
    .ldsp-order-log-item{display:flex;justify-content:space-between;font-size:10px;color:var(--txt-mut)}
    .ldsp-order-log-action{color:var(--txt)}
    .ldsp-order-copy-btn{padding:8px 16px;border:1px solid var(--border);border-radius:var(--r-sm);font-size:11px;color:var(--txt-sec);background:var(--bg-el);cursor:pointer;margin-top:10px;transition:all .15s}
    .ldsp-order-copy-btn:hover{border-color:var(--accent);color:var(--accent)}
    .ldsp-order-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:12px;color:var(--txt-mut)}
    .ldsp-order-empty-icon{font-size:40px;opacity:.5}
    .ldsp-order-empty-text{font-size:12px}
    /* Shop v2.0 订单详情页 - 重构优化 */
    .ldsp-order-detail{display:flex;flex-direction:column;gap:8px;padding:0;overflow:hidden}
    .ldsp-order-detail-header{display:flex;align-items:center;gap:8px;padding-bottom:4px}
    .ldsp-order-detail-header>span{flex:1;text-align:center;font-size:13px;font-weight:600}
    .ldsp-order-detail-id{font-size:11px;color:var(--txt-mut);font-family:monospace}
    .ldsp-order-detail-section{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:12px;display:flex;flex-direction:column;gap:8px}
    .ldsp-order-detail-section-title{font-size:11px;font-weight:600;color:var(--txt-sec);padding-bottom:6px;border-bottom:1px dashed var(--border);display:flex;align-items:center;gap:6px}
    .ldsp-order-detail-product{display:flex;gap:10px;align-items:center}
    .ldsp-order-detail-product-img{width:56px;height:56px;object-fit:cover;border-radius:var(--r-sm);background:var(--bg-el);flex-shrink:0}
    .ldsp-order-detail-product-img-placeholder{width:56px;height:56px;border-radius:var(--r-sm);display:flex;align-items:center;justify-content:center;font-size:20px;background:var(--bg-el);flex-shrink:0}
    .ldsp-order-detail-product-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px;overflow:hidden}
    .ldsp-order-detail-product-name{font-size:13px;font-weight:600;color:var(--txt);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
    .ldsp-order-detail-product-seller{font-size:10px;color:var(--txt-mut)}
    .ldsp-order-detail-row{display:flex;justify-content:space-between;align-items:flex-start;padding:4px 0;gap:8px;border-bottom:1px dashed var(--border)}
    .ldsp-order-detail-row:last-child{border-bottom:none}
    .ldsp-order-detail-row .label{font-size:10px;color:var(--txt-mut);white-space:nowrap;flex-shrink:0;min-width:40px}
    .ldsp-order-detail-row .value{font-size:10px;color:var(--txt);font-weight:500;text-align:right;word-break:break-all;min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis}
    .ldsp-order-detail-row .value.mono{font-family:monospace;font-size:9px;letter-spacing:-.3px}
    .ldsp-order-detail-row .value.price{font-size:13px;font-weight:700;color:var(--accent)}
    .ldsp-order-detail-row .value.status{padding:2px 6px;font-size:9px;font-weight:600;border-radius:3px}
    .ldsp-order-cdk-section{background:linear-gradient(135deg,rgba(34,197,94,.08),rgba(34,197,94,.02));border:1px solid rgba(34,197,94,.25);border-radius:var(--r-md);padding:10px}
    .ldsp-order-cdk-title{font-size:11px;font-weight:600;color:#22c55e;margin-bottom:6px;display:flex;align-items:center;gap:4px}
    .ldsp-order-cdk-content{background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);padding:8px 10px;font-size:11px;font-family:monospace;word-break:break-all;white-space:pre-wrap;line-height:1.5;max-height:100px;overflow-y:auto;color:var(--txt)}
    .ldsp-order-cdk-value{font-size:11px;font-family:monospace;color:var(--txt);word-break:break-all;white-space:pre-wrap;line-height:1.5}
    .ldsp-order-cdk-copy{position:absolute;top:6px;right:6px;width:24px;height:24px;display:flex;align-items:center;justify-content:center;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-sm);cursor:pointer;font-size:11px;transition:all .15s}
    .ldsp-order-cdk-copy:hover{border-color:var(--accent);color:var(--accent)}
    .ldsp-order-cdk-copy.copied{background:var(--accent);border-color:var(--accent);color:#fff}
    .ldsp-order-cdk-actions{display:flex;gap:6px;margin-top:8px}
    .ldsp-order-cdk-btn{flex:1;padding:6px 10px;border:1px solid rgba(34,197,94,.3);border-radius:var(--r-sm);font-size:10px;font-weight:600;color:#22c55e;background:rgba(34,197,94,.08);cursor:pointer;transition:all .15s;text-align:center}
    .ldsp-order-cdk-btn:hover{background:rgba(34,197,94,.15);border-color:rgba(34,197,94,.4)}
    .ldsp-order-cdk-btn.copied{background:#22c55e;border-color:#22c55e;color:#fff}
    .ldsp-order-detail-actions{display:flex;gap:8px;padding-top:12px}
    .ldsp-order-detail-btn{flex:1;padding:10px 16px;border:1px solid var(--border);border-radius:var(--r-md);font-size:12px;font-weight:600;color:var(--txt-sec);background:var(--bg-el);cursor:pointer;transition:all .15s;text-align:center}
    .ldsp-order-detail-btn:hover{border-color:var(--accent);color:var(--accent)}
    .ldsp-order-detail-btn.primary{border-color:var(--accent);background:var(--accent);color:#fff}
    .ldsp-order-detail-btn.primary:hover{filter:brightness(1.1)}
    .ldsp-order-detail-btn.danger{border-color:var(--err);color:var(--err)}
    .ldsp-order-detail-btn.danger:hover{background:var(--err);color:#fff}
    /* Shop v2.0 订单角色切换 */
    .ldsp-order-role-tabs{display:flex;gap:0;background:var(--bg-el);border-radius:var(--r-md);padding:3px;margin-bottom:12px}
    .ldsp-order-role-tab{flex:1;padding:8px 12px;font-size:11px;font-weight:500;color:var(--txt-mut);background:transparent;border:none;border-radius:var(--r-sm);cursor:pointer;transition:all .15s;text-align:center}
    .ldsp-order-role-tab.active{background:var(--bg-card);color:var(--txt);box-shadow:0 1px 3px rgba(0,0,0,.08)}
    .ldsp-order-role-tab:hover:not(.active){color:var(--txt-sec)}
    /* Shop v2.0 发货对话框 */
    .ldsp-deliver-dialog-overlay{position:fixed;inset:0;background:rgba(0,0,0,.5);z-index:100001;display:flex;align-items:center;justify-content:center;padding:20px}
    .ldsp-deliver-dialog{background:var(--bg-card);border-radius:var(--r-lg);width:100%;max-width:360px;box-shadow:0 20px 40px rgba(0,0,0,.2)}
    .ldsp-deliver-dialog-header{display:flex;justify-content:space-between;align-items:center;padding:14px 16px;border-bottom:1px solid var(--border);font-size:14px;font-weight:600;color:var(--txt)}
    .ldsp-deliver-dialog-close{width:28px;height:28px;display:flex;align-items:center;justify-content:center;border:none;background:transparent;font-size:18px;color:var(--txt-mut);cursor:pointer;border-radius:var(--r-sm)}
    .ldsp-deliver-dialog-close:hover{background:var(--bg-el);color:var(--txt)}
    .ldsp-deliver-dialog-body{padding:16px;display:flex;flex-direction:column;gap:12px}
    .ldsp-deliver-dialog-tip{font-size:11px;color:var(--txt-mut);line-height:1.5}
    .ldsp-deliver-dialog-input{width:100%;height:120px;padding:12px;border:1px solid var(--border);border-radius:var(--r-md);font-size:12px;color:var(--txt);background:var(--bg-el);resize:none;font-family:inherit}
    .ldsp-deliver-dialog-input:focus{outline:none;border-color:var(--accent)}
    .ldsp-deliver-dialog-footer{display:flex;gap:10px;padding:12px 16px;border-top:1px solid var(--border)}
    .ldsp-deliver-dialog-btn{flex:1;padding:10px 16px;border:1px solid var(--border);border-radius:var(--r-md);font-size:12px;font-weight:600;cursor:pointer;transition:all .15s}
    .ldsp-deliver-dialog-btn.cancel{background:var(--bg-el);color:var(--txt-sec)}
    .ldsp-deliver-dialog-btn.cancel:hover{border-color:var(--txt-mut)}
    .ldsp-deliver-dialog-btn.confirm{background:var(--accent);border-color:var(--accent);color:#fff}
    .ldsp-deliver-dialog-btn.confirm:hover{filter:brightness(1.1)}
    .ldsp-deliver-dialog-btn:disabled{opacity:.6;cursor:not-allowed}
    /* Shop v2.0 商户设置 */
    .ldsp-merchant-settings{display:flex;flex-direction:column;gap:12px;padding:2px}
    .ldsp-merchant-settings-header{display:flex;align-items:center;gap:12px;padding-bottom:8px;border-bottom:1px dashed var(--border)}
    .ldsp-merchant-settings-header span{font-size:14px;font-weight:600;color:var(--txt)}
    .ldsp-merchant-stats-card{background:linear-gradient(135deg,rgba(139,92,246,.1),rgba(139,92,246,.02));border:1px solid rgba(139,92,246,.2);border-radius:var(--r-md);padding:14px}
    .ldsp-merchant-stats-title{font-size:12px;font-weight:600;color:var(--accent);margin-bottom:12px}
    .ldsp-merchant-stats-grid{display:grid;grid-template-columns:repeat(2,1fr);gap:10px}
    .ldsp-merchant-stat{background:var(--bg-card);border-radius:var(--r-sm);padding:10px;text-align:center}
    .ldsp-merchant-stat-value{font-size:18px;font-weight:700;color:var(--txt)}
    .ldsp-merchant-stat-label{font-size:9px;color:var(--txt-mut);margin-top:2px}
    .ldsp-merchant-config-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:14px;display:flex;flex-direction:column;gap:12px}
    .ldsp-merchant-config-title{font-size:12px;font-weight:600;color:var(--txt)}
    .ldsp-merchant-config-status{display:flex;gap:8px}
    .ldsp-merchant-badge{padding:3px 8px;font-size:9px;font-weight:600;border-radius:4px}
    .ldsp-merchant-badge.verified{background:rgba(34,197,94,.15);color:#22c55e}
    .ldsp-merchant-badge.pending{background:rgba(234,179,8,.15);color:#eab308}
    .ldsp-merchant-badge.active{background:rgba(59,130,246,.15);color:#3b82f6}
    .ldsp-merchant-badge.inactive{background:rgba(107,114,128,.15);color:#6b7280}
    .ldsp-merchant-config-info{background:var(--bg-el);border-radius:var(--r-sm);padding:10px}
    .ldsp-merchant-config-row{display:flex;justify-content:space-between;font-size:11px;padding:4px 0}
    .ldsp-merchant-config-row .label{color:var(--txt-mut)}
    .ldsp-merchant-config-row .value{color:var(--txt);font-family:monospace}
    .ldsp-merchant-config-tip{font-size:10px;color:var(--txt-mut);line-height:1.5;padding:8px 10px;background:var(--bg-el);border-radius:var(--r-sm)}
    .ldsp-merchant-form-group{display:flex;flex-direction:column;gap:4px}
    .ldsp-merchant-form-label{font-size:10px;font-weight:600;color:var(--txt-sec)}
    .ldsp-merchant-form-input{width:100%;padding:10px 12px;border:1px solid var(--border);border-radius:var(--r-sm);font-size:12px;color:var(--txt);background:var(--bg-el)}
    .ldsp-merchant-form-input:focus{outline:none;border-color:var(--accent)}
    .ldsp-merchant-form-hint{font-size:9px;color:var(--txt-mut)}
    .ldsp-merchant-form-input:disabled{opacity:.7;cursor:not-allowed;background:var(--bg-card)}
    .ldsp-merchant-form-actions{display:flex;gap:10px;flex-wrap:wrap}
    .ldsp-merchant-edit-btn{flex:1;padding:10px 16px;border:1px solid var(--accent);border-radius:var(--r-md);font-size:12px;font-weight:600;background:transparent;color:var(--accent);cursor:pointer;transition:all .15s}
    .ldsp-merchant-edit-btn:hover{background:var(--accent);color:#fff}
    .ldsp-merchant-test-btn{padding:10px 16px;border:1px solid #f59e0b;border-radius:var(--r-md);font-size:12px;font-weight:600;background:transparent;color:#f59e0b;cursor:pointer;transition:all .15s}
    .ldsp-merchant-test-btn:hover{background:#f59e0b;color:#fff}
    .ldsp-merchant-test-btn:disabled{opacity:.6;cursor:not-allowed}
    .ldsp-merchant-save-btn{flex:1;padding:10px 16px;border:none;border-radius:var(--r-md);font-size:12px;font-weight:600;background:var(--accent);color:#fff;cursor:pointer;transition:all .15s}
    .ldsp-merchant-save-btn:hover{filter:brightness(1.1)}
    .ldsp-merchant-save-btn:disabled{opacity:.6;cursor:not-allowed}
    .ldsp-merchant-cancel-btn{padding:10px 16px;border:1px solid var(--border);border-radius:var(--r-md);font-size:12px;font-weight:600;color:var(--txt-sec);background:transparent;cursor:pointer;transition:all .15s}
    .ldsp-merchant-cancel-btn:hover{border-color:var(--txt-mut);color:var(--txt)}
    .ldsp-merchant-delete-btn{padding:10px 16px;border:1px solid var(--err);border-radius:var(--r-md);font-size:12px;font-weight:600;color:var(--err);background:transparent;cursor:pointer;transition:all .15s}
    .ldsp-merchant-delete-btn:hover{background:var(--err);color:#fff}
    .ldsp-merchant-delete-btn:disabled{opacity:.6;cursor:not-allowed}
    .ldsp-merchant-config-header{display:flex;justify-content:space-between;align-items:center;gap:10px;flex-wrap:wrap}
    .ldsp-merchant-help-card{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:14px}
    .ldsp-merchant-help-title{font-size:11px;font-weight:600;color:var(--txt-sec);margin-bottom:10px}
    .ldsp-merchant-help-content,.ldsp-merchant-help-content *{font-size:10px;color:var(--txt-mut);line-height:1.8;user-select:text!important;-webkit-user-select:text!important}
    .ldsp-merchant-help-content p{margin:0}
    .ldsp-merchant-help-content a{color:var(--accent);text-decoration:none}
    .ldsp-merchant-help-content a:hover{text-decoration:underline}
    .ldsp-shop-header-actions{display:flex;gap:8px;align-items:center}
    .ldsp-shop-web-btn{padding:4px 8px;border:1px solid var(--border);border-radius:var(--r-sm);font-size:9px;color:var(--txt-sec);background:var(--bg-el);cursor:pointer;transition:all .15s;text-decoration:none;display:flex;align-items:center;gap:3px;white-space:nowrap}
    .ldsp-shop-web-btn:hover{border-color:var(--accent);color:var(--accent);background:rgba(107,140,239,.08)}
    .ldsp-shop-settings-btn{padding:6px 12px;border:1px solid var(--border);border-radius:var(--r-md);font-size:10px;color:var(--txt-sec);background:var(--bg-el);cursor:pointer;transition:all .15s}
    .ldsp-shop-settings-btn:hover{border-color:var(--accent);color:var(--accent)}
    /* Shop v2.0 CDK 库存管理 */
    .ldsp-cdk-manager{display:flex;flex-direction:column;gap:12px;padding:2px}
    .ldsp-cdk-manager-header{display:flex;align-items:center;gap:12px;padding-bottom:8px;border-bottom:1px dashed var(--border)}
    .ldsp-cdk-manager-header span{font-size:13px;font-weight:600;color:var(--txt);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
    .ldsp-cdk-stats-grid{display:grid;grid-template-columns:repeat(4,1fr);gap:8px}
    .ldsp-cdk-stat{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-sm);padding:10px;text-align:center}
    .ldsp-cdk-stat-value{font-size:18px;font-weight:700;color:var(--txt)}
    .ldsp-cdk-stat-label{font-size:9px;color:var(--txt-mut);margin-top:2px}
    .ldsp-cdk-stat.available{border-color:rgba(34,197,94,.3);background:rgba(34,197,94,.05)}
    .ldsp-cdk-stat.available .ldsp-cdk-stat-value{color:#22c55e}
    .ldsp-cdk-stat.locked{border-color:rgba(234,179,8,.3);background:rgba(234,179,8,.05)}
    .ldsp-cdk-stat.locked .ldsp-cdk-stat-value{color:#eab308}
    .ldsp-cdk-stat.sold{border-color:rgba(59,130,246,.3);background:rgba(59,130,246,.05)}
    .ldsp-cdk-stat.sold .ldsp-cdk-stat-value{color:#3b82f6}
    .ldsp-cdk-upload-section{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:14px;display:flex;flex-direction:column;gap:10px}
    .ldsp-cdk-upload-title{font-size:11px;font-weight:600;color:var(--txt-sec)}
    .ldsp-cdk-upload-input{width:100%;height:100px;padding:10px;border:1px solid var(--border);border-radius:var(--r-sm);font-size:11px;color:var(--txt);background:var(--bg-el);resize:none;font-family:monospace}
    .ldsp-cdk-upload-input:focus{outline:none;border-color:var(--accent)}
    .ldsp-cdk-upload-footer{display:flex;gap:8px;align-items:center}
    .ldsp-cdk-upload-remark{flex:1;padding:8px 10px;border:1px solid var(--border);border-radius:var(--r-sm);font-size:11px;color:var(--txt);background:var(--bg-el)}
    .ldsp-cdk-upload-remark:focus{outline:none;border-color:var(--accent)}
    .ldsp-cdk-upload-btn{padding:8px 16px;border:none;border-radius:var(--r-sm);font-size:11px;font-weight:600;background:var(--accent);color:#fff;cursor:pointer;transition:all .15s}
    .ldsp-cdk-upload-btn:hover{filter:brightness(1.1)}
    .ldsp-cdk-upload-btn:disabled{opacity:.6;cursor:not-allowed}
    .ldsp-shop-cdk-mgr-section{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:14px;display:flex;flex-direction:column;gap:10px;overflow:hidden;min-height:100px}
    .ldsp-shop-cdk-mgr-header{display:flex;justify-content:space-between;align-items:center;font-size:11px;font-weight:600;color:var(--txt-sec);flex-shrink:0}
    .ldsp-shop-cdk-mgr-filter{padding:4px 8px;border:1px solid var(--border);border-radius:var(--r-sm);font-size:10px;color:var(--txt);background:var(--bg-el)}
    .ldsp-shop-cdk-mgr-list{display:flex;flex-direction:column;gap:6px;max-height:240px;overflow-y:auto;flex:1}
    .ldsp-shop-cdk-mgr-item{display:grid;grid-template-columns:1fr auto auto;align-items:center;gap:10px;padding:10px 12px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);min-height:40px}
    .ldsp-shop-cdk-mgr-code{font-size:12px;font-family:'Consolas','Monaco','Courier New',monospace;color:var(--txt);word-break:break-all;line-height:1.4}
    .ldsp-shop-cdk-mgr-meta{display:flex;flex-direction:column;gap:4px;align-items:flex-end;flex-shrink:0}
    .ldsp-shop-cdk-mgr-status{padding:3px 8px;font-size:9px;font-weight:600;border-radius:4px;white-space:nowrap}
    .ldsp-shop-cdk-mgr-status.available{background:rgba(34,197,94,.2);color:#22c55e}
    .ldsp-shop-cdk-mgr-status.locked{background:rgba(234,179,8,.2);color:#eab308}
    .ldsp-shop-cdk-mgr-status.sold{background:rgba(107,114,128,.2);color:#6b7280}
    .ldsp-shop-cdk-mgr-remark{font-size:9px;color:var(--txt-mut);max-width:80px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .ldsp-shop-cdk-mgr-actions{display:flex;gap:4px;flex-shrink:0}
    .ldsp-shop-cdk-mgr-copy{width:24px;height:24px;display:flex;align-items:center;justify-content:center;border:1px solid var(--border);border-radius:var(--r-sm);background:var(--bg-card);font-size:12px;cursor:pointer;transition:all .15s;flex-shrink:0}
    .ldsp-shop-cdk-mgr-copy:hover{border-color:var(--accent);background:rgba(107,140,239,.1)}
    .ldsp-shop-cdk-mgr-copy.copied{border-color:#22c55e;background:rgba(34,197,94,.15)}
    .ldsp-shop-cdk-mgr-delete{width:24px;height:24px;display:flex;align-items:center;justify-content:center;border:1px solid var(--border);border-radius:var(--r-sm);background:var(--bg-card);font-size:14px;color:var(--txt-mut);cursor:pointer;transition:all .15s;flex-shrink:0}
    .ldsp-shop-cdk-mgr-delete:hover{color:#fff;background:var(--err);border-color:var(--err)}
    .ldsp-shop-cdk-mgr-pagination{display:flex;justify-content:space-between;align-items:center;padding-top:10px;border-top:1px dashed var(--border);font-size:10px;color:var(--txt-mut);flex-shrink:0}
    .ldsp-shop-cdk-mgr-pagination-btns{display:flex;gap:6px}
    .ldsp-shop-cdk-mgr-page-btn{padding:5px 12px;border:1px solid var(--border);border-radius:var(--r-sm);font-size:10px;color:var(--txt-sec);background:var(--bg-el);cursor:pointer;transition:all .15s}
    .ldsp-shop-cdk-mgr-page-btn:hover:not(:disabled){border-color:var(--accent);color:var(--accent)}
    .ldsp-shop-cdk-mgr-page-btn:disabled{opacity:.5;cursor:not-allowed}
    .ldsp-shop-cdk-mgr-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;gap:10px;color:var(--txt-mut)}
    .ldsp-shop-cdk-mgr-empty-icon{font-size:36px;opacity:.5}
    .ldsp-shop-cdk-mgr-empty-text{font-size:13px;font-weight:500}
    .ldsp-shop-cdk-mgr-empty-hint{font-size:10px}
    .ldsp-shop-my-card-type{position:absolute;top:0;left:0;padding:3px 8px;font-size:8px;font-weight:700;border-radius:var(--r-sm) 0 6px 0;background:linear-gradient(135deg,#fbbf24 0%,#f59e0b 100%);color:#1c1917;letter-spacing:.3px;z-index:2}
    .ldsp-shop-my-card-stock{display:inline-flex;align-items:center;gap:2px;padding:2px 6px;border-radius:4px;font-size:9px;font-weight:600;background:rgba(34,197,94,.1);border:1px solid rgba(34,197,94,.2)}
    .ldsp-shop-my-card-stock .available{color:#22c55e;font-weight:700}
    .ldsp-shop-my-card-stock .divider{color:var(--txt-mut)}
    .ldsp-shop-my-card-stock .total{color:var(--txt-mut)}
    .ldsp-shop-my-card-stock.low{background:rgba(239,68,68,.1);border-color:rgba(239,68,68,.2)}
    .ldsp-shop-my-card-stock.low .available{color:#ef4444}
    /* 我的商品卡片 */
    .ldsp-shop-my-card{display:flex;gap:10px;padding:10px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);position:relative}
    .ldsp-shop-my-card-img-wrap{width:60px;height:60px;flex-shrink:0;position:relative}
    .ldsp-shop-my-card-img{width:60px;height:60px;object-fit:cover;border-radius:var(--r-sm);background:var(--bg-el);flex-shrink:0}
    .ldsp-shop-my-card-img-placeholder{width:60px;height:60px;border-radius:var(--r-sm);display:flex;align-items:center;justify-content:center;font-size:20px;flex-shrink:0}
    .ldsp-shop-card-placeholder{width:100%;height:100%;display:flex;align-items:center;justify-content:center;font-size:28px;border-radius:0}
    .ldsp-shop-my-card-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:4px}
    .ldsp-shop-my-card-name{font-size:12px;font-weight:600;color:var(--txt);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .ldsp-shop-my-card-meta{font-size:10px;color:var(--txt-mut);display:flex;align-items:center;gap:8px}
    .ldsp-shop-my-card-price{font-size:13px;font-weight:700;color:var(--accent)}
    .ldsp-shop-my-card-actions{display:flex;gap:6px;margin-top:auto}
    .ldsp-shop-my-card-btn{padding:4px 10px;border:1px solid var(--border);border-radius:var(--r-sm);font-size:10px;color:var(--txt-sec);background:var(--bg-el);cursor:pointer;transition:all .15s}
    .ldsp-shop-my-card-btn:hover{border-color:var(--accent);color:var(--accent)}
    .ldsp-shop-my-card-btn.danger:hover{border-color:var(--err);color:var(--err)}
    .ldsp-shop-my-card-status{position:absolute;top:8px;right:8px;padding:2px 6px;font-size:9px;font-weight:600;border-radius:4px}
    .ldsp-shop-my-card-status.active{background:rgba(34,197,94,.15);color:#22c55e}
    .ldsp-shop-my-card-status.inactive{background:rgba(239,68,68,.15);color:#ef4444}
    .ldsp-shop-my-card-status.rejected{background:rgba(239,68,68,.2);color:#dc2626}
    .ldsp-shop-my-card-reason{font-size:9px;color:#dc2626;background:rgba(239,68,68,.08);padding:4px 6px;border-radius:4px;border-left:2px solid #ef4444;margin-bottom:4px;line-height:1.4;word-break:break-all}
    .ldsp-shop-my-list{display:flex;flex-direction:column;gap:8px;overflow-y:auto;flex:1;min-height:0}
    /* LDC/工单/吃瓜/CDK 响应式适配 */
    @media (max-width:380px){.ldsp-ldc-header,.ldsp-ticket-header,.ldsp-melon-header,.ldsp-cdk-header{padding:8px 10px}.ldsp-ldc-title,.ldsp-ticket-title,.ldsp-melon-title,.ldsp-cdk-title{font-size:12px}.ldsp-ldc-tabs{}.ldsp-ldc-tab,.ldsp-cdk-tab{padding:8px 6px;font-size:10px}.ldsp-ldc-body,.ldsp-ticket-body,.ldsp-melon-body,.ldsp-cdk-body{padding:10px;gap:8px}.ldsp-ldc-balance-card{padding:12px}.ldsp-ldc-balance-main{gap:8px}.ldsp-ldc-balance-value{font-size:24px}.ldsp-ldc-balance-right{gap:3px}.ldsp-ldc-balance-sub,.ldsp-ldc-balance-estimate{font-size:9px}.ldsp-ldc-stats-grid{grid-template-columns:1fr}.ldsp-ldc-stat-card{padding:10px;gap:8px}.ldsp-ldc-stat-icon{font-size:16px}.ldsp-ldc-stat-num{font-size:13px}.ldsp-ldc-chart-bars{height:60px}.ldsp-ldc-filter-section{gap:6px;padding-bottom:8px}.ldsp-ldc-filter-label{font-size:9px;min-width:24px}.ldsp-ldc-filter-chip{padding:4px 8px;font-size:9px}.ldsp-ldc-trans-item{padding:8px}.ldsp-ldc-trans-icon{font-size:14px;width:22px;height:22px}.ldsp-ldc-trans-name{font-size:11px}.ldsp-ldc-trans-amount{font-size:13px}.ldsp-ticket-tabs{padding:0 8px}.ldsp-ticket-tab,.ldsp-melon-tab{padding:6px 10px;font-size:9px}.ldsp-ldc-support{gap:12px}.ldsp-ldc-support-header{padding:10px 8px}.ldsp-ldc-support-title{font-size:13px}.ldsp-ldc-support-grid{gap:8px}.ldsp-ldc-support-card{padding:14px 10px}.ldsp-ldc-support-icon{font-size:28px;margin-bottom:8px}.ldsp-ldc-support-amount{font-size:16px}.ldsp-github-star-card{gap:10px;padding:10px 12px}.ldsp-github-icon-wrap{width:30px;height:30px}.ldsp-github-icon{width:26px;height:26px}.ldsp-github-title{font-size:11px;gap:4px}.ldsp-github-star-icon{font-size:14px}.ldsp-github-desc{font-size:9px}.ldsp-github-arrow{font-size:14px}.ldsp-cdk-user-card{flex-wrap:wrap;gap:10px;padding:12px}.ldsp-cdk-user-card::before{display:none}.ldsp-cdk-user-avatar{width:40px;height:40px}.ldsp-cdk-user-info{flex:1;min-width:80px;display:flex;flex-direction:column;align-items:flex-start}.ldsp-cdk-user-name{font-size:13px}.ldsp-cdk-user-username{font-size:9px}.ldsp-cdk-user-level{font-size:9px;padding:2px 8px;margin-top:4px}.ldsp-cdk-score-card{min-width:70px;padding:10px}.ldsp-cdk-score-label{font-size:8px}.ldsp-cdk-score-value{font-size:20px}.ldsp-cdk-qty-card{flex-direction:column;padding:10px;gap:8px}.ldsp-cdk-qty-item{padding:8px}.ldsp-cdk-qty-item.remain{border-right:none;border-bottom:1px solid var(--border)}.ldsp-cdk-qty-item .num{font-size:20px}.ldsp-cdk-qty-divider{display:none}.ldsp-cdk-item{padding:10px}.ldsp-cdk-item-name{font-size:12px}.ldsp-cdk-item-content{padding:6px 8px;font-size:10px}}
    @media (max-width:320px){.ldsp-ldc-header,.ldsp-ticket-header,.ldsp-melon-header,.ldsp-cdk-header{padding:6px 8px}.ldsp-ldc-title,.ldsp-ticket-title,.ldsp-melon-title,.ldsp-cdk-title{font-size:11px;gap:4px}.ldsp-ldc-header-actions,.ldsp-cdk-header-actions{gap:5px}.ldsp-ldc-link,.ldsp-cdk-link{font-size:9px!important}.ldsp-ldc-refresh,.ldsp-ldc-close,.ldsp-ticket-close,.ldsp-melon-close,.ldsp-cdk-refresh,.ldsp-cdk-close{width:22px;height:22px;font-size:10px}.ldsp-ldc-refresh svg,.ldsp-cdk-refresh svg{width:10px;height:10px}.ldsp-ldc-tab,.ldsp-cdk-tab{padding:6px 4px;font-size:9px}.ldsp-ldc-body,.ldsp-ticket-body,.ldsp-melon-body,.ldsp-cdk-body{padding:8px;gap:6px}.ldsp-ldc-balance-card{padding:10px}.ldsp-ldc-balance-main{flex-direction:column;align-items:stretch;gap:10px}.ldsp-ldc-balance-left{text-align:center}.ldsp-ldc-balance-value{font-size:26px}.ldsp-ldc-balance-right{text-align:center;flex-direction:row;justify-content:center;gap:12px;flex-wrap:wrap;padding-top:8px;border-top:1px dashed rgba(139,92,246,.1)}.ldsp-ldc-balance-sub,.ldsp-ldc-balance-estimate{justify-content:center;font-size:9px}.ldsp-ldc-balance-footer{justify-content:center}.ldsp-ldc-stats-grid{grid-template-columns:1fr;gap:6px}.ldsp-ldc-stat-card{padding:8px;gap:6px;flex-direction:row}.ldsp-ldc-stat-icon{font-size:14px}.ldsp-ldc-stat-label{font-size:9px}.ldsp-ldc-stat-num{font-size:12px}.ldsp-ldc-section-title{font-size:10px}.ldsp-ldc-chart{padding:8px}.ldsp-ldc-chart-bars{height:50px}.ldsp-ldc-chart-label{font-size:8px}.ldsp-ldc-filter-section{gap:5px;padding-bottom:6px}.ldsp-ldc-filter-row{gap:6px}.ldsp-ldc-filter-label{font-size:8px;min-width:20px;padding-top:4px}.ldsp-ldc-filter-chip{padding:3px 6px;font-size:8px}.ldsp-ldc-trans-content{gap:6px}.ldsp-ldc-trans-summary{font-size:9px}.ldsp-ldc-trans-list{gap:4px}.ldsp-ldc-trans-item{padding:6px 8px;gap:6px}.ldsp-ldc-trans-icon{font-size:12px;width:20px;height:20px;border-radius:4px}.ldsp-ldc-trans-name{font-size:10px}.ldsp-ldc-trans-meta{font-size:8px;gap:4px}.ldsp-ldc-trans-type{font-size:8px;padding:1px 4px}.ldsp-ldc-trans-amount{font-size:11px}.ldsp-ldc-detail-amount-value{font-size:22px}.ldsp-ldc-detail-row{padding:8px 10px}.ldsp-ldc-detail-row .label,.ldsp-ldc-detail-row .value{font-size:10px}.ldsp-ticket-tabs{padding:0 6px}.ldsp-ticket-tab,.ldsp-melon-tab{padding:5px 8px;font-size:8px}.ldsp-ticket-item{padding:8px}.ldsp-ticket-item-title{font-size:10px}.ldsp-ticket-item-type,.ldsp-ticket-item-meta{font-size:8px}.ldsp-ldc-support{gap:8px}.ldsp-ldc-support-header{padding:8px 6px}.ldsp-ldc-support-title{font-size:12px;gap:5px}.ldsp-ldc-support-desc{font-size:9px}.ldsp-ldc-support-grid{gap:6px}.ldsp-ldc-support-card{padding:12px 8px}.ldsp-ldc-support-icon{font-size:24px;margin-bottom:6px}.ldsp-ldc-support-name{font-size:10px;margin-bottom:4px}.ldsp-ldc-support-amount{font-size:14px}.ldsp-ldc-support-badge{font-size:8px;padding:2px 5px;top:6px;right:6px}.ldsp-ldc-support-footer{padding:8px}.ldsp-ldc-support-footer-text{font-size:9px}.ldsp-github-star-card{gap:8px;padding:8px 10px}.ldsp-github-icon-wrap{width:26px;height:26px}.ldsp-github-icon{width:22px;height:22px}.ldsp-github-title{font-size:10px;gap:4px;flex-wrap:wrap}.ldsp-github-star-icon{font-size:12px}.ldsp-github-desc{font-size:8px;line-height:1.4}.ldsp-github-arrow{font-size:12px}.ldsp-cdk-user-card{padding:10px;gap:8px}.ldsp-cdk-user-avatar{width:36px;height:36px}.ldsp-cdk-user-name{font-size:12px}.ldsp-cdk-user-username{font-size:8px}.ldsp-cdk-user-level{font-size:8px;padding:2px 6px;margin-top:3px}.ldsp-cdk-score-card{min-width:60px;padding:8px}.ldsp-cdk-score-label{font-size:7px}.ldsp-cdk-score-value{font-size:16px}.ldsp-cdk-search{padding:6px 10px}.ldsp-cdk-search input{font-size:11px}.ldsp-cdk-qty-card{padding:8px;gap:6px}.ldsp-cdk-qty-item{padding:6px}.ldsp-cdk-qty-item .num{font-size:18px}.ldsp-cdk-qty-item .lbl{font-size:8px}.ldsp-cdk-item{padding:8px;gap:6px}.ldsp-cdk-item-name{font-size:11px}.ldsp-cdk-item-time{font-size:8px;padding:1px 4px}.ldsp-cdk-item-creator{font-size:9px}.ldsp-cdk-item-content{padding:5px 7px;font-size:9px;gap:6px}.ldsp-cdk-item-copy{width:24px;height:24px;font-size:10px}.ldsp-cdk-detail-title{font-size:12px}.ldsp-cdk-detail-meta{font-size:9px}.ldsp-cdk-detail-desc{font-size:10px;padding:8px}.ldsp-cdk-detail-content{padding:10px}.ldsp-cdk-detail-content-label{font-size:9px}.ldsp-cdk-detail-content-value{font-size:12px}.ldsp-cdk-detail-copy{font-size:9px}.ldsp-cdk-back-btn{padding:5px 10px;font-size:10px}.ldsp-cdk-detail-row{padding:6px 8px}.ldsp-cdk-detail-row .label,.ldsp-cdk-detail-row .value{font-size:9px}.ldsp-cdk-detail-tag{font-size:8px;padding:1px 6px}}
    @media (max-height:550px){.ldsp-ldc-body,.ldsp-ticket-body,.ldsp-melon-body,.ldsp-cdk-body{padding:8px;gap:6px}.ldsp-ldc-balance-card{padding:10px}.ldsp-ldc-balance-value{font-size:22px}.ldsp-ldc-stats-grid{gap:6px}.ldsp-ldc-stat-card{padding:8px}.ldsp-ldc-chart-bars{height:50px}.ldsp-ldc-section{gap:6px}.ldsp-ldc-filter-section{gap:5px;padding-bottom:6px}.ldsp-ldc-filter-chip{padding:4px 7px}.ldsp-ldc-support{gap:10px}.ldsp-ldc-support-header{padding:10px 8px}.ldsp-ldc-support-grid{gap:8px}.ldsp-ldc-support-card{padding:12px 8px}.ldsp-ldc-support-icon{font-size:26px;margin-bottom:6px}.ldsp-ldc-support-amount{font-size:16px}.ldsp-cdk-user-card{padding:10px;gap:8px}.ldsp-cdk-user-avatar{width:40px;height:40px}.ldsp-cdk-score-card{padding:8px}.ldsp-cdk-score-value{font-size:20px}.ldsp-cdk-qty-card{padding:10px}.ldsp-cdk-qty-item .num{font-size:18px}}
    @media (max-height:450px){.ldsp-ldc-body,.ldsp-cdk-body{padding:6px;gap:5px}.ldsp-ldc-balance-card{padding:8px}.ldsp-ldc-balance-value{font-size:18px}.ldsp-ldc-balance-label,.ldsp-ldc-balance-sub{font-size:9px}.ldsp-ldc-stats-grid{gap:4px}.ldsp-ldc-stat-card{padding:6px}.ldsp-ldc-stat-icon{font-size:12px}.ldsp-ldc-stat-num{font-size:11px}.ldsp-ldc-chart{padding:6px}.ldsp-ldc-chart-bars{height:40px}.ldsp-ldc-section{gap:4px}.ldsp-ldc-filter-section{gap:4px;padding-bottom:5px}.ldsp-ldc-filter-row{gap:4px}.ldsp-ldc-filter-chip{padding:3px 5px;font-size:8px}.ldsp-ldc-trans-item{padding:5px 6px}.ldsp-ldc-footer{padding-top:6px}.ldsp-ldc-support-header{padding:4px 0}.ldsp-ldc-support-title{font-size:12px}.ldsp-ldc-support-desc{font-size:10px}.ldsp-ldc-support-card{padding:8px 6px}.ldsp-ldc-support-icon{font-size:18px;margin-bottom:2px}.ldsp-ldc-support-name{font-size:10px}.ldsp-ldc-support-amount{font-size:12px}.ldsp-ldc-support-footer{padding:6px}.ldsp-ldc-support-footer-text{font-size:9px}.ldsp-cdk-user-card{padding:8px;gap:6px;flex-direction:row;text-align:left}.ldsp-cdk-user-avatar{width:36px;height:36px}.ldsp-cdk-user-name{font-size:12px}.ldsp-cdk-score-card{padding:6px;min-width:60px}.ldsp-cdk-score-value{font-size:16px}.ldsp-cdk-qty-card{padding:6px}.ldsp-cdk-qty-item .num{font-size:16px}.ldsp-cdk-item{padding:6px}}
    .ldsp-melon-overlay{position:absolute;top:0;left:0;right:0;bottom:0;background:var(--bg);border-radius:0 0 var(--r-lg) var(--r-lg);z-index:10;display:none;flex-direction:column;overflow:hidden}
    .ldsp-melon-overlay.show{display:flex}
    .ldsp-melon-header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;background:var(--bg-card);border-bottom:1px solid var(--border);flex-shrink:0}
    .ldsp-melon-title{font-size:13px;font-weight:700;display:flex;align-items:center;gap:6px;color:var(--txt)}
    .ldsp-melon-header-actions{display:flex;align-items:center;gap:6px}
    .ldsp-melon-refresh{padding:4px 10px;display:flex;align-items:center;justify-content:center;gap:4px;background:linear-gradient(135deg,rgba(74,222,128,.08),rgba(34,197,94,.12));border:1px solid rgba(74,222,128,.2);border-radius:6px;font-size:10px;color:#22c55e;cursor:pointer;transition:all .15s;white-space:nowrap}
    .ldsp-melon-refresh:hover{background:linear-gradient(135deg,rgba(74,222,128,.15),rgba(34,197,94,.2));border-color:rgba(74,222,128,.4);color:#16a34a}
    .ldsp-melon-refresh svg{flex-shrink:0}
    .ldsp-melon-close{width:24px;height:24px;display:flex;align-items:center;justify-content:center;background:var(--bg-el);border:1px solid var(--border);border-radius:6px;font-size:12px;color:var(--txt-sec);transition:background .15s,color .15s;cursor:pointer}
    .ldsp-melon-close:hover{background:var(--err-bg);color:var(--err);border-color:var(--err)}
    .ldsp-melon-tabs{display:flex;border-bottom:1px solid var(--border);padding:0 10px;background:var(--bg-card);flex-shrink:0}
    .ldsp-melon-tab{padding:8px 12px;font-size:10px;font-weight:600;color:var(--txt-mut);border-bottom:2px solid transparent;transition:color .15s,border-color .15s;cursor:pointer}
    .ldsp-melon-tab.active{color:var(--accent);border-color:var(--accent)}
    .ldsp-melon-tab:hover:not(.active){color:var(--txt-sec)}
    .ldsp-melon-body{flex:1;overflow-y:auto;padding:12px;background:var(--bg);display:flex;flex-direction:column;gap:10px}
    .ldsp-melon-info{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:10px;font-size:11px}
    .ldsp-melon-info-title{font-weight:600;color:var(--txt);margin-bottom:6px;display:flex;align-items:center;gap:5px}
    .ldsp-melon-info-row{display:flex;align-items:center;gap:6px;color:var(--txt-sec);font-size:10px;margin-top:4px}
    .ldsp-melon-info-label{color:var(--txt-mut);min-width:50px}
    .ldsp-melon-info-value{color:var(--txt);font-weight:500}
    .ldsp-melon-range{display:flex;align-items:center;gap:8px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:10px;flex-wrap:wrap}
    .ldsp-melon-range-label{font-size:10px;color:var(--txt-sec);white-space:nowrap}
    .ldsp-melon-range-input{width:70px;padding:5px 8px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:11px;color:var(--txt);text-align:center}
    .ldsp-melon-range-input:focus{border-color:var(--accent);outline:none}
    .ldsp-melon-range-sep{color:var(--txt-mut);font-size:10px}
    .ldsp-melon-range-hint{font-size:9px;color:var(--txt-mut);margin-left:auto}
    /* 模式选择器 - 卡片式设计 */
    .ldsp-melon-mode-selector{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:10px}
    .ldsp-melon-mode-label{font-size:10px;color:var(--txt-sec);margin-bottom:8px;display:block}
    .ldsp-melon-mode-cards{display:flex;gap:8px}
    .ldsp-melon-mode-card{flex:1;display:flex;align-items:center;gap:10px;padding:10px 12px;background:var(--bg-el);border:2px solid var(--border);border-radius:var(--r-md);cursor:pointer;transition:all .2s}
    .ldsp-melon-mode-card:hover{border-color:var(--txt-mut);background:var(--bg)}
    .ldsp-melon-mode-card.active{border-color:var(--accent);background:rgba(107,140,239,.08)}
    .ldsp-melon-mode-card input{display:none}
    .ldsp-melon-mode-card-icon{font-size:20px;flex-shrink:0}
    .ldsp-melon-mode-card-content{flex:1;min-width:0}
    .ldsp-melon-mode-card-title{font-size:11px;font-weight:600;color:var(--txt);margin-bottom:2px}
    .ldsp-melon-mode-card-desc{font-size:9px;color:var(--txt-mut)}
    .ldsp-melon-mode-card.active .ldsp-melon-mode-card-title{color:var(--accent)}
    .ldsp-melon-actions{display:flex;gap:8px}
    .ldsp-melon-btn-summarize{flex:1;padding:10px;background:linear-gradient(135deg,#22c55e,#16a34a);color:#fff;border:none;border-radius:var(--r-sm);font-size:12px;font-weight:600;cursor:pointer;transition:opacity .15s,transform .2s,box-shadow .2s;display:flex;align-items:center;justify-content:center;gap:6px}
    .ldsp-melon-btn-summarize:hover{box-shadow:0 4px 16px rgba(34,197,94,.35);transform:translateY(-1px)}
    .ldsp-melon-btn-summarize:disabled{opacity:.5;cursor:not-allowed;transform:none;box-shadow:none}
    .ldsp-melon-btn-summarize.loading{background:linear-gradient(135deg,#6b8cef,#5a7de0)}
    /* 输出区域 */
    .ldsp-melon-output-wrapper{display:flex;flex-direction:column;flex:1;min-height:0}
    .ldsp-melon-output-header{display:flex;align-items:center;justify-content:space-between;padding:6px 10px;background:var(--bg-card);border:1px solid var(--border);border-bottom:none;border-radius:var(--r-md) var(--r-md) 0 0}
    .ldsp-melon-output-title{font-size:10px;font-weight:600;color:var(--txt-sec)}
    .ldsp-melon-output-actions{display:flex;gap:6px}
    .ldsp-melon-resize-btn{display:flex;align-items:center;gap:4px;padding:4px 8px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:10px;color:var(--txt-sec);cursor:pointer;transition:all .15s}
    .ldsp-melon-resize-btn:hover{background:rgba(107,140,239,.1);border-color:var(--accent);color:var(--accent)}
    .ldsp-melon-copy-btn{display:flex;align-items:center;gap:4px;padding:4px 8px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:10px;color:var(--txt-sec);cursor:pointer;transition:all .15s}
    .ldsp-melon-copy-btn:hover{background:var(--accent);color:#fff;border-color:var(--accent)}
    .ldsp-melon-output{flex:1;min-height:120px;max-height:200px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:12px;overflow-y:auto;font-size:12px;line-height:1.6;color:var(--txt);transition:max-height .3s ease}
    .ldsp-melon-output.expanded{max-height:400px}
    .ldsp-melon-output-header+.ldsp-melon-output{border-radius:0 0 var(--r-md) var(--r-md)}
    .ldsp-melon-output:empty::before{content:'点击「立即吃瓜」获取帖子摘要...';color:var(--txt-mut);font-style:italic}
    .ldsp-melon-output-content{min-height:20px}
    .ldsp-melon-cursor{display:inline-block;color:var(--accent);animation:ldsp-blink 1s infinite}
    @keyframes ldsp-blink{0%,50%{opacity:1}51%,100%{opacity:0}}
    /* Markdown 渲染 */
    .ldsp-melon-markdown{font-size:12px;line-height:1.7}
    .ldsp-melon-markdown .ldsp-melon-p{margin:6px 0}
    .ldsp-melon-markdown .ldsp-melon-h2,.ldsp-melon-markdown .ldsp-melon-h3{font-size:13px;font-weight:700;margin:12px 0 8px;color:var(--txt);border-bottom:1px solid var(--border);padding-bottom:4px}
    .ldsp-melon-markdown .ldsp-melon-h4,.ldsp-melon-markdown .ldsp-melon-h5{font-size:12px;font-weight:600;margin:10px 0 6px;color:var(--txt)}
    .ldsp-melon-markdown .ldsp-melon-ul,.ldsp-melon-markdown .ldsp-melon-ol{margin:6px 0;padding-left:18px}
    .ldsp-melon-markdown .ldsp-melon-li,.ldsp-melon-markdown .ldsp-melon-oli{margin:3px 0}
    .ldsp-melon-markdown .ldsp-melon-quote{margin:8px 0;padding:8px 12px;background:var(--bg-el);border-left:3px solid var(--accent);border-radius:0 var(--r-sm) var(--r-sm) 0;color:var(--txt-sec);font-style:italic}
    .ldsp-melon-markdown strong{color:var(--accent);font-weight:600}
    .ldsp-melon-markdown .ldsp-melon-inline-code{background:var(--bg-el);padding:2px 5px;border-radius:3px;font-family:monospace;font-size:10px}
    .ldsp-melon-markdown .ldsp-melon-codeblock{background:var(--bg-el);padding:10px;border-radius:var(--r-sm);overflow-x:auto;margin:8px 0}
    .ldsp-melon-markdown .ldsp-melon-codeblock code{background:none;padding:0;font-family:monospace;font-size:10px}
    .ldsp-melon-markdown .ldsp-melon-hr{border:none;border-top:1px solid var(--border);margin:12px 0}
    .ldsp-melon-markdown .ldsp-melon-link{color:var(--accent);text-decoration:none}
    .ldsp-melon-markdown .ldsp-melon-link:hover{text-decoration:underline}
    .ldsp-melon-output h1,.ldsp-melon-output h2,.ldsp-melon-output h3{font-size:13px;font-weight:700;margin:12px 0 8px;color:var(--txt);border-bottom:1px solid var(--border);padding-bottom:4px}
    .ldsp-melon-output h1:first-child,.ldsp-melon-output h2:first-child,.ldsp-melon-output h3:first-child{margin-top:0}
    .ldsp-melon-output h4,.ldsp-melon-output h5{font-size:12px;font-weight:600;margin:10px 0 6px;color:var(--txt)}
    .ldsp-melon-output p{margin:6px 0}
    .ldsp-melon-output ul,.ldsp-melon-output ol{margin:6px 0;padding-left:18px}
    .ldsp-melon-output li{margin:3px 0}
    .ldsp-melon-output blockquote{margin:8px 0;padding:8px 12px;background:var(--bg-el);border-left:3px solid var(--accent);border-radius:0 var(--r-sm) var(--r-sm) 0;color:var(--txt-sec);font-style:italic}
    .ldsp-melon-output strong{color:var(--accent);font-weight:600}
    .ldsp-melon-output code{background:var(--bg-el);padding:2px 5px;border-radius:3px;font-family:monospace;font-size:10px}
    .ldsp-melon-output pre{background:var(--bg-el);padding:10px;border-radius:var(--r-sm);overflow-x:auto;margin:8px 0}
    .ldsp-melon-output pre code{background:none;padding:0}
    .ldsp-melon-output table{width:100%;border-collapse:collapse;margin:8px 0;font-size:10px}
    .ldsp-melon-output th,.ldsp-melon-output td{border:1px solid var(--border);padding:6px 8px;text-align:left}
    .ldsp-melon-output th{background:var(--bg-el);font-weight:600}
    .ldsp-melon-output hr{border:none;border-top:1px solid var(--border);margin:12px 0}
    .ldsp-melon-output a{color:var(--accent);text-decoration:none}
    .ldsp-melon-output a:hover{text-decoration:underline}
    .ldsp-melon-status{text-align:center;padding:20px;color:var(--txt-mut);font-size:11px}
    .ldsp-melon-status-icon{font-size:24px;margin-bottom:8px}
    .ldsp-melon-error{color:var(--err);background:var(--err-bg);padding:10px;border-radius:var(--r-sm);font-size:11px}
    /* 历史记录 */
    .ldsp-melon-history{display:flex;flex-direction:column;height:100%}
    .ldsp-melon-history-header{display:flex;align-items:center;justify-content:space-between;padding:8px 10px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);margin-bottom:10px}
    .ldsp-melon-history-header-left{display:flex;align-items:center;gap:8px}
    .ldsp-melon-history-count{font-size:10px;color:var(--txt-mut)}
    .ldsp-melon-history-storage-badge{font-size:9px;color:var(--txt-mut);background:var(--bg-el);padding:2px 6px;border-radius:10px}
    .ldsp-melon-history-storage-hint{font-size:10px;color:var(--txt-mut);margin-top:8px}
    .ldsp-melon-history-clear-all{padding:4px 8px;background:var(--err-bg);border:1px solid rgba(239,68,68,.2);border-radius:var(--r-sm);font-size:9px;color:var(--err);cursor:pointer;transition:all .15s}
    .ldsp-melon-history-clear-all:hover{background:var(--err);color:#fff}
    .ldsp-melon-history-list{flex:1;overflow-y:auto;display:flex;flex-direction:column;gap:8px}
    .ldsp-melon-history-item{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:10px;transition:border-color .15s}
    .ldsp-melon-history-item:hover{border-color:var(--accent)}
    .ldsp-melon-history-item-header{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;margin-bottom:6px}
    .ldsp-melon-history-item-title{font-size:11px;font-weight:600;color:var(--txt);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}
    .ldsp-melon-history-item-meta{display:flex;align-items:center;gap:6px;flex-shrink:0}
    .ldsp-melon-history-mode{font-size:9px;padding:2px 6px;border-radius:10px;font-weight:500}
    .ldsp-melon-history-mode.brief{background:rgba(59,130,246,.1);color:#3b82f6}
    .ldsp-melon-history-mode.detailed{background:rgba(34,197,94,.1);color:#22c55e}
    .ldsp-melon-history-date{font-size:9px;color:var(--txt-mut)}
    .ldsp-melon-history-item-preview{font-size:10px;color:var(--txt-sec);line-height:1.5;margin-bottom:8px;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
    .ldsp-melon-history-item-actions{display:flex;gap:6px}
    .ldsp-melon-history-btn{padding:4px 8px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:9px;color:var(--txt-sec);cursor:pointer;transition:all .15s}
    .ldsp-melon-history-btn:hover{background:var(--accent);color:#fff;border-color:var(--accent)}
    .ldsp-melon-history-delete:hover{background:var(--err);border-color:var(--err)}
    .ldsp-melon-history-empty{text-align:center;padding:40px 20px;color:var(--txt-mut)}
    .ldsp-melon-history-empty-icon{font-size:36px;margin-bottom:10px}
    .ldsp-melon-history-empty-text{font-size:12px;font-weight:500;margin-bottom:4px}
    .ldsp-melon-history-empty-hint{font-size:10px;color:var(--txt-mut)}
    .ldsp-melon-history-detail{display:flex;flex-direction:column;height:100%}
    .ldsp-melon-history-detail-header{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}
    .ldsp-melon-history-detail-actions{display:flex;gap:6px}
    .ldsp-melon-history-back{padding:6px 12px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:10px;color:var(--txt-sec);cursor:pointer;transition:all .15s}
    .ldsp-melon-history-back:hover{background:var(--bg-card);border-color:var(--accent);color:var(--accent)}
    .ldsp-melon-history-expand-btn{padding:6px 12px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:10px;color:var(--accent);cursor:pointer;transition:all .15s}
    .ldsp-melon-history-expand-btn:hover{background:rgba(107,140,239,.1);border-color:var(--accent)}
    .ldsp-melon-history-copy-all{padding:6px 12px;background:var(--accent);border:none;border-radius:var(--r-sm);font-size:10px;color:#fff;cursor:pointer;transition:opacity .15s}
    .ldsp-melon-history-copy-all:hover{opacity:.85}
    .ldsp-melon-history-detail-info{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:10px;margin-bottom:10px}
    .ldsp-melon-history-detail-title{font-size:12px;font-weight:600;color:var(--txt);margin-bottom:4px}
    .ldsp-melon-history-detail-meta{font-size:10px;color:var(--txt-mut)}
    .ldsp-melon-history-detail-content{flex:1;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:12px;overflow-y:auto;font-size:11px;line-height:1.6}
    /* 设置页 */
    .ldsp-melon-settings{display:flex;flex-direction:column;gap:12px}
    .ldsp-melon-setting-group{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);padding:12px}
    .ldsp-melon-setting-title{font-size:11px;font-weight:600;color:var(--txt);margin-bottom:10px;display:flex;align-items:center;gap:5px}
    .ldsp-melon-setting-row{display:flex;flex-direction:column;gap:4px;margin-bottom:10px}
    .ldsp-melon-setting-row:last-child{margin-bottom:0}
    .ldsp-melon-setting-label{font-size:10px;color:var(--txt-sec);display:flex;align-items:center;gap:4px}
    .ldsp-melon-setting-input{padding:8px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:11px;color:var(--txt)}
    .ldsp-melon-setting-input:focus{border-color:var(--accent);outline:none}
    .ldsp-melon-setting-input::placeholder{color:var(--txt-mut)}
    .ldsp-melon-setting-input:disabled{background:var(--bg);color:var(--txt-mut);cursor:not-allowed}
    .ldsp-melon-setting-actions{display:flex;gap:8px}
    .ldsp-melon-setting-btn{flex:1;padding:10px 12px;background:var(--grad);color:#fff;border:none;border-radius:var(--r-sm);font-size:11px;font-weight:600;cursor:pointer;transition:all .15s}
    .ldsp-melon-setting-btn:hover{box-shadow:0 4px 12px rgba(107,140,239,.3);transform:translateY(-1px)}
    .ldsp-melon-btn-edit{background:linear-gradient(135deg,#6b8cef,#5a7de0)}
    .ldsp-melon-btn-save{background:linear-gradient(135deg,#22c55e,#16a34a)}
    .ldsp-melon-btn-save:hover{box-shadow:0 4px 12px rgba(34,197,94,.35)}
    .ldsp-melon-btn-prompt{background:linear-gradient(135deg,#6b8cef,#5a7de0)}
    .ldsp-melon-setting-textarea{width:100%;padding:8px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:10px;color:var(--txt);resize:vertical;min-height:60px;font-family:monospace;line-height:1.5}
    .ldsp-melon-setting-textarea:focus{border-color:var(--accent);outline:none}
    .ldsp-melon-setting-textarea::placeholder{color:var(--txt-mut);font-size:9px;white-space:pre-wrap}
    .ldsp-melon-prompt-reset{margin-left:6px;cursor:pointer;color:var(--err);font-size:10px;opacity:.6;transition:all .15s}
    .ldsp-melon-prompt-reset:hover{opacity:1;color:var(--err)}
    .ldsp-melon-setting-prompt-actions{display:flex;gap:8px;margin-top:8px}
    .ldsp-melon-setting-security{display:flex;align-items:center;gap:8px;padding:10px 12px;background:rgba(34,197,94,.06);border:1px solid rgba(34,197,94,.15);border-radius:var(--r-md)}
    .ldsp-melon-setting-security-icon{font-size:14px;flex-shrink:0}
    .ldsp-melon-setting-security-text{font-size:10px;color:var(--txt-sec);line-height:1.4}
    .ldsp-melon-setting-security-text strong{color:var(--ok);font-weight:600}
    .ldsp-melon-setting-hint{font-size:9px;color:var(--txt-mut);margin-top:3px;line-height:1.4}
    .ldsp-melon-setting-hint strong{color:var(--accent);font-weight:500}
    .ldsp-melon-setting-subtitle{font-size:9px;color:var(--txt-mut);font-weight:400;margin-left:4px}
    .ldsp-melon-setting-actions{margin-top:10px}
    .ldsp-melon-prompt-status{font-size:9px;color:var(--txt-mut);margin-left:auto;font-weight:400}
    .ldsp-melon-prompt-status.custom{color:var(--ok)}
    .ldsp-melon-setting-danger{background:var(--err-bg);border-color:rgba(239,68,68,.15);padding:10px 12px}
    .ldsp-melon-setting-danger .ldsp-melon-setting-title{color:var(--err);margin-bottom:2px}
    .ldsp-melon-setting-danger-content{display:flex;align-items:center;justify-content:space-between;gap:12px}
    .ldsp-melon-setting-danger-info{flex:1}
    .ldsp-melon-setting-danger-desc{font-size:9px;color:var(--txt-sec)}
    .ldsp-melon-setting-danger .ldsp-melon-setting-btn{flex:none;padding:8px 16px}
    .ldsp-melon-btn-danger{background:linear-gradient(135deg,#ef4444,#dc2626) !important}
    .ldsp-melon-btn-danger:hover{box-shadow:0 4px 12px rgba(239,68,68,.35) !important}
    .ldsp-melon-warning{background:rgba(212,168,83,.1);border:1px solid rgba(212,168,83,.3);border-radius:var(--r-sm);padding:8px 10px;font-size:10px;color:var(--warn);margin-bottom:8px}
    .ldsp-melon-not-topic{text-align:center;padding:40px 20px;color:var(--txt-mut)}
    .ldsp-melon-not-topic-icon{font-size:36px;margin-bottom:10px}
    .ldsp-melon-not-topic-text{font-size:12px;line-height:1.6}
    .ldsp-melon-not-topic-hint{font-size:10px;color:var(--txt-mut);opacity:.7;margin-top:8px}
    /* 全屏展开查看弹窗 - 独立的变量定义以支持body级挂载 */
    .ldsp-melon-viewer-overlay{--bg:#12131a;--bg-card:rgba(24,26,36,.98);--bg-hover:rgba(38,42,56,.95);--bg-el:rgba(32,35,48,.88);--txt:#e4e6ed;--txt-sec:#9499ad;--txt-mut:#5d6275;--accent:#6b8cef;--border:rgba(255,255,255,.06);--r-sm:6px;--r-md:10px;--r-lg:14px;--err:#e07a8d;--err-bg:rgba(224,122,141,.12);position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.7);z-index:99999;display:flex;align-items:center;justify-content:center;animation:ldsp-viewer-fade-in .2s}
    .ldsp-melon-viewer-overlay.light{--bg:rgba(250,251,254,.97);--bg-card:rgba(255,255,255,.98);--bg-hover:rgba(238,242,250,.96);--bg-el:rgba(245,247,252,.94);--txt:#1e2030;--txt-sec:#4a5068;--txt-mut:#8590a6;--accent:#5070d0;--border:rgba(0,0,0,.08);--err:#d45d6e;--err-bg:rgba(212,93,110,.08)}
    @keyframes ldsp-viewer-fade-in{from{opacity:0}to{opacity:1}}
    @keyframes ldsp-viewer-scale-in{from{transform:scale(.9);opacity:0}to{transform:scale(1);opacity:1}}
    .ldsp-melon-viewer{position:absolute;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-lg);box-shadow:0 20px 60px rgba(0,0,0,.5);display:flex;flex-direction:column;min-width:320px;min-height:240px;max-width:95vw;max-height:90vh;overflow:hidden;animation:ldsp-viewer-scale-in .2s}
    .ldsp-melon-viewer-header{display:flex;align-items:center;justify-content:space-between;padding:12px 16px;background:var(--bg);border-bottom:1px solid var(--border);cursor:move;user-select:none;flex-shrink:0}
    .ldsp-melon-viewer-title{font-size:13px;font-weight:700;color:var(--txt);display:flex;align-items:center;gap:8px}
    .ldsp-melon-viewer-title-icon{font-size:16px}
    .ldsp-melon-viewer-actions{display:flex;gap:6px}
    .ldsp-melon-viewer-btn{width:28px;height:28px;display:flex;align-items:center;justify-content:center;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:12px;color:var(--txt-sec);cursor:pointer;transition:all .15s}
    .ldsp-melon-viewer-btn:hover{background:var(--bg-hover);border-color:var(--accent);color:var(--accent)}
    .ldsp-melon-viewer-close:hover{background:var(--err-bg);border-color:var(--err);color:var(--err)}
    .ldsp-melon-viewer-body{flex:1;display:flex;flex-direction:column;overflow:hidden;background:var(--bg-card)}
    .ldsp-melon-viewer-info{padding:12px 16px;background:var(--bg-el);border-bottom:1px solid var(--border);flex-shrink:0}
    .ldsp-melon-viewer-topic-title{font-size:14px;font-weight:700;color:var(--txt);line-height:1.4;margin-bottom:6px}
    .ldsp-melon-viewer-meta{display:flex;align-items:center;gap:8px;font-size:10px;color:var(--txt-mut)}
    .ldsp-melon-viewer-mode{padding:2px 6px;border-radius:8px;font-weight:600;font-size:9px}
    .ldsp-melon-viewer-mode.brief{background:rgba(34,197,94,.15);color:#22c55e}
    .ldsp-melon-viewer-mode.detailed{background:rgba(107,140,239,.15);color:#6b8cef}
    .ldsp-melon-viewer-content{flex:1;overflow-y:auto;padding:16px;background:var(--bg-card);color:var(--txt);--viewer-font-size:16px}
    .ldsp-melon-viewer-content.font-xs{--viewer-font-size:12px}
    .ldsp-melon-viewer-content.font-sm{--viewer-font-size:14px}
    .ldsp-melon-viewer-content.font-md{--viewer-font-size:16px}
    .ldsp-melon-viewer-content.font-lg{--viewer-font-size:18px}
    .ldsp-melon-viewer-content.font-xl{--viewer-font-size:20px}
    .ldsp-melon-viewer-content .ldsp-melon-markdown{font-size:var(--viewer-font-size,14px);line-height:1.7;color:var(--txt)}
    .ldsp-melon-viewer-content .ldsp-melon-markdown .ldsp-melon-p{color:var(--txt)}
    .ldsp-melon-viewer-content .ldsp-melon-markdown strong{color:var(--accent)}
    .ldsp-melon-viewer-content .ldsp-melon-markdown .ldsp-melon-h2,.ldsp-melon-viewer-content .ldsp-melon-markdown .ldsp-melon-h3,.ldsp-melon-viewer-content .ldsp-melon-markdown .ldsp-melon-h4{color:var(--txt);border-color:var(--border)}
    .ldsp-melon-viewer-content .ldsp-melon-markdown .ldsp-melon-quote{background:var(--bg-el);border-left-color:var(--accent);color:var(--txt-sec)}
    .ldsp-melon-viewer-content .ldsp-melon-markdown .ldsp-melon-inline-code{background:var(--bg-el);color:var(--txt)}
    .ldsp-melon-viewer-content .ldsp-melon-markdown .ldsp-melon-codeblock{background:var(--bg);border:1px solid var(--border);color:var(--txt);padding:10px;border-radius:6px;overflow-x:auto}
    .ldsp-melon-viewer-content .ldsp-melon-markdown .ldsp-melon-ul,.ldsp-melon-viewer-content .ldsp-melon-markdown .ldsp-melon-ol{margin:8px 0;padding-left:20px}
    .ldsp-melon-viewer-content .ldsp-melon-markdown .ldsp-melon-li,.ldsp-melon-viewer-content .ldsp-melon-markdown .ldsp-melon-oli{margin:4px 0;color:var(--txt)}
    .ldsp-melon-viewer-content .ldsp-melon-markdown .ldsp-melon-hr{border:none;border-top:1px solid var(--border);margin:12px 0}
    .ldsp-melon-viewer-content::-webkit-scrollbar{width:6px}
    .ldsp-melon-viewer-content::-webkit-scrollbar-track{background:var(--bg)}
    .ldsp-melon-viewer-content::-webkit-scrollbar-thumb{background:var(--txt-mut);border-radius:3px}
    .ldsp-melon-viewer-content::-webkit-scrollbar-thumb:hover{background:var(--txt-sec)}
    /* 对话功能样式 */
    .ldsp-melon-viewer-chat{padding:12px 16px;border-top:1px solid var(--border);background:var(--bg)}
    .ldsp-melon-chat-input-wrap{display:flex;gap:8px;align-items:center}
    .ldsp-melon-chat-input{flex:1;padding:10px 14px;border:1px solid var(--border);border-radius:var(--r-md);background:var(--bg-card);color:var(--txt);font-size:13px;outline:none;transition:border-color .15s}
    .ldsp-melon-chat-input:focus{border-color:var(--accent)}
    .ldsp-melon-chat-input::placeholder{color:var(--txt-mut)}
    .ldsp-melon-chat-input:disabled{opacity:.6;cursor:not-allowed}
    .ldsp-melon-chat-send{width:36px;height:36px;border:none;border-radius:var(--r-md);background:linear-gradient(135deg,#10b981,#059669);color:#fff;cursor:pointer;display:flex;align-items:center;justify-content:center;transition:all .15s;flex-shrink:0}
    .ldsp-melon-chat-send:hover:not(:disabled){transform:scale(1.05);box-shadow:0 4px 12px rgba(16,185,129,.3)}
    .ldsp-melon-chat-send:disabled{opacity:.5;cursor:not-allowed}
    .ldsp-melon-chat-send.loading{animation:ldsp-pulse 1s infinite}
    @keyframes ldsp-pulse{0%,100%{opacity:1}50%{opacity:.5}}
    .ldsp-melon-chat-hint{font-size:10px;color:var(--txt-mut);margin-top:6px;text-align:center}
    .ldsp-melon-font-btn{width:28px;height:28px;display:flex;align-items:center;justify-content:center;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:11px;color:var(--txt-sec);cursor:pointer;transition:all .15s;position:relative}
    .ldsp-melon-font-btn:hover{background:var(--bg-hover);border-color:var(--accent);color:var(--accent)}
    .ldsp-melon-font-dropdown{position:absolute;top:100%;right:0;margin-top:4px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-sm);box-shadow:0 4px 16px rgba(0,0,0,.2);padding:4px;display:none;z-index:10;min-width:70px}
    .ldsp-melon-font-dropdown.show{display:block}
    .ldsp-melon-font-option{padding:6px 10px;font-size:11px;color:var(--txt-sec);cursor:pointer;border-radius:4px;white-space:nowrap;transition:all .1s}
    .ldsp-melon-font-option:hover{background:var(--bg-hover);color:var(--txt)}
    .ldsp-melon-font-option.active{background:rgba(107,140,239,.15);color:var(--accent);font-weight:600}
    .ldsp-melon-chat-msg{margin-bottom:16px;animation:ldsp-slide-up .2s ease-out}
    @keyframes ldsp-slide-up{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}
    .ldsp-melon-chat-msg-label{font-size:11px;font-weight:600;margin-bottom:6px;display:flex;align-items:center;gap:4px}
    .ldsp-melon-chat-msg.assistant .ldsp-melon-chat-msg-label{color:var(--accent)}
    .ldsp-melon-chat-msg.user .ldsp-melon-chat-msg-label{color:#10b981}
    .ldsp-melon-chat-msg-content{font-size:var(--viewer-font-size,14px);line-height:1.6;color:var(--txt)}
    .ldsp-melon-chat-msg.user .ldsp-melon-chat-msg-content{background:var(--bg-el);padding:10px 14px;border-radius:var(--r-md);border:1px solid var(--border)}
    .ldsp-melon-chat-typing{color:var(--txt-mut);font-style:italic}
    /* 调整大小手柄 */
    .ldsp-melon-resize-handle{position:absolute;background:transparent}
    .ldsp-melon-resize-handle-e{top:10px;right:0;width:6px;height:calc(100% - 20px);cursor:e-resize}
    .ldsp-melon-resize-handle-s{bottom:0;left:10px;width:calc(100% - 20px);height:6px;cursor:s-resize}
    .ldsp-melon-resize-handle-se{bottom:0;right:0;width:16px;height:16px;cursor:se-resize}
    .ldsp-melon-resize-handle-se::before{content:'';position:absolute;right:3px;bottom:3px;width:8px;height:8px;border-right:2px solid var(--txt-mut);border-bottom:2px solid var(--txt-mut);opacity:.5}
    .ldsp-melon-resize-handle-w{top:10px;left:0;width:6px;height:calc(100% - 20px);cursor:w-resize}
    .ldsp-melon-resize-handle-n{top:0;left:10px;width:calc(100% - 20px);height:6px;cursor:n-resize}
    .ldsp-melon-resize-handle-nw{top:0;left:0;width:16px;height:16px;cursor:nw-resize}
    .ldsp-melon-resize-handle-ne{top:0;right:0;width:16px;height:16px;cursor:ne-resize}
    .ldsp-melon-resize-handle-sw{bottom:0;left:0;width:16px;height:16px;cursor:sw-resize}
    /* 自定义确认对话框 */
    .ldsp-melon-confirm-dialog{position:absolute;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,.5);display:flex;align-items:center;justify-content:center;z-index:100;animation:ldsp-fade-in .15s}
    .ldsp-melon-confirm-content{background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-lg);padding:20px;width:85%;max-width:280px;text-align:center;box-shadow:0 8px 32px rgba(0,0,0,.2);animation:ldsp-scale-in .2s}
    @keyframes ldsp-fade-in{from{opacity:0}to{opacity:1}}
    @keyframes ldsp-scale-in{from{transform:scale(.9);opacity:0}to{transform:scale(1);opacity:1}}
    .ldsp-melon-confirm-icon{font-size:32px;margin-bottom:12px}
    .ldsp-melon-confirm-message{font-size:12px;color:var(--txt);line-height:1.6;margin-bottom:16px}
    .ldsp-melon-confirm-actions{display:flex;gap:10px}
    .ldsp-melon-confirm-btn{flex:1;padding:10px 16px;border:none;border-radius:var(--r-sm);font-size:11px;font-weight:600;cursor:pointer;transition:all .15s}
    .ldsp-melon-confirm-cancel{background:var(--bg-el);color:var(--txt-sec);border:1px solid var(--border)}
    .ldsp-melon-confirm-cancel:hover{background:var(--bg);border-color:var(--txt-mut)}
    .ldsp-melon-confirm-ok{background:var(--err);color:#fff}
    .ldsp-melon-confirm-ok:hover{background:#dc2626;box-shadow:0 4px 12px rgba(239,68,68,.3)}
    .ldsp-user-meta{display:flex;align-items:center;flex-wrap:wrap;margin-top:2px}
    .ldsp-follow-stats{display:flex;gap:6px;padding:2px 0}
    .ldsp-follow-combined{display:inline-flex;align-items:center;gap:3px;padding:2px 0;font-size:10px;color:var(--txt-mut)}
    .ldsp-follow-part{cursor:pointer;transition:color .15s;font-weight:500}
    .ldsp-follow-part:hover{color:var(--accent)}
    .ldsp-follow-sep{color:var(--txt-mut);margin:0 1px}
    .ldsp-follow-num-following,.ldsp-follow-num-followers{font-weight:600;color:var(--txt-sec);transition:color .15s}
    .ldsp-follow-part:hover .ldsp-follow-num-following,.ldsp-follow-part:hover .ldsp-follow-num-followers{color:var(--accent)}
    .ldsp-join-days{display:inline-flex;align-items:center;font-size:10px;color:var(--txt-mut);margin-left:6px;cursor:pointer;transition:color .15s}
    .ldsp-join-days:hover{color:var(--accent)}
    .ldsp-join-days-num{font-weight:600;color:var(--txt-sec);margin:0 2px;transition:color .15s}
    .ldsp-join-days:hover .ldsp-join-days-num{color:var(--accent)}
    .ldsp-follow-stat{display:flex;align-items:center;gap:3px;padding:2px 6px;background:var(--bg-el);border:1px solid var(--border);border-radius:12px;font-size:9px;color:var(--txt-sec);cursor:pointer;transition:all .15s}
    .ldsp-follow-stat:hover{background:var(--bg-hover);border-color:var(--accent);color:var(--accent)}
    .ldsp-follow-stat:active{transform:scale(.98)}
    .ldsp-follow-stat-icon{display:flex;align-items:center;justify-content:center;width:12px;height:12px;opacity:.7}
    .ldsp-follow-stat-icon svg{width:11px;height:11px;stroke-width:2}
    .ldsp-follow-stat:hover .ldsp-follow-stat-icon{opacity:1}
    .ldsp-follow-stat:hover .ldsp-follow-stat-icon svg{stroke:var(--accent)}
    .ldsp-follow-stat-num{font-weight:700;color:var(--txt);font-size:10px}
    .ldsp-follow-stat-label{font-weight:500;font-size:9px}
    .ldsp-follow-overlay{position:absolute;top:0;left:0;right:0;bottom:0;background:var(--bg);border-radius:0 0 var(--r-lg) var(--r-lg);z-index:10;display:none;flex-direction:column;overflow:hidden}
    .ldsp-follow-overlay.show{display:flex}
    .ldsp-follow-header{display:flex;align-items:center;justify-content:space-between;padding:10px 12px;background:var(--bg-card);border-bottom:1px solid var(--border);flex-shrink:0}
    .ldsp-follow-title{font-size:13px;font-weight:700;display:flex;align-items:center;gap:6px;color:var(--txt)}
    .ldsp-follow-title svg{width:16px;height:16px;stroke:var(--accent);stroke-width:2}
    .ldsp-follow-close{width:24px;height:24px;display:flex;align-items:center;justify-content:center;background:var(--bg-el);border:1px solid var(--border);border-radius:6px;font-size:12px;color:var(--txt-sec);cursor:pointer;transition:background .15s,color .15s}
    .ldsp-follow-close:hover{background:var(--err-bg);color:var(--err);border-color:var(--err)}
    .ldsp-follow-tabs{display:flex;border-bottom:1px solid var(--border);background:var(--bg-card);flex-shrink:0}
    .ldsp-follow-tab{flex:1;display:flex;align-items:center;justify-content:center;gap:4px;padding:9px 10px;font-size:10px;font-weight:600;color:var(--txt-mut);border-bottom:2px solid transparent;cursor:pointer;transition:color .15s,border-color .15s,background .15s}
    .ldsp-follow-tab:hover:not(.active){background:var(--bg-hover)}
    .ldsp-follow-tab.active{color:var(--accent);border-color:var(--accent)}
    .ldsp-follow-tab-icon{display:flex;align-items:center;justify-content:center;width:14px;height:14px}
    .ldsp-follow-tab-icon svg{width:13px;height:13px;stroke-width:2}
    .ldsp-follow-tab.active .ldsp-follow-tab-icon svg{stroke:var(--accent)}
    .ldsp-follow-tab-count{padding:1px 6px;background:var(--bg-el);border-radius:10px;font-size:10px;font-weight:700;color:var(--txt-sec)}
    .ldsp-follow-tab.active .ldsp-follow-tab-count{background:var(--accent);color:#fff}
    .ldsp-follow-body{flex:1;overflow-y:auto;padding:10px;background:var(--bg)}
    .ldsp-follow-loading{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:40px 20px;color:var(--txt-mut)}
    .ldsp-follow-empty{display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px;padding:40px 20px;text-align:center;color:var(--txt-mut)}
    .ldsp-follow-empty-icon{width:48px;height:48px;opacity:.4}
    .ldsp-follow-empty-icon svg{width:100%;height:100%;stroke-width:1.5}
    .ldsp-follow-list{display:flex;flex-direction:column;gap:5px}
    .ldsp-follow-item{display:flex;align-items:center;gap:10px;padding:8px 10px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);cursor:pointer;transition:all .15s;text-decoration:none;position:relative;overflow:hidden}
    .ldsp-follow-item::before{content:'';position:absolute;left:0;top:0;bottom:0;width:2px;background:var(--accent);opacity:0;transition:opacity .15s}
    .ldsp-follow-item:hover{background:var(--bg-hover);border-color:rgba(107,140,239,.4);box-shadow:0 2px 8px rgba(107,140,239,.08)}
    .ldsp-follow-item:hover::before{opacity:1}
    .ldsp-follow-avatar-wrap{flex-shrink:0;position:relative}
    .ldsp-follow-avatar{width:36px;height:36px;border-radius:50%;object-fit:cover;background:var(--bg-el);border:2px solid var(--border);transition:border-color .15s,transform .15s}
    .ldsp-follow-item:hover .ldsp-follow-avatar{border-color:var(--accent);transform:scale(1.05)}
    .ldsp-follow-user-info{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}
    .ldsp-follow-user-name{font-size:12px;font-weight:600;color:var(--txt);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .ldsp-follow-item:hover .ldsp-follow-user-name{color:var(--accent)}
    .ldsp-follow-user-id{font-size:10px;color:var(--txt-mut);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .ldsp-follow-arrow{flex-shrink:0;width:20px;height:20px;display:flex;align-items:center;justify-content:center;color:var(--txt-mut);opacity:.4;transition:opacity .15s,transform .15s}
    .ldsp-follow-arrow svg{width:14px;height:14px}
    .ldsp-follow-item:hover .ldsp-follow-arrow{opacity:1;transform:translateX(2px);color:var(--accent)}
    .ldsp-activity-content{flex:1;overflow-y:auto;padding:8px}
    .ldsp-activity-toolbar{display:flex;align-items:center;gap:8px;margin-bottom:8px;padding:8px 10px;background:var(--bg-card);border:1px solid var(--border);border-radius:var(--r-md);min-height:32px;box-sizing:border-box}
    .ldsp-activity-search{flex:1;display:flex;align-items:center;gap:6px;min-width:0;height:20px}
    .ldsp-activity-search-icon{color:var(--txt-mut);font-size:12px;flex-shrink:0;line-height:1;display:flex;align-items:center}
    .ldsp-activity-search input{flex:1;border:none;background:transparent;font-size:12px;color:var(--txt);outline:none;min-width:40px;height:20px;line-height:20px;padding:0;margin:0}
    .ldsp-activity-search input::placeholder{color:var(--txt-mut)}
    .ldsp-activity-search-clear{width:16px;height:16px;display:flex;align-items:center;justify-content:center;background:var(--bg-el);border:none;border-radius:50%;cursor:pointer;color:var(--txt-mut);font-size:10px;line-height:1;transition:all .15s;flex-shrink:0;opacity:0;pointer-events:none}
    .ldsp-activity-search-clear.show{opacity:1;pointer-events:auto}
    .ldsp-activity-search-clear:hover{background:var(--err-bg);color:var(--err)}
    .ldsp-activity-toolbar-divider{width:1px;height:18px;background:var(--border);flex-shrink:0;align-self:center}
    .ldsp-activity-batch{display:flex;align-items:center;gap:4px;flex-shrink:0}
    .ldsp-activity-batch-label{font-size:11px;color:var(--txt-mut);white-space:nowrap;line-height:1;display:flex;align-items:center}
    .ldsp-activity-batch-select{padding:0 6px;background:var(--bg-el);border:1px solid var(--border);border-radius:var(--r-sm);font-size:11px;color:var(--txt);cursor:pointer;outline:none;transition:all .15s;width:52px;height:20px;line-height:18px;vertical-align:middle}
    .ldsp-activity-batch-select:hover,.ldsp-activity-batch-select:focus{border-color:var(--accent);background:var(--bg-hover)}
    .ldsp-activity-batch-select.loading{opacity:.5;pointer-events:none;cursor:wait}
    .ldsp-activity-toolbar.loading{position:relative;pointer-events:none}
    .ldsp-activity-toolbar.loading::after{content:'';position:absolute;inset:0;background:rgba(var(--bg-rgb,255,255,255),.6);border-radius:var(--r-md)}
    .ldsp-topic-list.loading{opacity:.4;pointer-events:none;position:relative}
    .ldsp-activity-loading-overlay{position:absolute;inset:0;display:flex;flex-direction:column;align-items:center;justify-content:center;background:rgba(var(--bg-rgb,255,255,255),.8);border-radius:var(--r-md);z-index:10}
    .ldsp-activity-loading-spinner{width:24px;height:24px;border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:ldsp-spin 0.8s linear infinite}
    .ldsp-activity-loading-text{margin-top:8px;font-size:11px;color:var(--txt-mut)}
    .ldsp-activity-stats{display:flex;align-items:center;font-size:9px;color:var(--txt-mut);margin-bottom:6px;padding:0 2px}
    .ldsp-activity-stats strong{color:var(--accent);font-weight:600}
    .ldsp-topic-list{display:flex;flex-direction:column;gap:6px}
    .ldsp-topic-list-enhanced .ldsp-topic-item{display:flex;flex-direction:row;align-items:center;gap:8px;padding:10px;background:var(--bg-card);border:1px solid var(--border);border-left:3px solid var(--accent);border-radius:var(--r-md);cursor:pointer;transition:all .15s ease;text-decoration:none}
    .ldsp-topic-item:hover{background:var(--bg-hover);border-color:rgba(107,140,239,.5);border-left-color:#5a7de0;transform:translateX(2px);box-shadow:0 2px 8px rgba(107,140,239,.1)}
    .ldsp-topic-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:6px}
    .ldsp-topic-header{display:flex;flex-direction:column;gap:4px}
    .ldsp-topic-title-row{display:flex;align-items:center;gap:5px;min-width:0}
    .ldsp-topic-badges{display:flex;gap:3px;flex-shrink:0}
    .ldsp-topic-badge{padding:1px 4px;border-radius:6px;font-size:8px;font-weight:600;line-height:1.2}
    .ldsp-badge-unread{background:var(--accent);color:#fff}
    .ldsp-badge-new{background:#10b981;color:#fff}
    .ldsp-topic-title{font-size:12px;font-weight:600;color:var(--txt);line-height:1.4;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1;min-width:0}
    .ldsp-topic-item:hover .ldsp-topic-title{color:var(--accent)}
    .ldsp-topic-info{display:flex;align-items:center;gap:4px;flex-wrap:wrap;min-height:16px}
    .ldsp-topic-tags{display:flex;gap:3px;flex-wrap:wrap}
    .ldsp-topic-tag{padding:1px 6px;background:rgba(107,140,239,.08);border:1px solid rgba(107,140,239,.2);border-radius:8px;font-size:8px;color:#5a7de0;max-width:55px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .ldsp-topic-tag-more{padding:1px 5px;background:rgba(107,140,239,.15);border:1px solid rgba(107,140,239,.3);border-radius:8px;font-size:8px;color:#4a6bc9;font-weight:600}
    .ldsp-topic-tag.ldsp-topic-category,.ldsp-bookmark-tag.ldsp-bookmark-category,.ldsp-mytopic-tag.ldsp-mytopic-category{background:var(--cat-bg,rgba(107,140,239,.12));border-color:var(--cat-bd,rgba(107,140,239,.35));color:var(--txt);font-weight:600}
    .ldsp-topic-footer{display:flex;align-items:center;gap:6px;flex-wrap:wrap}
    .ldsp-topic-posters{display:flex;align-items:center;flex-shrink:0}
    .ldsp-topic-avatar{width:16px;height:16px;border-radius:50%;border:1.5px solid var(--bg-card);margin-left:-5px;object-fit:cover;background:var(--bg-el)}
    .ldsp-topic-avatar:first-child{margin-left:0}
    .ldsp-poster-op{border-color:var(--accent)}
    .ldsp-poster-latest{border-color:#10b981}
    .ldsp-topic-posters-more{margin-left:2px;font-size:8px;color:var(--txt-mut)}
    .ldsp-topic-stats{display:flex;align-items:center;gap:6px;flex:1;justify-content:flex-end;flex-wrap:nowrap;min-width:0}
    .ldsp-topic-stat{display:flex;align-items:center;gap:2px;font-size:9px;color:var(--txt-mut);white-space:nowrap;flex-shrink:0}
    .ldsp-topic-stat svg{width:10px;height:10px;stroke-width:2;opacity:.6;flex-shrink:0}
    .ldsp-topic-stat em{font-style:normal}
    .ldsp-stat-like{color:#ef4444}
    .ldsp-stat-like svg{stroke:#ef4444;opacity:.8}
    .ldsp-topic-time{font-size:9px;color:var(--txt-mut);opacity:.8;white-space:nowrap;flex-shrink:0}
    .ldsp-topic-thumbnail{width:48px;height:48px;flex-shrink:0;border-radius:6px;overflow:hidden;background:var(--bg-el)}
    .ldsp-topic-thumbnail img{width:100%;height:100%;object-fit:cover;transition:transform .15s}
    .ldsp-topic-item:hover .ldsp-topic-thumbnail img{transform:scale(1.05)}
    .ldsp-bookmark-list{display:flex;flex-direction:column;gap:8px}
    .ldsp-bookmark-item{display:flex;flex-direction:column;gap:6px;padding:10px;background:var(--bg-card);border:1px solid var(--border);border-left:3px solid #f59e0b;border-radius:var(--r-md);cursor:pointer;transition:all .15s ease;text-decoration:none}
    .ldsp-bookmark-item:hover{background:var(--bg-hover);border-color:rgba(245,158,11,.5);border-left-color:#eab308;transform:translateX(2px);box-shadow:0 2px 8px rgba(245,158,11,.1)}
    .ldsp-bookmark-title{font-size:12px;font-weight:600;color:var(--txt);line-height:1.4;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
    .ldsp-bookmark-item:hover .ldsp-bookmark-title{color:#b45309}
    .ldsp-bookmark-meta{display:flex;flex-wrap:wrap;gap:8px;font-size:9px;color:var(--txt-mut);align-items:center}
    .ldsp-bookmark-time{display:flex;align-items:center;gap:3px;white-space:nowrap}
    .ldsp-bookmark-time svg{width:10px;height:10px;stroke-width:2;flex-shrink:0;opacity:.6}
    .ldsp-bookmark-tags{display:flex;flex-wrap:wrap;gap:3px}
    .ldsp-bookmark-tag{padding:1px 6px;background:rgba(245,158,11,.08);border:1px solid rgba(245,158,11,.2);border-radius:8px;font-size:8px;color:#b45309;max-width:70px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .ldsp-bookmark-tag-more{padding:1px 5px;background:rgba(245,158,11,.15);border:1px solid rgba(245,158,11,.3);border-radius:8px;font-size:8px;color:#92400e;font-weight:600}
    .ldsp-bookmark-excerpt{font-size:10px;color:var(--txt-sec);line-height:1.5;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;padding:6px 0 0;word-break:break-word;border-top:1px dashed var(--border);margin-top:2px}
    .ldsp-bookmark-excerpt *{all:revert;font-size:inherit;line-height:inherit;color:inherit;margin:0;padding:0;border:none;background:none;box-shadow:none}
    .ldsp-bookmark-excerpt img{display:inline-block!important;max-width:18px!important;max-height:18px!important;vertical-align:middle!important;margin:0 2px!important;border-radius:2px!important}
    .ldsp-bookmark-excerpt a{color:var(--accent)!important;text-decoration:none!important}
    .ldsp-bookmark-excerpt a:hover{text-decoration:underline!important}
    .ldsp-bookmark-excerpt .emoji{width:18px!important;height:18px!important;vertical-align:middle!important}
    .ldsp-bookmark-excerpt .lightbox,.ldsp-bookmark-excerpt .lightbox img{display:inline!important;max-width:18px!important;max-height:18px!important}
    .ldsp-bookmark-excerpt .anchor{display:none!important}
    .ldsp-reply-list{display:flex;flex-direction:column;gap:8px}
    .ldsp-reply-item{display:flex;flex-direction:column;gap:6px;padding:10px;background:var(--bg-card);border:1px solid var(--border);border-left:3px solid #10b981;border-radius:var(--r-md);cursor:pointer;transition:all .15s ease}
    .ldsp-reply-item:hover{background:var(--bg-hover);border-color:rgba(16,185,129,.5);border-left-color:#059669;transform:translateX(2px);box-shadow:0 2px 8px rgba(16,185,129,.1)}
    .ldsp-reply-title{font-size:12px;font-weight:600;color:var(--txt);line-height:1.4;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
    .ldsp-reply-item:hover .ldsp-reply-title{color:#059669}
    .ldsp-reply-meta{display:flex;flex-wrap:wrap;gap:8px;font-size:9px;color:var(--txt-mut);align-items:center}
    .ldsp-reply-time,.ldsp-reply-to{display:flex;align-items:center;gap:3px;white-space:nowrap}
    .ldsp-reply-time svg,.ldsp-reply-to svg{width:10px;height:10px;stroke-width:2;flex-shrink:0;opacity:.6}
    .ldsp-reply-to{color:#10b981;font-weight:500}
    .ldsp-reply-excerpt{font-size:10px;color:var(--txt-sec);line-height:1.5;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;padding:6px 0 0;word-break:break-word;border-top:1px dashed var(--border);margin-top:2px}
    .ldsp-reply-excerpt *{all:revert;font-size:inherit;line-height:inherit;color:inherit;margin:0;padding:0;border:none;background:none;box-shadow:none}
    .ldsp-reply-excerpt img{display:inline-block!important;max-width:16px!important;max-height:16px!important;vertical-align:middle!important;margin:0 2px!important;border-radius:2px!important}
    .ldsp-reply-excerpt a{color:var(--accent)!important;text-decoration:none!important}
    .ldsp-reply-excerpt a:hover{text-decoration:underline!important}
    .ldsp-reply-excerpt .emoji{width:16px!important;height:16px!important;vertical-align:middle!important}
    .ldsp-reply-excerpt .lightbox,.ldsp-reply-excerpt .lightbox img{display:inline!important;max-width:16px!important;max-height:16px!important}
    .ldsp-reply-excerpt .anchor{display:none!important}
    .ldsp-like-list{display:flex;flex-direction:column;gap:8px}
    .ldsp-like-item{display:flex;flex-direction:column;gap:6px;padding:10px;background:var(--bg-card);border:1px solid var(--border);border-left:3px solid #ef4444;border-radius:var(--r-md);cursor:pointer;transition:all .15s ease}
    .ldsp-like-item:hover{background:var(--bg-hover);border-color:rgba(239,68,68,.5);border-left-color:#dc2626;transform:translateX(2px);box-shadow:0 2px 8px rgba(239,68,68,.1)}
    .ldsp-like-title{font-size:12px;font-weight:600;color:var(--txt);line-height:1.4;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
    .ldsp-like-item:hover .ldsp-like-title{color:#dc2626}
    .ldsp-like-meta{display:flex;flex-wrap:wrap;gap:8px;font-size:9px;color:var(--txt-mut);align-items:center}
    .ldsp-like-time,.ldsp-like-author{display:flex;align-items:center;gap:3px;white-space:nowrap}
    .ldsp-like-time svg,.ldsp-like-author svg{width:10px;height:10px;stroke-width:2;flex-shrink:0;opacity:.6}
    .ldsp-like-author{color:#ef4444;font-weight:500}
    .ldsp-like-author svg{fill:#ef4444;stroke:none;opacity:.8}
    .ldsp-like-excerpt{font-size:10px;color:var(--txt-sec);line-height:1.5;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;padding:6px 0 0;word-break:break-word;border-top:1px dashed var(--border);margin-top:2px}
    .ldsp-like-excerpt *{all:revert;font-size:inherit;line-height:inherit;color:inherit;margin:0;padding:0;border:none;background:none;box-shadow:none}
    .ldsp-like-excerpt img{display:inline-block!important;max-width:16px!important;max-height:16px!important;vertical-align:middle!important;margin:0 2px!important;border-radius:2px!important}
    .ldsp-like-excerpt a{color:var(--accent)!important;text-decoration:none!important}
    .ldsp-like-excerpt a:hover{text-decoration:underline!important}
    .ldsp-like-excerpt .emoji{width:16px!important;height:16px!important;vertical-align:middle!important}
    .ldsp-like-excerpt .lightbox,.ldsp-like-excerpt .lightbox img{display:inline!important;max-width:16px!important;max-height:16px!important}
    .ldsp-like-excerpt .anchor{display:none!important}
    .ldsp-mytopic-list{display:flex;flex-direction:column;gap:8px}
    .ldsp-mytopic-item{display:flex;flex-direction:column;gap:5px;padding:10px;background:var(--bg-card);border:1px solid var(--border);border-left:3px solid #8b5cf6;border-radius:var(--r-md);cursor:pointer;transition:all .15s ease;text-decoration:none}
    .ldsp-mytopic-item:hover{background:var(--bg-hover);border-color:rgba(139,92,246,.5);border-left-color:#7c3aed;transform:translateX(2px);box-shadow:0 2px 8px rgba(139,92,246,.1)}
    .ldsp-mytopic-item.closed{opacity:.7}
    .ldsp-mytopic-item.closed .ldsp-mytopic-title{text-decoration:line-through;color:var(--txt-mut)}
    .ldsp-mytopic-header{display:flex;align-items:flex-start;gap:6px}
    .ldsp-mytopic-title{flex:1;font-size:12px;font-weight:600;color:var(--txt);line-height:1.4;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
    .ldsp-mytopic-item:hover .ldsp-mytopic-title{color:#7c3aed}
    .ldsp-mytopic-icons{display:flex;gap:3px;flex-shrink:0}
    .ldsp-mytopic-status{display:flex;align-items:center;justify-content:center;width:14px;height:14px}
    .ldsp-mytopic-status svg{width:11px;height:11px;fill:#8b5cf6}
    .ldsp-mytopic-status.ldsp-mytopic-closed svg{fill:var(--txt-mut);stroke:var(--txt-mut);stroke-width:2}
    .ldsp-mytopic-row{display:flex;align-items:center;flex-wrap:nowrap;gap:6px}
    .ldsp-mytopic-tags{display:flex;flex-wrap:wrap;gap:3px;flex:1;min-width:0}
    .ldsp-mytopic-tag{padding:1px 6px;background:rgba(139,92,246,.08);border:1px solid rgba(139,92,246,.2);border-radius:8px;font-size:8px;color:#7c3aed;max-width:60px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
    .ldsp-mytopic-tag-more{padding:1px 5px;background:rgba(139,92,246,.15);border:1px solid rgba(139,92,246,.3);border-radius:8px;font-size:8px;color:#6d28d9;font-weight:600}
    .ldsp-mytopic-time{display:flex;align-items:center;gap:3px;font-size:8px;color:var(--txt-mut);flex-shrink:0;white-space:nowrap}
    .ldsp-mytopic-time svg{width:9px;height:9px;stroke-width:2;flex-shrink:0;opacity:.7}
    .ldsp-mytopic-meta{display:flex;align-items:center;gap:8px;font-size:9px;color:var(--txt-mut);padding-top:5px;border-top:1px solid var(--border)}
    .ldsp-mytopic-stat{display:flex;align-items:center;gap:2px;white-space:nowrap}
    .ldsp-mytopic-stat svg{width:11px;height:11px;stroke-width:2;flex-shrink:0}
    .ldsp-mytopic-likes{color:#ef4444}
    .ldsp-mytopic-likes svg{stroke:#ef4444}
    .ldsp-mytopic-meta-right{display:flex;align-items:center;gap:6px;margin-left:auto;font-size:8px;white-space:nowrap}
    .ldsp-reaction-list{display:flex;flex-direction:column;gap:8px}
    .ldsp-reaction-item{display:flex;flex-direction:column;gap:6px;padding:10px;background:var(--bg-card);border:1px solid var(--border);border-left:3px solid #06b6d4;border-radius:var(--r-md);cursor:pointer;transition:all .15s ease}
    .ldsp-reaction-item:hover{background:var(--bg-hover);border-color:rgba(6,182,212,.5);border-left-color:#0891b2;transform:translateX(2px);box-shadow:0 2px 8px rgba(6,182,212,.1)}
    .ldsp-reaction-header{display:flex;align-items:center;gap:8px}
    .ldsp-reaction-icon{font-size:16px;flex-shrink:0;display:flex;align-items:center;justify-content:center;width:24px;height:24px;background:rgba(6,182,212,.1);border-radius:6px}
    .ldsp-reaction-title{flex:1;font-size:12px;font-weight:600;color:var(--txt);line-height:1.4;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical}
    .ldsp-reaction-item:hover .ldsp-reaction-title{color:#0891b2}
    .ldsp-reaction-meta{display:flex;flex-wrap:wrap;gap:8px;font-size:9px;color:var(--txt-mut);align-items:center}
    .ldsp-reaction-avatar{width:16px;height:16px;border-radius:50%;flex-shrink:0;object-fit:cover}
    .ldsp-reaction-author{display:flex;align-items:center;gap:3px;white-space:nowrap;color:#06b6d4;font-weight:500}
    .ldsp-reaction-time{display:flex;align-items:center;gap:3px;white-space:nowrap}
    .ldsp-reaction-time svg{width:10px;height:10px;stroke-width:2;flex-shrink:0;opacity:.6}
    .ldsp-reaction-count{padding:1px 5px;background:rgba(6,182,212,.15);border:1px solid rgba(6,182,212,.3);border-radius:8px;font-size:8px;color:#0891b2;font-weight:600}
    .ldsp-reaction-excerpt{font-size:10px;color:var(--txt-sec);line-height:1.5;overflow:hidden;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;padding:6px 0 0;word-break:break-word;border-top:1px dashed var(--border);margin-top:2px}
    .ldsp-reaction-excerpt *{all:revert;font-size:inherit;line-height:inherit;color:inherit;margin:0;padding:0;border:none;background:none;box-shadow:none}
    .ldsp-reaction-excerpt img{display:inline-block!important;max-width:16px!important;max-height:16px!important;vertical-align:middle!important;margin:0 2px!important;border-radius:2px!important}
    .ldsp-reaction-excerpt a{color:var(--accent)!important;text-decoration:none!important}
    .ldsp-reaction-excerpt a:hover{text-decoration:underline!important}
    .ldsp-reaction-excerpt .emoji{width:16px!important;height:16px!important;vertical-align:middle!important}
    .ldsp-reaction-excerpt .lightbox,.ldsp-reaction-excerpt .lightbox img{display:inline!important;max-width:16px!important;max-height:16px!important}
    .ldsp-reaction-excerpt .anchor{display:none!important}
    /* 活动内容区响应式 */
    @media (max-width:380px){.ldsp-activity-content{padding:8px}.ldsp-topic-list{gap:6px}.ldsp-topic-item{padding:10px;gap:8px}.ldsp-topic-title{font-size:12px}.ldsp-topic-footer{gap:6px}.ldsp-topic-stats{gap:6px}.ldsp-topic-stat{font-size:9px}.ldsp-topic-time{font-size:9px}.ldsp-topic-thumbnail{width:48px;height:48px}.ldsp-bookmark-list,.ldsp-reply-list,.ldsp-like-list,.ldsp-reaction-list,.ldsp-mytopic-list{gap:8px}.ldsp-bookmark-item,.ldsp-reply-item,.ldsp-like-item,.ldsp-reaction-item,.ldsp-mytopic-item{padding:10px}.ldsp-bookmark-title,.ldsp-reply-title,.ldsp-like-title,.ldsp-reaction-title,.ldsp-mytopic-title{font-size:12px}.ldsp-bookmark-meta,.ldsp-reply-meta,.ldsp-like-meta,.ldsp-reaction-meta,.ldsp-mytopic-meta{gap:10px;font-size:9px}.ldsp-follow-stats{gap:6px}.ldsp-follow-stat{padding:3px 6px;font-size:9px}.ldsp-follow-tab{padding:8px 10px;font-size:10px}.ldsp-follow-item{padding:10px;gap:10px}.ldsp-follow-avatar{width:36px;height:36px}.ldsp-follow-user-name{font-size:12px}}
    @media (max-width:320px){.ldsp-activity-content{padding:6px}.ldsp-topic-list{gap:4px}.ldsp-topic-item{padding:8px;gap:6px}.ldsp-topic-title{font-size:11px}.ldsp-topic-footer{gap:4px}.ldsp-topic-stats{gap:4px}.ldsp-topic-stat{font-size:8px}.ldsp-topic-stat svg{width:9px;height:9px}.ldsp-topic-time{font-size:8px}.ldsp-topic-thumbnail{width:40px;height:40px}.ldsp-topic-avatar{width:14px;height:14px;margin-left:-4px}.ldsp-topic-tag{max-width:45px;font-size:7px}.ldsp-bookmark-list,.ldsp-reply-list,.ldsp-like-list,.ldsp-reaction-list,.ldsp-mytopic-list{gap:6px}.ldsp-bookmark-item,.ldsp-reply-item,.ldsp-like-item,.ldsp-reaction-item,.ldsp-mytopic-item{padding:8px}.ldsp-bookmark-title,.ldsp-reply-title,.ldsp-like-title,.ldsp-reaction-title,.ldsp-mytopic-title{font-size:10px}.ldsp-bookmark-meta,.ldsp-reply-meta,.ldsp-like-meta,.ldsp-reaction-meta,.ldsp-mytopic-meta{gap:8px;font-size:8px}.ldsp-bookmark-excerpt,.ldsp-reply-excerpt,.ldsp-like-excerpt,.ldsp-reaction-excerpt{font-size:9px;-webkit-line-clamp:2}.ldsp-bookmark-tag,.ldsp-reply-to,.ldsp-like-author,.ldsp-reaction-author,.ldsp-mytopic-tag{font-size:7px}.ldsp-bookmark-time svg,.ldsp-reply-time svg,.ldsp-like-time svg,.ldsp-reaction-time svg,.ldsp-mytopic-time svg{width:9px;height:9px}.ldsp-follow-stats{gap:4px}.ldsp-follow-stat{padding:2px 4px;font-size:8px}.ldsp-follow-stat-icon{width:10px;height:10px}.ldsp-follow-stat-icon svg{width:9px;height:9px}.ldsp-follow-stat-num{font-size:9px}.ldsp-follow-tab{padding:7px 8px;font-size:9px}.ldsp-follow-tab-icon{width:12px;height:12px}.ldsp-follow-tab-icon svg{width:11px;height:11px}.ldsp-follow-tab-count{font-size:8px;padding:1px 4px}.ldsp-follow-item{padding:8px;gap:8px}.ldsp-follow-avatar{width:32px;height:32px}.ldsp-follow-user-name{font-size:11px}.ldsp-follow-user-id{font-size:9px}}
    @media (max-width:280px){.ldsp-topic-thumbnail{display:none}.ldsp-topic-posters{display:none}.ldsp-topic-stats{justify-content:flex-start}.ldsp-topic-tags{display:none}.ldsp-bookmark-tags,.ldsp-mytopic-tags{display:none}.ldsp-bookmark-excerpt,.ldsp-reply-excerpt,.ldsp-like-excerpt,.ldsp-reaction-excerpt{-webkit-line-clamp:2}.ldsp-follow-stat-label{display:none}.ldsp-follow-arrow{display:none}.ldsp-follow-tab-text{display:none}.ldsp-activity-content{padding:4px}.ldsp-topic-item{padding:6px}.ldsp-topic-title{font-size:10px}.ldsp-bookmark-item,.ldsp-reply-item,.ldsp-like-item,.ldsp-reaction-item,.ldsp-mytopic-item{padding:6px}.ldsp-bookmark-title,.ldsp-reply-title,.ldsp-like-title,.ldsp-reaction-title,.ldsp-mytopic-title{font-size:9px}.ldsp-follow-item{padding:6px;gap:6px}.ldsp-follow-avatar{width:28px;height:28px}.ldsp-follow-user-name{font-size:10px}.ldsp-reaction-icon{width:20px;height:20px;font-size:14px}}
    .ldsp-load-more{display:flex;align-items:center;justify-content:center;padding:12px;color:var(--txt-sec);font-size:10px}
    .ldsp-load-more.loading::after{content:'';width:14px;height:14px;border:2px solid var(--border);border-top-color:var(--accent);border-radius:50%;animation:ldsp-spin .8s linear infinite;margin-left:6px}
    .ldsp-activity-placeholder{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 20px;text-align:center;color:var(--txt-mut)}
    .ldsp-activity-placeholder-icon{font-size:32px;margin-bottom:10px;opacity:.5}
    .ldsp-activity-placeholder-text{font-size:11px}
    .ldsp-tooltip{position:fixed;z-index:2147483647;max-width:220px;padding:6px 10px;background:linear-gradient(135deg,rgba(30,32,48,.96),rgba(24,26,38,.98));color:#e8eaf0;font-size:11px;font-weight:500;line-height:1.4;border-radius:8px;box-shadow:0 4px 16px rgba(0,0,0,.3),0 0 0 1px rgba(255,255,255,.06);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);pointer-events:none;opacity:0;transform:translateY(4px);transition:opacity .15s ease,transform .15s ease;white-space:pre-line;word-break:break-word}
    .ldsp-tooltip.show{opacity:1;transform:translateY(0)}
    .ldsp-tooltip::before{content:'';position:absolute;width:8px;height:8px;background:inherit;transform:rotate(45deg);box-shadow:-1px -1px 0 rgba(255,255,255,.06)}
    .ldsp-tooltip.top::before{bottom:-4px;left:50%;margin-left:-4px}
    .ldsp-tooltip.bottom::before{top:-4px;left:50%;margin-left:-4px}
    .ldsp-tooltip.left::before{bottom:-4px;right:12px}
    .ldsp-tooltip.right::before{bottom:-4px;left:12px}
    #ldsp-panel.light .ldsp-tooltip{background:linear-gradient(135deg,rgba(255,255,255,.98),rgba(248,250,254,.98));color:#2d3148;box-shadow:0 4px 16px rgba(0,0,0,.12),0 0 0 1px rgba(0,0,0,.06)}
    #ldsp-panel.light .ldsp-tooltip::before{box-shadow:-1px -1px 0 rgba(0,0,0,.04)}`;
            }
        };
        
        // ==================== Tooltip 管理器 ====================
        const Tooltip = {
            el: null,
            timer: null,
            currentTarget: null,
            
            init() {
                if (this.el) return;
                this.el = document.createElement('div');
                this.el.className = 'ldsp-tooltip';
                document.body.appendChild(this.el);
            },
            
            show(target, text, delay = 400) {
                if (!text || !target) return;
                this.hide();
                this.currentTarget = target;
                this.timer = setTimeout(() => {
                    if (this.currentTarget !== target) return;
                    this.el.textContent = text;
                    this.el.classList.remove('show', 'top', 'bottom', 'left', 'right');
                    
                    // 先显示以获取尺寸
                    this.el.style.visibility = 'hidden';
                    this.el.style.display = 'block';
                    
                    const rect = target.getBoundingClientRect();
                    const tipRect = this.el.getBoundingClientRect();
                    const padding = 8;
                    
                    // 计算位置(优先显示在上方)
                    let top, left;
                    const spaceAbove = rect.top;
                    const spaceBelow = window.innerHeight - rect.bottom;
                    
                    if (spaceAbove >= tipRect.height + padding || spaceAbove > spaceBelow) {
                        // 显示在上方
                        top = rect.top - tipRect.height - padding;
                        this.el.classList.add('top');
                    } else {
                        // 显示在下方
                        top = rect.bottom + padding;
                        this.el.classList.add('bottom');
                    }
                    
                    // 水平居中,但不超出屏幕
                    left = rect.left + (rect.width - tipRect.width) / 2;
                    if (left < padding) {
                        left = padding;
                        this.el.classList.add('right');
                    } else if (left + tipRect.width > window.innerWidth - padding) {
                        left = window.innerWidth - tipRect.width - padding;
                        this.el.classList.add('left');
                    }
                    
                    // 确保不超出顶部
                    if (top < padding) top = padding;
                    
                    this.el.style.top = `${top}px`;
                    this.el.style.left = `${left}px`;
                    this.el.style.visibility = '';
                    
                    // 添加面板主题类
                    const panel = document.getElementById('ldsp-panel');
                    if (panel?.classList.contains('light')) {
                        this.el.closest('body').querySelector('.ldsp-tooltip')?.classList.add('light-theme');
                    }
                    
                    requestAnimationFrame(() => this.el.classList.add('show'));
                }, delay);
            },
            
            hide() {
                if (this.timer) {
                    clearTimeout(this.timer);
                    this.timer = null;
                }
                this.currentTarget = null;
                if (this.el) {
                    this.el.classList.remove('show');
                }
            },
            
            // 绑定到面板
            bindToPanel(panel) {
                this.init();
                
                // 使用事件委托
                panel.addEventListener('mouseenter', (e) => {
                    const target = e.target.closest('[title], [data-tip], [data-ldsp-tooltip]');
                    if (!target) return;
                    const text = target.dataset.ldspTooltip || target.dataset.tip || target.getAttribute('title');
                    if (text) {
                        // 临时移除 title 防止浏览器默认提示
                        if (target.hasAttribute('title')) {
                            target.dataset.tip = text;
                            target.removeAttribute('title');
                        }
                        this.show(target, text);
                    }
                }, true);
                
                panel.addEventListener('mouseleave', (e) => {
                    const target = e.target.closest('[data-tip], [data-ldsp-tooltip]');
                    if (target) this.hide();
                }, true);
                
                // 滚动和点击时隐藏
                panel.addEventListener('scroll', () => this.hide(), true);
                panel.addEventListener('click', () => this.hide(), true);
            }
        };

        // ==================== 工单管理器 ====================
        class TicketManager {
            // 跨标签页共享的缓存 key
            static CACHE_KEY = 'ldsp_ticket_unread';
            static CACHE_TTL = 30 * 1000;  // 30 秒缓存有效期
            
            constructor(oauth, panelBody) {
                this.oauth = oauth;
                this.panelBody = panelBody;
                this.overlay = null;
                this.ticketTypes = [];
                this.tickets = [];
                this.currentTicket = null;
                this.currentView = 'list';
                this.unreadCount = 0;
                this._isOverlayOpen = false;  // 工单面板是否打开
                this._lastHiddenTime = null;  // 页面隐藏时间,用于触发式检测
                this._checkUnreadPending = false;  // v3.5.2.9: 防止重复请求
            }

            async init() {
                this._createOverlay();
                await this._loadTicketTypes();
                this._bindVisibilityHandler();
                // 延迟 5 秒后首次检查
                setTimeout(() => this._checkUnread(), 5000);
            }
            
            _bindVisibilityHandler() {
                // 页面可见性变化时触发式检测(切换标签页、最小化窗口等)
                this._visibilityHandler = () => {
                    if (document.hidden) {
                        // 页面隐藏时,记录时间
                        this._lastHiddenTime = Date.now();
                    } else {
                        // 页面恢复可见时,检查是否超过10分钟阈值
                        const hiddenDuration = this._lastHiddenTime ? Date.now() - this._lastHiddenTime : 0;
                        const TEN_MINUTES = 10 * 60 * 1000;  // 10分钟
                        if (hiddenDuration >= TEN_MINUTES) {
                            // 超过10分钟,触发检测
                            this._checkUnread();
                        }
                        this._lastHiddenTime = null;
                    }
                };
                document.addEventListener('visibilitychange', this._visibilityHandler);
            }

            _createOverlay() {
                this.overlay = document.createElement('div');
                this.overlay.className = 'ldsp-ticket-overlay';
                this.overlay.innerHTML = `
                    <div class="ldsp-ticket-header">
                        <div class="ldsp-ticket-title">📪 工单系统</div>
                        <div class="ldsp-ticket-close">×</div>
                    </div>
                    <div class="ldsp-ticket-tabs">
                        <div class="ldsp-ticket-tab active" data-tab="list">我的工单</div>
                        <div class="ldsp-ticket-tab" data-tab="create">提交工单</div>
                    </div>
                    <div class="ldsp-ticket-body"></div>`;
                if (this.panelBody) {
                    this.panelBody.appendChild(this.overlay);
                }
                this._bindEvents();
            }

            _bindEvents() {
                this.overlay.querySelector('.ldsp-ticket-close').addEventListener('click', () => this.hide());
                document.addEventListener('keydown', e => {
                    if (e.key === 'Escape' && this.overlay.classList.contains('show')) this.hide();
                });
                this.overlay.querySelectorAll('.ldsp-ticket-tab').forEach(tab => {
                    tab.addEventListener('click', () => {
                        this.overlay.querySelectorAll('.ldsp-ticket-tab').forEach(t => t.classList.remove('active'));
                        tab.classList.add('active');
                        const tabName = tab.dataset.tab;
                        if (tabName === 'list') {
                            // 先显示加载状态
                            this._showListLoading();
                            this._loadTickets().then(() => this._renderList());
                        } else if (tabName === 'create') {
                            this._renderCreate();
                        }
                    });
                });
            }

            async _loadTicketTypes() {
                const defaultTypes = [
                    { id: 'feature_request', label: '功能建议', icon: '💡' },
                    { id: 'bug_report', label: 'BUG反馈', icon: '📪' }
                ];
                try {
                    const result = await this.oauth?.api('/api/tickets/types');
                    const data = result?.data?.data || result?.data;
                    this.ticketTypes = (result?.success && data?.types) ? data.types : defaultTypes;
                } catch (e) {
                    this.ticketTypes = defaultTypes;
                }
            }

            // 检查未读工单数(触发式调用,支持跨标签页缓存)
            // 触发时机:1. 面板打开 2. 页面可见性恢复(>10分钟) 3. 数据同步完成
            async _checkUnread(forceRefresh = false) {
                // 1. 检查登录状态
                if (!this.oauth?.isLoggedIn()) return;
                
                // v3.5.2.9: 防止并发请求
                if (this._checkUnreadPending) return;
                
                const now = Date.now();
                
                // 2. 检查跨标签页共享缓存(除非强制刷新)
                if (!forceRefresh) {
                    try {
                        const cached = GM_getValue(TicketManager.CACHE_KEY, null);
                        if (cached && (now - cached.time) < TicketManager.CACHE_TTL) {
                            // 使用缓存数据更新徽章
                            this.unreadCount = cached.count || 0;
                            this._updateBadge();
                            return;
                        }
                    } catch (e) { /* 缓存读取失败,继续请求 */ }
                }
                
                // 3. 发起请求
                this._checkUnreadPending = true;
                try {
                    const result = await this.oauth.api('/api/tickets/unread/count');
                    const data = result.data?.data || result.data;
                    if (result.success) {
                        this.unreadCount = data?.count || 0;
                        this._updateBadge();
                        // 更新跨标签页缓存
                        try {
                            GM_setValue(TicketManager.CACHE_KEY, { count: this.unreadCount, time: now });
                        } catch (e) { /* 缓存写入失败,忽略 */ }
                    }
                } catch (e) {
                    // 静默失败(未登录等情况)
                } finally {
                    this._checkUnreadPending = false;
                }
            }

            _updateBadge() {
                const btn = document.querySelector('.ldsp-ticket-btn');
                if (!btn) return;
                let badge = btn.querySelector('.ldsp-ticket-badge');
                if (this.unreadCount > 0) {
                    if (!badge) {
                        badge = document.createElement('span');
                        badge.className = 'ldsp-ticket-badge';
                        btn.appendChild(badge);
                    }
                    badge.textContent = this.unreadCount > 99 ? '99+' : this.unreadCount;
                } else if (badge) {
                    badge.remove();
                }
            }

            async show() {
                this.currentView = 'list';
                this._isOverlayOpen = true;
                const activeTab = this.overlay.querySelector('.ldsp-ticket-tab.active');
                if (activeTab?.dataset.tab === 'create') {
                    this._renderCreate();
                } else {
                    // 先显示加载状态
                    this._showListLoading();
                }
                this.overlay.classList.add('show');
                // 异步加载工单列表
                await this._loadTickets();
                this._updateTabBadge();
                if (activeTab?.dataset.tab !== 'create') {
                    this._renderList();
                }
                // 工单面板打开时立即检查一次(触发式检测)
                if (!document.hidden) {
                    this._checkUnread(true);  // 强制刷新,忽略缓存
                }
            }

            _updateTabBadge() {
                const listTab = this.overlay.querySelector('.ldsp-ticket-tab[data-tab="list"]');
                if (!listTab) return;
                const hasUnread = this.tickets.some(t => t.has_new_reply);
                listTab.classList.toggle('has-unread', hasUnread);
            }

            _showListLoading() {
                const body = this.overlay.querySelector('.ldsp-ticket-body');
                if (!body) return;
                body.classList.remove('detail-mode');
                body.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;flex:1;min-height:120px"><div class="ldsp-loading"><div class="ldsp-spinner"></div><div>加载中...</div></div></div>';
            }

            hide() {
                this._isOverlayOpen = false;
                this.overlay.classList.remove('show');
                this.currentView = 'list';
                this.currentTicket = null;
                this.overlay.querySelectorAll('.ldsp-ticket-tab').forEach(t => t.classList.remove('active'));
                this.overlay.querySelector('.ldsp-ticket-tab[data-tab="list"]')?.classList.add('active');
            }

            async _loadTickets() {
                try {
                    const result = await this.oauth?.api('/api/tickets');
                    const data = result?.data?.data || result?.data;
                    this.tickets = result?.success ? (data?.tickets || []) : [];
                } catch (e) {
                    this.tickets = [];
                }
            }

            _renderList() {
                this.currentView = 'list';
                const body = this.overlay.querySelector('.ldsp-ticket-body');
                body.classList.remove('detail-mode');
                
                if (this.tickets.length === 0) {
                    body.innerHTML = `
                        <div class="ldsp-ticket-empty">
                            <div class="ldsp-ticket-empty-icon">📭</div>
                            <div>暂无工单记录</div>
                            <div style="margin-top:6px;font-size:10px">点击"提交工单"反馈建议或问题</div>
                        </div>`;
                    return;
                }

                body.innerHTML = `
                    <div class="ldsp-ticket-list">
                        ${this.tickets.map(t => `
                            <div class="ldsp-ticket-item ${t.has_new_reply ? 'has-reply' : ''}" data-id="${t.id}">
                                <div class="ldsp-ticket-item-header">
                                    <span class="ldsp-ticket-item-type">${this._getTypeIcon(t.type)} ${this._getTypeLabel(t.type)}</span>
                                    <span class="ldsp-ticket-item-status ${t.status}">${t.status === 'open' ? '处理中' : '已关闭'}</span>
                                </div>
                                <div class="ldsp-ticket-item-title">${Utils.sanitize(t.title, 50)}</div>
                                <div class="ldsp-ticket-item-meta">
                                    <span>#${t.id}</span>
                                    <span>${this._formatTime(t.created_at)}</span>
                                </div>
                            </div>
                        `).join('')}
                    </div>`;

                body.querySelectorAll('.ldsp-ticket-item').forEach(item => {
                    item.addEventListener('click', () => this._showDetail(item.dataset.id));
                });
            }

            _renderCreate() {
                this.currentView = 'create';
                const body = this.overlay.querySelector('.ldsp-ticket-body');
                body.classList.remove('detail-mode');
                
                if (!this.ticketTypes || this.ticketTypes.length === 0) {
                    this.ticketTypes = [
                        { id: 'feature_request', label: '功能建议', icon: '💡' },
                        { id: 'bug_report', label: 'BUG反馈', icon: '📪' }
                    ];
                }
                
                // 不同类型的 placeholder 提示
                const placeholders = {
                    'feature_request': '请详细描述您的功能建议...',
                    'bug_report': '请详细描述您遇到的问题,建议包含以下信息:\n\n• 浏览器及版本(如 Chrome 120)\n• 操作系统(如 Windows 11)\n• 问题复现步骤\n• 预期行为与实际行为'
                };
                const defaultPlaceholder = placeholders[this.ticketTypes[0]?.id] || placeholders['feature_request'];
                
                body.innerHTML = `
                    <div class="ldsp-ticket-form">
                        <div class="ldsp-ticket-form-group">
                            <div class="ldsp-ticket-label">工单类型</div>
                            <div class="ldsp-ticket-types">
                                ${this.ticketTypes.map((t, i) => `
                                    <div class="ldsp-ticket-type ${i === 0 ? 'selected' : ''}" data-type="${t.id}">
                                        <span class="ldsp-ticket-type-icon">${t.icon}</span>
                                        <span class="ldsp-ticket-type-label">${t.label}</span>
                                    </div>
                                `).join('')}
                            </div>
                        </div>
                        <div class="ldsp-ticket-form-group">
                            <div class="ldsp-ticket-label">标题 <span style="color:var(--txt-mut);font-weight:400">(4-50字)</span></div>
                            <input type="text" class="ldsp-ticket-input" placeholder="简要描述您的问题或建议" minlength="4" maxlength="50">
                        </div>
                        <div class="ldsp-ticket-form-group">
                            <div class="ldsp-ticket-label">详细描述 <span style="color:var(--txt-mut);font-weight:400">(8-500字)</span></div>
                            <textarea class="ldsp-ticket-textarea" placeholder="${defaultPlaceholder}" minlength="8" maxlength="500"></textarea>
                        </div>
                        <button class="ldsp-ticket-submit">提交工单</button>
                    </div>`;

                const textarea = body.querySelector('.ldsp-ticket-textarea');
                body.querySelectorAll('.ldsp-ticket-type').forEach(type => {
                    type.addEventListener('click', () => {
                        body.querySelectorAll('.ldsp-ticket-type').forEach(t => t.classList.remove('selected'));
                        type.classList.add('selected');
                        // 根据类型更新 placeholder
                        const selectedType = type.dataset.type;
                        textarea.placeholder = placeholders[selectedType] || placeholders['feature_request'];
                    });
                });

                body.querySelector('.ldsp-ticket-submit').addEventListener('click', () => this._submitTicket());
            }

            async _submitTicket() {
                const body = this.overlay.querySelector('.ldsp-ticket-body');
                const type = body.querySelector('.ldsp-ticket-type.selected')?.dataset.type;
                const title = body.querySelector('.ldsp-ticket-input')?.value.trim();
                const content = body.querySelector('.ldsp-ticket-textarea')?.value.trim();
                const btn = body.querySelector('.ldsp-ticket-submit');

                if (!title || title.length < 4) { LDSPDialog.warning('标题至少需要4个字符'); return; }
                if (title.length > 50) { LDSPDialog.warning('标题最多50个字符'); return; }
                if (!content || content.length < 8) { LDSPDialog.warning('描述至少需要8个字符'); return; }
                if (content.length > 500) { LDSPDialog.warning('描述最多500个字符'); return; }

                btn.disabled = true;
                btn.textContent = '提交中...';

                try {
                    const result = await this.oauth.api('/api/tickets', {
                        method: 'POST',
                        body: JSON.stringify({ type: type || 'feature_request', title, content })
                    });
                    const data = result.data?.data || result.data;
                    if (result.success || data?.success) {
                        await this._loadTickets();
                        this.overlay.querySelectorAll('.ldsp-ticket-tab').forEach(t => t.classList.remove('active'));
                        this.overlay.querySelector('.ldsp-ticket-tab[data-tab="list"]')?.classList.add('active');
                        this._renderList();
                    } else {
                        // 检测登录失效情况
                        const errCode = result.error?.code;
                        if (errCode === 'NOT_LOGGED_IN' || errCode === 'AUTH_EXPIRED' || errCode === 'INVALID_TOKEN') {
                            LDSPDialog.error('登录已失效,请重新登录后再试');
                        } else {
                            LDSPDialog.error(result.error?.message || result.error || data?.error || '提交失败');
                        }
                    }
                } catch (e) {
                    LDSPDialog.error('提交失败: ' + (e.message || '网络错误'));
                } finally {
                    btn.disabled = false;
                    btn.textContent = '提交工单';
                }
            }

            async _showDetail(ticketId) {
                this.currentView = 'detail';
                const body = this.overlay.querySelector('.ldsp-ticket-body');
                body.classList.add('detail-mode');
                body.innerHTML = '<div style="display:flex;align-items:center;justify-content:center;flex:1"><div class="ldsp-loading"><div class="ldsp-spinner"></div><div>加载中...</div></div></div>';

                try {
                    const result = await this.oauth.api(`/api/tickets/${ticketId}`);
                    if (!result.success) {
                        const errCode = result.error?.code;
                        if (errCode === 'NOT_LOGGED_IN' || errCode === 'AUTH_EXPIRED' || errCode === 'INVALID_TOKEN') {
                            throw new Error('登录已失效,请重新登录');
                        }
                        throw new Error(result.error?.message || result.error || '加载失败');
                    }
                    
                    const data = result.data?.data || result.data;
                    const ticket = data?.ticket || data;
                    const replies = ticket?.replies || [];
                    this.currentTicket = ticket;

                    body.innerHTML = `
                        <div class="ldsp-ticket-detail">
                            <div class="ldsp-ticket-detail-top">
                                <div class="ldsp-ticket-back">← 返回</div>
                                <div class="ldsp-ticket-detail-header">
                                    <div class="ldsp-ticket-detail-title">${Utils.sanitize(ticket.title, 100)}</div>
                                    <div class="ldsp-ticket-detail-meta">
                                        <span>${this._getTypeIcon(ticket.type)} ${this._getTypeLabel(ticket.type)}</span>
                                        <span>#${ticket.id}</span>
                                        <span class="ldsp-ticket-item-status ${ticket.status}">${ticket.status === 'open' ? '处理中' : '已关闭'}</span>
                                    </div>
                                </div>
                            </div>
                            <div class="ldsp-ticket-messages">
                                <div class="ldsp-ticket-reply user">
                                    <div class="ldsp-ticket-reply-header">
                                        <span class="ldsp-ticket-reply-author">👤 我</span>
                                        <span>${this._formatTime(ticket.created_at)}</span>
                                    </div>
                                    <div class="ldsp-ticket-reply-content">${Utils.sanitize(ticket.content, 2000)}</div>
                                </div>
                                ${replies.map(r => `
                                    <div class="ldsp-ticket-reply ${r.is_admin ? 'admin' : 'user'}">
                                        <div class="ldsp-ticket-reply-header">
                                            <span class="ldsp-ticket-reply-author">${r.is_admin ? '👨‍💼 ' + (r.admin_name || '管理员') : '👤 我'}</span>
                                            <span>${this._formatTime(r.created_at)}</span>
                                        </div>
                                        <div class="ldsp-ticket-reply-content">${Utils.sanitize(r.content, 2000)}</div>
                                    </div>
                                `).join('')}
                            </div>
                            <div class="ldsp-ticket-input-area">
                                ${ticket.status === 'open' ? `
                                    <div class="ldsp-ticket-reply-form">
                                        <textarea class="ldsp-ticket-reply-input" placeholder="输入回复..." maxlength="500"></textarea>
                                        <button class="ldsp-ticket-reply-btn">发送</button>
                                    </div>
                                ` : '<div class="ldsp-ticket-closed-hint">此工单已关闭</div>'}
                            </div>
                        </div>`;

                    body.querySelector('.ldsp-ticket-back').addEventListener('click', () => {
                        this._loadTickets().then(() => this._renderList());
                    });

                    const replyBtn = body.querySelector('.ldsp-ticket-reply-btn');
                    if (replyBtn) {
                        replyBtn.addEventListener('click', () => this._sendReply(ticketId));
                    }
                    
                    requestAnimationFrame(() => {
                        const messagesEl = body.querySelector('.ldsp-ticket-messages');
                        if (messagesEl) messagesEl.scrollTop = messagesEl.scrollHeight;
                    });

                    if (ticket.has_new_reply) {
                        // 获取工单详情时已自动标记已读,只需更新本地状态
                        this._checkUnread();
                        const t = this.tickets.find(x => x.id == ticketId);
                        if (t) t.has_new_reply = false;
                        this._updateTabBadge();
                    }
                } catch (e) {
                    body.innerHTML = '<div class="ldsp-ticket-empty"><div class="ldsp-ticket-empty-icon">❌</div><div>加载失败</div></div>';
                }
            }

            async _sendReply(ticketId) {
                const body = this.overlay.querySelector('.ldsp-ticket-body');
                const input = body.querySelector('.ldsp-ticket-reply-input');
                const btn = body.querySelector('.ldsp-ticket-reply-btn');
                const text = input?.value.trim();

                if (!text) return;

                btn.disabled = true;
                try {
                    const result = await this.oauth.api(`/api/tickets/${ticketId}/reply`, {
                        method: 'POST',
                        body: JSON.stringify({ content: text })
                    });
                    if (result.success) {
                        this._showDetail(ticketId);
                    } else {
                        // 检测登录失效情况
                        const errCode = result.error?.code;
                        if (errCode === 'NOT_LOGGED_IN' || errCode === 'AUTH_EXPIRED' || errCode === 'INVALID_TOKEN') {
                            LDSPDialog.error('登录已失效,请重新登录后再试');
                        } else {
                            LDSPDialog.error(result.error?.message || result.error || '发送失败');
                        }
                    }
                } catch (e) {
                    LDSPDialog.error('网络错误');
                } finally {
                    btn.disabled = false;
                }
            }

            _getTypeIcon(type) {
                const t = this.ticketTypes.find(x => x.id === type);
                return t?.icon || '💡';
            }

            _getTypeLabel(type) {
                const t = this.ticketTypes.find(x => x.id === type);
                return t?.label || type;
            }

            _formatTime(ts) {
                if (!ts) return '';
                const d = new Date(ts);
                const now = new Date();
                const diff = (now - d) / 1000;
                if (diff < 60) return '刚刚';
                if (diff < 3600) return `${Math.floor(diff / 60)}分钟前`;
                if (diff < 86400) return `${Math.floor(diff / 3600)}小时前`;
                if (diff < 2592000) return `${Math.floor(diff / 86400)}天前`;
                return `${d.getMonth() + 1}/${d.getDate()}`;
            }

            // 销毁方法 - 清理定时器和事件监听
            destroy() {
                this._stopUnreadPoll();
                // 移除页面可见性监听
                if (this._visibilityHandler) {
                    document.removeEventListener('visibilitychange', this._visibilityHandler);
                    this._visibilityHandler = null;
                }
                if (this.overlay) {
                    this.overlay.remove();
                    this.overlay = null;
                }
            }
        }

        // ==================== 话题导出器 ====================
        class TopicExporter {
            constructor(panelBody, renderer) {
                this.panelBody = panelBody;
                this.renderer = renderer;
                this.overlay = null;
                this._cache = null;
                this._lastUrl = location.href;
                this._urlTimer = null;
                this._embedImg = true;
                this._format = 'html';
                this._abort = null;
            }

            init() { this._createOverlay(); }

            _createOverlay() {
                this.overlay = document.createElement('div');
                this.overlay.className = 'ldsp-export-overlay';
                this.overlay.innerHTML = `
                    <div class="ldsp-export-header">
                        <div class="ldsp-export-title">📥 导出帖子</div>
                        <div class="ldsp-export-header-actions">
                            <div class="ldsp-export-refresh" title="刷新信息"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 2v6h-6"/><path d="M3 12a9 9 0 0 1 15-6.7L21 8"/><path d="M3 22v-6h6"/><path d="M21 12a9 9 0 0 1-15 6.7L3 16"/></svg>刷新</div>
                            <div class="ldsp-export-close">×</div>
                        </div>
                    </div>
                    <div class="ldsp-export-body"></div>`;
                this.panelBody?.appendChild(this.overlay);
                this._bindEvents();
            }

            _bindEvents() {
                this.overlay.querySelector('.ldsp-export-close').addEventListener('click', () => this.hide());
                this.overlay.querySelector('.ldsp-export-refresh').addEventListener('click', () => this._refresh());
                document.addEventListener('keydown', e => {
                    if (e.key === 'Escape' && this.overlay.classList.contains('show')) this.hide();
                });
            }

            async _refresh() {
                const btn = this.overlay.querySelector('.ldsp-export-refresh');
                if (btn.classList.contains('spinning')) return;
                btn.classList.add('spinning');
                this._cache = null;
                await this._renderHome(true);
                setTimeout(() => btn.classList.remove('spinning'), 500);
            }

            _startUrlWatch() {
                if (this._urlTimer) return;
                this._urlTimer = setInterval(() => {
                    if (location.href !== this._lastUrl) {
                        this._lastUrl = location.href;
                        this._cache = null;
                        if (this.overlay.classList.contains('show') && this._getTopicId()) {
                            this._renderHome();
                            this._stopUrlWatch();
                        }
                    }
                }, 800);
            }

            _stopUrlWatch() { if (this._urlTimer) { clearInterval(this._urlTimer); this._urlTimer = null; } }

            show() {
                this._lastUrl = location.href;
                const tid = this._getTopicId();
                if (this._cache && this._cache.id !== tid) this._cache = null;
                this.overlay.classList.add('show');
                this._renderHome();
            }

            hide() { this._stopUrlWatch(); this.overlay.classList.remove('show'); }

            _getTopicId() {
                return location.href.match(/\/t(?:opic)?\/[^\/]+\/(\d+)/)?.[1] || location.href.match(/\/t(?:opic)?\/(\d+)/)?.[1];
            }

            async _getTopicInfo(refresh = false) {
                const tid = this._getTopicId();
                if (!tid) return null;
                if (!refresh && this._cache?.id === tid) return this._cache;
                
                try {
                    const csrf = document.querySelector('meta[name="csrf-token"]')?.content;
                    const res = await fetch(`${location.origin}/t/${tid}.json`, {
                        headers: { 'x-csrf-token': csrf, 'x-requested-with': 'XMLHttpRequest' }
                    });
                    if (!res.ok) throw new Error();
                    const d = await res.json();
                    
                    const catEl = document.querySelector('.badge-category__name, .category-name');
                    const catStyle = document.querySelector('.badge-category')?.getAttribute('style') || '';
                    const rawTags = d.tags?.length ? d.tags : Array.from(document.querySelectorAll('.discourse-tags .discourse-tag, .topic-header-extra .discourse-tag')).map(e => e.textContent.trim());
                    const tags = (rawTags || []).map(t => typeof t === 'string' ? t : (t?.name || '')).filter(Boolean);
                    
                    this._cache = {
                        id: tid, title: d.title || '未知标题',
                        category: catEl?.textContent?.trim() || '',
                        categoryColor: catStyle.match(/background-color:\s*#([0-9a-fA-F]+)/)?.[1] || '',
                        tags, postsCount: d.posts_count || 1, views: d.views || 0
                    };
                    return this._cache;
                } catch {
                    const catEl = document.querySelector('.badge-category__name, .category-name');
                    return {
                        id: tid, title: document.querySelector('.fancy-title, .topic-title')?.textContent?.trim() || '当前话题',
                        category: catEl?.textContent?.trim() || '', categoryColor: '',
                        tags: Array.from(document.querySelectorAll('.discourse-tags .discourse-tag')).map(e => e.textContent.trim()).filter(Boolean),
                        postsCount: 1, views: 0
                    };
                }
            }

            async _fetchPosts(tid, start, end, progress, signal) {
                const csrf = document.querySelector('meta[name="csrf-token"]')?.content;
                const opts = { headers: { 'x-csrf-token': csrf, 'x-requested-with': 'XMLHttpRequest' }, signal };

                progress?.('正在获取帖子列表...');
                const idRes = await fetch(`${location.origin}/t/${tid}/post_ids.json?post_number=0&limit=99999`, opts);
                if (!idRes.ok) throw new Error(`获取帖子列表失败 (${idRes.status})`);
                let pIds = (await idRes.json()).post_ids.slice(Math.max(0, start - 1), end);

                if (start <= 1 && pIds.length) {
                    const mainRes = await fetch(`${location.origin}/t/${tid}.json`, opts);
                    if (mainRes.ok) {
                        const firstId = (await mainRes.json()).post_stream?.posts?.[0]?.id;
                        if (firstId && !pIds.includes(firstId)) pIds.unshift(firstId);
                    }
                }
                if (!pIds.length) throw new Error('没有找到帖子内容');

                const posts = [], total = Math.ceil(pIds.length / 200);
                for (let i = 0; i < pIds.length; i += 200) {
                    if (signal?.aborted) throw new Error('导出已取消');
                    progress?.(`正在获取帖子内容 (${Math.floor(i/200)+1}/${total})...`);
                    const q = pIds.slice(i, i + 200).map(id => `post_ids[]=${id}`).join('&');
                    const res = await fetch(`${location.origin}/t/${tid}/posts.json?${q}&include_suggested=false`, opts);
                    if (!res.ok) throw new Error(`获取帖子详情失败 (${res.status})`);
                    
                    for (const p of (await res.json()).post_stream.posts) {
                        if (signal?.aborted) throw new Error('导出已取消');
                        let content = p.cooked || '', avatarUrl = p.avatar_template?.replace('{size}', '90') || '';
                        if (this._embedImg) {
                            content = await this._processImages(content);
                            if (avatarUrl) avatarUrl = await this._imgToBase64(avatarUrl.startsWith('http') ? avatarUrl : `${location.origin}${avatarUrl}`);
                        }
                        posts.push({
                            postNumber: p.post_number,
                            author: { username: p.username, name: p.name || p.username, avatarUrl },
                            timestamp: p.created_at, content,
                            replyTo: p.reply_to_post_number ? { postNumber: p.reply_to_post_number, username: p.reply_to_user?.username || '' } : null,
                            likeCount: p.actions_summary?.find(a => a.id === 2)?.count || 0
                        });
                    }
                }
                return posts;
            }

            async _imgToBase64(url) {
                try {
                    const blob = await (await fetch(url)).blob();
                    return new Promise((r, e) => { const rd = new FileReader(); rd.onloadend = () => r(rd.result); rd.onerror = e; rd.readAsDataURL(blob); });
                } catch { return url; }
            }

            async _processImages(html) {
                const div = document.createElement('div');
                div.innerHTML = html;
                for (const img of div.querySelectorAll('img')) {
                    const src = img.getAttribute('src');
                    if (src && !src.startsWith('data:')) {
                        try { img.setAttribute('src', await this._imgToBase64(src.startsWith('http') ? src : `${location.origin}${src}`)); } catch {}
                    }
                }
                return div.innerHTML;
            }

            async _renderHome(refresh = false) {
                const body = this.overlay.querySelector('.ldsp-export-body');
                const tid = this._getTopicId();

                if (!tid) {
                    this._startUrlWatch();
                    body.innerHTML = `<div class="ldsp-export-not-topic"><div class="ldsp-export-not-topic-icon">📄</div><div class="ldsp-export-not-topic-text">请先进入一个话题帖子<br>才能使用导出功能哦~</div></div>`;
                    return;
                }

                this._stopUrlWatch();
                body.innerHTML = `<div class="ldsp-export-status"><div class="ldsp-export-status-icon">⏳</div>正在获取话题信息...</div>`;

                const info = await this._getTopicInfo(refresh);
                if (!info) { body.innerHTML = `<div class="ldsp-export-status"><div class="ldsp-export-status-icon">❌</div>获取话题信息失败</div>`; return; }

                const total = info.postsCount || 1;
                const hint = total > 100 ? `共${total}楼,内容较多可能需要较长时间` : `共${total}楼`;
                const catHtml = info.category ? `<span class="ldsp-export-info-category" ${info.categoryColor ? `style="background:#${info.categoryColor}"` : ''}>📁 ${Utils.escapeHtml(info.category)}</span>` : '';
                const tagsHtml = (info.category || info.tags?.length) ? `<div class="ldsp-export-info-tags">${catHtml}${(info.tags||[]).map(t => `<span class="ldsp-export-info-tag">🏷️ ${Utils.escapeHtml(t)}</span>`).join('')}</div>` : '';

                body.innerHTML = `
                    <div class="ldsp-export-info">
                        <div class="ldsp-export-info-title">📋 ${Utils.escapeHtml(info.title)}</div>
                        ${tagsHtml}
                        <div class="ldsp-export-info-row"><span class="ldsp-export-info-label">话题ID</span><span class="ldsp-export-info-value">${info.id}</span></div>
                        <div class="ldsp-export-info-row"><span class="ldsp-export-info-label">总楼层</span><span class="ldsp-export-info-value">${total} 楼</span></div>
                        ${info.views ? `<div class="ldsp-export-info-row"><span class="ldsp-export-info-label">浏览量</span><span class="ldsp-export-info-value">${info.views.toLocaleString()}</span></div>` : ''}
                    </div>
                    <div class="ldsp-export-range">
                        <span class="ldsp-export-range-label">楼层范围</span>
                        <input type="number" class="ldsp-export-range-input" id="export-start" value="1" min="1" max="${total}">
                        <span class="ldsp-export-range-sep">~</span>
                        <input type="number" class="ldsp-export-range-input" id="export-end" value="${total}" min="1" max="${total}">
                        <span class="ldsp-export-range-hint">${hint}</span>
                    </div>
                    <div class="ldsp-export-format-selector">
                        <div class="ldsp-export-format-title">导出格式</div>
                        <div class="ldsp-export-format-cards">
                            <label class="ldsp-export-format-card ${this._format === 'html' ? 'active' : ''}" data-format="html"><input type="radio" name="export-format" value="html" ${this._format === 'html' ? 'checked' : ''}><div class="ldsp-export-format-card-icon"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="16 18 22 12 16 6"/><polyline points="8 6 2 12 8 18"/></svg></div><div class="ldsp-export-format-card-name">HTML</div></label>
                            <label class="ldsp-export-format-card ${this._format === 'pdf' ? 'active' : ''}" data-format="pdf"><input type="radio" name="export-format" value="pdf" ${this._format === 'pdf' ? 'checked' : ''}><div class="ldsp-export-format-card-icon"><svg viewBox="0 0 24 24" fill="none"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" fill="#ef4444" stroke="#dc2626" stroke-width="1"/><polyline points="14 2 14 8 20 8" fill="#fca5a5" stroke="#dc2626" stroke-width="1"/><text x="12" y="16" font-size="6" font-weight="bold" fill="#fff" text-anchor="middle" font-family="Arial">PDF</text></svg></div><div class="ldsp-export-format-card-name">PDF</div></label>
                            <label class="ldsp-export-format-card ${this._format === 'md' ? 'active' : ''}" data-format="md"><input type="radio" name="export-format" value="md" ${this._format === 'md' ? 'checked' : ''}><div class="ldsp-export-format-card-icon"><svg viewBox="0 0 24 24" fill="none"><path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z" fill="#334155" stroke="#1f2937" stroke-width="1"/><polyline points="14 2 14 8 20 8" fill="#64748b" stroke="#1f2937" stroke-width="1"/><text x="12" y="16" font-size="6" font-weight="bold" fill="#fff" text-anchor="middle" font-family="Arial">MD</text></svg></div><div class="ldsp-export-format-card-name">Markdown</div></label>
                        </div>
                    </div>
                    <div class="ldsp-export-options">
                        <div class="ldsp-export-options-title">导出选项</div>
                        <div class="ldsp-export-option"><input type="checkbox" id="export-embed-images" ${this._embedImg ? 'checked' : ''}><label for="export-embed-images">嵌入图片(文件更大但可离线查看)</label></div>
                    </div>
                    <div class="ldsp-export-actions"><button class="ldsp-export-btn-start" id="export-start-btn"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg><span>开始导出</span></button></div>
                    <div class="ldsp-export-status" id="export-status" style="display:none;"></div>`;

                body.querySelectorAll('.ldsp-export-format-card').forEach(c => c.addEventListener('click', () => {
                    body.querySelectorAll('.ldsp-export-format-card').forEach(x => x.classList.remove('active'));
                    c.classList.add('active'); this._format = c.dataset.format;
                }));
                body.querySelector('#export-embed-images').addEventListener('change', e => { this._embedImg = e.target.checked; });
                body.querySelector('#export-start-btn').addEventListener('click', () => this._doExport(info));
            }

            async _doExport(info) {
                const body = this.overlay.querySelector('.ldsp-export-body');
                const actions = body.querySelector('.ldsp-export-actions');
                const status = body.querySelector('#export-status');
                const start = parseInt(body.querySelector('#export-start').value) || 1;
                const end = parseInt(body.querySelector('#export-end').value) || info.postsCount;

                if (start > end) { this.renderer?.showToast('❌ 起始楼层不能大于结束楼层'); return; }

                this._abort = new AbortController();
                actions.innerHTML = `<button class="ldsp-export-btn-stop" id="export-stop-btn"><svg width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><rect x="6" y="6" width="12" height="12" rx="1"/></svg><span>停止导出</span></button>`;
                body.querySelector('#export-stop-btn').addEventListener('click', () => this._abort?.abort());
                status.style.display = 'block';

                const setStatus = msg => { status.innerHTML = `<div class="ldsp-export-status-icon">⏳</div>${msg}`; };

                try {
                    setStatus('正在获取帖子内容...');
                    const posts = await this._fetchPosts(info.id, start, end, setStatus, this._abort.signal);
                    if (this._abort.signal.aborted) throw new Error('导出已取消');
                    if (!posts.length) throw new Error('没有获取到帖子内容');

                    setStatus('正在生成文件...');
                    const data = { topic: info, posts, exportDate: new Date().toISOString(), postCount: posts.length, range: { start, end } };

                    if (this._format === 'md') {
                        const md = this._genMarkdown(data);
                        this._download(md, this._genFilename(info, 'md'), 'text/markdown');
                    } else {
                        const html = this._genHTML(data);
                        if (this._format === 'html') this._download(html, this._genFilename(info, 'html'), 'text/html');
                        else this._printPDF(html);
                    }

                    status.innerHTML = `<div class="ldsp-export-status-icon">✅</div>导出成功!`;
                    this.renderer?.showToast('✅ 导出成功!');
                } catch (e) {
                    const msg = e.name === 'AbortError' || e.message === '导出已取消' ? '导出已取消' : e.message;
                    status.innerHTML = `<div class="ldsp-export-status-icon">${msg === '导出已取消' ? '⏹️' : '❌'}</div>${Utils.escapeHtml(msg)}`;
                    if (msg !== '导出已取消') this.renderer?.showToast(`❌ ${msg}`);
                } finally {
                    this._abort = null;
                    actions.innerHTML = `<button class="ldsp-export-btn-start" id="export-start-btn"><svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/><polyline points="7 10 12 15 17 10"/><line x1="12" y1="15" x2="12" y2="3"/></svg><span>开始导出</span></button>`;
                    body.querySelector('#export-start-btn').addEventListener('click', () => this._doExport(info));
                }
            }

            _genFilename(info, ext) {
                const n = new Date(), d = `${n.getFullYear()}${String(n.getMonth()+1).padStart(2,'0')}${String(n.getDate()).padStart(2,'0')}`;
                const t = `${String(n.getHours()).padStart(2,'0')}${String(n.getMinutes()).padStart(2,'0')}`;
                return `LDStatusPro_${info.id}_${info.title.replace(/[<>:"/\\|?*]/g,'').substring(0,50)}_${d}_${t}.${ext}`;
            }

            _genHTML(data) {
                const { topic, posts, exportDate, postCount, range } = data;
                const n = new Date(exportDate);
                const ts = `${n.getFullYear()}-${String(n.getMonth()+1).padStart(2,'0')}-${String(n.getDate()).padStart(2,'0')} ${String(n.getHours()).padStart(2,'0')}:${String(n.getMinutes()).padStart(2,'0')}`;
                const sourceUrl = `${location.origin}/t/${topic.id}`;
                const catHex = (topic.categoryColor || '').replace('#', '');
                const catRgb = catHex ? Utils.hexToRgb(catHex) : null;
                const catText = catRgb ? ((0.299 * catRgb.r + 0.587 * catRgb.g + 0.114 * catRgb.b) > 160 ? '#111' : '#fff') : '#fff';
                const catStyle = catHex ? `style="background:#${catHex};color:${catText}"` : '';
                const postsHtml = posts.map(p => {
                    const postUrl = `${sourceUrl}/${p.postNumber}`;
                    const authorName = Utils.escapeHtml(p.author.name || p.author.username);
                    const authorUsername = Utils.escapeHtml(p.author.username);
                    const replyHtml = p.replyTo ? `<div class="reply-to">回复 <a href="${sourceUrl}/${p.replyTo.postNumber}" target="_blank">#${p.replyTo.postNumber}</a> @${Utils.escapeHtml(p.replyTo.username)}</div>` : '';
                    const likeHtml = p.likeCount > 0 ? `<div class="post-footer"><span class="like-count">❤️ ${p.likeCount}</span></div>` : '';
                    return `<div class="post" id="post-${p.postNumber}">
                        <div class="post-header">
                            <div class="post-author">
                                ${p.author.avatarUrl ? `<img src="${p.author.avatarUrl}" alt="${authorUsername}" class="avatar">` : '<div class="avatar-placeholder"></div>'}
                                <div class="author-info">
                                    <span class="author-name">${authorName}</span>
                                    <span class="author-username">@${authorUsername}</span>
                                </div>
                            </div>
                            <div class="post-meta">
                                <span class="post-time">${new Date(p.timestamp).toLocaleString('zh-CN')}</span>
                                <a class="post-number" href="${postUrl}" target="_blank">#${p.postNumber}</a>
                            </div>
                        </div>
                        ${replyHtml}
                        <div class="post-content">${p.content}</div>
                        ${likeHtml}
                    </div>`;
                }).join('');
                const css = `
:root{--accent:#5a7de0;--accent-2:#6b8cef;--bg:#f6f7fb;--card:#ffffff;--text:#1b1f2a;--muted:#6b7280;--border:#e6e9f2;}
*{box-sizing:border-box;margin:0;padding:0;}
body{font-family:-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,"PingFang SC","Hiragino Sans GB","Microsoft YaHei",sans-serif;line-height:1.7;color:var(--text);background:var(--bg);}
a{color:var(--accent);text-decoration:none;}
a:hover{text-decoration:underline;}
.container{max-width:920px;margin:0 auto;padding:24px;}
.header{background:linear-gradient(135deg,#6b8cef 0%,#5a7de0 100%);color:#fff;padding:24px;border-radius:14px;margin-bottom:20px;box-shadow:0 8px 24px rgba(91,125,224,0.25);}
.header h1{font-size:22px;font-weight:700;margin-bottom:10px;line-height:1.4;}
.meta{display:flex;flex-wrap:wrap;gap:10px;font-size:13px;opacity:0.95;}
.meta-item{display:flex;align-items:center;gap:4px;background:rgba(255,255,255,0.18);padding:4px 10px;border-radius:12px;}
.meta-item a{color:#fff;}
.badge{font-weight:600;}
.topic-tags{display:flex;flex-wrap:wrap;gap:8px;margin-top:12px;}
.topic-category{display:inline-flex;align-items:center;gap:4px;padding:4px 12px;border-radius:12px;font-size:12px;font-weight:600;}
.topic-tag{display:inline-flex;align-items:center;padding:4px 10px;background:rgba(255,255,255,0.16);border-radius:12px;font-size:11px;font-weight:500;}
.post{background:var(--card);border:1px solid var(--border);border-radius:12px;padding:16px;margin-bottom:12px;box-shadow:0 1px 4px rgba(15,23,42,0.06);}
.post-header{display:flex;align-items:center;justify-content:space-between;gap:12px;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid var(--border);}
.post-author{display:flex;align-items:center;gap:10px;min-width:0;}
.avatar{width:38px;height:38px;border-radius:50%;object-fit:cover;flex-shrink:0;}
.avatar-placeholder{width:38px;height:38px;border-radius:50%;background:linear-gradient(135deg,#e2e8f0,#cbd5f5);flex-shrink:0;}
.author-info{min-width:0;display:flex;align-items:center;gap:6px;flex-wrap:wrap;}
.author-name{font-weight:600;color:var(--text);}
.author-username{color:var(--muted);font-size:12px;}
.post-meta{display:flex;align-items:center;gap:8px;flex-shrink:0;flex-wrap:wrap;justify-content:flex-end;}
.post-time{color:var(--muted);font-size:12px;}
.post-number{background:#eef2ff;color:#3b5bd6;padding:4px 10px;border-radius:8px;font-size:12px;font-weight:600;}
.reply-to{background:#f8fafc;border-left:3px solid var(--accent);padding:8px 12px;margin-bottom:12px;border-radius:0 6px 6px 0;font-size:13px;color:#4b5563;}
.post-content{font-size:15px;line-height:1.8;word-wrap:break-word;}
.post-content p{margin:8px 0;}
.post-content img{max-width:100%;height:auto;border-radius:8px;margin:8px 0;}
.post-content blockquote,.post-content aside.quote{background:#f8fafc;border-left:3px solid var(--accent);padding:12px 16px;margin:12px 0;border-radius:0 6px 6px 0;color:#4b5563;}
.post-content pre{background:#0f172a;color:#e2e8f0;padding:14px;border-radius:8px;overflow-x:auto;font-size:13px;line-height:1.5;}
.post-content code{background:#f1f5f9;padding:2px 6px;border-radius:4px;font-size:13px;}
.post-content pre code{background:none;padding:0;}
.post-content ul,.post-content ol{padding-left:22px;margin:8px 0;}
.post-content li{margin:4px 0;}
.post-content table{width:100%;border-collapse:collapse;margin:10px 0;font-size:13px;}
.post-content th,.post-content td{border:1px solid var(--border);padding:6px 8px;text-align:left;}
.post-content hr{border:none;border-top:1px dashed var(--border);margin:12px 0;}
.post-footer{margin-top:12px;padding-top:12px;border-top:1px solid var(--border);font-size:13px;color:var(--muted);}
.like-count{color:#e74c3c;font-weight:600;}
.footer{text-align:center;padding:16px;color:var(--muted);font-size:12px;}
.footer a{color:var(--accent);}
@media print{
  @page{margin:12mm;}
  *{-webkit-print-color-adjust:exact;print-color-adjust:exact;}
  body{background:#fff;}
  .container{max-width:none;padding:0;}
  .header{box-shadow:none;border-radius:0;margin-bottom:12px;}
  .post{box-shadow:none;break-inside:auto;page-break-inside:auto;}
  .post-header,.reply-to,.post-footer,.topic-tags{break-inside:avoid;page-break-inside:avoid;}
  .post-number{background:#e5edff;color:#1f3fb8;}
}
@media(max-width:600px){
  .container{padding:14px;}
  .header{padding:16px;}
  .header h1{font-size:18px;}
  .meta{gap:8px;font-size:12px;}
  .post{padding:12px;}
  .post-header{gap:8px;flex-direction:column;align-items:flex-start;}
  .post-meta{justify-content:flex-start;}
  .avatar,.avatar-placeholder{width:32px;height:32px;}
  .post-content{font-size:14px;}
}`;
                const tagsHtml = (topic.category || topic.tags?.length) ? `<div class="topic-tags">${topic.category ? `<span class="topic-category" ${catStyle}>📁 ${Utils.escapeHtml(topic.category)}</span>` : ''}${(topic.tags||[]).map(t => `<span class="topic-tag">🏷️ ${Utils.escapeHtml(t)}</span>`).join('')}</div>` : '';
                return `<!DOCTYPE html><html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1.0"><meta name="color-scheme" content="light"><title>LDStatusPro 导出 - ${Utils.escapeHtml(topic.title)} - 话题#${topic.id} - ${ts}</title><style>${css}</style></head><body><div class="container"><div class="header"><h1>${Utils.escapeHtml(topic.title)}</h1><div class="meta"><span class="meta-item"><a href="https://github.com/caigg188/LDStatusPro" target="_blank" class="badge">LDStatusPro</a></span><span class="meta-item">📋 话题 #${topic.id}</span><span class="meta-item">📝 ${postCount} 楼 (${range.start}-${range.end})</span>${topic.views ? `<span class="meta-item">👁️ ${topic.views.toLocaleString()} 浏览</span>` : ''}<span class="meta-item">📅 ${ts}</span></div>${tagsHtml}</div>${postsHtml}<div class="footer">由 <a href="https://github.com/caigg188/LDStatusPro" target="_blank">LDStatusPro</a> 导出 | 来源: <a href="${sourceUrl}" target="_blank">${sourceUrl}</a></div></div></body></html>`;
            }

            _genMarkdown(data) {
                const { topic, posts, exportDate, postCount, range } = data;
                const n = new Date(exportDate);
                const ts = `${n.getFullYear()}-${String(n.getMonth() + 1).padStart(2, '0')}-${String(n.getDate()).padStart(2, '0')} ${String(n.getHours()).padStart(2, '0')}:${String(n.getMinutes()).padStart(2, '0')}`;
                const sourceUrl = `${location.origin}/t/${topic.id}`;
                const clean = v => String(v ?? '').replace(/\r?\n+/g, ' ').trim();
                const lines = [];

                lines.push(`# ${clean(topic.title) || '未命名话题'}`);
                lines.push('');
                lines.push(`> 话题ID: ${topic.id}`);
                lines.push(`> 导出时间: ${ts}`);
                lines.push(`> 楼层范围: ${range.start}-${range.end}(共 ${postCount} 楼)`);
                if (topic.views) lines.push(`> 浏览量: ${topic.views.toLocaleString()}`);
                if (topic.category) lines.push(`> 分类: ${clean(topic.category)}`);
                if (topic.tags?.length) {
                    const tagList = topic.tags.map(t => typeof t === 'string' ? t : (t?.name || '')).filter(Boolean);
                    if (tagList.length) lines.push(`> 标签: ${tagList.map(t => `\`${clean(t)}\``).join(' ')}`);
                }
                lines.push(`> 原文链接: ${sourceUrl}`);
                lines.push('');
                lines.push('---');
                lines.push('');

                posts.forEach((p, idx) => {
                    const authorUser = clean(p.author.username);
                    const authorName = clean(p.author.name || p.author.username);
                    const authorLine = authorName && authorUser && authorName !== authorUser
                        ? `${authorName} (@${authorUser})`
                        : (authorUser ? `@${authorUser}` : authorName || '未知作者');
                    const postUrl = `${sourceUrl}#post_${p.postNumber}`;

                    lines.push(`## #${p.postNumber} ${authorLine}`);
                    lines.push('');
                    const meta = [
                        `> 时间: ${new Date(p.timestamp).toLocaleString('zh-CN')}`,
                        p.replyTo?.postNumber ? `> 回复: #${p.replyTo.postNumber}${p.replyTo.username ? ` @${clean(p.replyTo.username)}` : ''}` : '',
                        p.likeCount > 0 ? `> 点赞: ${p.likeCount}` : '',
                        `> 链接: ${postUrl}`
                    ].filter(Boolean);
                    lines.push(meta.join('\n'));
                    lines.push('');

                    const contentMd = this._htmlToMarkdown(p.content);
                    lines.push(contentMd ? contentMd : '_(无内容)_');

                    if (idx !== posts.length - 1) {
                        lines.push('');
                        lines.push('---');
                        lines.push('');
                    }
                });

                return lines.join('\n').replace(/\n{3,}/g, '\n\n').trim() + '\n';
            }

            _htmlToMarkdown(html) {
                if (!html) return '';
                const wrapper = document.createElement('div');
                wrapper.innerHTML = html;
                let md = this._nodeToMarkdownChildren(wrapper);
                md = md.replace(/\u00a0/g, ' ');
                md = md.replace(/[ \t]+\n/g, '\n').replace(/\n{3,}/g, '\n\n').trim();
                return md;
            }

            _nodeToMarkdown(node) {
                if (!node) return '';
                if (node.nodeType === 3) return node.nodeValue || '';
                if (node.nodeType !== 1) return '';

                const tag = node.tagName.toLowerCase();
                if (tag === 'script' || tag === 'style') return '';

                switch (tag) {
                    case 'br':
                        return '\n';
                    case 'p':
                        return `${this._nodeToMarkdownChildren(node).trim()}\n\n`;
                    case 'strong':
                    case 'b':
                        return `**${this._nodeToMarkdownChildren(node)}**`;
                    case 'em':
                    case 'i':
                        return `*${this._nodeToMarkdownChildren(node)}*`;
                    case 'code': {
                        if (node.parentElement && node.parentElement.tagName.toLowerCase() === 'pre') {
                            return node.textContent || '';
                        }
                        const text = (node.textContent || '').replace(/`/g, '\\`');
                        return `\`${text}\``;
                    }
                    case 'pre': {
                        const codeEl = node.querySelector('code');
                        const raw = (codeEl ? codeEl.textContent : node.textContent) || '';
                        const code = raw.replace(/\n$/, '');
                        const className = codeEl?.className || node.className || '';
                        const m = className.match(/language-([\w-]+)/i) || className.match(/lang(?:uage)?-([\w-]+)/i);
                        const lang = m ? m[1] : '';
                        const fence = code.includes('```') ? '````' : '```';
                        return `\n\n${fence}${lang}\n${code}\n${fence}\n\n`;
                    }
                    case 'a': {
                        const href = node.getAttribute('href') || '';
                        const text = this._inlineMarkdown(node) || href;
                        return href ? `[${text}](${href})` : text;
                    }
                    case 'img': {
                        const src = node.getAttribute('src') || '';
                        const alt = (node.getAttribute('alt') || '').replace(/\n/g, ' ').trim();
                        return src ? `![${alt}](${src})` : '';
                    }
                    case 'ul':
                        return this._listToMarkdown(node, false);
                    case 'ol':
                        return this._listToMarkdown(node, true);
                    case 'blockquote':
                    case 'aside': {
                        if (tag === 'aside') {
                            if (!node.classList.contains('quote')) return this._nodeToMarkdownChildren(node);
                            const innerBlock = node.querySelector('blockquote');
                            if (innerBlock) return this._nodeToMarkdown(innerBlock);
                        }
                        const content = this._nodeToMarkdownChildren(node).trim();
                        if (!content) return '';
                        return content.split('\n').map(line => (line ? `> ${line}` : '>')).join('\n') + '\n\n';
                    }
                    case 'h1':
                    case 'h2':
                    case 'h3':
                    case 'h4':
                    case 'h5':
                    case 'h6': {
                        const level = parseInt(tag.slice(1), 10);
                        const text = this._nodeToMarkdownChildren(node).trim();
                        return text ? `${'#'.repeat(level)} ${text}\n\n` : '';
                    }
                    case 'hr':
                        return '\n\n---\n\n';
                    case 'table':
                        return `${this._tableToMarkdown(node)}\n\n`;
                    default:
                        return this._nodeToMarkdownChildren(node);
                }
            }

            _nodeToMarkdownChildren(node) {
                return Array.from(node.childNodes).map(n => this._nodeToMarkdown(n)).join('');
            }

            _inlineMarkdown(node) {
                return this._nodeToMarkdownChildren(node).replace(/\n+/g, ' ').trim();
            }

            _listToMarkdown(listEl, ordered) {
                const items = Array.from(listEl.children).filter(el => el.tagName && el.tagName.toLowerCase() === 'li');
                if (!items.length) return '';
                const lines = items.map((li, idx) => {
                    const prefix = ordered ? `${idx + 1}. ` : '- ';
                    const content = this._nodeToMarkdownChildren(li).trim();
                    if (!content) return '';
                    const parts = content.split('\n');
                    const first = parts.shift() || '';
                    const rest = parts.map(line => (line ? `  ${line}` : '')).join('\n');
                    return prefix + first + (rest ? `\n${rest}` : '');
                }).filter(Boolean);
                return lines.join('\n') + '\n\n';
            }

            _tableToMarkdown(tableEl) {
                const rows = Array.from(tableEl.querySelectorAll('tr'));
                if (!rows.length) return '';
                const getCells = (row) => Array.from(row.children).filter(el => el.tagName && /^(th|td)$/i.test(el.tagName));
                const formatCell = (cell) => this._escapeTableCell(this._inlineMarkdown(cell));
                const headerCells = getCells(rows[0]);
                const hasTh = headerCells.some(cell => cell.tagName.toLowerCase() === 'th');
                const header = headerCells.map(formatCell);
                const headerLine = `| ${header.join(' | ')} |`;
                const sepLine = `| ${header.map(() => '---').join(' | ')} |`;
                const bodyRows = hasTh ? rows.slice(1) : rows;
                const bodyLines = bodyRows.map(row => {
                    const cells = getCells(row).map(formatCell);
                    const padded = header.length
                        ? [...cells, ...Array(Math.max(0, header.length - cells.length)).fill('')].slice(0, header.length)
                        : cells;
                    return `| ${padded.join(' | ')} |`;
                });
                return [headerLine, sepLine, ...bodyLines].join('\n');
            }

            _escapeTableCell(text) {
                return String(text || '').replace(/\|/g, '\\|').replace(/\n/g, ' ').trim();
            }

            _download(content, filename, type) {
                const url = URL.createObjectURL(new Blob([content], { type }));
                const a = document.createElement('a');
                a.href = url; a.download = filename;
                document.body.appendChild(a); a.click(); document.body.removeChild(a);
                URL.revokeObjectURL(url);
            }

            _printPDF(html) {
                const w = window.open('', '_blank');
                if (!w) throw new Error('无法打开打印窗口,请检查弹窗拦截设置');
                w.document.open();
                w.document.write(html);
                w.document.close();
                const doPrint = () => {
                    try { w.focus(); } catch {}
                    setTimeout(() => { try { w.print(); } catch {} }, 200);
                };
                if (w.document.fonts && w.document.fonts.ready) {
                    w.document.fonts.ready.finally(doPrint);
                } else {
                    w.onload = doPrint;
                }
            }

            destroy() { this._stopUrlWatch(); this.overlay?.remove(); this.overlay = null; }
        }

        // ==================== 全局弹出框管理器 ====================
        const LDSPDialog = {
            _container: null,
            _panel: null,
            // 初始化:设置面板根元素
            init(panel) {
                this._panel = panel;
            },
            _getContainer() {
                const panel = this._getPanel();
                if (!panel) return null;
                // 确保容器存在且在面板中
                if (!this._container || !this._container.parentElement) {
                    if (this._container) this._container.remove();
                    this._container = document.createElement('div');
                    this._container.className = 'ldsp-toast-container';
                    panel.appendChild(this._container);
                }
                // 每次都更新位置,使用 fixed 定位
                const rect = panel.getBoundingClientRect();
                Object.assign(this._container.style, {
                    position: 'fixed',
                    top: (rect.top + 50) + 'px',
                    left: rect.left + 'px',
                    width: rect.width + 'px',
                    zIndex: '100000',
                    display: 'flex',
                    flexDirection: 'column',
                    alignItems: 'center',
                    gap: '8px',
                    pointerEvents: 'none',
                    padding: '0 12px',
                    boxSizing: 'border-box',
                    transition: 'none',
                    animation: 'none'
                });
                return this._container;
            },
            _getPanel() {
                // 只返回有效的面板元素,不回退到 body
                if (this._panel && this._panel.parentElement) return this._panel;
                const panel = document.getElementById('ldsp-panel');
                if (panel) {
                    this._panel = panel;
                    return panel;
                }
                return null; // 面板不存在返回 null
            },
            // Toast 通知
            toast(message, type = 'info', duration = 3000) {
                const icons = { success: '✅', error: '❌', warning: '⚠️', info: 'ℹ️' };
                const titles = { success: '成功', error: '错误', warning: '警告', info: '提示' };
                const colors = { success: 'var(--ok)', error: 'var(--err)', warning: 'var(--warn)', info: 'var(--accent)' };
                const toast = document.createElement('div');
                toast.className = `ldsp-toast ${type}`;
                // 内联样式确保显示正确,强制禁用 transition/animation
                Object.assign(toast.style, {
                    display: 'flex',
                    alignItems: 'flex-start',
                    gap: '10px',
                    padding: '10px 14px',
                    background: 'var(--bg-card)',
                    border: '1px solid var(--border)',
                    borderLeft: `3px solid ${colors[type] || colors.info}`,
                    borderRadius: 'var(--r-md)',
                    boxShadow: '0 4px 16px rgba(0,0,0,.2)',
                    pointerEvents: 'auto',
                    wordBreak: 'break-word',
                    maxWidth: '280px',
                    boxSizing: 'border-box',
                    opacity: '1',
                    transition: 'none',
                    animation: 'none',
                    transform: 'none'
                });
                toast.innerHTML = `
                    <div class="ldsp-toast-icon" style="font-size:16px;flex-shrink:0;line-height:1;color:${colors[type] || colors.info}">${icons[type] || icons.info}</div>
                    <div class="ldsp-toast-content" style="flex:1;min-width:0">
                        <div class="ldsp-toast-title" style="font-size:11px;font-weight:600;color:var(--txt);margin-bottom:2px">${titles[type] || titles.info}</div>
                        <div class="ldsp-toast-message" style="font-size:10px;color:var(--txt-sec);line-height:1.5">${message}</div>
                    </div>
                    <button class="ldsp-toast-close" style="flex-shrink:0;width:18px;height:18px;display:flex;align-items:center;justify-content:center;background:transparent;border:none;color:var(--txt-mut);cursor:pointer;border-radius:4px;font-size:14px;margin:-2px -4px -2px 0">×</button>`;
                const close = () => {
                    toast.style.opacity = '0';
                    toast.style.transform = 'translateY(-10px) scale(.95)';
                    setTimeout(() => toast.remove(), 250);
                };
                toast.querySelector('.ldsp-toast-close').onclick = close;
                if (duration > 0) setTimeout(close, duration);
                const container = this._getContainer();
                if (container) {
                    container.appendChild(toast);
                }
                return toast;
            },
            // 成功提示 - 使用系统原生 alert
            success(message) { alert('✅ 成功\n\n' + message.replace(/<br\s*\/?>/gi, '\n').replace(/<[^>]+>/g, '')); },
            // 错误提示 - 使用系统原生 alert
            error(message) { alert('❌ 错误\n\n' + message.replace(/<br\s*\/?>/gi, '\n').replace(/<[^>]+>/g, '')); },
            // 警告提示 - 使用系统原生 alert
            warning(message) { alert('⚠️ 警告\n\n' + message.replace(/<br\s*\/?>/gi, '\n').replace(/<[^>]+>/g, '')); },
            // 信息提示 - 使用系统原生 alert
            info(message) { alert('ℹ️ 提示\n\n' + message.replace(/<br\s*\/?>/gi, '\n').replace(/<[^>]+>/g, '')); },
            // 确认对话框 (返回 Promise)
            confirm(message, options = {}) {
                return new Promise((resolve) => {
                    const { title = '确认操作', icon = '⚠️', okText = '确认', cancelText = '取消', danger = false } = options;
                    const overlay = document.createElement('div');
                    overlay.className = 'ldsp-dialog-overlay';
                    overlay.innerHTML = `
                        <div class="ldsp-dialog">
                            <div class="ldsp-dialog-icon">${icon}</div>
                            <div class="ldsp-dialog-title">${title}</div>
                            <div class="ldsp-dialog-message">${message}</div>
                            <div class="ldsp-dialog-actions">
                                <button class="ldsp-dialog-btn ldsp-dialog-btn-cancel">${cancelText}</button>
                                <button class="ldsp-dialog-btn ${danger ? 'ldsp-dialog-btn-danger' : 'ldsp-dialog-btn-ok'}">${okText}</button>
                            </div>
                        </div>`;
                    let closed = false;
                    const close = (result) => {
                        if (closed) return;
                        closed = true;
                        overlay.style.opacity = '0';
                        setTimeout(() => { overlay.remove(); resolve(result); }, 150);
                    };
                    overlay.querySelector('.ldsp-dialog-btn-cancel').addEventListener('click', (e) => { e.stopPropagation(); close(false); });
                    overlay.querySelector('.ldsp-dialog-btn:last-child').addEventListener('click', (e) => { e.stopPropagation(); close(true); });
                    overlay.querySelector('.ldsp-dialog').addEventListener('click', (e) => e.stopPropagation());
                    overlay.addEventListener('click', () => close(false));
                    this._getPanel().appendChild(overlay);
                });
            },
            // 警告框 (单按钮)
            alert(message, options = {}) {
                return new Promise((resolve) => {
                    const { title = '提示', icon = 'ℹ️', okText = '知道了' } = options;
                    const overlay = document.createElement('div');
                    overlay.className = 'ldsp-dialog-overlay';
                    overlay.innerHTML = `
                        <div class="ldsp-dialog">
                            <div class="ldsp-dialog-icon">${icon}</div>
                            <div class="ldsp-dialog-title">${title}</div>
                            <div class="ldsp-dialog-message">${message}</div>
                            <div class="ldsp-dialog-actions">
                                <button class="ldsp-dialog-btn ldsp-dialog-btn-ok">${okText}</button>
                            </div>
                        </div>`;
                    let closed = false;
                    const close = () => {
                        if (closed) return;
                        closed = true;
                        overlay.style.opacity = '0';
                        setTimeout(() => { overlay.remove(); resolve(); }, 150);
                    };
                    overlay.querySelector('.ldsp-dialog-btn-ok').addEventListener('click', (e) => { e.stopPropagation(); close(); });
                    overlay.querySelector('.ldsp-dialog').addEventListener('click', (e) => e.stopPropagation());
                    overlay.addEventListener('click', () => close());
                    this._getPanel().appendChild(overlay);
                });
            }
        };

        // ==================== LDC 积分管理器 ====================
        class LDCManager {
            static CACHE_KEY = 'ldsp_ldc_cache';
            static CACHE_TTL = 600000; // 10分钟
            static LDC_ORIGIN = 'https://credit.linux.do';
            static TRANS_TYPES = [
                { id: '', label: '全部', icon: '📋' }, { id: 'receive', label: '收益', icon: '📥' },
                { id: 'payment', label: '支出', icon: '📤' }, { id: 'transfer', label: '转移', icon: '🔄' },
                { id: 'community', label: '社区划转', icon: '🏛️' }, { id: 'online', label: '在线流转', icon: '🌐' }
            ];
            static TIME_RANGES = [
                { id: 'today', label: '今日' }, { id: '7days', label: '近7天' },
                { id: '30days', label: '近30天' }, { id: 'all', label: '所有' }
            ];

            constructor(panelBody) {
                this.panelBody = panelBody;
                this.overlay = null;
                // 独立的加载状态与请求令牌,避免跨 tab 互相阻塞
                this._loadingState = { overview: false, trans: false };
                this._reqToken = { overview: 0, trans: 0 };
                this._tab = 'overview';
                this._trans = { orders: [], page: 1, total: 0, hasMore: false };
                this._order = null;
                this._userId = null;
                this._filter = { timeRange: '7days', type: '' };
                this._isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && /WebKit/.test(navigator.userAgent) && !/CriOS|FxiOS|OPiOS|EdgiOS/.test(navigator.userAgent);
                // iframe 桥接相关
                this._bridge = null;
                this._bridgeReady = null;
                this._requests = new Map();
                this._reqId = 0;
                this._msgHandler = null;
                // v2.0: API 配置
                this._apiUrl = (typeof ApiService !== 'undefined' && ApiService.baseUrl) || 'https://api.ldspro.qzz.io';
                this._tokenKey = `ldsp_${CURRENT_SITE.prefix}_leaderboard_token`;
                this._token = GM_getValue(this._tokenKey, null);
                // 小卖部相关
                this._shopView = 'list'; // list, my, form, detail
                
                // 统一错误消息格式化
                this._formatError = (resp) => {
                    if (!resp) return '请求失败,请稍后重试';
                    // 优先使用 error.message
                    if (resp.error?.message) return resp.error.message;
                    // 其次使用字符串类型的 error
                    if (typeof resp.error === 'string') return resp.error;
                    // 尝试 data.error
                    if (resp.data?.error?.message) return resp.data.error.message;
                    if (typeof resp.data?.error === 'string') return resp.data.error;
                    // 尝试 message
                    if (resp.message) return resp.message;
                    return '操作失败,请稍后重试';
                };
                this._shopProducts = [];
                this._shopMyProducts = [];
                this._shopCategories = [];
                this._shopCategory = '';
                this._shopProduct = null;
                this._shopEditProduct = null;
                // 小卖部缓存
                this._shopProductsCache = new Map(); // categoryId -> { products, timestamp }
                this._shopMyProductsCache = null; // { products, timestamp } 我的商品缓存
                this._shopCategoriesCache = null; // { categories, timestamp }
                this._shopCacheTTL = 60000; // 商品列表缓存 1 分钟
                this._shopCategoriesCacheTTL = 300000; // 分类缓存 5 分钟
                this._shopSubmitting = false; // 防重复提交锁
                this._shopTabClickDebounce = null; // Tab 点击防抖
                // 商品列表分页相关
                this._shopPage = 1;
                this._shopPageSize = 20;
                this._shopHasMore = true;
                this._shopLoading = false;
                this._shopScrollObserver = null;
                this._shopTotal = 0; // 当前分类的商品总数
                // 用户信息缓存,避免重复请求
                this._userCache = null;
                this._userCacheTime = 0;
                this._userCacheTTL = 60000; // 60s
                this._userPromise = null;
            }

            init() { 
                this._createOverlay();
                this._initBridge();
            }

            _initBridge() {
                // 创建消息监听
                const handler = (e) => {
                    if (e.origin !== LDCManager.LDC_ORIGIN || e.data?.type !== 'ldsp-ldc-response') return;
                    const p = this._requests.get(e.data.requestId);
                    if (p) { this._requests.delete(e.data.requestId); p(e.data); }
                };
                window.addEventListener('message', handler);
                this._msgHandler = handler;
                
                // 创建隐藏的 iframe
                const frame = document.createElement('iframe');
                frame.src = LDCManager.LDC_ORIGIN + '/home';
                frame.style.cssText = 'width:0;height:0;opacity:0;position:absolute;border:0;pointer-events:none';
                document.body.appendChild(frame);
                this._bridge = frame;
                
                // 等待 iframe 加载完成
                this._bridgeReady = new Promise(r => {
                    const t = setTimeout(() => r(), 2000);
                    frame.onload = () => { clearTimeout(t); setTimeout(r, 150); };
                });
            }

            _createOverlay() {
                this.overlay = document.createElement('div');
                this.overlay.className = 'ldsp-ldc-overlay';
                this.overlay.innerHTML = `
                    <div class="ldsp-ldc-header">
                        <div class="ldsp-ldc-title">🍟 LDC 积分</div>
                        <div class="ldsp-ldc-header-actions">
                            <a href="https://credit.linux.do/home" target="_blank" class="ldsp-ldc-link">LINUX DO CREDIT</a>
                            <button class="ldsp-ldc-refresh" title="刷新">
                                <svg viewBox="0 0 24 24"><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
                            </button>
                            <div class="ldsp-ldc-close">×</div>
                        </div>
                    </div>
                    <div class="ldsp-ldc-tabs">
                        <div class="ldsp-ldc-tab active" data-tab="overview">📊 概览</div>
                        <div class="ldsp-ldc-tab" data-tab="transactions">📜 记录</div>
                        <div class="ldsp-ldc-tab" data-tab="support">❤️ 支持</div>
                    </div>
                    <div class="ldsp-ldc-body">
                        <div class="ldsp-ldc-loading"><div class="ldsp-spinner"></div><div>加载中...</div></div>
                    </div>`;
                this.panelBody?.appendChild(this.overlay);
                this._bindEvents();
            }

            _bindEvents() {
                this.overlay.querySelector('.ldsp-ldc-close').addEventListener('click', () => this.hide());
                this.overlay.querySelector('.ldsp-ldc-refresh').addEventListener('click', () => {
                    if (this._tab === 'overview') this._fetchData();
                    else if (this._tab === 'transactions') this._fetchTrans(true);
                });
                this.overlay.querySelectorAll('.ldsp-ldc-tab').forEach(tab => {
                    tab.addEventListener('click', () => {
                        if (tab.dataset.tab !== this._tab) this._switchTab(tab.dataset.tab);
                    });
                });
                this._escHandler = (e) => {
                    if (e.key === 'Escape' && this.overlay.classList.contains('show')) {
                        this._order ? this._closeDetail() : this.hide();
                    }
                };
                document.addEventListener('keydown', this._escHandler);
            }

            _switchTab(tabId) {
                this._tab = tabId;
                this._order = null;
                this._shopProduct = null;
                this.overlay.querySelectorAll('.ldsp-ldc-tab').forEach(t => t.classList.toggle('active', t.dataset.tab === tabId));
                if (tabId === 'overview') this._loadCache() || this._fetchData();
                else if (tabId === 'transactions') this._fetchTrans(true);
                else if (tabId === 'support') this._renderSupport();
            }

            show() {
                this.overlay.classList.add('show');
                this._tab = 'overview';
                this._order = null;
                this._shopProduct = null;
                this.overlay.querySelectorAll('.ldsp-ldc-tab').forEach(t => t.classList.toggle('active', t.dataset.tab === 'overview'));
                this._loadCache() || this._fetchData();
            }
            
            // 显示独立的士多面板
            showShop() {
                if (!this.shopOverlay) {
                    this._createShopOverlay();
                }
                this.shopOverlay.classList.add('show');
                this._shopView = 'list';
                this._shopProduct = null;
                // 重置分页
                this._shopPage = 1;
                this._shopHasMore = true;
                this._shopLoading = false;
                this._renderShop();
            }
            
            hideShop() {
                this.shopOverlay?.classList.remove('show');
                this._shopProduct = null;
            }
            
            // 创建独立的士多 Overlay
            _createShopOverlay() {
                this.shopOverlay = document.createElement('div');
                this.shopOverlay.className = 'ldsp-ldc-overlay ldsp-shop-overlay';
                this.shopOverlay.innerHTML = `
                    <div class="ldsp-ldc-header">
                        <div class="ldsp-ldc-title">🍔 LD士多</div>
                        <div class="ldsp-ldc-header-actions">
                            <a href="https://ldst0re.qzz.io/" target="_blank" class="ldsp-ldc-link" title="打开网页版">🌐 网页版</a>
                            <button class="ldsp-ldc-refresh" title="刷新">
                                <svg viewBox="0 0 24 24"><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
                            </button>
                            <div class="ldsp-ldc-close">×</div>
                        </div>
                    </div>
                    <div class="ldsp-ldc-body">
                        <div class="ldsp-ldc-loading"><div class="ldsp-spinner"></div><div>加载中...</div></div>
                    </div>`;
                this.panelBody?.appendChild(this.shopOverlay);
                this._bindShopOverlayEvents();
            }
            
            _bindShopOverlayEvents() {
                this.shopOverlay.querySelector('.ldsp-ldc-close').addEventListener('click', () => this.hideShop());
                this.shopOverlay.querySelector('.ldsp-ldc-refresh').addEventListener('click', () => {
                    if (this._shopView === 'list') this._refreshShopList();
                    else if (this._shopView === 'my') this._refreshShopMy();
                    else if (this._shopView === 'orders') this._renderShopOrders();
                });
                // ESC 关闭
                this._shopEscHandler = (e) => {
                    if (e.key === 'Escape' && this.shopOverlay?.classList.contains('show')) {
                        if (this._shopProduct) {
                            this._shopProduct = null;
                            this._renderShopList();
                        } else {
                            this.hideShop();
                        }
                    }
                };
                document.addEventListener('keydown', this._shopEscHandler);
            }

            hide() { this.overlay.classList.remove('show'); this._order = null; }

            _loadCache() {
                try {
                    const c = GM_getValue(LDCManager.CACHE_KEY, null);
                    // 检查缓存有效性:时间未过期且包含 estimatedIncrease 字段(v3.5.2.4+ 新增)
                    if (c && Date.now() - c.time < LDCManager.CACHE_TTL && 'estimatedIncrease' in c) {
                        // 从缓存恢复 userId
                        if (c.userId) this._userId = c.userId;
                        this._renderOverview(c);
                        return true;
                    }
                } catch {}
                return false;
            }

            _saveCache(data) {
                // 缓存中保存 userId
                try { GM_setValue(LDCManager.CACHE_KEY, { ...data, userId: this._userId, time: Date.now() }); } catch {}
            }

            // 带缓存的用户信息获取,减少重复请求
            async _getUserInfo(force = false) {
                const now = Date.now();
                if (!force && this._userCache && (now - this._userCacheTime) < this._userCacheTTL) {
                    return this._userCache;
                }
                if (this._userPromise) return this._userPromise;
                this._userPromise = (async () => {
                    const user = await this._request('https://credit.linux.do/api/v1/oauth/user-info');
                    this._userCache = user || null;
                    this._userCacheTime = Date.now();
                    this._userPromise = null;
                    return user;
                })();
                return this._userPromise;
            }

            async _fetchData() {
                const token = ++this._reqToken.overview;
                this._loadingState.overview = true;
                const _body = this.overlay.querySelector('.ldsp-ldc-body');
                const btn = this.overlay.querySelector('.ldsp-ldc-refresh');
                _body.innerHTML = `<div class="ldsp-ldc-loading"><div class="ldsp-spinner"></div><div>加载中...</div></div>`;
                try {
                    const user = await this._getUserInfo(true);
                    if (!user || user._authError) {
                        this._showLoginGuide('auth');
                        return;
                    }
                    if (user._timeoutError) { this._showLoginGuide('timeout'); return; }
                    if (user._networkError) { this._showLoginGuide('network'); return; }
                    if (user._bridgeError) { this._showLoginGuide('timeout'); return; }

                    this._userId = user.id || user.user_id || null;
                    // nickname 是显示名,username 才是 linux.do 的用户名
                    const displayName = user.nickname || user.username || 'User';
                    const ldUsername = user.username; // linux.do 用户名
                    const communityBalance = parseFloat(user.community_balance ?? user['community-balance'] ?? 0);
                    
                    const data = {
                        username: displayName,
                        credits: user.available_balance || '0',
                        dailyLimit: user.remain_quota || '0',
                        incomeTotal: user.total_receive || '0',
                        expenseTotal: user.total_payment || '0',
                        dailyStats: [],
                        estimatedIncrease: null,
                        updateTime: new Date().toLocaleTimeString('zh-CN', { hour: '2-digit', minute: '2-digit' })
                    };

                    // 并行获取 gamification_score 与 7日统计,加速加载
                    const statsPromise = this._request('https://credit.linux.do/api/v1/dashboard/stats/daily?days=7');
                    let gamificationScore = null;
                    let gamificationPromise = Promise.resolve(null);
                    if (ldUsername) {
                        if (window.location.hostname === 'linux.do') {
                            gamificationPromise = (async () => {
                                try {
                                    const headers = buildAuthHeaders(`/u/${ldUsername}.json`, { 'Accept': 'application/json' });
                                    const resp = await fetch(`/u/${ldUsername}.json`, { credentials: 'include', headers });
                                    if (resp.ok) {
                                        const ldData = await resp.json();
                                        return ldData?.user?.gamification_score;
                                    }
                                } catch {}
                                return null;
                            })();
                        } else {
                            gamificationPromise = (async () => {
                                const ldUserData = await this._fetchGamificationScore(ldUsername);
                                return ldUserData?.gamification_score;
                            })();
                        }
                    }

                    const [stats, gScore] = await Promise.all([statsPromise, gamificationPromise]);
                    if (gScore !== null && gScore !== undefined) gamificationScore = parseFloat(gScore);
                    
                    // 计算今日预估增加
                    if (gamificationScore !== null && !isNaN(communityBalance)) {
                        data.estimatedIncrease = gamificationScore - communityBalance;
                    }

                    // 获取7天统计
                    if (stats && Array.isArray(stats)) {
                        data.dailyStats = stats.map(i => ({
                            date: i.date, dateShort: i.date.substring(5).replace('-', '/'),
                            income: parseFloat(i.income) || 0, expense: parseFloat(i.expense) || 0
                        }));
                    }
                    // 只渲染仍然处于 overview 的请求
                    if (token === this._reqToken.overview && this._tab === 'overview') {
                        this._renderOverview(data);
                        this._saveCache(data);
                    }
                } catch { if (token === this._reqToken.overview) this._showError('网络错误,请稍后重试'); }
                finally {
                    if (token === this._reqToken.overview) this._loadingState.overview = false;
                    btn?.classList.remove('spinning');
                }
            }

            // 获取 linux.do 用户的 gamification_score
            async _fetchGamificationScore(username) {
                if (!username) return null;
                const headers = buildAuthHeaders(`https://linux.do/u/${username}.json`, { 'Accept': 'application/json' });
                return new Promise(resolve => {
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url: `https://linux.do/u/${username}.json`,
                        withCredentials: true,
                        timeout: 10000,
                        headers,
                        onload: r => {
                            if (r.status === 200) {
                                try {
                                    const data = JSON.parse(r.responseText);
                                    resolve(data?.user || null);
                                    return;
                                } catch {}
                            }
                            resolve(null);
                        },
                        onerror: () => resolve(null),
                        ontimeout: () => resolve(null)
                    });
                });
            }

            _showLoginGuide(reason = 'auth') {
                const body = this.overlay.querySelector('.ldsp-ldc-body');
                this.overlay.querySelector('.ldsp-ldc-refresh')?.classList.remove('spinning');
                this._loading = false;
                
                const isTimeout = reason === 'timeout';
                const isNetwork = reason === 'network';
                
                const icon = isTimeout ? '⏱️' : isNetwork ? '🌐' : '🔐';
                const title = isTimeout ? '请求超时' : isNetwork ? '网络错误' : '需要登录';
                const desc = isTimeout 
                    ? '获取数据超时,可能是网络问题或未登录 LDC 站点。请先访问 LDC 官网确认已登录,然后返回重试。'
                    : isNetwork 
                    ? '网络连接失败,请检查网络后重试。如果问题持续,请尝试先访问 LDC 官网登录。'
                    : '请先登录 LDC 官网,登录后返回此页面刷新即可获取数据。';
                
                body.innerHTML = `
                    <div class="ldsp-ldc-ios-guide">
                        <div class="ldsp-ldc-ios-icon">${icon}</div>
                        <div class="ldsp-ldc-ios-title">${title}</div>
                        <div class="ldsp-ldc-ios-desc">${desc}</div>
                        <div class="ldsp-ldc-ios-solutions">
                            <div class="ldsp-ldc-ios-solution">
                                <div class="ldsp-ldc-ios-solution-title">✅ 解决方案</div>
                                <div class="ldsp-ldc-ios-solution-desc">访问 LDC 官网完成登录</div>
                                <a href="https://credit.linux.do/home" target="_blank" class="ldsp-ldc-ios-btn primary">打开 LDC 官网登录 ↗</a>
                            </div>
                        </div>
                        <button class="ldsp-ldc-retry-btn" style="margin-top:12px">🔄 重试</button>
                        <div class="ldsp-ldc-ios-tip">💡 登录后返回此页面点击重试即可</div>
                    </div>`;
                body.querySelector('.ldsp-ldc-retry-btn')?.addEventListener('click', () => {
                    if (this._tab === 'overview') this._fetchData();
                    else if (this._tab === 'transactions') this._fetchTrans(true);
                });
            }

            // 优先使用 iframe 桥接方式请求(兼容 iOS Safari);非 iOS 直接使用 GM 更快
            async _request(url, method = 'GET', data = null) {
                // 桌面/安卓:直接 GM,避免 iframe 额外等待
                if (!this._isIOS) {
                    return this._requestViaGM(url, method, data);
                }

                // iOS:先桥接,设置较短超时;失败后再 GM 兜底
                const bridgeResult = await this._requestViaBridge(url, method, data, 2000);
                if (bridgeResult && !bridgeResult._bridgeError) return bridgeResult;
                return await this._requestViaGM(url, method, data);
            }

            // iframe 桥接请求
            async _requestViaBridge(url, method = 'GET', data = null, timeoutMs = 15000) {
                if (this._bridgeReady) await this._bridgeReady;
                if (!this._bridge) return { _bridgeError: true, _error: '桥接未就绪' };
                
                return new Promise((resolve) => {
                    const id = ++this._reqId;
                    const timeout = setTimeout(() => { 
                        this._requests.delete(id); 
                        debugBridgeLog('LDC bridge timeout', { url, method });
                        resolve({ _bridgeError: true, _timeoutError: true, _error: '请求超时' }); 
                    }, timeoutMs);
                    
                    this._requests.set(id, ({ status, data: respData }) => {
                        clearTimeout(timeout);
                        if (status === 200) {
                            // LDC API 返回格式:{ code: 200, data: {...} }
                            if (respData?.data !== undefined) {
                                resolve(respData.data);
                            } else if (respData?._error || respData?.error_msg) {
                                resolve({ _error: respData._error || respData.error_msg });
                            } else {
                                resolve(respData);
                            }
                        } else if (status === 401 || status === 403) {
                            debugBridgeLog('LDC bridge auth fail', { url, status });
                            resolve({ _authError: true });
                        } else {
                            debugBridgeLog('LDC bridge status', { url, status });
                            resolve({ _bridgeError: true, _error: respData?._error || `请求失败 (${status})` });
                        }
                    });
                    
                    try {
                        this._bridge.contentWindow.postMessage({ 
                            type: 'ldsp-ldc-request', 
                            requestId: id, 
                            url,
                            method,
                            data 
                        }, LDCManager.LDC_ORIGIN);
                    } catch (e) {
                        clearTimeout(timeout);
                        this._requests.delete(id);
                        debugBridgeLog('LDC bridge postMessage error', e?.message || e);
                        resolve({ _bridgeError: true, _error: '发送请求失败' });
                    }
                });
            }

            // GM_xmlhttpRequest 方式请求(回退方案)
            _requestViaGM(url, method = 'GET', data = null) {
                return new Promise(resolve => {
                    GM_xmlhttpRequest({
                        method, url,
                        withCredentials: true,
                        timeout: 15000,
                        headers: { 'Accept': 'application/json', 'Content-Type': 'application/json', 'Referer': 'https://credit.linux.do/home' },
                        data: data ? JSON.stringify(data) : undefined,
                        onload: r => {
                            if (r.status === 200) {
                    try { const j = JSON.parse(r.responseText); resolve(j?.data ?? null); return; } catch {}
                }
                if (r.status === 401 || r.status === 403) {
                    debugBridgeLog('LDC GM auth fail', { url, status: r.status });
                    resolve({ _authError: true });
                    return;
                }
                debugBridgeLog('LDC GM unexpected status', { url, status: r.status });
                resolve(null);
            },
            onerror: () => { debugBridgeLog('LDC GM network error', url); resolve({ _networkError: true }); },
            ontimeout: () => { debugBridgeLog('LDC GM timeout', url); resolve({ _timeoutError: true }); }
        });
    });
}

            _renderOverview(data) {
                const body = this.overlay.querySelector('.ldsp-ldc-body');
                
                // 计算7天统计
                const stats7d = data.dailyStats.reduce((acc, d) => ({
                    income: acc.income + d.income,
                    expense: acc.expense + d.expense
                }), { income: 0, expense: 0 });

                // 生成预估增加 HTML
                let estimateHtml = '';
                if (data.estimatedIncrease !== null && data.estimatedIncrease !== undefined) {
                    const est = data.estimatedIncrease;
                    const estClass = est > 0 ? 'positive' : est < 0 ? 'negative' : 'neutral';
                    const estText = est > 0 ? `+${est.toFixed(2)}` : est < 0 ? `${est.toFixed(2)}` : '0.00';
                    estimateHtml = `
                        <div class="ldsp-ldc-balance-estimate">
                            <span class="ldsp-ldc-balance-estimate-tip" data-ldsp-tooltip="预估分数仅供参考,每日凌晨失效">?</span>
                            <span>预估变化</span>
                            <span class="ldsp-ldc-balance-estimate-value ${estClass}">${estText}</span>
                        </div>`;
                }

                // 生成迷你柱状图
                const maxVal = Math.max(...data.dailyStats.flatMap(d => [d.income, d.expense]), 1);
                const chartHtml = data.dailyStats.length > 0 ? `
                    <div class="ldsp-ldc-chart">
                        <div class="ldsp-ldc-chart-bars">
                            ${data.dailyStats.map(d => {
                                const incomeH = Math.round((d.income / maxVal) * 100);
                                const expenseH = Math.round((d.expense / maxVal) * 100);
                                return `<div class="ldsp-ldc-chart-col" title="${d.dateShort}\n收入: +${d.income.toFixed(2)}\n支出: -${d.expense.toFixed(2)}">
                                    <div class="ldsp-ldc-chart-bar-group">
                                        <div class="ldsp-ldc-chart-bar income" style="height:${incomeH}%"></div>
                                        <div class="ldsp-ldc-chart-bar expense" style="height:${expenseH}%"></div>
                                    </div>
                                    <span class="ldsp-ldc-chart-label">${d.dateShort.split('/')[1]}</span>
                                </div>`;
                            }).join('')}
                        </div>
                        <div class="ldsp-ldc-chart-legend">
                            <span class="ldsp-ldc-legend-item income"><i></i>收入</span>
                            <span class="ldsp-ldc-legend-item expense"><i></i>支出</span>
                        </div>
                    </div>` : '';

                body.innerHTML = `
                    <div class="ldsp-ldc-balance-card">
                        <div class="ldsp-ldc-balance-main">
                            <div class="ldsp-ldc-balance-left">
                                <div class="ldsp-ldc-balance-label">🍟可用余额:</div>
                                <div class="ldsp-ldc-balance-value">${Utils.escapeHtml(data.credits)}</div>
                            </div>
                            <div class="ldsp-ldc-balance-right">
                                <div class="ldsp-ldc-balance-sub">今日剩余额度 <span class="ldsp-ldc-balance-sub-value">${Utils.escapeHtml(data.dailyLimit)}</span></div>
                                ${estimateHtml}
                            </div>
                        </div>
                        <div class="ldsp-ldc-balance-footer">
                            <span class="ldsp-ldc-balance-time">数据更新于:${Utils.escapeHtml(data.updateTime)}</span>
                        </div>
                    </div>
                    <div class="ldsp-ldc-stats-grid">
                        <div class="ldsp-ldc-stat-card income">
                            <div class="ldsp-ldc-stat-icon">📈</div>
                            <div class="ldsp-ldc-stat-info">
                                <div class="ldsp-ldc-stat-label">总收入</div>
                                <div class="ldsp-ldc-stat-num">+${Utils.escapeHtml(data.incomeTotal)}</div>
                            </div>
                        </div>
                        <div class="ldsp-ldc-stat-card expense">
                            <div class="ldsp-ldc-stat-icon">📉</div>
                            <div class="ldsp-ldc-stat-info">
                                <div class="ldsp-ldc-stat-label">总支出</div>
                                <div class="ldsp-ldc-stat-num">-${Utils.escapeHtml(data.expenseTotal)}</div>
                            </div>
                        </div>
                    </div>
                    <div class="ldsp-ldc-section">
                        <div class="ldsp-ldc-section-header">
                            <span class="ldsp-ldc-section-title">📊 近7天收支</span>
                            <span class="ldsp-ldc-section-summary">
                                <em class="income">+${stats7d.income.toFixed(2)}</em>
                                <em class="expense">-${stats7d.expense.toFixed(2)}</em>
                            </span>
                        </div>
                        ${chartHtml || '<div class="ldsp-ldc-empty">暂无数据</div>'}
                    </div>
                    <div class="ldsp-ldc-info-section">
                        <a href="https://linux.do/t/topic/1353235" target="_blank" rel="noopener" class="ldsp-ldc-info-card faq">
                            <span class="ldsp-ldc-info-icon">📖</span>
                            <div class="ldsp-ldc-info-content">
                                <div class="ldsp-ldc-info-title">Credit FAQ & 积分规则</div>
                                <div class="ldsp-ldc-info-desc">了解 LDC 积分的获取方式、使用规则和常见问题</div>
                            </div>
                            <span class="ldsp-ldc-info-arrow">→</span>
                        </a>
                    </div>`;
            }

            _getTimeRange() {
                const now = new Date(), todayStart = new Date(now.getFullYear(), now.getMonth(), now.getDate());
                const end = new Date(todayStart); end.setDate(end.getDate() + 1);
                let start = new Date(todayStart);
                const r = this._filter.timeRange;
                if (r === 'today') start = todayStart;
                else if (r === '7days') start.setDate(start.getDate() - 6);
                else if (r === '30days') start.setDate(start.getDate() - 29);
                else start.setFullYear(start.getFullYear() - 1);
                const fmt = d => `${d.getFullYear()}-${String(d.getMonth()+1).padStart(2,'0')}-${String(d.getDate()).padStart(2,'0')}T00:00:00+08:00`;
                return { startTime: fmt(start), endTime: fmt(end) };
            }

            async _fetchTrans(refresh = false, more = false) {
                const token = ++this._reqToken.trans;
                this._loadingState.trans = true;
                const currentTab = this._tab; // 保存发起请求时的 tab 状态
                const _body = this.overlay.querySelector('.ldsp-ldc-body');
                const btn = this.overlay.querySelector('.ldsp-ldc-refresh');
                if (!more) { btn?.classList.add('spinning'); this._trans = { orders: [], page: 1, total: 0, hasMore: false }; this._renderTransUI(true); }
                const page = more ? this._trans.page + 1 : 1;

                try {
                    // 确保 _userId 已设置,用于准确判断收支
                    if (!this._userId) {
                        const user = await this._getUserInfo();
                        if (user && !user._authError) {
                            this._userId = user.id || user.user_id || null;
                        }
                    }
                    // 检查 tab 是否已切换,是则放弃渲染
                    if (this._tab !== currentTab) return;
                    const { startTime, endTime } = this._getTimeRange();
                    const payload = { page, page_size: 20, startTime, endTime };
                    if (this._filter.type) payload.type = this._filter.type;
                    const result = await this._request('https://credit.linux.do/api/v1/order/transactions', 'POST', payload);
                    // 请求返回后再次检查 tab 状态
                    if (this._tab !== currentTab || token !== this._reqToken.trans) return;
                    if (result?._authError) { this._showError('请先登录 credit.linux.do', true); return; }
                    if (!result) { this._showError('获取数据失败'); return; }
                    const orders = result.orders || [], total = result.total || 0;
                    this._trans.orders = more ? [...this._trans.orders, ...orders] : orders;
                    this._trans.page = page;
                    this._trans.total = total;
                    this._trans.hasMore = this._trans.orders.length < total;
                    this._renderTransUI();
                } catch { if (!more && this._tab === currentTab && token === this._reqToken.trans) this._showError('网络错误,请稍后重试'); }
                finally {
                    if (token === this._reqToken.trans) this._loadingState.trans = false;
                    btn?.classList.remove('spinning');
                }
            }

            _renderTransUI(loading = false) {
                const body = this.overlay.querySelector('.ldsp-ldc-body');
                const { orders, total, hasMore } = this._trans;
                const filterHtml = this._getFilterHtml();
                let content;
                if (loading) {
                    content = `<div class="ldsp-ldc-loading"><div class="ldsp-spinner"></div><div>加载中...</div></div>`;
                } else if (!orders.length) {
                    const tl = LDCManager.TIME_RANGES.find(t => t.id === this._filter.timeRange)?.label || '';
                    const tp = this._filter.type ? (LDCManager.TRANS_TYPES.find(t => t.id === this._filter.type)?.label || '') : '';
                    content = `<div class="ldsp-ldc-empty-state"><div class="ldsp-ldc-empty-icon">📭</div><div class="ldsp-ldc-empty-text">暂无交易记录</div>
                        ${tl || tp ? `<div class="ldsp-ldc-empty-hint">筛选条件:${tl}${tp ? ` · ${tp}` : ''}</div>` : ''}</div>`;
                } else {
                    const list = orders.map(o => {
                        const ti = LDCManager.TRANS_TYPES.find(t => t.id === o.type) || { icon: '📋', label: o.type };
                        // 收支判断:优先通过 payee/payer 判断,同时处理 amount 可能为负数的情况
                        const isIncome = this._userId 
                            ? o.payee_user_id === this._userId && o.payer_user_id !== this._userId
                            : (o.type === 'receive' || o.type === 'community' || o.payer_user_id === 0);
                        // 使用绝对值,因为 API 返回的 amount 可能已经是负数
                        const amt = Math.abs(parseFloat(o.amount) || 0);
                        return `<div class="ldsp-ldc-trans-item" data-order-id="${o.id}">
                            <div class="ldsp-ldc-trans-icon">${ti.icon}</div>
                            <div class="ldsp-ldc-trans-info"><div class="ldsp-ldc-trans-name">${Utils.escapeHtml(o.order_name || o.app_name || '未知')}</div>
                            <div class="ldsp-ldc-trans-meta"><span>${Utils.escapeHtml(this._fmtTime(o.trade_time || o.created_at))}</span>
                            <span class="ldsp-ldc-trans-type type-${o.type || 'default'}">${ti.label}</span></div></div>
                            <div class="ldsp-ldc-trans-amount ${isIncome ? 'income' : 'expense'}">${isIncome ? '+' : '-'}${amt.toFixed(2)}</div></div>`;
                    }).join('');
                    // 将 sentinel 放在 trans-list 内部,这样滚动时才能正确触发
                    content = `<div class="ldsp-ldc-trans-summary">共 ${total} 条记录${orders.length < total ? `,已加载 ${orders.length} 条` : ''}</div>
                        <div class="ldsp-ldc-trans-list">${list}${hasMore ? `<div class="ldsp-ldc-load-sentinel"></div>` : ''}</div>`;
                }
                body.innerHTML = `${filterHtml}<div class="ldsp-ldc-trans-content">${content}</div>`;
                this._bindFilterEvents(body);
                this._bindTransEvents();
            }

            _bindTransEvents() {
                const body = this.overlay.querySelector('.ldsp-ldc-body');
                body.querySelectorAll('.ldsp-ldc-trans-item').forEach(item => {
                    item.addEventListener('click', () => {
                        const o = this._trans.orders.find(x => String(x.id) === item.dataset.orderId);
                        if (o) this._showDetail(o);
                    });
                });
                // 瀑布流滚动加载
                this._setupInfiniteScroll(body);
            }

            _setupInfiniteScroll(body) {
                // 移除旧的监听器(如果存在)
                if (this._scrollHandler) {
                    body.removeEventListener('scroll', this._scrollHandler);
                }
                
                const sentinel = body.querySelector('.ldsp-ldc-load-sentinel');
                if (!sentinel || !this._trans.hasMore) return;
                
                // 找到实际的滚动容器 (.ldsp-ldc-trans-list)
                const scrollContainer = body.querySelector('.ldsp-ldc-trans-list');
                if (!scrollContainer) return;
                
                // 使用 IntersectionObserver 实现高性能滚动加载
                if (this._scrollObserver) {
                    this._scrollObserver.disconnect();
                }
                
                this._scrollObserver = new IntersectionObserver(async (entries) => {
                    const entry = entries[0];
                    if (entry.isIntersecting && !this._loadingState.trans && this._trans.hasMore) {
                        // 显示加载指示器
                        sentinel.innerHTML = '<div class="ldsp-ldc-loading-more"><div class="ldsp-mini-spin" style="width:14px;height:14px"></div><span>加载中...</span></div>';
                        await this._fetchTrans(false, true);
                    }
                }, {
                    root: scrollContainer,
                    rootMargin: '100px',
                    threshold: 0
                });
                
                this._scrollObserver.observe(sentinel);
            }

            // ==================== 小卖部功能 ====================
            // 默认分类(与数据库保持一致,使用数字 ID)
            static SHOP_CATEGORIES = [
                { id: 1, name: 'AI', icon: '🤖' },
                { id: 2, name: '存储', icon: '💾' },
                { id: 3, name: '小鸡', icon: '🐔' },
                { id: 4, name: '咨询', icon: '💬' }
            ];

            // 解包嵌套的 API 响应 { success, data: { success, data: {...} } } -> 内层 data
            _unwrapShopResponse(resp) {
                if (resp?.success && resp.data?.success && resp.data?.data) {
                    return { success: true, data: resp.data.data };
                }
                return resp;
            }

            async _fetchShopCategories(forceRefresh = false) {
                const now = Date.now();
                // 检查缓存是否有效
                if (!forceRefresh && this._shopCategoriesCache && (now - this._shopCategoriesCache.timestamp < this._shopCategoriesCacheTTL)) {
                    return this._shopCategoriesCache.categories;
                }
                try {
                    const resp = this._unwrapShopResponse(await this._shopRequest('/api/shop/categories'));
                    if (resp?.success && resp.data?.categories) {
                        this._shopCategories = resp.data.categories;
                        this._shopCategoriesCache = { categories: resp.data.categories, timestamp: now };
                        return resp.data.categories;
                    }
                } catch {}
                return LDCManager.SHOP_CATEGORIES;
            }

            async _fetchShopProducts(categoryId = '', forceRefresh = false, page = 1, append = false) {
                const cacheKey = categoryId || '__all__';
                const now = Date.now();
                // 首次加载检查缓存是否有效
                if (page === 1 && !forceRefresh) {
                    const cached = this._shopProductsCache.get(cacheKey);
                    if (cached && (now - cached.timestamp < this._shopCacheTTL)) {
                        this._shopHasMore = cached.hasMore !== false;
                        this._shopTotal = cached.total || cached.products.length; // 从缓存恢复总数
                        return cached.products;
                    }
                }
                try {
                    let url = `/api/shop/products?page=${page}&pageSize=${this._shopPageSize}`;
                    if (categoryId) url += `&categoryId=${encodeURIComponent(categoryId)}`;
                    const resp = this._unwrapShopResponse(await this._shopRequest(url));
                    if (resp?.success && resp.data?.products) {
                        const products = resp.data.products;
                        const total = resp.data.pagination?.total || resp.data.total || products.length;
                        this._shopHasMore = (page * this._shopPageSize) < total;
                        // 保存总数用于显示
                        if (page === 1) {
                            this._shopTotal = total;
                        }
                        
                        if (page === 1) {
                            this._shopProductsCache.set(cacheKey, { products, timestamp: now, hasMore: this._shopHasMore, total });
                            return products;
                        } else {
                            // 追加模式不更新缓存,仅返回新数据
                            return products;
                        }
                    }
                } catch {}
                this._shopHasMore = false;
                return [];
            }
            
            // 清除商品缓存(发布/更新/删除后调用)
            _invalidateShopCache() {
                this._shopProductsCache.clear();
                this._shopMyProductsCache = null;
            }

            async _fetchMyProducts(forceRefresh = false) {
                const now = Date.now();
                // 检查缓存是否有效
                if (!forceRefresh && this._shopMyProductsCache && (now - this._shopMyProductsCache.timestamp < this._shopCacheTTL)) {
                    return this._shopMyProductsCache.products;
                }
                try {
                    const resp = this._unwrapShopResponse(await this._shopRequest('/api/shop/my-products'));
                    if (resp?.success && resp.data?.products) {
                        this._shopMyProductsCache = { products: resp.data.products, timestamp: now };
                        return resp.data.products;
                    }
                } catch {}
                return [];
            }

            // 检查是否已登录(有 Token)
            _hasToken() {
                const tokenKey = `ldsp_${CURRENT_SITE.prefix}_leaderboard_token`;
                return !!GM_getValue(tokenKey, null);
            }

            async _createProduct(data) {
                // 检查登录状态
                if (!this._hasToken()) {
                    return { success: false, error: '请先在排行榜面板中登录后再发布物品' };
                }
                try {
                    const resp = this._unwrapShopResponse(await this._shopRequest('/api/shop/products', 'POST', data));
                    return resp;
                } catch (e) {
                    return { success: false, error: e.message || '创建失败' };
                }
            }

            async _updateProduct(id, data) {
                // 检查登录状态
                if (!this._hasToken()) {
                    return { success: false, error: '请先在排行榜面板中登录后再操作' };
                }
                try {
                    const resp = this._unwrapShopResponse(await this._shopRequest(`/api/shop/my-products/${id}`, 'PUT', data));
                    return resp;
                } catch (e) {
                    return { success: false, error: e.message || '更新失败' };
                }
            }

            async _toggleProductStatus(id, active, product = null) {
                try {
                    // 下架使用 offline 接口
                    if (!active) {
                        const resp = this._unwrapShopResponse(await this._shopRequest(`/api/shop/my-products/${id}/offline`, 'POST'));
                        return resp;
                    } else {
                        // 重新上架 - 发送商品当前数据触发后端状态更新
                        // 后端会自动将 offline 状态改为 pending
                        const data = product ? {
                            name: product.name,
                            categoryId: product.category_id,
                            description: product.description,
                            price: product.price,
                            discount: product.discount,
                            imageUrl: product.image_url || '',
                            paymentLink: product.payment_link
                        } : {};
                        const resp = this._unwrapShopResponse(await this._shopRequest(`/api/shop/my-products/${id}`, 'PUT', data));
                        return resp;
                    }
                } catch (e) {
                    return { success: false, error: e.message || '操作失败' };
                }
            }

            async _deleteMyProduct(id) {
                try {
                    const resp = this._unwrapShopResponse(await this._shopRequest(`/api/shop/my-products/${id}`, 'DELETE'));
                    return resp;
                } catch (e) {
                    return { success: false, error: e.message || '删除失败' };
                }
            }

            async _recordProductView(id) {
                // 调用商品详情 API 来记录浏览量
                // 后端会自动基于 IP 做防刷处理(1小时内同IP只计1次)
                try {
                    const resp = this._unwrapShopResponse(await this._shopRequest(`/api/shop/products/${id}`));
                    if (resp?.success && resp.data?.product) {
                        const newViewCount = resp.data.product.view_count || 0;
                        // 更新详情页面显示的浏览量(如果页面还在展示这个商品)
                        if (this._shopProduct?.id === id) {
                            this._shopProduct.view_count = newViewCount;
                            // 更新 DOM 中的浏览量显示
                            const viewEl = this.overlay.querySelector('.ldsp-shop-detail-info-item');
                            if (viewEl && viewEl.textContent.includes('👁')) {
                                viewEl.textContent = `👁 ${newViewCount}`;
                            }
                        }
                    }
                } catch (e) {
                    // 静默失败,不影响用户体验
                    console.log('[LDStatus Pro Shop] Record view failed:', e.message);
                }
            }

            async _shopRequest(path, method = 'GET', body = null) {
                const API_BASE = (typeof ApiService !== 'undefined' && ApiService.baseUrl) || 'https://api.ldspro.qzz.io';
                const url = API_BASE + path;
                
                // 获取 JWT Token 用于认证(存储键格式:ldsp_{site_prefix}_leaderboard_token)
                // CURRENT_SITE.prefix 对于 linux.do 是 'linux_do'
                const tokenKey = `ldsp_${CURRENT_SITE.prefix}_leaderboard_token`;
                const token = GM_getValue(tokenKey, null);
                
                // 调试日志
                console.log('[LDStatus Pro Shop] Request:', { url, method, tokenKey, hasToken: !!token, tokenPreview: token ? token.substring(0, 20) + '...' : null });
                
                const headers = {
                    'Content-Type': 'application/json',
                    'Accept': 'application/json'
                };
                if (token) {
                    headers['Authorization'] = `Bearer ${token}`;
                }
                
                return new Promise((resolve, reject) => {
                    GM_xmlhttpRequest({
                        method,
                        url,
                        headers,
                        data: body ? JSON.stringify(body) : undefined,
                        withCredentials: true,
                        timeout: 15000,
                        onload: r => {
                            if (r.status >= 200 && r.status < 300) {
                                try {
                                    resolve(JSON.parse(r.responseText));
                                } catch {
                                    resolve({ success: false, error: '响应解析失败' });
                                }
                            } else {
                                try {
                                    const err = JSON.parse(r.responseText);
                                    // 确保 error 是字符串
                                    const errMsg = typeof err.error === 'string' ? err.error :
                                                   (err.error?.message || err.message || `请求失败 (${r.status})`);
                                    resolve({ success: false, error: errMsg });
                                } catch {
                                    resolve({ success: false, error: `请求失败 (${r.status})` });
                                }
                            }
                        },
                        onerror: () => resolve({ success: false, error: '网络错误' }),
                        ontimeout: () => resolve({ success: false, error: '请求超时' })
                    });
                });
            }
            
            // 获取当前活动的士多 body 容器(支持独立士多面板和 LDC 内的士多)
            _getShopBody() {
                // 优先检查独立士多面板
                if (this.shopOverlay?.classList.contains('show')) {
                    return this.shopOverlay.querySelector('.ldsp-ldc-body');
                }
                // 否则返回 LDC overlay 的 body
                return this.overlay.querySelector('.ldsp-ldc-body');
            }

            async _renderShop() {
                const body = this._getShopBody();
                body.innerHTML = `<div class="ldsp-ldc-loading"><div class="ldsp-spinner"></div><div>加载中...</div></div>`;
                
                // 重置分页状态
                this._shopPage = 1;
                this._shopHasMore = true;
                this._shopLoading = false;
                
                // 并行获取分类和商品
                const [categories, products] = await Promise.all([
                    this._fetchShopCategories(),
                    this._fetchShopProducts(this._shopCategory, false, 1)
                ]);
                
                this._shopCategories = categories.length > 0 ? categories : LDCManager.SHOP_CATEGORIES;
                this._shopProducts = products;
                this._shopView = 'list';
                this._renderShopList();
            }

            // 刷新物品列表(点击当前Tab时调用,带1分钟缓存限制)
            async _refreshShopList() {
                const body = this._getShopBody();
                const cacheKey = this._shopCategory || '__all__';
                const cached = this._shopProductsCache.get(cacheKey);
                const now = Date.now();
                
                // 检查是否在1分钟内(缓存有效)
                if (cached && (now - cached.timestamp < this._shopCacheTTL)) {
                    // 缓存有效,直接使用缓存数据(不请求)
                    return;
                }
                
                // 重置分页状态
                this._shopPage = 1;
                this._shopHasMore = true;
                this._shopLoading = false;
                
                // 显示加载状态
                const grid = body.querySelector('.ldsp-shop-grid') || body.querySelector('.ldsp-shop-empty');
                if (grid) grid.innerHTML = `<div class="ldsp-ldc-loading" style="grid-column:1/-1"><div class="ldsp-spinner"></div><div>刷新中...</div></div>`;
                
                // 强制刷新获取数据
                this._shopProducts = await this._fetchShopProducts(this._shopCategory, true, 1);
                this._renderShopList();
            }

            // 刷新我的物品(点击当前Tab时调用,带1分钟缓存限制)
            async _refreshShopMy() {
                const body = this._getShopBody();
                const now = Date.now();
                
                // 检查是否在1分钟内(缓存有效)
                if (this._shopMyProductsCache && (now - this._shopMyProductsCache.timestamp < this._shopCacheTTL)) {
                    // 缓存有效,直接使用缓存数据(不请求)
                    return;
                }
                
                // 显示加载状态
                const list = body.querySelector('.ldsp-shop-my-list') || body.querySelector('.ldsp-shop-empty');
                if (list) list.innerHTML = `<div class="ldsp-ldc-loading"><div class="ldsp-spinner"></div><div>刷新中...</div></div>`;
                
                // 强制刷新获取数据并重新渲染
                this._shopMyProducts = await this._fetchMyProducts(true);
                // 直接渲染我的物品页面(数据已获取)
                this._renderShopMyWithData();
            }
            
            // 使用已有数据渲染我的物品(避免重复请求)
            _renderShopMyWithData() {
                const body = this._getShopBody();
                this._shopView = 'my';
                
                const products = this._shopMyProducts;
                const categories = this._shopCategories;
                
                const productList = products.length > 0 ? products.map(p => {
                    const hasImg = p.image_url && p.image_url.trim();
                    const cat = categories.find(c => c.id === p.category_id);
                    const catIcon = p.category_icon || cat?.icon || '📦';
                    const catName = p.category_name || cat?.name || '其他';
                    const status = p.status || 'pending';
                    const statusMap = {
                        'approved': { text: '在售', class: 'active' },
                        'pending': { text: '待审核', class: 'inactive' },
                        'rejected': { text: '已拒绝', class: 'inactive rejected' },
                        'offline': { text: '已下架', class: 'inactive' }
                    };
                    const statusInfo = statusMap[status] || statusMap['pending'];
                    const myCardBgColor = this._getRandomLightColor(p.id);
                    // 获取拒绝/下架原因
                    const statusReason = p.status_reason || p.reject_reason;
                    const isAdminAction = p.status_action === 'admin_offline' || p.status_admin;
                    // v2.0: 商品类型和库存(优先使用 availableStock 或 cdkStats.available)
                    const productType = p.product_type || 'link';
                    const isCdk = productType === 'cdk';
                    const stock = parseInt(p.stock) || 0;
                    const availableStock = p.availableStock !== undefined ? p.availableStock : (p.cdkStats?.available ?? stock);
                    const isLowStock = isCdk && stock !== -1 && availableStock <= 5 && availableStock > 0;
                    const isOutOfStock = isCdk && stock !== -1 && availableStock <= 0;
                    
                    return `<div class="ldsp-shop-my-card" data-product-id="${p.id}">
                        <div class="ldsp-shop-my-card-status ${statusInfo.class}">${statusInfo.text}</div>
                        ${isCdk ? `<div class="ldsp-shop-my-card-type">CDK</div>` : ''}
                        ${hasImg ? `<div class="ldsp-shop-my-card-img-wrap"><img class="ldsp-shop-my-card-img" src="${Utils.escapeHtml(p.image_url)}" alt="${Utils.escapeHtml(p.name)}" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'"><div class="ldsp-shop-my-card-img-placeholder" style="display:none;background:${myCardBgColor}">${catIcon}</div></div>` : 
                        `<div class="ldsp-shop-my-card-img-placeholder" style="background:${myCardBgColor}">${catIcon}</div>`}
                        <div class="ldsp-shop-my-card-info">
                            <div class="ldsp-shop-my-card-name">${Utils.escapeHtml(p.name)}</div>
                            ${(status === 'rejected' || status === 'offline') && statusReason ? `<div class="ldsp-shop-my-card-reason">${isAdminAction ? '⚠️ 管理员下架: ' : (status === 'rejected' ? '❌ 拒绝原因: ' : '📝 原因: ')}${Utils.escapeHtml(statusReason)}</div>` : ''}
                            <div class="ldsp-shop-my-card-meta">
                                <span>${catIcon} ${Utils.escapeHtml(catName)}</span>
                                ${isCdk ? `<span class="ldsp-shop-my-card-stock${isOutOfStock ? ' low' : isLowStock ? ' low' : ''}"><span class="available">${stock === -1 ? '∞' : availableStock}</span><span class="divider">/</span><span class="total">${p.cdkStats?.total || stock || 0}</span></span>` : ''}
                                <span>👁️ ${p.view_count || 0}</span>
                            </div>
                            <div class="ldsp-shop-my-card-price">${parseFloat(p.price).toFixed(2)} LDC</div>
                            <div class="ldsp-shop-my-card-actions">
                                <button class="ldsp-shop-my-card-btn" data-action="edit" data-id="${p.id}">编辑</button>
                                ${isCdk ? `<button class="ldsp-shop-my-card-btn" data-action="manage-cdk" data-id="${p.id}">CDK库存</button>` : ''}
                                ${status === 'approved' ? `<button class="ldsp-shop-my-card-btn danger" data-action="toggle" data-id="${p.id}">下架</button>` : ''}
                                ${status === 'offline' ? `<button class="ldsp-shop-my-card-btn" data-action="toggle" data-id="${p.id}">重新上架</button>` : ''}
                                ${status !== 'approved' ? `<button class="ldsp-shop-my-card-btn danger" data-action="delete" data-id="${p.id}">删除</button>` : ''}
                            </div>
                        </div>
                    </div>`;
                }).join('') : '';

                body.innerHTML = `
                    <div class="ldsp-shop">
                        <div class="ldsp-shop-header">
                            <div class="ldsp-shop-tabs">
                                <div class="ldsp-shop-tab" data-view="list">🏪 广场</div>
                                <div class="ldsp-shop-tab active" data-view="my">📦 我的</div>
                                <div class="ldsp-shop-tab" data-view="orders">📋 订单</div>
                                <div class="ldsp-shop-tab" data-view="settings">⚙️ 设置</div>
                            </div>
                            <button class="ldsp-shop-add-btn" data-action="add">➕ 发布</button>
                        </div>
                        ${products.length > 0 ? `<div class="ldsp-shop-my-list">${productList}</div>` : 
                        `<div class="ldsp-shop-empty"><div class="ldsp-shop-empty-icon">📭</div><div class="ldsp-shop-empty-text">您还没有发布物品</div><div class="ldsp-shop-empty-hint">点击上方按钮发布您的第一个物品</div></div>`}
                    </div>`;

                this._bindShopMyEvents(body);
            }

            _renderShopList() {
                const body = this._getShopBody();
                const categories = this._shopCategories;
                const products = this._shopProducts;
                
                // 修复:使用 String 转换确保比较正确
                const currentCategory = String(this._shopCategory || '');
                // 获取当前分类名称用于显示
                const currentCat = categories.find(c => String(c.id) === currentCategory);
                const categoryLabel = currentCat ? `${currentCat.icon || '📦'} ${currentCat.name}` : '🏷️ 全部';
                
                const categoryChips = [
                    `<div class="ldsp-shop-filter-chip${!currentCategory ? ' active' : ''}" data-category="">🏷️ 全部</div>`,
                    ...categories.map(c => `<div class="ldsp-shop-filter-chip${currentCategory === String(c.id) ? ' active' : ''}" data-category="${c.id}">${c.icon || '📦'} ${c.name}</div>`)
                ].join('');

                const productCards = products.length > 0 ? products.map(p => this._renderShopCard(p, categories)).join('') : '';
                
                // 添加加载更多哨兵
                const loadMoreHtml = this._shopHasMore ? '<div class="ldsp-shop-load-sentinel"><div class="ldsp-shop-load-more-hint">⬇️ 滚动加载更多</div></div>' : (products.length > 0 ? '<div class="ldsp-shop-loaded-all">✅ 已加载全部</div>' : '');

                body.innerHTML = `
                    <div class="ldsp-shop">
                        <div class="ldsp-shop-header">
                            <div class="ldsp-shop-tabs">
                                <div class="ldsp-shop-tab active" data-view="list">🏪 广场</div>
                                <div class="ldsp-shop-tab" data-view="my">📦 我的</div>
                                <div class="ldsp-shop-tab" data-view="orders">📋 订单</div>
                                <div class="ldsp-shop-tab" data-view="settings">⚙️ 设置</div>
                            </div>
                            <button class="ldsp-shop-add-btn" data-action="add">➕ 发布</button>
                        </div>
                        <div class="ldsp-shop-filter">${categoryChips}</div>
                        <div class="ldsp-shop-count">${categoryLabel} 共 <strong>${this._shopTotal}</strong> 件物品</div>
                        ${products.length > 0 ? `<div class="ldsp-shop-grid">${productCards}${loadMoreHtml}</div>` : 
                        `<div class="ldsp-shop-empty"><div class="ldsp-shop-empty-icon">🏪</div><div class="ldsp-shop-empty-text">暂无物品</div><div class="ldsp-shop-empty-hint">快来发布第一个物品吧~</div></div>`}
                    </div>`;

                this._bindShopListEvents(body);
            }
            
            // 格式化头像 URL
            _formatAvatar(avatar, username) {
                if (avatar && avatar.trim()) return avatar.trim();
                return '';
            }
            
            // 生成随机浅色背景
            _getRandomLightColor(seed) {
                const colors = [
                    'linear-gradient(135deg,#e0f2fe,#bae6fd)', // 浅蓝
                    'linear-gradient(135deg,#fce7f3,#fbcfe8)', // 浅粉
                    'linear-gradient(135deg,#d1fae5,#a7f3d0)', // 浅绿
                    'linear-gradient(135deg,#fef3c7,#fde68a)', // 浅黄
                    'linear-gradient(135deg,#ede9fe,#ddd6fe)', // 浅紫
                    'linear-gradient(135deg,#ffedd5,#fed7aa)', // 浅橙
                    'linear-gradient(135deg,#e0e7ff,#c7d2fe)', // 浅靖蓝
                    'linear-gradient(135deg,#f5f5f4,#e7e5e4)'  // 浅灰
                ];
                const index = seed ? Math.abs(seed) % colors.length : Math.floor(Math.random() * colors.length);
                return colors[index];
            }
            
            // 渲染单个商品卡片
            // v2.0: 支持 product_type (link/cdk/store), stock, sold_count
            _renderShopCard(p, categories) {
                const price = parseFloat(p.price) || 0;
                const discount = parseFloat(p.discount) || 1;
                const finalPrice = (price * discount).toFixed(2);
                const hasDiscount = discount < 1;
                const hasImg = p.image_url && p.image_url.trim();
                const cat = categories.find(c => c.id === p.category_id);
                const catIcon = p.category_icon || cat?.icon || '📦';
                const catName = p.category_name || cat?.name || '其他';
                const sellerAvatar = this._formatAvatar(p.seller_avatar, p.seller_username);
                const updateTime = this._formatRelativeTime(p.updated_at || p.created_at);
                const bgColor = this._getRandomLightColor(p.id);
                
                // v2.0: 商品类型和库存
                const productType = p.product_type || 'link';
                const isCdk = productType === 'cdk';
                const isStore = productType === 'store';
                const stock = parseInt(p.stock) || 0;
                const soldCount = parseInt(p.sold_count) || 0;
                const availableStock = p.availableStock !== undefined ? p.availableStock : stock;
                const isLowStock = isCdk && stock !== -1 && availableStock <= 5 && availableStock > 0;
                const isOutOfStock = isCdk && stock !== -1 && availableStock <= 0;
                
                // CDK 库存显示:剩余/总计
                const totalStock = isCdk ? (p.cdkStats?.total || stock) : 0;
                const stockClass = isOutOfStock ? 'out' : isLowStock ? 'low' : '';
                
                // 小店类型使用独立卡片样式
                if (isStore) {
                    const storeOwner = p.seller_username || p.seller_name || '店主';
                    return `<div class="ldsp-shop-card store-card" data-product-id="${p.id}">
                        <div class="ldsp-shop-card-type store">友情小店</div>
                        <div class="ldsp-shop-card-cover" style="${hasImg ? '' : `background:${bgColor}`}">
                            ${hasImg ? `<img src="${Utils.escapeHtml(p.image_url)}" alt="" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'"><div class="ldsp-shop-card-placeholder" style="display:none;background:${bgColor}">🏪</div>` : '🏪'}
                        </div>
                        <div class="ldsp-shop-card-body">
                            <div class="ldsp-shop-card-name">${Utils.escapeHtml(p.name)}</div>
                            <div class="ldsp-shop-card-meta">
                                <span class="ldsp-shop-card-time">${updateTime}</span>
                            </div>
                            <div class="ldsp-shop-card-footer">
                                <span class="ldsp-shop-card-store-owner">👤 ${Utils.escapeHtml(storeOwner)}</span>
                                <div class="ldsp-shop-card-views">👁${p.view_count || 0}</div>
                            </div>
                        </div>
                    </div>`;
                }
                
                return `<div class="ldsp-shop-card${isOutOfStock ? ' out-of-stock' : ''}" data-product-id="${p.id}">
                    ${isCdk ? `<div class="ldsp-shop-card-type cdk">CDK</div>` : ''}
                    ${hasDiscount ? `<div class="ldsp-shop-card-discount">-${Math.round((1 - discount) * 100)}%</div>` : ''}
                    <div class="ldsp-shop-card-cover" style="${hasImg ? '' : `background:${bgColor}`}">
                        ${hasImg ? `<img src="${Utils.escapeHtml(p.image_url)}" alt="" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'"><div class="ldsp-shop-card-placeholder" style="display:none;background:${bgColor}">${catIcon}</div>` : catIcon}
                    </div>
                    <div class="ldsp-shop-card-body">
                        <div class="ldsp-shop-card-name">${Utils.escapeHtml(p.name)}</div>
                        <div class="ldsp-shop-card-meta">
                            <span class="ldsp-shop-card-category">${Utils.escapeHtml(catName)}</span>
                            ${isCdk ? `<span class="ldsp-shop-card-stock${stockClass ? ' ' + stockClass : ''}"><span class="available">${stock === -1 ? '∞' : availableStock}</span><span class="divider">/</span><span class="total">${stock === -1 ? '∞' : totalStock}</span></span>` : ''}
                            <span class="ldsp-shop-card-time">${updateTime}</span>
                        </div>
                        <div class="ldsp-shop-card-seller">
                            <img class="ldsp-shop-card-seller-avatar" src="${sellerAvatar}" alt="" onerror="this.style.display='none'">
                            <span class="ldsp-shop-card-seller-name">${Utils.escapeHtml(p.seller_username || '匿名')}</span>
                            ${isCdk && soldCount > 0 ? `<span class="ldsp-shop-card-sold">已售${soldCount}</span>` : ''}
                        </div>
                        <div class="ldsp-shop-card-footer">
                            <div class="ldsp-shop-card-price${hasDiscount ? ' discounted' : ''}">${finalPrice}<span>LDC</span>${hasDiscount ? `<span class="ldsp-shop-card-original">${price.toFixed(2)}</span>` : ''}</div>
                            <div class="ldsp-shop-card-views">👁${p.view_count || 0}</div>
                        </div>
                    </div>
                </div>`;
            }
            
            // 格式化相对时间
            _formatRelativeTime(timestamp) {
                if (!timestamp) return '';
                const now = Date.now();
                const diff = now - timestamp;
                const minutes = Math.floor(diff / 60000);
                const hours = Math.floor(diff / 3600000);
                const days = Math.floor(diff / 86400000);
                if (minutes < 1) return '刚刚';
                if (minutes < 60) return `${minutes}分钟前`;
                if (hours < 24) return `${hours}小时前`;
                if (days < 30) return `${days}天前`;
                return new Date(timestamp).toLocaleDateString('zh-CN');
            }

            _bindShopListEvents(body) {
                // 设置瀑布流滚动加载
                this._setupShopInfiniteScroll(body);
                
                // Tab 切换(带防抖和刷新逻辑)
                body.querySelectorAll('.ldsp-shop-tab').forEach(tab => {
                    tab.addEventListener('click', () => {
                        // 防抖:300ms 内重复点击忽略
                        if (this._shopTabClickDebounce) {
                            clearTimeout(this._shopTabClickDebounce);
                        }
                        this._shopTabClickDebounce = setTimeout(() => {
                            this._shopTabClickDebounce = null;
                        }, 300);
                        
                        const targetView = tab.dataset.view;
                        const isCurrentView = this._shopView === 'list' && targetView === 'list';
                        
                        if (targetView === 'my') {
                            // 切换到我的
                            this._renderShopMy();
                        } else if (targetView === 'orders') {
                            // 切换到订单
                            this._renderShopOrders();
                        } else if (targetView === 'settings') {
                            // 切换到设置
                            this._renderMerchantSettings();
                        } else if (isCurrentView) {
                            // 点击当前所在的广场Tab,触发刷新
                            this._refreshShopList();
                        }
                    });
                });
                
                // 分类筛选
                body.querySelectorAll('.ldsp-shop-filter-chip').forEach(chip => {
                    chip.addEventListener('click', async () => {
                        const category = chip.dataset.category || '';
                        const currentCategory = String(this._shopCategory || '');
                        if (category !== currentCategory) {
                            this._shopCategory = category;
                            // 重置分页状态
                            this._shopPage = 1;
                            this._shopHasMore = true;
                            this._shopLoading = false;
                            // 先更新 UI 状态再加载
                            body.querySelectorAll('.ldsp-shop-filter-chip').forEach(c => {
                                c.classList.toggle('active', (c.dataset.category || '') === category);
                            });
                            // 显示加载中
                            const grid = body.querySelector('.ldsp-shop-grid') || body.querySelector('.ldsp-shop-empty');
                            if (grid) grid.innerHTML = `<div class="ldsp-ldc-loading" style="grid-column:1/-1"><div class="ldsp-spinner"></div><div>加载中...</div></div>`;
                            this._shopProducts = await this._fetchShopProducts(category, false, 1);
                            this._renderShopList();
                        }
                    });
                });
                
                // 发布按钮
                body.querySelector('.ldsp-shop-add-btn')?.addEventListener('click', () => {
                    this._shopEditProduct = null;
                    this._renderShopForm();
                });
                
                // 商品卡片点击
                body.querySelectorAll('.ldsp-shop-card').forEach(card => {
                    card.addEventListener('click', () => {
                        const id = parseInt(card.dataset.productId);
                        const product = this._shopProducts.find(p => p.id === id);
                        if (product) {
                            // 记录当前滚动位置
                            const shopGrid = body.querySelector('.ldsp-shop-grid');
                            this._shopListScrollTop = shopGrid?.parentElement?.scrollTop || body.scrollTop || 0;
                            this._showProductDetail(product);
                        }
                    });
                });
            }
            
            // 设置商品列表瀑布流滚动加载
            _setupShopInfiniteScroll(body) {
                // 移除旧的监听器
                if (this._shopScrollObserver) {
                    this._shopScrollObserver.disconnect();
                    this._shopScrollObserver = null;
                }
                
                const sentinel = body.querySelector('.ldsp-shop-load-sentinel');
                if (!sentinel || !this._shopHasMore) return;
                
                const grid = body.querySelector('.ldsp-shop-grid');
                if (!grid) return;
                
                // 使用 IntersectionObserver 实现高性能滚动加载
                this._shopScrollObserver = new IntersectionObserver(async (entries) => {
                    const entry = entries[0];
                    if (entry.isIntersecting && !this._shopLoading && this._shopHasMore) {
                        this._shopLoading = true;
                        // 显示加载指示器
                        sentinel.innerHTML = '<div class="ldsp-shop-loading-more"><div class="ldsp-mini-spin" style="width:16px;height:16px;border-width:2px"></div><span>加载中...</span></div>';
                        
                        // 加载下一页
                        this._shopPage++;
                        const newProducts = await this._fetchShopProducts(this._shopCategory, false, this._shopPage);
                        
                        if (newProducts.length > 0) {
                            // 追加新商品到列表
                            this._shopProducts = [...this._shopProducts, ...newProducts];
                            // 追加新卡片到 DOM
                            const newCards = newProducts.map(p => this._renderShopCard(p, this._shopCategories)).join('');
                            sentinel.insertAdjacentHTML('beforebegin', newCards);
                            // 绑定新卡片的点击事件
                            const allCards = grid.querySelectorAll('.ldsp-shop-card');
                            allCards.forEach(card => {
                                if (!card._bound) {
                                    card._bound = true;
                                    card.addEventListener('click', () => {
                                        const id = parseInt(card.dataset.productId);
                                        const product = this._shopProducts.find(p => p.id === id);
                                        if (product) {
                                            this._shopListScrollTop = grid.scrollTop || 0;
                                            this._showProductDetail(product);
                                        }
                                    });
                                }
                            });
                        }
                        
                        // 更新哨兵状态
                        if (this._shopHasMore) {
                            sentinel.innerHTML = '<div class="ldsp-shop-load-more-hint">⬇️ 滚动加载更多</div>';
                        } else {
                            sentinel.innerHTML = '<div class="ldsp-shop-loaded-all">✅ 已加载全部</div>';
                        }
                        
                        this._shopLoading = false;
                    }
                }, {
                    root: grid,
                    rootMargin: '100px',
                    threshold: 0
                });
                
                this._shopScrollObserver.observe(sentinel);
            }

            async _renderShopMy(forceRefresh = true) {
                const body = this._getShopBody();
                body.innerHTML = `<div class="ldsp-ldc-loading"><div class="ldsp-spinner"></div><div>加载中...</div></div>`;
                
                this._shopMyProducts = await this._fetchMyProducts(forceRefresh);
                // 使用统一的渲染函数(包含v2.0功能:CDK标记、库存显示、CDK管理按钮等)
                this._renderShopMyWithData();
            }

            _bindShopMyEvents(body) {
                // Tab 切换(带防抖和刷新逻辑)
                body.querySelectorAll('.ldsp-shop-tab').forEach(tab => {
                    tab.addEventListener('click', () => {
                        // 防抖:300ms 内重复点击忽略
                        if (this._shopTabClickDebounce) {
                            clearTimeout(this._shopTabClickDebounce);
                        }
                        this._shopTabClickDebounce = setTimeout(() => {
                            this._shopTabClickDebounce = null;
                        }, 300);
                        
                        const targetView = tab.dataset.view;
                        const isCurrentView = this._shopView === 'my' && targetView === 'my';
                        
                        if (targetView === 'list') {
                            // 切换到广场
                            this._shopCategory = '';
                            this._renderShop();
                        } else if (targetView === 'orders') {
                            // 切换到订单
                            this._renderShopOrders();
                        } else if (targetView === 'settings') {
                            // 切换到设置
                            this._renderMerchantSettings();
                        } else if (isCurrentView) {
                            // 点击当前所在的我的Tab,触发刷新
                            this._refreshShopMy();
                        }
                    });
                });
                
                // 发布按钮
                body.querySelector('.ldsp-shop-add-btn')?.addEventListener('click', () => {
                    this._shopEditProduct = null;
                    this._renderShopForm();
                });
                
                // 编辑/上下架按钮
                body.querySelectorAll('.ldsp-shop-my-card-btn').forEach(btn => {
                    btn.addEventListener('click', async (e) => {
                        e.stopPropagation();
                        const id = parseInt(btn.dataset.id);
                        const action = btn.dataset.action;
                        const product = this._shopMyProducts.find(p => p.id === id);
                        
                        if (action === 'edit' && product) {
                            this._shopEditProduct = product;
                            this._renderShopForm();
                        } else if (action === 'toggle' && product) {
                            btn.disabled = true;
                            const originalText = btn.textContent;
                            btn.textContent = '处理中...';
                            const isApproved = product.status === 'approved';
                            const resp = await this._toggleProductStatus(id, !isApproved, product);
                            if (resp?.success) {
                                LDSPDialog.success('操作成功');
                                this._invalidateShopCache();
                                this._renderShopMy();
                            } else {
                                btn.disabled = false;
                                btn.textContent = originalText;
                                LDSPDialog.error(this._formatError(resp));
                            }
                        } else if (action === 'manage-cdk' && product) {
                            // v2.0: CDK 库存管理
                            this._shopEditProduct = product;
                            this._renderCdkManager(product.id);
                        } else if (action === 'delete' && product) {
                            const confirmed = await LDSPDialog.confirm(`确定要删除「${Utils.escapeHtml(product.name)}」吗?<br>此操作不可恢复。`, { title: '删除确认', icon: '🗑️', danger: true });
                            if (confirmed) {
                                btn.disabled = true;
                                const originalText = btn.textContent;
                                btn.textContent = '删除中...';
                                const resp = await this._deleteMyProduct(id);
                                if (resp?.success) {
                                    LDSPDialog.success('物品已删除');
                                    this._invalidateShopCache();
                                    this._renderShopMy();
                                } else {
                                    btn.disabled = false;
                                    btn.textContent = originalText;
                                    LDSPDialog.error(this._formatError(resp));
                                }
                            }
                        }
                    });
                });
            }

            _renderShopForm() {
                const body = this._getShopBody();
                const isEdit = !!this._shopEditProduct;
                const p = this._shopEditProduct || {};
                const categories = this._shopCategories;
                
                // 商品类型:link (默认,外部支付链接) 或 cdk (平台内支付 + 自动发货)
                const productType = p.product_type || 'link';
                const isLinkType = productType === 'link';
                
                const categoryOptions = categories.map(c => 
                    `<option value="${c.id}"${p.category_id === c.id ? ' selected' : ''}>${c.icon || '📦'} ${c.name}</option>`
                ).join('');

                body.innerHTML = `
                    <div class="ldsp-shop-form">
                        <div class="ldsp-shop-form-header">
                            <button class="ldsp-shop-back-btn">← 返回</button>
                            <div class="ldsp-shop-form-title">${isEdit ? '编辑物品' : '发布物品'}</div>
                        </div>
                        <div class="ldsp-shop-form-group">
                            <label class="ldsp-shop-form-label">物品名称 <span class="required">*</span></label>
                            <input type="text" class="ldsp-shop-form-input" id="shop-name" placeholder="请输入物品名称" value="${Utils.escapeHtml(p.name || '')}" maxlength="100">
                        </div>
                        <div class="ldsp-shop-form-group">
                            <label class="ldsp-shop-form-label">物品分类 <span class="required">*</span></label>
                            <select class="ldsp-shop-form-select" id="shop-category">${categoryOptions}</select>
                        </div>
                        <div class="ldsp-shop-form-group">
                            <label class="ldsp-shop-form-label">物品描述 <span class="required">*</span></label>
                            <textarea class="ldsp-shop-form-input ldsp-shop-form-textarea" id="shop-desc" placeholder="详细描述您的物品信息以及服务方式..." maxlength="1000">${Utils.escapeHtml(p.description || '')}</textarea>
                        </div>
                        <div class="ldsp-shop-form-row">
                            <div class="ldsp-shop-form-group">
                                <label class="ldsp-shop-form-label">价格 (LDC) <span class="required">*</span></label>
                                <input type="number" class="ldsp-shop-form-input" id="shop-price" placeholder="0.00" min="0" step="0.01" value="${p.price || ''}">
                            </div>
                            <div class="ldsp-shop-form-group">
                                <label class="ldsp-shop-form-label">折扣</label>
                                <input type="number" class="ldsp-shop-form-input" id="shop-discount" placeholder="1 (无折扣)" min="0" max="1" step="0.01" value="${p.discount !== undefined && p.discount !== 1 ? p.discount : ''}">
                                <span class="ldsp-shop-form-hint">0-1之间,如 0.8 表示八折</span>
                            </div>
                        </div>
                        ${!isEdit ? `
                        <div class="ldsp-shop-form-group">
                            <label class="ldsp-shop-form-label">物品类型 <span class="required">*</span></label>
                            <div class="ldsp-shop-form-types">
                                <label class="ldsp-shop-type-option${isLinkType ? ' active' : ''}">
                                    <input type="radio" name="shop-type" value="link" ${isLinkType ? 'checked' : ''}>
                                    <span class="ldsp-shop-type-icon">🔗</span>
                                    <span class="ldsp-shop-type-name">链接类型</span>
                                    <span class="ldsp-shop-type-desc">提供外部支付链接</span>
                                </label>
                                <label class="ldsp-shop-type-option${!isLinkType ? ' active' : ''}">
                                    <input type="radio" name="shop-type" value="cdk" ${!isLinkType ? 'checked' : ''}>
                                    <span class="ldsp-shop-type-icon">🎫</span>
                                    <span class="ldsp-shop-type-name">CDK 类型</span>
                                    <span class="ldsp-shop-type-desc">平台内支付+自动发货</span>
                                </label>
                            </div>
                        </div>` : ''}
                        <div class="ldsp-shop-form-group ldsp-shop-type-link${isLinkType ? '' : ' hidden'}">
                            <label class="ldsp-shop-form-label">积分流转链接 <span class="required">*</span></label>
                            <input type="url" class="ldsp-shop-form-input" id="shop-payment-url" placeholder="https://credit.linux.do/paying/..." value="${Utils.escapeHtml(p.payment_link || '')}">
                            <span class="ldsp-shop-form-hint ldsp-shop-form-hint-selectable">LDC积分流转链接,获取可参照:<a href="https://linux.do/t/topic/1356124" target="_blank" rel="noopener">创建自己的积分流转链接</a></span>
                        </div>
                        <div class="ldsp-shop-form-group ldsp-shop-type-cdk${!isLinkType ? '' : ' hidden'}">
                            <label class="ldsp-shop-form-label">CDK 卡密 <span class="ldsp-shop-form-optional">(可选)</span></label>
                            <textarea class="ldsp-shop-form-input ldsp-shop-form-textarea" id="shop-cdk-codes" placeholder="每行一个 CDK,支持批量添加&#10;物品发布后也可在「我的物品」中管理 CDK 库存" rows="4"></textarea>
                            <span class="ldsp-shop-form-hint">💡 发布 CDK 物品前请先在「收款设置」中配置 LDC 收款信息</span>
                        </div>
                        <div class="ldsp-shop-form-group">
                            <label class="ldsp-shop-form-label">物品图片 (可选)</label>
                            <input type="url" class="ldsp-shop-form-input" id="shop-image" placeholder="https://..." value="${Utils.escapeHtml(p.image_url || '')}">
                            <span class="ldsp-shop-form-hint">图片URL地址,建议使用 https 链接</span>
                        </div>
                        <div class="ldsp-shop-form-actions">
                            <button class="ldsp-shop-form-btn secondary" data-action="cancel">取消</button>
                            <button class="ldsp-shop-form-btn primary" data-action="submit">${isEdit ? '保存修改' : '发布物品'}</button>
                        </div>
                    </div>`;

                this._bindShopFormEvents(body, isEdit);
            }

            _bindShopFormEvents(body, isEdit) {
                // 返回/取消按钮
                body.querySelector('.ldsp-shop-back-btn')?.addEventListener('click', () => {
                    isEdit ? this._renderShopMy() : this._renderShopList();
                });
                body.querySelector('[data-action="cancel"]')?.addEventListener('click', () => {
                    isEdit ? this._renderShopMy() : this._renderShopList();
                });
                
                // 商品类型切换(仅新建时可选)
                body.querySelectorAll('input[name="shop-type"]').forEach(radio => {
                    radio.addEventListener('change', () => {
                        const type = radio.value;
                        body.querySelectorAll('.ldsp-shop-type-option').forEach(opt => {
                            opt.classList.toggle('active', opt.querySelector('input').value === type);
                        });
                        body.querySelector('.ldsp-shop-type-link')?.classList.toggle('hidden', type !== 'link');
                        body.querySelector('.ldsp-shop-type-cdk')?.classList.toggle('hidden', type !== 'cdk');
                        // 更新提交按钮文字
                        const submitBtn = body.querySelector('[data-action="submit"]');
                        if (submitBtn && !submitBtn.disabled) {
                            submitBtn.textContent = type === 'cdk' ? '发布并上传CDK' : '发布物品';
                        }
                    });
                });
                
                // 提交按钮
                body.querySelector('[data-action="submit"]')?.addEventListener('click', async () => {
                    // 防重复提交
                    if (this._shopSubmitting) {
                        return;
                    }
                    
                    const name = body.querySelector('#shop-name')?.value?.trim();
                    const categoryIdRaw = body.querySelector('#shop-category')?.value;
                    const categoryId = categoryIdRaw ? parseInt(categoryIdRaw, 10) : null;
                    const description = body.querySelector('#shop-desc')?.value?.trim();
                    const price = parseFloat(body.querySelector('#shop-price')?.value) || 0;
                    const discount = parseFloat(body.querySelector('#shop-discount')?.value) || 1;
                    const imageUrl = body.querySelector('#shop-image')?.value?.trim();
                    
                    // v2.0: 商品类型
                    const productType = isEdit ? (this._shopEditProduct?.product_type || 'link') : 
                                        (body.querySelector('input[name="shop-type"]:checked')?.value || 'link');
                    const paymentLink = body.querySelector('#shop-payment-url')?.value?.trim();

                    // 增强验证(与服务端保持一致)
                    if (!name) { LDSPDialog.warning('请输入物品名称'); return; }
                    if (name.length < 2 || name.length > 50) { LDSPDialog.warning('物品名称需要2-50个字符'); return; }
                    if (!categoryId || isNaN(categoryId)) { LDSPDialog.warning('请选择物品分类'); return; }
                    if (!description) { LDSPDialog.warning('请输入物品描述'); return; }
                    if (description.length < 10 || description.length > 1000) { LDSPDialog.warning('物品描述需要10-1000个字符'); return; }
                    if (price <= 0 || price > 99999999) { LDSPDialog.warning('请输入有效价格(0-99999999)'); return; }
                    if (discount < 0.01 || discount > 1) { LDSPDialog.warning('折扣范围为0.01-1'); return; }
                    
                    // 根据商品类型验证
                    if (productType === 'link') {
                        if (!paymentLink) { LDSPDialog.warning('请输入积分流转链接'); return; }
                        if (!paymentLink.startsWith('https://credit.linux.do/')) {
                            LDSPDialog.warning('积分流转链接必须是 credit.linux.do 的链接'); return;
                        }
                    }
                    
                    // 图片URL验证(可选字段)
                    if (imageUrl && !imageUrl.startsWith('https://')) {
                        LDSPDialog.warning('图片链接请使用 https 开头的安全链接'); return;
                    }

                    // v2.0: CDK 商品可以直接填入 CDK
                    const cdkCodes = body.querySelector('#shop-cdk-codes')?.value?.trim();
                    
                    const data = { 
                        name, categoryId, description, price, discount, imageUrl,
                        productType,
                        paymentLink: productType === 'link' ? paymentLink : undefined,
                        cdkCodes: productType === 'cdk' && cdkCodes ? cdkCodes : undefined
                    };
                    
                    const btn = body.querySelector('[data-action="submit"]');
                    btn.disabled = true;
                    btn.textContent = productType === 'cdk' && cdkCodes ? '发布并上传CDK...' : '提交中...';
                    this._shopSubmitting = true;

                    let resp;
                    if (isEdit) {
                        resp = await this._updateProduct(this._shopEditProduct.id, data);
                    } else {
                        resp = await this._createProduct(data);
                    }

                    this._shopSubmitting = false;
                    if (resp?.success) {
                        this._shopEditProduct = null;
                        this._invalidateShopCache(); // 清除缓存
                        // 显示成功提示
                        const cdkInfo = resp.data?.cdkImported ? `<br>已导入 ${resp.data.cdkImported} 条 CDK` : '';
                        LDSPDialog.success(`${isEdit ? '物品已更新' : '物品提交成功,等待管理员审核'}${cdkInfo}`);
                        this._renderShopMy();
                    } else {
                        btn.disabled = false;
                        const pType = body.querySelector('input[name="shop-type"]:checked')?.value || 'link';
                        btn.textContent = isEdit ? '保存修改' : (pType === 'cdk' ? '发布并上传CDK' : '发布物品');
                        LDSPDialog.error(this._formatError(resp));
                    }
                });
            }

            async _showProductDetail(product) {
                this._shopProduct = product;
                this._shopView = 'detail';
                
                // 记录浏览量(通过获取详情 API 自动记录)
                this._recordProductView(product.id);
                
                const body = this._getShopBody();
                const categories = this._shopCategories;
                const cat = categories.find(c => c.id === product.category_id);
                const catIcon = product.category_icon || cat?.icon || '📦';
                const catName = product.category_name || cat?.name || '其他';
                
                const price = parseFloat(product.price) || 0;
                const discount = parseFloat(product.discount) || 1;
                const finalPrice = (price * discount).toFixed(2);
                const hasDiscount = discount < 1;
                const hasImg = product.image_url && product.image_url.trim();
                
                // v2.0: 商品类型
                const productType = product.product_type || 'link';
                const isCdk = productType === 'cdk';
                const isStore = productType === 'store';
                const stock = parseInt(product.stock) || 0;
                const soldCount = parseInt(product.sold_count) || 0;
                const availableStock = product.availableStock !== undefined ? product.availableStock : stock;
                // 总库存应使用实际 CDK 数量(cdkStats.total),而不是初始库存值
                const totalStock = product.cdkStats?.total || stock;
                const canPurchase = product.canPurchase !== false;
                const isOutOfStock = isCdk && stock !== -1 && availableStock <= 0;
                
                // 格式化时间
                const createdAt = product.created_at ? new Date(product.created_at).toLocaleString('zh-CN') : '—';
                const _updatedAt = product.updated_at ? new Date(product.updated_at).toLocaleString('zh-CN') : createdAt;

                // 格式化卖家头像
                const sellerAvatar = this._formatAvatar(product.seller_avatar, product.seller_username);
                
                // 无图片时的占位背景
                const bgColor = this._getRandomLightColor(product.id);
                
                // 小店类型使用独立详情页
                if (isStore) {
                    const storeOwner = product.seller_username || product.seller_name || '店主';
                    body.innerHTML = `
                        <div class="ldsp-shop-detail store-detail">
                            <div class="ldsp-shop-detail-header">
                                <button class="ldsp-shop-back-btn">←</button>
                                <span class="ldsp-shop-detail-category">🏪 小店</span>
                                <span class="ldsp-shop-detail-type-badge store">🏬 友情小店</span>
                            </div>
                            ${hasImg ? 
                                `<div class="ldsp-shop-detail-img-wrap"><img class="ldsp-shop-detail-img" src="${Utils.escapeHtml(product.image_url)}" alt="" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'"><div class="ldsp-shop-detail-placeholder" style="display:none;background:${bgColor}">🏪</div></div>` : 
                                `<div class="ldsp-shop-detail-placeholder" style="background:${bgColor}">🏪</div>`
                            }
                            <div class="ldsp-shop-detail-content">
                                <div class="ldsp-shop-detail-name">${Utils.escapeHtml(product.name)}</div>
                                <div class="ldsp-shop-detail-store-owner">👤 店主:<span>${Utils.escapeHtml(storeOwner)}</span></div>
                                <div class="ldsp-shop-detail-desc store-desc">${Utils.escapeHtml(product.description || '暂无描述')}</div>
                            </div>
                            <div class="ldsp-shop-detail-info">
                                <span class="ldsp-shop-detail-info-item">👁 ${product.view_count || 0}</span>
                                <span class="ldsp-shop-detail-info-item">📅 ${this._formatRelativeTime(product.updated_at || product.created_at)}</span>
                            </div>
                            <a href="${Utils.escapeHtml(product.payment_link || '')}" target="_blank" class="ldsp-shop-purchase-action" rel="noopener" style="background:linear-gradient(135deg,#06b6d4,#0891b2)">🏪 立即前往</a>
                        </div>`;
                    this._bindProductDetailEvents(body);
                    return;
                }
                
                // v2.0: 根据商品类型生成购买按钮
                let purchaseButton = '';
                if (isCdk) {
                    if (isOutOfStock) {
                        purchaseButton = `<button class="ldsp-shop-purchase-action" disabled style="opacity:.5;cursor:not-allowed;background:#999!important">😢 已售罄</button>`;
                    } else if (!canPurchase) {
                        purchaseButton = `<button class="ldsp-shop-purchase-action" disabled style="opacity:.5;cursor:not-allowed;background:#999!important">🚫 暂停销售</button>`;
                    } else {
                        purchaseButton = `<button class="ldsp-shop-purchase-action" data-action="buy-cdk" data-product-id="${product.id}">🛒 立即兑换 (${finalPrice} LDC)</button>`;
                    }
                } else {
                    purchaseButton = `<a href="${Utils.escapeHtml(product.payment_link || '')}" target="_blank" class="ldsp-shop-purchase-action" rel="noopener">🛒 立即兑换</a>`;
                }
                
                body.innerHTML = `
                    <div class="ldsp-shop-detail">
                        <div class="ldsp-shop-detail-header">
                            <button class="ldsp-shop-back-btn">←</button>
                            <span class="ldsp-shop-detail-category">${catIcon} ${Utils.escapeHtml(catName)}</span>
                            ${isCdk ? '<span class="ldsp-shop-detail-type-badge cdk">🎫 CDK自动发货</span>' : ''}
                        </div>
                        ${hasImg ? 
                            `<div class="ldsp-shop-detail-img-wrap"><img class="ldsp-shop-detail-img" src="${Utils.escapeHtml(product.image_url)}" alt="" onerror="this.style.display='none';this.nextElementSibling.style.display='flex'"><div class="ldsp-shop-detail-placeholder" style="display:none;background:${bgColor}">${catIcon}</div></div>` : 
                            `<div class="ldsp-shop-detail-placeholder" style="background:${bgColor}">${catIcon}</div>`
                        }
                        <div class="ldsp-shop-detail-content">
                            <div class="ldsp-shop-detail-name">${Utils.escapeHtml(product.name)}</div>
                            <div class="ldsp-shop-detail-desc">${Utils.escapeHtml(product.description || '暂无描述')}</div>
                        </div>
                        <div class="ldsp-shop-detail-price-row">
                            <div class="ldsp-shop-detail-price${hasDiscount ? ' discounted' : ''}">${finalPrice} <span>LDC</span></div>
                            ${hasDiscount ? `<div class="ldsp-shop-detail-original">${price.toFixed(2)}</div>` : ''}
                            ${hasDiscount ? `<div class="ldsp-shop-detail-discount-badge">-${Math.round((1 - discount) * 100)}%</div>` : ''}
                        </div>
                        <div class="ldsp-shop-detail-info">
                            <span class="ldsp-shop-detail-info-item">👁 ${product.view_count || 0}</span>
                            ${isCdk ? `<span class="ldsp-shop-detail-info-item stock${isOutOfStock ? ' low' : ''}">📦 <span class="available">${stock === -1 ? '∞' : availableStock}</span>/<span class="total">${stock === -1 ? '∞' : totalStock}</span></span>` : ''}
                            ${isCdk && soldCount > 0 ? `<span class="ldsp-shop-detail-info-item sold">🔥 已售${soldCount}</span>` : ''}
                            <span class="ldsp-shop-detail-info-item">📅 ${this._formatRelativeTime(product.updated_at || product.created_at)}</span>
                        </div>
                        <div class="ldsp-shop-seller" data-username="${Utils.escapeHtml(product.seller_username || '')}">
                            <img class="ldsp-shop-seller-avatar" src="${sellerAvatar}" alt="" onerror="this.style.display='none'">
                            <div class="ldsp-shop-seller-info">
                                <div class="ldsp-shop-seller-name">@${Utils.escapeHtml(product.seller_username || '未知')}</div>
                                <div class="ldsp-shop-seller-label">立即联系提供者 📧</div>
                            </div>
                            <span class="ldsp-shop-seller-arrow">→</span>
                        </div>
                        ${purchaseButton}
                    </div>`;

                this._bindProductDetailEvents(body);
            }

            _bindProductDetailEvents(body) {
                // 返回按钮
                body.querySelector('.ldsp-shop-back-btn')?.addEventListener('click', () => {
                    this._shopProduct = null;
                    this._renderShopList();
                    // 恢复滚动位置
                    if (this._shopListScrollTop) {
                        requestAnimationFrame(() => {
                            const newBody = this._getShopBody();
                            const shopContainer = newBody?.querySelector('.ldsp-shop');
                            if (shopContainer) {
                                shopContainer.scrollTop = this._shopListScrollTop;
                            } else if (newBody) {
                                newBody.scrollTop = this._shopListScrollTop;
                            }
                        });
                    }
                });
                
                // 卖家点击
                body.querySelector('.ldsp-shop-seller')?.addEventListener('click', () => {
                    const username = body.querySelector('.ldsp-shop-seller')?.dataset?.username;
                    if (username) {
                        window.open(`https://linux.do/u/${username}`, '_blank');
                    }
                });
                
                // v2.0: CDK 购买按钮
                body.querySelector('[data-action="buy-cdk"]')?.addEventListener('click', async (e) => {
                    const btn = e.target;
                    const productId = parseInt(btn.dataset.productId);
                    if (!productId) return;
                    
                    // 确认购买
                    const product = this._shopProduct;
                    if (!product) return;
                    
                    const price = parseFloat(product.price) || 0;
                    const discount = parseFloat(product.discount) || 1;
                    const finalPrice = (price * discount).toFixed(2);
                    
                    const confirmed = await LDSPDialog.confirm(`确认兑换「${Utils.escapeHtml(product.name)}」?<br><br>💰 价格:<strong>${finalPrice} LDC</strong><br><br>支付后系统将自动发放 CDK 到您的订单中。`, { title: '确认兑换', icon: '🛒' });
                    if (!confirmed) return;
                    
                    btn.disabled = true;
                    btn.textContent = '创建订单中...';
                    
                    try {
                        const resp = await this._createOrder(productId, 1);
                        if (resp?.success && resp.data?.paymentUrl) {
                            // 跳转支付页面
                            window.open(resp.data.paymentUrl, '_blank');
                            btn.textContent = '已跳转支付页面';
                            btn.style.background = '#22c55e';
                            
                            // 计算有效期提示
                            let expireHint = '';
                            if (resp.data.expireAt) {
                                expireHint = '<br>⏰ 订单有效期 <strong>1小时</strong>,请尽快完成支付';
                            }
                            
                            // 提示用户
                            setTimeout(() => {
                                LDSPDialog.alert(`订单已创建:<strong>${resp.data.orderNo}</strong><br><br>📝 请在新窗口中完成支付${expireHint}<br>✅ 支付完成后 CDK 将自动发放<br>📋 可在「我的订单」中查看状态`, { title: '订单创建成功', icon: '🎉' });
                            }, 500);
                        } else {
                            btn.disabled = false;
                            btn.textContent = `🛒 立即兑换 (${finalPrice} LDC)`;
                            LDSPDialog.error(this._formatError(resp));
                        }
                    } catch (error) {
                        btn.disabled = false;
                        btn.textContent = `🛒 立即兑换 (${finalPrice} LDC)`;
                        LDSPDialog.error('创建订单失败: ' + error.message);
                    }
                });
            }
            
            // v2.0: 创建订单 API
            async _createOrder(productId, quantity = 1) {
                return new Promise(resolve => {
                    GM_xmlhttpRequest({
                        method: 'POST',
                        url: `${this._apiUrl}/api/shop/orders`,
                        headers: {
                            'Content-Type': 'application/json',
                            'Authorization': `Bearer ${this._token}`
                        },
                        data: JSON.stringify({ productId, quantity }),
                        onload: r => {
                            try { resolve(JSON.parse(r.responseText)); }
                            catch { resolve({ success: false, error: '解析响应失败' }); }
                        },
                        onerror: () => resolve({ success: false, error: '网络错误' }),
                        ontimeout: () => resolve({ success: false, error: '请求超时' })
                    });
                });
            }

            // ==================== v2.0: 我的订单功能 ====================
            // 获取我的订单列表
            async _fetchMyOrders(role = 'buyer', status = '') {
                return new Promise(resolve => {
                    let url = `${this._apiUrl}/api/shop/orders?role=${role}`;
                    if (status) url += `&status=${status}`;
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url,
                        headers: { 'Authorization': `Bearer ${this._token}` },
                        onload: r => {
                            try {
                                const resp = JSON.parse(r.responseText);
                                resolve(resp?.data?.orders || []);
                            } catch { resolve([]); }
                        },
                        onerror: () => resolve([]),
                        ontimeout: () => resolve([])
                    });
                });
            }

            // 获取订单详情
            async _fetchOrderDetail(orderNo, role = 'buyer') {
                return new Promise(resolve => {
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url: `${this._apiUrl}/api/shop/orders/${orderNo}?role=${role}`,
                        headers: { 'Authorization': `Bearer ${this._token}` },
                        onload: r => {
                            try { resolve(JSON.parse(r.responseText)); }
                            catch { resolve({ success: false, error: '解析响应失败' }); }
                        },
                        onerror: () => resolve({ success: false, error: '网络错误' }),
                        ontimeout: () => resolve({ success: false, error: '请求超时' })
                    });
                });
            }

            // 取消订单
            async _cancelOrder(orderNo) {
                return new Promise(resolve => {
                    GM_xmlhttpRequest({
                        method: 'POST',
                        url: `${this._apiUrl}/api/shop/orders/${orderNo}/cancel`,
                        headers: {
                            'Content-Type': 'application/json',
                            'Authorization': `Bearer ${this._token}`
                        },
                        onload: r => {
                            try { resolve(JSON.parse(r.responseText)); }
                            catch { resolve({ success: false, error: '解析响应失败' }); }
                        },
                        onerror: () => resolve({ success: false, error: '网络错误' }),
                        ontimeout: () => resolve({ success: false, error: '请求超时' })
                    });
                });
            }

            // 刷新订单支付状态(主动查询 LDC)
            async _refreshOrderStatus(orderNo) {
                return new Promise(resolve => {
                    GM_xmlhttpRequest({
                        method: 'POST',
                        url: `${this._apiUrl}/api/shop/orders/${orderNo}/refresh`,
                        headers: {
                            'Content-Type': 'application/json',
                            'Authorization': `Bearer ${this._token}`
                        },
                        onload: r => {
                            try { resolve(JSON.parse(r.responseText)); }
                            catch { resolve({ success: false, error: '解析响应失败' }); }
                        },
                        onerror: () => resolve({ success: false, error: '网络错误' }),
                        ontimeout: () => resolve({ success: false, error: '请求超时' })
                    });
                });
            }

            // 手动发货 (卖家)
            async _deliverOrder(orderNo, content) {
                return new Promise(resolve => {
                    GM_xmlhttpRequest({
                        method: 'POST',
                        url: `${this._apiUrl}/api/shop/orders/${orderNo}/deliver`,
                        headers: {
                            'Content-Type': 'application/json',
                            'Authorization': `Bearer ${this._token}`
                        },
                        data: JSON.stringify({ content }),
                        onload: r => {
                            try { resolve(JSON.parse(r.responseText)); }
                            catch { resolve({ success: false, error: '解析响应失败' }); }
                        },
                        onerror: () => resolve({ success: false, error: '网络错误' }),
                        ontimeout: () => resolve({ success: false, error: '请求超时' })
                    });
                });
            }

            // 渲染我的订单页面
            async _renderShopOrders() {
                const body = this._getShopBody();
                body.innerHTML = `<div class="ldsp-ldc-loading"><div class="ldsp-spinner"></div><div>加载中...</div></div>`;
                
                this._shopView = 'orders';
                this._shopOrderRole = this._shopOrderRole || 'buyer';
                this._shopOrders = await this._fetchMyOrders(this._shopOrderRole);
                
                this._renderShopOrdersUI();
            }

            // 渲染订单列表 UI
            _renderShopOrdersUI() {
                const body = this._getShopBody();
                const orders = this._shopOrders || [];
                const role = this._shopOrderRole || 'buyer';
                
                const statusMap = {
                    'pending': { text: '待支付', color: '#f59e0b', icon: '⏳' },
                    'paying': { text: '支付中', color: '#3b82f6', icon: '💳' },
                    'paid': { text: '待发货', color: '#f97316', icon: '📦' },
                    'delivered': { text: '已发货', color: '#22c55e', icon: '✅' },
                    'refunded': { text: '已退款', color: '#8b5cf6', icon: '↩️' },
                    'expired': { text: '已过期', color: '#6b7280', icon: '⌛' },
                    'cancelled': { text: '已取消', color: '#ef4444', icon: '❌' }
                };

                const orderList = orders.length > 0 ? orders.map(o => {
                    const status = statusMap[o.status] || { text: o.status, color: '#6b7280', icon: '📋' };
                    const product = o.product || {};
                    const createTime = this._formatRelativeTime(new Date(o.created_at).getTime());
                    
                    // 计算待支付订单剩余有效期
                    let expireHint = '';
                    if (o.status === 'pending' && o.pay_expired_at) {
                        const expireTime = new Date(o.pay_expired_at.replace(' ', 'T') + '+08:00').getTime();
                        const remaining = expireTime - Date.now();
                        if (remaining > 0) {
                            const mins = Math.floor(remaining / 60000);
                            expireHint = `<span class="ldsp-order-expire" style="font-size:10px;color:#f59e0b;margin-left:6px">⏰ ${mins}分钟后过期</span>`;
                        } else {
                            expireHint = `<span class="ldsp-order-expire" style="font-size:10px;color:#ef4444;margin-left:6px">⚠️ 即将过期</span>`;
                        }
                    }
                    
                    return `<div class="ldsp-order-card" data-order-no="${o.order_no}">
                        <div class="ldsp-order-card-header">
                            <span class="ldsp-order-no">订单号: ${o.order_no}</span>
                            <span class="ldsp-order-status" style="color:${status.color}">${status.icon} ${status.text}${expireHint}</span>
                        </div>
                        <div class="ldsp-order-card-body">
                            <div class="ldsp-order-product">
                                <span class="ldsp-order-product-name">${Utils.escapeHtml(product.name || '物品')}</span>
                                <span class="ldsp-order-quantity">x${o.quantity || 1}</span>
                            </div>
                            <div class="ldsp-order-info">
                                <span>${role === 'buyer' ? '卖家' : '买家'}: ${Utils.escapeHtml(role === 'buyer' ? o.seller_username : o.buyer_username)}</span>
                                <span>${createTime}</span>
                            </div>
                            <div class="ldsp-order-footer">
                                <span class="ldsp-order-amount">💰 ${parseFloat(o.amount).toFixed(2)} LDC</span>
                                <div class="ldsp-order-actions">
                                    ${o.status === 'pending' && role === 'buyer' ? `<button class="ldsp-order-btn" data-action="cancel" data-order="${o.order_no}">取消</button>` : ''}
                                    ${o.status === 'paid' && role === 'seller' ? `<button class="ldsp-order-btn primary" data-action="deliver" data-order="${o.order_no}">发货</button>` : ''}
                                    ${o.status === 'delivered' && role === 'buyer' ? `<button class="ldsp-order-btn primary" data-action="view-cdk" data-order="${o.order_no}">查看CDK</button>` : ''}
                                    <button class="ldsp-order-btn" data-action="detail" data-order="${o.order_no}">详情</button>
                                </div>
                            </div>
                        </div>
                    </div>`;
                }).join('') : '';

                body.innerHTML = `
                    <div class="ldsp-shop">
                        <div class="ldsp-shop-header">
                            <div class="ldsp-shop-tabs">
                                <div class="ldsp-shop-tab" data-view="list">🏪 广场</div>
                                <div class="ldsp-shop-tab" data-view="my">📦 我的</div>
                                <div class="ldsp-shop-tab active" data-view="orders">📋 订单</div>
                                <div class="ldsp-shop-tab" data-view="settings">⚙️ 设置</div>
                            </div>
                        </div>
                        <div class="ldsp-order-role-tabs">
                            <div class="ldsp-order-role-tab${role === 'buyer' ? ' active' : ''}" data-role="buyer">🛒 我买的</div>
                            <div class="ldsp-order-role-tab${role === 'seller' ? ' active' : ''}" data-role="seller">📤 我卖的</div>
                        </div>
                        ${orders.length > 0 ? `<div class="ldsp-order-list">${orderList}</div>` : 
                        `<div class="ldsp-shop-empty"><div class="ldsp-shop-empty-icon">${role === 'buyer' ? '🛒' : '📤'}</div><div class="ldsp-shop-empty-text">${role === 'buyer' ? '您还没有兑换过物品' : '还没有人兑换您的物品'}</div></div>`}
                    </div>`;

                this._bindShopOrdersEvents(body);
            }

            // 绑定订单页面事件
            _bindShopOrdersEvents(body) {
                // Tab 切换
                body.querySelectorAll('.ldsp-shop-tab').forEach(tab => {
                    tab.addEventListener('click', async () => {
                        const targetView = tab.dataset.view;
                        if (targetView === 'list') {
                            this._shopCategory = '';
                            this._renderShop();
                        } else if (targetView === 'my') {
                            this._renderShopMy();
                        } else if (targetView === 'orders') {
                            // 点击当前所在的订单Tab,刷新列表
                            this._shopOrders = await this._fetchMyOrders(this._shopOrderRole);
                            this._renderShopOrdersUI();
                        } else if (targetView === 'settings') {
                            this._renderMerchantSettings();
                        }
                    });
                });

                // 买家/卖家角色切换
                body.querySelectorAll('.ldsp-order-role-tab').forEach(tab => {
                    tab.addEventListener('click', async () => {
                        const targetRole = tab.dataset.role;
                        if (targetRole !== this._shopOrderRole) {
                            this._shopOrderRole = targetRole;
                            body.innerHTML = `<div class="ldsp-ldc-loading"><div class="ldsp-spinner"></div><div>加载中...</div></div>`;
                            this._shopOrders = await this._fetchMyOrders(targetRole);
                            this._renderShopOrdersUI();
                        }
                    });
                });

                // 订单操作按钮
                body.querySelectorAll('.ldsp-order-btn').forEach(btn => {
                    btn.addEventListener('click', async (e) => {
                        e.stopPropagation();
                        const action = btn.dataset.action;
                        const orderNo = btn.dataset.order;

                        if (action === 'cancel') {
                            const confirmed = await LDSPDialog.confirm('确定要取消该订单吗?', { title: '取消订单', icon: '🚫', danger: true });
                            if (confirmed) {
                                btn.disabled = true;
                                btn.textContent = '取消中...';
                                const resp = await this._cancelOrder(orderNo);
                                if (resp?.success) {
                                    this._shopOrders = await this._fetchMyOrders(this._shopOrderRole);
                                    this._renderShopOrdersUI();
                                } else {
                                    btn.disabled = false;
                                    btn.textContent = '取消';
                                    LDSPDialog.error(this._formatError(resp));
                                }
                            }
                        } else if (action === 'deliver') {
                            this._showDeliverDialog(orderNo);
                        } else if (action === 'detail' || action === 'view-cdk') {
                            this._showOrderDetail(orderNo, action === 'view-cdk');
                        }
                    });
                });
            }

            // 显示发货对话框
            _showDeliverDialog(orderNo) {
                const dialog = document.createElement('div');
                dialog.className = 'ldsp-deliver-dialog-overlay';
                dialog.innerHTML = `
                    <div class="ldsp-deliver-dialog">
                        <div class="ldsp-deliver-dialog-header">
                            <span>📦 发货</span>
                            <button class="ldsp-deliver-dialog-close">×</button>
                        </div>
                        <div class="ldsp-deliver-dialog-body">
                            <div class="ldsp-deliver-dialog-tip">请输入需要发送给买家的内容(CDK、链接、密码等)</div>
                            <textarea class="ldsp-deliver-dialog-input" placeholder="输入发货内容..."></textarea>
                        </div>
                        <div class="ldsp-deliver-dialog-footer">
                            <button class="ldsp-deliver-dialog-btn cancel">取消</button>
                            <button class="ldsp-deliver-dialog-btn confirm">确认发货</button>
                        </div>
                    </div>
                `;
                document.body.appendChild(dialog);

                const close = () => dialog.remove();
                dialog.querySelector('.ldsp-deliver-dialog-close').addEventListener('click', close);
                dialog.querySelector('.ldsp-deliver-dialog-btn.cancel').addEventListener('click', close);
                dialog.addEventListener('click', (e) => { if (e.target === dialog) close(); });

                const confirmBtn = dialog.querySelector('.ldsp-deliver-dialog-btn.confirm');
                const textarea = dialog.querySelector('.ldsp-deliver-dialog-input');
                
                confirmBtn.addEventListener('click', async () => {
                    const content = textarea.value.trim();
                    if (!content) {
                        LDSPDialog.warning('请输入发货内容');
                        return;
                    }
                    confirmBtn.disabled = true;
                    confirmBtn.textContent = '发货中...';
                    const resp = await this._deliverOrder(orderNo, content);
                    if (resp?.success) {
                        close();
                        LDSPDialog.success('发货成功');
                        this._shopOrders = await this._fetchMyOrders(this._shopOrderRole);
                        this._renderShopOrdersUI();
                    } else {
                        confirmBtn.disabled = false;
                        confirmBtn.textContent = '确认发货';
                        LDSPDialog.error(this._formatError(resp));
                    }
                });

                textarea.focus();
            }

            // ========== 商户收款设置 ==========
            
            // 获取商户配置
            async _fetchMerchantConfig() {
                return new Promise(resolve => {
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url: `${this._apiUrl}/api/shop/merchant/config`,
                        headers: { 'Authorization': `Bearer ${this._token}` },
                        onload: r => {
                            try { resolve(JSON.parse(r.responseText)); }
                            catch { resolve({ success: false, error: '解析响应失败' }); }
                        },
                        onerror: () => resolve({ success: false, error: '网络错误' }),
                        ontimeout: () => resolve({ success: false, error: '请求超时' })
                    });
                });
            }

            // 保存商户配置
            async _saveMerchantConfig(ldcPid, ldcKey) {
                console.log('[LDC] Saving merchant config...', { ldcPid: ldcPid?.substring(0, 4) + '****', hasKey: !!ldcKey, apiUrl: this._apiUrl });
                // 对敏感数据进行 Base64 编码,避免 Cloudflare WAF 拦截
                const encodedKey = btoa(ldcKey);
                return new Promise(resolve => {
                    GM_xmlhttpRequest({
                        method: 'POST',
                        url: `${this._apiUrl}/api/shop/merchant/config`,
                        headers: {
                            'Content-Type': 'application/json',
                            'Authorization': `Bearer ${this._token}`
                        },
                        data: JSON.stringify({ ldcPid, ldcKeyEncoded: encodedKey }),
                        onload: r => {
                            try { 
                                resolve(JSON.parse(r.responseText)); 
                            } catch (e) { 
                                console.error('[LDC] Save merchant config parse error:', e, 'Status:', r.status, 'Response:', r.responseText?.substring(0, 500));
                                resolve({ success: false, error: `解析响应失败 (HTTP ${r.status})` }); 
                            }
                        },
                        onerror: (e) => {
                            console.error('[LDC] Save merchant config network error:', e);
                            resolve({ success: false, error: '网络错误' });
                        },
                        ontimeout: () => resolve({ success: false, error: '请求超时' })
                    });
                });
            }

            // 删除商户配置
            async _deleteMerchantConfig() {
                return new Promise(resolve => {
                    GM_xmlhttpRequest({
                        method: 'DELETE',
                        url: `${this._apiUrl}/api/shop/merchant/config`,
                        headers: { 'Authorization': `Bearer ${this._token}` },
                        onload: r => {
                            try { resolve(JSON.parse(r.responseText)); }
                            catch { resolve({ success: false, error: '解析响应失败' }); }
                        },
                        onerror: () => resolve({ success: false, error: '网络错误' }),
                        ontimeout: () => resolve({ success: false, error: '请求超时' })
                    });
                });
            }

            // 测试 LDC 回调配置
            async _testMerchantCallback() {
                return new Promise(resolve => {
                    GM_xmlhttpRequest({
                        method: 'POST',
                        url: `${this._apiUrl}/api/shop/merchant/test-callback`,
                        headers: { 'Authorization': `Bearer ${this._token}` },
                        onload: r => {
                            try { resolve(JSON.parse(r.responseText)); }
                            catch { resolve({ success: false, error: '解析响应失败' }); }
                        },
                        onerror: () => resolve({ success: false, error: '网络错误' }),
                        ontimeout: () => resolve({ success: false, error: '请求超时' })
                    });
                });
            }

            // 渲染商户收款设置页面
            async _renderMerchantSettings() {
                const body = this._getShopBody();
                body.innerHTML = `<div class="ldsp-ldc-loading"><div class="ldsp-spinner"></div><div>加载中...</div></div>`;
                
                const resp = await this._fetchMerchantConfig();
                // 解包嵌套的 data (后端返回 {success, data: {success, data: {...}}})
                const config = resp?.data?.data || resp?.data || {};
                const stats = config.stats || {};
                const isConfigured = !!config.configured;
                
                body.innerHTML = `
                    <div class="ldsp-shop">
                        <div class="ldsp-shop-header">
                            <div class="ldsp-shop-tabs">
                                <div class="ldsp-shop-tab" data-view="list">🏪 广场</div>
                                <div class="ldsp-shop-tab" data-view="my">📦 我的</div>
                                <div class="ldsp-shop-tab" data-view="orders">📋 订单</div>
                                <div class="ldsp-shop-tab active" data-view="settings">⚙️ 设置</div>
                            </div>
                        </div>
                        
                        ${isConfigured ? `
                        <div class="ldsp-merchant-stats-card">
                            <div class="ldsp-merchant-stats-title">📊 CDK分发收入统计</div>
                            <div class="ldsp-merchant-stats-grid">
                                <div class="ldsp-merchant-stat">
                                    <div class="ldsp-merchant-stat-value">${stats.totalOrders || 0}</div>
                                    <div class="ldsp-merchant-stat-label">总订单</div>
                                </div>
                                <div class="ldsp-merchant-stat">
                                    <div class="ldsp-merchant-stat-value">${parseFloat(stats.totalRevenue || 0).toFixed(2)}</div>
                                    <div class="ldsp-merchant-stat-label">总收入 (LDC)</div>
                                </div>
                                <div class="ldsp-merchant-stat">
                                    <div class="ldsp-merchant-stat-value">${stats.thisMonthOrders || 0}</div>
                                    <div class="ldsp-merchant-stat-label">本月订单</div>
                                </div>
                                <div class="ldsp-merchant-stat">
                                    <div class="ldsp-merchant-stat-value">${parseFloat(stats.thisMonthRevenue || 0).toFixed(2)}</div>
                                    <div class="ldsp-merchant-stat-label">本月收入 (LDC)</div>
                                </div>
                            </div>
                        </div>
                        ` : ''}
                        
                        <div class="ldsp-merchant-config-card">
                            <div class="ldsp-merchant-config-header">
                                <div class="ldsp-merchant-config-title">💳 LDC 收款配置</div>
                                ${isConfigured ? `
                                <div class="ldsp-merchant-config-status">
                                    <span class="ldsp-merchant-badge ${config.isVerified ? 'verified' : 'pending'}">${config.isVerified ? '✓ 已验证' : '⏳ 待验证'}</span>
                                    <span class="ldsp-merchant-badge ${config.isActive ? 'active' : 'inactive'}">${config.isActive ? '已启用' : '已禁用'}</span>
                                </div>
                                ` : ''}
                            </div>
                            
                            ${!isConfigured ? `
                            <div class="ldsp-merchant-config-tip">
                                💡 配置 LDC 收款后,您发布的 CDK 物品可支持平台内支付和自动发货。
                            </div>
                            ` : ''}
                            
                            <div class="ldsp-merchant-form-group">
                                <label class="ldsp-merchant-form-label">Client ID (PID)</label>
                                <input type="text" class="ldsp-merchant-form-input" id="merchant-pid" 
                                    value="${isConfigured ? Utils.escapeHtml(config.ldcPid || '') : ''}"
                                    placeholder="${isConfigured ? '' : '请输入您的 LDC Client ID'}"
                                    ${isConfigured ? 'disabled' : ''}>
                            </div>
                            <div class="ldsp-merchant-form-group">
                                <label class="ldsp-merchant-form-label">Client Key</label>
                                <input type="password" class="ldsp-merchant-form-input" id="merchant-key" 
                                    value="${isConfigured ? '••••••••••••••••' : ''}"
                                    placeholder="${isConfigured ? '' : '请输入您的 LDC Client Key'}"
                                    ${isConfigured ? 'disabled' : ''}>
                                <div class="ldsp-merchant-form-hint">${isConfigured ? '密钥已安全存储,修改时需重新输入' : '密钥将安全加密存储,不会明文显示'}</div>
                            </div>
                            
                            <div class="ldsp-merchant-form-actions">
                                ${isConfigured ? `
                                <button class="ldsp-merchant-edit-btn" id="merchant-edit-btn">✏️ 编辑配置</button>
                                <button class="ldsp-merchant-test-btn" id="merchant-test-btn">🔔 测试通知</button>
                                <button class="ldsp-merchant-save-btn" id="merchant-save-btn" style="display:none">💾 保存配置</button>
                                <button class="ldsp-merchant-cancel-btn" id="merchant-cancel-btn" style="display:none">取消</button>
                                <button class="ldsp-merchant-delete-btn">🗑️ 删除配置</button>
                                ` : `
                                <button class="ldsp-merchant-save-btn">💾 保存配置</button>
                                `}
                            </div>
                        </div>
                        
                        <div class="ldsp-merchant-help-card">
                            <div class="ldsp-merchant-help-title">❓ 如何获取 LDC 收款凭证</div>
                            <div class="ldsp-merchant-help-content">
                                <p>1. 访问 <a href="https://credit.linux.do/merchant" target="_blank" rel="noopener">LDC 集市</a></p>
                                <p>2. 创建新应用,配置以下地址:</p>
                                <p style="margin-top:6px">⚠️ <b>通知地址</b>(notify_url,服务器异步通知,必填):</p>
                                <p style="margin-left:12px;font-family:monospace;font-size:11px;color:#3b82f6;word-break:break-all">https://api.ldspro.qzz.io/api/shop/ldc/notify</p>
                                <p style="margin-top:6px">⚠️ <b>回调地址</b>(return_url,支付后浏览器跳转):</p>
                                <p style="margin-left:12px;font-family:monospace;font-size:11px;color:#3b82f6;word-break:break-all">https://api.ldspro.qzz.io/api/shop/ldc/return</p>
                                <p style="margin-top:8px">3. 在应用详情页获取 Client ID 和 Client Key</p>
                                <p>4. 填写到上方配置表单并保存</p>
                                <p style="margin-top:8px;font-size:11px;color:#94a3b8">💡 提示:<b style="color:#ef4444">通知地址</b>是支付成功后自动发货的关键,请务必正确配置</p>
                            </div>
                        </div>
                    </div>
                `;
                
                this._bindMerchantSettingsEvents(body, isConfigured, config);
            }

            // 绑定商户设置事件
            _bindMerchantSettingsEvents(body, isConfigured, config) {
                // Tab 切换
                body.querySelectorAll('.ldsp-shop-tab').forEach(tab => {
                    tab.addEventListener('click', () => {
                        const targetView = tab.dataset.view;
                        if (targetView === 'list') {
                            this._shopCategory = '';
                            this._renderShop();
                        } else if (targetView === 'my') {
                            this._renderShopMy();
                        } else if (targetView === 'orders') {
                            this._renderShopOrders();
                        }
                        // 点击当前设置Tab不做任何操作
                    });
                });
                
                // 编辑配置按钮(仅已配置时显示)
                const editBtn = body.querySelector('#merchant-edit-btn');
                const saveBtn = body.querySelector('#merchant-save-btn');
                const cancelBtn = body.querySelector('#merchant-cancel-btn');
                const pidInput = body.querySelector('#merchant-pid');
                const keyInput = body.querySelector('#merchant-key');
                
                editBtn?.addEventListener('click', () => {
                    // 启用输入框,切换按钮显示
                    pidInput.disabled = false;
                    keyInput.disabled = false;
                    keyInput.value = ''; // 清空密钥,需要重新输入
                    keyInput.placeholder = '请重新输入 Client Key';
                    editBtn.style.display = 'none';
                    saveBtn.style.display = '';
                    cancelBtn.style.display = '';
                    body.querySelector('.ldsp-merchant-delete-btn').style.display = 'none';
                    pidInput.focus();
                });
                
                // 取消编辑按钮
                cancelBtn?.addEventListener('click', () => {
                    // 恢复原状态
                    this._renderMerchantSettings();
                });
                
                // 保存配置
                body.querySelector('.ldsp-merchant-save-btn')?.addEventListener('click', async () => {
                    const pid = pidInput?.value?.trim();
                    const key = keyInput?.value?.trim();
                    
                    if (!pid || !key) {
                        LDSPDialog.warning('请填写完整的 Client ID 和 Client Key');
                        return;
                    }
                    
                    // 如果是编辑模式且密钥是占位符,需要重新输入
                    if (isConfigured && key === '••••••••••••••••') {
                        LDSPDialog.warning('请重新输入 Client Key');
                        keyInput.focus();
                        return;
                    }
                    
                    const btn = body.querySelector('.ldsp-merchant-save-btn');
                    btn.disabled = true;
                    btn.textContent = '验证中...';
                    
                    const resp = await this._saveMerchantConfig(pid, key);
                    if (resp?.success) {
                        // 检查是否有回调警告
                        const data = resp?.data || resp;
                        if (data.callbackWarning) {
                            LDSPDialog.warning(`配置已保存,但通知地址验证有警告:<br><br>${Utils.escapeHtml(data.callbackWarning)}<br><br>请确保 LDC 后台的通知地址设置为:<br><code style="font-size:11px;background:#333;padding:2px 6px;border-radius:3px">${Utils.escapeHtml(data.expectedNotifyUrl)}</code>`);
                        } else {
                            LDSPDialog.success('配置保存成功');
                        }
                        this._renderMerchantSettings();
                    } else {
                        btn.disabled = false;
                        btn.textContent = '💾 保存配置';
                        LDSPDialog.error(this._formatError(resp));
                    }
                });
                
                // 测试通知按钮
                body.querySelector('#merchant-test-btn')?.addEventListener('click', async () => {
                    const btn = body.querySelector('#merchant-test-btn');
                    btn.disabled = true;
                    btn.textContent = '测试中...';
                    
                    const resp = await this._testMerchantCallback();
                    btn.disabled = false;
                    btn.textContent = '🔔 测试通知';
                    
                    if (resp?.success) {
                        const data = resp?.data || resp;
                        const testData = data.data || data;
                        if (testData.status === 'ok') {
                            LDSPDialog.success(`✅ 通知测试成功!<br><br>您的通知地址配置正确:<br><code style="font-size:11px;background:#333;padding:2px 6px;border-radius:3px">${Utils.escapeHtml(testData.notifyUrl)}</code>`);
                        } else {
                            LDSPDialog.warning(`⚠️ ${Utils.escapeHtml(data.message || '通知测试完成')}<br><br>请确保 LDC 后台的通知地址设置正确:<br><code style="font-size:11px;background:#333;padding:2px 6px;border-radius:3px">${Utils.escapeHtml(testData.notifyUrl)}</code>${testData.hint ? '<br><br>💡 ' + Utils.escapeHtml(testData.hint) : ''}`);
                        }
                    } else {
                        LDSPDialog.error(this._formatError(resp));
                    }
                });

                // 删除配置
                body.querySelector('.ldsp-merchant-delete-btn')?.addEventListener('click', async () => {
                    const confirmed = await LDSPDialog.confirm('确定要删除收款配置吗?<br><br>删除后您的 CDK 物品将无法进行平台内支付。', { title: '删除配置', icon: '⚠️', danger: true });
                    if (!confirmed) return;
                    
                    const btn = body.querySelector('.ldsp-merchant-delete-btn');
                    btn.disabled = true;
                    btn.textContent = '删除中...';
                    
                    const resp = await this._deleteMerchantConfig();
                    if (resp?.success) {
                        LDSPDialog.success('配置已删除');
                        this._renderMerchantSettings();
                    } else {
                        btn.disabled = false;
                        btn.textContent = '🗑️ 删除配置';
                        LDSPDialog.error(this._formatError(resp));
                    }
                });
            }

            // ========== CDK 库存管理 ==========
            
            // 获取 CDK 列表
            async _fetchCdkList(productId, options = {}) {
                return new Promise(resolve => {
                    let url = `${this._apiUrl}/api/shop/products/${productId}/cdk?page=${options.page || 1}`;
                    if (options.status && options.status !== 'all') url += `&status=${options.status}`;
                    if (options.batchNo) url += `&batchNo=${options.batchNo}`;
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url,
                        headers: { 'Authorization': `Bearer ${this._token}` },
                        onload: r => {
                            try { resolve(JSON.parse(r.responseText)); }
                            catch { resolve({ success: false, error: '解析响应失败' }); }
                        },
                        onerror: () => resolve({ success: false, error: '网络错误' }),
                        ontimeout: () => resolve({ success: false, error: '请求超时' })
                    });
                });
            }

            // 上传 CDK
            async _uploadCdk(productId, codes, remark = '') {
                return new Promise(resolve => {
                    GM_xmlhttpRequest({
                        method: 'POST',
                        url: `${this._apiUrl}/api/shop/products/${productId}/cdk`,
                        headers: {
                            'Content-Type': 'application/json',
                            'Authorization': `Bearer ${this._token}`
                        },
                        data: JSON.stringify({ codes, remark }),
                        onload: r => {
                            try { resolve(JSON.parse(r.responseText)); }
                            catch { resolve({ success: false, error: '解析响应失败' }); }
                        },
                        onerror: () => resolve({ success: false, error: '网络错误' }),
                        ontimeout: () => resolve({ success: false, error: '请求超时' })
                    });
                });
            }

            // 删除 CDK
            async _deleteCdk(productId, cdkId) {
                return new Promise(resolve => {
                    GM_xmlhttpRequest({
                        method: 'DELETE',
                        url: `${this._apiUrl}/api/shop/products/${productId}/cdk/${cdkId}`,
                        headers: { 'Authorization': `Bearer ${this._token}` },
                        onload: r => {
                            try { resolve(JSON.parse(r.responseText)); }
                            catch { resolve({ success: false, error: '解析响应失败' }); }
                        },
                        onerror: () => resolve({ success: false, error: '网络错误' }),
                        ontimeout: () => resolve({ success: false, error: '请求超时' })
                    });
                });
            }

            // 渲染 CDK 管理页面
            async _renderCdkManager(productId) {
                const body = this._getShopBody();
                body.innerHTML = `<div class="ldsp-ldc-loading"><div class="ldsp-spinner"></div><div>加载中...</div></div>`;
                
                // 保留筛选和分页状态,除非是首次进入
                if (!this._cdkState || this._cdkState.productId !== productId) {
                    this._cdkState = { productId, page: 1, status: 'all' };
                }
                
                const resp = await this._fetchCdkList(productId, {
                    page: this._cdkState.page,
                    status: this._cdkState.status
                });
                if (!resp?.success) {
                    LDSPDialog.error(this._formatError(resp));
                    this._renderShopMy();
                    return;
                }
                
                const data = resp.data || {};
                const cdks = data.cdks || [];
                const stats = data.stats || {};
                const pagination = data.pagination || {};
                const product = this._shopEditProduct || {};
                const currentStatus = this._cdkState.status || 'all';
                
                body.innerHTML = `
                    <div class="ldsp-cdk-manager">
                        <div class="ldsp-cdk-manager-header">
                            <button class="ldsp-order-back-btn">← 返回</button>
                            <span>CDK 库存管理 - ${Utils.escapeHtml(product.name || '')}</span>
                        </div>
                        
                        <div class="ldsp-cdk-stats-grid">
                            <div class="ldsp-cdk-stat available">
                                <div class="ldsp-cdk-stat-value">${stats.available || 0}</div>
                                <div class="ldsp-cdk-stat-label">可用</div>
                            </div>
                            <div class="ldsp-cdk-stat locked">
                                <div class="ldsp-cdk-stat-value">${stats.locked || 0}</div>
                                <div class="ldsp-cdk-stat-label">锁定</div>
                            </div>
                            <div class="ldsp-cdk-stat sold">
                                <div class="ldsp-cdk-stat-value">${stats.sold || 0}</div>
                                <div class="ldsp-cdk-stat-label">已售</div>
                            </div>
                            <div class="ldsp-cdk-stat total">
                                <div class="ldsp-cdk-stat-value">${stats.total || 0}</div>
                                <div class="ldsp-cdk-stat-label">总计</div>
                            </div>
                        </div>
                        
                        <div class="ldsp-cdk-upload-section">
                            <div class="ldsp-cdk-upload-title">📤 批量上传 CDK</div>
                            <textarea class="ldsp-cdk-upload-input" id="cdk-upload-codes" placeholder="每行一个CDK,自动去重去空行"></textarea>
                            <div class="ldsp-cdk-upload-footer">
                                <input type="text" class="ldsp-cdk-upload-remark" id="cdk-upload-remark" placeholder="备注(可选)">
                                <button class="ldsp-cdk-upload-btn">上传</button>
                            </div>
                        </div>
                        
                        <div class="ldsp-shop-cdk-mgr-section">
                            <div class="ldsp-shop-cdk-mgr-header">
                                <span>📋 CDK 列表</span>
                                <div class="ldsp-shop-cdk-mgr-filter-wrap">
                                    <select class="ldsp-shop-cdk-mgr-filter" id="cdk-filter-status">
                                        <option value="all"${currentStatus === 'all' ? ' selected' : ''}>全部</option>
                                        <option value="available"${currentStatus === 'available' ? ' selected' : ''}>可用</option>
                                        <option value="locked"${currentStatus === 'locked' ? ' selected' : ''}>锁定</option>
                                        <option value="sold"${currentStatus === 'sold' ? ' selected' : ''}>已售</option>
                                    </select>
                                </div>
                            </div>
                            
                            ${cdks.length > 0 ? `
                            <div class="ldsp-shop-cdk-mgr-list">
                                ${cdks.map(c => `
                                    <div class="ldsp-shop-cdk-mgr-item" data-id="${c.id}">
                                        <div class="ldsp-shop-cdk-mgr-code" title="${Utils.escapeHtml(c.code)}">${Utils.escapeHtml(c.code)}</div>
                                        <div class="ldsp-shop-cdk-mgr-meta">
                                            <span class="ldsp-shop-cdk-mgr-status ${c.status}">${c.status === 'available' ? '✓ 可用' : c.status === 'locked' ? '⏳ 锁定' : '✗ 已售'}</span>
                                            ${c.remark ? `<span class="ldsp-shop-cdk-mgr-remark" title="${Utils.escapeHtml(c.remark)}">${Utils.escapeHtml(c.remark)}</span>` : ''}
                                        </div>
                                        <div class="ldsp-shop-cdk-mgr-actions">
                                            <button class="ldsp-shop-cdk-mgr-copy" data-code="${Utils.escapeHtml(c.code)}" title="复制CDK">📋</button>
                                            ${c.status === 'available' ? `<button class="ldsp-shop-cdk-mgr-delete" data-id="${c.id}" title="删除此CDK">×</button>` : ''}
                                        </div>
                                    </div>
                                `).join('')}
                            </div>
                            ${pagination.totalPages > 1 ? `
                            <div class="ldsp-shop-cdk-mgr-pagination">
                                <span>第 ${pagination.page} / ${pagination.totalPages} 页</span>
                                <div class="ldsp-shop-cdk-mgr-pagination-btns">
                                    <button class="ldsp-shop-cdk-mgr-page-btn" data-page="${pagination.page - 1}" ${pagination.page <= 1 ? 'disabled' : ''}>上一页</button>
                                    <button class="ldsp-shop-cdk-mgr-page-btn" data-page="${pagination.page + 1}" ${pagination.page >= pagination.totalPages ? 'disabled' : ''}>下一页</button>
                                </div>
                            </div>
                            ` : ''}
                            ` : `
                            <div class="ldsp-shop-cdk-mgr-empty">
                                <div class="ldsp-shop-cdk-mgr-empty-icon">📦</div>
                                <div class="ldsp-shop-cdk-mgr-empty-text">暂无 CDK</div>
                                <div class="ldsp-shop-cdk-mgr-empty-hint">在上方输入框中添加 CDK</div>
                            </div>
                            `}
                        </div>
                    </div>
                `;
                
                this._bindCdkManagerEvents(body, productId);
            }

            // 绑定 CDK 管理事件
            _bindCdkManagerEvents(body, productId) {
                // 返回按钮
                body.querySelector('.ldsp-order-back-btn')?.addEventListener('click', () => {
                    this._renderShopMy();
                });
                
                // 上传 CDK
                body.querySelector('.ldsp-cdk-upload-btn')?.addEventListener('click', async () => {
                    const codesInput = body.querySelector('#cdk-upload-codes');
                    const remarkInput = body.querySelector('#cdk-upload-remark');
                    const codes = codesInput?.value?.trim();
                    const remark = remarkInput?.value?.trim();
                    
                    if (!codes) {
                        LDSPDialog.warning('请输入 CDK');
                        return;
                    }
                    
                    const btn = body.querySelector('.ldsp-cdk-upload-btn');
                    btn.disabled = true;
                    btn.textContent = '上传中...';
                    
                    const resp = await this._uploadCdk(productId, codes, remark);
                    if (resp?.success) {
                        const data = resp.data || {};
                        LDSPDialog.success(`上传成功!<br>📥 导入: ${data.imported || 0} 条<br>🔄 重复跳过: ${data.duplicates || 0} 条<br>📦 当前库存: ${data.stock || 0} 条`);
                        this._renderCdkManager(productId);
                    } else {
                        btn.disabled = false;
                        btn.textContent = '上传';
                        LDSPDialog.error(this._formatError(resp));
                    }
                });
                
                // 状态筛选
                body.querySelector('#cdk-filter-status')?.addEventListener('change', async (e) => {
                    this._cdkState.status = e.target.value;
                    this._cdkState.page = 1;
                    this._renderCdkManager(productId);
                });
                
                // 分页
                body.querySelectorAll('.ldsp-shop-cdk-mgr-page-btn').forEach(btn => {
                    btn.addEventListener('click', async () => {
                        const page = parseInt(btn.dataset.page);
                        if (page && !btn.disabled) {
                            this._cdkState.page = page;
                            this._renderCdkManager(productId);
                        }
                    });
                });
                
                // 复制单个 CDK
                body.querySelectorAll('.ldsp-shop-cdk-mgr-copy').forEach(btn => {
                    btn.addEventListener('click', async (e) => {
                        e.stopPropagation();
                        const code = btn.dataset.code;
                        if (!code) return;
                        
                        const doCopy = () => {
                            btn.classList.add('copied');
                            btn.textContent = '✓';
                            setTimeout(() => {
                                btn.classList.remove('copied');
                                btn.textContent = '📋';
                            }, 1500);
                        };
                        
                        if (navigator.clipboard?.writeText) {
                            try {
                                await navigator.clipboard.writeText(code);
                                doCopy();
                            } catch {
                                // 降级方案
                                const textarea = document.createElement('textarea');
                                textarea.value = code;
                                textarea.style.cssText = 'position:fixed;left:-9999px';
                                document.body.appendChild(textarea);
                                textarea.select();
                                document.execCommand('copy');
                                document.body.removeChild(textarea);
                                doCopy();
                            }
                        } else {
                            // 降级方案
                            const textarea = document.createElement('textarea');
                            textarea.value = code;
                            textarea.style.cssText = 'position:fixed;left:-9999px';
                            document.body.appendChild(textarea);
                            textarea.select();
                            document.execCommand('copy');
                            document.body.removeChild(textarea);
                            doCopy();
                        }
                    });
                });
                
                // 删除单个 CDK
                body.querySelectorAll('.ldsp-shop-cdk-mgr-delete').forEach(btn => {
                    btn.addEventListener('click', async (e) => {
                        e.stopPropagation();
                        const cdkId = parseInt(btn.dataset.id);
                        const confirmed = await LDSPDialog.confirm('确定要删除这个 CDK 吗?', { title: '删除 CDK', icon: '🗑️', danger: true });
                        if (confirmed) {
                            btn.disabled = true;
                            const resp = await this._deleteCdk(productId, cdkId);
                            if (resp?.success) {
                                this._renderCdkManager(productId);
                            } else {
                                btn.disabled = false;
                                LDSPDialog.error(this._formatError(resp));
                            }
                        }
                    });
                });
            }

            // 显示订单详情
            async _showOrderDetail(orderNo, showCdk = false) {
                const body = this._getShopBody();
                body.innerHTML = `<div class="ldsp-ldc-loading"><div class="ldsp-spinner"></div><div>加载中...</div></div>`;

                // 使用当前角色获取订单详情
                const role = this._shopOrderRole || 'buyer';
                const resp = await this._fetchOrderDetail(orderNo, role);
                if (!resp?.success || !resp?.data?.order) {
                    LDSPDialog.error(this._formatError(resp));
                    this._renderShopOrdersUI();
                    return;
                }

                const order = resp.data.order;
                const logs = resp.data.logs || [];
                const product = order.product || {};
                const statusMap = {
                    'pending': { text: '待支付', color: '#f59e0b', icon: '⏳' },
                    'paying': { text: '支付中', color: '#3b82f6', icon: '💳' },
                    'paid': { text: '待发货', color: '#f97316', icon: '📦' },
                    'delivered': { text: '已完成', color: '#22c55e', icon: '✅' },
                    'refunded': { text: '已退款', color: '#8b5cf6', icon: '↩️' },
                    'expired': { text: '已过期', color: '#6b7280', icon: '⌛' },
                    'cancelled': { text: '已取消', color: '#ef4444', icon: '❌' }
                };
                const status = statusMap[order.status] || { text: order.status, color: '#6b7280', icon: '📋' };
                const fmtDate = (d) => d ? new Date(d).toLocaleString('zh-CN', {month:'2-digit',day:'2-digit',hour:'2-digit',minute:'2-digit'}) : '';

                body.innerHTML = `
                    <div class="ldsp-order-detail">
                        <div class="ldsp-order-detail-header">
                            <button class="ldsp-order-back-btn">←</button>
                            <span>订单详情</span>
                            <div style="width:32px"></div>
                        </div>
                        <div class="ldsp-order-detail-status" style="background:${status.color}12;border-color:${status.color}40">
                            <span style="color:${status.color}">${status.icon} ${status.text}</span>
                        </div>
                        <div class="ldsp-order-detail-card">
                            <div class="ldsp-order-detail-row"><span class="label">订单号</span><span class="value mono" title="${order.order_no}">${order.order_no}</span></div>
                            <div class="ldsp-order-detail-row"><span class="label">商品</span><span class="value" title="${Utils.escapeHtml(product.name || '-')}">${Utils.escapeHtml(product.name || '-')}</span></div>
                            <div class="ldsp-order-detail-row"><span class="label">数量</span><span class="value">${order.quantity || 1}</span></div>
                            <div class="ldsp-order-detail-row"><span class="label">金额</span><span class="value price">${parseFloat(order.amount).toFixed(2)} LDC</span></div>
                            <div class="ldsp-order-detail-row"><span class="label">${role === 'buyer' ? '卖家' : '买家'}</span><span class="value">${Utils.escapeHtml(role === 'buyer' ? order.seller_username : order.buyer_username)}</span></div>
                            <div class="ldsp-order-detail-row"><span class="label">下单</span><span class="value">${fmtDate(order.created_at)}</span></div>
                            ${order.paid_at ? `<div class="ldsp-order-detail-row"><span class="label">支付</span><span class="value">${fmtDate(order.paid_at)}</span></div>` : ''}
                            ${order.delivered_at ? `<div class="ldsp-order-detail-row"><span class="label">发货</span><span class="value">${fmtDate(order.delivered_at)}</span></div>` : ''}
                        </div>
                        ${order.status === 'pending' && role === 'buyer' ? (() => {
                            // 计算待支付订单剩余有效期
                            let expireInfo = '';
                            if (order.pay_expired_at) {
                                const expireTime = new Date(order.pay_expired_at.replace(' ', 'T') + '+08:00').getTime();
                                const remaining = expireTime - Date.now();
                                if (remaining > 0) {
                                    const mins = Math.floor(remaining / 60000);
                                    const secs = Math.floor((remaining % 60000) / 1000);
                                    expireInfo = `<div style="font-size:11px;color:#f59e0b;font-weight:600;margin-bottom:6px">⏰ 剩余支付时间:${mins}分${secs}秒</div>`;
                                } else {
                                    expireInfo = `<div style="font-size:11px;color:#ef4444;font-weight:600;margin-bottom:6px">⚠️ 订单即将过期,请尽快支付</div>`;
                                }
                            }
                            return `
                            <div class="ldsp-order-pending-notice" style="background:rgba(245,158,11,.06);border:1px solid rgba(245,158,11,.2);border-radius:var(--r-md);padding:10px 12px">
                                ${expireInfo}
                                <div style="display:flex;align-items:center;gap:8px">
                                    <div style="font-size:9px;color:var(--txt-sec);line-height:1.4;flex:1">已支付?点击刷新查询订单状态</div>
                                    <button class="ldsp-order-refresh-btn" data-order="${order.order_no}" style="padding:5px 10px;background:#f59e0b;color:#fff;border:none;border-radius:var(--r-sm);font-size:9px;font-weight:600;cursor:pointer;white-space:nowrap">🔄 刷新</button>
                                </div>
                            </div>
                        `;
                        })() : ''}
                        ${order.status === 'delivered' && order.deliveryContent ? `
                            <div class="ldsp-order-cdk-section">
                                <div class="ldsp-order-cdk-title">${role === 'buyer' ? '🎫 您购买的 CDK' : '📤 已发货内容'}</div>
                                <div class="ldsp-order-cdk-content">${Utils.escapeHtml(order.deliveryContent)}</div>
                                <div class="ldsp-order-cdk-actions">
                                    <button class="ldsp-order-cdk-btn" data-action="copy" data-content="${Utils.escapeHtml(order.deliveryContent)}">📋 一键复制</button>
                                </div>
                            </div>
                        ` : ''}
                        ${logs.length > 0 ? `
                            <div class="ldsp-order-logs">
                                <div class="ldsp-order-logs-title">📋 订单记录</div>
                                ${logs.slice(0, 5).map(log => `
                                    <div class="ldsp-order-log-item">
                                        <span class="ldsp-order-log-action">${Utils.escapeHtml(log.action)}</span>
                                        <span class="ldsp-order-log-time">${fmtDate(log.created_at)}</span>
                                    </div>
                                `).join('')}
                            </div>
                        ` : ''}
                    </div>`;

                // 返回按钮
                body.querySelector('.ldsp-order-back-btn')?.addEventListener('click', () => {
                    this._renderShopOrdersUI();
                });

                // 刷新支付状态按钮
                body.querySelector('.ldsp-order-refresh-btn')?.addEventListener('click', async (e) => {
                    const btn = e.target;
                    const orderNo = btn.dataset.order;
                    
                    btn.disabled = true;
                    btn.textContent = '⏳ 查询中...';
                    
                    const resp = await this._refreshOrderStatus(orderNo);
                    
                    if (resp?.success) {
                        if (resp.data?.status === 'delivered') {
                            alert('✅ 支付成功,已自动发货!');
                            this._showOrderDetail(orderNo, true); // 刷新页面显示 CDK
                        } else if (resp.data?.status === 'paid') {
                            alert('✅ 支付成功,等待卖家发货');
                            this._showOrderDetail(orderNo);
                        } else {
                            alert(`ℹ️ ${resp.data?.message || '订单尚未支付'}`);
                            btn.disabled = false;
                            btn.textContent = '🔄 刷新支付状态';
                        }
                    } else {
                        alert(`❌ ${this._formatError(resp)}`);
                        btn.disabled = false;
                        btn.textContent = '🔄 刷新支付状态';
                    }
                });

                // 复制按钮(CDK 一键复制)
                body.querySelector('.ldsp-order-cdk-btn[data-action="copy"]')?.addEventListener('click', (e) => {
                    const btn = e.target;
                    const content = btn.dataset.content;
                    if (!content) return;
                    
                    const doCopy = () => {
                        btn.classList.add('copied');
                        btn.textContent = '✅ 已复制';
                        setTimeout(() => {
                            btn.classList.remove('copied');
                            btn.textContent = '📋 一键复制';
                        }, 2000);
                    };
                    
                    if (navigator.clipboard?.writeText) {
                        navigator.clipboard.writeText(content).then(doCopy).catch(() => {
                            // 降级方案
                            const textarea = document.createElement('textarea');
                            textarea.value = content;
                            textarea.style.cssText = 'position:fixed;left:-9999px';
                            document.body.appendChild(textarea);
                            textarea.select();
                            document.execCommand('copy');
                            document.body.removeChild(textarea);
                            doCopy();
                        });
                    } else {
                        // 降级方案
                        const textarea = document.createElement('textarea');
                        textarea.value = content;
                        textarea.style.cssText = 'position:fixed;left:-9999px';
                        document.body.appendChild(textarea);
                        textarea.select();
                        document.execCommand('copy');
                        document.body.removeChild(textarea);
                        doCopy();
                    }
                });
            }

            static SUPPORT_TIERS = [
                { id: 1, name: '做的不错', amount: 5, icon: '🌱', url: 'https://credit.linux.do/paying/online?token=cf4a5cd58a11fe68a6191c5e3bcca9a34fb8f4eb951eca46bbb0a40042b7e0ea' },
                { id: 2, name: '大力支持', amount: 10, icon: '🚀', badge: '热门', url: 'https://credit.linux.do/paying/online?token=8f4f08c0ceb719c922d105a3be4c2d6d890aa17b47b73fa756510aa1abdc1bf7' },
                { id: 3, name: '深得我心', amount: 25, icon: '❤️', badge: '火爆', url: 'https://credit.linux.do/paying/online?token=7a3d6fb647a275b55c248ad58b546e02905f7a05c08579906773bd323c2e2242' },
                { id: 4, name: '社区贡献者', amount: 50, icon: '👑', badge: '尊享', url: 'https://credit.linux.do/paying/online?token=78d243962c635d1fe2deaecd2e8c59abf778113d25b1e5d05364c8a586fd5384' }
            ];

            _renderSupport() {
                const body = this.overlay.querySelector('.ldsp-ldc-body');
                const tiers = LDCManager.SUPPORT_TIERS.map(t => `
                    <a href="${t.url}" target="_blank" class="ldsp-ldc-support-card tier-${t.id}" rel="noopener">
                        ${t.badge ? `<span class="ldsp-ldc-support-badge">${t.badge}</span>` : ''}
                        <div class="ldsp-ldc-support-icon">${t.icon}</div>
                        <div class="ldsp-ldc-support-name">${t.name}</div>
                        <div class="ldsp-ldc-support-amount">${t.amount} <span>LDC</span></div>
                    </a>`).join('');
                body.innerHTML = `
                    <div class="ldsp-ldc-support">
                        <div class="ldsp-ldc-support-header">
                            <div class="ldsp-ldc-support-title"><span class="ldsp-ldc-support-heart">💖</span>支持 LDStatus Pro</div>
                            <div class="ldsp-ldc-support-desc">感谢您使用 LDStatus Pro!<br>您的支持是我持续开发的动力</div>
                        </div>
                        <a href="https://github.com/caigg188/LDStatusPro" target="_blank" rel="noopener" class="ldsp-github-star-card">
                            <div class="ldsp-github-icon-wrap"><svg class="ldsp-github-icon" viewBox="0 0 98 96"><path fill-rule="evenodd" clip-rule="evenodd" d="M48.854 0C21.839 0 0 22 0 49.217c0 21.756 13.993 40.172 33.405 46.69 2.427.49 3.316-1.059 3.316-2.362 0-1.141-.08-5.052-.08-9.127-13.59 2.934-16.42-5.867-16.42-5.867-2.184-5.704-5.42-7.17-5.42-7.17-4.448-3.015.324-3.015.324-3.015 4.934.326 7.523 5.052 7.523 5.052 4.367 7.496 11.404 5.378 14.235 4.074.404-3.178 1.699-5.378 3.074-6.6-10.839-1.141-22.243-5.378-22.243-24.283 0-5.378 1.94-9.778 5.014-13.2-.485-1.222-2.184-6.275.486-13.038 0 0 4.125-1.304 13.426 5.052a46.97 46.97 0 0 1 12.214-1.63c4.125 0 8.33.571 12.213 1.63 9.302-6.356 13.427-5.052 13.427-5.052 2.67 6.763.97 11.816.485 13.038 3.155 3.422 5.015 7.822 5.015 13.2 0 18.905-11.404 23.06-22.324 24.283 1.78 1.548 3.316 4.481 3.316 9.126 0 6.6-.08 11.897-.08 13.526 0 1.304.89 2.853 3.316 2.364 19.412-6.52 33.405-24.935 33.405-46.691C97.707 22 75.788 0 48.854 0z"/></svg></div>
                            <div class="ldsp-github-content"><div class="ldsp-github-title">在 GitHub 上 Star 一下<span class="ldsp-github-star-icon">⭐</span></div>
                            <div class="ldsp-github-desc">您的 Star 是对作者最好的鼓励</div></div>
                            <span class="ldsp-github-arrow">→</span>
                        </a>
                        <div class="ldsp-ldc-support-grid">${tiers}</div>
                        <div class="ldsp-ldc-support-footer"><div class="ldsp-ldc-support-footer-text">🙏 每一份支持都将用于<em>服务器维护</em>和<em>功能开发</em><br>感谢社区每一位用户的信任与陪伴</div></div>
                    </div>`;
            }

            _showDetail(o) {
                this._order = o;
                const body = this.overlay.querySelector('.ldsp-ldc-body');
                // 使用绝对值,因为 API 返回的 amount 可能已经是负数
                const amt = Math.abs(parseFloat(o.amount) || 0);
                const ti = LDCManager.TRANS_TYPES.find(t => t.id === o.type) || { icon: '📋', label: o.type };
                // 收支判断:优先通过 payee/payer 判断
                const inc = this._userId 
                    ? o.payee_user_id === this._userId && o.payer_user_id !== this._userId
                    : (o.type === 'receive' || o.type === 'community' || o.payer_user_id === 0);
                const row = (l, v, cls = '') => `<div class="ldsp-ldc-detail-row"><span class="label">${l}</span><span class="value${cls ? ' ' + cls : ''}">${v}</span></div>`;
                body.innerHTML = `<div class="ldsp-ldc-detail">
                    <div class="ldsp-ldc-detail-header"><button class="ldsp-ldc-back-btn">← 返回</button><span>交易详情</span></div>
                    <div class="ldsp-ldc-detail-amount ${inc ? 'income-bg' : 'expense-bg'}">
                        <div class="ldsp-ldc-detail-amount-value ${inc ? 'income' : 'expense'}">${inc ? '+' : '-'}${amt.toFixed(2)}</div>
                        <div class="ldsp-ldc-detail-amount-label">LDC</div>
                    </div>
                    <div class="ldsp-ldc-detail-card">
                        ${row('交易状态', o.status === 'success' ? '✓ 成功' : o.status, 'status-' + o.status)}
                        ${row('交易类型', `<span class="ldsp-ldc-trans-type type-${o.type || 'default'}" style="font-size:11px">${ti.icon} ${ti.label}</span>`)}
                        ${row('订单名称', Utils.escapeHtml(o.order_name || '—'))}
                        ${o.app_name ? row('应用名称', Utils.escapeHtml(o.app_name)) : ''}
                        ${row('交易时间', Utils.escapeHtml(this._fmtTime(o.trade_time || o.created_at, true)))}
                        ${row('订单号', Utils.escapeHtml(o.order_no || '—'), 'mono')}
                        ${o.payee_username ? row('收款方', Utils.escapeHtml(o.payee_username)) : ''}
                        ${o.payer_username ? row('付款方', Utils.escapeHtml(o.payer_username)) : ''}
                        ${o.remark ? row('备注', Utils.escapeHtml(o.remark)) : ''}
                        ${o.app_homepage_url ? `<div class="ldsp-ldc-detail-row"><span class="label">应用链接</span><a class="value link" href="${Utils.escapeHtml(o.app_homepage_url)}" target="_blank">${Utils.escapeHtml(o.app_homepage_url)}</a></div>` : ''}
                    </div></div>`;
                body.querySelector('.ldsp-ldc-back-btn')?.addEventListener('click', () => this._closeDetail());
            }

            _closeDetail() { this._order = null; this._renderTransUI(); }

            _fmtTime(t, full = false) {
                if (!t) return '—';
                try {
                    const d = new Date(t);
                    return full ? d.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' })
                        : d.toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });
                } catch { return '—'; }
            }

            _showError(msg, login = false) {
                const body = this.overlay.querySelector('.ldsp-ldc-body');
                this.overlay.querySelector('.ldsp-ldc-refresh')?.classList.remove('spinning');
                this._loading = false;
                const icon = msg.includes('超时') ? '⏱️' : msg.includes('网络') ? '🌐' : msg.includes('登录') ? '🔐' : '😕';
                const isTrans = this._tab === 'transactions';
                const filter = isTrans ? this._getFilterHtml() : '';
                body.innerHTML = `${filter}<div class="ldsp-ldc-error"><div class="ldsp-ldc-error-icon">${icon}</div>
                    <div class="ldsp-ldc-error-msg">${Utils.escapeHtml(msg)}</div>
                    ${login ? '<a href="https://credit.linux.do" target="_blank" class="ldsp-ldc-login-btn">去登录 →</a>' : ''}
                    <button class="ldsp-ldc-retry-btn">🔄 重试</button></div>`;
                if (isTrans) this._bindFilterEvents(body);
                body.querySelector('.ldsp-ldc-retry-btn')?.addEventListener('click', () => {
                    this._tab === 'overview' ? this._fetchData() : this._fetchTrans(true);
                });
            }

            _getFilterHtml() {
                const tr = LDCManager.TIME_RANGES.map(t => `<div class="ldsp-ldc-filter-chip${this._filter.timeRange === t.id ? ' active' : ''}" data-time="${t.id}">${t.label}</div>`).join('');
                const tp = LDCManager.TRANS_TYPES.map(t => `<div class="ldsp-ldc-filter-chip${this._filter.type === t.id ? ' active' : ''}" data-type="${t.id}">${t.icon} ${t.label}</div>`).join('');
                return `<div class="ldsp-ldc-filter-section"><div class="ldsp-ldc-filter-row"><span class="ldsp-ldc-filter-label">时间</span><div class="ldsp-ldc-filter-chips">${tr}</div></div>
                    <div class="ldsp-ldc-filter-row"><span class="ldsp-ldc-filter-label">类型</span><div class="ldsp-ldc-filter-chips ldsp-ldc-filter-chips-wrap">${tp}</div></div></div>`;
            }

            _bindFilterEvents(el) {
                el.querySelectorAll('[data-time]').forEach(c => c.addEventListener('click', () => {
                    if (c.dataset.time !== this._filter.timeRange) { this._filter.timeRange = c.dataset.time; this._fetchTrans(true); }
                }));
                el.querySelectorAll('[data-type]').forEach(c => c.addEventListener('click', () => {
                    if (c.dataset.type !== this._filter.type) { this._filter.type = c.dataset.type; this._fetchTrans(true); }
                }));
            }

            destroy() {
                if (this._escHandler) { document.removeEventListener('keydown', this._escHandler); this._escHandler = null; }
                if (this._scrollObserver) { this._scrollObserver.disconnect(); this._scrollObserver = null; }
                // 清理 iframe 桥接
                if (this._msgHandler) { window.removeEventListener('message', this._msgHandler); this._msgHandler = null; }
                if (this._bridge) { this._bridge.remove(); this._bridge = null; }
                this._requests.clear();
                if (this.overlay) { this.overlay.remove(); this.overlay = null; }
            }
        }

        // ==================== CDK 管理器 ====================
        // 通过 iframe 桥接获取 cdk.linux.do 数据
        class CDKManager {
            static CDK_ORIGIN = 'https://cdk.linux.do';

            constructor(panelBody, renderer) {
                this.panelBody = panelBody;
                this.renderer = renderer;
                this.overlay = null;
                this._loading = false;
                this._tab = 'home';
                this._userInfo = null;
                this._received = { results: [], total: 0, page: 1 };
                this._project = null;
                this._search = '';
                this._searchTimer = null;
                this._bridge = null;
                this._bridgeReady = null;
                this._requests = new Map();
                this._reqId = 0;
            }

            init() {
                this._createOverlay();
                this._initBridge();
            }

            _initBridge() {
                const handler = (e) => {
                    if (e.origin !== CDKManager.CDK_ORIGIN || e.data?.type !== 'ldsp-cdk-response') return;
                    const p = this._requests.get(e.data.requestId);
                    if (p) { this._requests.delete(e.data.requestId); p(e.data); }
                };
                window.addEventListener('message', handler);
                this._msgHandler = handler;
                
                const frame = document.createElement('iframe');
                frame.src = CDKManager.CDK_ORIGIN + '/dashboard';
                frame.style.cssText = 'width:0;height:0;opacity:0;position:absolute;border:0;pointer-events:none';
                document.body.appendChild(frame);
                this._bridge = frame;
                
                this._bridgeReady = new Promise(r => {
                    const t = setTimeout(() => r(), 5000);
                    frame.onload = () => { clearTimeout(t); setTimeout(r, 300); };
                });
            }

            _createOverlay() {
                this.overlay = document.createElement('div');
                this.overlay.className = 'ldsp-cdk-overlay';
                this.overlay.innerHTML = `
                    <div class="ldsp-cdk-header">
                        <div class="ldsp-cdk-title">🎁 CDK 系统</div>
                        <div class="ldsp-cdk-header-actions">
                            <a href="https://cdk.linux.do" target="_blank" class="ldsp-cdk-link">CDK.LINUX.DO</a>
                            <button class="ldsp-cdk-refresh" title="刷新">
                                <svg viewBox="0 0 24 24"><path d="M17.65 6.35C16.2 4.9 14.21 4 12 4c-4.42 0-7.99 3.58-7.99 8s3.57 8 7.99 8c3.73 0 6.84-2.55 7.73-6h-2.08c-.82 2.33-3.04 4-5.65 4-3.31 0-6-2.69-6-6s2.69-6 6-6c1.66 0 3.14.69 4.22 1.78L13 11h7V4l-2.35 2.35z"/></svg>
                            </button>
                            <div class="ldsp-cdk-close">×</div>
                        </div>
                    </div>
                    <div class="ldsp-cdk-tabs">
                        <div class="ldsp-cdk-tab active" data-tab="home">🏠 首页</div>
                        <div class="ldsp-cdk-tab" data-tab="received">📋 领取记录</div>
                    </div>
                    <div class="ldsp-cdk-body">
                        <div class="ldsp-cdk-loading">
                            <div class="ldsp-spinner"></div>
                            <div>加载中...</div>
                        </div>
                    </div>`;
                if (this.panelBody) {
                    this.panelBody.appendChild(this.overlay);
                }
                this._bindEvents();
            }

            _bindEvents() {
                this.overlay.querySelector('.ldsp-cdk-close').addEventListener('click', () => this.hide());
                this.overlay.querySelector('.ldsp-cdk-refresh').addEventListener('click', () => {
                    this._tab === 'home' ? this._fetchUserInfo() : this._fetchReceived();
                });
                this.overlay.querySelectorAll('.ldsp-cdk-tab').forEach(tab => {
                    tab.addEventListener('click', () => {
                        if (tab.dataset.tab !== this._tab) this._switchTab(tab.dataset.tab);
                    });
                });
                this._escHandler = (e) => {
                    if (e.key === 'Escape' && this.overlay.classList.contains('show')) {
                        this._project ? this._closeDetail() : this.hide();
                    }
                };
                document.addEventListener('keydown', this._escHandler);
            }

            _switchTab(tabId) {
                this._tab = tabId;
                this._project = null;
                this.overlay.querySelectorAll('.ldsp-cdk-tab').forEach(t => t.classList.toggle('active', t.dataset.tab === tabId));
                if (tabId === 'home') {
                    this._userInfo ? this._renderHome(this._userInfo) : this._fetchUserInfo();
                } else {
                    this._search = '';
                    this._received = { results: [], total: 0, page: 1 };
                    this._fetchReceived();
                }
            }

            show() {
                this.overlay.classList.add('show');
                this._tab = 'home';
                this._project = null;
                this.overlay.querySelectorAll('.ldsp-cdk-tab').forEach(t => t.classList.toggle('active', t.dataset.tab === 'home'));
                this._userInfo ? this._renderHome(this._userInfo) : this._fetchUserInfo();
            }

            hide() {
                this.overlay.classList.remove('show');
                this._project = null;
            }

            async _fetchUserInfo() {
                if (this._loading) return;
                this._loading = true;
                const body = this.overlay.querySelector('.ldsp-cdk-body');
                const btn = this.overlay.querySelector('.ldsp-cdk-refresh');
                btn?.classList.add('spinning');
                body.innerHTML = `<div class="ldsp-cdk-loading"><div class="ldsp-spinner"></div><div>加载中...</div></div>`;

                try {
                    const data = await this._request('https://cdk.linux.do/api/v1/oauth/user-info');
                    if (data._authError) { 
                        this._showError('请先登录 cdk.linux.do 后刷新', true); 
                        return; 
                    }
                    if (data._error) { 
                        // 超时或网络错误时,提示用户可能需要登录
                        const isTimeout = data._error.includes('超时');
                        this._showError(isTimeout ? '请求超时,请确认已登录 cdk.linux.do 后重试' : data._error, isTimeout); 
                        return; 
                    }
                    this._userInfo = data.data;
                    this._renderHome(this._userInfo);
                } catch (e) {
                    this._showError('网络错误,请稍后重试');
                } finally {
                    this._loading = false;
                    btn?.classList.remove('spinning');
                }
            }

            _renderHome(user) {
                const body = this.overlay.querySelector('.ldsp-cdk-body');
                
                const avatarUrl = user.avatar_url || '';
                const nickname = user.nickname || user.username || 'User';
                const username = user.username || '';
                const userId = user.id || user.user_id || '';
                const trustLevel = user.trust_level || 0;
                const score = user.score || 0;
                
                body.innerHTML = `
                    <div class="ldsp-cdk-user-card">
                        ${avatarUrl ? `<img class="ldsp-cdk-user-avatar" src="${Utils.escapeHtml(avatarUrl)}" alt="avatar" onerror="this.style.display='none'">` : '<div class="ldsp-cdk-user-avatar" style="display:flex;align-items:center;justify-content:center;background:var(--bg-hover);font-size:24px">👤</div>'}
                        <div class="ldsp-cdk-user-info">
                            <div class="ldsp-cdk-user-name">${Utils.escapeHtml(nickname)}</div>
                            <div class="ldsp-cdk-user-username">@${Utils.escapeHtml(username)}${userId ? ` <span style="color:var(--txt-mut);font-size:10px">#${userId}</span>` : ''}</div>
                            <div class="ldsp-cdk-user-level">
                                <span>🏅 LV ${trustLevel}</span>
                            </div>
                        </div>
                        <div class="ldsp-cdk-score-card">
                            <div class="ldsp-cdk-score-label">CDK 分数</div>
                            <div class="ldsp-cdk-score-value">${score}</div>
                        </div>
                    </div>
                    <div style="padding:14px;background:linear-gradient(135deg,rgba(6,182,212,.06),rgba(14,165,233,.03));border:1px solid rgba(6,182,212,.15);border-radius:var(--r-md);font-size:11px;color:var(--txt-sec);line-height:1.7">
                        <div style="font-weight:600;color:var(--txt);margin-bottom:8px;display:flex;align-items:center;gap:6px"><span style="font-size:14px">💡</span>关于 CDK 分数</div>
                        <div>CDK 分数用于参与社区项目的抽奖和领取活动,很多项目会设置CDK分数要求。</div>
                        <div style="margin-top:8px;padding-top:8px;border-top:1px solid var(--border);font-size:10px;color:var(--txt-mut)">🥇 通过获取徽章来提高CDK分数</div>
                    </div>
                    <div style="text-align:center;margin-top:auto;padding-top:12px">
                        <a href="https://cdk.linux.do" target="_blank" style="display:inline-flex;align-items:center;gap:4px;color:#06b6d4;font-size:11px;text-decoration:none;padding:8px 16px;background:rgba(6,182,212,.08);border:1px solid rgba(6,182,212,.2);border-radius:var(--r-sm);transition:all .15s" onmouseover="this.style.background='rgba(6,182,212,.15)'" onmouseout="this.style.background='rgba(6,182,212,.08)'">🔗 访问 CDK 系统完整功能</a>
                    </div>`;
            }

            async _fetchReceived(loadMore = false) {
                if (this._loading) return;
                this._loading = true;
                const body = this.overlay.querySelector('.ldsp-cdk-body');
                const btn = this.overlay.querySelector('.ldsp-cdk-refresh');
                const trigger = body?.querySelector('.ldsp-cdk-load-trigger');
                if (!loadMore) {
                    btn?.classList.add('spinning');
                    this._received = { results: [], total: 0, page: 1 };
                    body.innerHTML = `<div class="ldsp-cdk-loading"><div class="ldsp-spinner"></div><div>加载中...</div></div>`;
                } else if (trigger) {
                    trigger.innerHTML = '<div class="ldsp-mini-spin"></div>加载中...';
                    trigger.classList.add('loading');
                }
                const page = loadMore ? this._received.page + 1 : 1;

                try {
                    const search = this._search ? `&search=${encodeURIComponent(this._search)}` : '';
                    const data = await this._request(`https://cdk.linux.do/api/v1/projects/received?current=${page}&size=20${search}`);
                    if (data._authError) { this._showError('请先登录 cdk.linux.do', true); return; }
                    if (data._error) { this._showError(data._error); return; }

                    const results = data.data?.results || [];
                    this._received.results = loadMore ? [...this._received.results, ...results] : results;
                    this._received.total = data.data?.total || 0;
                    this._received.page = page;
                    this._renderReceived(loadMore);
                } catch (e) {
                    this._showError('网络错误');
                } finally {
                    this._loading = false;
                    btn?.classList.remove('spinning');
                }
            }

            _renderReceived(append = false) {
                const body = this.overlay.querySelector('.ldsp-cdk-body');
                const { results, total } = this._received;
                const hasMore = results.length < total;

                if (!append) {
                    let html = `<div class="ldsp-cdk-list-header">
                        <div class="ldsp-cdk-search"><span class="ldsp-cdk-search-icon">🔍</span>
                            <input type="text" placeholder="搜索项目名称或创建者..." value="${Utils.escapeHtml(this._search)}">
                            <button class="ldsp-cdk-search-clear ${this._search ? 'show' : ''}">×</button></div>
                        ${total > 0 ? `<div class="ldsp-cdk-stats-row"><span class="total">📦 共 <strong>${total}</strong> 条记录</span><span class="loaded">已加载 ${results.length} 条</span></div>` : ''}
                    </div>`;

                    if (!results.length) {
                        html += `<div class="ldsp-cdk-empty"><div class="ldsp-cdk-empty-icon">📭</div>
                            <div class="ldsp-cdk-empty-text">${this._search ? '未找到匹配的记录' : '暂无领取记录'}</div></div>`;
                    } else {
                        html += '<div class="ldsp-cdk-list ldsp-cdk-scroll-list">' + this._renderReceivedItems(results) + '</div>';
                        html += hasMore ? '<div class="ldsp-cdk-load-trigger">⬇️ 滚动加载更多</div>' : `<div class="ldsp-cdk-loaded-all">✅ 已全部加载</div>`;
                    }
                    body.innerHTML = html;
                    this._bindReceivedEvents(body);
                } else {
                    const list = body.querySelector('.ldsp-cdk-scroll-list');
                    const trigger = body.querySelector('.ldsp-cdk-load-trigger');
                    const loaded = body.querySelector('.ldsp-cdk-stats-row .loaded');
                    if (list) {
                        const startIdx = results.length - 20;
                        list.insertAdjacentHTML('beforeend', this._renderReceivedItems(results.slice(Math.max(0, startIdx))));
                        this._bindItemEvents(list);
                    }
                    if (loaded) loaded.textContent = `已加载 ${results.length} 条`;
                    if (trigger) {
                        trigger.outerHTML = hasMore ? '<div class="ldsp-cdk-load-trigger">⬇️ 滚动加载更多</div>' : `<div class="ldsp-cdk-loaded-all">✅ 已全部加载</div>`;
                    }
                }
            }

            _renderReceivedItems(items) {
                return items.map(item => `
                    <div class="ldsp-cdk-item" data-id="${Utils.escapeHtml(item.project_id)}">
                        <div class="ldsp-cdk-item-header">
                            <div class="ldsp-cdk-item-name">${Utils.escapeHtml(item.project_name)}</div>
                            <div class="ldsp-cdk-item-time">📅 ${this._formatTime(item.received_at)}</div>
                        </div>
                        <div class="ldsp-cdk-item-creator">👤 ${Utils.escapeHtml(item.project_creator_nickname || item.project_creator)}</div>
                        <div class="ldsp-cdk-item-content">
                            <span class="ldsp-cdk-item-content-text">${Utils.escapeHtml(item.content)}</span>
                            <button class="ldsp-cdk-item-copy" data-content="${Utils.escapeHtml(item.content)}" title="复制内容">📋</button>
                        </div>
                    </div>`).join('');
            }

            _bindReceivedEvents(el) {
                const input = el.querySelector('.ldsp-cdk-search input');
                const clear = el.querySelector('.ldsp-cdk-search-clear');
                input?.addEventListener('input', (e) => {
                    clear?.classList.toggle('show', !!e.target.value);
                    clearTimeout(this._searchTimer);
                    this._searchTimer = setTimeout(() => { this._search = e.target.value.trim(); this._fetchReceived(); }, 500);
                });
                clear?.addEventListener('click', () => { input.value = ''; clear.classList.remove('show'); this._search = ''; this._fetchReceived(); });
                this._bindItemEvents(el);
                // 瀑布流滚动加载
                const body = this.overlay.querySelector('.ldsp-cdk-body');
                body?.removeEventListener('scroll', this._scrollHandler);
                this._scrollHandler = () => {
                    if (this._loading) return;
                    const { scrollTop, scrollHeight, clientHeight } = body;
                    if (scrollHeight - scrollTop - clientHeight < 80) {
                        const { results, total } = this._received;
                        if (results.length < total) this._fetchReceived(true);
                    }
                };
                body?.addEventListener('scroll', this._scrollHandler, { passive: true });
            }

            _bindItemEvents(el) {
                el.querySelectorAll('.ldsp-cdk-item:not([data-bound])').forEach(item => {
                    item.dataset.bound = '1';
                    item.addEventListener('click', (e) => {
                        if (!e.target.closest('.ldsp-cdk-item-copy')) this._fetchDetail(item.dataset.id);
                    });
                });
                el.querySelectorAll('.ldsp-cdk-item-copy:not([data-bound])').forEach(btn => {
                    btn.dataset.bound = '1';
                    btn.addEventListener('click', (e) => {
                        e.stopPropagation();
                        this._copy(btn, btn.dataset.content);
                    });
                });
            }

            async _copy(btn, text) {
                try { await navigator.clipboard.writeText(text); btn.innerHTML = '✅'; } catch { btn.innerHTML = '❌'; }
                setTimeout(() => btn.innerHTML = '📋', 1500);
            }

            async _fetchDetail(id) {
                const body = this.overlay.querySelector('.ldsp-cdk-body');
                body.innerHTML = `<div class="ldsp-cdk-loading"><div class="ldsp-spinner"></div><div>加载详情...</div></div>`;
                try {
                    const data = await this._request(`https://cdk.linux.do/api/v1/projects/${id}`);
                    if (data._error) { this._showError(data._error); return; }
                    this._project = data.data;
                    this._renderDetail(data.data);
                } catch (e) {
                    this._showError('获取详情失败');
                }
            }

            _renderDetail(p) {
                const body = this.overlay.querySelector('.ldsp-cdk-body');
                const status = p.status === 0 ? '进行中' : '已结束';
                const statusColor = p.status === 0 ? '#22c55e' : 'var(--txt-mut)';
                const tags = p.tags?.length ? `<div class="ldsp-cdk-detail-tags">${p.tags.map(t => `<span class="ldsp-cdk-detail-tag">${Utils.escapeHtml(t)}</span>`).join('')}</div>` : '';
                const total = p.total_items || 0, remain = p.available_items_count || 0;
                // 构建要求列表
                const reqs = [];
                if (p.minimum_trust_level > 0) reqs.push(['🏅 最低信任等级', `LV ${p.minimum_trust_level}`]);
                if (typeof p.risk_level === 'number') reqs.push(['⭐ 最低用户分数', 100 - p.risk_level]);
                if (p.allow_same_ip === false) reqs.push(['🌐 IP限制', '限制同一IP']);
                const reqsHtml = reqs.length ? reqs.map(([l, v]) => `<div class="ldsp-cdk-detail-row"><span class="label">${l}</span><span class="value">${v}</span></div>`).join('') : '<div class="ldsp-cdk-detail-row"><span class="label">📋 领取要求</span><span class="value">无特殊要求</span></div>';
                const rows = [
                    ['开始时间', this._formatTime(p.start_time, true)],
                    ['结束时间', this._formatTime(p.end_time, true)],
                    ['创建时间', this._formatTime(p.created_at, true)]
                ].map(([l, v]) => `<div class="ldsp-cdk-detail-row"><span class="label">${l}</span><span class="value">${v}</span></div>`).join('');

                body.innerHTML = `<div class="ldsp-cdk-detail">
                    <div class="ldsp-cdk-detail-header"><button class="ldsp-cdk-back-btn">← 返回</button></div>
                    <div class="ldsp-cdk-detail-title">${Utils.escapeHtml(p.name)}</div>
                    <div class="ldsp-cdk-detail-meta">创建者: ${Utils.escapeHtml(p.creator_nickname || p.creator_username)} · <span style="color:${statusColor}">${status}</span>${p.is_completed ? ' · ✅ 已领取' : ''}</div>
                    ${tags}${p.description ? `<div class="ldsp-cdk-detail-desc">${Utils.escapeHtml(p.description)}</div>` : ''}
                    ${p.received_content ? `<div class="ldsp-cdk-detail-content"><div class="ldsp-cdk-detail-content-label">🎁 分发内容</div>
                        <div class="ldsp-cdk-detail-content-value"><span>${Utils.escapeHtml(p.received_content)}</span>
                        <button class="ldsp-cdk-detail-copy" data-content="${Utils.escapeHtml(p.received_content)}">📋</button></div></div>` : ''}
                    <div class="ldsp-cdk-qty-card">
                        <div class="ldsp-cdk-qty-item remain"><div class="num">${remain}</div><div class="lbl">剩余数量</div></div>
                        <div class="ldsp-cdk-qty-item total"><div class="num">${total}</div><div class="lbl">总数量</div></div>
                    </div>
                    <div class="ldsp-cdk-detail-section"><div class="ldsp-cdk-detail-section-title">📋 领取要求</div><div class="ldsp-cdk-detail-info">${reqsHtml}</div></div>
                    <div class="ldsp-cdk-detail-section"><div class="ldsp-cdk-detail-section-title">📅 时间信息</div><div class="ldsp-cdk-detail-info">${rows}</div></div></div>`;

                body.querySelector('.ldsp-cdk-back-btn')?.addEventListener('click', () => this._closeDetail());
                const copyBtn = body.querySelector('.ldsp-cdk-detail-copy');
                copyBtn?.addEventListener('click', () => this._copy(copyBtn, copyBtn.dataset.content));
            }

            _closeDetail() { this._project = null; this._renderReceived(); }

            _formatTime(t, full = false) {
                if (!t) return '—';
                try {
                    const d = new Date(t);
                    if (full) return d.toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });
                    const diff = Date.now() - d, mins = Math.floor(diff / 60000), hrs = Math.floor(diff / 3600000), days = Math.floor(diff / 86400000);
                    if (mins < 1) return '刚刚';
                    if (mins < 60) return `${mins}分钟前`;
                    if (hrs < 24) return `${hrs}小时前`;
                    if (days < 7) return `${days}天前`;
                    return d.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit' });
                } catch { return '—'; }
            }

            async _request(url) {
                if (this._bridgeReady) await this._bridgeReady;
                if (!this._bridge) return { _error: '桥接未就绪' };
                
                return new Promise((resolve) => {
                    const id = ++this._reqId;
                    const timeout = setTimeout(() => { this._requests.delete(id); resolve({ _error: '请求超时' }); }, 15000);
                    this._requests.set(id, ({ status, data }) => {
                        clearTimeout(timeout);
                        if (status === 200) {
                            resolve(data._error ? { _error: data._error } : (data.error_msg ? { _error: data.error_msg } : data));
                        } else if (status === 401 || status === 403) {
                            resolve({ _authError: true });
                        } else {
                            debugBridgeLog('CDK bridge status', { url, status });
                            resolve({ _error: data._error || `请求失败 (${status})` });
                        }
                    });
                    try {
                        this._bridge.contentWindow.postMessage({ type: 'ldsp-cdk-request', requestId: id, url }, CDKManager.CDK_ORIGIN);
                    } catch (e) {
                        clearTimeout(timeout);
                        this._requests.delete(id);
                        debugBridgeLog('CDK bridge postMessage error', e?.message || e);
                        resolve({ _bridgeError: true, _error: '发送请求失败' });
                    }
                }).then(async resp => {
                    // 桥接失败/未登录时,尝试 GM 兜底(桌面端更稳定)
                    if (resp?._bridgeError || resp?._timeoutError || resp?._networkError) {
                        debugBridgeLog('CDK bridge fallback to GM', { url, reason: resp });
                        const gmResp = await this._requestViaGM(url);
                        return gmResp ?? resp;
                    }
                    return resp;
                });
            }

            _requestViaGM(url) {
                if (typeof GM_xmlhttpRequest !== 'function') return null;
                return new Promise(resolve => {
                    GM_xmlhttpRequest({
                        method: 'GET',
                        url,
                        withCredentials: true,
                        timeout: 12000,
                        headers: { 'Accept': 'application/json', 'Referer': 'https://cdk.linux.do/dashboard' },
                        onload: r => {
                            if (r.status === 200) {
                                try { resolve(JSON.parse(r.responseText)); return; } catch {}
                            }
                            if (r.status === 401 || r.status === 403) { resolve({ _authError: true }); return; }
                            debugBridgeLog('CDK GM status', { url, status: r.status });
                            resolve({ _error: `请求失败 (${r.status})` });
                        },
                        ontimeout: () => { debugBridgeLog('CDK GM timeout', url); resolve({ _timeoutError: true }); },
                        onerror: () => { debugBridgeLog('CDK GM network', url); resolve({ _networkError: true }); }
                    });
                });
            }

            _showError(msg, showLogin = false) {
                const body = this.overlay.querySelector('.ldsp-cdk-body');
                this.overlay.querySelector('.ldsp-cdk-refresh')?.classList.remove('spinning');
                this._loading = false;
                const icon = msg.includes('超时') ? '⏱️' : msg.includes('网络') ? '🌐' : msg.includes('登录') ? '🔐' : '😕';
                body.innerHTML = `<div class="ldsp-cdk-error"><div class="ldsp-cdk-error-icon">${icon}</div>
                    <div class="ldsp-cdk-error-msg">${Utils.escapeHtml(msg)}</div>
                    ${showLogin ? '<a href="https://cdk.linux.do" target="_blank" class="ldsp-cdk-login-btn">去登录 →</a>' : ''}
                    <button class="ldsp-cdk-retry-btn">🔄 重试</button></div>`;
                body.querySelector('.ldsp-cdk-retry-btn')?.addEventListener('click', () => {
                    this._tab === 'home' ? this._fetchUserInfo() : this._fetchReceived();
                });
            }

            destroy() {
                if (this._escHandler) { document.removeEventListener('keydown', this._escHandler); this._escHandler = null; }
                if (this._searchTimer) clearTimeout(this._searchTimer);
                if (this._msgHandler) { window.removeEventListener('message', this._msgHandler); this._msgHandler = null; }
                if (this._bridge) { this._bridge.remove(); this._bridge = null; }
                this._requests.clear();
                if (this.overlay) { this.overlay.remove(); this.overlay = null; }
            }
        }

        // ==================== 吃瓜助手 ====================
        class MelonHelper {
            static STORAGE_KEY = 'ldsp_melon_config';
            static HISTORY_KEY = 'ldsp_melon_history';
            
            // 简略总结提示词
            static PROMPT_BRIEF = `用简洁的方式总结以下论坛讨论:`;

            // 详细总结提示词
            static PROMPT_DETAILED = `详细分析和总结以下论坛讨论:`;

            constructor(panelBody, renderer) {
                this.panelBody = panelBody;
                this.renderer = renderer;
                this.overlay = null;
                this.config = this._loadConfig();
                this.history = this._loadHistory();
                this._abortController = null;
                this._isEditing = false;
                this._topicCache = null;
                this._currentOutput = '';  // 当前输出的原始文本
                this._summaryMode = 'detailed';  // 默认详细模式
                this._lastUrl = location.href;  // 上次URL
                this._urlCheckInterval = null;  // URL检测定时器
            }

            _loadConfig() {
                try {
                    const saved = GM_getValue(MelonHelper.STORAGE_KEY, null);
                    const defaultConfig = {
                        apiUrl: '',
                        apiKey: '',
                        model: 'gpt-4o-mini',
                        promptBrief: '',
                        promptDetailed: ''
                    };
                    return saved ? { ...defaultConfig, ...JSON.parse(saved) } : defaultConfig;
                } catch {
                    return { apiUrl: '', apiKey: '', model: 'gpt-4o-mini', promptBrief: '', promptDetailed: '' };
                }
            }

            _saveConfig() {
                try {
                    GM_setValue(MelonHelper.STORAGE_KEY, JSON.stringify(this.config));
                } catch (e) {
                    Logger.error('[MelonHelper] Save config failed:', e);
                }
            }

            _loadHistory() {
                try {
                    const saved = GM_getValue(MelonHelper.HISTORY_KEY, null);
                    return saved ? JSON.parse(saved) : [];
                } catch {
                    return [];
                }
            }

            _saveHistory() {
                try {
                    GM_setValue(MelonHelper.HISTORY_KEY, JSON.stringify(this.history));
                } catch (e) {
                    Logger.error('[MelonHelper] Save history failed:', e);
                }
            }

            _addToHistory(topicId, title, summary, mode) {
                // 以 topicId + mode 为主键,同一话题的简略和详细版可以同时存在
                const historyKey = `${topicId}_${mode}`;
                const existingIndex = this.history.findIndex(h => `${h.topicId}_${h.mode}` === historyKey);
                const record = {
                    topicId,
                    title,
                    summary,
                    mode,
                    timestamp: Date.now()
                };
                if (existingIndex >= 0) {
                    this.history[existingIndex] = record;
                } else {
                    this.history.unshift(record);
                }
                // 最多保留 100 条
                if (this.history.length > 100) {
                    this.history = this.history.slice(0, 100);
                }
                this._saveHistory();
            }

            _clearHistory() {
                this.history = [];
                this._saveHistory();
            }

            init() {
                this._createOverlay();
            }

            _createOverlay() {
                this.overlay = document.createElement('div');
                this.overlay.className = 'ldsp-melon-overlay';
                this.overlay.innerHTML = `
                    <div class="ldsp-melon-header">
                        <div class="ldsp-melon-title">🍉 吃瓜助手</div>
                        <div class="ldsp-melon-header-actions">
                            <div class="ldsp-melon-refresh" title="刷新数据"><svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M21 2v6h-6"/><path d="M3 12a9 9 0 0 1 15-6.7L21 8"/><path d="M3 22v-6h6"/><path d="M21 12a9 9 0 0 1-15 6.7L3 16"/></svg>刷新</div>
                            <div class="ldsp-melon-close">×</div>
                        </div>
                    </div>
                    <div class="ldsp-melon-tabs">
                        <div class="ldsp-melon-tab active" data-tab="home">首页</div>
                        <div class="ldsp-melon-tab" data-tab="history">历史</div>
                        <div class="ldsp-melon-tab" data-tab="settings">设置</div>
                    </div>
                    <div class="ldsp-melon-body"></div>`;
                if (this.panelBody) {
                    this.panelBody.appendChild(this.overlay);
                }
                this._bindEvents();
            }

            _bindEvents() {
                this.overlay.querySelector('.ldsp-melon-close').addEventListener('click', () => this.hide());
                this.overlay.querySelector('.ldsp-melon-refresh').addEventListener('click', () => this._handleRefresh());
                document.addEventListener('keydown', e => {
                    if (e.key === 'Escape' && this.overlay.classList.contains('show')) this.hide();
                });
                this.overlay.querySelectorAll('.ldsp-melon-tab').forEach(tab => {
                    tab.addEventListener('click', () => {
                        this.overlay.querySelectorAll('.ldsp-melon-tab').forEach(t => t.classList.remove('active'));
                        tab.classList.add('active');
                        const tabName = tab.dataset.tab;
                        if (tabName === 'home') {
                            this._renderHome();
                        } else if (tabName === 'history') {
                            this._renderHistory();
                        } else if (tabName === 'settings') {
                            this._renderSettings();
                        }
                    });
                });
                
            }

            async _handleRefresh() {
                const refreshBtn = this.overlay.querySelector('.ldsp-melon-refresh');
                if (!refreshBtn || refreshBtn.classList.contains('disabled')) return;
                
                // 获取当前活动的标签页
                const activeTab = this.overlay.querySelector('.ldsp-melon-tab.active');
                const tabName = activeTab?.dataset.tab;
                
                // 清除缓存
                this._topicCache = null;
                
                // 根据当前标签页刷新
                if (tabName === 'home') {
                    await this._renderHome(true);
                } else if (tabName === 'history') {
                    this._renderHistory();
                }
                // 设置页不需要刷新
            }
            
            // URL 监听 - 仅在面板打开且非话题页时启动
            _startUrlWatch() {
                if (this._urlCheckInterval) return;
                this._urlCheckInterval = setInterval(() => {
                    const currentUrl = location.href;
                    if (currentUrl !== this._lastUrl) {
                        this._lastUrl = currentUrl;
                        this._topicCache = null;  // 清空话题缓存
                        
                        // 如果面板打开且在首页,检测到话题 ID 有变化时刷新
                        if (this.overlay.classList.contains('show')) {
                            const activeTab = this.overlay.querySelector('.ldsp-melon-tab.active');
                            if (activeTab?.dataset.tab === 'home') {
                                const newTopicId = this._getTopicId();
                                // 只有当进入新话题时才刷新(从非话题页进入话题页)
                                if (newTopicId) {
                                    this._renderHome();
                                    // 成功进入话题后停止轮询,避免频繁刷新
                                    this._stopUrlWatch();
                                }
                            }
                        }
                    }
                }, 800);  // 降低检测频率
            }
            
            _stopUrlWatch() {
                if (this._urlCheckInterval) {
                    clearInterval(this._urlCheckInterval);
                    this._urlCheckInterval = null;
                }
            }

            show() {
                // 更新 URL 记录
                this._lastUrl = location.href;
                
                // 检查是否切换了话题,如果是则清空缓存
                const currentTopicId = this._getTopicId();
                if (this._topicCache && this._topicCache.id !== currentTopicId) {
                    this._topicCache = null;
                    Logger.log('[MelonHelper] Topic changed, clearing cache');
                }
                
                this.overlay.classList.add('show');
                const activeTab = this.overlay.querySelector('.ldsp-melon-tab.active');
                if (activeTab?.dataset.tab === 'settings') {
                    this._renderSettings();
                } else {
                    this._renderHome();
                }
            }

            hide() {
                // 停止 URL 监听
                this._stopUrlWatch();
                // 中止正在进行的请求
                if (this._abortController) {
                    this._abortController.abort();
                    this._abortController = null;
                }
                this.overlay.classList.remove('show');
            }

            _getTopicId() {
                return window.location.href.match(/\/t(?:opic)?\/[^\/]+\/(\d+)/)?.[1] || 
                       window.location.href.match(/\/t(?:opic)?\/(\d+)/)?.[1];
            }

            _getReplyCount() {
                const el = document.querySelector('.timeline-replies');
                if (!el) return 0;
                const txt = el.textContent.trim();
                return parseInt(txt.includes('/') ? txt.split('/')[1] : txt) || 0;
            }

            async _getTopicInfo(forceRefresh = false) {
                const topicId = this._getTopicId();
                if (!topicId) return null;
                
                // 使用缓存(同一话题不重复请求)
                if (!forceRefresh && this._topicCache && this._topicCache.id === topicId) {
                    Logger.log('[MelonHelper] Using cached topic info');
                    return this._topicCache;
                }
                
                try {
                    Logger.log('[MelonHelper] Fetching topic info for:', topicId);
                    const csrf = document.querySelector('meta[name="csrf-token"]')?.content;
                    const response = await fetch(`${location.origin}/t/${topicId}.json`, {
                        headers: {
                            'x-csrf-token': csrf,
                            'x-requested-with': 'XMLHttpRequest'
                        }
                    });
                    if (!response.ok) {
                        Logger.warn('[MelonHelper] Topic API response not ok:', response.status);
                        throw new Error(`HTTP ${response.status}`);
                    }
                    const data = await response.json();
                    Logger.log('[MelonHelper] Topic data received:', { title: data.title, posts_count: data.posts_count });
                    
                    this._topicCache = {
                        id: topicId,
                        title: data.title || '未知标题',
                        category: data.category_id ? (document.querySelector('.category-name')?.textContent || '') : '',
                        postsCount: data.posts_count || 1,
                        replyCount: Math.max(0, (data.posts_count || 1) - 1),
                        views: data.views || 0,
                        likeCount: data.like_count || 0,
                        createdAt: data.created_at,
                        lastPostedAt: data.last_posted_at
                    };
                    return this._topicCache;
                } catch (e) {
                    Logger.error('[MelonHelper] Get topic info failed:', e);
                    // 降级:从页面 DOM 获取
                    const fallbackInfo = {
                        id: topicId,
                        title: document.querySelector('.fancy-title, .topic-title')?.textContent?.trim() || '当前话题',
                        replyCount: this._getReplyCount(),
                        postsCount: Math.max(1, this._getReplyCount() + 1),
                        views: 0
                    };
                    Logger.log('[MelonHelper] Using fallback info:', fallbackInfo);
                    return fallbackInfo;
                }
            }

            async _fetchDialogues(topicId, start, end, progressCallback) {
                Logger.log('[MelonHelper] Fetching dialogues:', { topicId, start, end });
                const csrf = document.querySelector('meta[name="csrf-token"]')?.content;
                const opts = {
                    headers: {
                        'x-csrf-token': csrf,
                        'x-requested-with': 'XMLHttpRequest'
                    }
                };

                // 获取帖子ID列表
                progressCallback?.('正在获取帖子列表...');
                const idRes = await fetch(`${location.origin}/t/${topicId}/post_ids.json?post_number=0&limit=99999`, opts);
                if (!idRes.ok) throw new Error(`获取帖子列表失败 (${idRes.status})`);
                const idData = await idRes.json();
                Logger.log('[MelonHelper] Total post IDs:', idData.post_ids?.length);
                
                let pIds = idData.post_ids.slice(Math.max(0, start - 1), end);
                Logger.log('[MelonHelper] Selected post IDs count:', pIds.length);

                // 如果包含第1楼,获取主帖信息确保第一楼ID正确
                if (start <= 1 && pIds.length > 0) {
                    const mainRes = await fetch(`${location.origin}/t/${topicId}.json`, opts);
                    if (mainRes.ok) {
                        const mainData = await mainRes.json();
                        const firstId = mainData.post_stream?.posts?.[0]?.id;
                        if (firstId && !pIds.includes(firstId)) {
                            pIds.unshift(firstId);
                            Logger.log('[MelonHelper] Added first post ID:', firstId);
                        }
                    }
                }

                if (pIds.length === 0) {
                    throw new Error('没有找到帖子内容');
                }

                let text = '';
                const totalBatches = Math.ceil(pIds.length / 200);
                
                // 分批获取帖子详情(每批200条)
                for (let i = 0; i < pIds.length; i += 200) {
                    const batchNum = Math.floor(i / 200) + 1;
                    progressCallback?.(`正在获取帖子内容 (${batchNum}/${totalBatches})...`);
                    
                    const chunk = pIds.slice(i, i + 200);
                    const q = chunk.map(id => `post_ids[]=${id}`).join('&');
                    const res = await fetch(`${location.origin}/t/${topicId}/posts.json?${q}&include_suggested=false`, opts);
                    if (!res.ok) throw new Error(`获取帖子详情失败 (${res.status})`);
                    const data = await res.json();
                    
                    Logger.log('[MelonHelper] Batch', batchNum, 'posts count:', data.post_stream?.posts?.length);

                    text += data.post_stream.posts.map(p => {
                        let content = p.cooked || '';

                        // 处理图片
                        content = content.replace(
                            /<div class="lightbox-wrapper">\s*<a class="lightbox" href="([^"]+)"(?:\s+data-download-href="([^"]+)")?[^>]*title="([^"]*)"[^>]*>[\s\S]*?<\/a>\s*<\/div>/gi,
                            (match, hrefUrl, downloadHref, title) => {
                                let imgUrl = hrefUrl || `${location.origin}${downloadHref || ''}`;
                                const filename = title || '图片';
                                return `\n[图片: ${filename}](${imgUrl})\n`;
                            }
                        );

                        // 处理附件
                        content = content.replace(
                            /<a class="attachment" href="([^"]+)"[^>]*>([^<]+)<\/a>/gi,
                            (match, url, name) => `\n[附件: ${name.trim()}](${url})\n`
                        );

                        // 处理emoji
                        content = content.replace(
                            /<img[^>]+class="emoji[^>]*alt="([^"]*)"[^>]*>/gi,
                            '$1 '
                        );

                        // 处理引用块
                        content = content.replace(
                            /<aside class="quote(?:-modified)?[^>]*>[\s\S]*?<blockquote>([\s\S]*?)<\/blockquote>[\s\S]*?<\/aside>/gi,
                            (match, quoteInner) => {
                                let cleanQuote = quoteInner.replace(/<[^>]+>/g, '').trim();
                                return `\n[引用]\n${cleanQuote}\n[/引用]\n`;
                            }
                        );

                        // 移除所有HTML标签
                        content = content.replace(/<[^>]+>/g, '').trim();

                        // 格式化输出
                        const userName = p.name || p.username;
                        const userPart = `${userName}(${p.username})`;
                        let replyPart = '';

                        if (p.reply_to_post_number && p.reply_to_user) {
                            const replyToName = p.reply_to_user.name || p.reply_to_user.username;
                            const replyToUsername = p.reply_to_user.username;
                            replyPart = `-回复[${p.reply_to_post_number}楼] ${replyToName}(${replyToUsername})`;
                        }

                        return `[${p.post_number}楼] ${userPart}${replyPart}:\n${content}`;
                    }).join('\n\n');
                }

                return text;
            }

            async _renderHome(forceRefresh = false) {
                const body = this.overlay.querySelector('.ldsp-melon-body');
                const topicId = this._getTopicId();

                if (!topicId) {
                    // 非话题页,启动 URL 监听等待用户进入话题
                    this._startUrlWatch();
                    body.innerHTML = `
                        <div class="ldsp-melon-not-topic">
                            <div class="ldsp-melon-not-topic-icon">🍈</div>
                            <div class="ldsp-melon-not-topic-text">请先进入一个话题帖子<br>才能使用吃瓜助手哦~</div>
                            <div class="ldsp-melon-not-topic-hint">进入话题后会自动刷新</div>
                        </div>`;
                    return;
                }

                // 已在话题页,停止 URL 监听
                this._stopUrlWatch();

                // 先显示加载状态
                body.innerHTML = `<div class="ldsp-melon-status"><div class="ldsp-melon-status-icon">⏳</div>正在获取话题信息...</div>`;

                const info = await this._getTopicInfo(forceRefresh);
                if (!info) {
                    body.innerHTML = `<div class="ldsp-melon-error">❌ 获取话题信息失败,请刷新页面后重试</div>`;
                    return;
                }

                const totalPosts = info.postsCount || 1;
                const defaultEnd = totalPosts;
                const rangeHint = totalPosts > 100 ? `共${totalPosts}楼,内容较多可能需要较长时间` : `共${totalPosts}楼`;

                // 检查是否已配置 API
                const hasConfig = this.config.apiUrl && this.config.apiKey;

                body.innerHTML = `
                    <div class="ldsp-melon-info">
                        <div class="ldsp-melon-info-title">📋 ${Utils.escapeHtml(info.title)}</div>
                        <div class="ldsp-melon-info-row">
                            <span class="ldsp-melon-info-label">总楼层</span>
                            <span class="ldsp-melon-info-value">${totalPosts} 楼</span>
                        </div>
                        ${info.views ? `<div class="ldsp-melon-info-row">
                            <span class="ldsp-melon-info-label">浏览量</span>
                            <span class="ldsp-melon-info-value">${info.views.toLocaleString()}</span>
                        </div>` : ''}
                        ${info.likeCount ? `<div class="ldsp-melon-info-row">
                            <span class="ldsp-melon-info-label">点赞数</span>
                            <span class="ldsp-melon-info-value">${info.likeCount}</span>
                        </div>` : ''}
                    </div>
                    ${!hasConfig ? `<div class="ldsp-melon-warning">⚠️ 请先在「设置」中配置 API 信息</div>` : ''}
                    <div class="ldsp-melon-range">
                        <span class="ldsp-melon-range-label">楼层范围</span>
                        <input type="number" class="ldsp-melon-range-input" id="melon-start" value="1" min="1" max="${totalPosts}">
                        <span class="ldsp-melon-range-sep">~</span>
                        <input type="number" class="ldsp-melon-range-input" id="melon-end" value="${defaultEnd}" min="1" max="${totalPosts}">
                        <span class="ldsp-melon-range-hint">${rangeHint}</span>
                    </div>
                    <div class="ldsp-melon-mode-selector">
                        <span class="ldsp-melon-mode-label">总结模式</span>
                        <div class="ldsp-melon-mode-cards">
                            <label class="ldsp-melon-mode-card ${this._summaryMode === 'brief' ? 'active' : ''}">
                                <input type="radio" name="melon-mode" value="brief" ${this._summaryMode === 'brief' ? 'checked' : ''}>
                                <div class="ldsp-melon-mode-card-icon">⚡</div>
                                <div class="ldsp-melon-mode-card-content">
                                    <div class="ldsp-melon-mode-card-title">简略模式</div>
                                    <div class="ldsp-melon-mode-card-desc">~150字快速概要</div>
                                </div>
                            </label>
                            <label class="ldsp-melon-mode-card ${this._summaryMode === 'detailed' ? 'active' : ''}">
                                <input type="radio" name="melon-mode" value="detailed" ${this._summaryMode === 'detailed' ? 'checked' : ''}>
                                <div class="ldsp-melon-mode-card-icon">📊</div>
                                <div class="ldsp-melon-mode-card-content">
                                    <div class="ldsp-melon-mode-card-title">详细模式</div>
                                    <div class="ldsp-melon-mode-card-desc">完整结构化分析</div>
                                </div>
                            </label>
                        </div>
                    </div>
                    <div class="ldsp-melon-actions">
                        <button class="ldsp-melon-btn-summarize" id="melon-summarize" ${!hasConfig ? 'disabled' : ''}>
                            <span>🍉</span>
                            <span>立即吃瓜</span>
                        </button>
                    </div>
                    <div class="ldsp-melon-output-wrapper">
                        <div class="ldsp-melon-output-header" style="display:none;">
                            <span class="ldsp-melon-output-title">📝 总结结果</span>
                            <div class="ldsp-melon-output-actions">
                                <button class="ldsp-melon-resize-btn" id="melon-expand" title="在新窗口中展开查看">
                                    <span>🔍</span>
                                    <span>展开</span>
                                </button>
                                <button class="ldsp-melon-copy-btn" id="melon-copy" title="复制到剪贴板">
                                    <span>📋</span>
                                    <span>复制</span>
                                </button>
                            </div>
                        </div>
                        <div class="ldsp-melon-output" id="melon-output"></div>
                    </div>`;

                // 绑定模式选择
                body.querySelectorAll('input[name="melon-mode"]').forEach(radio => {
                    radio.addEventListener('change', (e) => {
                        this._summaryMode = e.target.value;
                        // 更新卡片active状态
                        body.querySelectorAll('.ldsp-melon-mode-card').forEach(card => {
                            card.classList.toggle('active', card.querySelector('input').value === e.target.value);
                        });
                    });
                });

                // 绑定复制按钮
                body.querySelector('#melon-copy').addEventListener('click', () => this._copyOutput());
                
                // 绑定展开按钮 - 打开独立大窗口
                body.querySelector('#melon-expand').addEventListener('click', () => {
                    if (this._currentOutput) {
                        this._showViewer({
                            title: info.title,
                            summary: this._currentOutput,
                            mode: this._summaryMode,
                            topicId: info.id
                        });
                    }
                });

                body.querySelector('#melon-summarize').addEventListener('click', () => this._doSummarize(info));
            }

            async _copyOutput() {
                if (!this._currentOutput) {
                    return;
                }
                try {
                    await navigator.clipboard.writeText(this._currentOutput);
                    const copyBtn = this.overlay.querySelector('#melon-copy');
                    if (copyBtn) {
                        const originalText = copyBtn.innerHTML;
                        copyBtn.innerHTML = '<span>✅</span><span>已复制</span>';
                        setTimeout(() => {
                            copyBtn.innerHTML = originalText;
                        }, 1500);
                    }
                } catch (e) {
                    Logger.error('[MelonHelper] Copy failed:', e);
                }
            }

            async _doSummarize(topicInfo) {
                const body = this.overlay.querySelector('.ldsp-melon-body');
                const btn = body.querySelector('#melon-summarize');
                const output = body.querySelector('#melon-output');
                const outputHeader = body.querySelector('.ldsp-melon-output-header');
                const startInput = body.querySelector('#melon-start');
                const endInput = body.querySelector('#melon-end');

                const start = parseInt(startInput.value) || 1;
                const end = parseInt(endInput.value) || topicInfo.postsCount;

                Logger.log('[MelonHelper] Starting summarize:', { topicId: topicInfo.id, start, end, mode: this._summaryMode });

                if (start > end) {
                    output.innerHTML = '<div class="ldsp-melon-error">❌ 起始楼层不能大于结束楼层</div>';
                    return;
                }

                if (start < 1) {
                    output.innerHTML = '<div class="ldsp-melon-error">❌ 起始楼层不能小于1</div>';
                    return;
                }

                if (!this.config.apiUrl || !this.config.apiKey) {
                    output.innerHTML = '<div class="ldsp-melon-error">❌ 请先在「设置」中配置 API 地址和密钥</div>';
                    return;
                }

                btn.disabled = true;
                btn.classList.add('loading');
                this._currentOutput = '';  // 清空当前输出
                outputHeader.style.display = 'none';  // 隐藏复制按钮
                
                const updateStatus = (msg) => {
                    btn.innerHTML = `<span>⏳</span><span>${msg}</span>`;
                };
                
                updateStatus('获取帖子内容...');
                output.innerHTML = '<div class="ldsp-melon-status"><div class="ldsp-melon-status-icon">🔄</div>正在获取帖子内容...</div>';

                try {
                    // 1. 获取帖子内容
                    const dialogues = await this._fetchDialogues(topicInfo.id, start, end, updateStatus);
                    
                    Logger.log('[MelonHelper] Dialogues length:', dialogues?.length);
                    
                    if (!dialogues || dialogues.trim().length < 20) {
                        throw new Error('获取帖子内容失败或内容为空');
                    }

                    updateStatus('AI 分析中...');
                    output.innerHTML = '<div class="ldsp-melon-output-content"></div><div class="ldsp-melon-cursor">▌</div>';

                    // 2. 根据模式选择提示词(优先使用自定义提示词)
                    const prompt = this._summaryMode === 'brief' 
                        ? (this.config.promptBrief || MelonHelper.PROMPT_BRIEF)
                        : (this.config.promptDetailed || MelonHelper.PROMPT_DETAILED);
                    const userContent = `话题标题: ${topicInfo.title}\n\n帖子内容 (第${start}楼 ~ 第${end}楼):\n${dialogues}`;

                    Logger.log('[MelonHelper] Calling AI, content length:', userContent.length, 'mode:', this._summaryMode);
                    this._abortController = new AbortController();

                    // 3. 流式调用 AI 接口
                    await this._callAIStream(prompt, userContent, this._abortController.signal, (chunk) => {
                        // 增量更新
                        this._currentOutput += chunk;
                        const contentDiv = output.querySelector('.ldsp-melon-output-content');
                        if (contentDiv) {
                            contentDiv.innerHTML = this._renderMarkdown(this._currentOutput);
                            // 自动滚动到底部
                            output.scrollTop = output.scrollHeight;
                        }
                    });
                    
                    // 移除光标
                    const cursor = output.querySelector('.ldsp-melon-cursor');
                    if (cursor) cursor.remove();
                    
                    Logger.log('[MelonHelper] AI response complete, length:', this._currentOutput?.length);
                    
                    // 显示复制按钮
                    outputHeader.style.display = 'flex';

                    // 保存到历史
                    this._addToHistory(topicInfo.id, topicInfo.title, this._currentOutput, this._summaryMode);
                    
                    this.renderer?.showToast('✅ 吃瓜完成!');

                } catch (e) {
                    Logger.error('[MelonHelper] Summarize error:', e);
                    // 移除光标
                    const cursor = output.querySelector('.ldsp-melon-cursor');
                    if (cursor) cursor.remove();
                    
                    if (e.name === 'AbortError') {
                        output.innerHTML = '<div class="ldsp-melon-status"><div class="ldsp-melon-status-icon">⏹️</div>已取消</div>';
                    } else {
                        output.innerHTML = `<div class="ldsp-melon-error">❌ ${Utils.escapeHtml(e.message || '请求失败')}</div>`;
                    }
                } finally {
                    btn.disabled = false;
                    btn.classList.remove('loading');
                    btn.innerHTML = '<span>🍉</span><span>立即吃瓜</span>';
                    this._abortController = null;
                }
            }

            // 流式 AI 调用
            async _callAIStream(systemPrompt, userContent, signal, onChunk, messages = null) {
                const { apiUrl, apiKey, model } = this.config;

                // 构建消息数组(支持对话历史)
                const msgArray = messages || [
                    { role: 'system', content: systemPrompt },
                    { role: 'user', content: userContent }
                ];

                // 构建流式请求体
                const requestBody = {
                    model: model || 'gpt-4o-mini',
                    messages: msgArray,
                    max_tokens: 4096,
                    temperature: 0.7,
                    stream: true  // 启用流式输出
                };

                Logger.log('[MelonHelper] Calling AI API (stream):', apiUrl);

                try {
                    const response = await fetch(apiUrl, {
                        method: 'POST',
                        headers: {
                            'Content-Type': 'application/json',
                            'Authorization': `Bearer ${apiKey}`
                        },
                        body: JSON.stringify(requestBody),
                        signal: signal
                    });

                    Logger.log('[MelonHelper] API response status:', response.status);

                    if (!response.ok) {
                        let errMsg = `请求失败 (${response.status})`;
                        try {
                            const errData = await response.json();
                            errMsg = errData.error?.message || errMsg;
                            Logger.error('[MelonHelper] API error response:', errData);
                        } catch {}
                        throw new Error(errMsg);
                    }

                    // 处理 SSE 流式响应
                    const reader = response.body.getReader();
                    const decoder = new TextDecoder();
                    let buffer = '';

                    while (true) {
                        const { done, value } = await reader.read();
                        if (done) break;

                        buffer += decoder.decode(value, { stream: true });
                        const lines = buffer.split('\n');
                        buffer = lines.pop() || '';  // 保留未完成的行

                        for (const line of lines) {
                            const trimmed = line.trim();
                            if (!trimmed || trimmed === 'data: [DONE]') continue;
                            if (!trimmed.startsWith('data: ')) continue;

                            try {
                                const json = JSON.parse(trimmed.slice(6));
                                const content = json.choices?.[0]?.delta?.content;
                                if (content) {
                                    onChunk(content);
                                }
                            } catch (e) {
                                // 忽略解析错误
                                Logger.log('[MelonHelper] SSE parse skip:', trimmed);
                            }
                        }
                    }

                    Logger.log('[MelonHelper] Stream complete');
                } catch (e) {
                    if (e.name === 'AbortError') {
                        throw e;
                    }
                    Logger.error('[MelonHelper] Stream fetch error:', e);
                    if (e.message === 'Failed to fetch' || e.name === 'TypeError') {
                        throw new Error('网络请求失败,请检查 API 地址是否正确,或该 API 是否支持跨域请求');
                    }
                    throw e;
                }
            }

            _renderMarkdown(md) {
                if (!md) return '';
                
                let html = md;
                
                // 1. 保护代码块,先提取出来
                const codeBlocks = [];
                html = html.replace(/```(\w*)\n([\s\S]*?)```/g, (match, lang, code) => {
                    const placeholder = `%%CODEBLOCK_${codeBlocks.length}%%`;
                    codeBlocks.push(`<pre class="ldsp-melon-codeblock"><code>${Utils.escapeHtml(code.trim())}</code></pre>`);
                    return placeholder;
                });
                
                // 2. 行内代码
                const inlineCodes = [];
                html = html.replace(/`([^`\n]+)`/g, (match, code) => {
                    const placeholder = `%%INLINECODE_${inlineCodes.length}%%`;
                    inlineCodes.push(`<code class="ldsp-melon-inline-code">${Utils.escapeHtml(code)}</code>`);
                    return placeholder;
                });
                
                // 3. 标题 - 支持 emoji 开头的标题
                html = html.replace(/^#### (.+)$/gm, '<h5 class="ldsp-melon-h5">$1</h5>');
                html = html.replace(/^### (.+)$/gm, '<h4 class="ldsp-melon-h4">$1</h4>');
                html = html.replace(/^## (.+)$/gm, '<h3 class="ldsp-melon-h3">$1</h3>');
                html = html.replace(/^# (.+)$/gm, '<h2 class="ldsp-melon-h2">$1</h2>');
                
                // 4. 粗体 - 修复跨行和 emoji 后的情况
                html = html.replace(/\*\*([^*]+?)\*\*/g, '<strong>$1</strong>');
                html = html.replace(/__([^_]+?)__/g, '<strong>$1</strong>');
                
                // 5. 斜体
                html = html.replace(/(?<!\*)\*([^*\n]+?)\*(?!\*)/g, '<em>$1</em>');
                html = html.replace(/(?<!_)_([^_\n]+?)_(?!_)/g, '<em>$1</em>');
                
                // 6. 引用块
                html = html.replace(/^> (.+)$/gm, '<blockquote class="ldsp-melon-quote">$1</blockquote>');
                html = html.replace(/<\/blockquote>\n<blockquote class="ldsp-melon-quote">/g, '<br>');
                
                // 7. 无序列表
                html = html.replace(/^[-*] (.+)$/gm, '<li class="ldsp-melon-li">$1</li>');
                html = html.replace(/((?:<li class="ldsp-melon-li">[^<]*<\/li>\n?)+)/g, '<ul class="ldsp-melon-ul">$1</ul>');
                
                // 8. 有序列表
                html = html.replace(/^\d+\. (.+)$/gm, '<li class="ldsp-melon-oli">$1</li>');
                html = html.replace(/((?:<li class="ldsp-melon-oli">[^<]*<\/li>\n?)+)/g, '<ol class="ldsp-melon-ol">$1</ol>');
                
                // 9. 链接
                html = html.replace(/\[([^\]]+)\]\(([^)]+)\)/g, '<a href="$2" target="_blank" class="ldsp-melon-link">$1</a>');
                
                // 10. 分隔线
                html = html.replace(/^---+$/gm, '<hr class="ldsp-melon-hr">');
                html = html.replace(/^\*\*\*+$/gm, '<hr class="ldsp-melon-hr">');
                
                // 11. 段落处理
                html = html.replace(/\n\n+/g, '</p><p class="ldsp-melon-p">');
                html = html.replace(/\n/g, '<br>');
                
                // 12. 清理
                html = html.replace(/<p class="ldsp-melon-p"><\/p>/g, '');
                html = html.replace(/<p class="ldsp-melon-p">(<h[2-5])/g, '$1');
                html = html.replace(/(<\/h[2-5]>)<\/p>/g, '$1');
                html = html.replace(/<p class="ldsp-melon-p">(<ul)/g, '$1');
                html = html.replace(/(<\/ul>)<\/p>/g, '$1');
                html = html.replace(/<p class="ldsp-melon-p">(<ol)/g, '$1');
                html = html.replace(/(<\/ol>)<\/p>/g, '$1');
                html = html.replace(/<p class="ldsp-melon-p">(<blockquote)/g, '$1');
                html = html.replace(/(<\/blockquote>)<\/p>/g, '$1');
                html = html.replace(/<p class="ldsp-melon-p">(<pre)/g, '$1');
                html = html.replace(/(<\/pre>)<\/p>/g, '$1');
                html = html.replace(/<br><(h[2-5]|ul|ol|blockquote|pre)/g, '<$1');
                html = html.replace(/<\/(h[2-5]|ul|ol|blockquote|pre)><br>/g, '</$1>');
                
                // 13. 恢复代码块
                codeBlocks.forEach((block, i) => {
                    html = html.replace(`%%CODEBLOCK_${i}%%`, block);
                });
                inlineCodes.forEach((code, i) => {
                    html = html.replace(`%%INLINECODE_${i}%%`, code);
                });
                
                return `<div class="ldsp-melon-markdown"><p class="ldsp-melon-p">${html}</p></div>`;
            }

            _renderSettings() {
                const body = this.overlay.querySelector('.ldsp-melon-body');
                const hasConfig = this.config.apiUrl && this.config.apiKey;
                // 如果已有配置,默认不可编辑状态
                const isEditing = !hasConfig || this._isEditing;
                
                body.innerHTML = `
                    <div class="ldsp-melon-settings">
                        <div class="ldsp-melon-setting-security">
                            <div class="ldsp-melon-setting-security-icon">🔒</div>
                            <div class="ldsp-melon-setting-security-text">
                                <strong>数据安全</strong> · 您的 API Key 等配置仅保存在浏览器本地,不会上传至任何服务器
                            </div>
                        </div>
                        <div class="ldsp-melon-setting-group">
                            <div class="ldsp-melon-setting-title">🔑 API 配置</div>
                            <div class="ldsp-melon-setting-row">
                                <label class="ldsp-melon-setting-label">API 地址 <span style="color:var(--err)">*</span></label>
                                <input type="text" class="ldsp-melon-setting-input" id="melon-api-url" 
                                    placeholder="https://api.openai.com/v1/chat/completions" 
                                    value="${Utils.escapeHtml(this.config.apiUrl || '')}"
                                    ${!isEditing ? 'disabled' : ''}>
                                <div class="ldsp-melon-setting-hint">⚠️ 完整格式: https://xxxxxx<strong>/v1/chat/completions</strong>(别漏掉后缀)</div>
                            </div>
                            <div class="ldsp-melon-setting-row">
                                <label class="ldsp-melon-setting-label">API Key <span style="color:var(--err)">*</span></label>
                                <input type="${isEditing ? 'text' : 'password'}" class="ldsp-melon-setting-input" id="melon-api-key" 
                                    placeholder="sk-..." 
                                    value="${Utils.escapeHtml(this.config.apiKey || '')}"
                                    ${!isEditing ? 'disabled' : ''}>
                            </div>
                            <div class="ldsp-melon-setting-row">
                                <label class="ldsp-melon-setting-label">模型名称</label>
                                <input type="text" class="ldsp-melon-setting-input" id="melon-model" 
                                    placeholder="gemini-2.0-flash" 
                                    value="${Utils.escapeHtml(this.config.model || 'gpt-4o-mini')}"
                                    ${!isEditing ? 'disabled' : ''}>
                                <div class="ldsp-melon-setting-hint">只填一个模型名,推荐: <strong>gemini-2.0-flash</strong>、<strong>claude-3-haiku</strong>、<strong>deepseek-v3</strong></div>
                            </div>
                            <div class="ldsp-melon-setting-actions">
                                ${isEditing ? `
                                    <button class="ldsp-melon-setting-btn ldsp-melon-btn-save" id="melon-save-settings">💾 保存配置</button>
                                ` : `
                                    <button class="ldsp-melon-setting-btn ldsp-melon-btn-edit" id="melon-edit-settings">✏️ 修改配置</button>
                                `}
                            </div>
                        </div>
                        <div class="ldsp-melon-setting-group">
                            <div class="ldsp-melon-setting-title">📝 自定义提示词 <span class="ldsp-melon-setting-subtitle">(可选,留空用默认)</span></div>
                            <div class="ldsp-melon-setting-row">
                                <label class="ldsp-melon-setting-label">
                                    简略模式
                                    <span class="ldsp-melon-prompt-reset" data-prompt="brief" title="清空并恢复默认">🗑️</span>
                                    <span class="ldsp-melon-prompt-status ${this.config.promptBrief ? 'custom' : ''}">${this.config.promptBrief ? '✅ 已自定义' : '默认'}</span>
                                </label>
                                <textarea class="ldsp-melon-setting-textarea" id="melon-prompt-brief" 
                                    placeholder="${Utils.escapeHtml(MelonHelper.PROMPT_BRIEF.trim())}"
                                    rows="4">${Utils.escapeHtml(this.config.promptBrief || '')}</textarea>
                            </div>
                            <div class="ldsp-melon-setting-row">
                                <label class="ldsp-melon-setting-label">
                                    详细模式
                                    <span class="ldsp-melon-prompt-reset" data-prompt="detailed" title="清空并恢复默认">🗑️</span>
                                    <span class="ldsp-melon-prompt-status ${this.config.promptDetailed ? 'custom' : ''}">${this.config.promptDetailed ? '✅ 已自定义' : '默认'}</span>
                                </label>
                                <textarea class="ldsp-melon-setting-textarea" id="melon-prompt-detailed" 
                                    placeholder="${Utils.escapeHtml(MelonHelper.PROMPT_DETAILED.trim())}"
                                    rows="6">${Utils.escapeHtml(this.config.promptDetailed || '')}</textarea>
                            </div>
                            <div class="ldsp-melon-setting-actions">
                                <button class="ldsp-melon-setting-btn ldsp-melon-btn-prompt" id="melon-save-prompts">💾 保存提示词</button>
                            </div>
                        </div>
                        <div class="ldsp-melon-setting-group ldsp-melon-setting-danger">
                            <div class="ldsp-melon-setting-danger-content">
                                <div class="ldsp-melon-setting-danger-info">
                                    <div class="ldsp-melon-setting-title">🗑️ 重置配置</div>
                                    <div class="ldsp-melon-setting-danger-desc">清空 API 配置和自定义提示词</div>
                                </div>
                                <button class="ldsp-melon-setting-btn ldsp-melon-btn-danger" id="melon-clear-all-data">重置</button>
                            </div>
                        </div>
                    </div>`;

                if (isEditing) {
                    body.querySelector('#melon-save-settings').addEventListener('click', () => {
                        const apiUrl = body.querySelector('#melon-api-url').value.trim();
                        const apiKey = body.querySelector('#melon-api-key').value.trim();
                        const model = body.querySelector('#melon-model').value.trim() || 'gpt-4o-mini';
                        
                        // 验证必填项
                        if (!apiUrl) {
                            this.renderer?.showToast('⚠️ 请输入 API 地址');
                            return;
                        }
                        if (!apiKey) {
                            this.renderer?.showToast('⚠️ 请输入 API Key');
                            return;
                        }
                        
                        // 简单验证 URL 格式
                        if (!apiUrl.startsWith('http://') && !apiUrl.startsWith('https://')) {
                            this.renderer?.showToast('⚠️ API 地址格式不正确');
                            return;
                        }
                        
                        this.config.apiUrl = apiUrl;
                        this.config.apiKey = apiKey;
                        this.config.model = model;
                        this._saveConfig();
                        this._isEditing = false;
                        this.renderer?.showToast('✅ 设置已保存');
                        this._renderSettings();  // 重新渲染为不可编辑状态
                    });
                } else {
                    body.querySelector('#melon-edit-settings').addEventListener('click', () => {
                        this._isEditing = true;
                        this._renderSettings();  // 重新渲染为可编辑状态
                    });
                }
                
                // 保存提示词按钮
                body.querySelector('#melon-save-prompts')?.addEventListener('click', () => {
                    const promptBrief = body.querySelector('#melon-prompt-brief').value.trim();
                    const promptDetailed = body.querySelector('#melon-prompt-detailed').value.trim();
                    this.config.promptBrief = promptBrief;
                    this.config.promptDetailed = promptDetailed;
                    this._saveConfig();
                    this.renderer?.showToast('✅ 提示词已保存');
                    // 更新状态提示
                    const hintBrief = body.querySelector('#melon-prompt-brief')?.parentElement?.querySelector('.ldsp-melon-setting-hint');
                    const hintDetailed = body.querySelector('#melon-prompt-detailed')?.parentElement?.querySelector('.ldsp-melon-setting-hint');
                    if (hintBrief) hintBrief.textContent = promptBrief ? '✅ 已自定义' : '💡 使用默认提示词';
                    if (hintDetailed) hintDetailed.textContent = promptDetailed ? '✅ 已自定义' : '💡 使用默认提示词';
                });
                
                // 恢复默认提示词
                body.querySelectorAll('.ldsp-melon-prompt-reset').forEach(btn => {
                    btn.addEventListener('click', () => {
                        const type = btn.dataset.prompt;
                        if (type === 'brief') {
                            body.querySelector('#melon-prompt-brief').value = '';
                            this.config.promptBrief = '';
                        } else {
                            body.querySelector('#melon-prompt-detailed').value = '';
                            this.config.promptDetailed = '';
                        }
                        this._saveConfig();
                        this.renderer?.showToast('✅ 已恢复默认提示词');
                    });
                });
                
                // 重置配置按钮(只清空配置和提示词,不清空历史记录)
                body.querySelector('#melon-clear-all-data')?.addEventListener('click', () => {
                    this._showConfirm('确定要重置配置吗?<br>将清空 API 配置和自定义提示词,历史记录不受影响。', () => {
                        // 清空配置和提示词
                        this.config = { apiUrl: '', apiKey: '', model: 'gpt-4o-mini', promptBrief: '', promptDetailed: '' };
                        this._saveConfig();
                        this._isEditing = false;
                        this.renderer?.showToast('✅ 配置已重置');
                        this._renderSettings();
                    });
                });
            }

            _renderHistory() {
                const body = this.overlay.querySelector('.ldsp-melon-body');
                
                if (this.history.length === 0) {
                    body.innerHTML = `
                        <div class="ldsp-melon-history-empty">
                            <div class="ldsp-melon-history-empty-icon">📭</div>
                            <div class="ldsp-melon-history-empty-text">暂无历史记录</div>
                            <div class="ldsp-melon-history-empty-hint">使用吃瓜助手总结话题后会自动保存到这里</div>
                            <div class="ldsp-melon-history-storage-hint">💾 数据仅存储在浏览器本地</div>
                        </div>`;
                    return;
                }
                
                const historyHtml = this.history.map((h, idx) => {
                    const date = new Date(h.timestamp);
                    const dateStr = `${date.getMonth() + 1}/${date.getDate()} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
                    const modeLabel = h.mode === 'brief' ? '简略' : '详细';
                    const previewText = h.summary.slice(0, 100).replace(/\n/g, ' ') + (h.summary.length > 100 ? '...' : '');
                    
                    return `
                        <div class="ldsp-melon-history-item" data-index="${idx}">
                            <div class="ldsp-melon-history-item-header">
                                <span class="ldsp-melon-history-item-title" title="${Utils.escapeHtml(h.title)}">${Utils.escapeHtml(h.title)}</span>
                                <span class="ldsp-melon-history-item-meta">
                                    <span class="ldsp-melon-history-mode ${h.mode}">${modeLabel}</span>
                                    <span class="ldsp-melon-history-date">${dateStr}</span>
                                </span>
                            </div>
                            <div class="ldsp-melon-history-item-preview">${Utils.escapeHtml(previewText)}</div>
                            <div class="ldsp-melon-history-item-actions">
                                <button class="ldsp-melon-history-btn ldsp-melon-history-view" data-idx="${idx}" title="查看完整内容">👁️ 查看</button>
                                <button class="ldsp-melon-history-btn ldsp-melon-history-expand" data-idx="${idx}" title="展开大窗口查看">🔍 展开</button>
                                <button class="ldsp-melon-history-btn ldsp-melon-history-copy" data-idx="${idx}" title="复制到剪贴板">📋 复制</button>
                                <button class="ldsp-melon-history-btn ldsp-melon-history-goto" data-topic="${h.topicId}" title="跳转到话题">🔗 话题</button>
                                <button class="ldsp-melon-history-btn ldsp-melon-history-delete" data-idx="${idx}" title="删除此记录">🗑️</button>
                            </div>
                        </div>`;
                }).join('');
                
                body.innerHTML = `
                    <div class="ldsp-melon-history">
                        <div class="ldsp-melon-history-header">
                            <div class="ldsp-melon-history-header-left">
                                <span class="ldsp-melon-history-count">共 ${this.history.length} 条记录</span>
                                <span class="ldsp-melon-history-storage-badge">💾 本地存储</span>
                            </div>
                            <button class="ldsp-melon-history-clear-all" id="melon-clear-history">🗑️ 清空全部</button>
                        </div>
                        <div class="ldsp-melon-history-list">
                            ${historyHtml}
                        </div>
                    </div>`;
                
                // 绑定事件 - 使用自定义确认框
                body.querySelector('#melon-clear-history').addEventListener('click', () => {
                    this._showConfirm('确定要清空所有历史记录吗?<br>此操作不可撤销。', () => {
                        this._clearHistory();
                        this.renderer?.showToast('✅ 历史记录已清空');
                        this._renderHistory();
                    });
                });
                
                body.querySelectorAll('.ldsp-melon-history-view').forEach(btn => {
                    btn.addEventListener('click', () => {
                        const idx = parseInt(btn.dataset.idx);
                        this._showHistoryDetail(idx);
                    });
                });
                
                body.querySelectorAll('.ldsp-melon-history-copy').forEach(btn => {
                    btn.addEventListener('click', async () => {
                        const idx = parseInt(btn.dataset.idx);
                        const record = this.history[idx];
                        if (record) {
                            try {
                                await navigator.clipboard.writeText(record.summary);
                                btn.textContent = '✅ 已复制';
                                setTimeout(() => { btn.innerHTML = '📋 复制'; }, 1500);
                            } catch (e) {
                                Logger.error('[MelonHelper] Copy history failed:', e);
                            }
                        }
                    });
                });
                
                body.querySelectorAll('.ldsp-melon-history-goto').forEach(btn => {
                    btn.addEventListener('click', () => {
                        const topicId = btn.dataset.topic;
                        window.open(`${location.origin}/t/${topicId}`, '_blank');
                    });
                });
                
                body.querySelectorAll('.ldsp-melon-history-delete').forEach(btn => {
                    btn.addEventListener('click', () => {
                        const idx = parseInt(btn.dataset.idx);
                        this.history.splice(idx, 1);
                        this._saveHistory();
                        this.renderer?.showToast('✅ 已删除');
                        this._renderHistory();
                    });
                });
                
                // 绑定展开按钮 - 打开独立大窗口
                body.querySelectorAll('.ldsp-melon-history-expand').forEach(btn => {
                    btn.addEventListener('click', () => {
                        const idx = parseInt(btn.dataset.idx);
                        const record = this.history[idx];
                        if (record) {
                            this._showViewer({
                                title: record.title,
                                summary: record.summary,
                                mode: record.mode,
                                topicId: record.topicId,
                                timestamp: record.timestamp
                            });
                        }
                    });
                });
            }

            _showHistoryDetail(idx) {
                const record = this.history[idx];
                if (!record) return;
                
                const body = this.overlay.querySelector('.ldsp-melon-body');
                const date = new Date(record.timestamp);
                const dateStr = `${date.getFullYear()}/${date.getMonth() + 1}/${date.getDate()} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
                const modeLabel = record.mode === 'brief' ? '简略模式' : '详细模式';
                
                body.innerHTML = `
                    <div class="ldsp-melon-history-detail">
                        <div class="ldsp-melon-history-detail-header">
                            <button class="ldsp-melon-history-back" id="melon-history-back">← 返回列表</button>
                            <div class="ldsp-melon-history-detail-actions">
                                <button class="ldsp-melon-history-expand-btn" id="melon-history-expand">🔍 展开</button>
                                <button class="ldsp-melon-history-copy-all" id="melon-history-copy-all">📋 复制</button>
                            </div>
                        </div>
                        <div class="ldsp-melon-history-detail-info">
                            <div class="ldsp-melon-history-detail-title">${Utils.escapeHtml(record.title)}</div>
                            <div class="ldsp-melon-history-detail-meta">${modeLabel} · ${dateStr}</div>
                        </div>
                        <div class="ldsp-melon-history-detail-content">
                            ${this._renderMarkdown(record.summary)}
                        </div>
                    </div>`;
                
                body.querySelector('#melon-history-back').addEventListener('click', () => {
                    this._renderHistory();
                });
                
                // 展开查看按钮
                body.querySelector('#melon-history-expand').addEventListener('click', () => {
                    this._showViewer({
                        title: record.title,
                        summary: record.summary,
                        mode: record.mode,
                        topicId: record.topicId,
                        timestamp: record.timestamp
                    });
                });
                
                body.querySelector('#melon-history-copy-all').addEventListener('click', async () => {
                    try {
                        await navigator.clipboard.writeText(record.summary);
                        const btn = body.querySelector('#melon-history-copy-all');
                        btn.textContent = '✅ 已复制';
                        setTimeout(() => { btn.innerHTML = '📋 复制'; }, 1500);
                    } catch (e) {
                        Logger.error('[MelonHelper] Copy detail failed:', e);
                    }
                });
            }

            destroy() {
                this._stopUrlWatch();
                this._closeViewer();
                if (this._abortController) {
                    this._abortController.abort();
                    this._abortController = null;
                }
                if (this.overlay) {
                    this.overlay.remove();
                    this.overlay = null;
                }
            }
            
            // 显示全屏可调整大小的查看器(支持对话功能)
            _showViewer(data) {
                // 移除已存在的 viewer
                this._closeViewer();
                
                const dateStr = data.timestamp 
                    ? new Date(data.timestamp).toLocaleString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
                    : '刚刚';
                const modeLabel = data.mode === 'brief' ? '简略' : '详细';
                
                // 检测当前主题
                const isLightTheme = document.querySelector('#ldsp-panel.light') !== null;
                
                // 初始化对话历史
                this._chatMessages = [
                    { role: 'system', content: `你是一个帮助用户理解论坛帖子内容的助手。以下是帖子的总结:\n\n${data.summary}\n\n请基于这个总结回答用户的问题。如果问题超出总结内容范围,请告知用户。` },
                    { role: 'assistant', content: data.summary }
                ];
                this._chatAbortController = null;
                
                const overlay = document.createElement('div');
                overlay.className = 'ldsp-melon-viewer-overlay' + (isLightTheme ? ' light' : '');
                overlay.innerHTML = `
                    <div class="ldsp-melon-viewer" style="width:700px;height:600px;">
                        <div class="ldsp-melon-viewer-header">
                            <div class="ldsp-melon-viewer-title">
                                <span class="ldsp-melon-viewer-title-icon">🍈</span>
                                <span>吃瓜详情</span>
                            </div>
                            <div class="ldsp-melon-viewer-actions">
                                <div class="ldsp-melon-font-btn" id="viewer-font" title="调整字体大小">
                                    <span>Aa</span>
                                    <div class="ldsp-melon-font-dropdown" id="font-dropdown">
                                        <div class="ldsp-melon-font-option" data-size="xs">小</div>
                                        <div class="ldsp-melon-font-option" data-size="sm">较小</div>
                                        <div class="ldsp-melon-font-option active" data-size="md">标准</div>
                                        <div class="ldsp-melon-font-option" data-size="lg">较大</div>
                                        <div class="ldsp-melon-font-option" data-size="xl">大</div>
                                    </div>
                                </div>
                                <button class="ldsp-melon-viewer-btn" id="viewer-copy" title="复制全部内容">📋</button>
                                <button class="ldsp-melon-viewer-btn" id="viewer-goto" title="跳转到话题">🔗</button>
                                <button class="ldsp-melon-viewer-btn ldsp-melon-viewer-close" id="viewer-close" title="关闭">✕</button>
                            </div>
                        </div>
                        <div class="ldsp-melon-viewer-body">
                            <div class="ldsp-melon-viewer-info">
                                <div class="ldsp-melon-viewer-topic-title">${Utils.escapeHtml(data.title)}</div>
                                <div class="ldsp-melon-viewer-meta">
                                    <span class="ldsp-melon-viewer-mode ${data.mode}">${modeLabel}模式</span>
                                    <span>${dateStr}</span>
                                </div>
                            </div>
                            <div class="ldsp-melon-viewer-content" id="viewer-chat-content">
                                <div class="ldsp-melon-chat-msg assistant">
                                    <div class="ldsp-melon-chat-msg-label">📝 AI 总结</div>
                                    <div class="ldsp-melon-chat-msg-content">${this._renderMarkdown(data.summary)}</div>
                                </div>
                            </div>
                        </div>
                        <div class="ldsp-melon-viewer-chat">
                            <div class="ldsp-melon-chat-input-wrap">
                                <input type="text" class="ldsp-melon-chat-input" id="viewer-chat-input" placeholder="输入问题继续追问..." />
                                <button class="ldsp-melon-chat-send" id="viewer-chat-send" title="发送">
                                    <svg viewBox="0 0 24 24" width="18" height="18" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>
                                </button>
                            </div>
                            <div class="ldsp-melon-chat-hint">💡 基于以上总结内容进行追问</div>
                        </div>
                        <div class="ldsp-melon-resize-handle ldsp-melon-resize-handle-e"></div>
                        <div class="ldsp-melon-resize-handle ldsp-melon-resize-handle-s"></div>
                        <div class="ldsp-melon-resize-handle ldsp-melon-resize-handle-se"></div>
                        <div class="ldsp-melon-resize-handle ldsp-melon-resize-handle-w"></div>
                        <div class="ldsp-melon-resize-handle ldsp-melon-resize-handle-n"></div>
                        <div class="ldsp-melon-resize-handle ldsp-melon-resize-handle-nw"></div>
                        <div class="ldsp-melon-resize-handle ldsp-melon-resize-handle-ne"></div>
                        <div class="ldsp-melon-resize-handle ldsp-melon-resize-handle-sw"></div>
                    </div>`;
                
                document.body.appendChild(overlay);
                this._viewerOverlay = overlay;
                
                const viewer = overlay.querySelector('.ldsp-melon-viewer');
                const header = overlay.querySelector('.ldsp-melon-viewer-header');
                const chatInput = overlay.querySelector('#viewer-chat-input');
                const chatSendBtn = overlay.querySelector('#viewer-chat-send');
                const chatContent = overlay.querySelector('#viewer-chat-content');
                
                // 对话发送功能
                const sendChat = async () => {
                    const question = chatInput.value.trim();
                    if (!question) return;
                    if (!this.config.apiUrl || !this.config.apiKey) {
                        this.renderer?.showToast('⚠️ 请先配置 API');
                        return;
                    }
                    
                    chatInput.value = '';
                    chatInput.disabled = true;
                    chatSendBtn.disabled = true;
                    chatSendBtn.classList.add('loading');
                    
                    // 添加用户消息
                    const userMsgEl = document.createElement('div');
                    userMsgEl.className = 'ldsp-melon-chat-msg user';
                    userMsgEl.innerHTML = `
                        <div class="ldsp-melon-chat-msg-label">🙋 你的问题</div>
                        <div class="ldsp-melon-chat-msg-content">${Utils.escapeHtml(question)}</div>`;
                    chatContent.appendChild(userMsgEl);
                    
                    // 添加 AI 响应占位
                    const aiMsgEl = document.createElement('div');
                    aiMsgEl.className = 'ldsp-melon-chat-msg assistant';
                    aiMsgEl.innerHTML = `
                        <div class="ldsp-melon-chat-msg-label">🤖 AI 回复</div>
                        <div class="ldsp-melon-chat-msg-content"><span class="ldsp-melon-chat-typing">思考中...</span></div>`;
                    chatContent.appendChild(aiMsgEl);
                    chatContent.scrollTop = chatContent.scrollHeight;
                    
                    // 更新对话历史
                    this._chatMessages.push({ role: 'user', content: question });
                    
                    let aiResponse = '';
                    this._chatAbortController = new AbortController();
                    
                    try {
                        await this._callAIStream(null, null, this._chatAbortController.signal, (chunk) => {
                            aiResponse += chunk;
                            const contentEl = aiMsgEl.querySelector('.ldsp-melon-chat-msg-content');
                            if (contentEl) {
                                contentEl.innerHTML = this._renderMarkdown(aiResponse);
                                chatContent.scrollTop = chatContent.scrollHeight;
                            }
                        }, this._chatMessages);
                        
                        // 保存 AI 响应到对话历史
                        this._chatMessages.push({ role: 'assistant', content: aiResponse });
                        
                    } catch (e) {
                        Logger.error('[MelonHelper] Chat error:', e);
                        const contentEl = aiMsgEl.querySelector('.ldsp-melon-chat-msg-content');
                        if (contentEl) {
                            if (e.name === 'AbortError') {
                                contentEl.innerHTML = '<span style="color:var(--txt-mut)">已取消</span>';
                            } else {
                                contentEl.innerHTML = `<span style="color:#ef4444">❌ ${Utils.escapeHtml(e.message || '请求失败')}</span>`;
                            }
                        }
                        // 移除失败的用户消息
                        this._chatMessages.pop();
                    } finally {
                        chatInput.disabled = false;
                        chatSendBtn.disabled = false;
                        chatSendBtn.classList.remove('loading');
                        this._chatAbortController = null;
                        chatInput.focus();
                    }
                };
                
                chatSendBtn.addEventListener('click', sendChat);
                chatInput.addEventListener('keydown', (e) => {
                    if (e.key === 'Enter' && !e.shiftKey) {
                        e.preventDefault();
                        sendChat();
                    }
                });
                
                // 关闭按钮
                overlay.querySelector('#viewer-close').addEventListener('click', () => this._closeViewer());
                overlay.addEventListener('click', (e) => {
                    if (e.target === overlay) this._closeViewer();
                });
                
                // ESC 关闭
                this._viewerEscHandler = (e) => {
                    if (e.key === 'Escape') this._closeViewer();
                };
                document.addEventListener('keydown', this._viewerEscHandler);
                
                // 复制按钮 - 复制所有对话内容
                overlay.querySelector('#viewer-copy').addEventListener('click', async () => {
                    try {
                        // 收集所有对话内容
                        let allContent = `话题:${data.title}\n\n`;
                        this._chatMessages.forEach(msg => {
                            if (msg.role === 'assistant') {
                                allContent += `【AI】\n${msg.content}\n\n`;
                            } else if (msg.role === 'user') {
                                allContent += `【问】${msg.content}\n\n`;
                            }
                        });
                        await navigator.clipboard.writeText(allContent.trim());
                        const btn = overlay.querySelector('#viewer-copy');
                        btn.textContent = '✅';
                        setTimeout(() => { btn.textContent = '📋'; }, 1500);
                    } catch (e) {
                        Logger.error('[MelonHelper] Copy viewer content failed:', e);
                    }
                });
                
                // 跳转按钮
                overlay.querySelector('#viewer-goto').addEventListener('click', () => {
                    if (data.topicId) {
                        window.open(`${location.origin}/t/${data.topicId}`, '_blank');
                    }
                });
                
                // 字体大小调节
                const fontBtn = overlay.querySelector('#viewer-font');
                const fontDropdown = overlay.querySelector('#font-dropdown');
                const viewerContent = overlay.querySelector('#viewer-chat-content');
                
                // 从存储中读取字体大小设置
                const savedFontSize = GM_getValue('ldsp_melon_font_size', 'md');
                if (savedFontSize !== 'md') {
                    viewerContent.className = `ldsp-melon-viewer-content font-${savedFontSize}`;
                    fontDropdown.querySelectorAll('.ldsp-melon-font-option').forEach(opt => {
                        opt.classList.toggle('active', opt.dataset.size === savedFontSize);
                    });
                }
                
                fontBtn.addEventListener('click', (e) => {
                    e.stopPropagation();
                    fontDropdown.classList.toggle('show');
                });
                
                fontDropdown.querySelectorAll('.ldsp-melon-font-option').forEach(opt => {
                    opt.addEventListener('click', (e) => {
                        e.stopPropagation();
                        const size = opt.dataset.size;
                        // 更新样式
                        viewerContent.className = `ldsp-melon-viewer-content font-${size}`;
                        // 更新选中状态
                        fontDropdown.querySelectorAll('.ldsp-melon-font-option').forEach(o => {
                            o.classList.toggle('active', o.dataset.size === size);
                        });
                        // 保存设置
                        GM_setValue('ldsp_melon_font_size', size);
                        // 关闭下拉
                        fontDropdown.classList.remove('show');
                    });
                });
                
                // 点击其他区域关闭下拉菜单
                overlay.addEventListener('click', () => {
                    fontDropdown.classList.remove('show');
                });
                
                // 拖拽移动
                let isDragging = false;
                let startX, startY, startLeft, startTop;
                
                header.addEventListener('mousedown', (e) => {
                    if (e.target.closest('.ldsp-melon-viewer-btn')) return;
                    isDragging = true;
                    startX = e.clientX;
                    startY = e.clientY;
                    const rect = viewer.getBoundingClientRect();
                    startLeft = rect.left;
                    startTop = rect.top;
                    viewer.style.position = 'fixed';
                    viewer.style.left = `${startLeft}px`;
                    viewer.style.top = `${startTop}px`;
                    viewer.style.transform = 'none';
                    e.preventDefault();
                });
                
                document.addEventListener('mousemove', this._viewerMoveHandler = (e) => {
                    if (!isDragging) return;
                    const dx = e.clientX - startX;
                    const dy = e.clientY - startY;
                    viewer.style.left = `${startLeft + dx}px`;
                    viewer.style.top = `${startTop + dy}px`;
                });
                
                document.addEventListener('mouseup', this._viewerUpHandler = () => {
                    isDragging = false;
                });
                
                // 调整大小
                let isResizing = false;
                let resizeDir = '';
                let resizeStartX, resizeStartY, resizeStartW, resizeStartH, resizeStartL, resizeStartT;
                
                overlay.querySelectorAll('.ldsp-melon-resize-handle').forEach(handle => {
                    handle.addEventListener('mousedown', (e) => {
                        isResizing = true;
                        resizeDir = handle.className.replace('ldsp-melon-resize-handle ldsp-melon-resize-handle-', '');
                        resizeStartX = e.clientX;
                        resizeStartY = e.clientY;
                        const rect = viewer.getBoundingClientRect();
                        resizeStartW = rect.width;
                        resizeStartH = rect.height;
                        resizeStartL = rect.left;
                        resizeStartT = rect.top;
                        viewer.style.position = 'fixed';
                        viewer.style.left = `${resizeStartL}px`;
                        viewer.style.top = `${resizeStartT}px`;
                        viewer.style.transform = 'none';
                        e.preventDefault();
                        e.stopPropagation();
                    });
                });
                
                document.addEventListener('mousemove', this._viewerResizeHandler = (e) => {
                    if (!isResizing) return;
                    const dx = e.clientX - resizeStartX;
                    const dy = e.clientY - resizeStartY;
                    let newW = resizeStartW, newH = resizeStartH;
                    let newL = resizeStartL, newT = resizeStartT;
                    
                    if (resizeDir.includes('e')) newW = Math.max(320, resizeStartW + dx);
                    if (resizeDir.includes('w')) {
                        newW = Math.max(320, resizeStartW - dx);
                        newL = resizeStartL + (resizeStartW - newW);
                    }
                    if (resizeDir.includes('s')) newH = Math.max(240, resizeStartH + dy);
                    if (resizeDir.includes('n')) {
                        newH = Math.max(240, resizeStartH - dy);
                        newT = resizeStartT + (resizeStartH - newH);
                    }
                    
                    viewer.style.width = `${newW}px`;
                    viewer.style.height = `${newH}px`;
                    viewer.style.left = `${newL}px`;
                    viewer.style.top = `${newT}px`;
                });
                
                document.addEventListener('mouseup', this._viewerResizeUpHandler = () => {
                    isResizing = false;
                });
            }
            
            _closeViewer() {
                // 取消正在进行的对话请求
                if (this._chatAbortController) {
                    this._chatAbortController.abort();
                    this._chatAbortController = null;
                }
                // 清理对话历史
                this._chatMessages = null;
                
                if (this._viewerOverlay) {
                    this._viewerOverlay.remove();
                    this._viewerOverlay = null;
                }
                if (this._viewerEscHandler) {
                    document.removeEventListener('keydown', this._viewerEscHandler);
                    this._viewerEscHandler = null;
                }
                if (this._viewerMoveHandler) {
                    document.removeEventListener('mousemove', this._viewerMoveHandler);
                    this._viewerMoveHandler = null;
                }
                if (this._viewerUpHandler) {
                    document.removeEventListener('mouseup', this._viewerUpHandler);
                    this._viewerUpHandler = null;
                }
                if (this._viewerResizeHandler) {
                    document.removeEventListener('mousemove', this._viewerResizeHandler);
                    this._viewerResizeHandler = null;
                }
                if (this._viewerResizeUpHandler) {
                    document.removeEventListener('mouseup', this._viewerResizeUpHandler);
                    this._viewerResizeUpHandler = null;
                }
            }
            
            // 显示自定义确认对话框
            _showConfirm(message, onConfirm) {
                const existingDialog = this.overlay.querySelector('.ldsp-melon-confirm-dialog');
                if (existingDialog) existingDialog.remove();
                
                const dialog = document.createElement('div');
                dialog.className = 'ldsp-melon-confirm-dialog';
                dialog.innerHTML = `
                    <div class="ldsp-melon-confirm-content">
                        <div class="ldsp-melon-confirm-icon">⚠️</div>
                        <div class="ldsp-melon-confirm-message">${message}</div>
                        <div class="ldsp-melon-confirm-actions">
                            <button class="ldsp-melon-confirm-btn ldsp-melon-confirm-cancel">取消</button>
                            <button class="ldsp-melon-confirm-btn ldsp-melon-confirm-ok">确认</button>
                        </div>
                    </div>`;
                
                dialog.querySelector('.ldsp-melon-confirm-cancel').addEventListener('click', () => dialog.remove());
                dialog.querySelector('.ldsp-melon-confirm-ok').addEventListener('click', () => {
                    dialog.remove();
                    onConfirm();
                });
                dialog.addEventListener('click', (e) => {
                    if (e.target === dialog) dialog.remove();
                });
                
                this.overlay.appendChild(dialog);
            }
        }

        // ==================== 关注/粉丝管理器 ====================
        class FollowManager {
            static CACHE_KEY = 'ldsp_follow_cache';
            
            // SVG 图标定义
            static ICONS = {
                // 关注:人+右箭头(表示我关注的人/向外关注)
                following: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="10" cy="8" r="4"/><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><path d="M19 8h4m-2-2v4"/></svg>',
                // 粉丝:人+心形(表示喜欢我的人)
                followers: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="10" cy="8" r="4"/><path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><path d="M19.5 10.5c-.6-.6-1.5-.6-2.1 0l-.4.4-.4-.4c-.6-.6-1.5-.6-2.1 0-.6.6-.6 1.5 0 2.1l2.5 2.4 2.5-2.4c.6-.6.6-1.5 0-2.1z"/></svg>',
                // 用户组图标(用于标题)
                users: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="9" cy="7" r="4"/><path d="M3 21v-2a4 4 0 0 1 4-4h4a4 4 0 0 1 4 4v2"/><circle cx="17" cy="10" r="3"/><path d="M21 21v-1.5a3 3 0 0 0-3-3h-1"/></svg>',
                // 单人图标(用于空状态)
                user: '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor"><circle cx="12" cy="8" r="4"/><path d="M4 21v-2a4 4 0 0 1 4-4h8a4 4 0 0 1 4 4v2"/></svg>'
            };
            
            constructor(network, storage, panelBody, renderer) {
                this.network = network;
                this.storage = storage;
                this.panelBody = panelBody;
                this.renderer = renderer;
                this.overlay = null;
                this.following = [];
                this.followers = [];
                this.followingCount = 0;
                this.followersCount = 0;
                this.cakedate = null;
                this.animatedAvatar = null;
                this._loaded = false;
                this._loading = false;
                this._profileLoaded = false;
            }

            async init() {
                // 先从缓存加载数量显示
                this._loadFromCache();
                this._createOverlay();
            }
            
            // 从本地缓存加载
            _loadFromCache() {
                try {
                    const username = this.storage.getUser();
                    if (!username) return;
                    
                    const cacheKey = `${FollowManager.CACHE_KEY}_${CURRENT_SITE.prefix}_${username}`;
                    const cached = GM_getValue(cacheKey, null);
                    if (cached && typeof cached === 'object') {
                        this.followingCount = cached.followingCount || 0;
                        this.followersCount = cached.followersCount || 0;
                        this.cakedate = cached.cakedate || null;
                        this.animatedAvatar = cached.animatedAvatar || null;
                        // 立即更新显示
                        this._updateStats();
                        this._updateDays();
                    }
                } catch (e) {
                    // 缓存读取失败,忽略
                }
            }
            
            // 保存到本地缓存
            _saveToCache() {
                try {
                    const username = this.storage.getUser();
                    if (!username) return;
                    
                    const cacheKey = `${FollowManager.CACHE_KEY}_${CURRENT_SITE.prefix}_${username}`;
                    GM_setValue(cacheKey, {
                        followingCount: this.followingCount,
                        followersCount: this.followersCount,
                        cakedate: this.cakedate,
                        animatedAvatar: this.animatedAvatar,
                        time: Date.now()
                    });
                } catch (e) {
                    // 缓存写入失败,忽略
                }
            }

            _createOverlay() {
                this.overlay = document.createElement('div');
                this.overlay.className = 'ldsp-follow-overlay';
                this.overlay.innerHTML = `
                    <div class="ldsp-follow-header">
                        <div class="ldsp-follow-title">${FollowManager.ICONS.users} 关注与粉丝</div>
                        <div class="ldsp-follow-close">×</div>
                    </div>
                    <div class="ldsp-follow-tabs">
                        <div class="ldsp-follow-tab active" data-tab="following">
                            <span class="ldsp-follow-tab-icon">${FollowManager.ICONS.following}</span>
                            <span class="ldsp-follow-tab-text">关注</span>
                            <span class="ldsp-follow-tab-count">${this.followingCount}</span>
                        </div>
                        <div class="ldsp-follow-tab" data-tab="followers">
                            <span class="ldsp-follow-tab-icon">${FollowManager.ICONS.followers}</span>
                            <span class="ldsp-follow-tab-text">粉丝</span>
                            <span class="ldsp-follow-tab-count">${this.followersCount}</span>
                        </div>
                    </div>
                    <div class="ldsp-follow-body"></div>`;
                if (this.panelBody) {
                    this.panelBody.appendChild(this.overlay);
                }
                this._bindEvents();
            }

            _bindEvents() {
                this.overlay.querySelector('.ldsp-follow-close').addEventListener('click', () => this.hide());
                document.addEventListener('keydown', e => {
                    if (e.key === 'Escape' && this.overlay.classList.contains('show')) this.hide();
                });
                this.overlay.querySelectorAll('.ldsp-follow-tab').forEach(tab => {
                    tab.addEventListener('click', () => {
                        this.overlay.querySelectorAll('.ldsp-follow-tab').forEach(t => t.classList.remove('active'));
                        tab.classList.add('active');
                        const tabName = tab.dataset.tab;
                        this._renderList(tabName);
                    });
                });
            }

            async loadData(forceRefresh = false) {
                if (this._loading) return;
                if (this._loaded && !forceRefresh) return;
                
                this._loading = true;
                const username = this.storage.getUser();
                if (!username) {
                    this._loading = false;
                    return;
                }

                // 保存旧的数量用于比较
                const oldFollowingCount = this.followingCount;
                const oldFollowersCount = this.followersCount;

                const baseUrl = `https://${CURRENT_SITE.domain}`;
                
                try {
                    const [followingRes, followersRes] = await Promise.all([
                        this.network.fetchJson(`${baseUrl}/u/${username}/follow/following`, { headers: buildAuthHeaders(`${baseUrl}/u/${username}/follow/following`, { 'Accept': 'application/json' }) }),
                        this.network.fetchJson(`${baseUrl}/u/${username}/follow/followers`, { headers: buildAuthHeaders(`${baseUrl}/u/${username}/follow/followers`, { 'Accept': 'application/json' }) })
                    ]);
                    
                    this.following = Array.isArray(followingRes) ? followingRes : [];
                    this.followers = Array.isArray(followersRes) ? followersRes : [];
                    this.followingCount = this.following.length;
                    this.followersCount = this.followers.length;
                    this._loaded = true;
                    
                    // 检查是否有变化,有变化则更新缓存
                    if (this.followingCount !== oldFollowingCount || this.followersCount !== oldFollowersCount) {
                        this._saveToCache();
                    }
                    
                    // 更新显示
                    this._updateStats();
                    this._updateTabCounts();
                } catch (e) {
                    Logger.warn('Failed to load follow data:', e.message);
                    // 加载失败时保持缓存的数量,不清零
                } finally {
                    this._loading = false;
                }
            }
            
            // 加载用户 profile 数据(cakedate, animated_avatar)
            async loadProfile() {
                if (this._profileLoaded) return;
                
                const username = this.storage.getUser();
                if (!username) return;
                
                const baseUrl = `https://${CURRENT_SITE.domain}`;
                
                try {
                    const profileUrl = `${baseUrl}/u/${encodeURIComponent(username)}.json`;
                    const profileRes = await this.network.fetchJson(profileUrl, { headers: buildAuthHeaders(profileUrl, { 'Accept': 'application/json' }) });
                    if (profileRes && profileRes.user) {
                        const user = profileRes.user;
                        let hasChanges = false;
                        
                        // 获取 cakedate
                        if (user.cakedate && user.cakedate !== this.cakedate) {
                            this.cakedate = user.cakedate;
                            this._updateDays();
                            hasChanges = true;
                        }
                        
                        // 获取 animated_avatar
                        if (user.animated_avatar) {
                            const animatedUrl = user.animated_avatar.startsWith('http') 
                                ? user.animated_avatar 
                                : `${baseUrl}${user.animated_avatar}`;
                            // 只有动态头像变化时才更新
                            if (animatedUrl !== this.animatedAvatar) {
                                this.animatedAvatar = animatedUrl;
                                // 通过 renderer 渲染头像
                                if (this.renderer) {
                                    this.renderer.renderAvatar(animatedUrl);
                                }
                                hasChanges = true;
                            }
                        }
                        
                        this._profileLoaded = true;
                        if (hasChanges) {
                            this._saveToCache();
                        }
                    }
                } catch (e) {
                    Logger.warn('Failed to load user profile:', e.message);
                }
            }
            
            // 更新注册天数显示
            _updateDays() {
                const daysNumEl = document.querySelector('.ldsp-join-days-num');
                const siteEl = document.querySelector('.ldsp-join-days-site');
                const joinDaysEl = document.querySelector('.ldsp-join-days');
                
                if (!this.cakedate) {
                    if (joinDaysEl) joinDaysEl.style.display = 'none';
                    return;
                }
                
                try {
                    const joinDate = new Date(this.cakedate);
                    const now = new Date();
                    const diffTime = Math.abs(now - joinDate);
                    const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
                    const siteShort = CURRENT_SITE.domain === 'linux.do' ? 'L站' : 'IF站';
                    const siteFull = CURRENT_SITE.domain === 'linux.do' ? 'Linux.do' : 'IDCFlare';
                    if (siteEl) siteEl.textContent = siteShort;
                    if (daysNumEl) daysNumEl.textContent = diffDays;
                    if (joinDaysEl) {
                        joinDaysEl.style.display = '';
                        // 设置悬浮提示:于xxxx年xx月xx日加入xx站
                        const year = joinDate.getFullYear();
                        const month = joinDate.getMonth() + 1;
                        const day = joinDate.getDate();
                        joinDaysEl.title = `于${year}年${month}月${day}日加入${siteFull}`;
                    }
                } catch (e) {
                    if (joinDaysEl) joinDaysEl.style.display = 'none';
                }
            }

            _updateStats() {
                // 支持新的合并按钮样式
                const combinedEl = document.querySelector('.ldsp-follow-combined');
                if (combinedEl) {
                    const followingNum = combinedEl.querySelector('.ldsp-follow-num-following');
                    const followersNum = combinedEl.querySelector('.ldsp-follow-num-followers');
                    if (followingNum) followingNum.textContent = this.followingCount;
                    if (followersNum) followersNum.textContent = this.followersCount;
                }
                // 兼容旧样式
                const statsEl = document.querySelector('.ldsp-follow-stats');
                if (statsEl) {
                    const following = statsEl.querySelector('.ldsp-follow-stat-following .ldsp-follow-stat-num');
                    const followers = statsEl.querySelector('.ldsp-follow-stat-followers .ldsp-follow-stat-num');
                    if (following) following.textContent = this.followingCount;
                    if (followers) followers.textContent = this.followersCount;
                }
            }

            _updateTabCounts() {
                const followingTab = this.overlay.querySelector('.ldsp-follow-tab[data-tab="following"] .ldsp-follow-tab-count');
                const followersTab = this.overlay.querySelector('.ldsp-follow-tab[data-tab="followers"] .ldsp-follow-tab-count');
                if (followingTab) followingTab.textContent = this.followingCount;
                if (followersTab) followersTab.textContent = this.followersCount;
            }

            async show() {
                this.overlay.classList.add('show');
                const body = this.overlay.querySelector('.ldsp-follow-body');
                body.innerHTML = '<div class="ldsp-follow-loading"><div class="ldsp-spinner"></div><div>加载中...</div></div>';
                
                // 加载数据(如果还没加载)
                if (!this._loaded) {
                    await this.loadData();
                }
                
                // 渲染当前激活的tab
                const activeTab = this.overlay.querySelector('.ldsp-follow-tab.active');
                this._renderList(activeTab?.dataset.tab || 'following');
            }

            hide() {
                this.overlay.classList.remove('show');
            }

            _renderList(type = 'following') {
                const body = this.overlay.querySelector('.ldsp-follow-body');
                const list = type === 'following' ? this.following : this.followers;
                const emptyText = type === 'following' ? '还没有关注任何人' : '还没有粉丝';
                const emptyIcon = type === 'following' ? FollowManager.ICONS.user : FollowManager.ICONS.followers;
                
                if (list.length === 0) {
                    body.innerHTML = `
                        <div class="ldsp-follow-empty">
                            <div class="ldsp-follow-empty-icon">${emptyIcon}</div>
                            <div>${emptyText}</div>
                        </div>`;
                    return;
                }

                const baseUrl = `https://${CURRENT_SITE.domain}`;
                body.innerHTML = `
                    <div class="ldsp-follow-list">
                        ${list.map(user => {
                            const avatarUrl = this._getAvatarUrl(user.avatar_template, user.animated_avatar, 64);
                            const displayName = user.name || user.username;
                            return `
                                <a href="${baseUrl}/u/${user.username}" target="_blank" class="ldsp-follow-item" data-username="${user.username}">
                                    <div class="ldsp-follow-avatar-wrap">
                                        <img class="ldsp-follow-avatar" src="${avatarUrl}" alt="${Utils.escapeHtml(user.username)}" loading="lazy">
                                    </div>
                                    <div class="ldsp-follow-user-info">
                                        <div class="ldsp-follow-user-name">${Utils.escapeHtml(displayName)}</div>
                                        <div class="ldsp-follow-user-id">@${Utils.escapeHtml(user.username)}</div>
                                    </div>
                                    <div class="ldsp-follow-arrow">
                                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
                                            <path d="M9 18l6-6-6-6"/>
                                        </svg>
                                    </div>
                                </a>`;
                        }).join('')}
                    </div>`;
            }

            _getAvatarUrl(template, animatedAvatar, size = 64) {
                if (!template) return '';
                // 优先使用动画头像
                if (animatedAvatar) {
                    return `https://${CURRENT_SITE.domain}${animatedAvatar}`;
                }
                // 替换size占位符
                let url = template.replace('{size}', size);
                // 如果是相对路径,添加域名
                if (url.startsWith('/')) {
                    url = `https://${CURRENT_SITE.domain}${url}`;
                }
                return url;
            }

            getStats() {
                return {
                    following: this.followingCount,
                    followers: this.followersCount
                };
            }

            destroy() {
                if (this.overlay) {
                    this.overlay.remove();
                    this.overlay = null;
                }
            }
        }

        // ==================== 面板渲染器 ====================
        class Renderer {
            constructor(panel) {
                this.panel = panel;
                this.prevValues = new Map();
                this.lastPct = -1;
            }

            // 渲染用户信息
            renderUser(name, level, isOK, reqs, displayName = null) {
                const _done = reqs.filter(r => r.isSuccess).length;
                const $ = this.panel.$;
                // XSS 防护:使用 textContent 而不是 innerHTML,并清理输入
                const safeName = Utils.sanitize(name, 30);
                const safeDisplayName = Utils.sanitize(displayName, 100);
                // 如果有 displayName 则显示 displayName + @username,否则只显示 username
                if (safeDisplayName && safeDisplayName !== safeName) {
                    $.userDisplayName.textContent = safeDisplayName;
                    $.userHandle.textContent = `@${safeName}`;
                    $.userHandle.style.display = '';
                } else {
                    $.userDisplayName.textContent = safeName;
                    $.userHandle.textContent = '';
                    $.userHandle.style.display = 'none';
                }
            }

            renderReqs(reqs, level = null) {
                const done = reqs.filter(r => r.isSuccess).length, remain = reqs.length - done;
                const pct = Math.round(done / reqs.length * 100), cfg = Screen.getConfig();
                const r = cfg.ringSize / 2 - 8, circ = 2 * Math.PI * r, off = circ * (1 - pct / 100);
                const anim = this.lastPct === -1 || this.lastPct !== pct || this.panel.animRing;
                this.lastPct = pct; this.panel.animRing = false;

                const pl = level !== null ? parseInt(level, 10) : NaN;
                let lv = !isNaN(pl) ? pl : (this.panel.cachedLevel ?? (typeof (this.panel.oauth?.getUserInfo?.()?.trust_level ?? this.panel.oauth?.getUserInfo?.()?.trustLevel) === 'number' ? this.panel.oauth.getUserInfo().trust_level : 2));
                if (level !== null && !isNaN(pl)) this.panel.cachedLevel = lv;

                const canUp = lv < 3, tgt = canUp ? lv + 1 : lv;
                const [tipText, tipClass] = !canUp ? [lv >= 4 ? '🏆 已达最高等级' : '🎖️ 已达普通用户最高等级', 'max'] : remain > 0 ? [`⏳ 距升级还需完成 ${remain} 项要求`, 'progress'] : ['🎉 已满足升级条件', 'ok'];

                const colors = ['#5070d0','#5bb5a6','#f97316','#22c55e','#eab308','#ec4899','#f43f5e','#6b8cef'];
                const shapes = '●■★❤✨❀';
                const confetti = pct === 100 ? Array.from({length:28},(_,i)=>{const a=(i/28)*360+(Math.random()-.5)*25,rad=a*Math.PI/180,d=55+Math.random()*45;return`<span class="ldsp-confetti-piece" style="color:${colors[i%8]};--tx:${Math.cos(rad)*d}px;--ty:${Math.sin(rad)*d*.7}px;--drift:${(Math.random()-.5)*40}px;--rot:${(Math.random()-.5)*900}deg;animation-delay:${Math.random()*.06}s">${shapes[Math.floor(Math.random()*6)]}</span>`}).join('') : '';

                const h = [`<div class="ldsp-ring${pct===100?' complete':''}">`, pct===100?`<div class="ldsp-confetti">${confetti}</div>`:''];
                h.push(`<div class="ldsp-ring-stat"><div class="ldsp-ring-stat-val ok">✓${done}</div><div class="ldsp-ring-stat-lbl">已达标</div></div>`,
                    `<div class="ldsp-ring-center"><div class="ldsp-ring-wrap"><svg width="${cfg.ringSize}" height="${cfg.ringSize}" viewBox="0 0 ${cfg.ringSize} ${cfg.ringSize}"><defs><linearGradient id="ldsp-grad" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" style="stop-color:#5070d0"/><stop offset="100%" style="stop-color:#5bb5a6"/></linearGradient></defs><circle class="ldsp-ring-bg" cx="${cfg.ringSize/2}" cy="${cfg.ringSize/2}" r="${r}"/><circle class="ldsp-ring-fill${anim?' anim':''}" cx="${cfg.ringSize/2}" cy="${cfg.ringSize/2}" r="${r}" stroke-dasharray="${circ}" stroke-dashoffset="${anim?circ:off}" style="--circ:${circ};--off:${off}"/></svg><div class="ldsp-ring-txt"><div class="ldsp-ring-val${anim?' anim':''}">${pct}%</div><div class="ldsp-ring-lbl">完成度</div></div></div><div class="ldsp-ring-lvl lv${lv}">${canUp?`Lv${lv} → Lv${tgt}`:`Lv${lv} ★`}</div></div>`,
                    `<div class="ldsp-ring-stat"><div class="ldsp-ring-stat-val fail">○${remain}</div><div class="ldsp-ring-stat-lbl">待完成</div></div></div>`,
                    `<div class="ldsp-ring-tip ${tipClass}">${tipText}</div>`);

                for (const req of reqs) {
                    const name = Utils.simplifyName(req.name), prev = this.prevValues.get(req.name);
                    const upd = prev !== undefined && prev !== req.currentValue;
                    const chg = req.change ? `<span class="ldsp-item-chg ${req.change>0?'up':'down'}">${req.change>0?'+':''}${req.change}</span>` : '';
                    h.push(`<div class="ldsp-item ${req.isSuccess?'ok':'fail'}"><span class="ldsp-item-icon">${req.isSuccess?'✓':'○'}</span><span class="ldsp-item-name">${name}</span><div class="ldsp-item-vals"><span class="ldsp-item-cur${upd?' upd':''}">${req.currentValue}</span><span class="ldsp-item-sep">/</span><span class="ldsp-item-req">${req.requiredValue}</span></div>${chg}</div>`);
                    this.prevValues.set(req.name, req.currentValue);
                }
                h.push(`<a class="ldsp-learn-trust" href="https://linux.do/t/topic/2460" target="_blank" rel="noopener">了解论坛信任等级 →</a>`);
                this.panel.$.reqs.innerHTML = h.join('');

                if (pct === 100) setTimeout(() => this.panel.$.reqs.querySelector('.ldsp-ring.complete')?.classList.add('anim-done'), anim ? 950 : 50);
            }

            // 渲染阅读卡片(带缓存,避免频繁更新导致动画闪烁)
            renderReading(minutes, isTracking = true) {
                const lv = Utils.getReadingLevel(minutes);
                const timeStr = Utils.formatReadingTime(minutes);
                const $ = this.panel.$;
                
                // 缓存上次渲染的状态,避免不必要的 DOM 操作和样式更新
                const cacheKey = `${lv.label}|${timeStr}|${isTracking}|${minutes >= 180}|${minutes >= 450}`;
                if (this._readingCache === cacheKey) return;
                this._readingCache = cacheKey;
                
                // 只更新变化的内容
                if ($.readingIcon.textContent !== lv.icon) $.readingIcon.textContent = lv.icon;
                if ($.readingTime.textContent !== timeStr) $.readingTime.textContent = timeStr;
                if ($.readingLabel.textContent !== lv.label) $.readingLabel.textContent = lv.label;
                
                // 只在颜色变化时更新样式(避免重置动画)
                if (this._readingColor !== lv.color) {
                    this._readingColor = lv.color;
                    $.reading.style.cssText = `background:${lv.bg};color:${lv.color};--rc:${lv.color}`;
                    $.readingTime.style.color = lv.color;
                    $.readingLabel.style.color = lv.color;
                }
                
                // tracking 类表示正在追踪,显示波浪效果和"阅读时间记录中..."
                $.reading.classList.toggle('tracking', isTracking);
                // hi 类表示阅读时间达到沉浸阅读(180-450分钟)
                $.reading.classList.toggle('hi', minutes >= 180 && minutes < 450);
                // max 类表示阅读时间达到极限(450分钟+)
                $.reading.classList.toggle('max', minutes >= 450);
            }

            // 渲染头像
            renderAvatar(url) {
                const wrap = this.panel.$.user.querySelector('.ldsp-avatar-wrap');
                if (!wrap) return;
                const el = wrap.querySelector('.ldsp-avatar-ph, .ldsp-avatar');
                if (!el) return;
                const img = document.createElement('img');
                img.className = 'ldsp-avatar';
                img.src = url;
                img.alt = 'Avatar';
                img.onerror = () => {
                    const ph = document.createElement('div');
                    ph.className = 'ldsp-avatar-ph';
                    ph.textContent = '👤';
                    img.replaceWith(ph);
                };
                el.replaceWith(img);
            }

            renderTrends(tab) {
                const tabs = [['today','☀️','今日'],['week','📅','本周'],['month','📊','本月'],['year','📈','本年'],['all','🌐','全部']];
                this.panel.$.trends.innerHTML = `<div class="ldsp-subtabs">${tabs.map(([id,i,l])=>`<div class="ldsp-subtab${tab===id?' active':''}" data-tab="${id}">${i} ${l}</div>`).join('')}</div><div class="ldsp-trend-content"></div>`;
            }

            getTrendFields(reqs) {
                return CONFIG.TREND_FIELDS.map(f => { const r = reqs.find(x => x.name.includes(f.search)); return r ? {...f, req: r, name: r.name} : null; }).filter(Boolean);
            }

            renderTodayTrend(reqs, rt, td) {
                if (!td) return `<div class="ldsp-empty"><div class="ldsp-empty-icon">☀️</div><div class="ldsp-empty-txt">今日首次访问<br>数据将从现在开始统计</div></div>`;
                const now = new Date(), start = new Date(td.startTs), lv = Utils.getReadingLevel(rt), pct = Math.min(rt/600*100,100);
                const startStr = `${start.getHours()}:${String(start.getMinutes()).padStart(2,'0')}`, nowStr = `${now.getHours()}:${String(now.getMinutes()).padStart(2,'0')}`;
                let h = `<div class="ldsp-time-info">今日 00:00 ~ ${nowStr} (首次记录于 ${startStr})</div><div class="ldsp-rd-stats" style="background:${lv.bg.replace('0.15','0.08')}"><div class="ldsp-rd-stats-icon">${lv.icon}</div><div class="ldsp-rd-stats-info"><div class="ldsp-rd-stats-val">${Utils.formatReadingTime(rt)}</div><div class="ldsp-rd-stats-lbl">今日累计阅读</div></div><div class="ldsp-rd-stats-badge" style="background:${lv.bg};color:${lv.color};box-shadow:0 3px 12px ${lv.bg.replace('0.15','0.4')},inset 0 1px 0 rgba(255,255,255,.25)">${lv.label}</div></div><div class="ldsp-rd-prog"><div class="ldsp-rd-prog-hdr"><span class="ldsp-rd-prog-title">📖 阅读目标 (10小时)</span><span class="ldsp-rd-prog-val">${Math.round(pct)}%</span></div><div class="ldsp-rd-prog-bar"><div class="ldsp-rd-prog-fill" style="width:${pct}%;background:${lv.bg.replace('0.15','1')}"></div></div></div>`;
                if (reqs?.length) {
                    const chgs = reqs.map(r=>({name:Utils.simplifyName(r.name),diff:r.currentValue-(td.startData[r.name]||0)})).filter(c=>c.diff!==0).sort((a,b)=>b.diff-a.diff);
                    const pos = chgs.filter(c=>c.diff>0).length, neg = chgs.filter(c=>c.diff<0).length;
                    h += `<div class="ldsp-today-stats"><div class="ldsp-today-stat"><div class="ldsp-today-stat-val">${pos}</div><div class="ldsp-today-stat-lbl">📈 增长项</div></div><div class="ldsp-today-stat"><div class="ldsp-today-stat-val">${neg}</div><div class="ldsp-today-stat-lbl">📉 下降项</div></div></div>`;
                    h += chgs.length ? `<div class="ldsp-chart"><div class="ldsp-chart-title">📊 今日变化明细</div><div class="ldsp-changes">${chgs.map(c=>`<div class="ldsp-chg-row"><span class="ldsp-chg-name">${c.name}</span><span class="ldsp-chg-val ${c.diff>0?'up':'down'}">${c.diff>0?'+':''}${c.diff}</span></div>`).join('')}</div></div>` : `<div class="ldsp-no-chg">今日暂无数据变化</div>`;
                }
                return h;
            }

            renderWeekTrend(hist, reqs, hm, tracker) {
                let h = this._renderWeekChart(tracker);
                if (reqs?.length) {
                    const recent = hist.filter(x => x.ts > Date.now()-7*86400000);
                    if (recent.length >= 1) {
                        const daily = hm.aggregateDaily(recent, reqs, 7), trends = [];
                        for (const f of this.getTrendFields(reqs)) {
                            const d = this._calcDailyTrend(daily, f.name, 7);
                            if (d.values.some(v => v > 0)) trends.push({label: f.label, ...d, current: f.req.currentValue});
                        }
                        if (trends.length) {
                            h += `<div class="ldsp-chart"><div class="ldsp-chart-title">📈 本周每日增量<span class="ldsp-chart-sub">每日累积量</span></div>${this._renderSparkRows(trends)}${trends[0].dates.length?`<div class="ldsp-date-labels">${trends[0].dates.map(d=>`<span class="ldsp-date-lbl">${d}</span>`).join('')}</div>`:''}</div>`;
                        }
                    }
                }
                return h;
            }

            renderMonthTrend(hist, reqs, hm, tracker) {
                let h = this._renderMonthChart(tracker);
                if (reqs?.length && hist.length >= 1) {
                    const weekly = hm.aggregateWeekly(hist, reqs), trends = [];
                    for (const f of this.getTrendFields(reqs)) {
                        const d = this._calcWeeklyTrend(weekly, f.name);
                        if (d.values.length) trends.push({label: f.label, ...d, current: f.req.currentValue});
                    }
                    if (trends.length) {
                        h += `<div class="ldsp-chart"><div class="ldsp-chart-title">📈 本月每周增量<span class="ldsp-chart-sub">每周累积量</span></div>${this._renderSparkRows(trends,true)}${trends[0].labels?.length?`<div class="ldsp-date-labels" style="padding-left:60px">${trends[0].labels.map(l=>`<span class="ldsp-date-lbl">${l}</span>`).join('')}</div>`:''}</div>`;
                    }
                }
                return h;
            }

            renderYearTrend(hist, reqs, hm, tracker) {
                let h = this._renderYearChart(tracker);
                if (reqs?.length) {
                    // v3.5.2.9: 只取当前年的数据,而不是过去365天
                    const currentYear = new Date().getFullYear();
                    const yearStart = new Date(currentYear, 0, 1).getTime();
                    const recent = hist.filter(x => x.ts >= yearStart);
                    if (recent.length >= 1) {
                        const monthly = hm.aggregateMonthly(recent, reqs), trends = [];
                        for (const f of this.getTrendFields(reqs)) {
                            const d = this._calcMonthlyTrend(monthly, f.name);
                            if (d.values.some(v => v > 0)) trends.push({label: f.label, ...d, current: f.req.currentValue});
                        }
                        if (trends.length) {
                            h += `<div class="ldsp-chart"><div class="ldsp-chart-title">📊 本年每月增量<span class="ldsp-chart-sub">每月累积量</span></div>`;
                            trends.forEach(t => { const m = Math.max(...t.values,1); h += `<div class="ldsp-spark-row"><span class="ldsp-spark-lbl">${t.label}</span><div class="ldsp-spark-bars" style="max-width:100%">${t.values.map((v,i)=>`<div class="ldsp-spark-bar" style="height:${Math.max(v/m*16,2)}px" data-v="${i+1}月: ${v}"></div>`).join('')}</div><span class="ldsp-spark-val">${t.current}</span></div>`; });
                            h += `</div>`;
                        }
                    }
                }
                return h;
            }

            renderAllTrend(hist, reqs, tracker) {
                const total = tracker.getTotalTime();
                const rd = tracker.storage.get('readingTime', null);
                const days = rd?.dailyData ? Object.keys(rd.dailyData).length : 1;
                const avg = Math.round(total / Math.max(days, 1)), lv = Utils.getReadingLevel(avg);
                let h = `<div class="ldsp-time-info">共记录 <span>${days}</span> 天阅读数据</div>`;
                if (total > 0) h += `<div class="ldsp-rd-stats" style="background:${lv.bg.replace('0.15','0.08')}"><div class="ldsp-rd-stats-icon">${lv.icon}</div><div class="ldsp-rd-stats-info"><div class="ldsp-rd-stats-val">${Utils.formatReadingTime(total)}</div><div class="ldsp-rd-stats-lbl">累计阅读时间 · 日均 ${Utils.formatReadingTime(avg)}</div></div><div class="ldsp-rd-stats-badge" style="background:${lv.bg};color:${lv.color};box-shadow:0 3px 12px ${lv.bg.replace('0.15','0.4')},inset 0 1px 0 rgba(255,255,255,.25)">${lv.label}</div></div>`;
                if (reqs?.length && hist.length >= 1) {
                    const oldest = hist[0], newest = hist.at(-1), rec = hist.length, span = Math.ceil((Date.now()-oldest.ts)/86400000);
                    if (span > days) h = h.replace(`共记录 <span>${days}</span> 天阅读数据`, `共记录 <span>${rec}</span> 天数据${span>rec?` · 跨度 ${span} 天`:''}`);
                    const chgs = reqs.map(r=>({name:Utils.simplifyName(r.name),diff:(newest.data[r.name]||0)-(oldest.data[r.name]||0),current:r.currentValue,required:r.requiredValue})).filter(c=>c.diff!==0||c.current>0);
                    if (chgs.length) h += `<div class="ldsp-chart"><div class="ldsp-chart-title">📊 累计变化 <span style="font-size:9px;color:var(--txt-mut);font-weight:normal">(${rec}天)</span></div><div class="ldsp-changes">${chgs.map(c=>`<div class="ldsp-chg-row"><span class="ldsp-chg-name">${c.name}</span><span class="ldsp-chg-cur">${c.current}/${c.required}</span>${c.diff?`<span class="ldsp-chg-val ${c.diff>0?'up':'down'}">${c.diff>0?'+':''}${c.diff}</span>`:''}</div>`).join('')}</div></div>`;
                    if (rec >= 2) {
                        const avgChgs = reqs.map(r=>({name:Utils.simplifyName(r.name),avg:Math.round(((newest.data[r.name]||0)-(oldest.data[r.name]||0))/Math.max(rec-1,1)*10)/10})).filter(c=>c.avg>0);
                        if (avgChgs.length) h += `<div class="ldsp-chart"><div class="ldsp-chart-title">📈 日均增量</div><div class="ldsp-changes">${avgChgs.map(c=>`<div class="ldsp-chg-row"><span class="ldsp-chg-name">${c.name}</span><span class="ldsp-chg-val up">+${c.avg}</span></div>`).join('')}</div></div>`;
                    }
                }
                return h;
            }

            _renderSparkRows(trends, isWk = false) {
                // v3.5.2.9: 为条形图添加悬浮提示(使用 data-v 配合 CSS ::after)
                // 移除内联 opacity 设置,统一由 CSS 控制,避免气泡提示继承透明度
                return trends.map(t => { 
                    const m = Math.max(...t.values,1); 
                    const tipLabels = isWk ? (t.labels || []) : (t.dates || []);
                    return `<div class="ldsp-spark-row"><span class="ldsp-spark-lbl">${t.label}</span><div class="ldsp-spark-bars">${t.values.map((v,i)=>{
                        const tip = tipLabels[i] ? `${tipLabels[i]}: ${v}` : `${v}`;
                        const isLast = i === t.values.length - 1;
                        // 使用 class 而非内联 opacity,确保气泡提示不受影响
                        return `<div class="ldsp-spark-bar${isLast ? ' ldsp-spark-current' : ''}" style="height:${Math.max(v/m*20,2)}px" data-v="${tip}"></div>`;
                    }).join('')}</div><span class="ldsp-spark-val">${t.current}</span></div>`; 
                }).join('');
            }

            _renderWeekChart(tracker) {
                const days = tracker.getWeekHistory(), max = Math.max(...days.map(d=>d.minutes),60), total = days.reduce((s,d)=>s+d.minutes,0);
                return `<div class="ldsp-chart"><div class="ldsp-chart-title">⏱️ 7天阅读时间<span class="ldsp-chart-sub">共 ${Utils.formatReadingTime(total)} · 日均 ${Utils.formatReadingTime(Math.round(total/7))}</span></div><div class="ldsp-rd-week">${days.map(d=>`<div class="ldsp-rd-day"><div class="ldsp-rd-day-bar" style="height:${Math.max(d.minutes/max*45,3)}px;opacity:${d.isToday?1:0.7}" data-t="${Utils.formatReadingTime(d.minutes)}"></div><span class="ldsp-rd-day-lbl">${d.day}</span></div>`).join('')}</div></div>`;
            }

            _renderMonthChart(tracker) {
                const td = new Date(), [yr,mo,cd] = [td.getFullYear(),td.getMonth(),td.getDate()], dim = new Date(yr,mo+1,0).getDate();
                const days = []; let max = 1, total = 0;
                for (let d = 1; d <= dim; d++) {
                    const isT = d===cd, isFut = d>cd, mins = isFut ? 0 : (isT ? tracker.getTodayTime() : tracker.getTimeForDate(new Date(yr,mo,d).toDateString()));
                    if (!isFut) { max = Math.max(max, mins); total += mins; }
                    days.push({d, mins: Math.max(mins,0), isT, isFut});
                }
                const fs = dim>=31?'7px':(dim>=28?'8px':'9px');
                return `<div class="ldsp-chart"><div class="ldsp-chart-title">⏱️ 本月阅读时间<span class="ldsp-chart-sub">共 ${Utils.formatReadingTime(total)} · 日均 ${Utils.formatReadingTime(cd>0?Math.round(total/cd):0)}</span></div><div class="ldsp-rd-week" style="height:100px;align-items:flex-end;gap:1px">${days.map(dy=>`<div class="ldsp-rd-day" style="margin:0 1px;flex:1;min-width:2px"><div class="ldsp-rd-day-bar" style="height:${max>0?(dy.mins>0?Math.max(dy.mins/max*45,2):1):1}px;opacity:${dy.isFut?0.35:(dy.isT?1:0.75)};background:var(--accent2);width:100%;border-radius:3px 3px 0 0" data-t="${dy.d}日: ${dy.isFut?'0分钟 (未到)':Utils.formatReadingTime(dy.mins)}"></div><div class="ldsp-rd-day-lbl" style="margin-top:3px;font-size:${fs}">${dy.d}</div></div>`).join('')}</div></div>`;
            }

            _renderYearChart(tracker) {
                const today = new Date();
                const year = today.getFullYear();
                const data = tracker.getYearData();

                const jan1 = new Date(year, 0, 1);
                const blanks = jan1.getDay() === 0 ? 6 : jan1.getDay() - 1;

                let total = 0;
                data.forEach(m => total += m);

                const days = Array(blanks).fill({ empty: true });
                let d = new Date(jan1);
                while (d <= today) {
                    days.push({
                        date: new Date(d),
                        mins: Math.max(data.get(d.toDateString()) || 0, 0),
                        month: d.getMonth(),
                        day: d.getDate()
                    });
                    d.setDate(d.getDate() + 1);
                }

                const COLS = 14;
                while (days.length % COLS) days.push({ empty: true });

                const rows = [];
                for (let i = 0; i < days.length; i += COLS) {
                    rows.push(days.slice(i, i + COLS));
                }

                const monthRows = new Map();
                rows.forEach((r, i) => {
                    r.forEach(day => {
                        if (!day.empty) {
                            const m = day.month;
                            if (!monthRows.has(m)) monthRows.set(m, { start: i, end: i });
                            else monthRows.get(m).end = i;
                        }
                    });
                });

                const labels = new Map();
                monthRows.forEach((info, m) => {
                    const mid = Math.floor((info.start + info.end) / 2);
                    if (!labels.has(mid)) labels.set(mid, CONFIG.MONTHS[m]);
                });

                // v3.5.2.9: 当行数少于4行时添加 few-rows 类,让tooltip向下显示避免被遮挡
                const fewRowsClass = rows.length < 4 ? ' few-rows' : '';
                let html = `<div class="ldsp-chart"><div class="ldsp-chart-title">⏱️ 本年阅读时间<span class="ldsp-chart-sub">共 ${Utils.formatReadingTime(total)}</span></div><div class="ldsp-year-heatmap${fewRowsClass}"><div class="ldsp-year-wrap">`;

                rows.forEach((row, i) => {
                    const lbl = labels.get(i) || '';
                    html += `<div class="ldsp-year-row"><span class="ldsp-year-month">${lbl}</span><div class="ldsp-year-cells">`;
                    row.forEach(day => {
                        if (day.empty) {
                            html += `<div class="ldsp-year-cell empty"></div>`;
                        } else {
                            const lv = Utils.getHeatmapLevel(day.mins);
                            html += `<div class="ldsp-year-cell l${lv}"><div class="ldsp-year-tip">${day.month + 1}/${day.day}<br>${Utils.formatReadingTime(day.mins)}</div></div>`;
                        }
                    });
                    html += `</div></div>`;
                });

                html += `</div><div class="ldsp-heatmap-legend"><span>&lt;1分</span>`;
                const legendColors = ['rgba(107,140,239,.1)', 'rgba(180,230,210,.35)', 'rgba(130,215,180,.5)', 'rgba(90,195,155,.65)', 'linear-gradient(135deg,#6dcfa5,#50c090)'];
                for (let i = 0; i <= 4; i++) html += `<div class="ldsp-heatmap-legend-cell" style="background:${legendColors[i]}"></div>`;
                html += `<span>&gt;5小时</span></div></div></div>`;

                return html;
            }

            _calcDailyTrend(daily, name, maxDays) {
                const sorted = [...daily.keys()].sort((a, b) => new Date(a) - new Date(b)).slice(-maxDays);
                return {
                    values: sorted.map(d => Math.max(daily.get(d)[name] || 0, 0)),
                    dates: sorted.map(d => Utils.formatDate(new Date(d).getTime(), 'short'))
                };
            }

            _calcWeeklyTrend(weekly, name) {
                const sorted = [...weekly.keys()].sort((a, b) => a - b);
                return {
                    values: sorted.map(i => Math.max(weekly.get(i).data[name] || 0, 0)),
                    labels: sorted.map(i => weekly.get(i).label)
                };
            }

            _calcMonthlyTrend(monthly, name) {
                const sorted = [...monthly.keys()].sort((a, b) => new Date(a) - new Date(b));
                return {
                    values: sorted.map(m => Math.max(monthly.get(m)[name] || 0, 0)),
                    dates: sorted.map(m => `${new Date(m).getMonth() + 1}月`)
                };
            }

            // Toast 提示
            showToast(msg) {
                const toast = document.createElement('div');
                toast.className = 'ldsp-toast';
                toast.innerHTML = msg;
                this.panel.el.appendChild(toast);
                requestAnimationFrame(() => toast.classList.add('show'));
                setTimeout(() => {
                    toast.classList.remove('show');
                    setTimeout(() => toast.remove(), 300);
                }, 4000);
            }

            // 登录提示模态框
            showLoginPrompt(isUpgrade = false) {
                const overlay = document.createElement('div');
                overlay.className = 'ldsp-modal-overlay';
                overlay.innerHTML = `
                    <div class="ldsp-modal">
                        <div class="ldsp-modal-hdr"><span class="ldsp-modal-icon">${isUpgrade ? '🎉' : '👋'}</span><span class="ldsp-modal-title">${isUpgrade ? '升级到 v3.0' : '欢迎使用 LDStatus Pro'}</span></div>
                        <div class="ldsp-modal-body">
                            ${isUpgrade ? `<p>v3.0 版本新增了 <strong>云同步</strong> 功能!</p><p>登录后,你的阅读数据将自动同步到云端,支持跨浏览器、跨设备访问。</p>` : `<p>登录 Linux.do 账号后可以:</p><ul><li>☁️ 阅读数据云端同步</li><li>🔄 跨浏览器/设备同步</li><li>🏆 查看/加入阅读排行榜</li></ul>`}
                        </div>
                        <div class="ldsp-modal-footer">
                            <button class="ldsp-modal-btn primary" id="ldsp-modal-login">🚀 立即登录</button>
                            <button class="ldsp-modal-btn secondary" id="ldsp-modal-skip">稍后再说</button>
                        </div>
                        <div class="ldsp-modal-note">登录仅用于云同步,不登录也可正常使用本地功能</div>
                    </div>`;
                this.panel.el.appendChild(overlay);
                requestAnimationFrame(() => overlay.classList.add('show'));
                return overlay;
            }

            renderLeaderboard(tab) {
                const tabs = [['daily','📅 日榜'],['weekly','📊 周榜'],['monthly','📈 月榜']];
                this.panel.$.leaderboard.innerHTML = `<div class="ldsp-subtabs">${tabs.map(([id,l])=>`<div class="ldsp-subtab${tab===id?' active':''}" data-lb="${id}">${l}</div>`).join('')}</div><div class="ldsp-lb-content"></div>`;
            }
            renderLeaderboardLogin() { return `<div class="ldsp-lb-login"><div class="ldsp-lb-login-icon">🔐</div><div class="ldsp-lb-login-title">需要登录</div><div class="ldsp-lb-login-desc">登录后可以:<br>☁️ 阅读数据云端同步<br>🏆 查看/加入排行榜</div><button class="ldsp-lb-btn primary" id="ldsp-lb-login">🚀 立即登录</button><div class="ldsp-privacy-note"><span>🔒</span><span>仅获取基本信息,用于数据同步</span></div></div>`; }
            renderLeaderboardJoin() { return `<div class="ldsp-join-prompt"><div class="ldsp-join-prompt-icon">🏆</div><div class="ldsp-join-prompt-title">加入阅读排行榜</div><div class="ldsp-join-prompt-desc">加入后可以查看排行榜,你的阅读时间将与其他用户一起展示<br>这是完全可选的,随时可以退出</div><button class="ldsp-lb-btn primary" id="ldsp-lb-join">✨ 加入排行榜</button><div class="ldsp-privacy-note"><span>🔒</span><span>仅展示用户名和阅读时间</span></div></div>`; }
            renderRegistrationPaused() { return `<div class="ldsp-join-prompt paused"><div class="ldsp-join-prompt-icon">🚧</div><div class="ldsp-join-prompt-title">已暂停新用户注册</div><div class="ldsp-join-prompt-desc">由于后端服务压力,暂不开放新用户注册。已注册用户可正常登录使用。</div></div>`; }

            renderLeaderboardData(data, uid, joined, type = 'daily') {
                const fmtInt = ms => { const m = Math.round(ms/60000); return m < 60 ? `每 ${m} 分钟更新` : `每 ${Math.round(m/60)} 小时更新`; };
                const rules = { daily: fmtInt(CONFIG.CACHE.LEADERBOARD_DAILY_TTL), weekly: fmtInt(CONFIG.CACHE.LEADERBOARD_WEEKLY_TTL), monthly: fmtInt(CONFIG.CACHE.LEADERBOARD_MONTHLY_TTL) };
                if (!data?.rankings?.length) return `<div class="ldsp-lb-empty"><div class="ldsp-lb-empty-icon">📭</div><div class="ldsp-lb-empty-txt">暂无排行数据<br>成为第一个上榜的人吧!</div></div>`;
                let h = `<div class="ldsp-lb-period"><button class="ldsp-lb-refresh" data-type="${type}" title="手动刷新">🔄</button>${data.period?`📅 统计周期: <span>${data.period}</span>`:''}<span class="ldsp-update-rule">🔄 ${rules[type]}</span></div>`;
                if (data.myRank && joined) h += `<div class="ldsp-my-rank${data.myRank.in_top?'':' not-in-top'}"><div><div class="ldsp-my-rank-lbl">我的排名${data.myRank.in_top?'':'<span class="ldsp-not-in-top-hint">(未入榜)</span>'}</div><div class="ldsp-my-rank-val">${data.myRank.rank?`#${data.myRank.rank}`:(data.myRank.rank_display||'--')}</div></div><div class="ldsp-my-rank-time">${Utils.formatReadingTime(data.myRank.minutes)}</div></div>`;
                h += '<div class="ldsp-rank-list">';
                const base = `https://${CURRENT_SITE.domain}`;
                data.rankings.forEach((u,i) => {
                    const r = i+1, me = uid && u.user_id===uid, cls = [r<=3?`t${r}`:'', me?'me':''].filter(Boolean).join(' ');
                    const av = u.avatar_url ? (u.avatar_url.startsWith('http')?u.avatar_url:`${base}${u.avatar_url}`) : '';
                    const su = Utils.escapeHtml(Utils.sanitize(u.username,30)), sn = Utils.escapeHtml(Utils.sanitize(u.name,100));
                    const nm = sn?.trim() ? `<span class="ldsp-rank-display-name">${sn}</span><span class="ldsp-rank-username">@${su}</span>` : `<span class="ldsp-rank-name-only">${su}</span>`;
                    h += `<div class="ldsp-rank-item ${cls}" style="animation-delay:${i*30}ms"><div class="ldsp-rank-num">${r<=3?['🥇','🥈','🥉'][r-1]:r}</div>${av?`<img class="ldsp-rank-avatar" src="${av}" onerror="this.outerHTML='<div class=\\'ldsp-rank-avatar\\' style=\\'display:flex;align-items:center;justify-content:center;font-size:12px\\'>👤</div>'">`:'<div class="ldsp-rank-avatar" style="display:flex;align-items:center;justify-content:center;font-size:12px">👤</div>'}<div class="ldsp-rank-info">${nm}${me?'<span class="ldsp-rank-me-tag">(我)</span>':''}</div><div class="ldsp-rank-time">${Utils.formatReadingTime(u.minutes)}</div></div>`;
                });
                h += '</div>';
                if (joined) h += `<div style="margin-top:12px;text-align:center"><button class="ldsp-lb-btn danger" id="ldsp-lb-quit" style="font-size:9px;padding:4px 8px">退出排行榜</button></div>`;
                return h;
            }
            renderLeaderboardLoading() { return `<div class="ldsp-mini-loader"><div class="ldsp-mini-spin"></div><div class="ldsp-mini-txt">加载排行榜...</div></div>`; }
            renderLeaderboardError(msg) { return `<div class="ldsp-lb-empty"><div class="ldsp-lb-empty-icon">❌</div><div class="ldsp-lb-empty-txt">${msg}</div><button class="ldsp-lb-btn secondary" id="ldsp-lb-retry" style="margin-top:12px">🔄 重试</button></div>`; }

            renderActivity(tab) {
                const tabs = [['read','📖','已读'],['bookmarks','⭐','收藏'],['replies','💬','回复'],['reactions','🤝','互动'],['likes','❤️','赞过'],['topics','📝','我的话题']];
                this.panel.$.activity.innerHTML = `<div class="ldsp-subtabs">${tabs.map(([id,i,l])=>`<div class="ldsp-subtab${tab===id?' active':''}" data-activity="${id}">${i} ${l}</div>`).join('')}</div><div class="ldsp-activity-content"></div>`;
            }
            renderActivityLoading() { return `<div class="ldsp-mini-loader"><div class="ldsp-mini-spin"></div><div class="ldsp-mini-txt">加载中...</div></div>`; }
            renderActivityEmpty(icon, msg) { return `<div class="ldsp-lb-empty"><div class="ldsp-lb-empty-icon">${icon}</div><div class="ldsp-lb-empty-txt">${msg}</div></div>`; }
            renderActivityError(msg) { return `<div class="ldsp-lb-empty"><div class="ldsp-lb-empty-icon">❌</div><div class="ldsp-lb-empty-txt">${msg}</div><button class="ldsp-lb-btn secondary ldsp-activity-retry" style="margin-top:12px">🔄 重试</button></div>`; }

            renderTopicListWithSearch(topics, hasMore, search = '', batchSize = 20, totalLoaded = 0) {
                const toolbar = `<div class="ldsp-activity-toolbar"><div class="ldsp-activity-search"><span class="ldsp-activity-search-icon">🔍</span><input type="text" placeholder="搜索标题或标签..." value="${Utils.escapeHtml(search)}"><button class="ldsp-activity-search-clear ${search ? 'show' : ''}">×</button></div><div class="ldsp-activity-toolbar-divider"></div><div class="ldsp-activity-batch"><span class="ldsp-activity-batch-label">加载</span><select class="ldsp-activity-batch-select"><option value="20"${batchSize===20?' selected':''}>20</option><option value="50"${batchSize===50?' selected':''}>50</option><option value="100"${batchSize===100?' selected':''}>100</option><option value="200"${batchSize===200?' selected':''}>200</option><option value="300"${batchSize===300?' selected':''}>300</option></select></div></div>`;
                const stats = `<div class="ldsp-activity-stats">已加载 <strong>${totalLoaded}</strong> 条${search ? ` · 匹配 <strong>${topics.length}</strong> 条` : ''}</div>`;
                if (!topics?.length) return toolbar + stats + this.renderActivityEmpty('📭', search ? '未找到匹配的话题' : '暂无已读话题');
                return toolbar + stats + this._renderTopicItems(topics, hasMore);
            }

            _renderTopicItems(topics, hasMore) {
                const ic = { reply:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>', view:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>', like:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M20.84 4.61a5.5 5.5 0 0 0-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 0 0-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 0 0 0-7.78z"/></svg>' };
                const getAv = u => { if(!u?.avatar_template)return''; let t=u.avatar_template; if(t.startsWith('/'))t=`https://${CURRENT_SITE.domain}${t}`; return t.replace('{size}',36); };
                const getThumb = t => { if(!t.thumbnails?.length)return null; const s=t.thumbnails.find(x=>x.max_width&&x.max_width<=200); return s?.url||t.thumbnails[t.thumbnails.length-1]?.url||t.image_url; };
                // 提取tag名称,兼容新旧格式(新格式是对象数组 {id,name,slug},旧格式是字符串数组)
                const getTagName = tag => typeof tag === 'string' ? tag : (tag?.name || '');
                let h = '<div class="ldsp-topic-list ldsp-topic-list-enhanced">';
                topics.forEach((t,i) => {
                    const title = Utils.escapeHtml(t.title||'无标题'), pc = t.posts_count||0, v = t.views||0, lc = t.like_count||0;
                    const ur = t.unread_posts||t.unread||0, np = t.new_posts||0, tags = t.tags||[], rt = Utils.formatRelativeTime(t.bumped_at);
                    const catName = (typeof t.category_name === 'string' && t.category_name.trim())
                        ? t.category_name
                        : (typeof t.category === 'string' ? t.category : (t.category?.name || ''));
                    const catColor = t.category_color || t.category?.color || '';
                    const catBg = Utils.hexToRgba(catColor, 0.12);
                    const catBd = Utils.hexToRgba(catColor, 0.35);
                    const catStyle = (catName && (catBg || catBd))
                        ? `style="${catBg ? `--cat-bg:${catBg};` : ''}${catBd ? `--cat-bd:${catBd};` : ''}"`
                        : '';
                    const catH = catName ? `<span class="ldsp-topic-tag ldsp-topic-category" ${catStyle}>📁 ${Utils.escapeHtml(catName)}</span>` : '';
                    const url = `https://${CURRENT_SITE.domain}/t/topic/${t.id}`, thumb = getThumb(t), ps = t.postersInfo||[];
                    const badge = ur>0?`<span class="ldsp-topic-badge ldsp-badge-unread">${ur}</span>`:np>0?`<span class="ldsp-topic-badge ldsp-badge-new">${np}</span>`:'';
                    const tagsH = (catName || tags.length)?`<div class="ldsp-topic-tags">${catH}${tags.slice(0,2).map(x=>`<span class="ldsp-topic-tag">${Utils.escapeHtml(getTagName(x))}</span>`).join('')}${tags.length>2?`<span class="ldsp-topic-tag-more">+${tags.length-2}</span>`:''}</div>`:'';
                    let psH = ps.length?'<div class="ldsp-topic-posters">'+ps.slice(0,3).map((p,j)=>`<img src="${getAv(p)}" class="ldsp-topic-avatar ${p.description?.includes('原始发帖人')?'ldsp-poster-op':p.extras?.includes('latest')?'ldsp-poster-latest':''}" title="${Utils.escapeHtml(p.name||p.username)}" style="z-index:${5-j}" loading="lazy">`).join('')+(ps.length>3?`<span class="ldsp-topic-posters-more">+${ps.length-3}</span>`:'')+'</div>':'';
                    h += `<a href="${url}" target="_blank" class="ldsp-topic-item" style="animation-delay:${i*20}ms"><div class="ldsp-topic-main"><div class="ldsp-topic-header"><div class="ldsp-topic-title-row">${badge?`<div class="ldsp-topic-badges">${badge}</div>`:''}<div class="ldsp-topic-title" title="${title}">${title}</div></div><div class="ldsp-topic-info">${tagsH}</div></div><div class="ldsp-topic-footer">${psH}<div class="ldsp-topic-stats"><span class="ldsp-topic-stat" title="回复">${ic.reply}<em>${pc}</em></span><span class="ldsp-topic-stat" title="阅读">${ic.view}<em>${v>=1000?(v/1000).toFixed(1)+'k':v}</em></span>${lc>0?`<span class="ldsp-topic-stat ldsp-stat-like" title="点赞">${ic.like}<em>${lc}</em></span>`:''}<span class="ldsp-topic-time">${rt}</span></div></div></div>${thumb?`<div class="ldsp-topic-thumbnail"><img src="${thumb}" loading="lazy"></div>`:''}</a>`;
                });
                return h+'</div>'+(hasMore?'<div class="ldsp-load-more"><div class="ldsp-load-more-spinner"></div><span>加载更多...</span></div>':'');
            }

            renderTopicList(topics, hasMore) {
                if (!topics?.length) return this.renderActivityEmpty('📭', '暂无已读话题');
                return this._renderTopicItems(topics, hasMore);
            }

            renderBookmarkList(bm, hasMore) {
                if (!bm?.length) return this.renderActivityEmpty('⭐', '暂无收藏');
                return this._renderBookmarkItems(bm, hasMore);
            }

            renderBookmarkListWithSearch(bookmarks, hasMore, search = '', batchSize = 20, totalLoaded = 0) {
                const toolbar = `<div class="ldsp-activity-toolbar"><div class="ldsp-activity-search"><span class="ldsp-activity-search-icon">🔍</span><input type="text" placeholder="搜索标题或标签..." value="${Utils.escapeHtml(search)}"><button class="ldsp-activity-search-clear ${search ? 'show' : ''}">×</button></div><div class="ldsp-activity-toolbar-divider"></div><div class="ldsp-activity-batch"><span class="ldsp-activity-batch-label">加载</span><select class="ldsp-activity-batch-select"><option value="20"${batchSize===20?' selected':''}>20</option><option value="50"${batchSize===50?' selected':''}>50</option><option value="100"${batchSize===100?' selected':''}>100</option><option value="200"${batchSize===200?' selected':''}>200</option><option value="300"${batchSize===300?' selected':''}>300</option></select></div></div>`;
                const stats = `<div class="ldsp-activity-stats">已加载 <strong>${totalLoaded}</strong> 条${search ? ` · 匹配 <strong>${bookmarks.length}</strong> 条` : ''}</div>`;
                if (!bookmarks?.length) return toolbar + stats + this.renderActivityEmpty('⭐', search ? '未找到匹配的收藏' : '暂无收藏');
                return toolbar + stats + this._renderBookmarkItems(bookmarks, hasMore);
            }

            _renderBookmarkItems(bm, hasMore) {
                const ic = { clock:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>', calendar:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>' };
                // 提取tag名称,兼容新旧格式(新格式是对象数组 {id,name,slug},旧格式是字符串数组)
                const getTagName = tag => typeof tag === 'string' ? tag : (tag?.name || '');
                let h = '<div class="ldsp-bookmark-list">';
                bm.forEach((b,i) => {
                    const title = Utils.escapeHtml(b.title||b.fancy_title||'无标题'), tags = b.tags||[], rt = Utils.formatRelativeTime(b.bumped_at), ct = Utils.formatDateTime(b.created_at), url = b.bookmarkable_url||'#', ex = b.excerpt||'';
                    const catName = (typeof b.category_name === 'string' && b.category_name.trim())
                        ? b.category_name
                        : (typeof b.category === 'string' ? b.category : (b.category?.name || ''));
                    const catColor = b.category_color || b.category?.color || '';
                    const catBg = Utils.hexToRgba(catColor, 0.12);
                    const catBd = Utils.hexToRgba(catColor, 0.35);
                    const catStyle = (catName && (catBg || catBd))
                        ? `style="${catBg ? `--cat-bg:${catBg};` : ''}${catBd ? `--cat-bd:${catBd};` : ''}"`
                        : '';
                    const catH = catName ? `<span class="ldsp-bookmark-tag ldsp-bookmark-category" ${catStyle}>📁 ${Utils.escapeHtml(catName)}</span>` : '';
                    const tagsH = (catName || tags.length)?`<div class="ldsp-bookmark-tags">${catH}${tags.slice(0,4).map(t=>`<span class="ldsp-bookmark-tag">${Utils.escapeHtml(getTagName(t))}</span>`).join('')}${tags.length>4?`<span class="ldsp-bookmark-tag-more">+${tags.length-4}</span>`:''}</div>`:'';
                    h += `<div class="ldsp-bookmark-item" data-url="${url}" style="animation-delay:${i*30}ms"><div class="ldsp-bookmark-title">${title}</div><div class="ldsp-bookmark-meta"><span class="ldsp-bookmark-time" title="收藏时间">${ic.calendar}${ct||'--'}</span><span class="ldsp-bookmark-time" title="最后活动">${ic.clock}${rt||'--'}</span></div>${tagsH}${ex?`<div class="ldsp-bookmark-excerpt">${ex}</div>`:''}</div>`;
                });
                return h+'</div>'+(hasMore?'<div class="ldsp-load-more"><div class="ldsp-load-more-spinner"></div><span>加载更多...</span></div>':'');
            }

            renderReplyList(rp, hasMore) {
                if (!rp?.length) return this.renderActivityEmpty('💬', '暂无回复');
                const ic = { clock:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>', reply:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>' };
                let h = '<div class="ldsp-reply-list">';
                rp.forEach((r,i) => {
                    const title = Utils.escapeHtml(r.title||'无标题'), ex = r.excerpt||'', rt = Utils.formatRelativeTime(r.created_at), url = `https://${CURRENT_SITE.domain}/t/topic/${r.topic_id}/${r.post_number}`;
                    h += `<div class="ldsp-reply-item" data-url="${url}" style="animation-delay:${i*30}ms"><div class="ldsp-reply-title">${title}</div><div class="ldsp-reply-meta"><span class="ldsp-reply-time" title="回复时间">${ic.clock}${rt||'--'}</span>${r.reply_to_post_number?`<span class="ldsp-reply-to" title="回复楼层">${ic.reply}#${r.reply_to_post_number}</span>`:''}</div>${ex?`<div class="ldsp-reply-excerpt">${ex}</div>`:''}</div>`;
                });
                return h+'</div>'+(hasMore?'<div class="ldsp-load-more"><div class="ldsp-load-more-spinner"></div><span>加载更多...</span></div>':'');
            }

            renderLikeList(lk, hasMore) {
                if (!lk?.length) return this.renderActivityEmpty('❤️', '暂无赞过内容');
                const ic = { clock:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>', user:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 00-4-4H8a4 4 0 00-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>' };
                let h = '<div class="ldsp-like-list">';
                lk.forEach((l,i) => {
                    const title = Utils.escapeHtml(l.title||'无标题'), ex = l.excerpt||'', rt = Utils.formatRelativeTime(l.created_at), url = `https://${CURRENT_SITE.domain}/t/topic/${l.topic_id}/${l.post_number}`;
                    const name = Utils.escapeHtml(l.name||l.username||'匿名');
                    h += `<div class="ldsp-like-item" data-url="${url}" style="animation-delay:${i*30}ms"><div class="ldsp-like-title">${title}</div><div class="ldsp-like-meta"><span class="ldsp-like-time" title="点赞时间">${ic.clock}${rt||'--'}</span><span class="ldsp-like-author" title="作者:@${l.username}">${ic.user}${name}</span></div>${ex?`<div class="ldsp-like-excerpt">${ex}</div>`:''}</div>`;
                });
                return h+'</div>'+(hasMore?'<div class="ldsp-load-more"><div class="ldsp-load-more-spinner"></div><span>加载更多...</span></div>':'');
            }

            renderMyTopicList(tp, hasMore) {
                if (!tp?.length) return this.renderActivityEmpty('📝', '暂无发布的话题');
                const ic = { reply:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15a2 2 0 01-2 2H7l-4 4V5a2 2 0 012-2h14a2 2 0 012 2z"/></svg>', view:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>', heart:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20.84 4.61a5.5 5.5 0 00-7.78 0L12 5.67l-1.06-1.06a5.5 5.5 0 00-7.78 7.78l1.06 1.06L12 21.23l7.78-7.78 1.06-1.06a5.5 5.5 0 000-7.78z"/></svg>', clock:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>', calendar:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="4" width="18" height="18" rx="2" ry="2"/><line x1="16" y1="2" x2="16" y2="6"/><line x1="8" y1="2" x2="8" y2="6"/><line x1="3" y1="10" x2="21" y2="10"/></svg>', pin:'<svg viewBox="0 0 24 24" fill="currentColor"><path d="M16 12V4h1V2H7v2h1v8l-2 2v2h5.2v6h1.6v-6H18v-2l-2-2z"/></svg>', lock:'<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0110 0v4"/></svg>' };
                // 提取tag名称,兼容新旧格式(新格式是对象数组 {id,name,slug},旧格式是字符串数组)
                const getTagName = tag => typeof tag === 'string' ? tag : (tag?.name || '');
                let h = '<div class="ldsp-mytopic-list">';
                tp.forEach((t,i) => {
                    const title = Utils.escapeHtml(t.title||t.fancy_title||'无标题'), pc = t.posts_count||0, v = t.views||0, lc = t.like_count||0, tags = t.tags||[];
                    const rt = Utils.formatRelativeTime(t.bumped_at), ct = Utils.formatDateTime(t.created_at), url = `https://${CURRENT_SITE.domain}/t/topic/${t.id}`;
                    const catName = (typeof t.category_name === 'string' && t.category_name.trim())
                        ? t.category_name
                        : (typeof t.category === 'string' ? t.category : (t.category?.name || ''));
                    const catColor = t.category_color || t.category?.color || '';
                    const catBg = Utils.hexToRgba(catColor, 0.12);
                    const catBd = Utils.hexToRgba(catColor, 0.35);
                    const catStyle = (catName && (catBg || catBd))
                        ? `style="${catBg ? `--cat-bg:${catBg};` : ''}${catBd ? `--cat-bd:${catBd};` : ''}"`
                        : '';
                    const catH = catName ? `<span class="ldsp-mytopic-tag ldsp-mytopic-category" ${catStyle}>📁 ${Utils.escapeHtml(catName)}</span>` : '';
                    const tagsH = (catName || tags.length)?`<div class="ldsp-mytopic-tags">${catH}${tags.slice(0,3).map(x=>`<span class="ldsp-mytopic-tag">${Utils.escapeHtml(getTagName(x))}</span>`).join('')}${tags.length>3?`<span class="ldsp-mytopic-tag-more">+${tags.length-3}</span>`:''}</div>`:'';
                    const st = (t.pinned?`<span class="ldsp-mytopic-status" title="已置顶">${ic.pin}</span>`:'')+(t.closed?`<span class="ldsp-mytopic-status ldsp-mytopic-closed" title="已关闭">${ic.lock}</span>`:'');
                    h += `<a href="${url}" target="_blank" class="ldsp-mytopic-item${t.closed?' closed':''}" style="animation-delay:${i*30}ms" title="${title}"><div class="ldsp-mytopic-header"><div class="ldsp-mytopic-title">${title}</div>${st?`<div class="ldsp-mytopic-icons">${st}</div>`:''}</div><div class="ldsp-mytopic-row">${tagsH}<span class="ldsp-mytopic-time" title="创建时间">${ic.calendar}${ct||'--'}</span></div><div class="ldsp-mytopic-meta"><span class="ldsp-mytopic-stat" title="回复数">${ic.reply}${pc}</span><span class="ldsp-mytopic-stat" title="浏览量">${ic.view}${v}</span><span class="ldsp-mytopic-stat ldsp-mytopic-likes" title="点赞数">${ic.heart}${lc}</span><div class="ldsp-mytopic-meta-right"><span class="ldsp-mytopic-time" title="最后活动">${ic.clock}${rt||'--'}</span></div></div></a>`;
                });
                return h+'</div>'+(hasMore?'<div class="ldsp-load-more"><div class="ldsp-load-more-spinner"></div><span>加载更多...</span></div>':'');
            }

            renderReactionList(rc, hasMore) {
                if (!rc?.length) return this.renderActivityEmpty('🎭', '暂无互动记录');
                const icClk = '<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><path d="M12 6v6l4 2"/></svg>';
                const getAv = u => { if(!u?.avatar_template)return''; let t=u.avatar_template; return (t.startsWith('/')?`https://${CURRENT_SITE.domain}${t}`:t).replace('{size}',36); };
                const emojiMap = {'+1':'👍','-1':'👎','laughing':'😂','heart':'❤️','open_mouth':'😮','cry':'😢','angry':'😠','thinking':'🤔','clap':'👏','fire':'🔥','tada':'🎉','rocket':'🚀','eyes':'👀','confused':'😕','star':'⭐','pray':'🙏','folded_hands':'🙏','grinning_face':'😀','rofl':'🤣','melting_face':'🫠','distorted_face':'🥴','zany_face':'🤪'};
                const getEmoji = v => emojiMap[v] || emojiMap[v.replace(/^(tieba_|bili_|twemoji_)/,'')] || (v.length<=4?v:'👍');
                let h = '<div class="ldsp-reaction-list">';
                rc.forEach((r,i) => {
                    const p = r.post||{}, pu = p.user||{}, rd = r.reaction||{};
                    const title = Utils.escapeHtml(p.topic_title||p.topic?.title||'无标题'), ex = p.excerpt||'', rt = Utils.formatRelativeTime(r.created_at);
                    const url = `https://${CURRENT_SITE.domain}/t/topic/${p.topic_id}/${p.post_number}`;
                    const name = Utils.escapeHtml(pu.name||pu.username||p.username||'匿名'), un = pu.username||p.username, av = getAv(pu);
                    const rv = rd.reaction_value||'+1', ri = getEmoji(rv), cnt = rd.reaction_users_count||1;
                    h += `<div class="ldsp-reaction-item" data-url="${url}" style="animation-delay:${i*30}ms"><div class="ldsp-reaction-header"><div class="ldsp-reaction-icon" title="${rv}">${ri}</div><div class="ldsp-reaction-title">${title}</div></div><div class="ldsp-reaction-meta">${av?`<img src="${av}" class="ldsp-reaction-avatar" loading="lazy">`:''}<span class="ldsp-reaction-author" title="作者:@${un}">${name}</span><span class="ldsp-reaction-time" title="互动时间">${icClk}${rt||'--'}</span>${cnt>1?`<span class="ldsp-reaction-count" title="共${cnt}人">+${cnt-1}</span>`:''}</div>${ex?`<div class="ldsp-reaction-excerpt">${ex}</div>`:''}</div>`;
                });
                return h+'</div>'+(hasMore?'<div class="ldsp-load-more"><div class="ldsp-load-more-spinner"></div><span>加载更多...</span></div>':'');
            }
        }

        // ==================== 我的活动管理器 ==
        class ActivityManager {
            constructor(network) {
                this.network = network;
                this._cache = new Map();
                this._loading = new Map();
                this._pageState = new Map(); // 记录每个子tab的分页状态
                this._categoryCache = null; // { map, time, source }
            }

            _updateCategoryCacheFromResponse(response, source = 'partial') {
                const roots = [];
                if (Array.isArray(response?.topic_list?.categories)) roots.push(...response.topic_list.categories);
                if (Array.isArray(response?.category_list?.categories)) roots.push(...response.category_list.categories);
                if (Array.isArray(response?.categories)) roots.push(...response.categories);
                if (!roots.length) return false;

                const map = this._categoryCache?.map ? { ...this._categoryCache.map } : {};
                const collect = (cats) => {
                    cats.forEach(cat => {
                        if (!cat || cat.id == null) return;
                        map[cat.id] = {
                            name: cat.name || cat.slug || '',
                            color: cat.color || cat.hex_color || '',
                            textColor: cat.text_color || cat.textColor || ''
                        };
                        const children = cat.subcategory_list?.categories || cat.subcategories || cat.children;
                        if (Array.isArray(children) && children.length) collect(children);
                    });
                };
                collect(roots);

                const currentSource = this._categoryCache?.source;
                this._categoryCache = {
                    map,
                    time: Date.now(),
                    source: source === 'full' ? 'full' : (currentSource || 'partial')
                };
                return true;
            }

            async _getCategoryMap(forceFull = false) {
                const ttl = 30 * 60 * 1000;
                if (!forceFull && this._categoryCache && this._categoryCache.source === 'full' && Date.now() - this._categoryCache.time < ttl) {
                    return this._categoryCache.map || {};
                }
                try {
                    const url = `https://${CURRENT_SITE.domain}/categories.json`;
                    const response = await this.network.fetchJson(url, {
                        headers: {
                            'Accept': 'application/json, text/javascript, */*; q=0.01',
                            'X-Requested-With': 'XMLHttpRequest',
                            'Discourse-Present': 'true',
                            'Discourse-Logged-In': 'true'
                        },
                        credentials: 'include'
                    });
                    const updated = this._updateCategoryCacheFromResponse(response, 'full');
                    if (!updated) throw new Error('empty categories');
                } catch (e) {
                    try {
                        const url = `https://${CURRENT_SITE.domain}/site.json`;
                        const response = await this.network.fetchJson(url, {
                            headers: {
                                'Accept': 'application/json, text/javascript, */*; q=0.01',
                                'X-Requested-With': 'XMLHttpRequest',
                                'Discourse-Present': 'true',
                                'Discourse-Logged-In': 'true'
                            },
                            credentials: 'include'
                        });
                        this._updateCategoryCacheFromResponse(response, 'full');
                    } catch (_) {
                        // ignore
                    }
                }
                return this._categoryCache?.map || {};
            }

            _extractCategoryInfo(item, categoryMap) {
                if (!item) return { missing: false };
                const rawCategory = item.category;
                const catObj = rawCategory && typeof rawCategory === 'object' ? rawCategory : null;
                const catId = item.category_id ?? item.categoryId ?? catObj?.id ?? item.topic?.category_id ?? item.topic?.categoryId ?? item.topic_category_id ?? null;
                const mapped = catId != null ? categoryMap?.[catId] : null;
                const name = (mapped?.name || item.category_name || item.categoryName || catObj?.name || (typeof rawCategory === 'string' ? rawCategory : '') || '').trim();
                const color = (mapped?.color || item.category_color || item.categoryColor || catObj?.color || '').trim();
                const textColor = (mapped?.textColor || item.category_text_color || item.categoryTextColor || catObj?.text_color || catObj?.textColor || '').trim();
                return { id: catId, name, color, textColor, missing: catId != null && !mapped };
            }

            _applyCategoryInfo(item, info) {
                if (!item || !info) return;
                if (info.id != null && item.category_id == null) item.category_id = info.id;
                if (info.name) item.category_name = info.name;
                if (info.color) item.category_color = info.color;
                if (info.textColor) item.category_text_color = info.textColor;
            }

            // 获取已读话题列表(单页)
            async _fetchReadPage(page = 0) {
                const url = page > 0 
                    ? `https://${CURRENT_SITE.domain}/read.json?page=${page}`
                    : `https://${CURRENT_SITE.domain}/read.json`;
                
                const response = await this.network.fetchJson(url, {
                    headers: {
                        'Accept': 'application/json, text/javascript, */*; q=0.01',
                        'X-Requested-With': 'XMLHttpRequest',
                        'Discourse-Present': 'true',
                        'Discourse-Logged-In': 'true'
                    },
                    credentials: 'include'
                });

                if (!response || !response.topic_list) {
                    throw new Error('无效的响应数据');
                }

                this._updateCategoryCacheFromResponse(response);
                let categoryMap = await this._getCategoryMap();
                let needsFull = false;

                // 构建用户ID到用户信息的映射
                const usersMap = {};
                if (response.users && Array.isArray(response.users)) {
                    response.users.forEach(user => {
                        usersMap[user.id] = user;
                    });
                }

                // 为每个话题附加发帖人信息
                const topics = (response.topic_list.topics || []).map(topic => {
                    if (topic.posters && topic.posters.length > 0) {
                        topic.postersInfo = topic.posters.map(poster => {
                            const user = usersMap[poster.user_id];
                            return user ? { ...user, description: poster.description, extras: poster.extras } : null;
                        }).filter(Boolean);
                    }
                    const catInfo = this._extractCategoryInfo(topic, categoryMap);
                    if (catInfo.missing) needsFull = true;
                    this._applyCategoryInfo(topic, catInfo);
                    return topic;
                });

                if (needsFull) {
                    categoryMap = await this._getCategoryMap(true);
                    topics.forEach(topic => this._applyCategoryInfo(topic, this._extractCategoryInfo(topic, categoryMap)));
                }

                return {
                    topics: topics,
                    hasMore: !!response.topic_list.more_topics_url
                };
            }

            // 批量获取已读话题列表(获取100条)
            async getReadTopics(startPage = 0, batchSize = 100) {
                const cacheKey = `read_batch_${startPage}`;
                
                // 检查缓存(1分钟有效)
                const cached = this._cache.get(cacheKey);
                if (cached && Date.now() - cached.time < 60000) {
                    return cached.data;
                }

                // 防止重复请求
                if (this._loading.get(cacheKey)) {
                    return new Promise((resolve) => {
                        const check = () => {
                            if (!this._loading.get(cacheKey)) {
                                resolve(this._cache.get(cacheKey)?.data);
                            } else {
                                setTimeout(check, 100);
                            }
                        };
                        check();
                    });
                }

                this._loading.set(cacheKey, true);

                try {
                    const allTopics = [];
                    let currentPage = startPage;
                    let hasMore = true;
                    const perPage = 30; // 每页约30条
                    const maxPages = Math.ceil(batchSize / perPage);

                    // 循环获取直到达到100条或没有更多数据
                    for (let i = 0; i < maxPages && hasMore && allTopics.length < batchSize; i++) {
                        const pageResult = await this._fetchReadPage(currentPage);
                        const existingIds = new Set(allTopics.map(t => t.id));
                        const newTopics = pageResult.topics.filter(t => !existingIds.has(t.id));
                        allTopics.push(...newTopics);
                        hasMore = pageResult.hasMore;
                        currentPage++;
                    }

                    const result = {
                        topics: allTopics.slice(0, batchSize),
                        hasMore: hasMore || allTopics.length >= batchSize,
                        page: currentPage - 1
                    };

                    this._cache.set(cacheKey, { data: result, time: Date.now() });
                    return result;
                } catch (e) {
                    throw new Error(e.message || '获取已读话题失败');
                } finally {
                    this._loading.set(cacheKey, false);
                }
            }

            // 获取收藏列表
            async getBookmarks(page = 0, username) {
                if (!username) throw new Error('未登录');
                
                const cacheKey = `bookmarks_${page}`;
                
                // 检查缓存(1分钟有效)
                const cached = this._cache.get(cacheKey);
                if (cached && Date.now() - cached.time < 60000) {
                    return cached.data;
                }

                // 防止重复请求
                if (this._loading.get(cacheKey)) {
                    return new Promise((resolve) => {
                        const check = () => {
                            if (!this._loading.get(cacheKey)) {
                                resolve(this._cache.get(cacheKey)?.data);
                            } else {
                                setTimeout(check, 100);
                            }
                        };
                        check();
                    });
                }

                this._loading.set(cacheKey, true);

                try {
                    const url = `https://${CURRENT_SITE.domain}/u/${encodeURIComponent(username)}/bookmarks.json?page=${page}`;
                    
                    const response = await this.network.fetchJson(url, {
                        headers: {
                            'Accept': 'application/json, text/javascript, */*; q=0.01',
                            'X-Requested-With': 'XMLHttpRequest',
                            'Discourse-Present': 'true',
                            'Discourse-Logged-In': 'true'
                        },
                        credentials: 'include'
                    });

                    if (!response || !response.user_bookmark_list) {
                        throw new Error('无效的响应数据');
                    }

                    this._updateCategoryCacheFromResponse(response);
                    let categoryMap = await this._getCategoryMap();
                    let needsFull = false;
                    const bookmarks = (response.user_bookmark_list.bookmarks || []).map(b => {
                        const catInfo = this._extractCategoryInfo(b, categoryMap);
                        if (catInfo.missing) needsFull = true;
                        this._applyCategoryInfo(b, catInfo);
                        return b;
                    });
                    if (needsFull) {
                        categoryMap = await this._getCategoryMap(true);
                        bookmarks.forEach(b => this._applyCategoryInfo(b, this._extractCategoryInfo(b, categoryMap)));
                    }

                    const result = {
                        bookmarks: bookmarks,
                        hasMore: !!response.user_bookmark_list.more_bookmarks_url,
                        page: page
                    };

                    this._cache.set(cacheKey, { data: result, time: Date.now() });
                    return result;
                } catch (e) {
                    throw new Error(e.message || '获取收藏列表失败');
                } finally {
                    this._loading.set(cacheKey, false);
                }
            }

            // 获取回复列表
            async getReplies(offset = 0, username) {
                if (!username) throw new Error('未登录');
                
                const cacheKey = `replies_${offset}`;
                
                // 检查缓存(1分钟有效)
                const cached = this._cache.get(cacheKey);
                if (cached && Date.now() - cached.time < 60000) {
                    return cached.data;
                }

                // 防止重复请求
                if (this._loading.get(cacheKey)) {
                    return new Promise((resolve) => {
                        const check = () => {
                            if (!this._loading.get(cacheKey)) {
                                resolve(this._cache.get(cacheKey)?.data);
                            } else {
                                setTimeout(check, 100);
                            }
                        };
                        check();
                    });
                }

                this._loading.set(cacheKey, true);

                try {
                    const url = `https://${CURRENT_SITE.domain}/user_actions.json?offset=${offset}&username=${encodeURIComponent(username)}&filter=5`;
                    
                    const response = await this.network.fetchJson(url, {
                        headers: {
                            'Accept': 'application/json, text/javascript, */*; q=0.01',
                            'X-Requested-With': 'XMLHttpRequest',
                            'Discourse-Present': 'true',
                            'Discourse-Logged-In': 'true'
                        },
                        credentials: 'include'
                    });

                    if (!response || !response.user_actions) {
                        throw new Error('无效的响应数据');
                    }

                    const replies = response.user_actions || [];
                    const result = {
                        replies: replies,
                        hasMore: replies.length >= 30, // 每页30条,如果返回30条说明可能还有更多
                        offset: offset
                    };

                    this._cache.set(cacheKey, { data: result, time: Date.now() });
                    return result;
                } catch (e) {
                    throw new Error(e.message || '获取回复列表失败');
                } finally {
                    this._loading.set(cacheKey, false);
                }
            }

            // 获取赞过列表
            async getLikes(offset = 0, username) {
                if (!username) throw new Error('未登录');
                
                const cacheKey = `likes_${offset}`;
                
                // 检查缓存(1分钟有效)
                const cached = this._cache.get(cacheKey);
                if (cached && Date.now() - cached.time < 60000) {
                    return cached.data;
                }

                // 防止重复请求
                if (this._loading.get(cacheKey)) {
                    return new Promise((resolve) => {
                        const check = () => {
                            if (!this._loading.get(cacheKey)) {
                                resolve(this._cache.get(cacheKey)?.data);
                            } else {
                                setTimeout(check, 100);
                            }
                        };
                        check();
                    });
                }

                this._loading.set(cacheKey, true);

                try {
                    const url = `https://${CURRENT_SITE.domain}/user_actions.json?offset=${offset}&username=${encodeURIComponent(username)}&filter=1`;
                    
                    const response = await this.network.fetchJson(url, {
                        headers: {
                            'Accept': 'application/json, text/javascript, */*; q=0.01',
                            'X-Requested-With': 'XMLHttpRequest',
                            'Discourse-Present': 'true',
                            'Discourse-Logged-In': 'true'
                        },
                        credentials: 'include'
                    });

                    if (!response || !response.user_actions) {
                        throw new Error('无效的响应数据');
                    }

                    const likes = response.user_actions || [];
                    const result = {
                        likes: likes,
                        hasMore: likes.length >= 30, // 每页30条,如果返回30条说明可能还有更多
                        offset: offset
                    };

                    this._cache.set(cacheKey, { data: result, time: Date.now() });
                    return result;
                } catch (e) {
                    throw new Error(e.message || '获取赞过列表失败');
                } finally {
                    this._loading.set(cacheKey, false);
                }
            }

            // 获取我的话题列表
            async getMyTopics(page = 0, username) {
                if (!username) throw new Error('未登录');
                
                const cacheKey = `topics_${page}`;
                
                // 检查缓存(1分钟有效)
                const cached = this._cache.get(cacheKey);
                if (cached && Date.now() - cached.time < 60000) {
                    return cached.data;
                }

                // 防止重复请求
                if (this._loading.get(cacheKey)) {
                    return new Promise((resolve) => {
                        const check = () => {
                            if (!this._loading.get(cacheKey)) {
                                resolve(this._cache.get(cacheKey)?.data);
                            } else {
                                setTimeout(check, 100);
                            }
                        };
                        check();
                    });
                }

                this._loading.set(cacheKey, true);

                try {
                    const url = page > 0
                        ? `https://${CURRENT_SITE.domain}/topics/created-by/${encodeURIComponent(username)}.json?page=${page}`
                        : `https://${CURRENT_SITE.domain}/topics/created-by/${encodeURIComponent(username)}.json`;
                    
                    const response = await this.network.fetchJson(url, {
                        headers: {
                            'Accept': 'application/json, text/javascript, */*; q=0.01',
                            'X-Requested-With': 'XMLHttpRequest',
                            'Discourse-Present': 'true',
                            'Discourse-Logged-In': 'true'
                        },
                        credentials: 'include'
                    });

                    if (!response || !response.topic_list) {
                        throw new Error('无效的响应数据');
                    }

                    this._updateCategoryCacheFromResponse(response);
                    let categoryMap = await this._getCategoryMap();
                    let needsFull = false;
                    const topics = (response.topic_list.topics || []).map(t => {
                        const catInfo = this._extractCategoryInfo(t, categoryMap);
                        if (catInfo.missing) needsFull = true;
                        this._applyCategoryInfo(t, catInfo);
                        return t;
                    });
                    if (needsFull) {
                        categoryMap = await this._getCategoryMap(true);
                        topics.forEach(t => this._applyCategoryInfo(t, this._extractCategoryInfo(t, categoryMap)));
                    }
                    const result = {
                        topics: topics,
                        hasMore: topics.length >= 30, // 每页30条
                        page: page
                    };

                    this._cache.set(cacheKey, { data: result, time: Date.now() });
                    return result;
                } catch (e) {
                    throw new Error(e.message || '获取我的话题失败');
                } finally {
                    this._loading.set(cacheKey, false);
                }
            }

            // 获取互动记录(reactions)
            async getReactions(beforeId = null, username) {
                if (!username) throw new Error('未登录');
                
                const cacheKey = `reactions_${beforeId || 'first'}`;
                
                // 检查缓存(1分钟有效)
                const cached = this._cache.get(cacheKey);
                if (cached && Date.now() - cached.time < 60000) {
                    return cached.data;
                }

                // 防止重复请求
                if (this._loading.get(cacheKey)) {
                    return new Promise((resolve) => {
                        const check = () => {
                            if (!this._loading.get(cacheKey)) {
                                resolve(this._cache.get(cacheKey)?.data);
                            } else {
                                setTimeout(check, 100);
                            }
                        };
                        check();
                    });
                }

                this._loading.set(cacheKey, true);

                try {
                    let url = `https://${CURRENT_SITE.domain}/discourse-reactions/posts/reactions.json?username=${encodeURIComponent(username)}`;
                    if (beforeId) {
                        url += `&before_reaction_user_id=${beforeId}`;
                    }
                    
                    const response = await this.network.fetchJson(url, {
                        headers: {
                            'Accept': 'application/json, text/javascript, */*; q=0.01',
                            'X-Requested-With': 'XMLHttpRequest',
                            'Discourse-Present': 'true',
                            'Discourse-Logged-In': 'true'
                        },
                        credentials: 'include'
                    });

                    if (!response || !Array.isArray(response)) {
                        throw new Error('无效的响应数据');
                    }

                    const reactions = response || [];
                    // 获取最后一条记录的 id 用于分页
                    const lastId = reactions.length > 0 ? reactions[reactions.length - 1].id : null;
                    
                    const result = {
                        reactions: reactions,
                        hasMore: reactions.length >= 20, // 每页约20条,如果返回20条说明可能还有更多
                        lastId: lastId
                    };

                    this._cache.set(cacheKey, { data: result, time: Date.now() });
                    return result;
                } catch (e) {
                    throw new Error(e.message || '获取互动记录失败');
                } finally {
                    this._loading.set(cacheKey, false);
                }
            }

            // 清除缓存和分页状态
            clearCache(type) {
                if (type) {
                    // 清除特定类型的缓存
                    for (const key of this._cache.keys()) {
                        if (key.startsWith(type)) {
                            this._cache.delete(key);
                        }
                    }
                    // 同时清除分页状态
                    this._pageState.delete(type);
                } else {
                    this._cache.clear();
                    this._pageState.clear();
                }
            }

            // 获取分页状态
            getPageState(type) {
                return this._pageState.get(type) || { page: 0, allTopics: [], hasMore: true, search: '', batchSize: 20 };
            }

            // 设置分页状态
            setPageState(type, state) {
                this._pageState.set(type, state);
            }
        }

        // ==================== 主面板类 ====================
        class Panel {
            constructor() {
                this._initManagers();
                this._initState();
                this._initUI();
                this._initCloudServices();
                this._initEventListeners();
                this._initTimers();
            }
            
            // 初始化管理器实例
            _initManagers() {
                this.storage = new Storage();
                this.network = new Network();
                this.historyMgr = new HistoryManager(this.storage);
                this.tracker = new ReadingTracker(this.storage);
                this.notifier = new Notifier(this.storage);
                this.activityMgr = new ActivityManager(this.network);

                // 排行榜相关(仅支持的站点)
                this.hasLeaderboard = CURRENT_SITE.supportsLeaderboard;
                if (this.hasLeaderboard) {
                    this.oauth = new OAuthManager(this.storage, this.network);
                    this.leaderboard = new LeaderboardManager(this.oauth, this.tracker, this.storage);
                    this.cloudSync = new CloudSyncManager(this.storage, this.oauth, this.tracker);
                    this.cloudSync.setHistoryManager(this.historyMgr);
                    this.lbTab = this.storage.getGlobal('leaderboardTab', 'daily');
                }
            }
            
            // 初始化状态变量
            _initState() {
                this.prevReqs = [];
                this.trendTab = this.storage.getGlobal('trendTab', 'today');
                // 兼容性:修复旧版本的无效 tab 值
                if (!['today', 'week', 'month', 'year', 'all'].includes(this.trendTab)) {
                    this.trendTab = 'today';
                    this.storage.setGlobal('trendTab', 'today');
                }
                this.avatar = this.storage.get('userAvatar', null);
                this.readingTime = 0;
                this.username = null;
                this.animRing = true;
                this.cachedHistory = [];
                this.cachedReqs = [];
                this.loading = false;
                this.registrationPaused = false;  // 后端暂停新用户注册开关(通过 /api/user/status 透出)
                this.hasJoinedBefore = false;    // joinedAt 存在时为 true,用于判断老用户
                this._readingTimer = null;
                this._destroyed = false;  // 销毁标记
                this._followDataLoaded = false;
                this._followProfileLoaded = false;
                this._actionsUserExpanded = false;
                this._actionsCheckRaf = null;
                this._actionsRowHeight = 0;
                this._lastSizeKey = null;
                
                // 我的活动相关状态
                this.activitySubTab = 'read';  // 默认子tab
                this._activityScrollHandler = null;  // 滚动事件处理器
            }
            
            // 初始化 UI
            _initUI() {
                Styles.inject();
                this._createPanel();
                this.renderer = new Renderer(this);
                this._bindEvents();
                this._restore();
                this.fetch();

                // 初始化全局弹窗管理器(传入面板根元素)
                LDSPDialog.init(this.el);
                // 管理器改为按需懒加载,减少首屏开销
                this._loadAvatarFromCache();
                // 轻量预取关注/粉丝/入站天数(空闲时执行,不阻塞首屏)
                requestIdleCallback(() => {
                    const fm = this._ensureFollowManager();
                    fm?.loadData().catch(e => Logger.warn('FollowManager prefetch error:', e));
                    fm?.loadProfile().catch(e => Logger.warn('FollowManager profile prefetch error:', e));
                });
            }

            // ==================== 懒加载各功能管理器 ====================
            _ensureTicketManager() {
                if (!this.hasLeaderboard || !this.oauth) return null;
                if (!this.ticketManager) {
                    this.ticketManager = new TicketManager(this.oauth, this.$.panelBody);
                    this.ticketManager.init().catch(e => Logger.warn('TicketManager init error:', e));
                }
                return this.ticketManager;
            }

            _ensureMelonHelper() {
                if (!this.melonHelper) {
                    this.melonHelper = new MelonHelper(this.$.panelBody, this.renderer);
                    this.melonHelper.init();
                }
                return this.melonHelper;
            }

            _ensureTopicExporter() {
                if (!this.topicExporter) {
                    this.topicExporter = new TopicExporter(this.$.panelBody, this.renderer);
                    this.topicExporter.init();
                }
                return this.topicExporter;
            }

            _ensureLdcManager() {
                if (CURRENT_SITE.domain !== 'linux.do') return null;
                if (!this.ldcManager) {
                    this.ldcManager = new LDCManager(this.$.panelBody, this.renderer);
                    this.ldcManager.init();
                }
                return this.ldcManager;
            }

            _ensureCdkManager() {
                if (CURRENT_SITE.domain !== 'linux.do') return null;
                if (!this.cdkManager) {
                    this.cdkManager = new CDKManager(this.$.panelBody, this.renderer);
                    this.cdkManager.init();
                }
                return this.cdkManager;
            }

            _ensureFollowManager() {
                if (!this.followManager) {
                    this.followManager = new FollowManager(this.network, this.storage, this.$.panelBody, this.renderer);
                    this.followManager.init();
                }
                // 懒加载数据与用户档案,避免首屏请求
                if (!this._followDataLoaded) {
                    this._followDataLoaded = true;
                    requestIdleCallback(() => {
                        this.followManager.loadData().catch(e => Logger.warn('FollowManager load error:', e));
                    });
                }
                if (!this._followProfileLoaded) {
                    this._followProfileLoaded = true;
                    requestIdleCallback(() => {
                        this.followManager.loadProfile().catch(e => Logger.warn('FollowManager profile error:', e));
                    });
                }
                return this.followManager;
            }
            
            // 初始化云服务
            _initCloudServices() {
                // 检查待处理的 OAuth 登录结果(统一同窗口模式)
                let justLoggedIn = false;
                if (this.hasLeaderboard && this.oauth) {
                    justLoggedIn = this._checkPendingOAuthLogin();
                }

                if (!this.hasLeaderboard) return;
                
                // 注册同步状态回调
                this.cloudSync.setSyncStateCallback(syncing => {
                    if (this._destroyed) return;
                    if (this.$.btnCloudSync) {
                        this.$.btnCloudSync.disabled = syncing;
                        this.$.btnCloudSync.textContent = syncing ? '⏳' : '☁️';
                        this.$.btnCloudSync.title = syncing ? '同步中...' : '云同步';
                    }
                    // 云同步完成时检查未读工单
                    if (!syncing) this.ticketManager?._checkUnread();
                });

                if (this.oauth.isLoggedIn() && !justLoggedIn) {
                    this._initLoggedInUser();
                } else if (justLoggedIn) {
                    if (this.oauth.isJoined()) this.leaderboard.startSync();
                } else {
                    this._checkLoginPrompt();
                }
            }
            
            // 初始化已登录用户
            _initLoggedInUser() {
                const oauthUser = this.oauth.getUserInfo();
                if (oauthUser?.username) {
                    const currentUser = this.storage.getUser();
                    if (currentUser !== oauthUser.username) {
                        this.storage.setUser(oauthUser.username);
                        this.storage.invalidateCache();
                        this.storage.migrate(oauthUser.username);
                    }
                    this._updateUserInfoFromOAuth(oauthUser);
                }
                // 串行化同步请求
                this.cloudSync.onPageLoad()
                    .then(() => this.cloudSync.syncRequirementsOnLoad())
                    .then(() => {
                        // 同步完成后清除缓存,确保下次获取最新数据
                        this._cloudReqsCache = null;
                        this._cloudReqsCacheTime = 0;
                    })
                    .catch(e => Logger.warn('CloudSync error:', e));
                this._syncPrefs();
                if (this.oauth.isJoined()) this.leaderboard.startSync();
                this._updateLoginUI();
            }
            
            // 初始化事件监听器
            _initEventListeners() {
                // 窗口大小变化
                this._resizeHandler = Utils.debounce(() => this._onResize(), 250);
                window.addEventListener('resize', this._resizeHandler);
                
                // 订阅 Token 过期事件
                EventBus.on('auth:expired', () => {
                    this.renderer?.showToast('⚠️ 登录已过期,请重新登录');
                    this._updateLoginUI();
                });
                
                // 订阅阅读数据同步完成事件
                EventBus.on('reading:synced', ({ merged, source }) => {
                    Logger.log(`阅读数据已同步: ${merged} 天, 来源: ${source}`);
                    // 更新本地阅读时间变量
                    this.readingTime = this.tracker.getTodayTime();
                    // 清除年度缓存
                    this.tracker._yearCache = null;
                    // 检查当前是否在趋势页面(通过 DOM 查询)
                    const trendsActive = this.el?.querySelector('.ldsp-tab[data-tab="trends"].active');
                    if (trendsActive) {
                        // 使用缓存的历史和要求数据重新渲染趋势页面
                        this._renderTrends(this.cachedHistory || [], this.cachedReqs || []);
                    }
                    // 更新顶部阅读时间显示
                    this.renderer.renderReading(this.readingTime, this.tracker.isActive);
                    // 数据同步完成后触发工单未读检测
                    this.ticketManager?._checkUnread();
                });
            }
            
            // 初始化定时任务
            _initTimers() {
                // 定期刷新数据(只有领导者标签页执行)
                this._refreshTimer = setInterval(() => {
                    if (!this._destroyed && TabLeader.isLeader()) {
                        this.fetch();
                    }
                }, CONFIG.INTERVALS.REFRESH);
                
                // v3.5.2.9: 优化请求时序,避免并发导致429
                // 公告加载延迟到 3 秒后(在云同步下载完成之后)
                setTimeout(() => !this._destroyed && this._loadAnnouncement(), 3000);
                
                // 版本检查延迟到 4 秒后
                setTimeout(() => !this._destroyed && this._checkUpdate(true), 4000);
            }

            _createPanel() {
                this.el = document.createElement('div');
                this.el.id = 'ldsp-panel';
                this.el.setAttribute('role', 'complementary');
                this.el.setAttribute('aria-label', `${CURRENT_SITE.name} 信任级别面板`);

                this.el.innerHTML = `
                    <div class="ldsp-hdr">
                        <div class="ldsp-hdr-info">
                            <div class="ldsp-site-wrap">
                                <img class="ldsp-site-icon" src="${CURRENT_SITE.icon}" alt="${CURRENT_SITE.name}">
                                <span class="ldsp-site-ver">v${GM_info.script.version}</span>
                            </div>
                            <div class="ldsp-hdr-text">
                                <span class="ldsp-title">${CURRENT_SITE.name}</span>
                                <span class="ldsp-ver"><span class="ldsp-app-name">LDStatus Pro</span></span>
                            </div>
                        </div>
                        <div class="ldsp-hdr-btns">
                            <button class="ldsp-update" title="检查更新" aria-label="检查更新">🔍</button>
                            <button class="ldsp-cloud-sync" title="云同步" aria-label="云同步" style="display:none">☁️</button>
                            <button class="ldsp-refresh" title="刷新数据" aria-label="刷新数据">🔄</button>
                            <button class="ldsp-theme" title="切换主题" aria-label="切换主题">🌓</button>
                            <button class="ldsp-toggle" title="折叠面板" aria-label="折叠面板"><span class="ldsp-toggle-arrow">◀</span><svg class="ldsp-toggle-logo" viewBox="0 0 64 64" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="ldsp-logo-grad" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#8fa8f8"/><stop offset="100%" stop-color="#7ed4c4"/></linearGradient></defs><path d="M 31,4 A 28,28 0 1,1 11,52" fill="none" stroke="url(#ldsp-logo-grad)" stroke-width="8" stroke-linecap="round"/><rect x="25" y="26" width="12" height="12" rx="3" fill="url(#ldsp-logo-grad)" transform="rotate(45 31 32)"/></svg></button>
                        </div>
                    </div>
                    <div class="ldsp-update-bubble" style="display:none">
                        <div class="ldsp-update-bubble-close">×</div>
                        <div class="ldsp-update-bubble-icon">🎉</div>
                        <div class="ldsp-update-bubble-title">发现新版本</div>
                        <div class="ldsp-update-bubble-ver"></div>
                        <button class="ldsp-update-bubble-btn">🚀 立即更新</button>
                    </div>
                    <div class="ldsp-body">
                        <div class="ldsp-announcement">
                            <div class="ldsp-announcement-inner">
                                <span class="ldsp-announcement-text"></span>
                            </div>
                        </div>
                        <div class="ldsp-user">
                            <div class="ldsp-user-left">
                                <div class="ldsp-user-row">
                                    <div class="ldsp-avatar-wrap" data-clickable><div class="ldsp-avatar-ph">👤</div></div>
                                    <div class="ldsp-user-info">
                                        <div class="ldsp-user-display-name">加载中...</div>
                                        <div class="ldsp-user-handle"></div>
                                    </div>
                                </div>
                                <div class="ldsp-user-meta">
                                    <div class="ldsp-follow-combined">
                                        <span class="ldsp-follow-part ldsp-follow-following" data-clickable data-tab="following" title="查看关注列表">关注 <span class="ldsp-follow-num-following">-</span></span>
                                        <span class="ldsp-follow-sep">·</span>
                                        <span class="ldsp-follow-part ldsp-follow-followers" data-clickable data-tab="followers" title="查看粉丝列表"><span class="ldsp-follow-num-followers">-</span> 粉丝</span>
                                    </div>
                                    <span class="ldsp-join-days">来<span class="ldsp-join-days-site"></span><span class="ldsp-join-days-num">-</span>天</span>
                                </div>
                                <div class="ldsp-user-actions-wrap">
                                    <div class="ldsp-user-actions collapsed">
                                        <div class="ldsp-action-btn ldsp-login-btn" data-clickable title="点击登录"><span class="ldsp-action-icon">🔑</span><span class="ldsp-action-text">点击登录</span></div>
                                        <div class="ldsp-action-btn ldsp-logout-btn" data-clickable title="注销登录"><span class="ldsp-action-icon">⏻</span><span class="ldsp-action-text">注销</span></div>
                                        ${CURRENT_SITE.domain === 'linux.do' ? '<div class="ldsp-action-btn ldsp-ldc-btn" data-clickable title="Linux Do Credit"><span class="ldsp-action-icon">🍟</span><span class="ldsp-action-text">LDC</span></div>' : ''}
                                        ${CURRENT_SITE.domain === 'linux.do' ? '<div class="ldsp-action-btn ldsp-shop-btn" data-clickable title="LD士多"><span class="ldsp-action-icon">🍔</span><span class="ldsp-action-text">士多</span></div>' : ''}
                                        ${CURRENT_SITE.domain === 'linux.do' ? '<div class="ldsp-action-btn ldsp-cdk-btn" data-clickable title="CDK 系统"><span class="ldsp-action-icon">🎁</span><span class="ldsp-action-text">CDK</span></div>' : ''}
                                        <div class="ldsp-action-btn ldsp-melon-btn" data-clickable title="AI 帖子总结"><span class="ldsp-action-icon">🍉</span><span class="ldsp-action-text">总结</span></div>
                                        <div class="ldsp-action-btn ldsp-export-btn" data-clickable title="导出帖子为PDF/HTML/Markdown"><span class="ldsp-action-icon">📥</span><span class="ldsp-action-text">导出</span></div>
                                        <div class="ldsp-action-btn ldsp-ticket-btn" data-clickable title="工单系统"><span class="ldsp-action-icon">📪</span><span class="ldsp-action-text">工单</span></div>
                                    </div>
                                    <div class="ldsp-user-actions-toggle" data-clickable title="展开更多按钮"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polyline points="6 9 12 15 18 9"/></svg><span class="ldsp-toggle-text">展开更多</span></div>
                                </div>
                            </div>
                            <div class="ldsp-reading" data-clickable title="点击访问 LDStatus Pro 官网">
                                <div class="ldsp-reading-ripple"></div>
                                <span class="ldsp-reading-icon">🌱</span>
                                <span class="ldsp-reading-time">--</span>
                                <span class="ldsp-reading-label">今日阅读</span>
                            </div>
                        </div>
                        <div class="ldsp-tabs">
                            <button class="ldsp-tab active" data-tab="reqs"><span class="ldsp-tab-icon">📋</span><span class="ldsp-tab-text">要求</span></button>
                            <button class="ldsp-tab" data-tab="trends"><span class="ldsp-tab-icon">📈</span><span class="ldsp-tab-text">趋势</span></button>
                            ${this.hasLeaderboard ? '<button class="ldsp-tab" data-tab="leaderboard"><span class="ldsp-tab-icon">🏆</span><span class="ldsp-tab-text">排行</span></button>' : ''}
                            <button class="ldsp-tab" data-tab="activity"><span class="ldsp-tab-icon">👤</span><span class="ldsp-tab-text">我的</span></button>
                        </div>
                        <div class="ldsp-content">
                            <div id="ldsp-reqs" class="ldsp-section active"><div class="ldsp-loading"><div class="ldsp-spinner"></div><div>加载中...</div></div></div>
                            <div id="ldsp-trends" class="ldsp-section"><div class="ldsp-empty"><div class="ldsp-empty-icon">📊</div><div class="ldsp-empty-txt">暂无历史数据</div></div></div>
                            ${this.hasLeaderboard ? '<div id="ldsp-leaderboard" class="ldsp-section"><div class="ldsp-loading"><div class="ldsp-spinner"></div><div>加载中...</div></div></div>' : ''}
                            <div id="ldsp-activity" class="ldsp-section"><div class="ldsp-empty"><div class="ldsp-empty-icon">👤</div><div class="ldsp-empty-txt">选择一个分类查看</div></div></div>
                        </div>
                        <div class="ldsp-confirm-overlay">
                            <div class="ldsp-confirm-box">
                                <div class="ldsp-confirm-icon">⏻</div>
                                <div class="ldsp-confirm-title">确认注销登录吗?</div>
                                <div class="ldsp-confirm-msg">退出后排行榜和云同步功能将不可用</div>
                                <div class="ldsp-confirm-btns">
                                    <button class="ldsp-confirm-btn cancel">取消</button>
                                    <button class="ldsp-confirm-btn confirm">确认注销</button>
                                </div>
                            </div>
                        </div>
                    </div>
                    <div class="ldsp-resize-handle ldsp-resize-e"></div>
                    <div class="ldsp-resize-handle ldsp-resize-s"></div>
                    <div class="ldsp-resize-handle ldsp-resize-se"></div>`;

                document.body.appendChild(this.el);
                
                // 初始化resize功能(仅桌面端)
                this._initResize();
                // 应用初始面板尺寸(含自定义尺寸/相对尺寸)
                this._applyPanelSize('init');
                
                // 绑定自定义 Tooltip
                Tooltip.bindToPanel(this.el);

                this.$ = {
                    header: this.el.querySelector('.ldsp-hdr'),
                    announcement: this.el.querySelector('.ldsp-announcement'),
                    announcementText: this.el.querySelector('.ldsp-announcement-text'),
                    user: this.el.querySelector('.ldsp-user'),
                    userDisplayName: this.el.querySelector('.ldsp-user-display-name'),
                    userHandle: this.el.querySelector('.ldsp-user-handle'),
                    ticketBtn: this.el.querySelector('.ldsp-ticket-btn'),
                    melonBtn: this.el.querySelector('.ldsp-melon-btn'),
                    exportBtn: this.el.querySelector('.ldsp-export-btn'),
                    ldcBtn: this.el.querySelector('.ldsp-ldc-btn'),
                    shopBtn: this.el.querySelector('.ldsp-shop-btn'),
                    cdkBtn: this.el.querySelector('.ldsp-cdk-btn'),
                    logoutBtn: this.el.querySelector('.ldsp-logout-btn'),
                    loginBtn: this.el.querySelector('.ldsp-login-btn'),
                    actionsArea: this.el.querySelector('.ldsp-user-actions'),
                    actionsToggle: this.el.querySelector('.ldsp-user-actions-toggle'),
                    confirmOverlay: this.el.querySelector('.ldsp-confirm-overlay'),
                    confirmCancel: this.el.querySelector('.ldsp-confirm-btn.cancel'),
                    confirmOk: this.el.querySelector('.ldsp-confirm-btn.confirm'),
                    panelBody: this.el.querySelector('.ldsp-body'),
                    reading: this.el.querySelector('.ldsp-reading'),
                    readingIcon: this.el.querySelector('.ldsp-reading-icon'),
                    readingTime: this.el.querySelector('.ldsp-reading-time'),
                    readingLabel: this.el.querySelector('.ldsp-reading-label'),
                    tabs: this.el.querySelectorAll('.ldsp-tab'),
                    sections: this.el.querySelectorAll('.ldsp-section'),
                    reqs: this.el.querySelector('#ldsp-reqs'),
                    trends: this.el.querySelector('#ldsp-trends'),
                    leaderboard: this.el.querySelector('#ldsp-leaderboard'),
                    activity: this.el.querySelector('#ldsp-activity'),
                    btnToggle: this.el.querySelector('.ldsp-toggle'),
                    btnRefresh: this.el.querySelector('.ldsp-refresh'),
                    btnTheme: this.el.querySelector('.ldsp-theme'),
                    btnUpdate: this.el.querySelector('.ldsp-update'),
                    btnCloudSync: this.el.querySelector('.ldsp-cloud-sync'),
                    updateBubble: this.el.querySelector('.ldsp-update-bubble'),
                    updateBubbleVer: this.el.querySelector('.ldsp-update-bubble-ver'),
                    updateBubbleBtn: this.el.querySelector('.ldsp-update-bubble-btn'),
                    updateBubbleClose: this.el.querySelector('.ldsp-update-bubble-close')
                };
            }

            _bindEvents() {
                // 拖拽(支持鼠标和触摸)
                let dragging = false, ox, oy, moved = false, sx, sy;
                const THRESHOLD = 5;

                const getPos = e => e.touches ? { x: e.touches[0].clientX, y: e.touches[0].clientY } : { x: e.clientX, y: e.clientY };

                // 保存拖拽开始前的样式,以便在没有实际移动时恢复
                let dragStartStyles = null;
                
                const startDrag = e => {
                    if (!this.el.classList.contains('collapsed') && e.target.closest('button')) return;
                    const p = getPos(e);
                    dragging = true;
                    moved = false;
                    // 获取当前位置
                    const rect = this.el.getBoundingClientRect();
                    
                    // 保存拖拽开始前的样式
                    dragStartStyles = {
                        left: this.el.style.left,
                        right: this.el.style.right,
                        top: this.el.style.top
                    };
                    
                    this.el.classList.add('no-trans');
                    // 统一切换到 left 定位进行拖拽
                    this.el.style.left = rect.left + 'px';
                    this.el.style.right = 'auto';
                    ox = p.x - rect.left;
                    oy = p.y - rect.top;
                    sx = p.x;
                    sy = p.y;
                    e.preventDefault();
                };

                const updateDrag = e => {
                    if (!dragging) return;
                    const p = getPos(e);
                    if (Math.abs(p.x - sx) > THRESHOLD || Math.abs(p.y - sy) > THRESHOLD) moved = true;
                    const { innerWidth: vw, innerHeight: vh } = window;
                    const w = this.el.offsetWidth, h = this.el.offsetHeight;
                    this.el.style.left = Math.max(8, Math.min(p.x - ox, vw - w - 8)) + 'px';
                    this.el.style.top = Math.max(8, Math.min(p.y - oy, vh - h - 8)) + 'px';
                };

                const endDrag = () => {
                    if (!dragging) return;
                    dragging = false;
                    this.el.classList.remove('no-trans');
                    
                    // 如果没有实际移动,恢复拖拽开始前的样式
                    // 这样点击折叠面板时就不会因为 hover 缩放导致位置偏移
                    if (!moved) {
                        if (dragStartStyles) {
                            this.el.style.left = dragStartStyles.left;
                            this.el.style.right = dragStartStyles.right;
                            this.el.style.top = dragStartStyles.top;
                        }
                        dragStartStyles = null;
                        return;
                    }
                    dragStartStyles = null;
                    
                    // 根据最终位置决定定位模式和展开方向
                    const rect = this.el.getBoundingClientRect();
                    const { innerWidth: vw } = window;
                    const centerX = rect.left + rect.width / 2;
                    const alignRight = centerX > vw / 2;
                    
                    if (alignRight) {
                        // 切换到 right 定位
                        const rightDist = Math.round(vw - rect.right);
                        this.el.style.right = rightDist + 'px';
                        this.el.style.left = 'auto';
                    }
                    // 左侧保持 left 定位(在 startDrag 中已设置)
                    
                    // 更新展开方向类
                    this.el.classList.toggle('expand-left', alignRight);
                    this.el.classList.toggle('expand-right', !alignRight);
                    
                    // 更新箭头方向
                    this._updateArrow();
                    
                    this._savePosition();
                };

                // 鼠标事件
                this.$.header.addEventListener('mousedown', e => !this.el.classList.contains('collapsed') && startDrag(e));
                this.el.addEventListener('mousedown', e => this.el.classList.contains('collapsed') && startDrag(e));
                document.addEventListener('mousemove', updateDrag);
                document.addEventListener('mouseup', endDrag);
                // 触摸事件(移动端拖拽)
                this.$.header.addEventListener('touchstart', e => !this.el.classList.contains('collapsed') && startDrag(e), { passive: false });
                this.el.addEventListener('touchstart', e => this.el.classList.contains('collapsed') && startDrag(e), { passive: false });
                document.addEventListener('touchmove', updateDrag, { passive: false });
                document.addEventListener('touchend', e => {
                    const wasDragging = dragging;
                    const isCollapsed = this.el.classList.contains('collapsed');
                    endDrag();
                    // 触摸未移动且是折叠状态,视为点击展开
                    if (wasDragging && !moved && isCollapsed) {
                        this._toggle();
                    }
                    // 移动端:清除折叠 logo 的 hover 残留效果
                    if (isCollapsed && wasDragging) {
                        this.el.classList.add('no-hover-effect');
                        setTimeout(() => this.el.classList.remove('no-hover-effect'), 50);
                    }
                });

                // 按钮事件
                this.$.btnToggle.addEventListener('click', e => {
                    e.stopPropagation();
                    if (moved) { moved = false; return; }
                    this._toggle();
                });

                this.$.btnRefresh.addEventListener('click', () => {
                    if (this.loading) return;
                    this.animRing = true;
                    this.fetch();
                    // 刷新数据时同步检查未读工单
                    this.ticketManager?._checkUnread();
                });

                this.$.btnTheme.addEventListener('click', () => this._switchTheme());
                this.$.btnUpdate.addEventListener('click', () => this._checkUpdate());
                
                // 彩蛋:点击头像打开GitHub仓库
                this.$.user.addEventListener('click', e => {
                    if (e.target.closest('.ldsp-avatar-wrap')) {
                        window.open('https://github.com/caigg188/LDStatusPro', '_blank');
                    }
                });
                
                // 注销登录按钮与确认弹窗
                const showLogoutConfirm = () => {
                    if (!this.hasLeaderboard || !this.oauth?.isLoggedIn()) {
                        this.renderer.showToast('ℹ️ 当前未登录');
                        return;
                    }
                    this.$.confirmOverlay?.classList.add('show');
                };
                const hideLogoutConfirm = () => {
                    this.$.confirmOverlay?.classList.remove('show');
                };
                const doLogout = () => {
                    hideLogoutConfirm();
                    this.oauth.logout();
                    this.leaderboard?.stopSync();
                    this.registrationPaused = false;
                    this.hasJoinedBefore = false;
                    this.renderer.showToast('✅ 已退出登录');
                    this._updateLoginUI();
                    this._renderLeaderboard();
                };
                // 注销按钮点击
                this.$.logoutBtn?.addEventListener('click', (e) => {
                    e.stopPropagation();
                    showLogoutConfirm();
                });
                // 确认弹窗按钮
                this.$.confirmCancel?.addEventListener('click', hideLogoutConfirm);
                this.$.confirmOk?.addEventListener('click', doLogout);
                // 点击遮罩关闭
                this.$.confirmOverlay?.addEventListener('click', (e) => {
                    if (e.target === this.$.confirmOverlay) hideLogoutConfirm();
                });
                
                // 云同步按钮(状态由 CloudSyncManager 的回调自动管理)
                this.$.btnCloudSync?.addEventListener('click', async () => {
                    if (!this.hasLeaderboard || !this.oauth?.isLoggedIn()) return;
                    if (this.cloudSync.isSyncing()) return;  // 正在同步中,忽略点击
                    try {
                        await this.cloudSync.fullSync();
                        this.renderer.showToast('✅ 数据同步完成');
                        this.renderer.renderReading(this.tracker.getTodayTime(), this.tracker.isActive);
                        // 显示成功状态
                        if (this.$.btnCloudSync) {
                            this.$.btnCloudSync.textContent = '✅';
                            setTimeout(() => {
                                if (this.$.btnCloudSync) this.$.btnCloudSync.textContent = '☁️';
                            }, 1000);
                        }
                    } catch (e) {
                        // v3.5.2.9: 使用统一的错误格式化
                        this.renderer.showToast(ErrorFormatter.withIcon(e));
                        // 显示失败状态
                        if (this.$.btnCloudSync) {
                            this.$.btnCloudSync.textContent = '❌';
                            setTimeout(() => {
                                if (this.$.btnCloudSync) this.$.btnCloudSync.textContent = '☁️';
                            }, 10000);
                        }
                    }
                });

                // 工单按钮
                this.$.ticketBtn?.addEventListener('click', e => {
                    e.stopPropagation();
                    if (!this.hasLeaderboard || !this.oauth?.isLoggedIn()) {
                        this.renderer.showToast('⚠️ 请先登录后使用工单功能');
                        return;
                    }
                    const mgr = this._ensureTicketManager();
                    mgr?.show();
                });

                // 吃瓜助手按钮
                this.$.melonBtn?.addEventListener('click', e => {
                    e.stopPropagation();
                    this._ensureMelonHelper()?.show();
                });

                // 导出帖子按钮
                this.$.exportBtn?.addEventListener('click', e => {
                    e.stopPropagation();
                    this._ensureTopicExporter()?.show();
                });

                // LDC 积分按钮(仅 linux.do)
                this.$.ldcBtn?.addEventListener('click', e => {
                    e.stopPropagation();
                    this._ensureLdcManager()?.show();
                });

                // 士多按钮(仅 linux.do)
                this.$.shopBtn?.addEventListener('click', e => {
                    e.stopPropagation();
                    this._ensureLdcManager()?.showShop();
                });

                // CDK 按钮(仅 linux.do)
                this.$.cdkBtn?.addEventListener('click', e => {
                    e.stopPropagation();
                    this._ensureCdkManager()?.show();
                });
                
                // 按钮区域展开/收起
                this.$.actionsToggle?.addEventListener('click', e => {
                    e.stopPropagation();
                    const area = this.$.actionsArea;
                    const toggle = this.$.actionsToggle;
                    if (!area || !toggle || !toggle.classList.contains('show')) return;
                    
                    const willExpand = area.classList.contains('collapsed');
                    this._actionsUserExpanded = willExpand;
                    this._applyActionsToggleState(true, willExpand);
                });
                
                // 检查按钮区域是否需要展开/收起功能(延迟执行以确保DOM已渲染)
                this._scheduleActionsOverflowCheck();
                
                // 关注/粉丝分别点击
                this.el.querySelectorAll('.ldsp-follow-part').forEach(part => {
                    part.addEventListener('click', e => {
                        e.stopPropagation();
                        const username = this.storage.getUser();
                        if (!username) {
                            this.renderer.showToast('⚠️ 请先登录论坛');
                            return;
                        }
                        const fm = this._ensureFollowManager();
                        if (fm) {
                            // 根据点击的区域打开对应列表
                            const tabName = part.dataset.tab || 'following';
                            const tabs = fm.overlay.querySelectorAll('.ldsp-follow-tab');
                            tabs.forEach(t => t.classList.remove('active'));
                            const targetTab = fm.overlay.querySelector(`.ldsp-follow-tab[data-tab="${tabName}"]`);
                            targetTab?.classList.add('active');
                            fm.show();
                        }
                    });
                });
                
                // 兼容旧样式的关注/粉丝统计点击
                this.el.querySelectorAll('.ldsp-follow-stat').forEach(stat => {
                    stat.addEventListener('click', e => {
                        e.stopPropagation();
                        const username = this.storage.getUser();
                        if (!username) {
                            this.renderer.showToast('⚠️ 请先登录论坛');
                            return;
                        }
                        const fm = this._ensureFollowManager();
                        if (fm) {
                            // 设置激活的tab
                            const isFollowing = stat.classList.contains('ldsp-follow-stat-following');
                            const tabs = fm.overlay.querySelectorAll('.ldsp-follow-tab');
                            tabs.forEach(t => t.classList.remove('active'));
                            const targetTab = fm.overlay.querySelector(`.ldsp-follow-tab[data-tab="${isFollowing ? 'following' : 'followers'}"]`);
                            targetTab?.classList.add('active');
                            fm.show();
                        }
                    });
                });
                
                // 阅读卡片点击彩蛋 - 跳转到官网(动态URL)
                this.$.reading?.addEventListener('click', async e => {
                    e.stopPropagation();
                    const url = await this.cloudSync?.getWebsiteUrl() || 'https://ldspro.qzz.io/';
                    window.open(url, '_blank');
                });

                // 标签页切换
                this.$.tabs.forEach((tab, i) => {
                    tab.addEventListener('click', () => {
                        this.$.tabs.forEach(t => { t.classList.remove('active'); t.setAttribute('aria-selected', 'false'); });
                        this.$.sections.forEach(s => s.classList.remove('active'));
                        tab.classList.add('active');
                        tab.setAttribute('aria-selected', 'true');
                        this.el.querySelector(`#ldsp-${tab.dataset.tab}`).classList.add('active');

                        if (tab.dataset.tab === 'reqs') {
                            this.animRing = true;
                            this.cachedReqs.length && this.renderer.renderReqs(this.cachedReqs);
                        } else if (tab.dataset.tab === 'leaderboard') {
                            this._renderLeaderboard();
                        } else if (tab.dataset.tab === 'activity') {
                            this._renderActivity();
                        }
                    });

                    tab.addEventListener('keydown', e => {
                        if (['ArrowRight', 'ArrowLeft'].includes(e.key)) {
                            e.preventDefault();
                            const next = e.key === 'ArrowRight' ? (i + 1) % this.$.tabs.length : (i - 1 + this.$.tabs.length) % this.$.tabs.length;
                            this.$.tabs[next].click();
                            this.$.tabs[next].focus();
                        }
                    });
                });
                
                // 监听 Token 过期事件,刷新 UI
                window.addEventListener('ldsp_token_expired', () => {
                    this.renderer.showToast('⚠️ 登录已过期,请重新登录');
                    this._renderLeaderboard();
                });
                
                // 滚动条自动隐藏:滚动时显示,停止后隐藏
                this._initScrollbarAutoHide();
            }
            
            _initScrollbarAutoHide() {
                // 使用闭包存储状态,避免污染实例属性
                const timers = new WeakMap();
                const showScrollbar = (el) => {
                    if (this._programmaticScroll) return; // 忽略程序性滚动
                    el.classList.add('scrolling');
                    clearTimeout(timers.get(el));
                    timers.set(el, setTimeout(() => el.classList.remove('scrolling'), 800));
                };
                // 事件委托:捕获所有可滚动元素的滚动事件
                this.el.addEventListener('scroll', (e) => {
                    const t = e.target;
                    if (t.classList.contains('ldsp-content') || 
                        t.classList.contains('ldsp-subtabs') || 
                        t.classList.contains('ldsp-year-heatmap')) {
                        showScrollbar(t);
                    }
                }, { capture: true, passive: true });
            }
            
            // 程序性滚动(不显示滚动条)
            _scrollTo(el, top) {
                this._programmaticScroll = true;
                el.scrollTop = top;
                requestAnimationFrame(() => { this._programmaticScroll = false; });
            }
            
            // 检查按钮区域是否需要展开/收起功能
            _scheduleActionsOverflowCheck() {
                if (this._actionsCheckRaf) return;
                this._actionsCheckRaf = requestAnimationFrame(() => {
                    this._actionsCheckRaf = null;
                    this._checkActionsOverflow();
                });
            }
            
            _applyActionsToggleState(showToggle, expanded) {
                const area = this.$.actionsArea;
                const toggle = this.$.actionsToggle;
                if (!area || !toggle) return;
                
                toggle.classList.toggle('show', !!showToggle);
                toggle.classList.toggle('expanded', !!expanded);
                area.classList.toggle('collapsed', !!showToggle && !expanded);
                if (showToggle && !expanded && this._actionsRowHeight) {
                    area.style.maxHeight = `${this._actionsRowHeight}px`;
                } else {
                    area.style.maxHeight = '';
                }
                
                const textEl = toggle.querySelector('.ldsp-toggle-text');
                if (textEl) textEl.textContent = expanded ? '收起' : '展开更多';
                toggle.setAttribute('aria-expanded', expanded ? 'true' : 'false');
                toggle.setAttribute('aria-hidden', showToggle ? 'false' : 'true');
            }
            
            _checkActionsOverflow() {
                const area = this.$.actionsArea;
                const toggle = this.$.actionsToggle;
                if (!area || !toggle) return;
                
                const buttons = Array.from(area.querySelectorAll('.ldsp-action-btn'))
                    .filter(btn => btn.offsetParent !== null);
                
                if (buttons.length === 0) {
                    this._actionsUserExpanded = false;
                    this._applyActionsToggleState(false, false);
                    return;
                }
                
                // 临时移除折叠状态来计算实际行数
                const wasCollapsed = area.classList.contains('collapsed');
                if (wasCollapsed) area.classList.remove('collapsed');
                void area.offsetHeight;
                
                const firstBtn = buttons[0];
                const rowHeight = Math.ceil(firstBtn?.offsetHeight || 0);
                if (rowHeight) this._actionsRowHeight = rowHeight;
                
                const rowTops = new Set();
                for (const btn of buttons) {
                    rowTops.add(Math.round(btn.offsetTop));
                }
                const rows = Math.max(1, rowTops.size);
                
                const showToggle = rows > 2;
                if (!showToggle) {
                    this._actionsUserExpanded = false;
                }
                const expanded = showToggle ? this._actionsUserExpanded : true;
                this._applyActionsToggleState(showToggle, expanded);
            }

            _restore() {
                const isCollapsed = this.storage.getGlobal('collapsed', false);
                if (isCollapsed) {
                    this.el.classList.add('collapsed');
                }

                const theme = this.storage.getGlobal('theme', 'light');
                if (theme === 'light') this.el.classList.add('light');
                this.$.btnTheme.textContent = theme === 'dark' ? '🌓' : '☀️';

                requestAnimationFrame(() => {
                    // _restorePosition 会设置位置和 expand-left/expand-right 类
                    this._restorePosition();
                    // 更新箭头方向
                    this._updateArrow();
                });
            }

            _updateExpandDir() {
                const rect = this.el.getBoundingClientRect();
                const center = rect.left + rect.width / 2;
                const alignRight = center > innerWidth / 2;
                this.el.classList.toggle('expand-left', alignRight);
                this.el.classList.toggle('expand-right', !alignRight);
            }
            
            _updateArrow() {
                const isCollapsed = this.el.classList.contains('collapsed');
                const alignRight = this.el.classList.contains('expand-left');
                const arrow = this.$.btnToggle.querySelector('.ldsp-toggle-arrow');
                if (arrow) {
                    // 折叠时箭头指向展开方向,展开时箭头指向折叠方向
                    if (isCollapsed) {
                        arrow.textContent = alignRight ? '◀' : '▶';
                    } else {
                        arrow.textContent = alignRight ? '▶' : '◀';
                    }
                }
            }

            _getPanelSizeBounds(cfg, vw, vh) {
                const margin = 8;
                const minW = cfg.isVerySmall ? 200 : 220;
                const minH = cfg.isVerySmall ? 240 : 300;
                const maxW = Math.min(420, Math.max(minW, vw - margin * 2));
                const maxH = Math.max(minH, vh - margin * 2);
                return { minW, maxW, minH, maxH, margin };
            }
            
            _getPanelSizeConfig() {
                const cfg = Screen.getConfig();
                const { innerWidth: vw, innerHeight: vh } = window;
                const custom = this.storage.getGlobal('customSize', null);
                const bounds = this._getPanelSizeBounds(cfg, vw, vh);
                
                let width = cfg.width;
                let maxHeight = cfg.maxHeight;
                
                if (custom && typeof custom === 'object') {
                    const wRatio = Utils.toSafeNumber(custom.widthRatio, 0);
                    const hRatio = Utils.toSafeNumber(custom.heightRatio, 0);
                    const w = Utils.toSafeNumber(custom.width, 0);
                    const h = Utils.toSafeNumber(custom.height, 0);
                    
                    if (wRatio > 0) width = vw * wRatio;
                    else if (w > 0) width = w;
                    
                    if (hRatio > 0) maxHeight = vh * hRatio;
                    else if (h > 0) maxHeight = h;
                }
                
                width = Math.round(Math.min(bounds.maxW, Math.max(bounds.minW, width)));
                maxHeight = Math.round(Math.min(bounds.maxH, Math.max(bounds.minH, maxHeight)));
                
                return { ...cfg, width, maxHeight, bounds, usingCustom: !!custom };
            }
            
            _applyPanelSize(_reason = '') {
                const cfg = this._getPanelSizeConfig();
                const el = this.el;
                if (!el) return;
                
                const key = `${cfg.width}|${cfg.maxHeight}|${cfg.fontSize}|${cfg.padding}|${cfg.avatarSize}|${cfg.ringSize}`;
                if (this._lastSizeKey === key) return;
                this._lastSizeKey = key;
                
                el.style.setProperty('--w', `${cfg.width}px`);
                el.style.setProperty('--h', `${cfg.maxHeight}px`);
                el.style.setProperty('--fs', `${cfg.fontSize}px`);
                el.style.setProperty('--pd', `${cfg.padding}px`);
                el.style.setProperty('--av', `${cfg.avatarSize}px`);
                el.style.setProperty('--ring', `${cfg.ringSize}px`);
                
                // 即使处于折叠状态,也更新尺寸以便展开时生效
                el.style.width = `${cfg.width}px`;
                el.style.maxHeight = `${cfg.maxHeight}px`;
            }
            
            _onResize() {
                // 先应用尺寸(含自定义尺寸/相对变化)
                this._applyPanelSize('resize');
                
                // 窗口变化时恢复位置(会同时更新 expand-left/expand-right 类)
                this._restorePosition();
                
                // 重新检测按钮区域溢出(延迟执行以确保布局已更新)
                this._scheduleActionsOverflowCheck();
            }
            
            /**
             * 保存位置(拖拽后调用)
             * 
             * 存储格式:{ topRatio, anchorX, alignRight }
             * 直接从 inline style 读取位置,避免精度累积误差
             */
            _savePosition() {
                const el = this.el;
                const rect = el.getBoundingClientRect();
                const { innerWidth: vw, innerHeight: vh } = window;
                
                const centerX = rect.left + rect.width / 2;
                const alignRight = centerX > vw / 2;
                
                // 直接从 inline style 读取锚点位置,取整避免精度问题
                let anchorX;
                if (alignRight) {
                    const styleRight = parseFloat(el.style.right);
                    anchorX = !isNaN(styleRight) ? Math.round(styleRight) : Math.round(vw - rect.right);
                } else {
                    const styleLeft = parseFloat(el.style.left);
                    anchorX = !isNaN(styleLeft) ? Math.round(styleLeft) : Math.round(rect.left);
                }
                
                this.storage.setGlobalNow('position', { 
                    topRatio: rect.top / vh,
                    anchorX,
                    alignRight 
                });
            }
            
            /**
             * 恢复位置
             * 根据 alignRight 使用不同的定位模式,锚定边缘位置不变
             */
            _restorePosition() {
                const el = this.el;
                const pos = this.storage.getGlobal('position');
                const { innerWidth: vw, innerHeight: vh } = window;
                const margin = 8;
                const isCollapsed = el.classList.contains('collapsed');
                const cfg = Screen.getConfig();
                
                // 折叠状态尺寸常量
                const COLLAPSED_SIZE = 48;
                const panelWidth = isCollapsed ? COLLAPSED_SIZE : (parseInt(el.style.width) || cfg.width);
                const panelHeight = isCollapsed ? COLLAPSED_SIZE : (parseInt(el.style.maxHeight) || cfg.maxHeight);
                
                let alignRight = true;
                let anchorX = 20;
                let top;
                
                if (pos && typeof pos.alignRight === 'boolean') {
                    alignRight = pos.alignRight;
                    anchorX = pos.anchorX ?? 20;
                    top = pos.topRatio !== undefined ? pos.topRatio * vh : pos.top;
                } else if (pos && (pos.left !== undefined || pos.leftDist !== undefined)) {
                    // 兼容旧格式
                    const leftDist = parseFloat(pos.left || pos.leftDist);
                    top = parseFloat(pos.top || 0);
                    alignRight = leftDist + panelWidth / 2 > vw / 2;
                    anchorX = alignRight ? (vw - leftDist - panelWidth) : leftDist;
                } else if (pos && pos.rightDist !== undefined) {
                    // 兼容上一版本格式
                    alignRight = pos.alignRight;
                    anchorX = alignRight ? pos.rightDist : pos.leftDist;
                    top = pos.topRatio !== undefined ? pos.topRatio * vh : 0;
                } else {
                    // 默认位置
                    top = vh - panelHeight - 20;
                }
                
                // 确保在视口内
                top = Math.max(margin, Math.min(top, vh - panelHeight - margin));
                anchorX = Math.max(margin, Math.min(anchorX, vw - panelWidth - margin));
                
                // 取整避免精度问题
                anchorX = Math.round(anchorX);
                top = Math.round(top);
                
                if (alignRight) {
                    // 靠右:使用 right 定位
                    el.style.right = `${anchorX}px`;
                    el.style.left = 'auto';
                } else {
                    // 靠左:使用 left 定位
                    el.style.left = `${anchorX}px`;
                    el.style.right = 'auto';
                }
                el.style.top = `${top}px`;
                
                // 更新展开方向类
                el.classList.toggle('expand-left', alignRight);
                el.classList.toggle('expand-right', !alignRight);
            }

            // 初始化面板手动调整大小功能(仅桌面端)
            _initResize() {
                // 检测是否为桌面端(有鼠标悬停能力且是精确指针)
                if (!window.matchMedia('(hover:hover) and (pointer:fine)').matches) return;
                
                const el = this.el;
                const handles = el.querySelectorAll('.ldsp-resize-handle');
                if (!handles.length) return;
                
                let startX, startY, startW, startH, startLeft, startTop, direction, alignRight;
                
                const onMouseMove = (e) => {
                    if (!direction) return;
                    e.preventDefault();
                    
                    const dx = e.clientX - startX;
                    const dy = e.clientY - startY;
                    const { innerWidth: vw, innerHeight: vh } = window;
                    const cfg = Screen.getConfig();
                    const bounds = this._getPanelSizeBounds(cfg, vw, vh);
                    
                    let maxW = Math.min(bounds.maxW, vw - bounds.margin - startLeft);
                    let maxH = Math.min(bounds.maxH, vh - bounds.margin - startTop);
                    maxW = Math.max(bounds.minW, maxW);
                    maxH = Math.max(bounds.minH, maxH);
                    
                    // 根据方向调整尺寸
                    if (direction.includes('e')) {
                        const newW = Math.max(bounds.minW, Math.min(maxW, startW + dx));
                        el.style.width = `${newW}px`;
                        el.style.setProperty('--w', `${newW}px`);
                    }
                    if (direction.includes('s')) {
                        const newH = Math.max(bounds.minH, Math.min(maxH, startH + dy));
                        el.style.maxHeight = `${newH}px`;
                        el.style.setProperty('--h', `${newH}px`);
                    }
                };
                
                const onMouseUp = () => {
                    if (!direction) return;
                    direction = null;
                    el.classList.remove('resizing');
                    document.removeEventListener('mousemove', onMouseMove);
                    document.removeEventListener('mouseup', onMouseUp);
                    
                    const rect = el.getBoundingClientRect();
                    const { innerWidth: vw, innerHeight: vh } = window;
                    const width = Math.round(rect.width);
                    const height = Math.round(rect.height);
                    const widthRatio = vw > 0 ? Math.round((width / vw) * 10000) / 10000 : 0;
                    const heightRatio = vh > 0 ? Math.round((height / vh) * 10000) / 10000 : 0;
                    
                    // 保存用户自定义的尺寸(像素 + 相对比例)
                    this.storage.setGlobalNow('customSize', {
                        width,
                        height,
                        widthRatio,
                        heightRatio,
                        updatedAt: Date.now()
                    });
                    
                    // 恢复原有对齐方式(保持当前右/左边距)
                    if (alignRight) {
                        const right = Math.max(8, Math.round(vw - rect.right));
                        el.style.right = `${right}px`;
                        el.style.left = 'auto';
                    } else {
                        const left = Math.max(8, Math.round(rect.left));
                        el.style.left = `${left}px`;
                        el.style.right = 'auto';
                    }
                    
                    // 保存位置(保持 alignRight 不变)
                    this._savePositionKeepAlign(!!alignRight);
                    this._lastSizeKey = null;
                };
                
                handles.forEach(handle => {
                    handle.addEventListener('mousedown', (e) => {
                        if (el.classList.contains('collapsed')) return;
                        e.preventDefault();
                        e.stopPropagation();
                        
                        // 判断调整方向
                        if (handle.classList.contains('ldsp-resize-e')) direction = 'e';
                        else if (handle.classList.contains('ldsp-resize-s')) direction = 's';
                        else if (handle.classList.contains('ldsp-resize-se')) direction = 'se';
                        
                        const rect = el.getBoundingClientRect();
                        startX = e.clientX;
                        startY = e.clientY;
                        startW = rect.width;
                        startH = rect.height;
                        startLeft = rect.left;
                        startTop = rect.top;
                        
                        // 记录当前对齐方式,避免 resize 期间锚点跳动
                        const savedPos = this.storage.getGlobal('position');
                        alignRight = savedPos?.alignRight ?? el.classList.contains('expand-left');
                        
                        // resize 期间统一使用 left 定位,保证拖拽方向与鼠标一致
                        el.style.left = `${Math.round(startLeft)}px`;
                        el.style.right = 'auto';
                        
                        el.classList.add('resizing');
                        document.addEventListener('mousemove', onMouseMove);
                        document.addEventListener('mouseup', onMouseUp);
                    });
                });
            }

            /**
             * 面板展开/折叠切换
             * 
             * 设计原则:
             * - 左侧面板(靠左对齐):折叠/展开时保持左边缘位置不变
             * - 右侧面板(靠右对齐):折叠/展开时保持右边缘位置不变
             * 
             * 实现方式:
             * - 读取已保存的 alignRight 来确定对齐方向(避免因尺寸变化导致判断不一致)
             * - 根据当前定位模式读取正确的锚点位置
             * - left 定位的面板取 left 值,right 定位的面板取 right 值
             */
            _toggle() {
                if (this._toggleAnimating) return;
                this._toggleAnimating = true;
                
                const el = this.el;
                const isCurrentlyCollapsed = el.classList.contains('collapsed');
                const willCollapse = !isCurrentlyCollapsed;
                const { innerWidth: vw } = window;
                
                // 从已保存的位置获取对齐方向,避免因面板尺寸变化导致判断不一致
                const savedPos = this.storage.getGlobal('position');
                const alignRight = savedPos?.alignRight ?? el.classList.contains('expand-left');
                
                // 1. 立即禁用过渡和清除 transform(避免 hover 缩放效果影响位置计算)
                el.classList.add('no-trans');
                el.classList.remove('anim');
                el.style.transform = 'none';
                
                // 强制应用样式
                void el.offsetWidth;
                
                // 2. 获取当前锚点位置(直接使用保存的值最可靠)
                let anchorX = savedPos?.anchorX;
                
                // 如果没有保存的值,从 style 或 rect 计算
                if (anchorX === undefined || anchorX === null) {
                    if (alignRight) {
                        const styleRight = parseFloat(el.style.right);
                        if (!isNaN(styleRight)) {
                            anchorX = styleRight;
                        } else {
                            const rect = el.getBoundingClientRect();
                            anchorX = vw - rect.right;
                        }
                    } else {
                        const styleLeft = parseFloat(el.style.left);
                        if (!isNaN(styleLeft)) {
                            anchorX = styleLeft;
                        } else {
                            const rect = el.getBoundingClientRect();
                            anchorX = rect.left;
                        }
                    }
                }
                
                // 3. 设置定位模式(取整避免精度问题)
                anchorX = Math.round(anchorX);
                if (alignRight) {
                    el.style.right = anchorX + 'px';
                    el.style.left = 'auto';
                } else {
                    el.style.left = anchorX + 'px';
                    el.style.right = 'auto';
                }
                
                // 清除内联 transform(让 CSS 类控制)
                el.style.transform = '';
                
                // 更新展开方向类
                el.classList.toggle('expand-left', alignRight);
                el.classList.toggle('expand-right', !alignRight);
                
                // 4. 强制浏览器应用定位模式
                void el.offsetWidth;
                
                // 5. 启用过渡动画
                el.classList.remove('no-trans');
                el.classList.add('anim');
                
                // 6. 切换折叠状态(使用 rAF 确保过渡生效)
                requestAnimationFrame(() => {
                    el.classList.toggle('collapsed');
                    
                    // 更新箭头方向
                    this._updateArrow();
                    
                    // 保存折叠状态
                    this.storage.setGlobalNow('collapsed', willCollapse);
                    
                    // 展开时的额外处理
                    if (!willCollapse) {
                        this.animRing = true;
                        this.cachedReqs.length && setTimeout(() => this.renderer.renderReqs(this.cachedReqs), 100);
                    }

                    // 7. 动画结束后清理状态
                    setTimeout(() => {
                        el.classList.remove('anim');
                        // 保存位置(保持当前的 alignRight,只更新 anchorX 和 top)
                        this._savePositionKeepAlign(alignRight);
                        this._toggleAnimating = false;
                    }, 420);
                });
            }
            
            /**
             * 保存位置,但保持指定的对齐方向
             * 完全从 inline style 读取位置值,避免 getBoundingClientRect 在动画期间的不稳定值
             */
            _savePositionKeepAlign(alignRight) {
                const el = this.el;
                const { innerWidth: vw, innerHeight: vh } = window;
                
                // 直接从 inline style 读取锚点位置
                let anchorX;
                if (alignRight) {
                    const styleRight = parseFloat(el.style.right);
                    // 如果 style.right 是 'auto' 或无效值,从 rect 计算
                    if (!isNaN(styleRight)) {
                        anchorX = Math.round(styleRight);
                    } else {
                        const rect = el.getBoundingClientRect();
                        anchorX = Math.round(vw - rect.right);
                    }
                } else {
                    const styleLeft = parseFloat(el.style.left);
                    // 如果 style.left 是 'auto' 或无效值,从 rect 计算
                    if (!isNaN(styleLeft)) {
                        anchorX = Math.round(styleLeft);
                    } else {
                        const rect = el.getBoundingClientRect();
                        anchorX = Math.round(rect.left);
                    }
                }
                
                // 确保 anchorX 在合理范围内(防止保存无效值)
                anchorX = Math.max(8, anchorX);
                
                // top 也从 style 读取,避免动画期间 getBoundingClientRect 的不稳定
                const styleTop = parseFloat(el.style.top);
                const topRatio = !isNaN(styleTop) ? styleTop / vh : undefined;
                
                // 如果有有效的 topRatio,则保存;否则保留之前的值
                if (topRatio !== undefined) {
                    this.storage.setGlobalNow('position', { 
                        topRatio,
                        anchorX,
                        alignRight 
                    });
                } else {
                    // 只更新 anchorX,保留之前的 topRatio
                    const oldPos = this.storage.getGlobal('position') || {};
                    this.storage.setGlobalNow('position', { 
                        ...oldPos,
                        anchorX,
                        alignRight 
                    });
                }
            }

            _switchTheme() {
                const light = this.el.classList.toggle('light');
                this.$.btnTheme.textContent = light ? '☀️' : '🌓';
                this.storage.setGlobalNow('theme', light ? 'light' : 'dark');
            }

            // 从缓存加载头像(优先动态头像)
            _loadAvatarFromCache() {
                // 1. 优先使用缓存的动态头像
                if (this.followManager?.animatedAvatar) {
                    this.renderer.renderAvatar(this.followManager.animatedAvatar);
                    return;
                }
                // 2. 其次使用缓存的普通头像
                if (this.avatar) {
                    this.renderer.renderAvatar(this.avatar);
                    return;
                }
                // 3. 都没有则尝试从页面获取
                const el = document.querySelector('.current-user img.avatar');
                if (el) {
                    this._updateAvatar(el.src);
                }
            }

            // 更新普通头像(从页面获取时调用)
            _updateAvatar(url) {
                if (!url) return;
                if (url.startsWith('/')) url = `https://${CURRENT_SITE.domain}${url}`;
                url = url.replace(PATTERNS.AVATAR_SIZE, '/128/');
                // 只有头像变化时才更新
                if (this.avatar === url) return;
                this.avatar = url;
                this.storage.set('userAvatar', url);
                // 只有在没有动态头像时才渲染普通头像
                if (!this.followManager?.animatedAvatar) {
                    this.renderer.renderAvatar(url);
                }
            }

            _startReadingUpdate() {
                if (this._readingTimer) return;
                this._readingTimer = setInterval(() => {
                    this.readingTime = this.tracker.getTodayTime();
                    this.renderer.renderReading(this.readingTime, this.tracker.isActive);
                }, CONFIG.INTERVALS.READING_UPDATE);
            }

            _setLoading(v) {
                this.loading = v;
                this.$.btnRefresh.disabled = v;
                this.$.btnRefresh.style.animation = v ? 'spin 1s linear infinite' : '';
            }

            async fetch() {
                if (this.loading) return;
                this._setLoading(true);
                this.$.reqs.innerHTML = `<div class="ldsp-loading"><div class="ldsp-spinner"></div><div>加载中...</div></div>`;

                try {
                    const url = CURRENT_SITE.apiUrl;
                    
                    // 使用 network.fetch(包含 GM_xmlhttpRequest 绕过跨域,以及 fallback)
                    const html = await this.network.fetch(url);
                    
                    if (!html) {
                        throw new Error('无法获取数据');
                    }
                    
                    await this._parse(html);
                } catch (e) {
                    // v3.5.2.9: 使用统一的错误格式化
                    this._showError(ErrorFormatter.format(e));
                    // 即使获取升级要求失败,也要确保阅读追踪器正常初始化
                    this._ensureTrackerInitialized();
                } finally {
                    this._setLoading(false);
                }
            }
            
            // 确保阅读追踪器已初始化(在网络错误等情况下)
            _ensureTrackerInitialized() {
                // 如果已经有定时器在运行,说明已初始化
                if (this._readingTimer) return;
                
                // 尝试从 OAuth 获取用户名
                let username = this.username;
                if (!username && this.oauth?.isLoggedIn()) {
                    const oauthUser = this.oauth.getUserInfo();
                    if (oauthUser?.username) {
                        username = oauthUser.username;
                        this.storage.setUser(username);
                        this.username = username;
                    }
                }
                
                // 尝试从本地存储获取用户名
                if (!username) {
                    username = this.storage.getUser();
                    if (username) this.username = username;
                }
                
                // 初始化阅读追踪器
                this.tracker.init(username || 'anonymous');
                this._startReadingUpdate();
                this.readingTime = this.tracker.getTodayTime();
                this.renderer.renderReading(this.readingTime, this.tracker.isActive);
                
                Logger.log(`阅读追踪器已降级初始化: ${username || 'anonymous'}`);
            }

            _showError(msg) {
                this.$.reqs.innerHTML = `<div class="ldsp-empty"><div class="ldsp-empty-icon">❌</div><div class="ldsp-empty-txt">${Utils.escapeHtml(msg)}</div></div>`;
            }

            // 更新信任等级到服务端和本地缓存
            async _updateTrustLevel(connectLevel) {
                // 同时检查 oauth 和 cloudSync.oauth 的登录状态
                if (!this.oauth?.isLoggedIn() || !this.cloudSync?.oauth?.isLoggedIn()) return;
                
                const userInfo = this.oauth.getUserInfo();
                // v3.4.7: 兼容 trust_level 和 trustLevel 两种命名格式
                const currentLevel = userInfo?.trust_level ?? userInfo?.trustLevel;
                
                // 只有当等级变化时才更新
                if (currentLevel === connectLevel) return;
                
                try {
                    // 更新服务端(使用统一的 oauth 实例)
                    const result = await this.oauth.api('/api/user/trust-level', {
                        method: 'POST',
                        body: { trust_level: connectLevel }
                    });
                    
                    if (result?.success) {
                        // 更新本地缓存
                        const updatedUserInfo = { ...userInfo, trust_level: connectLevel };
                        this.oauth.setUserInfo(updatedUserInfo);
                    }
                } catch (e) { /* 同步失败,忽略 */ }
            }

            // 当没有升级要求表格时显示备选内容
            // 优先级:1. 服务端同步的数据 2. summary API 数据
            async _showFallbackStats(username, level) {
                const $ = this.$;
                
                // 优先从 OAuth 获取用户信息(更可靠,尤其在移动端)
                let effectiveUsername = username;
                let numLevel = parseInt(level) || 0;
                
                if (this.oauth?.isLoggedIn()) {
                    const oauthUser = this.oauth.getUserInfo();
                    // 优先使用 OAuth 中的用户名
                    if (oauthUser?.username) {
                        effectiveUsername = oauthUser.username;
                    }
                    // v3.5.1: 使用传入的等级和 OAuth 缓存中较高的值
                    // 传入的等级来自 connect 页面解析,是最新的
                    // OAuth 缓存可能是登录时的旧等级
                    const oauthTrustLevel = oauthUser?.trust_level ?? oauthUser?.trustLevel;
                    if (typeof oauthTrustLevel === 'number') {
                        numLevel = Math.max(numLevel, oauthTrustLevel);
                        // 如果传入的等级更高,更新本地缓存
                        if (parseInt(level) > oauthTrustLevel) {
                            const updatedUser = { ...oauthUser, trust_level: parseInt(level) };
                            this.oauth.setUserInfo(updatedUser);
                        }
                    }
                }
                
                // 确保阅读追踪器已初始化(_showFallbackStats 可能在 tracker.init 之前被调用)
                if (effectiveUsername && effectiveUsername !== '未知') {
                    if (!this.username) {
                        this.storage.setUser(effectiveUsername);
                        this.username = effectiveUsername;
                    }
                    this.tracker.init(effectiveUsername);
                    this._startReadingUpdate();
                    this.readingTime = this.tracker.getTodayTime();
                    this.renderer.renderReading(this.readingTime, this.tracker.isActive);
                } else {
                    // 即使没有用户名,也初始化匿名模式追踪
                    this.tracker.init('anonymous');
                    this._startReadingUpdate();
                }
                
                // 显示用户信息
                if (effectiveUsername && effectiveUsername !== '未知') {
                    $.userDisplayName.textContent = effectiveUsername;
                    $.userHandle.textContent = '';
                    $.userHandle.style.display = 'none';
                }
                
                // === 方案1:优先从服务端获取已同步的升级要求数据 ===
                // 这些数据是桌面端解析 connect 页面后上传的,最完整准确
                if (this.oauth?.isLoggedIn() && this.cloudSync) {
                    this.$.reqs.innerHTML = `<div class="ldsp-loading"><div class="ldsp-spinner"></div><div>正在获取云端数据...</div></div>`;
                    try {
                        const cloudData = await this._fetchCloudRequirements();
                        if (cloudData && cloudData.length > 0) {
                            return this._renderCloudRequirements(cloudData, effectiveUsername, numLevel);
                        }
                    } catch (e) { /* 云端获取失败,继续尝试 summary */ }
                }
                
                // === 方案2:从 summary API 获取统计数据 ===
                if (effectiveUsername && effectiveUsername !== '未知') {
                    this.$.reqs.innerHTML = `<div class="ldsp-loading"><div class="ldsp-spinner"></div><div>正在获取统计数据...</div></div>`;
                    const summaryData = await this._fetchSummaryData(effectiveUsername);
                    if (summaryData && Object.keys(summaryData).length > 0) {
                        return this._renderSummaryData(summaryData, effectiveUsername, numLevel);
                    }
                }
                
                // 如果无法获取 summary 数据,显示简要信息
                this.$.reqs.innerHTML = `
                    <div class="ldsp-empty">
                        <div class="ldsp-empty-icon">📊</div>
                        <div class="ldsp-empty-txt">
                            <div style="margin-bottom:8px;">当前信任等级:<b style="color:#5a7de0;">${numLevel}</b></div>
                            <div style="font-size:12px;color:#6b7280;">暂无升级进度数据</div>
                            <div style="margin-top:10px;font-size:11px;color:#9ca3af;">
                                <a href="https://linux.do/t/topic/2460" target="_blank" style="color:#5a7de0;text-decoration:none;">📖 查看完整信任等级说明</a>
                            </div>
                        </div>
                    </div>`;
                
                // 初始化 todayData(用于今日趋势显示)
                const todayData = this._getTodayData();
                if (!todayData) {
                    this._setTodayData({}, true);
                }
                
                // 低信任等级用户也可以查看阅读时间趋势
                const history = this.historyMgr.getHistory();
                this.cachedHistory = history;
                this.cachedReqs = []; // 空的升级要求数组
                this._renderTrends(history, []);
            }

            /**
             * 从服务端获取最近同步的升级要求数据
             * v3.5.2.4 优化:添加 30 分钟缓存,减少重复请求
             * 云端数据主要用于历史对比,实时数据从 LinuxDo 获取
             * @param {boolean} forceRefresh - 是否强制刷新(忽略缓存)
             * @returns {Array|null} - 升级要求数组或 null
             */
            async _fetchCloudRequirements(forceRefresh = false) {
                // 前置登录检查,避免无效请求
                if (!this.cloudSync?.oauth?.isLoggedIn()) return null;
                
                const now = Date.now();
                const CACHE_TTL = 30 * 60 * 1000; // 30 分钟缓存(云端数据用于历史对比,不需要频繁刷新)
                
                // 检查缓存是否有效
                if (!forceRefresh && this._cloudReqsCache && this._cloudReqsCacheTime) {
                    if ((now - this._cloudReqsCacheTime) < CACHE_TTL) {
                        Logger.log('[CloudReqs] Using cached data, age:', Math.round((now - this._cloudReqsCacheTime) / 1000), 's');
                        return this._cloudReqsCache;
                    }
                }
                
                try {
                    // 获取最近一天的历史数据
                    const result = await this.cloudSync.oauth.api('/api/requirements/history?days=1');
                    if (!result?.success || !result.data?.history?.length) {
                        return null;
                    }
                    
                    // 取最新的一条记录
                    const latestRecord = result.data.history[result.data.history.length - 1];
                    if (!latestRecord?.data) return null;
                    
                    // 转换为升级要求数组格式
                    const reqs = [];
                    for (const [name, value] of Object.entries(latestRecord.data)) {
                        if (name === '阅读时间(分钟)') continue; // 跳过阅读时间
                        reqs.push({
                            name,
                            currentValue: value,
                            requiredValue: 0, // 从配置获取
                            isSuccess: false,
                            change: 0,
                            isReverse: /举报|禁言|封禁/.test(name)
                        });
                    }
                    
                    const reqsData = reqs.length > 0 ? reqs : null;
                    
                    // 更新缓存
                    this._cloudReqsCache = reqsData;
                    this._cloudReqsCacheTime = now;
                    
                    return reqsData;
                } catch (e) {
                    return null;
                }
            }

            /**
             * 渲染从云端获取的升级要求数据
             * @param {Array} cloudReqs - 云端数据数组
             * @param {string} username - 用户名
             * @param {number} level - 信任等级
             */
            _renderCloudRequirements(cloudReqs, username, level) {
                // 2-4级用户的升级/保持要求配置(基于 connect 页面的 14 项要求)
                const LEVEL_2_PLUS_REQUIREMENTS = {
                    '访问次数': 50,           // 50%
                    '回复的话题': 10,
                    '浏览的话题': 500,
                    '浏览的话题(所有时间)': 200,
                    '已读帖子': 20000,
                    '已读帖子(所有时间)': 500,
                    '被举报的帖子': 5,         // 最多5个(反向)
                    '发起举报的用户': 5,       // 最多5个(反向)
                    '点赞': 30,
                    '获赞': 20,
                    '获赞:单日最高数量': 7,
                    '获赞:点赞用户数量': 5,
                    '被禁言(过去 6 个月)': 0, // 必须为0(反向)
                    '被封禁(过去 6 个月)': 0  // 必须为0(反向)
                };
                
                // 为云端数据填充要求值
                const reqs = cloudReqs.map(req => {
                    const requiredValue = LEVEL_2_PLUS_REQUIREMENTS[req.name] ?? 0;
                    const isReverse = /举报|禁言|封禁/.test(req.name);
                    const isSuccess = isReverse 
                        ? req.currentValue <= requiredValue 
                        : req.currentValue >= requiredValue;
                    
                    return {
                        ...req,
                        requiredValue,
                        isSuccess,
                        isReverse
                    };
                });
                
                // 按照配置顺序排序
                const orderedNames = Object.keys(LEVEL_2_PLUS_REQUIREMENTS);
                const orderedReqs = reqs.sort((a, b) => {
                    const idxA = orderedNames.indexOf(a.name);
                    const idxB = orderedNames.indexOf(b.name);
                    if (idxA === -1 && idxB === -1) return 0;
                    if (idxA === -1) return 1;
                    if (idxB === -1) return -1;
                    return idxA - idxB;
                });
                
                // 检查是否达标
                const isOK = orderedReqs.every(r => r.isSuccess);
                
                // 保存历史数据
                const histData = {};
                orderedReqs.forEach(r => histData[r.name] = r.currentValue);
                const history = this.historyMgr.addHistory(histData, this.readingTime);
                
                // 保存今日数据
                const todayData = this._getTodayData();
                this._setTodayData(histData, !todayData);
                
                // 获取显示名称
                let displayName = null;
                if (this.hasLeaderboard && this.oauth?.isLoggedIn()) {
                    const oauthUser = this.oauth.getUserInfo();
                    if (oauthUser?.name && oauthUser.name !== oauthUser.username) {
                        displayName = oauthUser.name;
                    }
                }
                
                // 渲染
                this.renderer.renderUser(username, level.toString(), isOK, orderedReqs, displayName);
                this.renderer.renderReqs(orderedReqs, level);
                
                this.cachedHistory = history;
                this.cachedReqs = orderedReqs;
                
                this._renderTrends(history, orderedReqs);
                this._setLastVisit(histData);
                this.prevReqs = orderedReqs;
                
                return true;
            }
            
            /**
             * 从 summary.json API 获取用户统计数据 (使用 user_summary 字段)
             * @param {string} username - 用户名
             * @returns {Object|null} - 统计数据对象或null
             */
            async _fetchSummaryData(username) {
                try {
                    const baseUrl = `https://${CURRENT_SITE.domain}`;
                    const data = {};
                    
                    // 优先使用 summary.json API(Discourse 标准 API)的 user_summary 字段
                    const jsonUrl = `${baseUrl}/u/${encodeURIComponent(username)}/summary.json`;
                    
                    // 尝试多种方式获取数据,兼容不同的用户脚本管理器
                    let jsonText = null;
                    
                    // 方法1: 使用 GM_xmlhttpRequest
                    try {
                        jsonText = await this.network.fetch(jsonUrl, { maxRetries: 2, timeout: 10000, headers: buildAuthHeaders(jsonUrl) });
                    } catch (e) { /* GM fetch 失败 */ }
                    
                    // 方法2: 如果 GM 方式失败,尝试原生 fetch(同源请求更可靠)
                    if (!jsonText) {
                        try {
                            const headers = buildAuthHeaders(jsonUrl, { 'Accept': 'application/json' });
                            const response = await fetch(jsonUrl, { 
                                credentials: 'include',
                                headers
                            });
                            if (response.ok) {
                                jsonText = await response.text();
                            }
                        } catch (e) { /* native fetch 失败 */ }
                    }
                    
                    if (jsonText) {
                        try {
                            const json = JSON.parse(jsonText);
                            
                            // 从 user_summary 字段提取统计数据
                            const stats = json?.user_summary;
                            if (stats) {
                                // 映射 Discourse API 字段到显示名称
                                if (stats.days_visited !== undefined) data['访问天数'] = stats.days_visited;
                                if (stats.topics_entered !== undefined) data['浏览话题'] = stats.topics_entered;
                                if (stats.posts_read_count !== undefined) data['已读帖子'] = stats.posts_read_count;
                                if (stats.likes_given !== undefined) data['送出赞'] = stats.likes_given;
                                if (stats.likes_received !== undefined) data['获赞'] = stats.likes_received;
                                if (stats.post_count !== undefined) data['回复'] = stats.post_count;
                                if (stats.topic_count !== undefined) data['创建话题'] = stats.topic_count;
                                // 额外有用的字段
                                if (stats.time_read !== undefined) data['阅读时间'] = Math.round(stats.time_read / 60); // 秒转分钟
                                
                                if (Object.keys(data).length > 0) {
                                    return data;
                                }
                            }
                        } catch (e) { /* JSON 解析失败 */ }
                    }
                    
                    // 方法B:回退到 HTML 解析
                    const url = `${baseUrl}/u/${encodeURIComponent(username)}/summary`;
                    let html = null;
                    
                    // 先尝试 GM_xmlhttpRequest
                    try {
                        html = await this.network.fetch(url, { maxRetries: 2, headers: buildAuthHeaders(url) });
                    } catch (e) { /* GM fetch 失败 */ }
                    
                    // 备用:原生 fetch
                    if (!html) {
                        try {
                            const headers = buildAuthHeaders(url);
                            const resp = await fetch(url, { credentials: 'include', headers });
                            if (resp.ok) {
                                html = await resp.text();
                            }
                        } catch (e) { /* native fetch 失败 */ }
                    }
                    
                    if (!html) {
                        return null;
                    }
                    
                    const doc = new DOMParser().parseFromString(html, 'text/html');
                    
                    // 辅助函数:解析数值(支持 k、m 等缩写和逗号分隔)
                    const parseValue = (text) => {
                        if (!text) return 0;
                        const cleaned = text.replace(/,/g, '').trim();
                        const match = cleaned.match(/([\d.]+)\s*([km万亿])?/i);
                        if (!match) return 0;
                        let value = parseFloat(match[1]);
                        const suffix = match[2]?.toLowerCase();
                        if (suffix === 'k' || suffix === '万') value *= 1000;
                        if (suffix === 'm' || suffix === '亿') value *= 1000000;
                        return Math.round(value);
                    };
                    
                    // 方法1:通过 class 名称查找统计项(Discourse 标准结构)
                    const statItems = doc.querySelectorAll('li[class*="stats-"], .stat-item, .user-stat');
                    statItems.forEach(item => {
                        const className = item.className || '';
                        const valueEl = item.querySelector('.value .number, .value, .stat-value');
                        if (!valueEl) return;
                        
                        // 优先从 title 获取完整数值
                        let value = 0;
                        const titleAttr = valueEl.getAttribute('title') || item.getAttribute('title');
                        if (titleAttr) {
                            value = parseValue(titleAttr);
                        } else {
                            value = parseValue(valueEl.textContent);
                        }
                        
                        // 根据 class 名称映射
                        if (className.includes('days-visited')) data['访问天数'] = value;
                        else if (className.includes('topics-entered')) data['浏览话题'] = value;
                        else if (className.includes('posts-read')) data['已读帖子'] = value;
                        else if (className.includes('likes-given')) data['送出赞'] = value;
                        else if (className.includes('likes-received')) data['获赞'] = value;
                        else if (className.includes('post-count')) data['回复'] = value;
                        else if (className.includes('topic-count')) data['创建话题'] = value;
                        else if (className.includes('solved-count')) data['解决方案'] = value;
                    });
                    
                    // 方法2:如果方法1没找到数据,尝试通过标签文本匹配
                    if (Object.keys(data).length === 0) {
                        // 查找所有可能包含统计数据的元素
                        const allStats = doc.querySelectorAll('.stats-section li, .top-section li, .user-summary-stat');
                        allStats.forEach(item => {
                            const text = item.textContent.trim();
                            const labelEl = item.querySelector('.label, .stat-label');
                            const valueEl = item.querySelector('.value, .number, .stat-value');
                            
                            if (!labelEl && !valueEl) return;
                            
                            const label = (labelEl?.textContent || '').toLowerCase().trim();
                            let value = 0;
                            
                            if (valueEl) {
                                const titleAttr = valueEl.getAttribute('title') || item.getAttribute('title');
                                value = parseValue(titleAttr || valueEl.textContent);
                            }
                            
                            // 根据标签文本匹配
                            if (label.includes('访问') || label.includes('visited') || text.includes('访问天数')) {
                                data['访问天数'] = value;
                            } else if (label.includes('浏览') && label.includes('话题') || label.includes('topics') || text.includes('浏览的话题')) {
                                data['浏览话题'] = value;
                            } else if (label.includes('已读') || label.includes('阅读') || label.includes('posts read') || text.includes('已读帖子')) {
                                data['已读帖子'] = value;
                            } else if (label.includes('送出') || label.includes('given') || text.includes('已送出')) {
                                data['送出赞'] = value;
                            } else if (label.includes('收到') || label.includes('received') || text.includes('已收到')) {
                                data['获赞'] = value;
                            } else if (label.includes('帖子') && !label.includes('已读') || label.includes('创建的帖子') || text.includes('创建的帖子')) {
                                data['回复'] = value;
                            } else if (label.includes('创建') && label.includes('话题') || text.includes('创建的话题')) {
                                data['创建话题'] = value;
                            }
                        });
                    }
                    
                    // 方法3:通用文本解析(作为最后手段)
                    if (Object.keys(data).length === 0) {
                        const statsText = doc.body?.textContent || '';
                        // 尝试匹配 "数字+标签" 的模式
                        const patterns = [
                            { regex: /([\d,.]+[km]?)\s*访问天数/i, key: '访问天数' },
                            { regex: /([\d,.]+[km]?)\s*浏览的?话题/i, key: '浏览话题' },
                            { regex: /([\d,.]+[km]?)\s*已读帖子/i, key: '已读帖子' },
                            { regex: /([\d,.]+[km]?)\s*已?送出/i, key: '送出赞' },
                            { regex: /([\d,.]+[km]?)\s*已?收到/i, key: '获赞' },
                            { regex: /([\d,.]+[km]?)\s*创建的帖子/i, key: '回复' }
                        ];
                        patterns.forEach(p => {
                            const match = statsText.match(p.regex);
                            if (match) data[p.key] = parseValue(match[1]);
                        });
                    }
                    
                    return Object.keys(data).length > 0 ? data : null;
                } catch (e) {
                    return null;
                }
            }
            
            /**
             * 渲染 summary 统计数据(低信任等级用户)
             * 使用与 2 级用户相同的 renderReqs 方法显示进度
             */
            _renderSummaryData(data, username, level) {
                // 构建要求数据结构(用于显示和趋势)
                const reqs = [];
                
                // 这是 connect 页面获取失败时的 fallback 方案
                // summary API 只能提供有限的累计统计数据(约8项)
                // 注意:summary API 返回的是累计总数,而 2→3 级升级要求是"过去100天"的数据
                // 因此这里的数据仅供参考,不能完全代表升级进度
                // 升级要求参考: https://linux.do/t/topic/2460
                let statsConfig;
                
                if (level === 0) {
                    // 0级升1级要求:
                    // - 进入5个话题、阅读30篇帖子、阅读10分钟
                    statsConfig = [
                        { key: '浏览话题', required: 5 },
                        { key: '已读帖子', required: 30 },
                        { key: '阅读时间', required: 10 }  // 10分钟
                    ];
                } else if (level === 1) {
                    // 1级升2级要求:
                    // - 访问15天、浏览20话题、阅读100帖子、阅读60分钟
                    // - 送出和收到各1个赞、回复3个不同话题
                    statsConfig = [
                        { key: '访问天数', required: 15 },
                        { key: '浏览话题', required: 20 },
                        { key: '已读帖子', required: 100 },
                        { key: '阅读时间', required: 60 },  // 60分钟
                        { key: '送出赞', required: 1 },
                        { key: '获赞', required: 1 },
                        { key: '回复', required: 3 }  // 3个不同话题
                    ];
                } else {
                    // 2级及以上用户:仅显示统计数据,不显示升级要求
                    // 重要:2级用户的升级进度应从 connect 页面获取(有详细的100天内数据)
                    // 这里只是 connect 页面完全无法获取时的兜底方案
                    // summary API 返回的是累计数据,无法反映真实的升级进度
                    statsConfig = [
                        { key: '访问天数', required: 0, isStats: true },
                        { key: '浏览话题', required: 0, isStats: true },
                        { key: '已读帖子', required: 0, isStats: true },
                        { key: '送出赞', required: 0, isStats: true },
                        { key: '获赞', required: 0, isStats: true },
                        { key: '回复', required: 0, isStats: true },
                        { key: '创建话题', required: 0, isStats: true },
                        { key: '阅读时间', required: 0, isStats: true }
                    ];
                }
                
                statsConfig.forEach(config => {
                    // 获取当前值(如果没有数据则默认为 0)
                    const currentValue = data[config.key] !== undefined ? data[config.key] : 0;
                    const requiredValue = config.required;
                    const isSuccess = currentValue >= requiredValue;
                    const prev = this.prevReqs.find(p => p.name === config.key);
                    
                    reqs.push({
                        name: config.key,
                        currentValue,
                        requiredValue,
                        isSuccess,
                        change: prev ? currentValue - prev.currentValue : 0,
                        isReverse: false
                    });
                });
                
                // 如果没有任何配置项,返回 false
                if (reqs.length === 0) return false;
                
                // 检查升级条件
                const requiredItems = reqs.filter(r => r.requiredValue > 0);
                const metItems = requiredItems.filter(r => r.isSuccess);
                const isOK = requiredItems.length > 0 && metItems.length === requiredItems.length;
                
                // 通知检查
                this.notifier.check(reqs);
                
                // 保存历史数据
                const histData = {};
                reqs.forEach(r => histData[r.name] = r.currentValue);
                const history = this.historyMgr.addHistory(histData, this.readingTime);
                
                // 保存今日数据
                const todayData = this._getTodayData();
                this._setTodayData(histData, !todayData);
                
                // 获取 OAuth 用户信息中的显示名称
                let displayName = null;
                if (this.hasLeaderboard && this.oauth?.isLoggedIn()) {
                    const oauthUser = this.oauth.getUserInfo();
                    if (oauthUser?.name && oauthUser.name !== oauthUser.username) {
                        displayName = oauthUser.name;
                    }
                }
                
                // 渲染用户信息和统计数据(与 2 级用户使用相同的 renderReqs 方法)
                this.renderer.renderUser(username, level.toString(), isOK, reqs, displayName);
                this.renderer.renderReqs(reqs, level);
                
                // 保存缓存
                this.cachedHistory = history;
                this.cachedReqs = reqs;
                this.prevReqs = reqs;
                
                // 0-1级用户也触发数据同步(阅读时间等)
                if (this.hasLeaderboard && this.cloudSync && this.oauth?.isLoggedIn()) {
                    // 同步阅读时间数据
                    this.cloudSync.upload().catch(() => {});
                }
                
                // 渲染趋势
                this._renderTrends(history, reqs);
                
                return true;
            }
            
            async _parse(html) {
                const doc = new DOMParser().parseFromString(html, 'text/html');
                
                // 尝试获取用户名(即使没有升级要求数据也可能有用户信息)
                const avatarEl = doc.querySelector('img[src*="avatar"]');
                
                // 尝试从页面提取用户名和信任等级
                let username = null;
                let level = '?';
                let connectLevel = null;  // 从 connect 页面获取的等级(最新)
                
                // 1. 优先从 h1 标签获取等级信息: "你好,昵称 (username) X级用户"
                const h1El = doc.querySelector('h1');
                if (h1El) {
                    const h1Text = h1El.textContent;
                    const h1Match = h1Text.match(PATTERNS.TRUST_LEVEL_H1);
                    if (h1Match) {
                        username = h1Match[1];  // 括号内的 username
                        connectLevel = parseInt(h1Match[2]) || 0;
                        level = connectLevel.toString();
                    }
                }
                
                // 2. 从头像 alt 获取用户名(备用)
                if (!username && avatarEl?.alt) {
                    username = avatarEl.alt;
                }
                
                // 3. 查找包含信任级别的区块获取更多信息
                const section = [...doc.querySelectorAll('.bg-white.p-6.rounded-lg')].find(d => d.querySelector('h2')?.textContent.includes('信任级别'));
                

                
                // 如果没找到 section,检查原因
                if (!section) {
                    // 检查是否返回了错误的页面(主站而非 connect)
                    const isMainSite = html?.includes('欢迎来到 LINUX DO') || doc.querySelector('title')?.textContent?.includes('LINUX DO -');
                    const isConnectPage = html?.includes('信任级别') || html?.includes('trust level');
                    
                    if (isMainSite && !isConnectPage) {
                        // 返回了主站页面,说明 connect 认证失败(常见于 iOS Safari + Stay)
                        // 使用 OAuth 缓存的用户信息
                        let oauthUsername = username;
                        let oauthLevel = level;
                        
                        if (this.oauth?.isLoggedIn()) {
                            const oauthUser = this.oauth.getUserInfo();
                            if (oauthUser?.username) oauthUsername = oauthUser.username;
                            const trustLevel = oauthUser?.trust_level ?? oauthUser?.trustLevel;
                            if (typeof trustLevel === 'number') oauthLevel = trustLevel.toString();
                        }
                        
                        // 直接使用 fallback 显示,不弹窗打扰用户
                        console.warn('[LDStatus Pro] Connect 页面认证失败,使用 summary 数据');
                        return await this._showFallbackStats(oauthUsername, oauthLevel);
                    }
                    
                    return await this._showFallbackStats(username, level);
                }
                
                if (section) {
                    const heading = section.querySelector('h2').textContent;
                    const match = heading.match(PATTERNS.TRUST_LEVEL);
                    if (match) {
                        if (!username) username = match[1];
                        if (connectLevel === null) {
                            connectLevel = parseInt(match[2]) || 0;
                            level = match[2];
                        }
                    }
                }
                
                // 无论是否有升级要求,只要能识别用户就初始化阅读追踪
                if (username && username !== '未知') {
                    this.storage.setUser(username);
                    this.username = username;
                    this.tracker.init(username);
                    this._startReadingUpdate();
                } else {
                    // 即使没有用户名,也尝试使用匿名模式初始化阅读追踪
                    this.tracker.init('anonymous');
                    this._startReadingUpdate();
                }

                if (avatarEl) this._updateAvatar(avatarEl.src);

                this.readingTime = this.tracker.getTodayTime();
                this.renderer.renderReading(this.readingTime, this.tracker.isActive);
                
                // 如果用户已登录,且从 connect 获取到了等级信息,更新本地缓存和服务端
                if (connectLevel !== null && this.oauth?.isLoggedIn()) {
                    this._updateTrustLevel(connectLevel);
                }
                
                // 如果没有找到升级要求区块,fallback 到 summary 数据
                if (!section) {
                    return await this._showFallbackStats(username, level);
                }

                const rows = section.querySelectorAll('table tr');
                const reqs = [];

                for (let i = 1; i < rows.length; i++) {
                    const cells = rows[i].querySelectorAll('td');
                    if (cells.length < 3) continue;

                    const name = cells[0].textContent.trim();
                    const curMatch = cells[1].textContent.match(PATTERNS.NUMBER);
                    const reqMatch = cells[2].textContent.match(PATTERNS.NUMBER);
                    const currentValue = curMatch ? +curMatch[1] : 0;
                    const requiredValue = reqMatch ? +reqMatch[1] : 0;
                    const isSuccess = cells[1].classList.contains('text-green-500');
                    const prev = this.prevReqs.find(p => p.name === name);

                    reqs.push({
                        name, currentValue, requiredValue, isSuccess,
                        change: prev ? currentValue - prev.currentValue : 0,
                        isReverse: PATTERNS.REVERSE.test(name)
                    });
                }

                const orderedReqs = Utils.reorderRequirements(reqs);
                const isOK = !section.querySelector('p.text-red-500');

                this.notifier.check(orderedReqs);

                const histData = {};
                orderedReqs.forEach(r => histData[r.name] = r.currentValue);
                const history = this.historyMgr.addHistory(histData, this.readingTime);

                // 触发升级要求数据上传(trust_level >= 2 时异步上传)
                if (this.hasLeaderboard && this.cloudSync && this.oauth?.isLoggedIn()) {
                    this.cloudSync.uploadRequirements().catch(() => {});
                }

                const todayData = this._getTodayData();
                this._setTodayData(histData, !todayData);

                // 如果已登录,优先使用 OAuth 用户信息中的 name
                let displayName = null;
                if (this.hasLeaderboard && this.oauth?.isLoggedIn()) {
                    const oauthUser = this.oauth.getUserInfo();
                    if (oauthUser?.name && oauthUser.name !== oauthUser.username) {
                        displayName = oauthUser.name;
                    }
                }
                this.renderer.renderUser(username, level, isOK, orderedReqs, displayName);
                this.renderer.renderReqs(orderedReqs, level);

                this.cachedHistory = history;
                this.cachedReqs = orderedReqs;

                this._renderTrends(history, orderedReqs);
                this._setLastVisit(histData);
                this.prevReqs = orderedReqs;
            }

            _getTodayData() {
                const stored = this.storage.get('todayData', null);
                return stored?.date === Utils.getTodayKey() ? stored : null;
            }

            _setTodayData(data, isStart = false) {
                const today = Utils.getTodayKey();
                const existing = this._getTodayData();
                const now = Date.now();

                this.storage.set('todayData', isStart || !existing
                    ? { date: today, startData: data, startTs: now, currentData: data, currentTs: now }
                    : { ...existing, currentData: data, currentTs: now }
                );
            }

            _setLastVisit(data) {
                this.storage.set('lastVisit', { ts: Date.now(), data });
            }

            _renderTrends(history, reqs) {
                this.renderer.renderTrends(this.trendTab);

                this.$.trends.querySelectorAll('.ldsp-subtab').forEach(tab => {
                    tab.addEventListener('click', () => {
                        this.trendTab = tab.dataset.tab;
                        this.storage.setGlobal('trendTab', this.trendTab);
                        this.$.trends.querySelectorAll('.ldsp-subtab').forEach(t => t.classList.remove('active'));
                        tab.classList.add('active');
                        this._renderTrendContent(history, reqs);
                    });
                });

                this._renderTrendContent(history, reqs);
            }

            _renderTrendContent(history, reqs) {
                const container = this.$.trends.querySelector('.ldsp-trend-content');

                if (this.trendTab === 'year') {
                    container.innerHTML = `<div class="ldsp-mini-loader"><div class="ldsp-mini-spin"></div><div class="ldsp-mini-txt">加载数据中...</div></div>`;
                    requestAnimationFrame(() => {
                        setTimeout(() => {
                            container.innerHTML = this.renderer.renderYearTrend(history, reqs, this.historyMgr, this.tracker);
                            // 自动滚动热力图到today位置(底部),使用程序性滚动避免显示滚动条
                            const heatmap = container.querySelector('.ldsp-year-heatmap');
                            if (heatmap) {
                                requestAnimationFrame(() => this._scrollTo(heatmap, heatmap.scrollHeight));
                            }
                        }, 50);
                    });
                    return;
                }

                const fns = {
                    // 使用 tracker.getTodayTime() 获取实时阅读时间,而不是缓存的 this.readingTime
                    today: () => this.renderer.renderTodayTrend(reqs, this.tracker.getTodayTime(), this._getTodayData()),
                    week: () => this.renderer.renderWeekTrend(history, reqs, this.historyMgr, this.tracker),
                    month: () => this.renderer.renderMonthTrend(history, reqs, this.historyMgr, this.tracker),
                    all: () => this.renderer.renderAllTrend(history, reqs, this.tracker)
                };

                container.innerHTML = fns[this.trendTab]?.() || '';
            }

            /**
             * 加载并显示系统公告(公开接口,不需要登录)
             */
            async _loadAnnouncement() {
                try {
                    const result = await new Promise((resolve, reject) => {
                        GM_xmlhttpRequest({
                            method: 'GET',
                            url: `${CONFIG.LEADERBOARD_API}/api/config/announcement`,
                            headers: { 'Content-Type': 'application/json' },
                            timeout: 10000,
                            onload: res => {
                                if (res.status >= 200 && res.status < 300) {
                                    try {
                                        resolve(JSON.parse(res.responseText));
                                    } catch (e) {
                                        reject(new Error('Parse error'));
                                    }
                                } else {
                                    reject(new Error(`HTTP ${res.status}`));
                                }
                            },
                            onerror: () => reject(new Error('Network error')),
                            ontimeout: () => reject(new Error('Timeout'))
                        });
                    });
                    
                    if (!result.success || !result.data) return;
                    
                    // 处理可能的双重嵌套: result.data.data 或 result.data
                    const announcement = result.data.data || result.data;
                    if (!announcement.enabled) return;
                    
                    // v3.3.3: 支持多条公告 - 兼容旧版单条公告格式
                    let items = [];
                    if (Array.isArray(announcement.items) && announcement.items.length > 0) {
                        items = announcement.items;
                    } else if (announcement.content) {
                        // 兼容旧版单条公告格式
                        items = [{
                            content: announcement.content,
                            type: announcement.type || 'info',
                            expiresAt: announcement.expiresAt || null
                        }];
                    }
                    
                    // 过滤已过期的公告
                    const now = Date.now();
                    items = items.filter(item => !item.expiresAt || item.expiresAt > now);
                    
                    if (items.length === 0) {
                        return;
                    }
                    
                    // 显示公告
                    this._showAnnouncements(items);
                } catch (e) {
                    console.warn('[Announcement] Load failed:', e.message);
                }
            }

            /**
             * 显示多条公告轮播
             * @param {Array} items - 公告数组 [{content, type, expiresAt}, ...]
             */
            _showAnnouncements(items) {
                if (!this.$.announcement || !this.$.announcementText) return;
                
                // 清除之前的轮播定时器
                if (this._announcementTimer) {
                    clearTimeout(this._announcementTimer);
                    this._announcementTimer = null;
                }
                
                this._announcementItems = items;
                this._announcementIndex = 0;
                
                // 显示第一条公告
                this._displayCurrentAnnouncement();
                
                // 显示公告栏
                requestAnimationFrame(() => {
                    this.$.announcement.classList.add('active');
                });
            }
            
            /**
             * 安排下一条公告的切换(使用动画结束事件)
             */
            _scheduleNextAnnouncement() {
                if (this._announcementItems.length <= 1) return;
                
                const inner = this.$.announcement.querySelector('.ldsp-announcement-inner');
                if (!inner) return;
                
                // 移除旧的监听器
                if (this._announcementEndHandler) {
                    inner.removeEventListener('animationend', this._announcementEndHandler);
                }
                
                // 添加新的动画结束监听器
                this._announcementEndHandler = () => {
                    this._announcementIndex = (this._announcementIndex + 1) % this._announcementItems.length;
                    this._displayCurrentAnnouncement();
                };
                inner.addEventListener('animationend', this._announcementEndHandler, { once: true });
            }
            
            /**
             * 显示当前索引的公告
             */
            _displayCurrentAnnouncement() {
                const item = this._announcementItems[this._announcementIndex];
                if (!item) return;
                
                // 设置公告类型样式
                this.$.announcement.className = 'ldsp-announcement active';
                if (item.type && item.type !== 'info') {
                    this.$.announcement.classList.add(item.type);
                }
                
                // 设置公告内容(带序号,如果多条)
                const prefix = this._announcementItems.length > 1 
                    ? `[${this._announcementIndex + 1}/${this._announcementItems.length}] ` 
                    : '';
                this.$.announcementText.textContent = prefix + item.content;
                
                // 重置动画并计算滚动参数
                const inner = this.$.announcement.querySelector('.ldsp-announcement-inner');
                if (inner) {
                    inner.style.animation = 'none';
                    inner.offsetHeight; // 触发重排
                    
                    // 计算文字实际宽度和容器宽度
                    const textWidth = this.$.announcementText.scrollWidth;
                    const containerWidth = this.$.announcement.clientWidth;
                    
                    // 计算滚动距离:从容器右侧开始,滚动到文字完全离开左侧
                    // 起点:100%(容器宽度)
                    // 终点:-(textWidth / containerWidth * 100)%
                    const endX = -Math.ceil((textWidth / containerWidth) * 100);
                    this.$.announcement.style.setProperty('--start-x', '100%');
                    this.$.announcement.style.setProperty('--end-x', `${endX}%`);
                    
                    // 根据总滚动距离设置速度(每秒约 50px)
                    const totalDistance = containerWidth + textWidth;
                    const duration = Math.max(8, Math.min(60, totalDistance / 50));
                    this.$.announcement.style.setProperty('--marquee-duration', `${duration}s`);
                    
                    // 单条公告无限循环,多条公告播放一次后切换
                    const iteration = this._announcementItems.length > 1 ? '1' : 'infinite';
                    this.$.announcement.style.setProperty('--marquee-iteration', iteration);
                    
                    inner.style.animation = '';
                }
                
                // 如果是多条公告,监听动画结束后切换
                if (this._announcementItems.length > 1) {
                    this._scheduleNextAnnouncement();
                }
            }

            async _checkUpdate(autoCheck = false) {
                const url = 'https://raw.githubusercontent.com/caigg188/LDStatusPro/main/LDStatusPro.user.js';
                this.$.btnUpdate.textContent = '⏳';

                try {
                    const text = await this.network.fetch(url, { maxRetries: 1 });
                    const match = text.match(PATTERNS.VERSION);
                    if (match) {
                        const remote = match[1];
                        const current = GM_info.script.version;
                        if (Utils.compareVersion(remote, current) > 0) {
                            this.$.btnUpdate.textContent = '🆕';
                            this.$.btnUpdate.title = `新版本 v${remote}`;
                            this.$.btnUpdate.classList.add('has-update');
                            this._remoteVersion = remote;
                            this._updateUrl = url;
                            
                            // 检查是否已经提示过这个版本
                            const dismissedVer = this.storage.getGlobal('dismissedUpdateVer', '');
                            const shouldShowBubble = autoCheck 
                                ? (dismissedVer !== remote)  // 自动检查:只有未忽略的版本才显示
                                : true;  // 手动检查:总是显示
                            
                            if (shouldShowBubble) {
                                this._showUpdateBubble(current, remote);
                            }
                            
                            this.$.btnUpdate.onclick = () => this._showUpdateBubble(current, remote);
                        } else {
                            this.$.btnUpdate.textContent = '✅';
                            this.$.btnUpdate.title = '已是最新版本';
                            this.$.btnUpdate.classList.remove('has-update');
                            if (!autoCheck) {
                                this.renderer.showToast('✅ 已是最新版本');
                            }
                            setTimeout(() => {
                                this.$.btnUpdate.textContent = '🔍';
                                this.$.btnUpdate.title = '检查更新';
                            }, 2000);
                        }
                    }
                } catch (e) {
                    this.$.btnUpdate.textContent = '❌';
                    this.$.btnUpdate.title = '检查失败';
                    if (!autoCheck) {
                        this.renderer.showToast('❌ 检查更新失败');
                    }
                    setTimeout(() => {
                        this.$.btnUpdate.textContent = '🔍';
                        this.$.btnUpdate.title = '检查更新';
                    }, 2000);
                }
            }

            _showUpdateBubble(current, remote) {
                this.$.updateBubbleVer.innerHTML = `<span style="color:var(--txt-mut)">v${current}</span> → <span style="color:var(--accent);font-weight:700">v${remote}</span>`;
                this.$.updateBubble.style.display = 'block';
                // 延迟一帧添加动画类,确保过渡效果生效
                requestAnimationFrame(() => {
                    this.$.updateBubble.classList.add('show');
                });
                
                // 绑定关闭按钮
                this.$.updateBubbleClose.onclick = () => this._hideUpdateBubble(true);
                
                // 绑定更新按钮
                this.$.updateBubbleBtn.onclick = () => this._doUpdate();
            }

            _hideUpdateBubble(dismiss = false) {
                // 如果用户主动关闭,记录已忽略的版本
                if (dismiss && this._remoteVersion) {
                    this.storage.setGlobalNow('dismissedUpdateVer', this._remoteVersion);
                }
                
                this.$.updateBubble.classList.remove('show');
                setTimeout(() => {
                    this.$.updateBubble.style.display = 'none';
                }, 300);
            }

            _doUpdate() {
                this.$.updateBubbleBtn.disabled = true;
                this.$.updateBubbleBtn.textContent = '⏳ 更新中...';
                
                // 打开更新链接,Tampermonkey 会自动弹出更新确认
                window.open(this._updateUrl || 'https://raw.githubusercontent.com/caigg188/LDStatusPro/main/LDStatusPro.user.js');
                
                // 提示用户
                setTimeout(() => {
                    this.$.updateBubbleBtn.textContent = '✅ 请在弹出窗口确认更新';
                    setTimeout(() => {
                        this._hideUpdateBubble();
                        this.$.updateBubbleBtn.disabled = false;
                        this.$.updateBubbleBtn.textContent = '🚀 立即更新';
                    }, 3000);
                }, 1000);
            }

            // ========== 登录相关 ==========

            _updateLoginUI() {
                // 无排行榜站点隐藏登录相关按钮
                if (!this.hasLeaderboard) {
                    if (this.$.btnCloudSync) this.$.btnCloudSync.style.display = 'none';
                    if (this.$.logoutBtn) this.$.logoutBtn.style.display = 'none';
                    if (this.$.ticketBtn) this.$.ticketBtn.style.display = 'none';
                    if (this.$.loginBtn) this.$.loginBtn.style.display = 'none';
                    return;
                }
                const logged = this.oauth.isLoggedIn();
                const forumLogged = !!this.storage.getUser();  // 论坛登录状态
                this.$.user.classList.toggle('not-logged', !logged);

                // 显示/隐藏云同步按钮
                if (this.$.btnCloudSync) {
                    this.$.btnCloudSync.style.display = logged ? '' : 'none';
                }

                // 显示/隐藏注销按钮和工单按钮(未登录时都隐藏)
                if (this.$.logoutBtn) {
                    this.$.logoutBtn.style.display = logged ? '' : 'none';
                }
                if (this.$.ticketBtn) {
                    this.$.ticketBtn.style.display = logged ? '' : 'none';
                }
                // 显示/隐藏登录按钮(已登录时隐藏)
                if (this.$.loginBtn) {
                    this.$.loginBtn.style.display = logged ? 'none' : '';
                }
                
                // 显示/隐藏关注粉丝和天数(论坛未登录时隐藏整个容器)
                const userMeta = this.el.querySelector('.ldsp-user-meta');
                if (userMeta) {
                    userMeta.style.display = forumLogged ? '' : 'none';
                }

                if (!logged) {
                    this._bindUserLogin();
                }
                
                // 登录状态变化后重新计算按钮区域折叠状态
                this._scheduleActionsOverflowCheck();
            }

            _bindLoginButton() {
                if (this._loginBtnBound || !this.$.loginBtn) return;
                this._loginBtnBound = true;
                this.$.loginBtn.addEventListener('click', async (e) => {
                    e.stopPropagation();
                    if (!this.oauth?.isLoggedIn()) {
                        await this._doLogin();
                    }
                });
            }

            _bindUserLogin() {
                if (this._userLoginBound) return;
                this._userLoginBound = true;

                const handle = async e => {
                    if (!this.oauth.isLoggedIn() && this.$.user.classList.contains('not-logged')) {
                        e.stopPropagation();
                        await this._doLogin();
                    }
                };

                this.$.user.querySelector('.ldsp-avatar-wrap')?.addEventListener('click', handle);
                this.$.userDisplayName.addEventListener('click', handle);
                
                // 绑定登录按钮
                this._bindLoginButton();
            }

            /**
             * 检查并处理待处理的 OAuth 登录结果
             * 统一同窗口登录模式:用户授权后会跳转回原页面,登录结果通过 URL hash 传递
             * 数据在脚本最开始就被捕获到 _pendingOAuthData 全局变量
             */
            _checkPendingOAuthLogin() {
                console.log('[OAuth] _checkPendingOAuthLogin called, _pendingOAuthData:', _pendingOAuthData ? 'present' : 'null');
                // 优先使用脚本启动时捕获的数据(避免 Discourse 路由处理掉 hash)
                let pendingResult = _pendingOAuthData;
                _pendingOAuthData = null; // 清除已使用的数据
                
                // 备用:再次尝试从 URL hash 读取
                if (!pendingResult) {
                    console.log('[OAuth] No early captured data, trying URL hash fallback...');
                    pendingResult = this.oauth._checkUrlHashLogin();
                }
                
                console.log('[OAuth] pendingResult:', pendingResult ? { success: pendingResult.success, hasToken: !!pendingResult.token, hasUser: !!pendingResult.user } : 'null');
                
                if (pendingResult?.success && pendingResult.token && pendingResult.user) {
                    console.log('[OAuth] ✅ Processing login result for user:', pendingResult.user?.username);
                    // 【关键】先同步保存登录信息,确保后续的 isLoggedIn() 检查能返回 true
                    this.oauth.setToken(pendingResult.token);
                    this.oauth.setUserInfo(pendingResult.user);
                    this.oauth.setJoined(pendingResult.isJoined || false);
                    // 处理登录结果(异步操作如同步、UI更新等)
                    this._handlePendingLoginResult(pendingResult);
                    return true; // 返回 true 表示有登录结果被处理
                } else {
                    console.log('[OAuth] No valid pending login result');
                    return false;
                }
            }

            // 处理待处理的登录结果(登录信息已在 _checkPendingOAuthLogin 中同步保存)
            async _handlePendingLoginResult(result) {
                try {
                    this.renderer.showToast('✅ 登录成功');
                    
                    // 同步用户名到 storage
                    if (result.user?.username) {
                        this.storage.setUser(result.user.username);
                        this.storage.invalidateCache();
                        this.storage.migrate(result.user.username);
                        this._updateUserInfoFromOAuth(result.user);
                    }
                    
                    this._updateLoginUI();
                    await this._syncPrefs();
                    
                    // 登录成功后重新获取 connect 页面数据(此时 OAuth 用户信息已设置,可以正确显示信任等级)
                    this.fetch();
                    
                    // 首次登录后的完整同步:阅读数据 + 要求数据
                    this.cloudSync.fullSync().then(() => {
                        // 阅读数据同步完成后,同步要求数据(信任等级进度)
                        return this.cloudSync.syncRequirementsOnLoad();
                    }).then(() => {
                        // 同步完成后清除缓存,确保下次获取最新数据
                        this._cloudReqsCache = null;
                        this._cloudReqsCacheTime = 0;
                    }).catch(e => console.warn('[CloudSync]', e));
                } catch (e) {
                    console.error('[OAuth] Handle pending login error:', e);
                }
            }

            async _doLogin() {
                try {
                    this.renderer.showToast('⏳ 正在跳转到授权页面...');
                    // 统一同窗口登录:login() 会跳转页面,不会返回
                    // 登录成功后页面会跳转回来,由 _checkPendingOAuthLogin 处理结果
                    await this.oauth.login();
                    // 如果 login() 返回了用户(从 localStorage 读取的待处理结果),处理它
                    // 注意:正常情况下不会执行到这里,因为页面会跳转
                } catch (e) {
                    this.renderer.showToast(`❌ ${e.message}`);
                }
            }

            // 使用 OAuth 用户信息更新界面
            _updateUserInfoFromOAuth(user) {
                if (!user) return;
                const $ = this.$;
                // 显示用户名和昵称
                if (user.name && user.name !== user.username) {
                    $.userDisplayName.textContent = user.name;
                    $.userHandle.textContent = `@${user.username}`;
                    $.userHandle.style.display = '';
                } else {
                    $.userDisplayName.textContent = user.username;
                    $.userHandle.textContent = '';
                    $.userHandle.style.display = 'none';
                }
                // 更新头像(如果有)
                if (user.avatar_url) {
                    this._updateAvatar(user.avatar_url.startsWith('http') ? user.avatar_url : `https://linux.do${user.avatar_url}`);
                }
            }

            _checkLoginPrompt() {
                const KEY = 'ldsp_login_prompt_version';
                const VER = '3.0';
                if (this.storage.getGlobal(KEY, null) === VER) {
                    this._updateLoginUI();
                    return;
                }

                const hasData = this.storage.get('readingTime', null);
                const isUpgrade = hasData && Object.keys(hasData.dailyData || {}).length > 0;

                setTimeout(() => {
                    const overlay = this.renderer.showLoginPrompt(isUpgrade);
                    this._bindLoginPrompt(overlay, KEY, VER);
                }, 1500);
            }

            _bindLoginPrompt(overlay, key, ver) {
                const close = (skipped = false) => {
                    overlay.classList.remove('show');
                    setTimeout(() => overlay.remove(), 300);
                    this.storage.setGlobalNow(key, ver);
                    skipped && this._updateLoginUI();
                };

                const loginBtn = overlay.querySelector('#ldsp-modal-login');
                loginBtn?.addEventListener('click', async () => {
                    loginBtn.disabled = true;
                    loginBtn.textContent = '⏳ 跳转中...';
                    try {
                        // 统一同窗口登录:会跳转到授权页面
                        // 登录成功后返回此页面,由 _checkPendingOAuthLogin 处理
                        await this.oauth.login();
                        // 正常情况下不会执行到这里,因为页面会跳转
                    } catch (e) {
                        this.renderer.showToast(`❌ ${e.message}`);
                        loginBtn.disabled = false;
                        loginBtn.textContent = '🚀 立即登录';
                    }
                });

                overlay.querySelector('#ldsp-modal-skip')?.addEventListener('click', () => close(true));
                overlay.addEventListener('click', e => e.target === overlay && close(true));
            }

            async _syncPrefs() {
                if (!this.hasLeaderboard || !this.oauth.isLoggedIn()) return;
                try {
                    const result = await this.oauth.api('/api/user/status');
                    if (result.success && result.data) {
                        const prevPaused = this.registrationPaused;
                        const prevJoinedBefore = this.hasJoinedBefore;
                        this.registrationPaused = !!result.data.registrationPaused;
                        this.hasJoinedBefore = !!result.data.joinedAt;
                        this.oauth.setJoined(result.data.isJoined || false);
                        if (this.oauth.isJoined()) {
                            this.leaderboard.startSync();
                        } else if (prevPaused !== this.registrationPaused || prevJoinedBefore !== this.hasJoinedBefore) {
                            await this._renderLeaderboardContent();
                        }
                    }
                } catch (e) {
                    console.warn('[Prefs]', e);
                }
            }

            // ========== 我的活动 ==========

            async _renderActivity() {
                if (!this.$.activity) return;

                // 检查论坛登录状态:未登录时显示登录提示
                const username = this.storage.getUser();
                if (!username) {
                    this.$.activity.innerHTML = `
                        <div class="ldsp-lb-login">
                            <div class="ldsp-lb-login-icon">🔐</div>
                            <div class="ldsp-lb-login-title">需要登录</div>
                            <div class="ldsp-lb-login-desc">请先登录论坛后再查看您的活动数据</div>
                        </div>`;
                    return;
                }

                this.renderer.renderActivity(this.activitySubTab);

                // 绑定子tab点击事件
                this.$.activity.querySelectorAll('.ldsp-subtab').forEach(tab => {
                    tab.addEventListener('click', () => {
                        const tabId = tab.dataset.activity;
                        this.activitySubTab = tabId;
                        this.$.activity.querySelectorAll('.ldsp-subtab').forEach(t => t.classList.remove('active'));
                        tab.classList.add('active');
                        // 清除缓存强制重新加载
                        this.activityMgr.clearCache(tabId);
                        this._renderActivityContent();
                    });
                });

                await this._renderActivityContent();
            }

            async _renderActivityContent() {
                const container = this.$.activity.querySelector('.ldsp-activity-content');
                if (!container) return;

                // 清理之前的滚动事件
                this._cleanupActivityScroll();

                container.innerHTML = this.renderer.renderActivityLoading();

                try {
                    switch (this.activitySubTab) {
                        case 'read':
                            await this._loadReadTopics(container);
                            break;
                        case 'bookmarks':
                            await this._loadBookmarks(container);
                            break;
                        case 'replies':
                            await this._loadReplies(container);
                            break;
                        case 'likes':
                            await this._loadLikes(container);
                            break;
                        case 'reactions':
                            await this._loadReactions(container);
                            break;
                        case 'topics':
                            await this._loadMyTopics(container);
                            break;
                        default:
                            container.innerHTML = this.renderer.renderActivityEmpty('📭', '请选择一个分类');
                    }
                } catch (e) {
                    container.innerHTML = this.renderer.renderActivityError(e.message || '加载失败');
                    container.querySelector('.ldsp-activity-retry')?.addEventListener('click', () => {
                        this.activityMgr.clearCache(this.activitySubTab);
                        this._renderActivityContent();
                    });
                }
            }

            async _loadReadTopics(container, searchOnly = false, targetBatchSize = null) {
                // 检查登录状态
                const username = this.storage.getUser();
                if (!username) {
                    container.innerHTML = this.renderer.renderActivityEmpty('🔒', '请先登录论坛');
                    return;
                }
                
                // 获取当前分页状态
                let state = this.activityMgr.getPageState('read');
                
                // 如果是首次加载,重置状态
                if (!state.allTopics || state.allTopics.length === 0) {
                    state = { page: 0, allTopics: [], hasMore: true, search: '', batchSize: 20 };
                }

                // 使用 targetBatchSize 或 state.batchSize
                const batchSize = targetBatchSize || state.batchSize || 20;
                if (targetBatchSize) {
                    state.batchSize = targetBatchSize;
                }

                // 如果只是搜索过滤,不加载新数据
                if (!searchOnly) {
                    try {
                        // 需要加载更多数据:计算还需要多少条
                        const needMore = batchSize - state.allTopics.length;
                        if (needMore > 0 && state.hasMore) {
                            const result = await this.activityMgr.getReadTopics(state.page, needMore);
                            
                            // 合并话题(避免重复)
                            const existingIds = new Set(state.allTopics.map(t => t.id));
                            const newTopics = result.topics.filter(t => !existingIds.has(t.id));
                            state.allTopics = [...state.allTopics, ...newTopics];
                            state.hasMore = result.hasMore;
                            state.page = result.page + 1;
                            
                            this.activityMgr.setPageState('read', state);
                        }
                    } catch (e) {
                        if (state.allTopics.length === 0) throw e;
                        this.renderer.showToast(`⚠️ ${e.message}`);
                    }
                }

                // 根据搜索词过滤
                const search = state.search || '';
                let filteredTopics = state.allTopics;
                if (search) {
                    const kw = search.toLowerCase();
                    // 兼容新旧tags格式(新格式是对象数组 {id,name,slug},旧格式是字符串数组)
                    const getTagName = tag => typeof tag === 'string' ? tag : (tag?.name || '');
                    filteredTopics = state.allTopics.filter(t => 
                        (t.title && t.title.toLowerCase().includes(kw)) ||
                        (t.tags && t.tags.some(tag => getTagName(tag).toLowerCase().includes(kw)))
                    );
                }

                container.innerHTML = this.renderer.renderTopicListWithSearch(filteredTopics, state.hasMore && !search, search, batchSize, state.allTopics.length);

                // 绑定搜索事件
                this._bindReadSearchEvents(container, state);

                // 绑定瀑布流滚动加载(搜索时禁用)
                if (state.hasMore && !search) {
                    this._bindActivityScroll(container);
                }
            }

            _bindReadSearchEvents(container, state) {
                const input = container.querySelector('.ldsp-activity-search input');
                const clear = container.querySelector('.ldsp-activity-search-clear');
                const batchSelect = container.querySelector('.ldsp-activity-batch-select');
                
                const doSearch = () => {
                    clearTimeout(this._readSearchTimer);
                    state.search = input.value.trim();
                    this.activityMgr.setPageState('read', state);
                    this._loadReadTopics(container, true);
                };
                
                input?.addEventListener('input', (e) => {
                    clear?.classList.toggle('show', !!e.target.value);
                    clearTimeout(this._readSearchTimer);
                    // 增加防抖时间到600ms,避免输入中断
                    this._readSearchTimer = setTimeout(doSearch, 600);
                });
                
                // 回车键立即搜索
                input?.addEventListener('keydown', (e) => {
                    if (e.key === 'Enter') {
                        e.preventDefault();
                        doSearch();
                    }
                });
                
                clear?.addEventListener('click', () => {
                    input.value = '';
                    clear.classList.remove('show');
                    state.search = '';
                    this.activityMgr.setPageState('read', state);
                    this._loadReadTopics(container, true);
                });

                batchSelect?.addEventListener('change', async (e) => {
                    const newBatchSize = parseInt(e.target.value, 10);
                    state.batchSize = newBatchSize;
                    this.activityMgr.setPageState('read', state);
                    
                    // 如果需要加载更多数据
                    if (newBatchSize > state.allTopics.length && state.hasMore) {
                        // 显示加载状态
                        const toolbar = container.querySelector('.ldsp-activity-toolbar');
                        const topicList = container.querySelector('.ldsp-topic-list');
                        toolbar?.classList.add('loading');
                        batchSelect.classList.add('loading');
                        
                        if (topicList) {
                            topicList.classList.add('loading');
                            // 添加加载提示覆盖层
                            const loadingOverlay = document.createElement('div');
                            loadingOverlay.className = 'ldsp-activity-loading-overlay';
                            loadingOverlay.innerHTML = `<div class="ldsp-activity-loading-spinner"></div><div class="ldsp-activity-loading-text">正在加载 ${newBatchSize} 条数据...</div>`;
                            topicList.style.position = 'relative';
                            topicList.appendChild(loadingOverlay);
                        }
                        
                        await this._loadReadTopics(container, false, newBatchSize);
                    } else {
                        // 数据足够,只需重新渲染
                        await this._loadReadTopics(container, true, newBatchSize);
                    }
                });
            }

            async _loadBookmarks(container, searchOnly = false, targetBatchSize = null) {
                // 获取当前用户名
                const username = this.storage.getUser();
                if (!username) {
                    container.innerHTML = this.renderer.renderActivityEmpty('🔒', '请先登录论坛');
                    return;
                }

                // 获取当前分页状态
                let state = this.activityMgr.getPageState('bookmarks');
                
                // 如果是首次加载,重置状态
                if (!state.allItems || state.allItems.length === 0) {
                    state = { page: 0, allItems: [], hasMore: true, search: '', batchSize: 20 };
                }

                // 使用 targetBatchSize 或 state.batchSize
                const batchSize = targetBatchSize || state.batchSize || 20;
                if (targetBatchSize) {
                    state.batchSize = targetBatchSize;
                }

                // 如果只是搜索过滤,不加载新数据
                if (!searchOnly) {
                    try {
                        // 需要加载更多数据:计算还需要多少条
                        const needMore = batchSize - state.allItems.length;
                        if (needMore > 0 && state.hasMore) {
                            // 循环加载直到达到目标数量
                            while (state.allItems.length < batchSize && state.hasMore) {
                                const result = await this.activityMgr.getBookmarks(state.page, username);
                                
                                // 合并收藏(避免重复)
                                const existingIds = new Set(state.allItems.map(b => b.id));
                                const newItems = result.bookmarks.filter(b => !existingIds.has(b.id));
                                state.allItems = [...state.allItems, ...newItems];
                                state.hasMore = result.hasMore;
                                state.page = result.page + 1;
                                
                                this.activityMgr.setPageState('bookmarks', state);
                                
                                if (newItems.length === 0) break;
                            }
                        }
                    } catch (e) {
                        if (state.allItems.length === 0) throw e;
                        this.renderer.showToast(`⚠️ ${e.message}`);
                    }
                }

                // 根据搜索词过滤
                const search = state.search || '';
                let filteredItems = state.allItems;
                if (search) {
                    const kw = search.toLowerCase();
                    // 兼容新旧tags格式(新格式是对象数组 {id,name,slug},旧格式是字符串数组)
                    const getTagName = tag => typeof tag === 'string' ? tag : (tag?.name || '');
                    filteredItems = state.allItems.filter(b => 
                        (b.title && b.title.toLowerCase().includes(kw)) ||
                        (b.fancy_title && b.fancy_title.toLowerCase().includes(kw)) ||
                        (b.tags && b.tags.some(tag => getTagName(tag).toLowerCase().includes(kw))) ||
                        (b.excerpt && b.excerpt.toLowerCase().includes(kw))
                    );
                }

                container.innerHTML = this.renderer.renderBookmarkListWithSearch(filteredItems, state.hasMore && !search, search, batchSize, state.allItems.length);
                this._bindBookmarkClicks(container);

                // 绑定搜索事件
                this._bindBookmarkSearchEvents(container, state);

                // 绑定瀑布流滚动加载(搜索时禁用)
                if (state.hasMore && !search) {
                    this._bindActivityScroll(container, 'bookmarks');
                }
            }

            _bindBookmarkSearchEvents(container, state) {
                const input = container.querySelector('.ldsp-activity-search input');
                const clear = container.querySelector('.ldsp-activity-search-clear');
                const batchSelect = container.querySelector('.ldsp-activity-batch-select');
                
                const doSearch = () => {
                    clearTimeout(this._bookmarkSearchTimer);
                    state.search = input.value.trim();
                    this.activityMgr.setPageState('bookmarks', state);
                    this._loadBookmarks(container, true);
                };
                
                input?.addEventListener('input', (e) => {
                    clear?.classList.toggle('show', !!e.target.value);
                    clearTimeout(this._bookmarkSearchTimer);
                    // 增加防抖时间到600ms,避免输入中断
                    this._bookmarkSearchTimer = setTimeout(doSearch, 600);
                });
                
                // 回车键立即搜索
                input?.addEventListener('keydown', (e) => {
                    if (e.key === 'Enter') {
                        e.preventDefault();
                        doSearch();
                    }
                });
                
                clear?.addEventListener('click', () => {
                    input.value = '';
                    clear.classList.remove('show');
                    state.search = '';
                    this.activityMgr.setPageState('bookmarks', state);
                    this._loadBookmarks(container, true);
                });

                batchSelect?.addEventListener('change', async (e) => {
                    const newBatchSize = parseInt(e.target.value, 10);
                    state.batchSize = newBatchSize;
                    this.activityMgr.setPageState('bookmarks', state);
                    
                    // 如果需要加载更多数据
                    if (newBatchSize > state.allItems.length && state.hasMore) {
                        // 显示加载状态
                        const toolbar = container.querySelector('.ldsp-activity-toolbar');
                        const list = container.querySelector('.ldsp-bookmark-list');
                        toolbar?.classList.add('loading');
                        batchSelect.classList.add('loading');
                        
                        if (list) {
                            list.classList.add('loading');
                            const loadingOverlay = document.createElement('div');
                            loadingOverlay.className = 'ldsp-activity-loading-overlay';
                            loadingOverlay.innerHTML = `<div class="ldsp-activity-loading-spinner"></div><div class="ldsp-activity-loading-text">正在加载 ${newBatchSize} 条数据...</div>`;
                            list.style.position = 'relative';
                            list.appendChild(loadingOverlay);
                        }
                        
                        await this._loadBookmarks(container, false, newBatchSize);
                    } else {
                        // 数据足够,只需重新渲染
                        await this._loadBookmarks(container, true, newBatchSize);
                    }
                });
            }

            async _loadReplies(container) {
                // 获取当前用户名
                const username = this.storage.getUser();
                if (!username) {
                    container.innerHTML = this.renderer.renderActivityEmpty('🔒', '请先登录论坛');
                    return;
                }

                // 获取当前分页状态
                let state = this.activityMgr.getPageState('replies');
                
                // 如果是首次加载,重置状态
                if (!state.allItems || state.allItems.length === 0) {
                    state = { offset: 0, allItems: [], hasMore: true };
                }

                try {
                    const result = await this.activityMgr.getReplies(state.offset, username);
                    
                    // 合并回复(避免重复,使用 post_id 作为唯一标识)
                    const existingIds = new Set(state.allItems.map(r => r.post_id));
                    const newItems = result.replies.filter(r => !existingIds.has(r.post_id));
                    state.allItems = [...state.allItems, ...newItems];
                    state.hasMore = result.hasMore;
                    
                    this.activityMgr.setPageState('replies', state);

                    container.innerHTML = this.renderer.renderReplyList(state.allItems, state.hasMore);
                    this._bindReplyClicks(container);

                    // 绑定瀑布流滚动加载
                    if (state.hasMore) {
                        this._bindActivityScroll(container, 'replies');
                    }
                } catch (e) {
                    if (state.allItems && state.allItems.length > 0) {
                        // 如果已有数据,显示已有数据并提示加载更多失败
                        container.innerHTML = this.renderer.renderReplyList(state.allItems, false);
                        this._bindReplyClicks(container);
                        this.renderer.showToast(`⚠️ ${e.message}`);
                    } else {
                        throw e;
                    }
                }
            }

            _bindReplyClicks(container) {
                container.querySelectorAll('.ldsp-reply-item[data-url]').forEach(item => {
                    item.addEventListener('click', (e) => {
                        // 如果点击的是excerpt内的链接,不阻止默认行为
                        if (e.target.closest('.ldsp-reply-excerpt a')) {
                            return;
                        }
                        e.preventDefault();
                        const url = item.dataset.url;
                        if (url && url !== '#') {
                            window.open(url, '_blank');
                        }
                    });
                });
            }

            async _loadLikes(container) {
                // 获取当前用户名
                const username = this.storage.getUser();
                if (!username) {
                    container.innerHTML = this.renderer.renderActivityEmpty('🔒', '请先登录论坛');
                    return;
                }

                // 获取当前分页状态
                let state = this.activityMgr.getPageState('likes');
                
                // 如果是首次加载,重置状态
                if (!state.allItems || state.allItems.length === 0) {
                    state = { offset: 0, allItems: [], hasMore: true };
                }

                try {
                    const result = await this.activityMgr.getLikes(state.offset, username);
                    
                    // 合并赞过(避免重复,使用 post_id 作为唯一标识)
                    const existingIds = new Set(state.allItems.map(l => l.post_id));
                    const newItems = result.likes.filter(l => !existingIds.has(l.post_id));
                    state.allItems = [...state.allItems, ...newItems];
                    state.hasMore = result.hasMore;
                    
                    this.activityMgr.setPageState('likes', state);

                    container.innerHTML = this.renderer.renderLikeList(state.allItems, state.hasMore);
                    this._bindLikeClicks(container);

                    // 绑定瀑布流滚动加载
                    if (state.hasMore) {
                        this._bindActivityScroll(container, 'likes');
                    }
                } catch (e) {
                    if (state.allItems && state.allItems.length > 0) {
                        // 如果已有数据,显示已有数据并提示加载更多失败
                        container.innerHTML = this.renderer.renderLikeList(state.allItems, false);
                        this._bindLikeClicks(container);
                        this.renderer.showToast(`⚠️ ${e.message}`);
                    } else {
                        throw e;
                    }
                }
            }

            _bindLikeClicks(container) {
                container.querySelectorAll('.ldsp-like-item[data-url]').forEach(item => {
                    item.addEventListener('click', (e) => {
                        // 如果点击的是excerpt内的链接,不阻止默认行为
                        if (e.target.closest('.ldsp-like-excerpt a')) {
                            return;
                        }
                        e.preventDefault();
                        const url = item.dataset.url;
                        if (url && url !== '#') {
                            window.open(url, '_blank');
                        }
                    });
                });
            }

            async _loadMyTopics(container) {
                // 获取当前用户名
                const username = this.storage.getUser();
                if (!username) {
                    container.innerHTML = this.renderer.renderActivityEmpty('🔒', '请先登录论坛');
                    return;
                }

                // 获取当前分页状态
                let state = this.activityMgr.getPageState('topics');
                
                // 如果是首次加载,重置状态
                if (!state.allItems || state.allItems.length === 0) {
                    state = { page: 0, allItems: [], hasMore: true };
                }

                try {
                    const result = await this.activityMgr.getMyTopics(state.page, username);
                    
                    // 合并话题(避免重复,使用 id 作为唯一标识)
                    const existingIds = new Set(state.allItems.map(t => t.id));
                    const newItems = result.topics.filter(t => !existingIds.has(t.id));
                    state.allItems = [...state.allItems, ...newItems];
                    state.hasMore = result.hasMore;
                    
                    this.activityMgr.setPageState('topics', state);

                    container.innerHTML = this.renderer.renderMyTopicList(state.allItems, state.hasMore);

                    // 绑定瀑布流滚动加载
                    if (state.hasMore) {
                        this._bindActivityScroll(container, 'topics');
                    }
                } catch (e) {
                    if (state.allItems && state.allItems.length > 0) {
                        // 如果已有数据,显示已有数据并提示加载更多失败
                        container.innerHTML = this.renderer.renderMyTopicList(state.allItems, false);
                        this.renderer.showToast(`⚠️ ${e.message}`);
                    } else {
                        throw e;
                    }
                }
            }

            async _loadReactions(container) {
                // 获取当前用户名
                const username = this.storage.getUser();
                if (!username) {
                    container.innerHTML = this.renderer.renderActivityEmpty('🔒', '请先登录论坛');
                    return;
                }

                // 获取当前分页状态
                let state = this.activityMgr.getPageState('reactions');
                
                // 如果是首次加载,重置状态
                if (!state.allItems || state.allItems.length === 0) {
                    state = { lastId: null, allItems: [], hasMore: true };
                }

                try {
                    const result = await this.activityMgr.getReactions(state.lastId, username);
                    
                    // 合并互动记录(避免重复,使用 id 作为唯一标识)
                    const existingIds = new Set(state.allItems.map(r => r.id));
                    const newItems = result.reactions.filter(r => !existingIds.has(r.id));
                    state.allItems = [...state.allItems, ...newItems];
                    state.hasMore = result.hasMore;
                    state.lastId = result.lastId;
                    
                    this.activityMgr.setPageState('reactions', state);

                    container.innerHTML = this.renderer.renderReactionList(state.allItems, state.hasMore);
                    this._bindReactionClicks(container);

                    // 绑定瀑布流滚动加载
                    if (state.hasMore) {
                        this._bindActivityScroll(container, 'reactions');
                    }
                } catch (e) {
                    if (state.allItems && state.allItems.length > 0) {
                        // 如果已有数据,显示已有数据并提示加载更多失败
                        container.innerHTML = this.renderer.renderReactionList(state.allItems, false);
                        this._bindReactionClicks(container);
                        this.renderer.showToast(`⚠️ ${e.message}`);
                    } else {
                        throw e;
                    }
                }
            }

            _bindReactionClicks(container) {
                container.querySelectorAll('.ldsp-reaction-item[data-url]').forEach(item => {
                    item.addEventListener('click', (e) => {
                        // 如果点击的是excerpt内的链接,不阻止默认行为
                        if (e.target.closest('.ldsp-reaction-excerpt a')) {
                            return;
                        }
                        e.preventDefault();
                        const url = item.dataset.url;
                        if (url && url !== '#') {
                            window.open(url, '_blank');
                        }
                    });
                });
            }

            _bindActivityScroll(container, type = 'read') {
                const content = this.el.querySelector('.ldsp-content');
                if (!content) return;

                let isLoading = false;
                const threshold = 100; // 距离底部100px时触发加载

                this._activityScrollHandler = async () => {
                    if (isLoading) return;
                    
                    const scrollTop = content.scrollTop;
                    const scrollHeight = content.scrollHeight;
                    const clientHeight = content.clientHeight;

                    if (scrollHeight - scrollTop - clientHeight < threshold) {
                        const state = this.activityMgr.getPageState(type);
                        if (!state.hasMore) return;

                        isLoading = true;
                        const loadMoreEl = container.querySelector('.ldsp-load-more');
                        if (loadMoreEl) {
                            loadMoreEl.classList.add('loading');
                        }

                        try {
                            // 加载下一页
                            let result, newItems;
                            const username = this.storage.getUser();
                            
                            if (type === 'bookmarks') {
                                result = await this.activityMgr.getBookmarks(state.page, username);
                                const existingIds = new Set(state.allItems.map(b => b.id));
                                newItems = result.bookmarks.filter(b => !existingIds.has(b.id));
                                state.allItems = [...state.allItems, ...newItems];
                                state.hasMore = result.hasMore;
                                state.page = result.page + 1;
                                this.activityMgr.setPageState(type, state);
                                // 根据搜索词过滤
                                const search = state.search || '';
                                const batchSize = state.batchSize || 20;
                                let filteredItems = state.allItems;
                                if (search) {
                                    const kw = search.toLowerCase();
                                    // 兼容新旧tags格式(新格式是对象数组 {id,name,slug},旧格式是字符串数组)
                                    const getTagName = tag => typeof tag === 'string' ? tag : (tag?.name || '');
                                    filteredItems = state.allItems.filter(b => 
                                        (b.title && b.title.toLowerCase().includes(kw)) ||
                                        (b.fancy_title && b.fancy_title.toLowerCase().includes(kw)) ||
                                        (b.tags && b.tags.some(tag => getTagName(tag).toLowerCase().includes(kw))) ||
                                        (b.excerpt && b.excerpt.toLowerCase().includes(kw))
                                    );
                                }
                                container.innerHTML = this.renderer.renderBookmarkListWithSearch(filteredItems, state.hasMore && !search, search, batchSize, state.allItems.length);
                                this._bindBookmarkClicks(container);
                                this._bindBookmarkSearchEvents(container, state);
                            } else if (type === 'replies') {
                                state.offset += 30;
                                result = await this.activityMgr.getReplies(state.offset, username);
                                const existingIds = new Set(state.allItems.map(r => r.post_id));
                                newItems = result.replies.filter(r => !existingIds.has(r.post_id));
                                state.allItems = [...state.allItems, ...newItems];
                                state.hasMore = result.hasMore;
                                this.activityMgr.setPageState(type, state);
                                container.innerHTML = this.renderer.renderReplyList(state.allItems, state.hasMore);
                                this._bindReplyClicks(container);
                            } else if (type === 'likes') {
                                state.offset += 30;
                                result = await this.activityMgr.getLikes(state.offset, username);
                                const existingIds = new Set(state.allItems.map(l => l.post_id));
                                newItems = result.likes.filter(l => !existingIds.has(l.post_id));
                                state.allItems = [...state.allItems, ...newItems];
                                state.hasMore = result.hasMore;
                                this.activityMgr.setPageState(type, state);
                                container.innerHTML = this.renderer.renderLikeList(state.allItems, state.hasMore);
                                this._bindLikeClicks(container);
                            } else if (type === 'topics') {
                                state.page++;
                                result = await this.activityMgr.getMyTopics(state.page, username);
                                const existingIds = new Set(state.allItems.map(t => t.id));
                                newItems = result.topics.filter(t => !existingIds.has(t.id));
                                state.allItems = [...state.allItems, ...newItems];
                                state.hasMore = result.hasMore;
                                this.activityMgr.setPageState(type, state);
                                container.innerHTML = this.renderer.renderMyTopicList(state.allItems, state.hasMore);
                            } else if (type === 'reactions') {
                                result = await this.activityMgr.getReactions(state.lastId, username);
                                const existingIds = new Set(state.allItems.map(r => r.id));
                                newItems = result.reactions.filter(r => !existingIds.has(r.id));
                                state.allItems = [...state.allItems, ...newItems];
                                state.hasMore = result.hasMore;
                                state.lastId = result.lastId;
                                this.activityMgr.setPageState(type, state);
                                container.innerHTML = this.renderer.renderReactionList(state.allItems, state.hasMore);
                                this._bindReactionClicks(container);
                            } else {
                                // 已读话题:滚动加载使用默认大小(约30条/页)
                                // state.page 存储的是下一页的页码
                                result = await this.activityMgr.getReadTopics(state.page, 30);
                                const existingIds = new Set(state.allTopics.map(t => t.id));
                                newItems = result.topics.filter(t => !existingIds.has(t.id));
                                state.allTopics = [...state.allTopics, ...newItems];
                                state.hasMore = result.hasMore;
                                state.page = result.page + 1;  // 下一次从下一页开始
                                this.activityMgr.setPageState(type, state);
                                // 根据搜索词过滤
                                const search = state.search || '';
                                const batchSize = state.batchSize || 20;
                                let filteredTopics = state.allTopics;
                                if (search) {
                                    const kw = search.toLowerCase();
                                    // 兼容新旧tags格式(新格式是对象数组 {id,name,slug},旧格式是字符串数组)
                                    const getTagName = tag => typeof tag === 'string' ? tag : (tag?.name || '');
                                    filteredTopics = state.allTopics.filter(t => 
                                        (t.title && t.title.toLowerCase().includes(kw)) ||
                                        (t.tags && t.tags.some(tag => getTagName(tag).toLowerCase().includes(kw)))
                                    );
                                }
                                container.innerHTML = this.renderer.renderTopicListWithSearch(filteredTopics, state.hasMore && !search, search, batchSize, state.allTopics.length);
                                this._bindReadSearchEvents(container, state);
                            }
                            
                            if (state.hasMore) {
                                // 继续监听
                                isLoading = false;
                            } else {
                                this._cleanupActivityScroll();
                            }
                        } catch (e) {
                            this.renderer.showToast(`⚠️ 加载更多失败: ${e.message}`);
                            // 回退页码/偏移(reactions 使用 lastId,不需要回退)
                            if (type === 'replies' || type === 'likes') {
                                state.offset -= 30;
                            } else if (type !== 'reactions') {
                                state.page--;
                            }
                            this.activityMgr.setPageState(type, state);
                            isLoading = false;
                            
                            if (loadMoreEl) {
                                loadMoreEl.classList.remove('loading');
                                loadMoreEl.innerHTML = '<span>加载失败,向下滚动重试</span>';
                            }
                        }
                    }
                };

                content.addEventListener('scroll', this._activityScrollHandler, { passive: true });
            }

            _bindBookmarkClicks(container) {
                container.querySelectorAll('.ldsp-bookmark-item[data-url]').forEach(item => {
                    item.addEventListener('click', (e) => {
                        // 如果点击的是excerpt内的链接,不阻止默认行为
                        if (e.target.closest('.ldsp-bookmark-excerpt a')) {
                            return;
                        }
                        e.preventDefault();
                        const url = item.dataset.url;
                        if (url && url !== '#') {
                            window.open(url, '_blank');
                        }
                    });
                });
            }

            _cleanupActivityScroll() {
                if (this._activityScrollHandler) {
                    const content = this.el.querySelector('.ldsp-content');
                    if (content) {
                        content.removeEventListener('scroll', this._activityScrollHandler);
                    }
                    this._activityScrollHandler = null;
                }
                // 重置分页状态
                this.activityMgr.setPageState('read', { page: 0, allTopics: [], hasMore: true, search: '', batchSize: 20 });
                this.activityMgr.setPageState('bookmarks', { page: 0, allItems: [], hasMore: true });
                this.activityMgr.setPageState('replies', { offset: 0, allItems: [], hasMore: true });
                this.activityMgr.setPageState('likes', { offset: 0, allItems: [], hasMore: true });
                this.activityMgr.setPageState('reactions', { lastId: null, allItems: [], hasMore: true });
                this.activityMgr.setPageState('topics', { page: 0, allItems: [], hasMore: true });
            }

            // ========== 排行榜 ==========

            async _renderLeaderboard() {
                if (!this.hasLeaderboard || !this.$.leaderboard) return;

                const logged = this.oauth.isLoggedIn();
                const joined = this.oauth.isJoined();

                this.renderer.renderLeaderboard(this.lbTab, logged, joined);

                this.$.leaderboard.querySelectorAll('.ldsp-subtab').forEach(tab => {
                    tab.addEventListener('click', () => {
                        this.lbTab = tab.dataset.lb;
                        this.storage.setGlobal('leaderboardTab', this.lbTab);
                        this.$.leaderboard.querySelectorAll('.ldsp-subtab').forEach(t => t.classList.remove('active'));
                        tab.classList.add('active');
                        this._renderLeaderboardContent();
                    });
                });

                await this._renderLeaderboardContent();
            }

            async _renderLeaderboardContent() {
                if (!this.hasLeaderboard) return;

                const container = this.$.leaderboard.querySelector('.ldsp-lb-content');
                if (!container) return;

                const logged = this.oauth.isLoggedIn();
                const joined = this.oauth.isJoined();

                if (!logged) {
                    container.innerHTML = this.renderer.renderLeaderboardLogin();
                    const loginBtn = container.querySelector('#ldsp-lb-login');
                    if (loginBtn) {
                        loginBtn.onclick = async () => {
                            loginBtn.disabled = true;
                            loginBtn.textContent = '⏳ 跳转中...';
                            try {
                                // 统一同窗口登录:会跳转到授权页面
                                await this.oauth.login();
                                // 正常情况下不会执行到这里,因为页面会跳转
                            } catch (e) {
                                this.renderer.showToast(`❌ ${e.message}`);
                                loginBtn.disabled = false;
                                loginBtn.textContent = '🚀 立即登录';
                            }
                        };
                    }
                    return;
                }

                if (!joined) {
                    if (this.registrationPaused && !this.hasJoinedBefore) {
                        container.innerHTML = this.renderer.renderRegistrationPaused();
                        return;
                    }

                    container.innerHTML = this.renderer.renderLeaderboardJoin();
                    const joinBtn = container.querySelector('#ldsp-lb-join');
                    if (joinBtn) {
                        joinBtn.onclick = async () => {
                            joinBtn.disabled = true;
                            joinBtn.textContent = '⏳ 加入中...';
                            try {
                                await this.leaderboard.join();
                                this.hasJoinedBefore = true;
                                this.leaderboard.startSync();
                                this.renderer.showToast('✅ 已成功加入排行榜');
                                await this._renderLeaderboardContent();
                            } catch (e) {
                                const msg = ErrorFormatter.withIcon(e);
                                this.renderer.showToast(msg);
                                const paused = e?.code === 'REGISTRATION_PAUSED' || (e?.message || '').includes('已暂停新用户注册');
                                if (paused) {
                                    this.registrationPaused = true;
                                    await this._renderLeaderboardContent();
                                    return;
                                }
                                joinBtn.disabled = false;
                                joinBtn.textContent = '✨ 加入排行榜';
                            }
                        };
                    }
                    return;
                }

                container.innerHTML = this.renderer.renderLeaderboardLoading();

                try {
                    const data = await this.leaderboard.getLeaderboard(this.lbTab);
                    const user = this.oauth.getUserInfo();
                    container.innerHTML = this.renderer.renderLeaderboardData(data, user?.id, joined, this.lbTab);
                    this._bindLeaderboardEvents(container, joined);
                } catch (e) {
                    // v3.5.2.9: 使用统一的错误格式化
                    container.innerHTML = this.renderer.renderLeaderboardError(ErrorFormatter.format(e));
                    container.querySelector('#ldsp-lb-retry')?.addEventListener('click', () => {
                        this.leaderboard.clearCache();
                        this._renderLeaderboardContent();
                    });
                }
            }

            // 绑定排行榜内容区的事件(统一绑定,避免代码重复)
            _bindLeaderboardEvents(container, joined) {
                // 手动刷新按钮
                const refreshBtn = container.querySelector('.ldsp-lb-refresh');
                if (refreshBtn) {
                    refreshBtn.onclick = async (e) => {
                        const btn = e.target;
                        const type = btn.dataset.type;
                        if (btn.disabled) return;
                        
                        const cooldown = this.leaderboard.getRefreshCooldown(type);
                        if (cooldown > 0) {
                            this.renderer.showToast(`⏳ 请等待 ${cooldown} 秒后再刷新`);
                            return;
                        }
                        
                        btn.disabled = true;
                        btn.classList.add('spinning');
                        
                        try {
                            const result = await this.leaderboard.forceRefresh(type);
                            this.renderer.showToast(result.fromCache ? '📦 获取缓存数据' : '✅ 已刷新排行榜');
                            const userData = this.oauth.getUserInfo();
                            container.innerHTML = this.renderer.renderLeaderboardData(result.data, userData?.id, joined, type);
                            this._bindLeaderboardEvents(container, joined);
                        } catch (err) {
                            // v3.5.2.9: 使用统一的错误格式化
                            this.renderer.showToast(ErrorFormatter.withIcon(err));
                            btn.disabled = false;
                            btn.classList.remove('spinning');
                        }
                    };
                }

                // 退出排行榜按钮
                const quitBtn = container.querySelector('#ldsp-lb-quit');
                if (quitBtn) {
                    quitBtn.onclick = async () => {
                        const confirmed = await LDSPDialog.confirm('确定要退出排行榜吗?', { title: '退出排行榜', icon: '🚪', danger: true });
                        if (!confirmed) return;
                        quitBtn.disabled = true;
                        quitBtn.textContent = '退出中...';
                        try {
                            await this.leaderboard.quit();
                            this.leaderboard.stopSync();
                            this.renderer.showToast('✅ 已退出排行榜');
                            await this._renderLeaderboardContent();
                        } catch (e) {
                            // v3.5.2.9: 使用统一的错误格式化
                            this.renderer.showToast(ErrorFormatter.withIcon(e));
                            quitBtn.disabled = false;
                            quitBtn.textContent = '退出排行榜';
                        }
                    };
                }
            }

            destroy() {
                if (this._destroyed) return;
                this._destroyed = true;
                
                // 清理定时器
                if (this._refreshTimer) {
                    clearInterval(this._refreshTimer);
                    this._refreshTimer = null;
                }
                if (this._readingTimer) {
                    clearInterval(this._readingTimer);
                    this._readingTimer = null;
                }
                
                // 清理事件监听器
                if (this._resizeHandler) {
                    window.removeEventListener('resize', this._resizeHandler);
                }
                if (this._actionsCheckRaf) {
                    cancelAnimationFrame(this._actionsCheckRaf);
                    this._actionsCheckRaf = null;
                }
                
                // 清理阅读追踪器
                this.tracker?.destroy();
                
                // 清理排行榜相关
                if (this.hasLeaderboard) {
                    this.leaderboard?.destroy();
                    this.cloudSync?.destroy();
                }
                
                // 清理工单管理器
                this.ticketManager?.destroy();

                // 清理吃瓜助手
                this.melonHelper?.destroy();

                // 清理 LDC 管理器
                this.ldcManager?.destroy();

                // 清理 CDK 管理器
                this.cdkManager?.destroy();
                
                // 清理关注/粉丝管理器
                this.followManager?.destroy();
                
                // 保存数据
                this.storage?.flush();
                
                // 清理事件总线
                EventBus.clear();
                
                // 移除面板
                this.el?.remove();
                
                Logger.log('Panel destroyed');
            }
        }

        // ==================== 启动 ====================
        async function startup() {
            // 初始化全局领导者管理器(必须在其他组件之前)
            TabLeader.init();
            
            // 性能优化:使用 requestIdleCallback 在空闲时加载非关键配置
            requestIdleCallback(() => {
                Network.loadReadingLevels().catch(() => {});
            }, { timeout: 3000 });
            
            // 创建面板
            try {
                new Panel();
            } catch (e) {
                Logger.error('Panel initialization failed:', e);
            }
        }

        // 确保 DOM 就绪后启动
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', startup, { once: true });
        } else {
            // 使用 requestAnimationFrame 确保在下一帧渲染
            requestAnimationFrame(startup);
        }

    })();