linux.do player helper

观影助手

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         linux.do player helper
// @namespace    http://tampermonkey.net/
// @version      1.0.6
// @description  观影助手
// @match        *://linux.do/*
// @match        *://idcflare.com/*
// @match        https://*.bing.com
// @match        https://*.bing.com/chrome/newtab
// @grant        GM_addStyle
// @require      https://unpkg.com/[email protected]/dist/jquery.js
// @require      https://cdn.tailwindcss.com
// @require      https://cdn.jsdelivr.net/npm/sweetalert2@11
// @require      https://cdn.jsdelivr.net/npm/[email protected]/Sortable.min.js
// @license      MIT
// @run-at       document-end
// ==/UserScript==

(function () {
    'use strict';

    // =================================================================================
    // 1. 配置中心 (CONFIG)
    // =================================================================================

    // 插件显示模式,是否嵌合为页面图标
    let showLogoInner = true;
    // 能够设置嵌合为页面图标的网站host(Discourse论坛)
    const canEmbedHosts = ['linux.do', 'idcflare.com'];

    const CONFIG = {
        STORAGE_KEYS: {
            THEME: 'elegant-theme',
            TAB_POSITION: 'elegant-tab-position',
            TAB_ORDER: 'elegant-tab-order',
            DOUBAN_API: 'douban-api',
            DOUBAN_PROXY: 'douban-api-proxy',
            DOUBAN_PROXY_MODE: 'douban-proxy-mode', // 'cdn' 或 'proxy'
            DOUBAN_IMAGE_URL: 'douban-image-url',
            DOUBAN_IMAGE_MODE: 'douban-image-mode', // 'cdn' 或 'proxy'
            DAILY_API: 'daily-api',
            DAILY_DATA_CACHE: 'elegant-daily-data-cache',
            DAILY_DATA_TIMESTAMP: 'elegant-daily-data-timestamp',
            REDIRECT_URL: 'redirect-url',
            BANGUMI_CUSTOM_URL: 'bangumi_id_custom_jump_url',
            SHOW_LOGO_INNER: 'show-logo-inner', // 是否显示为页面嵌合图标
            // 豆瓣查找本地缓存存储键
            DOUBAN_CACHE_MOVIE: 'elegant-douban-cache-movie',
            DOUBAN_CACHE_TV: 'elegant-douban-cache-tv',
            DOUBAN_CACHE_ANIME: 'elegant-douban-cache-anime',
            DOUBAN_CACHE_VARIETY: 'elegant-douban-cache-variety',
            DOUBAN_CACHE_TIMESTAMP: 'elegant-douban-cache-timestamp',
            DOUBAN_DAILY_LINK: 'douban-daily-link', // 豆瓣卡片与每日放送联动
            DAILY_LINK_POSITION: 'daily-link-position', // 联动卡片位置:head/tail
        },
        SELECTORS: {
            MAIN_MODAL: '#elegant-main-modal',
            SETTINGS_MODAL: '#elegant-settings-modal',
            OVERLAY: '#elegant-modal-overlay',
            MAIN_BUTTON: '#elegant-main-button',
            TABS_CONTAINER: '.tabs-container',
            TABS_HEADER: '.tabs-header',
            TAB_CONTENT: '.tab-content',
            CONTEXT_MENU: '#elegant-context-menu',
            // 豆瓣查找相关选择器
            DOUBAN_TABS: '.douban-tabs',
            DOUBAN_CATEGORY: '.douban-category',
            DOUBAN_FILTER: '.douban-filter',
            DOUBAN_CONTENT: '#douban-content-container',
            DOUBAN_LOADING: '.douban-loading',
        },
        CLASSES: {
            HIDDEN: 'hidden',
            ACTIVE: 'active',
            FADE_IN: 'animate-fade-in',
            FADE_OUT: 'animate-fade-out',
            LOADING: 'loading'
        },
        ANIMATION_DURATION: 300, // ms
        WEEK_DAYS: ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日'],
        DEFAULT_DAILY_API: 'https://api.bgm.tv/calendar', // 默认每日放送API
        DEFAULT_DOUBAN_API: 'https://m.douban.com/rexxar/api/v2', // 默认豆瓣API
        REDIRECT_DELAY: 1500, // 跳转延迟时间(毫秒)
        DOUBAN_PAGE_SIZE: 20, // 豆瓣每页数据量
    };

    // 豆瓣API端点配置
    const DOUBAN_ENDPOINTS = {
        movie_recommend: '/movie/recommend',
        movie_recent_hot: '/subject/recent_hot/movie',
        tv_recommend: '/tv/recommend',
        tv_recent_hot: '/subject/recent_hot/tv',
    };

    // 各tab的分类配置
    const DOUBAN_TAB_CATEGORIES = {
        movie: {
            all: '全部',
            hot: '热门电影',
            latest: '最新电影',
            high_score: '豆瓣高分',
            hidden_gem: '冷门佳片'
        },
        tv: {
            all: '全部',
            hot: '最近热门'
        },
        anime: {
            series: '番剧',
            movie: '剧场版'
        },
        variety: {
            all: '全部',
            hot: '最近热门'
        }
    };

    // 各tab分类对应的筛选选项
    const DOUBAN_CATEGORY_FILTERS = {
        // 电影-全部:类型、地区、年代、排序
        movie_all: { type: 'dropdown', fields: ['type', 'region', 'year', 'sort'] },
        // 电影-热门/最新/高分/冷门:全部、华语、欧美、韩国、日本
        movie_hot: { type: 'capsule', options: { all: '全部', chinese: '华语', western: '欧美', korea: '韩国', japan: '日本' } },
        movie_latest: { type: 'capsule', options: { all: '全部', chinese: '华语', western: '欧美', korea: '韩国', japan: '日本' } },
        movie_high_score: { type: 'capsule', options: { all: '全部', chinese: '华语', western: '欧美', korea: '韩国', japan: '日本' } },
        movie_hidden_gem: { type: 'capsule', options: { all: '全部', chinese: '华语', western: '欧美', korea: '韩国', japan: '日本' } },
        // 剧集-全部:类型、地区、年代、平台、排序
        tv_all: { type: 'dropdown', fields: ['type', 'region', 'year', 'platform', 'sort'] },
        // 剧集-最近热门:全部、国产、欧美、日本、韩国、动漫、纪录片
        tv_hot: { type: 'capsule', options: { tv: '全部', tv_domestic: '国产', tv_american: '欧美', tv_japanese: '日本', tv_korean: '韩国', tv_animation: '动漫', tv_documentary: '纪录片' } },
        // 动漫-番剧:类型、地区、年代、平台、排序
        anime_series: { type: 'dropdown', fields: ['anime_type', 'region', 'year', 'platform', 'sort'] },
        // 动漫-剧场版:类型、地区、年代、排序(无平台)
        anime_movie: { type: 'dropdown', fields: ['anime_type', 'region', 'year', 'sort'] },
        // 综艺-全部:类型、地区、年代、平台、排序
        variety_all: { type: 'dropdown', fields: ['variety_type', 'region', 'year', 'platform', 'sort'] },
        // 综艺-最近热门:全部、国内、国外
        variety_hot: { type: 'capsule', options: { show: '全部', show_domestic: '国内', show_foreign: '国外' } }
    };

    // 豆瓣筛选选项配置
    const DOUBAN_FILTER_OPTIONS = {
        // 电影类型
        type: { all: '全部', 喜剧: '喜剧', 爱情: '爱情', 动作: '动作', 科幻: '科幻', 动画: '动画', 悬疑: '悬疑', 惊悚: '惊悚', 冒险: '冒险', 音乐: '音乐', 历史: '历史', 奇幻: '奇幻', 恐怖: '恐怖', 战争: '战争', 传记: '传记', 歌舞: '歌舞', 武侠: '武侠', 灾难: '灾难', 西部: '西部', 纪录片: '纪录片', 短片: '短片' },
        // 剧集类型
        tv_type: { all: '全部', 喜剧: '喜剧', 爱情: '爱情', 悬疑: '悬疑', 动画: '动画', 武侠: '武侠', 古装: '古装', 家庭: '家庭', 科幻: '科幻', 恐怖: '恐怖', 历史: '历史', 战争: '战争', 动作: '动作', 冒险: '冒险', 传记: '传记', 剧情: '剧情', 奇幻: '奇幻', 惊悚: '惊悚', 灾难: '灾难', 歌舞: '歌舞', 音乐: '音乐' },
        // 动漫类型
        anime_type: { all: '全部', 励志: '励志', 恋爱: '恋爱', 治愈: '治愈', 运动: '运动', 青春: '青春', 校园: '校园', 黑色幽默: '黑色幽默', 历史: '历史', 歌舞: '歌舞', 恶搞: '恶搞', 后宫: '后宫', 悬疑: '悬疑', 魔幻: '魔幻', 科幻: '科幻' },
        // 综艺类型
        variety_type: { all: '全部', 真人秀: '真人秀', 脱口秀: '脱口秀', 音乐: '音乐', 歌舞: '歌舞' },
        // 地区
        region: { all: '全部', 华语: '华语', 欧美: '欧美', 韩国: '韩国', 日本: '日本', 中国大陆: '中国大陆', 美国: '美国', 中国香港: '中国香港', 中国台湾: '中国台湾', 英国: '英国', 法国: '法国', 德国: '德国', 意大利: '意大利', 西班牙: '西班牙', 印度: '印度', 泰国: '泰国', 俄罗斯: '俄罗斯', 加拿大: '加拿大', 澳大利亚: '澳大利亚', 爱尔兰: '爱尔兰', 瑞典: '瑞典', 巴西: '巴西', 丹麦: '丹麦' },
        // 年代(动态生成)
        get year() {
            const currentYear = new Date().getFullYear();
            const years = [['all', '全部']];
            for (let y = currentYear; y >= 2019; y--) years.push([String(y), String(y)]);
            return years.concat([['2020年代', '2020年代'], ['2010年代', '2010年代'], ['2000年代', '2000年代'], ['90年代', '90年代'], ['80年代', '80年代'], ['70年代', '70年代'], ['60年代', '60年代'], ['更早', '更早']]);
        },
        // 平台
        platform: { all: '全部', 腾讯视频: '腾讯视频', 爱奇艺: '爱奇艺', 优酷: '优酷', 湖南卫视: '湖南卫视', Netflix: 'Netflix', HBO: 'HBO', BBC: 'BBC', NHK: 'NHK', CBS: 'CBS', NBC: 'NBC', tvN: 'tvN' },
        // 排序
        sort: { recommend: '综合排序', U: '近期热度', R: '首映时间', S: '高分优先' }
    };

    // =================================================================================
    // 2. 数据驱动视图 (TABS_CONFIG)
    // =================================================================================

    const TABS_CONFIG = {
        'tab1': {
            label: '每日放送',
            renderContent: renderDailyContent
        },
        'tab2': {
            label: '豆瓣查找',
            renderContent: renderDoubanContent
        },
        'tab3': {
            label: '管理跳转',
            renderContent: renderTagsManageContent
        },
        'tab4': {
            label: '管理收藏',
            renderContent: renderFavoritesContent
        }
    };

    const DEFAULT_TABS_ORDER = [
        { id: 'tab1', label: '每日放送' },
        { id: 'tab2', label: '豆瓣查找' },
        { id: 'tab3', label: '管理跳转' },
        { id: 'tab4', label: '管理收藏' }
    ];

    // 用于存储预加载数据Promise的全局变量
    let dailyDataPromise = null;
    // 用于记录原始每日放送API值
    let originalDailyApiValue = '';
    // 用于记录当前激活的标签页ID
    let currentActiveTabId = null;
    // 用于存储当前右键点击的卡片ID和类型
    let currentRightClickedCardId = null;
    let currentRightClickedCardType = null; // 'bangumi' 或 'douban'
    // 豆瓣查找相关状态
    let doubanCurrentTab = 'anime'; // 当前豆瓣查找的tab
    let doubanCurrentCategory = 'series'; // 当前分类
    let doubanIsLoading = false; // 是否正在加载
    // 每个tab独立的分页状态
    let doubanTabState = {
        movie: { page: 1, hasMore: true },
        tv: { page: 1, hasMore: true },
        anime: { page: 1, hasMore: true },
        variety: { page: 1, hasMore: true }
    };
    // 数据加载状态缓存
    let dailyDataLoaded = false;
    let doubanDataLoaded = false;
    // 豆瓣内部tab数据缓存 { movie: {html, hasMore, page, config, loaded}, tv: {...}, ... }
    // config字段记录该缓存对应的筛选配置,只有配置匹配时才使用缓存
    let doubanTabCache = {};

    // =================================================================================
    // 3. 豆瓣查找功能实现
    // =================================================================================

    function renderDoubanContent() {
        const categories = DOUBAN_TAB_CATEGORIES[doubanCurrentTab];
        const defaultCategories = { movie: 'latest', tv: 'hot', anime: 'series', variety: 'hot' };
        doubanCurrentCategory = getSetting(`douban-${doubanCurrentTab}-category`, defaultCategories[doubanCurrentTab]);

        return `
            <div id="douban-content-container" class="douban-container">
                <div class="douban-tabs">
                    <button class="douban-tab ${doubanCurrentTab === 'anime' ? 'active' : ''}" data-tab="anime">动漫</button>
                    <button class="douban-tab ${doubanCurrentTab === 'tv' ? 'active' : ''}" data-tab="tv">剧集</button>
                    <button class="douban-tab ${doubanCurrentTab === 'variety' ? 'active' : ''}" data-tab="variety">综艺</button>
                    <button class="douban-tab ${doubanCurrentTab === 'movie' ? 'active' : ''}" data-tab="movie">电影</button>
                </div>

                <div class="douban-filters">
                    <div class="filter-row">
                        <div class="filter-label">分类:</div>
                        <div class="filter-options douban-category" id="douban-category-options"></div>
                    </div>

                    <div class="filter-row">
                        <div class="filter-label">筛选:</div>
                        <div class="filter-options douban-filter" id="douban-filter-options"></div>
                    </div>
                </div>

                <div class="douban-results-wrapper">
                    <div class="douban-results ${doubanCurrentTab === 'movie' ? 'active' : ''}" id="douban-results-movie"></div>
                    <div class="douban-results ${doubanCurrentTab === 'tv' ? 'active' : ''}" id="douban-results-tv"></div>
                    <div class="douban-results ${doubanCurrentTab === 'anime' ? 'active' : ''}" id="douban-results-anime"></div>
                    <div class="douban-results ${doubanCurrentTab === 'variety' ? 'active' : ''}" id="douban-results-variety"></div>
                </div>
            </div>
        `;
    }

    // 获取当前tab的筛选存储键

    function updateDoubanCategoryOptions() {
        const $categoryOptions = $('#douban-category-options');
        const categories = DOUBAN_TAB_CATEGORIES[doubanCurrentTab];
        // 使用默认分类,不保存到 localStorage
        const defaultCategories = { movie: 'latest', tv: 'hot', anime: 'series', variety: 'hot' };
        const category = defaultCategories[doubanCurrentTab];
        doubanCurrentCategory = category;

        $categoryOptions.html(
            Object.entries(categories).map(([key, label]) =>
                `<button class="filter-option ${category === key ? 'active' : ''}" data-value="${key}">${label}</button>`
            ).join('')
        );
    }

    function updateDoubanFilterOptions() {
        const $filterOptions = $('#douban-filter-options');
        const filterKey = `${doubanCurrentTab}_${doubanCurrentCategory}`;
        const filterConfig = DOUBAN_CATEGORY_FILTERS[filterKey];

        if (!filterConfig) {
            $filterOptions.empty();
            return;
        }

        $filterOptions.empty();

        if (filterConfig.type === 'capsule') {
            // 胶囊按钮模式
            // 根据不同的tab和分类设置默认筛选
            const defaultFilters = {
                movie_hot: 'all',
                movie_latest: 'all',
                movie_high_score: 'all',
                movie_hidden_gem: 'all',
                tv_hot: 'tv_domestic',  // 剧集-最近热门默认为国产
                variety_hot: 'show_domestic'  // 综艺-最近热门默认为国内
            };
            // 获取当前filterKey对应的选项keys
            const optionKeys = Object.keys(filterConfig.options);
            const defaultFilter = defaultFilters[filterKey] || optionKeys[0];
            // 使用默认筛选,不读取 localStorage
            const savedFilter = defaultFilter;
            $filterOptions.html(
                Object.entries(filterConfig.options).map(([key, label]) =>
                    `<button class="filter-option ${savedFilter === key ? 'active' : ''}" data-value="${key}">${label}</button>`
                ).join('')
            );
        } else if (filterConfig.type === 'dropdown') {
            // 下拉菜单模式
            const fieldNames = { type: '类型', tv_type: '类型', anime_type: '类型', variety_type: '类型', region: '地区', year: '年代', platform: '平台', sort: '排序' };
            // 为不同tab和字段设置默认值
            const defaultFieldValues = {
                anime_type: 'all',
                // 动漫默认:地区中国大陆,排序近期热度
                anime_region: '中国大陆',
                anime_sort: 'U'
            };
            filterConfig.fields.forEach(field => {
                const options = DOUBAN_FILTER_OPTIONS[field] || DOUBAN_FILTER_OPTIONS.type;
                const isArray = Array.isArray(options);
                // 使用tab+字段特定的默认值,不读取 localStorage
                const tabFieldKey = `${doubanCurrentTab}_${field}`;
                const fieldDefault = defaultFieldValues[tabFieldKey] || defaultFieldValues[field] || (field === 'sort' ? 'recommend' : 'all');
                const savedValue = fieldDefault;
                // 当值为"全部"或"综合排序"时显示筛选名称,否则显示选中的值
                const optionLabel = isArray ? (options.find(o => o[0] === savedValue)?.[1] || '全部') : (options[savedValue] || '全部');
                const displayText = (savedValue === 'all' || savedValue === 'recommend') ? fieldNames[field] : optionLabel;

                const optionsHtml = isArray
                    ? options.map(([key, value]) => `<button class="ph-dd-item ${savedValue === key ? 'active' : ''}" data-value="${key}">${value}</button>`).join('')
                    : Object.entries(options).map(([key, value]) => `<button class="ph-dd-item ${savedValue === key ? 'active' : ''}" data-value="${key}">${value}</button>`).join('');

                $filterOptions.append(`
                    <div class="ph-dd-wrap" data-type="${field}">
                        <button class="ph-dd-toggle">
                            <span class="ph-dd-text">${displayText}</span>
                            <svg xmlns="http://www.w3.org/2000/svg" class="ph-dd-icon" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7" />
                            </svg>
                        </button>
                        <div class="ph-dd-menu">
                            <div class="ph-dd-grid">${optionsHtml}</div>
                        </div>
                    </div>
                `);
            });
        }
    }

    function buildDoubanApiUrl(tabType = null, preloading = false) {
        const doubanApi = CONFIG.DEFAULT_DOUBAN_API;
        const doubanProxy = getSetting(CONFIG.STORAGE_KEYS.DOUBAN_PROXY, '');
        const proxyMode = getSetting(CONFIG.STORAGE_KEYS.DOUBAN_PROXY_MODE, 'cdn');
        const currentTab = tabType || doubanCurrentTab;
        const category = tabType ? Object.keys(DOUBAN_TAB_CATEGORIES[tabType])[0] : doubanCurrentCategory;

        // 获取当前筛选值
        const filterValues = preloading ? {} : getCurrentFilterValues(currentTab, category);
        // 根据tab和category确定默认筛选值
        const defaultFilters = { tv_hot: 'tv_domestic', variety_hot: 'show_domestic' };
        const filterDefault = defaultFilters[`${currentTab}_${category}`] || 'all';
        const filter = filterValues.filter || filterDefault;

        // 根据模式构建baseUrl
        let baseUrl;
        if (!doubanProxy) {
            baseUrl = doubanApi;
        } else if (proxyMode === 'cdn') {
            // CDN模式:替换豆瓣host
            try {
                const apiUrl = new URL(doubanApi);
                const cdnUrl = new URL(doubanProxy);
                baseUrl = `${cdnUrl.protocol}//${cdnUrl.host}${apiUrl.pathname}`;
            } catch {
                baseUrl = doubanApi;
            }
        } else {
            // 代理模式:直接拼接
            baseUrl = doubanProxy.replace(/\/+$/, '') + '/' + doubanApi;
        }

        const page = tabType ? 1 : doubanTabState[doubanCurrentTab].page;
        const start = (page - 1) * CONFIG.DOUBAN_PAGE_SIZE;
        const count = CONFIG.DOUBAN_PAGE_SIZE;

        // 根据tab和分类确定API端点和参数
        const filterKey = `${currentTab}_${category}`;
        const filterConfig = DOUBAN_CATEGORY_FILTERS[filterKey];

        // 电影分类处理
        if (currentTab === 'movie') {
            if (category === 'all') {
                // 电影-全部:使用recommend API
                return buildRecommendUrl(baseUrl, DOUBAN_ENDPOINTS.movie_recommend, start, count, 'movie', filterValues);
            } else {
                // 电影-热门/最新/高分/冷门:使用recent_hot API
                const categoryMap = { hot: '热门', latest: '最新', high_score: '豆瓣高分', hidden_gem: '冷门佳片' };
                const typeMap = { all: '全部', chinese: '华语', western: '欧美', korea: '韩国', japan: '日本' };
                return `${baseUrl}${DOUBAN_ENDPOINTS.movie_recent_hot}?start=${start}&limit=${count}&category=${encodeURIComponent(categoryMap[category])}&type=${encodeURIComponent(typeMap[filter] || '全部')}`;
            }
        }

        // 剧集分类处理
        if (currentTab === 'tv') {
            if (category === 'all') {
                return buildRecommendUrl(baseUrl, DOUBAN_ENDPOINTS.tv_recommend, start, count, 'tv', filterValues);
            } else {
                // 剧集-最近热门
                return `${baseUrl}${DOUBAN_ENDPOINTS.tv_recent_hot}?start=${start}&limit=${count}&category=tv&type=${filter}`;
            }
        }

        // 动漫分类处理
        if (currentTab === 'anime') {
            if (category === 'series') {
                // 番剧:使用tv/recommend,固定selected_categories包含动画+电视剧
                return buildAnimeSeriesUrl(baseUrl, start, count, filterValues);
            } else {
                // 剧场版:使用movie/recommend,固定selected_categories包含动画
                return buildAnimeMovieUrl(baseUrl, start, count, filterValues);
            }
        }

        // 综艺分类处理
        if (currentTab === 'variety') {
            if (category === 'all') {
                return buildVarietyUrl(baseUrl, start, count, filterValues);
            } else {
                // 综艺-最近热门
                return `${baseUrl}${DOUBAN_ENDPOINTS.tv_recent_hot}?start=${start}&limit=${count}&category=show&type=${filter}`;
            }
        }

        return `${baseUrl}${DOUBAN_ENDPOINTS.movie_recommend}?start=${start}&count=${count}`;
    }

    function buildDoubanApiUrlForPreload(tabType, config) {
        const doubanApi = CONFIG.DEFAULT_DOUBAN_API;
        const doubanProxy = getSetting(CONFIG.STORAGE_KEYS.DOUBAN_PROXY, '');
        const proxyMode = getSetting(CONFIG.STORAGE_KEYS.DOUBAN_PROXY_MODE, 'cdn');

        // 根据模式构建baseUrl
        let baseUrl;
        if (!doubanProxy) {
            baseUrl = doubanApi;
        } else if (proxyMode === 'cdn') {
            try {
                const apiUrl = new URL(doubanApi);
                const cdnUrl = new URL(doubanProxy);
                baseUrl = `${cdnUrl.protocol}//${cdnUrl.host}${apiUrl.pathname}`;
            } catch {
                baseUrl = doubanApi;
            }
        } else {
            baseUrl = doubanProxy.replace(/\/+$/, '') + '/' + doubanApi;
        }

        const start = 0;
        const count = config.pageSize;

        // 根据tab和配置构建URL
        if (tabType === 'movie') {
            // 最新电影、全部
            const categoryMap = { latest: '最新' };
            const typeMap = { all: '全部' };
            return `${baseUrl}${DOUBAN_ENDPOINTS.movie_recent_hot}?start=${start}&limit=${count}&category=${encodeURIComponent(categoryMap[config.category])}&type=${encodeURIComponent(typeMap[config.filter])}`;
        }

        if (tabType === 'tv') {
            // 最近热门、国产
            const typeMap = { tv_domestic: 'tv_domestic' };
            return `${baseUrl}${DOUBAN_ENDPOINTS.tv_recent_hot}?start=${start}&limit=${count}&category=tv&type=${typeMap[config.filter]}`;
        }

        if (tabType === 'anime') {
            // 番剧、中国大陆、近期热度
            const selectedCategories = { '类型': '动画', '形式': '电视剧', '地区': '中国大陆' };
            const tags = ['动画', '中国大陆'];
            return `${baseUrl}${DOUBAN_ENDPOINTS.tv_recommend}?refresh=0&start=${start}&count=${count}&selected_categories=${encodeURIComponent(JSON.stringify(selectedCategories))}&uncollect=false&score_range=0,10&tags=${encodeURIComponent(tags.join(','))}&sort=${config.sort}`;
        }

        if (tabType === 'variety') {
            // 最近热门、国内
            const typeMap = { show_domestic: 'show_domestic' };
            return `${baseUrl}${DOUBAN_ENDPOINTS.tv_recent_hot}?start=${start}&limit=${count}&category=show&type=${typeMap[config.filter]}`;
        }

        return `${baseUrl}${DOUBAN_ENDPOINTS.movie_recommend}?start=${start}&count=${count}`;
    }

    function buildRecommendUrl(baseUrl, endpoint, start, count, mediaType, filterValues = {}) {
        // 从filterValues获取筛选值,默认为all
        const type = filterValues.type || filterValues[mediaType === 'movie' ? 'type' : 'tv_type'] || 'all';
        const region = filterValues.region || 'all';
        const year = filterValues.year || 'all';
        const platform = filterValues.platform || 'all';
        const sort = filterValues.sort || 'recommend';

        // 电影和剧集默认 {"类型":""},而不是空对象
        const selectedCategories = { '类型': '' };
        const tags = [];

        // 类型
        if (type !== 'all') {
            selectedCategories['类型'] = type;
            tags.push(type);
        }
        // 剧集需要形式(仅当有类型筛选时才添加)
        if (mediaType === 'tv' && type !== 'all') {
            selectedCategories['形式'] = '电视剧';
        }
        // 地区
        if (region !== 'all') {
            selectedCategories['地区'] = region;
            tags.push(region);
        }
        // 年代
        if (year !== 'all') tags.push(year);
        // 平台(仅剧集)
        if (mediaType === 'tv' && platform !== 'all') tags.push(platform);

        let url = `${baseUrl}${endpoint}?refresh=0&start=${start}&count=${count}&selected_categories=${encodeURIComponent(JSON.stringify(selectedCategories))}&uncollect=false&score_range=0,10&tags=${encodeURIComponent(tags.join(','))}`;
        if (sort !== 'recommend') url += `&sort=${sort}`;
        return url;
    }

    function buildAnimeSeriesUrl(baseUrl, start, count, filterValues = {}) {
        const type = filterValues.anime_type || 'all';
        const region = filterValues.region || 'all';
        const year = filterValues.year || 'all';
        const platform = filterValues.platform || 'all';
        const sort = filterValues.sort || 'recommend';

        const selectedCategories = { '类型': '动画', '形式': '电视剧' };
        const tags = ['动画'];

        // 类型(动漫子类型如国漫等)
        if (type !== 'all') tags.push(type);
        // 地区
        if (region !== 'all') { selectedCategories['地区'] = region; tags.push(region); }
        // 年代
        if (year !== 'all') tags.push(year);
        // 平台
        if (platform !== 'all') tags.push(platform);

        let url = `${baseUrl}${DOUBAN_ENDPOINTS.tv_recommend}?refresh=0&start=${start}&count=${count}&selected_categories=${encodeURIComponent(JSON.stringify(selectedCategories))}&uncollect=false&score_range=0,10&tags=${encodeURIComponent(tags.join(','))}`;
        if (sort !== 'recommend') url += `&sort=${sort}`;
        return url;
    }

    function buildAnimeMovieUrl(baseUrl, start, count, filterValues = {}) {
        const type = filterValues.anime_type || 'all';
        const region = filterValues.region || 'all';
        const year = filterValues.year || 'all';
        const sort = filterValues.sort || 'recommend';

        const selectedCategories = { '类型': '动画' };
        const tags = ['动画'];

        // 类型(动漫子类型)
        if (type !== 'all') tags.push(type);
        // 地区
        if (region !== 'all') { selectedCategories['地区'] = region; tags.push(region); }
        // 年代
        if (year !== 'all') tags.push(year);

        let url = `${baseUrl}${DOUBAN_ENDPOINTS.movie_recommend}?refresh=0&start=${start}&count=${count}&selected_categories=${encodeURIComponent(JSON.stringify(selectedCategories))}&uncollect=false&score_range=0,10&tags=${encodeURIComponent(tags.join(','))}`;
        if (sort !== 'recommend') url += `&sort=${sort}`;
        return url;
    }

    function buildVarietyUrl(baseUrl, start, count, filterValues = {}) {
        const type = filterValues.variety_type || 'all';
        const region = filterValues.region || 'all';
        const year = filterValues.year || 'all';
        const platform = filterValues.platform || 'all';
        const sort = filterValues.sort || 'recommend';

        // 综艺的selected_categories:类型为空或具体类型,形式固定为综艺
        const selectedCategories = { '类型': type !== 'all' ? type : '', '形式': '综艺' };
        const tags = ['综艺'];

        // 类型(综艺不在tags里重复加"综艺",只加具体类型如真人秀)
        if (type !== 'all') tags.push(type);
        // 地区
        if (region !== 'all') { selectedCategories['地区'] = region; tags.push(region); }
        // 年代
        if (year !== 'all') tags.push(year);
        // 平台
        if (platform !== 'all') tags.push(platform);

        let url = `${baseUrl}${DOUBAN_ENDPOINTS.tv_recommend}?refresh=0&start=${start}&count=${count}&selected_categories=${encodeURIComponent(JSON.stringify(selectedCategories))}&uncollect=false&score_range=0,10&tags=${encodeURIComponent(tags.join(','))}`;
        if (sort !== 'recommend') url += `&sort=${sort}`;
        return url;
    }

    async function loadDoubanData(reset = false) {
        if (doubanIsLoading) return;

        doubanIsLoading = true;
        const $results = $(`#douban-results-${doubanCurrentTab}`);
        const tabState = doubanTabState[doubanCurrentTab];

        if (reset) {
            tabState.page = 1;
            tabState.hasMore = true;
            $results.html(`
                <div class="douban-loading">
                    <div class="loading-spinner"></div>
                    <p class="loading-text">正在加载数据...</p>
                </div>
            `);
        } else {
            // 翻页时显示底部加载动画
            $results.find('.douban-load-more').remove();
            $results.append(`
                <div class="douban-load-more">
                    <div class="loading-spinner"></div>
                    <p class="loading-text">加载更多...</p>
                </div>
            `);
        }

        try {
            const url = buildDoubanApiUrl();
            const response = await $.ajax({
                url: url,
                method: 'GET',
                dataType: 'json'
            });

            // 移除加载动画
            $results.find('.douban-loading, .douban-load-more').remove();

            if (reset) {
                // reset时清空内容并移除错误消息
                $results.empty();
            } else {
                // 非reset时只移除错误消息(保留已有数据和容器)
                $results.find('.error-message').remove();
            }

            // 处理返回数据(recommend API返回items,recent_hot API返回subjects)
            // 只保留type是movie或tv的item
            const rawItems = response.items || response.subjects || [];
            const items = rawItems.filter(item => item.type === 'movie' || item.type === 'tv');

            if (items.length > 0) {
                // 获取或创建grid容器(用于加载更多时之前请求失败的情况)
                let $grid = $results.find('.douban-grid');
                if ($grid.length === 0) {
                    $results.append('<div class="douban-grid"></div>');
                    $grid = $results.find('.douban-grid').last();
                }
                // 追加卡片到grid容器
                items.forEach(item => {
                    $grid.append(renderDoubanItem(item));
                });

                tabState.hasMore = true;
            } else {
                tabState.hasMore = false;
                if (reset) {
                    $results.html('<div class="no-content">没有找到相关内容</div>');
                }
            }

            // 保存当前tab的状态(记录筛选配置)
            const filterValues = getCurrentFilterValues(doubanCurrentTab, doubanCurrentCategory);
            doubanTabCache[doubanCurrentTab] = {
                hasMore: tabState.hasMore,
                page: tabState.page,
                loaded: true,
                isPreloaded: false,  // 不是预加载的数据
                config: {
                    category: doubanCurrentCategory,
                    ...filterValues
                }
            };
        } catch (error) {
            console.error('获取豆瓣数据失败:', error);
            $results.find('.douban-loading, .douban-load-more').remove();

            if (reset) {
                $results.html('<div class="error-message">加载数据失败,请检查网络连接或API设置</div>');
            }
            // 添加"加载更多"按钮,阻止自动滚动加载
            $results.append('<button class="douban-load-more-btn">加载更多</button>');
        } finally {
            doubanIsLoading = false;
            doubanDataLoaded = true;
        }
    }

    /**
     * 预加载所有豆瓣tab数据(带每日缓存检查)
     * 注意:此函数只用于预加载图片,不影响实际的数据请求
     */
    async function preloadAllDoubanData() {
        const tabs = ['movie', 'tv', 'anime', 'variety'];
        const defaultConfigs = {
            movie: { category: 'latest', filter: 'all', pageSize: 18 },
            tv: { category: 'hot', filter: 'tv_domestic', pageSize: 18 },
            anime: { category: 'series', region: '中国大陆', sort: 'U', pageSize: 25 },
            variety: { category: 'hot', filter: 'show_domestic', pageSize: 25 }
        };

        // 豆瓣缓存键映射
        const cacheKeys = {
            movie: CONFIG.STORAGE_KEYS.DOUBAN_CACHE_MOVIE,
            tv: CONFIG.STORAGE_KEYS.DOUBAN_CACHE_TV,
            anime: CONFIG.STORAGE_KEYS.DOUBAN_CACHE_ANIME,
            variety: CONFIG.STORAGE_KEYS.DOUBAN_CACHE_VARIETY
        };

        // 检查缓存是否有效(是否是今天)
        const cachedTimestamp = getSetting(CONFIG.STORAGE_KEYS.DOUBAN_CACHE_TIMESTAMP, null);
        const cacheValid = cachedTimestamp && isSameDay(new Date(parseInt(cachedTimestamp)), new Date());

        for (const tab of tabs) {
            // 如果缓存有效,尝试从本地读取并预加载图片
            if (cacheValid) {
                const cachedData = getJsonSetting(cacheKeys[tab], null);
                if (cachedData && cachedData.html) {
                    // 从缓存的HTML中提取图片URL并预加载
                    const imgMatches = cachedData.html.match(/src="([^"]+)"/g) || [];
                    imgMatches.forEach(match => {
                        const imageUrl = match.match(/src="([^"]+)"/)?.[1];
                        if (imageUrl) fetch(imageUrl, { referrerPolicy: 'no-referrer' }).catch(() => {});
                    });
                    continue;
                }
            }

            // 缓存无效或不存在,发起新请求并预加载图片
            try {
                const config = defaultConfigs[tab];
                const url = buildDoubanApiUrlForPreload(tab, config);
                const response = await $.ajax({ url, method: 'GET', dataType: 'json' });
                // 只保留type是movie或tv的item
                const items = (response.items || response.subjects || []).filter(item => item.type === 'movie' || item.type === 'tv');
                const html = items.length > 0
                    ? `<div class="douban-grid">${items.map(item => renderDoubanItem(item)).join('')}</div>`
                    : '<div class="no-content">没有找到相关内容</div>';

                // 保存到本地缓存(标记为预加载,实际使用时会重新请求)
                const cacheData = {
                    hasMore: items.length >= config.pageSize,
                    page: 1,
                    isPreloaded: true,  // 标记为预加载数据
                    html,
                    config: config
                };

                localStorage.setItem(cacheKeys[tab], JSON.stringify(cacheData));

                // 预加载图片
                items.forEach(item => {
                    const imageUrl = getDoubanProxiedImageUrl(item.pic?.large || item.pic?.normal || '');
                    if (imageUrl) fetch(imageUrl, { referrerPolicy: 'no-referrer' }).catch(() => {});
                });
            } catch (e) {
                console.error(`预加载豆瓣${tab}数据失败:`, e);
            }
        }

        // 更新缓存时间戳
        localStorage.setItem(CONFIG.STORAGE_KEYS.DOUBAN_CACHE_TIMESTAMP, new Date().getTime());
    }

    /**
     * 获取当前筛选选项的值
     * @param {string} tab - 当前tab
     * @param {string} category - 当前分类
     * @returns {Object} - 当前筛选选项的值
     */
    function getCurrentFilterValues(tab, category) {
        const filterKey = `${tab}_${category}`;
        const filterConfig = DOUBAN_CATEGORY_FILTERS[filterKey];

        if (!filterConfig) {
            return {};
        }

        if (filterConfig.type === 'capsule') {
            const activeOption = $('.douban-filter .filter-option.active').data('value');
            return { filter: activeOption };
        } else if (filterConfig.type === 'dropdown') {
            const values = {};
            filterConfig.fields.forEach(field => {
                const activeItem = $(`.ph-dd-wrap[data-type="${field}"] .ph-dd-item.active`);
                values[field] = activeItem.data('value') || 'all';
            });
            return values;
        }

        return {};
    }

    /**
     * 检查当前筛选是否是默认配置
     * 只有默认配置才使用预加载的缓存
     */
    function isDefaultFilter(tab, category, filterValues) {
        const defaultConfigs = {
            movie: { category: 'latest', filter: 'all' },
            tv: { category: 'hot', filter: 'tv_domestic' },
            anime: { category: 'series', region: '中国大陆', sort: 'U' },
            variety: { category: 'hot', filter: 'show_domestic' }
        };

        const defaultConfig = defaultConfigs[tab];
        if (!defaultConfig) return false;

        // 检查category是否匹配
        if (category !== defaultConfig.category) return false;

        // 如果filterValues为空,视为默认配置(初始化时DOM未渲染)
        if (!filterValues || Object.keys(filterValues).length === 0) return true;

        // 检查筛选值
        if (tab === 'anime' && category === 'series') {
            // 动漫-番剧:检查region和sort(all视为默认)
            if (filterValues.region && filterValues.region !== 'all' && filterValues.region !== '中国大陆') return false;
            if (filterValues.sort && filterValues.sort !== 'all' && filterValues.sort !== 'U') return false;
        } else {
            // 其他情况检查filter
            if (filterValues.filter !== defaultConfig.filter) return false;
        }

        return true;
    }

    function renderDoubanItem(item) {
        // 兼容不同API返回的数据结构
        // recommend API: pic.large/pic.normal, rating.value
        // recent_hot API: cover.url 或 cover_url, rating.value 或 rate
        let imageUrl = item.pic?.large || item.pic?.normal || item.cover?.url || item.cover_url || '';
        imageUrl = getDoubanProxiedImageUrl(imageUrl);

        const rating = item.rating?.value || item.rate;
        const ratingHtml = rating ? `<div class="douban-rating">${rating}</div>` : '';
        const title = item.title || item.name || '';
        const doubanUrl = `https://movie.douban.com/subject/${item.id}/`;

        return `
            <div class="douban-card" data-id="${item.id}" data-title="${title}" data-douban-url="${doubanUrl}">
                <div class="douban-image-container">
                    <a href="${doubanUrl}" target="_blank" class="douban-link-btn" title="跳转到豆瓣详情页">
                        <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                            <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
                            <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
                        </svg>
                    </a>
                    <img src="${imageUrl}" alt="${title}" class="douban-image" referrerpolicy="no-referrer" loading="lazy" decoding="async" data-nimg="fill">
                    ${ratingHtml}
                </div>
                <div class="douban-title" title="${title}">${title}</div>
                <div class="douban-year">${item.year || ''}</div>
            </div>
        `;
    }

    // =================================================================================
    // 4. 每日放送功能实现 (已优化)
    // =================================================================================

    function renderDailyContent() {
        return `
            <div id="daily-content-container" class="daily-container">
                ${renderLoadingSpinner()}
            </div>
        `;
    }

    function renderLoadingSpinner() {
        return `
            <div class="daily-loading">
                <div class="loading-spinner"></div>
                <p class="loading-text">正在加载每日放送数据...</p>
            </div>
        `;
    }

    /**
     * 检查两个日期是否是同一天
     * @param {Date} d1 - 日期对象1
     * @param {Date} d2 - 日期对象2
     * @returns {boolean}
     */
    function isSameDay(d1, d2) {
        return d1.getFullYear() === d2.getFullYear() &&
            d1.getMonth() === d2.getMonth() &&
            d1.getDate() === d2.getDate();
    }

    /**
     * 此函数现在只负责发起网络请求并缓存结果
     */
    function fetchAndCacheDailyData() {
        return new Promise((resolve, reject) => {
            const dailyApi = CONFIG.DEFAULT_DAILY_API;

            $.ajax({
                url: dailyApi,
                method: 'GET',
                dataType: 'json',
                success: function (data) {
                    // 请求成功后,将数据和当前时间戳存入localStorage
                    localStorage.setItem(CONFIG.STORAGE_KEYS.DAILY_DATA_CACHE, JSON.stringify(data));
                    localStorage.setItem(CONFIG.STORAGE_KEYS.DAILY_DATA_TIMESTAMP, new Date().getTime());
                    resolve(data);
                },
                error: function (xhr, status, error) {
                    console.error('Ajax请求失败:', status, error);
                    reject(new Error(`请求失败: ${status}`));
                }
            });
        });
    }

    /**
     * 获取每日放送数据的主函数,包含缓存逻辑
     * @returns {Promise} - 返回一个解析后为API数据的Promise
     */
    function getDailyData() {
        const cachedData = getJsonSetting(CONFIG.STORAGE_KEYS.DAILY_DATA_CACHE, null);
        const cachedTimestamp = getSetting(CONFIG.STORAGE_KEYS.DAILY_DATA_TIMESTAMP, null);

        // 检查是否存在缓存,并且缓存时间戳是今天
        if (cachedData && cachedTimestamp && isSameDay(new Date(parseInt(cachedTimestamp)), new Date())) {
            // 如果缓存有效,直接返回一个已解析的Promise,值为缓存数据
            return Promise.resolve(cachedData);
        } else {
            // 否则,发起新的网络请求
            return fetchAndCacheDailyData();
        }
    }

    /**
     * 处理和渲染函数,现在接收一个Promise作为参数
     * @param {Promise} dataPromise - 包含每日放送数据的Promise
     */
    async function processAndRenderDailyData(dataPromise) {
        const $container = $('#daily-content-container');

        // 如果容器不存在或已被移除,则中止
        if ($container.length === 0) return;

        // 立即显示加载动画
        $container.html(renderLoadingSpinner());

        let data;
        try {
            // 等待传入的Promise完成
            data = await dataPromise;
        } catch (error) {
            console.error('获取每日放送数据失败:', error);
            $container.html('<div class="error-message">加载数据失败,请检查网络连接或API设置</div>');
            showNotification('获取每日放送数据失败,请检查API设置', 'error');
            return;
        }

        // 调整today的计算方式,使其对应新的星期顺序(星期一为0,星期日为6)
        const today = (new Date().getDay() + 6) % 7;
        const weekTabsHtml = CONFIG.WEEK_DAYS.map((day, index) => {
            const isActive = index === today ? 'active' : '';
            return `<button class="week-tab ${isActive}" data-weekday="${index}">${day}</button>`;
        }).join('');

        const weekContentHtml = CONFIG.WEEK_DAYS.map((_, index) => {
            const isActive = index === today ? 'active' : '';
            return `<div class="week-content ${isActive}" id="weekday-${index}"></div>`;
        }).join('');

        const dailyHtml = `
            <div class="daily-tabs-container">
                <div class="week-tabs">${weekTabsHtml}</div>
                <div class="week-contents">${weekContentHtml}</div>
            </div>
        `;

        $container.html(dailyHtml);

        if (Array.isArray(data)) {
            data.forEach((weekdayData, index) => {
                if (weekdayData && weekdayData.items) {
                    // 直接使用index作为索引
                    renderWeekdayContent(index, weekdayData.items);
                }
            });
        } else if (typeof data === 'object') {
            Object.keys(data).forEach(key => {
                const weekdayIndex = parseInt(key);
                if (!isNaN(weekdayIndex) && weekdayIndex >= 0 && weekdayIndex < 7) {
                    // 直接使用weekdayIndex作为索引
                    renderWeekdayContent(weekdayIndex, data[key]);
                }
            });
        }

        $('.week-tab').on('click', function () {
            const weekday = $(this).data('weekday');
            $(this).addClass('active').siblings().removeClass('active');
            $(`#weekday-${weekday}`).addClass('active').siblings().removeClass('active');
        });

        dailyDataLoaded = true;
    }

    /**
     * 获取Bangumi的自定义跳转URL
     * @param {string} bangumiId - Bangumi的ID
     * @returns {string|null} - 自定义URL或null
     */
    function getBangumiCustomUrl(bangumiId) {
        const customUrls = getJsonSetting(CONFIG.STORAGE_KEYS.BANGUMI_CUSTOM_URL, {});
        const item = customUrls[bangumiId];
        // 兼容旧格式(直接存URL字符串)和新格式(对象)
        if (typeof item === 'string') return item;
        return item?.url || null;
    }

    /**
     * 保存Bangumi的自定义跳转URL
     * @param {string} bangumiId - Bangumi的ID
     * @param {string} url - 自定义URL
     * @param {string} name - 名称(可选)
     */
    function saveBangumiCustomUrl(bangumiId, url, name = '') {
        const customUrls = getJsonSetting(CONFIG.STORAGE_KEYS.BANGUMI_CUSTOM_URL, {});
        if (url) {
            const existing = customUrls[bangumiId];
            customUrls[bangumiId] = {
                url,
                name: name || (typeof existing === 'object' ? existing.name : '') || bangumiId,
                time: Date.now()
            };
        } else {
            delete customUrls[bangumiId];
        }
        localStorage.setItem(CONFIG.STORAGE_KEYS.BANGUMI_CUSTOM_URL, JSON.stringify(customUrls));
    }

    /**
     * 获取豆瓣的自定义跳转URL
     */
    function getDoubanCustomUrl(doubanId) {
        const customUrls = getJsonSetting('douban_id_custom_jump_url', {});
        const key = `douban_${doubanId}`;
        const item = customUrls[key];
        if (typeof item === 'string') return item;
        return item?.url || null;
    }

    /**
     * 保存豆瓣的自定义跳转URL
     */
    function saveDoubanCustomUrl(doubanId, url, name = '') {
        const customUrls = getJsonSetting('douban_id_custom_jump_url', {});
        const key = `douban_${doubanId}`;
        if (url) {
            const existing = customUrls[key];
            customUrls[key] = {
                url,
                name: name || (typeof existing === 'object' ? existing.name : '') || doubanId,
                time: Date.now()
            };
        } else {
            delete customUrls[key];
        }
        localStorage.setItem('douban_id_custom_jump_url', JSON.stringify(customUrls));
    }

    /**
     * 获取豆瓣卡片的每日放送联动设置
     * @param {string} doubanId - 豆瓣ID
     * @returns {object|null} - 联动数据 {weekdays, title, imageUrl} 或 null
     */
    function getDoubanDailyLink(doubanId) {
        const links = getJsonSetting(CONFIG.STORAGE_KEYS.DOUBAN_DAILY_LINK, {});
        const key = `douban_${doubanId}`;
        const data = links[key];
        if (!data) return null;
        // 兼容旧格式(单个weekday)
        if (typeof data.weekday === 'number') {
            return { ...data, weekdays: [data.weekday] };
        }
        return data;
    }

    /**
     * 保存豆瓣卡片的每日放送联动设置
     * @param {string} doubanId - 豆瓣ID
     * @param {Array} weekdays - 星期数组(0-6),空数组表示不联动
     * @param {string} title - 标题
     * @param {string} imageUrl - 图片URL
     */
    function saveDoubanDailyLink(doubanId, weekdays, title = '', imageUrl = '') {
        const links = getJsonSetting(CONFIG.STORAGE_KEYS.DOUBAN_DAILY_LINK, {});
        const key = `douban_${doubanId}`;
        if (!weekdays || weekdays.length === 0) {
            delete links[key];
        } else {
            links[key] = { weekdays, title, imageUrl };
        }
        localStorage.setItem(CONFIG.STORAGE_KEYS.DOUBAN_DAILY_LINK, JSON.stringify(links));
    }

    /**
     * 获取指定星期的所有联动豆瓣卡片
     * @param {number} weekday - 星期几(0-6)
     * @returns {Array} - 联动卡片数组
     */
    function getLinkedDoubanCards(weekday) {
        const links = getJsonSetting(CONFIG.STORAGE_KEYS.DOUBAN_DAILY_LINK, {});
        return Object.entries(links)
            .filter(([_, data]) => {
                // 兼容旧格式
                if (typeof data.weekday === 'number') return data.weekday === weekday;
                return data.weekdays && data.weekdays.includes(weekday);
            })
            .map(([key, data]) => ({ id: key.replace(/^douban_/, ''), ...data }));
    }

    /**
     * 确保URL有协议头
     */
    function ensureProtocol(url) {
        if (!url) return url;
        return /^https?:\/\//i.test(url) ? url : `https://${url}`;
    }

    /**
     * 渲染管理跳转内容
     */
    function renderTagsManageContent() {
        return `
            <div class="tags-manage-container">
                <div class="tags-filter-bar">
                    <div class="custom-select-wrapper ph-select" id="tags-type-filter-wrapper">
                        <div class="custom-select-trigger" tabindex="0"><span class="custom-select-value">全部</span><svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg></div>
                        <div class="custom-select-options">
                            <div class="custom-select-option selected hover:!text-white" data-value="all"><svg class="option-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 13l4 4L19 7"/></svg><span>全部</span></div>
                            <div class="custom-select-option hover:!text-white" data-value="bangumi"><svg class="option-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 13l4 4L19 7"/></svg><span>每日放送</span></div>
                            <div class="custom-select-option hover:!text-white" data-value="douban"><svg class="option-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 13l4 4L19 7"/></svg><span>豆瓣查找</span></div>
                        </div>
                        <input type="hidden" id="tags-type-filter" value="all">
                    </div>
                    <input type="text" id="tags-search-input" class="tags-search" placeholder="搜索名称或URL..." style="border: 1px solid #d1d5db !important; border-color: #d1d5db !important; background: white !important; color: #374151 !important;">
                    <div class="custom-select-wrapper ph-select" id="tags-sort-filter-wrapper">
                        <div class="custom-select-trigger" tabindex="0"><span class="custom-select-value">最新优先</span><svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg></div>
                        <div class="custom-select-options">
                            <div class="custom-select-option selected hover:!text-white" data-value="desc"><svg class="option-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 13l4 4L19 7"/></svg><span>最新优先</span></div>
                            <div class="custom-select-option hover:!text-white" data-value="asc"><svg class="option-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 13l4 4L19 7"/></svg><span>最早优先</span></div>
                        </div>
                        <input type="hidden" id="tags-sort-filter" value="desc">
                    </div>
                </div>
                <div id="tags-list" class="ph-tags-list"></div>
            </div>
        `;
    }

    /**
     * 获取所有自定义URL数据
     */
    function getAllCustomUrls() {
        const bangumiUrls = getJsonSetting(CONFIG.STORAGE_KEYS.BANGUMI_CUSTOM_URL, {});
        const doubanUrls = getJsonSetting('douban_id_custom_jump_url', {});
        const result = [];

        // 处理bangumi数据
        Object.entries(bangumiUrls).forEach(([id, item]) => {
            const url = typeof item === 'string' ? item : item?.url;
            if (url) {
                result.push({
                    type: 'bangumi',
                    id,
                    url,
                    name: (typeof item === 'object' ? item.name : '') || id,
                    time: (typeof item === 'object' ? item.time : 0) || 0
                });
            }
        });

        // 处理douban数据
        Object.entries(doubanUrls).forEach(([key, item]) => {
            const url = typeof item === 'string' ? item : item?.url;
            if (url) {
                const id = key.replace(/^douban_/, '');
                result.push({
                    type: 'douban',
                    id,
                    url,
                    name: (typeof item === 'object' ? item.name : '') || id,
                    time: (typeof item === 'object' ? item.time : 0) || 0
                });
            }
        });

        return result;
    }

    /**
     * 渲染标签列表
     */
    function renderTagsList() {
        const typeFilter = $('#tags-type-filter').val();
        const searchText = $('#tags-search-input').val().toLowerCase();
        const sortOrder = $('#tags-sort-filter').val();

        let items = getAllCustomUrls();

        // 筛选类型
        if (typeFilter !== 'all') {
            items = items.filter(item => item.type === typeFilter);
        }

        // 搜索筛选
        if (searchText) {
            items = items.filter(item =>
                item.name.toLowerCase().includes(searchText) ||
                item.url.toLowerCase().includes(searchText)
            );
        }

        // 排序
        items.sort((a, b) => sortOrder === 'desc' ? b.time - a.time : a.time - b.time);

        const $list = $('#tags-list');
        if (items.length === 0) {
            $list.html('<div class="no-content">暂无自定义URL数据</div>');
            return;
        }

        const html = items.map(item => {
            const timeStr = item.time ? new Date(item.time).toLocaleString() : '未知';
            return `
                <div class="tag-item" data-type="${item.type}" data-id="${item.id}">
                    <span class="tag-label tag-${item.type}">${item.type === 'bangumi' ? '每日放送' : '豆瓣查找'}</span>
                    <span class="tag-name" title="${item.name}">${item.name}</span>
                    <span class="tag-url" title="${item.url}">${item.url}</span>
                    <span class="tag-time">${timeStr}</span>
                    <button class="tag-btn tag-edit" title="编辑">
                        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
                    </button>
                    <button class="tag-btn tag-delete" title="删除">
                        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
                    </button>
                </div>
            `;
        }).join('');

        $list.html(html);
    }

    /**
     * 渲染管理收藏内容
     */
    function renderFavoritesContent() {
        return `
            <div class="favorites-container">
                <div class="favorites-filter-bar">
                    <div class="custom-select-wrapper ph-select" id="favorites-sort-filter-wrapper">
                        <div class="custom-select-trigger" tabindex="0"><span class="custom-select-value">最新优先</span><svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg></div>
                        <div class="custom-select-options">
                            <div class="custom-select-option selected hover:!text-white" data-value="desc"><svg class="option-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 13l4 4L19 7"/></svg><span>最新优先</span></div>
                            <div class="custom-select-option hover:!text-white" data-value="asc"><svg class="option-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 13l4 4L19 7"/></svg><span>最早优先</span></div>
                        </div>
                        <input type="hidden" id="favorites-sort-filter" value="desc">
                    </div>
                    <input type="text" id="favorites-search-input" class="favorites-search" placeholder="搜索名称或URL...">
                    <div><button id="favorites-add-btn" class="favorites-add-btn">+ 新增</button></div>
                </div>
                <div id="favorites-list" class="favorites-list"></div>
            </div>
        `;
    }

    /**
     * 获取所有收藏数据
     */
    function getAllFavorites() {
        return getJsonSetting('user_favorites', []);
    }

    /**
     * 保存收藏数据
     */
    function saveFavorites(favorites) {
        localStorage.setItem('user_favorites', JSON.stringify(favorites));
    }

    /**
     * 渲染收藏列表
     */
    function renderFavoritesList() {
        const searchText = $('#favorites-search-input').val()?.toLowerCase() || '';
        const sortOrder = $('#favorites-sort-filter').val() || 'desc';

        let items = getAllFavorites();

        // 搜索筛选
        if (searchText) {
            items = items.filter(item =>
                item.name.toLowerCase().includes(searchText) ||
                item.url.toLowerCase().includes(searchText)
            );
        }

        // 排序
        items.sort((a, b) => sortOrder === 'desc' ? b.time - a.time : a.time - b.time);

        const $list = $('#favorites-list');
        if (items.length === 0) {
            $list.html('<div class="no-content">暂无收藏数据</div>');
            return;
        }

        const html = items.map(item => {
            const timeStr = item.time ? new Date(item.time).toLocaleString() : '未知';
            return `
                <div class="favorite-item" data-id="${item.id}">
                    <span class="favorite-name" title="${item.name}">${item.name}</span>
                    <span class="favorite-url" title="${item.url}">${item.url}</span>
                    <span class="favorite-time">${timeStr}</span>
                    <button class="favorite-btn favorite-jump" title="跳转">
                        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
                    </button>
                    <button class="favorite-btn favorite-edit" title="编辑">
                        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"/><path d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"/></svg>
                    </button>
                    <button class="favorite-btn favorite-delete" title="删除">
                        <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="3 6 5 6 21 6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6m3 0V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/></svg>
                    </button>
                </div>
            `;
        }).join('');

        $list.html(html);
    }

    function renderWeekdayContent(weekdayIndex, items) {
        const $container = $(`#weekday-${weekdayIndex}`);

        if (!items || items.length === 0) {
            $container.html('<div class="no-content">该日没有放送内容</div>');
        } else {
            const itemsHtml = items.map(item => {
                let imageUrl, name, rating, bangumiId;

                // 处理不同数据结构
                if (item.images && item.images.large) {
                    // BGM.tv API结构
                    imageUrl = item.images.large;
                    name = item.name_cn || item.name;
                    rating = item.rating ? item.rating.score : null;
                    bangumiId = item.id ? `bgm_${item.id}` : null;
                } else if (item.image) {
                    // 其他API结构
                    imageUrl = item.image;
                    name = item.title || item.name;
                    rating = item.score || item.rating;
                    bangumiId = item.id ? `other_${item.id}` : `name_${name}`;
                }

                const ratingHtml = rating ? `<div class="anime-rating">${rating}</div>` : '';

                // 添加data-id属性存储唯一ID,方便后续自定义跳转
                return `
                    <div class="anime-card" data-id="${bangumiId}" data-name="${name}" data-source="bangumi">
                        <div class="anime-image-container">
                            <img src="${imageUrl}" alt="${name}" class="anime-image">
                            ${ratingHtml}
                        </div>
                        <div class="anime-title" title="${name}">${name}</div>
                    </div>
                `;
            }).join('');

            $container.html(`<div class="anime-grid">${itemsHtml}</div>`);
        }

        // 追加联动的豆瓣卡片
        appendLinkedDoubanCards(weekdayIndex);
    }

    /**
     * 追加联动的豆瓣卡片到指定星期
     */
    function appendLinkedDoubanCards(weekdayIndex) {
        const $container = $(`#weekday-${weekdayIndex}`);
        let $grid = $container.find('.anime-grid');

        // 移除已存在的联动卡片
        $grid.find('.anime-card[data-source="douban"]').remove();

        const linkedCards = getLinkedDoubanCards(weekdayIndex);
        if (linkedCards.length === 0) return;

        // 如果没有grid容器,创建一个
        if ($grid.length === 0) {
            $container.html('<div class="anime-grid"></div>');
            $grid = $container.find('.anime-grid');
        }

        // 渲染联动卡片
        const linkedHtml = linkedCards.map(card => `
            <div class="anime-card douban-linked-card" data-id="${card.id}" data-title="${card.title}" data-source="douban" data-douban-url="https://movie.douban.com/subject/${card.id}/">
                <div class="anime-image-container">
                    <a href="https://movie.douban.com/subject/${card.id}/" target="_blank" class="douban-link-btn" title="跳转到豆瓣详情页">
                        <svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
                            <path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/>
                            <path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>
                        </svg>
                    </a>
                    <img src="${card.imageUrl}" alt="${card.title}" class="anime-image" referrerpolicy="no-referrer">
                </div>
                <div class="anime-title" title="${card.title}">${card.title}</div>
            </div>
        `).join('');

        // 根据设置决定追加到首部还是尾部(默认首部)
        const position = localStorage.getItem(CONFIG.STORAGE_KEYS.DAILY_LINK_POSITION);
        if (position === 'tail') {
            $grid.append(linkedHtml);
        } else {
            $grid.prepend(linkedHtml);
        }
    }

    /**
     * 刷新所有星期的联动豆瓣卡片
     */
    function refreshLinkedDoubanCards() {
        for (let i = 0; i < 7; i++) {
            const $container = $(`#weekday-${i}`);
            if ($container.length > 0) {
                appendLinkedDoubanCards(i);
            }
        }
    }

    // =================================================================================
    // 5. 状态与工具函数 (State & Utils)
    // =================================================================================

    const getSetting = (key, defaultValue) => localStorage.getItem(key) || defaultValue;
    const getJsonSetting = (key, defaultValue) => {
        const item = localStorage.getItem(key);
        return item ? JSON.parse(item) : defaultValue;
    };

    /**
     * 获取豆瓣处理后的图片URL
     * @param {string} originalUrl - 原始图片URL
     * @returns {string} - 处理后的图片URL
     */
    function getDoubanProxiedImageUrl(originalUrl) {
        const imageUrl = getSetting(CONFIG.STORAGE_KEYS.DOUBAN_IMAGE_URL, '');
        const mode = getSetting(CONFIG.STORAGE_KEYS.DOUBAN_IMAGE_MODE, 'cdn');
        if (!originalUrl || originalUrl.startsWith('data:')) return originalUrl;

        // 替换为小尺寸图片
        originalUrl = originalUrl.replace('m_ratio_poster', 's_ratio_poster');

        if (!imageUrl) return originalUrl;

        if (mode === 'cdn') {
            // CDN模式:替换原图片的host
            try {
                const url = new URL(originalUrl);
                const cdnUrl = new URL(imageUrl);
                // 使用CDN的协议和host,保留原图片的pathname、search和hash
                return `${cdnUrl.protocol}//${cdnUrl.host}${url.pathname}${url.search}${url.hash}`;
            } catch {
                return originalUrl;
            }
        } else {
            // 代理模式:尾部直接拼接原图片URL
            return imageUrl.replace(/\/+$/, '') + '/' + originalUrl;
        }
    }

    /**
     * 批量加载豆瓣图片(img已设置referrerpolicy="no-referrer")
     */
    function getTabOrder() {
        const savedOrder = getJsonSetting(CONFIG.STORAGE_KEYS.TAB_ORDER, DEFAULT_TABS_ORDER);
        const savedIds = new Set(savedOrder.map(tab => tab.id));
        const defaultTabsToAdd = DEFAULT_TABS_ORDER.filter(tab => !savedIds.has(tab.id));
        return [...savedOrder, ...defaultTabsToAdd];
    }

    function initCustomSelect(wrapperId, inputId, storageKey, defaultValue) {
        const mode = getSetting(storageKey, defaultValue);
        $(`#${inputId}`).val(mode);
        const $wrapper = $(`#${wrapperId}`);
        const $options = $wrapper.find('.custom-select-option');
        const $valueDisplay = $wrapper.find('.custom-select-value');
        $options.removeClass('selected').filter(`[data-value="${mode}"]`).addClass('selected');
        $valueDisplay.text($options.filter('.selected').find('span').text());
        $wrapper.removeClass('open');
        $wrapper.find('.custom-select-trigger').off('click').on('click', function (e) { e.stopPropagation(); $('.custom-select-wrapper').not($wrapper).removeClass('open'); $wrapper.toggleClass('open'); });
        $options.off('click').on('click', function (e) { e.stopPropagation(); const val = $(this).data('value'); $options.removeClass('selected'); $(this).addClass('selected'); $valueDisplay.text($(this).find('span').text()); $(`#${inputId}`).val(val); $wrapper.removeClass('open'); });
    }

    function showNotification(message, icon = 'success') {
        Swal.mixin({
            toast: true,
            position: 'top',
            showConfirmButton: false,
            timer: 2000,
            timerProgressBar: true,
            didOpen: (toast) => {
                toast.addEventListener('mouseenter', Swal.stopTimer);
                toast.addEventListener('mouseleave', Swal.resumeTimer);
            }
        }).fire({ icon, title: message });
    }

    // =================================================================================
    // 6. UI渲染与更新 (UI Rendering & Updates)
    // =================================================================================

    function renderTabs(tabOrder, preserveActiveTab = true, preserveContent = false) {
        const $mainModal = $(CONFIG.SELECTORS.MAIN_MODAL);
        const $tabsHeader = $mainModal.find(CONFIG.SELECTORS.TABS_HEADER);
        const $tabContent = $mainModal.find(CONFIG.SELECTORS.TAB_CONTENT);

        // 记录当前激活的标签页
        const activeTabId = preserveActiveTab ? currentActiveTabId : null;

        // 如果需要保留内容,则只重新排序tabs header
        if (preserveContent) {
            const tabButtonsHtml = tabOrder.map(tab => `
                <button class="tab-button" data-tab="${tab.id}">${TABS_CONFIG[tab.id]?.label || tab.label}</button>
            `).join('');

            $tabsHeader.html(tabButtonsHtml);

            // 恢复或设置激活的标签页
            let $activeTabButton;
            if (activeTabId && $tabsHeader.find(`[data-tab="${activeTabId}"]`).length > 0) {
                $activeTabButton = $tabsHeader.find(`[data-tab="${activeTabId}"]`);
            } else {
                $activeTabButton = $tabsHeader.children().first();
            }

            $activeTabButton.addClass(CONFIG.CLASSES.ACTIVE).siblings().removeClass(CONFIG.CLASSES.ACTIVE);
            $(`#${$activeTabButton.data('tab')}`).addClass(CONFIG.CLASSES.ACTIVE).siblings().removeClass(CONFIG.CLASSES.ACTIVE);

            // 更新当前激活的标签页ID
            currentActiveTabId = $activeTabButton.data('tab');
            return;
        }

        const tabButtonsHtml = tabOrder.map(tab => `
            <button class="tab-button" data-tab="${tab.id}">${TABS_CONFIG[tab.id]?.label || tab.label}</button>
        `).join('');

        const tabPanesHtml = tabOrder.map(tab => `
            <div class="ph-tab-pane" id="${tab.id}">
                ${TABS_CONFIG[tab.id]?.renderContent() || '<div>内容未定义</div>'}
            </div>
        `).join('');

        $tabsHeader.html(tabButtonsHtml);
        $tabContent.html(tabPanesHtml);

        // 恢复或设置激活的标签页
        let $activeTabButton;
        if (activeTabId && $tabsHeader.find(`[data-tab="${activeTabId}"]`).length > 0) {
            $activeTabButton = $tabsHeader.find(`[data-tab="${activeTabId}"]`);
        } else {
            $activeTabButton = $tabsHeader.children().first();
        }

        $activeTabButton.addClass(CONFIG.CLASSES.ACTIVE).siblings().removeClass(CONFIG.CLASSES.ACTIVE);
        $(`#${$activeTabButton.data('tab')}`).addClass(CONFIG.CLASSES.ACTIVE).siblings().removeClass(CONFIG.CLASSES.ACTIVE);

        // 更新当前激活的标签页ID
        currentActiveTabId = $activeTabButton.data('tab');

        // 如果当前激活的标签页是tab1(每日放送),且未加载过数据
        if (currentActiveTabId === 'tab1' && !dailyDataLoaded) {
            processAndRenderDailyData(dailyDataPromise);
        } else if (currentActiveTabId === 'tab2' && !doubanDataLoaded) {
            // 如果当前激活的标签页是tab2(豆瓣查找),且未加载过数据
            setTimeout(() => {
                updateDoubanCategoryOptions();
                updateDoubanFilterOptions();
                // 检查是否是默认筛选条件,只有默认筛选才使用预加载缓存
                const cache = doubanTabCache[doubanCurrentTab];
                const filterValues = getCurrentFilterValues(doubanCurrentTab, doubanCurrentCategory);

                if (cache?.isPreloaded && isDefaultFilter(doubanCurrentTab, doubanCurrentCategory, filterValues)) {
                    $(`#douban-results-${doubanCurrentTab}`).html(cache.html);
                    doubanTabState[doubanCurrentTab].hasMore = cache.hasMore;
                    doubanTabState[doubanCurrentTab].page = cache.page;
                    doubanDataLoaded = true;
                } else {
                    loadDoubanData(true);
                }
            }, 150);
        }
    }

    function applyTabPosition(position) {
        $(CONFIG.SELECTORS.TABS_CONTAINER)
            .removeClass('tabs-top tabs-left tabs-right')
            .addClass(`tabs-${position}`);
    }

    // 创建嵌合到页面头部的图标
    function createEmbeddedButton() {
        // 查找 header 标签
        const $header = $('header');
        if ($header.length === 0) return false;

        // 查找 header 里的 .header-buttons
        const $headerButtons = $header.find('.header-buttons');
        if ($headerButtons.length === 0) return false;

        // 避免重复创建
        if ($('#custom-player-helper').length > 0) return true;

        const embeddedButton = $(
            '<span id="custom-player-helper" class="btn no-text icon btn-flat" title="观影助手">' +
            '<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 d-icon" fill="currentColor" viewBox="0 0 16 16">' +
            '<path d="M8 16c3.314 0 6-2 6-5.5 0-1.5-.5-4-2.5-6 .25 1.5-1.25 2-1.25 2C11 4 9 .5 6 0c.357 2 .5 4-2 6-1.25 1-2 2.729-2 4.5C2 14 4.686 16 8 16Zm0-1c-1.657 0-3-1-3-2.75 0-.75.25-2 1.25-3C6.125 10 7 10.5 7 10.5c-.375-1.25.5-3.25 2-3.5-.179 1-.25 2 1 3 .625.5 1 1.364 1 2.25C11 14 9.657 15 8 15Z"/>' +
            '</svg>' +
            '</span>'
        );

        // 添加到 header 的 .header-buttons 里面
        $headerButtons.prepend(embeddedButton);
        return true;
    }

    // 等待目标元素出现后创建按钮
    function waitForElementAndCreateButton() {
        // 如果已经存在,直接创建
        if (createEmbeddedButton()) return;

        // 使用 MutationObserver 监听 DOM 变化
        const observer = new MutationObserver((mutations, obs) => {
            if (createEmbeddedButton()) {
                obs.disconnect(); // 创建成功后停止监听
            }
        });

        // 开始监听 document.body 的子元素变化
        observer.observe(document.body, {
            childList: true,
            subtree: true
        });

        // 10秒后自动停止监听(避免无限等待)
        setTimeout(() => {
            observer.disconnect();
        }, 10000);
    }

    // 移除嵌合到页面头部的图标
    function removeEmbeddedButton() {
        $('#custom-player-helper').remove();
    }

    // 切换显示模式
    function toggleDisplayMode(showInner) {
        showLogoInner = showInner;
        if (showInner) {
            // 隐藏悬浮按钮,显示嵌合图标
            $(CONFIG.SELECTORS.MAIN_BUTTON).hide();
            removeEmbeddedButton();
            waitForElementAndCreateButton();
        } else {
            // 显示悬浮按钮,移除嵌合图标
            $(CONFIG.SELECTORS.MAIN_BUTTON).show();
            removeEmbeddedButton();
        }
    }

    function createMainButton() {
        const mainButton = $(`
            <button id="${CONFIG.SELECTORS.MAIN_BUTTON.substring(1)}" class="main-button">
                <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 d-icon" fill="currentColor" viewBox="0 0 16 16">
                    <path d="M8 16c3.314 0 6-2 6-5.5 0-1.5-.5-4-2.5-6 .25 1.5-1.25 2-1.25 2C11 4 9 .5 6 0c.357 2 .5 4-2 6-1.25 1-2 2.729-2 4.5C2 14 4.686 16 8 16Zm0-1c-1.657 0-3-1-3-2.75 0-.75.25-2 1.25-3C6.125 10 7 10.5 7 10.5c-.375-1.25.5-3.25 2-3.5-.179 1-.25 2 1 3 .625.5 1 1.364 1 2.25C11 14 9.657 15 8 15Z"/>
                </svg>
            </button>
        `);
        $('body').append(mainButton);

        // 检查是否在可嵌合的网站
        if (canEmbedHosts.includes(window.location.hostname)) {
            // 从 localStorage 读取设置,默认为 true(嵌合图标模式)
            const saved = localStorage.getItem(CONFIG.STORAGE_KEYS.SHOW_LOGO_INNER);
            const savedMode = saved === null ? true : saved === 'true';
            toggleDisplayMode(savedMode);
        }
    }

    function createMainModal() {
        const tabPosition = getSetting(CONFIG.STORAGE_KEYS.TAB_POSITION, 'top');
        const mainModal = $(`
            <div id="${CONFIG.SELECTORS.MAIN_MODAL.substring(1)}" class="${CONFIG.CLASSES.HIDDEN} fixed inset-0 z-[10000] overflow-y-auto">
                <div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
                    <div class="relative transform overflow-hidden rounded-2xl bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 text-left shadow-xl transition-all sm:my-8 w-[80vw] h-[80vh] flex flex-col">
                        <div class="flex-shrink-0 h-12 bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center px-4 rounded-t-2xl">
                            <h3 class="text-lg font-semibold text-gray-900 dark:text-white">助手</h3>
                            <div class="flex space-x-2">
                                <button id="elegant-theme-toggle" class="p-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-800 transition-colors">
                                     <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-600 dark:text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /></svg>
                                </button>
                                <button id="elegant-settings-button" class="p-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-800 transition-colors">
                                    <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-600 dark:text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z" /><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 12a3 3 0 11-6 0 3 3 0 016 0z" /></svg>
                                </button>
                                <button id="elegant-close-modal" class="p-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-800 transition-colors">
                                    <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-600 dark:text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>
                                </button>
                            </div>
                        </div>
                        <div class="flex-grow overflow-hidden">
                            <div class="tabs-container tabs-${tabPosition} h-full">
                                <div class="tabs-header"></div>
                                <div class="tab-content-wrapper">
                                    <div class="tab-content"></div>
                                    <button id="back-to-top" class="back-to-top hidden">
                                        <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 15l7-7 7 7" />
                                        </svg>
                                    </button>
                                </div>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        `);
        $('body').append(mainModal);
        renderTabs(getTabOrder());
    }

    function createSettingsModal() {
        const settingsModal = $(`
            <div id="${CONFIG.SELECTORS.SETTINGS_MODAL.substring(1)}" class="${CONFIG.CLASSES.HIDDEN} fixed inset-0 z-[10000] overflow-y-auto">
                <div class="flex min-h-full items-end justify-center p-4 text-center sm:items-center sm:p-0">
                    <div class="relative transform overflow-hidden rounded-2xl bg-white text-gray-900 dark:bg-gray-900 dark:text-gray-100 text-left shadow-xl transition-all sm:my-8 w-[80vw] h-[80vh] flex flex-col">
                        <div class="flex-shrink-0 h-12 bg-white dark:bg-gray-900 border-b border-gray-200 dark:border-gray-700 flex justify-between items-center px-4 rounded-t-2xl">
                            <h3 class="text-lg font-semibold text-gray-900 dark:text-white">设置</h3>
                            <button id="elegant-close-settings" class="p-1 rounded-full hover:bg-gray-200 dark:hover:bg-gray-800 transition-colors">
                                <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 text-gray-600 dark:text-gray-300" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /></svg>
                            </button>
                        </div>
                        <div class="flex-grow overflow-hidden flex">
                            <div class="settings-tabs-nav w-32 bg-white dark:bg-gray-900 p-2 border-r border-gray-200 dark:border-gray-700">
                                <div class="tabs-nav-container h-full flex items-center"><div class="tabs-nav-list w-full">
                                    <button class="settings-tab-button active" data-settings-tab="api">API相关</button>
                                    <button class="settings-tab-button" data-settings-tab="tabs">Tab相关</button>
                                </div></div>
                            </div>
                            <div class="settings-tab-content flex-1 overflow-hidden">
                                <div class="settings-content-container h-full flex items-center justify-center">
                                    <div class="settings-ph-tab-pane active" id="api-settings">
                                        <div class="settings-form-centered">
                                            <div class="form-row"><label class="form-label"><div class="custom-select-wrapper" id="douban-proxy-mode-wrapper"><div class="custom-select-trigger" tabindex="0"><span class="custom-select-value">豆瓣接口CDN</span></div><div class="custom-select-options"><div class="custom-select-option selected hover:!text-white" data-value="cdn"><svg class="option-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 13l4 4L19 7"/></svg><span>豆瓣接口CDN</span></div><div class="custom-select-option hover:!text-white" data-value="proxy"><svg class="option-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 13l4 4L19 7"/></svg><span>豆瓣API代理</span></div></div><input type="hidden" id="douban-proxy-mode" value="cdn"></div></label><input type="text" class="form-input text-input" data-key="${CONFIG.STORAGE_KEYS.DOUBAN_PROXY}" placeholder="CDN替换host,代理直接拼接" style="padding: 10px 14px; border: 1px solid #d1d5db; border-radius: 8px; font-size: 14px; background-color: #f9fafb; color: #111827; outline: none;"></div>
                                            <div class="form-row"><label class="form-label"><div class="custom-select-wrapper" id="douban-image-mode-wrapper"><div class="custom-select-trigger" tabindex="0"><span class="custom-select-value">豆瓣图片CDN</span></div><div class="custom-select-options"><div class="custom-select-option selected hover:!text-white" data-value="cdn"><svg class="option-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 13l4 4L19 7"/></svg><span>豆瓣图片CDN</span></div><div class="custom-select-option hover:!text-white" data-value="proxy"><svg class="option-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 13l4 4L19 7"/></svg><span>豆瓣图片代理</span></div></div><input type="hidden" id="douban-image-mode" value="cdn"></div></label><input type="text" class="form-input text-input" data-key="${CONFIG.STORAGE_KEYS.DOUBAN_IMAGE_URL}" placeholder="CDN替换host,代理直接拼接" style="padding: 10px 14px; border: 1px solid #d1d5db; border-radius: 8px; font-size: 14px; background-color: #f9fafb; color: #111827; outline: none;"></div>
                                            <div class="form-row"><label class="form-label">点击跳转URL</label><input type="text" class="form-input text-input" data-key="${CONFIG.STORAGE_KEYS.REDIRECT_URL}" placeholder="卡片跳转的全局URL" style="padding: 10px 14px; border: 1px solid #d1d5db; border-radius: 8px; font-size: 14px; background-color: #f9fafb; color: #111827; outline: none;"></div>
                                            <div class="form-row" style="justify-content: center;"><div class="logo-mode-switch"><span class="logo-mode-text-left">每日放送的追加卡片在首部</span><label class="logo-mode-toggle"><input type="checkbox" id="daily-link-position-toggle" data-key="${CONFIG.STORAGE_KEYS.DAILY_LINK_POSITION}"><span class="logo-mode-slider"></span></label><span class="logo-mode-text-right">每日放送的追加卡片在尾部</span></div></div>
                                            <div class="form-row logo-mode-row ${canEmbedHosts.includes(window.location.hostname) ? '' : 'hidden'}" style="justify-content: center;"><div class="logo-mode-switch"><span class="logo-mode-text-left">显示为页面右上角嵌合图标</span><label class="logo-mode-toggle"><input type="checkbox" id="logo-mode-toggle" data-key="${CONFIG.STORAGE_KEYS.SHOW_LOGO_INNER}"><span class="logo-mode-slider"></span></label><span class="logo-mode-text-right">显示为页面右上角悬浮按钮</span></div></div>
                                        </div>
                                    </div>
                                    <div class="settings-ph-tab-pane" id="tabs-settings">
                                        <div class="settings-form-centered">
                                            <div class="form-row"><label class="form-label">Tab位置</label><div class="custom-select-wrapper ph-select ph-select-fixed" id="tab-position-wrapper"><div class="custom-select-trigger" tabindex="0"><span class="custom-select-value">上方</span><svg class="w-4 h-4 ml-1" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 9l-7 7-7-7"/></svg></div><div class="custom-select-options"><div class="custom-select-option selected hover:!text-white" data-value="top"><svg class="option-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 13l4 4L19 7"/></svg><span>上方</span></div><div class="custom-select-option hover:!text-white" data-value="left"><svg class="option-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 13l4 4L19 7"/></svg><span>左侧</span></div><div class="custom-select-option hover:!text-white" data-value="right"><svg class="option-check" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 13l4 4L19 7"/></svg><span>右侧</span></div></div><input type="hidden" id="elegant-tab-position" value="top"></div></div>
                                            <div class="form-row"><label class="form-label">Tab顺序</label><div class="tab-order-container"><div id="tab-sort-list" class="tab-sort-list"></div><p class="tab-order-hint">拖拽项目可以调整Tab顺序</p></div></div>
                                        </div>
                                    </div>
                                </div>
                            </div>
                        </div>
                        <div class="flex-shrink-0 h-16 bg-white dark:bg-gray-900 border-t border-gray-200 dark:border-gray-700 flex justify-center items-center px-4">
                            <div class="flex space-x-4">
                                <button id="elegant-cancel-settings" class="cancel-btn">取消</button>
                                <button id="elegant-save-settings" class="save-btn">保存设置</button>
                            </div>
                        </div>
                    </div>
                </div>
            </div>
        `);
        $('body').append(settingsModal);
    }

    function createContextMenu() {
        const weekCheckboxes = CONFIG.WEEK_DAYS.map((day, i) => `
            <label class="daily-link-item" data-weekday="${i}">
                <span class="daily-link-check"></span>
                <span class="daily-link-text">${day}</span>
            </label>
        `).join('');
        const contextMenu = $(`
            <div id="${CONFIG.SELECTORS.CONTEXT_MENU.substring(1)}" class="${CONFIG.CLASSES.HIDDEN} context-menu">
                <div class="context-menu-header">
                    <h3>自定义跳转URL</h3>
                    <button id="context-menu-close" class="context-menu-close">
                        <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
                            <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
                        </svg>
                    </button>
                </div>
                <div class="context-menu-content">
                    <div class="form-row">
                        <label class="form-label">剧集名称</label>
                        <input type="text" id="context-menu-anime-name" class="form-input text-input" readonly>
                    </div>
                    <div class="form-row">
                        <label class="form-label">跳转URL</label>
                        <input type="text" id="context-menu-custom-url" class="form-input text-input" placeholder="为空即使用全局的URL">
                    </div>
                    <div class="form-row douban-only-row" style="display:none;">
                        <label class="form-label">每日放送</label>
                        <div id="context-menu-daily-link" class="daily-link-list">
                            ${weekCheckboxes}
                        </div>
                    </div>
                </div>
                <div class="context-menu-footer">
                    <button id="context-menu-save" class="save-btn">保存</button>
                    <button id="context-menu-reset" class="cancel-btn">重置</button>
                </div>
            </div>
        `);
        $('body').append(contextMenu);
    }

    function initializeSettingsValues() {
        // API inputs
        $('#api-settings .text-input').each(function () {
            const key = $(this).data('key');
            const value = getSetting(key, '');
            $(this).val(value);

            // 记录每日放送API的原始值
            if (key === CONFIG.STORAGE_KEYS.DAILY_API) {
                originalDailyApiValue = value;
            }
        });

        // 设置输入框的暗夜模式内联样式
        const isDark = $('html').hasClass('dark');
        const inputStyle = isDark
            ? 'padding: 10px 14px; border: 1px solid #4b5563; border-radius: 8px; font-size: 14px; color: #e5e7eb !important; outline: none;'
            : 'padding: 10px 14px; border: 1px solid #d1d5db; border-radius: 8px; font-size: 14px; color: #374151 !important; outline: none;';
        $('#api-settings .text-input').attr('style', inputStyle);
        $('#tags-search-input, #favorites-search-input').attr('style', inputStyle);

        // Tab Position - 自定义下拉框
        const tabPosition = getSetting(CONFIG.STORAGE_KEYS.TAB_POSITION, 'top');
        $('#elegant-tab-position').val(tabPosition);
        initCustomSelect('tab-position-wrapper', 'elegant-tab-position', CONFIG.STORAGE_KEYS.TAB_POSITION, 'top');

        // 豆瓣接口模式 - 自定义下拉框
        initCustomSelect('douban-proxy-mode-wrapper', 'douban-proxy-mode', CONFIG.STORAGE_KEYS.DOUBAN_PROXY_MODE, 'cdn');

        // 豆瓣图片模式 - 自定义下拉框
        initCustomSelect('douban-image-mode-wrapper', 'douban-image-mode', CONFIG.STORAGE_KEYS.DOUBAN_IMAGE_MODE, 'cdn');

        // Logo 显示模式 - 开关(unchecked=嵌合图标,checked=悬浮按钮)
        const savedLogo = localStorage.getItem(CONFIG.STORAGE_KEYS.SHOW_LOGO_INNER);
        const logoMode = savedLogo === null ? true : savedLogo === 'true';
        $('#logo-mode-toggle').prop('checked', !logoMode);

        // 联动卡片位置 - 开关(unchecked=首部,checked=尾部)
        const savedPosition = localStorage.getItem(CONFIG.STORAGE_KEYS.DAILY_LINK_POSITION);
        $('#daily-link-position-toggle').prop('checked', savedPosition === 'tail');

        // Tab Order List
        const tabOrder = getTabOrder();
        const $tabListContainer = $('#tab-sort-list').empty();
        tabOrder.forEach(tab => {
            $tabListContainer.append(`
                <div class="tab-sort-item" data-id="${tab.id}">
                    <div class="tab-drag-handle">
                        <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 8h16M4 16h16" /></svg>
                    </div>
                    <div class="tab-label">${TABS_CONFIG[tab.id]?.label || tab.label}</div>
                </div>
            `);
        });

        if (window.Sortable) {
            new Sortable($tabListContainer[0], {
                animation: 150,
                ghostClass: 'tab-sort-ghost',
                handle: '.tab-drag-handle',
            });
        }
    }

    // =================================================================================
    // 7. 事件处理 (Event Handlers)
    // =================================================================================

    function bindEvents() {
        const $body = $('body');
        $body.on('click', CONFIG.SELECTORS.MAIN_BUTTON, () => openModal(CONFIG.SELECTORS.MAIN_MODAL));
        // 嵌合图标按钮的点击事件
        $body.on('click', '#custom-player-helper', () => openModal(CONFIG.SELECTORS.MAIN_MODAL));
        $body.on('click', CONFIG.SELECTORS.OVERLAY, closeAllModals);
        $(document).on('keydown', e => { if (e.key === 'Escape') closeAllModals(); });

        const $mainModal = $(CONFIG.SELECTORS.MAIN_MODAL);
        $mainModal.on('click', '#elegant-close-modal', closeAllModals);
        $mainModal.on('click', '#elegant-settings-button', () => switchModals(CONFIG.SELECTORS.MAIN_MODAL, CONFIG.SELECTORS.SETTINGS_MODAL));
        $mainModal.on('click', '#elegant-theme-toggle', toggleTheme);

        $mainModal.on('click', '.tab-button', function () {
            const $this = $(this);
            const tabId = $this.data('tab');
            currentActiveTabId = tabId; // 更新当前激活的标签页ID

            $this.addClass(CONFIG.CLASSES.ACTIVE).siblings().removeClass(CONFIG.CLASSES.ACTIVE);
            $(`#${tabId}`).addClass(CONFIG.CLASSES.ACTIVE).siblings().removeClass(CONFIG.CLASSES.ACTIVE);

            // 仅在未加载过数据时才请求
            if (tabId === 'tab1' && !dailyDataLoaded) {
                processAndRenderDailyData(dailyDataPromise);
            } else if (tabId === 'tab2' && !doubanDataLoaded) {
                setTimeout(() => {
                    updateDoubanCategoryOptions();
                    updateDoubanFilterOptions();
                    // 从localStorage读取预加载缓存
                    const cacheKeys = {
                        movie: CONFIG.STORAGE_KEYS.DOUBAN_CACHE_MOVIE,
                        tv: CONFIG.STORAGE_KEYS.DOUBAN_CACHE_TV,
                        anime: CONFIG.STORAGE_KEYS.DOUBAN_CACHE_ANIME,
                        variety: CONFIG.STORAGE_KEYS.DOUBAN_CACHE_VARIETY
                    };
                    const cachedData = getJsonSetting(cacheKeys[doubanCurrentTab], null);
                    const filterValues = getCurrentFilterValues(doubanCurrentTab, doubanCurrentCategory);

                    if (cachedData?.html && isDefaultFilter(doubanCurrentTab, doubanCurrentCategory, filterValues)) {
                        $(`#douban-results-${doubanCurrentTab}`).html(cachedData.html);
                        doubanTabState[doubanCurrentTab].hasMore = cachedData.hasMore;
                        doubanTabState[doubanCurrentTab].page = cachedData.page || 1;
                        doubanDataLoaded = true;
                    } else {
                        loadDoubanData(true);
                    }
                }, 100);
            } else if (tabId === 'tab3') {
                renderTagsList();
            } else if (tabId === 'tab4') {
                renderFavoritesList();
            }
        });

        // 管理跳转事件 - 自定义下拉框
        $mainModal.on('click', '#tags-type-filter-wrapper .custom-select-trigger, #tags-sort-filter-wrapper .custom-select-trigger', function(e) {
            e.stopPropagation();
            const $wrapper = $(this).closest('.custom-select-wrapper');
            $('.custom-select-wrapper').not($wrapper).removeClass('open');
            $wrapper.toggleClass('open');
        });
        $mainModal.on('click', '#tags-type-filter-wrapper .custom-select-option, #tags-sort-filter-wrapper .custom-select-option', function(e) {
            e.stopPropagation();
            const $wrapper = $(this).closest('.custom-select-wrapper');
            const val = $(this).data('value');
            $wrapper.find('.custom-select-option').removeClass('selected');
            $(this).addClass('selected');
            $wrapper.find('.custom-select-value').text($(this).find('span').text());
            $wrapper.find('input[type="hidden"]').val(val);
            $wrapper.removeClass('open');
            renderTagsList();
        });
        $mainModal.on('input', '#tags-search-input', renderTagsList);

        $mainModal.on('click', '.tag-delete', function () {
            const $item = $(this).closest('.tag-item');
            const type = $item.data('type');
            const id = $item.data('id');

            if (type === 'douban') {
                saveDoubanCustomUrl(id, '');
            } else {
                saveBangumiCustomUrl(id, '');
            }
            showNotification('已删除');
            renderTagsList();
        });

        $mainModal.on('click', '.tag-edit', function () {
            const $item = $(this).closest('.tag-item');
            const type = $item.data('type');
            const id = $item.data('id');
            const name = $item.find('.tag-name').text();
            const url = $item.find('.tag-url').text();

            Swal.fire({
                title: '编辑自定义URL',
                html: `
                    <div style="margin-bottom:12px;color:#666;font-size:14px;">名称:${name}</div>
                    <input id="swal-url" class="swal2-input" placeholder="请输入URL" value="${url}">
                `,
                showCancelButton: true,
                confirmButtonText: '保存',
                cancelButtonText: '取消',
                preConfirm: () => {
                    const value = $('#swal-url').val().trim();
                    if (!value) { Swal.showValidationMessage('请输入URL'); return false; }
                    return value;
                }
            }).then(result => {
                if (result.isConfirmed) {
                    if (type === 'douban') {
                        saveDoubanCustomUrl(id, result.value, name);
                    } else {
                        saveBangumiCustomUrl(id, result.value, name);
                    }
                    showNotification('已保存');
                    renderTagsList();
                }
            });
        });

        // 管理收藏事件 - 自定义下拉框
        $mainModal.on('click', '#favorites-sort-filter-wrapper .custom-select-trigger', function(e) {
            e.stopPropagation();
            const $wrapper = $(this).closest('.custom-select-wrapper');
            $('.custom-select-wrapper').not($wrapper).removeClass('open');
            $wrapper.toggleClass('open');
        });
        $mainModal.on('click', '#favorites-sort-filter-wrapper .custom-select-option', function(e) {
            e.stopPropagation();
            const $wrapper = $(this).closest('.custom-select-wrapper');
            const val = $(this).data('value');
            $wrapper.find('.custom-select-option').removeClass('selected');
            $(this).addClass('selected');
            $wrapper.find('.custom-select-value').text($(this).find('span').text());
            $wrapper.find('input[type="hidden"]').val(val);
            $wrapper.removeClass('open');
            renderFavoritesList();
        });
        $mainModal.on('input', '#favorites-search-input', renderFavoritesList);

        $mainModal.on('click', '#favorites-add-btn', function () {
            Swal.fire({
                title: '新增收藏',
                html: `
                    <input id="swal-fav-name" class="swal2-input" placeholder="名称">
                    <input id="swal-fav-url" class="swal2-input" placeholder="URL">
                `,
                showCancelButton: true,
                confirmButtonText: '保存',
                cancelButtonText: '取消',
                preConfirm: () => {
                    const name = $('#swal-fav-name').val().trim();
                    const url = $('#swal-fav-url').val().trim();
                    if (!name || !url) { Swal.showValidationMessage('请填写名称和URL'); return false; }
                    return { name, url };
                }
            }).then(result => {
                if (result.isConfirmed) {
                    const favorites = getAllFavorites();
                    favorites.push({ id: Date.now().toString(), name: result.value.name, url: result.value.url, time: Date.now() });
                    saveFavorites(favorites);
                    showNotification('已添加');
                    renderFavoritesList();
                }
            });
        });

        $mainModal.on('click', '.favorite-jump', function () {
            const id = $(this).closest('.favorite-item').data('id');
            const item = getAllFavorites().find(f => f.id == id);
            if (item) window.open(ensureProtocol(item.url), '_blank');
        });

        $mainModal.on('click', '.favorite-edit', function () {
            const id = $(this).closest('.favorite-item').data('id');
            const favorites = getAllFavorites();
            const item = favorites.find(f => f.id == id);
            if (!item) return;

            Swal.fire({
                title: '编辑收藏',
                html: `
                    <input id="swal-fav-name" class="swal2-input" placeholder="名称" value="${item.name}">
                    <input id="swal-fav-url" class="swal2-input" placeholder="URL" value="${item.url}">
                `,
                showCancelButton: true,
                confirmButtonText: '保存',
                cancelButtonText: '取消',
                preConfirm: () => {
                    const name = $('#swal-fav-name').val().trim();
                    const url = $('#swal-fav-url').val().trim();
                    if (!name || !url) { Swal.showValidationMessage('请填写名称和URL'); return false; }
                    return { name, url };
                }
            }).then(result => {
                if (result.isConfirmed) {
                    item.name = result.value.name;
                    item.url = result.value.url;
                    item.time = Date.now();
                    saveFavorites(favorites);
                    showNotification('已保存');
                    renderFavoritesList();
                }
            });
        });

        $mainModal.on('click', '.favorite-delete', function () {
            const id = $(this).closest('.favorite-item').data('id');
            let favorites = getAllFavorites();
            favorites = favorites.filter(f => f.id != id);
            saveFavorites(favorites);
            showNotification('已删除');
            renderFavoritesList();
        });

        // 豆瓣查找相关事件
        $mainModal.on('click', '.douban-tab', function () {
            const $this = $(this);
            const tab = $this.data('tab');
            if (tab === doubanCurrentTab) return;

            doubanCurrentTab = tab;
            $this.addClass(CONFIG.CLASSES.ACTIVE).siblings().removeClass(CONFIG.CLASSES.ACTIVE);

            // CSS切换显示/隐藏
            $('.douban-results').removeClass('active');
            $(`#douban-results-${tab}`).addClass('active');

            // 更新分类和筛选选项(会重置为默认状态)
            updateDoubanCategoryOptions();
            updateDoubanFilterOptions();

            // 检查结果容器是否已有内容(之前加载过)
            const $results = $(`#douban-results-${tab}`);
            if ($results.find('.douban-grid').length > 0) {
                return;
            }

            // 从localStorage读取预加载的默认数据
            const cacheKeys = {
                movie: CONFIG.STORAGE_KEYS.DOUBAN_CACHE_MOVIE,
                tv: CONFIG.STORAGE_KEYS.DOUBAN_CACHE_TV,
                anime: CONFIG.STORAGE_KEYS.DOUBAN_CACHE_ANIME,
                variety: CONFIG.STORAGE_KEYS.DOUBAN_CACHE_VARIETY
            };
            const cachedData = getJsonSetting(cacheKeys[tab], null);

            if (cachedData?.html) {
                $results.html(cachedData.html);
                doubanTabState[tab].hasMore = cachedData.hasMore !== false;
                doubanTabState[tab].page = cachedData.page || 1;
            } else {
                loadDoubanData(true);
            }
        });

        $mainModal.on('click', '.douban-category .filter-option', function () {
            const $this = $(this);
            const value = $this.data('value');

            $this.addClass(CONFIG.CLASSES.ACTIVE).siblings().removeClass(CONFIG.CLASSES.ACTIVE);
            doubanCurrentCategory = value;

            // 更新筛选选项
            updateDoubanFilterOptions();

            // 重新加载数据
            loadDoubanData(true);
        });

        $mainModal.on('click', '.douban-filter .filter-option', function () {
            const $this = $(this);
            const value = $this.data('value');

            $this.addClass(CONFIG.CLASSES.ACTIVE).siblings().removeClass(CONFIG.CLASSES.ACTIVE);

            // 重新加载数据
            loadDoubanData(true);
        });

        // 下拉菜单事件
        $mainModal.on('click', '.ph-dd-toggle', function (e) {
            e.preventDefault();
            e.stopPropagation();

            const $this = $(this);
            const $menu = $this.next('.ph-dd-menu');

            // 关闭其他下拉菜单
            $('.ph-dd-menu').not($menu).removeClass('show');

            // 切换当前下拉菜单
            $menu.toggleClass('show');
        });

        $mainModal.on('click', '.ph-dd-item', function (e) {
            e.preventDefault();

            const $this = $(this);
            const $toggle = $this.closest('.ph-dd-wrap').find('.ph-dd-toggle');
            const type = $toggle.closest('.ph-dd-wrap').data('type');
            const value = $this.data('value');

            // 更新按钮文本(全部/综合排序时显示筛选名称)
            const fieldNames = { type: '类型', tv_type: '类型', anime_type: '类型', variety_type: '类型', region: '地区', year: '年代', platform: '平台', sort: '排序' };
            const displayText = (value === 'all' || value === 'recommend') ? fieldNames[type] : $this.text();
            $toggle.find('.ph-dd-text').text(displayText);

            // 更新选中状态
            $this.addClass(CONFIG.CLASSES.ACTIVE).siblings().removeClass(CONFIG.CLASSES.ACTIVE);

            // 重新加载数据
            loadDoubanData(true);
        });

        // 点击其他地方关闭下拉菜单
        $(document).on('click', function () {
            $('.ph-dd-menu').removeClass('show');
            $('.custom-select-wrapper').removeClass('open');
        });

        // 豆瓣查找滚动加载(绑定到.tab-content)
        const $tabContent = $mainModal.find('.tab-content');
        const $backToTop = $mainModal.find('#back-to-top');

        $tabContent.on('scroll', function () {
            // 显示/隐藏返回顶部按钮
            $backToTop.toggleClass('hidden', this.scrollTop < 300);

            // 豆瓣查找滚动加载
            if (currentActiveTabId !== 'tab2' || doubanIsLoading) return;
            const tabState = doubanTabState[doubanCurrentTab];
            if (!tabState.hasMore) return;
            // 只检查当前激活tab的加载更多按钮
            if ($(`#douban-results-${doubanCurrentTab} .douban-load-more-btn`).length > 0) return;

            if (this.scrollTop + this.clientHeight >= this.scrollHeight - 150) {
                tabState.page++;
                loadDoubanData(false);
            }
        });

        // 加载更多按钮点击事件
        $mainModal.on('click', '.douban-load-more-btn', function () {
            // 检查是否已经有数据(有.douban-grid容器)
            const $results = $(this).closest('.douban-results');
            const hasExistingData = $results.find('.douban-grid').length > 0;

            $(this).remove();

            // 如果已有数据,则翻页加载更多;如果没有数据(重试失败请求),不增加页码
            if (hasExistingData) {
                doubanTabState[doubanCurrentTab].page++;
            }

            loadDoubanData(false);
        });

        // 返回顶部按钮点击事件
        $backToTop.on('click', function () {
            $tabContent.animate({ scrollTop: 0 }, 300);
        });

        const $settingsModal = $(CONFIG.SELECTORS.SETTINGS_MODAL);
        $settingsModal.on('click', '#elegant-close-settings, #elegant-cancel-settings', () => switchModals(CONFIG.SELECTORS.SETTINGS_MODAL, CONFIG.SELECTORS.MAIN_MODAL));
        $settingsModal.on('click', '#elegant-save-settings', handleSaveSettings);
        $settingsModal.on('click', '.settings-tab-button', function () {
            const $this = $(this);
            const tabId = $this.data('settingsTab');
            $this.addClass(CONFIG.CLASSES.ACTIVE).siblings().removeClass(CONFIG.CLASSES.ACTIVE);
            $(`#${tabId}-settings`).addClass(CONFIG.CLASSES.ACTIVE).siblings().removeClass(CONFIG.CLASSES.ACTIVE);
        });

        // 右键菜单事件(每日放送和豆瓣查找卡片共用)
        $body.on('contextmenu', '.anime-card, .douban-card', function (e) {
            e.preventDefault(); // 阻止默认右键菜单

            const $card = $(this);
            const isDouban = $card.hasClass('douban-card');
            const isLinkedDouban = $card.data('source') === 'douban'; // 在每日放送中显示的联动豆瓣卡片
            const animeName = isDouban || isLinkedDouban ? $card.data('title') : $card.data('name');
            const cardId = $card.data('id');
            const imageUrl = $card.find('img').attr('src') || '';

            // 设置当前右键点击的卡片ID和类型
            currentRightClickedCardId = cardId;
            currentRightClickedCardType = (isDouban || isLinkedDouban) ? 'douban' : 'bangumi';

            // 获取已保存的自定义URL(根据类型使用不同存储),没有则显示全局URL
            let customUrl = (isDouban || isLinkedDouban) ? getDoubanCustomUrl(cardId) : getBangumiCustomUrl(cardId);
            if (!customUrl) {
                customUrl = getSetting(CONFIG.STORAGE_KEYS.REDIRECT_URL, '');
            }

            // 填充右键菜单数据
            $('#context-menu-anime-name').val(animeName);
            $('#context-menu-custom-url').val(customUrl || '');

            // 豆瓣卡片显示联动设置行
            const $dailyLinkRow = $('.douban-only-row');
            if (isDouban || isLinkedDouban) {
                $dailyLinkRow.show();
                const linkData = getDoubanDailyLink(cardId);
                const selectedWeekdays = linkData ? linkData.weekdays : [];
                // 更新多选状态
                $('#context-menu-daily-link .daily-link-item').each(function() {
                    const weekday = parseInt($(this).data('weekday'));
                    $(this).toggleClass('checked', selectedWeekdays.includes(weekday));
                });
                // 存储图片URL供保存时使用
                $('#context-menu-daily-link').data('imageUrl', imageUrl);
            } else {
                $dailyLinkRow.hide();
            }

            // 显示右键菜单
            const $contextMenu = $(CONFIG.SELECTORS.CONTEXT_MENU);
            $contextMenu.removeClass(CONFIG.CLASSES.HIDDEN);

            // 定位右键菜单
            const menuWidth = $contextMenu.outerWidth();
            const menuHeight = $contextMenu.outerHeight();
            let left = e.clientX;
            let top = e.clientY;

            // 确保菜单不超出视窗
            if (left + menuWidth > window.innerWidth) {
                left = window.innerWidth - menuWidth;
            }
            if (top + menuHeight > window.innerHeight) {
                top = window.innerHeight - menuHeight;
            }

            $contextMenu.css({
                left: `${left}px`,
                top: `${top}px`
            });
        });

        // 点击其他地方关闭右键菜单
        $(document).on('click', function (e) {
            if (!$(e.target).closest(CONFIG.SELECTORS.CONTEXT_MENU).length &&
                !$(e.target).hasClass('anime-card') &&
                !$(e.target).hasClass('douban-card')) {
                $(CONFIG.SELECTORS.CONTEXT_MENU).addClass(CONFIG.CLASSES.HIDDEN);
            }
        });

        // 右键菜单内部按钮事件
        $(CONFIG.SELECTORS.CONTEXT_MENU).on('click', '#context-menu-close', function () {
            $(CONFIG.SELECTORS.CONTEXT_MENU).addClass(CONFIG.CLASSES.HIDDEN);
        });

        // 多选项点击切换
        $(CONFIG.SELECTORS.CONTEXT_MENU).on('click', '.daily-link-item', function () {
            $(this).toggleClass('checked');
        });

        $(CONFIG.SELECTORS.CONTEXT_MENU).on('click', '#context-menu-save', function () {
            if (!currentRightClickedCardId) return;

            const customUrl = $('#context-menu-custom-url').val().trim();
            const name = $('#context-menu-anime-name').val().trim();
            if (currentRightClickedCardType === 'douban') {
                saveDoubanCustomUrl(currentRightClickedCardId, customUrl, name);
                // 保存联动设置(多选)
                const weekdays = [];
                $('#context-menu-daily-link .daily-link-item.checked').each(function() {
                    weekdays.push(parseInt($(this).data('weekday')));
                });
                const imageUrl = $('#context-menu-daily-link').data('imageUrl') || '';
                saveDoubanDailyLink(currentRightClickedCardId, weekdays, name, imageUrl);
                // 刷新每日放送显示
                if (dailyDataLoaded) {
                    refreshLinkedDoubanCards();
                }
            } else {
                saveBangumiCustomUrl(currentRightClickedCardId, customUrl, name);
            }
            showNotification('设置已保存');
            $(CONFIG.SELECTORS.CONTEXT_MENU).addClass(CONFIG.CLASSES.HIDDEN);
        });

        $(CONFIG.SELECTORS.CONTEXT_MENU).on('click', '#context-menu-reset', function () {
            if (!currentRightClickedCardId) return;

            $('#context-menu-custom-url').val('');
            if (currentRightClickedCardType === 'douban') {
                saveDoubanCustomUrl(currentRightClickedCardId, '');
                // 重置联动设置
                $('#context-menu-daily-link .daily-link-item').removeClass('checked');
                saveDoubanDailyLink(currentRightClickedCardId, []);
                if (dailyDataLoaded) {
                    refreshLinkedDoubanCards();
                }
            } else {
                saveBangumiCustomUrl(currentRightClickedCardId, '');
            }
            showNotification('设置已重置');
            $(CONFIG.SELECTORS.CONTEXT_MENU).addClass(CONFIG.CLASSES.HIDDEN);
        });

        // 每日放送卡片点击事件(使用事件委托)
        $body.on('click', '.anime-card', function (e) {
            // 如果是右键点击,不处理左键事件
            if (e.button === 2) return;

            // 如果点击的是链接按钮,不处理卡片点击事件
            if ($(e.target).closest('.douban-link-btn').length > 0) {
                return;
            }

            const $card = $(this);
            const cardId = $card.data('id');
            const isLinkedDouban = $card.data('source') === 'douban';
            const animeName = isLinkedDouban ? $card.data('title') : $card.data('name');

            // 根据卡片来源获取自定义URL
            let redirectUrl = isLinkedDouban ? getDoubanCustomUrl(cardId) : getBangumiCustomUrl(cardId);

            // 如果没有自定义URL,使用全局设置的跳转URL
            if (!redirectUrl) {
                redirectUrl = getSetting(CONFIG.STORAGE_KEYS.REDIRECT_URL, '');
            }

            // 确保URL有协议头
            redirectUrl = ensureProtocol(redirectUrl);

            if (redirectUrl) {
                // 复制剧集名称到剪贴板
                navigator.clipboard.writeText(animeName).then(() => {
                    // 显示提示,设置z-index确保在顶层显示
                    const toast = Swal.mixin({
                        toast: true,
                        position: 'top',
                        showConfirmButton: false,
                        timer: CONFIG.REDIRECT_DELAY,
                        timerProgressBar: true,
                    }).fire({
                        icon: 'success',
                        title: '已复制剧集名称,即将跳转...'
                    });

                    // 设置延时执行跳转
                    setTimeout(() => {
                        window.open(redirectUrl, '_blank');
                    }, CONFIG.REDIRECT_DELAY);

                }).catch(err => {
                    console.error('复制失败:', err);
                    // 即使复制失败,也显示提示并跳转
                    const toast = Swal.mixin({
                        toast: true,
                        position: 'top',
                        showConfirmButton: false,
                        timer: CONFIG.REDIRECT_DELAY,
                        timerProgressBar: true,
                    }).fire({
                        icon: 'warning',
                        title: '复制剧集名称失败,但仍将跳转...'
                    });

                    setTimeout(() => {
                        window.open(redirectUrl, '_blank');
                    }, CONFIG.REDIRECT_DELAY);
                });
            }
        });

        // 豆瓣卡片点击事件
        $body.on('click', '.douban-card', function (e) {
            // 如果是右键点击,不处理左键事件
            if (e.button === 2) return;

            // 如果点击的是链接按钮,不处理卡片点击事件(链接按钮会自动跳转)
            if ($(e.target).closest('.douban-link-btn').length > 0) {
                return;
            }

            const $card = $(this);
            const cardId = $card.data('id');
            const title = $card.data('title');

            // 优先使用自定义URL,否则使用全局设置的跳转URL
            let redirectUrl = getDoubanCustomUrl(cardId);
            if (!redirectUrl) {
                redirectUrl = getSetting(CONFIG.STORAGE_KEYS.REDIRECT_URL, '');
            }

            // 确保URL有协议头
            redirectUrl = ensureProtocol(redirectUrl);

            if (redirectUrl) {
                // 复制标题到剪贴板
                navigator.clipboard.writeText(title).then(() => {
                    // 显示提示,设置延时执行跳转
                    const toast = Swal.mixin({
                        toast: true,
                        position: 'top',
                        showConfirmButton: false,
                        timer: CONFIG.REDIRECT_DELAY,
                        timerProgressBar: true,
                    }).fire({
                        icon: 'success',
                        title: '已复制标题,即将跳转...'
                    });

                    // 设置延时执行跳转
                    setTimeout(() => {
                        window.open(redirectUrl, '_blank');
                    }, CONFIG.REDIRECT_DELAY);

                }).catch(err => {
                    console.error('复制失败:', err);
                    // 即使复制失败,也显示提示并跳转
                    const toast = Swal.mixin({
                        toast: true,
                        position: 'top',
                        showConfirmButton: false,
                        timer: CONFIG.REDIRECT_DELAY,
                        timerProgressBar: true,
                    }).fire({
                        icon: 'warning',
                        title: '复制标题失败,但仍将跳转...'
                    });

                    setTimeout(() => {
                        window.open(redirectUrl, '_blank');
                    }, CONFIG.REDIRECT_DELAY);
                });
            }
        });
    }

    function handleSaveSettings() {
        // 检查每日放送API是否发生变化
        const newDailyApiValue = $('#daily-api-input').val();
        const dailyApiChanged = newDailyApiValue !== originalDailyApiValue;

        // 检查豆瓣代理URL是否发生变化
        const oldDoubanProxy = getSetting(CONFIG.STORAGE_KEYS.DOUBAN_PROXY, '');
        const newDoubanProxy = $(`input[data-key="${CONFIG.STORAGE_KEYS.DOUBAN_PROXY}"]`).val() || '';
        const doubanProxyChanged = oldDoubanProxy !== newDoubanProxy;

        // 保存所有API设置
        $('#api-settings .text-input').each(function () {
            localStorage.setItem($(this).data('key'), $(this).val());
        });

        // 如果每日放送API发生变化,则清除缓存并重新预加载数据
        if (dailyApiChanged) {
            // 清除旧的缓存数据
            localStorage.removeItem(CONFIG.STORAGE_KEYS.DAILY_DATA_CACHE);
            localStorage.removeItem(CONFIG.STORAGE_KEYS.DAILY_DATA_TIMESTAMP);

            // 更新全局Promise,重新开始预加载
            dailyDataPromise = getDailyData();

            // 捕获可能的错误
            dailyDataPromise.catch(error => {
                console.error("Refetching daily data after API change failed:", error);
            });
        }

        // 保存Tab位置
        const newTabPosition = $('#elegant-tab-position').val();
        localStorage.setItem(CONFIG.STORAGE_KEYS.TAB_POSITION, newTabPosition);

        // 保存豆瓣接口模式和图片模式
        localStorage.setItem(CONFIG.STORAGE_KEYS.DOUBAN_PROXY_MODE, $('#douban-proxy-mode').val());
        localStorage.setItem(CONFIG.STORAGE_KEYS.DOUBAN_IMAGE_MODE, $('#douban-image-mode').val());

        // 保存Logo显示模式(unchecked=嵌合图标true,checked=悬浮按钮false)
        const newLogoMode = !$('#logo-mode-toggle').prop('checked');
        localStorage.setItem(CONFIG.STORAGE_KEYS.SHOW_LOGO_INNER, newLogoMode);
        // 如果在可嵌合的网站且模式发生变化,则切换显示模式
        if (canEmbedHosts.includes(window.location.hostname) && newLogoMode !== showLogoInner) {
            toggleDisplayMode(newLogoMode);
        }

        // 保存联动卡片位置(unchecked=首部head,checked=尾部tail)
        const oldLinkPosition = localStorage.getItem(CONFIG.STORAGE_KEYS.DAILY_LINK_POSITION) || 'head';
        const newLinkPosition = $('#daily-link-position-toggle').prop('checked') ? 'tail' : 'head';
        localStorage.setItem(CONFIG.STORAGE_KEYS.DAILY_LINK_POSITION, newLinkPosition);
        // 如果位置变化且每日放送已加载,刷新联动卡片
        if (oldLinkPosition !== newLinkPosition && dailyDataLoaded) {
            refreshLinkedDoubanCards();
        }

        // 在保存新顺序前先获取旧顺序
        const oldTabOrder = getTabOrder();

        // 保存Tab顺序
        const newTabOrder = [];
        $('#tab-sort-list .tab-sort-item').each(function () {
            newTabOrder.push({ id: $(this).data('id'), label: $(this).find('.tab-label').text() });
        });
        localStorage.setItem(CONFIG.STORAGE_KEYS.TAB_ORDER, JSON.stringify(newTabOrder));

        // 应用Tab位置
        applyTabPosition(newTabPosition);

        // 比较新旧顺序
        const tabOrderChanged = JSON.stringify(oldTabOrder) !== JSON.stringify(newTabOrder);

        // 如果Tab顺序或位置发生变化,则重新渲染标签页(保留内容,避免重新请求)
        if (tabOrderChanged || newTabPosition !== getSetting(CONFIG.STORAGE_KEYS.TAB_POSITION, 'top')) {
            renderTabs(newTabOrder, true, true);
        }

        // 如果当前激活的标签页是"每日放送"且API发生了变化,则重新渲染每日放送内容
        if (dailyApiChanged && currentActiveTabId === 'tab1') {
            processAndRenderDailyData(dailyDataPromise);
        }

        // 如果豆瓣代理URL发生变化,重置豆瓣状态并重新加载
        if (doubanProxyChanged) {
            doubanDataLoaded = false;
            // 重置所有tab的分页状态
            Object.keys(doubanTabState).forEach(tab => {
                doubanTabState[tab] = { page: 1, hasMore: true };
            });
            doubanTabCache = {};
            // 清除豆瓣缓存
            localStorage.removeItem(CONFIG.STORAGE_KEYS.DOUBAN_CACHE_MOVIE);
            localStorage.removeItem(CONFIG.STORAGE_KEYS.DOUBAN_CACHE_TV);
            localStorage.removeItem(CONFIG.STORAGE_KEYS.DOUBAN_CACHE_ANIME);
            localStorage.removeItem(CONFIG.STORAGE_KEYS.DOUBAN_CACHE_VARIETY);
            localStorage.removeItem(CONFIG.STORAGE_KEYS.DOUBAN_CACHE_TIMESTAMP);
            // 如果当前在豆瓣查找tab,立即重新加载
            if (currentActiveTabId === 'tab2') {
                loadDoubanData(true);
            }
        }

        showNotification('设置已保存并生效');
        switchModals(CONFIG.SELECTORS.SETTINGS_MODAL, CONFIG.SELECTORS.MAIN_MODAL);
    }

    // =================================================================================
    // 8. 核心交互逻辑 (Core Interaction Logic)
    // =================================================================================

    function openModal(modalSelector) {
        $(CONFIG.SELECTORS.OVERLAY).show();
        const $modal = $(modalSelector);

        if (modalSelector === CONFIG.SELECTORS.SETTINGS_MODAL) {
            initializeSettingsValues();
        }

        // 更新输入框的主题样式
        updateInputThemeStyles($('html').hasClass('dark'));

        $modal.removeClass(CONFIG.CLASSES.HIDDEN).addClass(CONFIG.CLASSES.FADE_IN)
            .one('animationend', () => $modal.removeClass(CONFIG.CLASSES.FADE_IN));
    }

    function closeModal(modalSelector, onClosed) {
        const $modal = $(modalSelector);
        $modal.addClass(CONFIG.CLASSES.FADE_OUT)
            .one('animationend', () => {
                $modal.addClass(CONFIG.CLASSES.HIDDEN).removeClass(CONFIG.CLASSES.FADE_OUT);
                if (onClosed) onClosed();
            });
    }

    function closeAllModals() {
        let activeModals = [CONFIG.SELECTORS.MAIN_MODAL, CONFIG.SELECTORS.SETTINGS_MODAL]
            .filter(sel => !$(sel).hasClass(CONFIG.CLASSES.HIDDEN));

        if (activeModals.length === 0) return;

        activeModals.forEach(sel => closeModal(sel));
        $(CONFIG.SELECTORS.OVERLAY).fadeOut(CONFIG.ANIMATION_DURATION);
    }

    function switchModals(fromSelector, toSelector) {
        closeModal(fromSelector, () => openModal(toSelector));
    }

    function toggleTheme() {
        const $html = $('html');
        $html.toggleClass('dark');
        const isDark = $html.hasClass('dark');
        localStorage.setItem(CONFIG.STORAGE_KEYS.THEME, isDark ? 'dark' : 'light');

        // 更新输入框的内联样式(使用切换后的状态)
        updateInputThemeStyles(isDark);
    }

    function updateInputThemeStyles(isDark) {
        const inputStyle = isDark
            ? 'padding: 10px 14px; border: 1px solid #4b5563; border-radius: 8px; font-size: 14px; color: #e5e7eb !important; outline: none;'
            : 'padding: 10px 14px; border: 1px solid #d1d5db; border-radius: 8px; font-size: 14px; color: #374151 !important; outline: none;';
        $('#api-settings .text-input').attr('style', inputStyle);
        $('#tags-search-input, #favorites-search-input').attr('style', inputStyle);
    }

    function initTheme() {
        if (getSetting(CONFIG.STORAGE_KEYS.THEME) === 'dark') {
            $('html').addClass('dark');
        }
    }

    // =================================================================================
    // 9. 样式注入与初始化 (Style Injection & Initialization)
    // =================================================================================
    function injectStyles() {
        const styles = `
            *:not(.ph-tab-pane *),
            *:not(.ph-tab-pane *)::before,
            *:not(.ph-tab-pane *)::after {
                box-sizing: unset;
            }
            /* Core Animations */
            @keyframes fadeIn { from { opacity: 0; transform: translateY(20px); } to { opacity: 1; transform: translateY(0); } }
            @keyframes fadeOut { from { opacity: 1; transform: translateY(0); } to { opacity: 0; transform: translateY(20px); } }
            .animate-fade-in { animation: fadeIn ${CONFIG.ANIMATION_DURATION}ms ease-out; }
            .animate-fade-out { animation: fadeOut ${CONFIG.ANIMATION_DURATION}ms ease-in; }
            /* Main Components */
            .modal-overlay { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background-color: rgba(0, 0, 0, 0.5); z-index: 9999; display: none; }
            .main-button { position: fixed; top: 100px; right: 30px; z-index: 10000; width: 45px; height: 45px; border-radius: 50%; background: linear-gradient(135deg, rgba(59, 130, 246, 0.12), rgba(34, 197, 94, 0.10)); color: white; display: flex; align-items: center; justify-content: center; cursor: pointer; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); transition: all 0.3s ease; border: none; }
            .main-button:hover { transform: translateY(-2px); box-shadow: 0 8px 20px rgba(0, 0, 0, 0.2); }
            /* Custom Scrollbar */
            #elegant-main-modal ::-webkit-scrollbar, #elegant-settings-modal ::-webkit-scrollbar { width: 8px; height: 8px; }
            #elegant-main-modal ::-webkit-scrollbar-track, #elegant-settings-modal ::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; }
            .dark #elegant-main-modal ::-webkit-scrollbar-track, .dark #elegant-settings-modal ::-webkit-scrollbar-track { background: #2d3748; }
            #elegant-main-modal ::-webkit-scrollbar-thumb, #elegant-settings-modal ::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 4px; }
            #elegant-main-modal ::-webkit-scrollbar-thumb:hover, #elegant-settings-modal ::-webkit-scrollbar-thumb:hover { background: #a8a8a8; }
            .dark #elegant-main-modal ::-webkit-scrollbar-thumb, .dark #elegant-settings-modal ::-webkit-scrollbar-thumb { background: #4a5568; }
            .dark #elegant-main-modal ::-webkit-scrollbar-thumb:hover, .dark #elegant-settings-modal ::-webkit-scrollbar-thumb:hover { background: #718096; }
            /* Tabs Layout */
            .tabs-container { display: flex; height: 100%; }
            .tabs-top { flex-direction: column; }
            .tabs-left { flex-direction: row; }
            .tabs-right { flex-direction: row-reverse; }
            .tabs-header { display: flex; background: #f3f4f6; border-radius: 12px; padding: 8px; }
            .dark .tabs-header { background: #1f2937; }
            .tabs-top .tabs-header { flex-direction: row; margin: 16px; }
            .tabs-left .tabs-header { flex-direction: column; margin: 16px 0 16px 16px; min-width: 160px; }
            .tabs-right .tabs-header { flex-direction: column; margin: 16px; margin-bottom: 16px; min-width: 160px; }
            .tab-button { padding: 12px 20px; border: none; background: transparent; cursor: pointer; border-radius: 8px; font-weight: 500; transition: all 0.3s ease; color: #4b5563; flex: 1; display: flex; align-items: center; justify-content: center; }
            .dark .tab-button { color: #9ca3af; }
            .tab-button.active { background: white; color: #3b82f6; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); }
            .dark .tab-button.active { background: #374151; color: #60a5fa; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2); }
            .tab-content-wrapper { flex: 1; position: relative; overflow: hidden; }
            .tab-content { height: 100%; padding: 0 16px 16px; overflow-y: auto; }
            .back-to-top { position: absolute; right: 15px; bottom: 24px; width: 40px; height: 40px; border-radius: 50%; background: linear-gradient(135deg, #65a0ff 0%, #ffffff 100%); color: white; border: none; cursor: pointer; display: flex; align-items: center; justify-content: center; box-shadow: 0 4px 12px rgba(59, 130, 246, 0.3); transition: all 0.3s ease; z-index: 10; }
            .back-to-top:hover { transform: translateY(-2px); box-shadow: 0 6px 16px rgba(59, 130, 246, 0.4); }
            .back-to-top.hidden { display: none; }
            .ph-tab-pane { display: none; }
            .ph-tab-pane.active { display: block; animation: fadeIn 0.3s ease; }
            /* Content Cards */
            .content-card { background: #f9fafb; border-radius: 12px; padding: 20px; margin-bottom: 16px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05); }
            .dark .content-card { background: #1f2937; }
            .content-title { font-size: 18px; font-weight: 600; margin-bottom: 12px; color: #111827; }
            .dark .content-title { color: #f9fafb; }
            .content-text { color: #4b5563; line-height: 1.6; }
            .dark .content-text { color: #d1d5db; }
            .daily-container { min-height: 400px; }
            /* Daily Loading Spinner */
            .daily-loading { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 300px; }
            .loading-spinner { width: 50px; height: 50px; border: 5px solid #f3f4f6; border-top: 5px solid #3b82f6; border-radius: 50%; animation: spin 1s linear infinite; margin-bottom: 16px; }
            .dark .loading-spinner { border-color: #374151; border-top-color: #60a5fa; }
            .loading-text { color: #6b7280; font-size: 14px; }
            .dark .loading-text { color: #9ca3af; }
            @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
            /* Daily Tabs - 优化星期tab切换抖动问题 */
            .daily-tabs-container { display: flex; flex-direction: column; height: 100%; }
            .week-tabs { display: flex; justify-content: center; margin-bottom: 16px; border-bottom: 1px solid #e5e7eb; }
            .dark .week-tabs { border-color: #4b5563; }
            .week-tab {
                padding: 10px 16px;
                margin: 0 4px;
                border: none;
                background: transparent;
                cursor: pointer;
                border-radius: 8px 8px 0 0;
                font-weight: 500;
                color: #6b7280;
                /* 优化:添加固定边框,避免切换时布局变化 */
                border-bottom: 2px solid transparent;
                /* 优化:只对颜色和背景应用过渡效果,避免边框变化导致的抖动 */
                transition: color 0.3s ease, background-color 0.3s ease;
            }
            .dark .week-tab { color: #9ca3af; }
            .week-tab:hover { color: #3b82f6; background: #f3f4f6; }
            .dark .week-tab:hover { color: #60a5fa; background: #374151; }
            .week-tab.active {
                color: #3b82f6;
                border-bottom-color: #3b82f6;
            }
            .dark .week-tab.active {
                color: #60a5fa;
                border-bottom-color: #60a5fa;
            }
            .week-contents { flex: 1; overflow: hidden; padding-top: 16px; }
            .week-content { display: none; animation: fadeIn 0.3s ease; }
            .week-content.active { display: block; }
            .no-content { text-align: center; color: #6b7280; padding: 40px 0; }
            .dark .no-content { color: #9ca3af; }
            .error-message { text-align: center; color: #ef4444; padding: 40px 0; }
            .douban-load-more-btn {
                display: block; width: 100%; padding: 12px; margin-top: 16px;
                background: #f3f4f6; border: 1px solid #d1d5db; border-radius: 8px;
                color: #374151; font-size: 14px; cursor: pointer; transition: all 0.2s;
            }
            .douban-load-more-btn:hover { background: #e5e7eb; }
            .dark .douban-load-more-btn { background: #374151; border-color: #4b5563; color: #e5e7eb; }
            .dark .douban-load-more-btn:hover { background: #4b5563; }
            /* 管理跳转样式 */
            .tags-manage-container { padding: 16px 0; }
            .tags-filter-bar { display: flex; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
            .tags-select, .tags-search {
                padding: 8px 12px; border-radius: 8px; border: 1px solid #d1d5db; border-color: #d1d5db !important;
                background: white !important; color: #374151 !important; font-size: 14px; outline: none;
            }
            .dark .tags-select { background: #374151 !important; border-color: #4b5563 !important; color: #e5e7eb !important; }
            .dark .tags-search { background: #374151 !important; border-color: #4b5563 !important; color: #e5e7eb !important; }
            .tags-search { flex: 1; min-width: 150px; align-self: center; }
            #elegant-main-modal .tags-search:focus { border-color: #d1d5db; outline: none; }
            #elegant-main-modal.dark .tags-search:focus { border-color: #4b5563; outline: none; }
            .ph-tags-list { display: flex; flex-direction: column; gap: 8px; }
            .tag-item {
                display: flex; align-items: center; gap: 12px; padding: 12px 16px;
                background: #f9fafb; border-radius: 8px; border: 1px solid #e5e7eb;
            }
            .dark .tag-item { background: #1f2937; border-color: #374151; }
            .tag-label {
                padding: 2px 8px; border-radius: 4px; font-size: 12px; font-weight: 600;
                flex-shrink: 0;
            }
            .tag-bangumi { background: #f59e0b; color: white; }
            .dark .tag-bangumi { background: #d97706; color: white; }
            .tag-douban { background: #3b82f6; color: white; }
            .dark .tag-douban { background: #2563eb; color: white; }
            .tag-name { flex: 0 0 220px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #374151; }
            .dark .tag-name { color: #e5e7eb; }
            .tag-url { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #6b7280; font-size: 13px; }
            .dark .tag-url { color: #9ca3af; }
            .tag-time { flex: 0 0 140px; color: #9ca3af; font-size: 12px; text-align: right; }
            .dark .tag-time { color: #6b7280; }
            .tag-btn {
                padding: 6px; border: none; background: transparent; cursor: pointer;
                color: #6b7280; border-radius: 4px; transition: all 0.2s;
            }
            .tag-btn:hover { background: #e5e7eb; color: #374151; }
            .dark .tag-btn:hover { background: #374151; color: #e5e7eb; }
            .tag-delete:hover { color: #ef4444; }
            .dark .tag-delete:hover { color: #f87171; }
            /* 管理收藏样式 */
            .favorites-container { padding: 16px 0; }
            .favorites-filter-bar { display: flex; gap: 12px; margin-bottom: 16px; flex-wrap: wrap; }
            .favorites-filter-bar .favorites-search:focus { border-color: #d1d5db !important; outline: none; }
            .dark .favorites-filter-bar .favorites-search:focus { border-color: #4b5563 !important; outline: none; }
            .favorites-select, .favorites-search {
                padding: 8px 16px; border-radius: 8px; border: 1px solid #d1d5db; border-color: #d1d5db !important;
                background: white !important; color: #374151 !important; font-size: 14px; outline: none;
            }
            .dark .favorites-select, .dark .favorites-search { background: #374151 !important; border-color: #4b5563 !important; color: #e5e7eb !important; }
            .dark .favorites-search { border-color: #4b5563 !important; }
            .favorites-search { flex: 1; min-width: 150px; align-self: center; }
            .favorites-add-btn {
                padding: 10px 16px; border-radius: 8px; border: none; background: #3b82f6;
                color: white; font-size: 14px; cursor: pointer; transition: background 0.2s;
            }
            .favorites-add-btn:hover { background: #2563eb; }
            .favorites-list { display: flex; flex-direction: column; gap: 8px; }
            .favorite-item {
                display: flex; align-items: center; gap: 12px; padding: 12px 16px;
                background: #f9fafb; border-radius: 8px; border: 1px solid #e5e7eb;
            }
            .dark .favorite-item { background: #1f2937; border-color: #374151; }
            .favorite-name { flex: 0 0 150px; font-weight: 500; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #374151; }
            .dark .favorite-name { color: #e5e7eb; }
            .favorite-url { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: #6b7280; font-size: 13px; }
            .dark .favorite-url { color: #9ca3af; }
            .favorite-time { flex: 0 0 140px; color: #9ca3af; font-size: 12px; text-align: right; }
            .dark .favorite-time { color: #6b7280; }
            .favorite-btn {
                padding: 6px; border: none; background: transparent; cursor: pointer;
                color: #6b7280; border-radius: 4px; transition: all 0.2s;
            }
            .favorite-btn:hover { background: #e5e7eb; color: #374151; }
            .dark .favorite-btn:hover { background: #374151; color: #e5e7eb; }
            .favorite-jump:hover { color: #3b82f6; }
            .dark .favorite-jump:hover { color: #60a5fa; }
            .favorite-delete:hover { color: #ef4444; }
            .dark .favorite-delete:hover { color: #f87171; }
            /* Anime Grid */
            .anime-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 16px; }
            .anime-card {
                background: #f9fafb;
                border-radius: 8px;
                overflow: hidden;
                transition: transform 0.3s ease;
                box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
                cursor: pointer;
            }
            .dark .anime-card {
                background: #1f2937;
                box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
            }
            .anime-card:hover {
                transform: translateY(-4px);
                box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
            }
            .dark .anime-card:hover {
                box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
            }
            .anime-image-container { position: relative; }
            .anime-image { width: 100%; height: 240px; object-fit: cover; }
            .anime-rating { position: absolute; top: 8px; right: 8px; background: rgba(0, 0, 0, 0.7); color: white; padding: 4px 8px; border-radius: 4px; font-size: 12px; font-weight: 600; }
            .anime-title {
                padding: 8px;
                font-size: 14px;
                font-weight: 500;
                color: #111827;
                text-align: center;
                white-space: nowrap;
                overflow: hidden;
                text-overflow: ellipsis;
                cursor: default;
            }
            .dark .anime-title { color: #f9fafb; }
            .anime-card:hover .anime-title { color: #3b82f6; }
            /* 联动豆瓣卡片样式 */
            .douban-linked-card { position: relative; }
            .douban-linked-card .douban-link-btn {
                position: absolute;
                top: 4px;
                right: 4px;
                width: 28px;
                height: 28px;
                background: rgba(59, 130, 246, 0.9);
                border-radius: 50%;
                display: flex;
                align-items: center;
                justify-content: center;
                color: white;
                opacity: 0;
                transition: opacity 0.2s ease;
                z-index: 10;
                text-decoration: none;
            }
            .douban-linked-card:hover .douban-link-btn { opacity: 1; }
            .douban-linked-card .douban-link-btn:hover { background: rgba(37, 99, 235, 1); }
            /* Settings Modal Styles */
            .settings-tabs-nav { flex-shrink: 0; }
            .tabs-nav-container { width: 100%; }
            .tabs-nav-list { display: flex; flex-direction: column; align-items: center; gap: 12px; }
            .settings-tab-button { width: 80%; padding: 12px 0; border: none; background: transparent; cursor: pointer; border-radius: 6px; font-weight: 500; transition: all 0.3s ease; color: #4b5563; text-align: center; }
            .settings-tab-button.active { background: #f3f4f6; color: #3b82f6; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05); }
            .dark .settings-tab-button { color: #9ca3af; }
            .dark .settings-tab-button.active { background: #374151; color: #60a5fa; box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); }
            .settings-tab-button:hover:not(.active) { background: #f3f4f6; }
            .dark .settings-tab-button:hover:not(.active) { background: #374151; }
            .settings-tab-content { flex: 1; }
            .settings-content-container { padding: 0 20px; width: 100%; }
            .settings-ph-tab-pane { display: none; width: 100%; animation: fadeIn 0.3s ease; }
            .settings-ph-tab-pane.active { display: block; }
            .settings-form-centered { width: 100%; max-width: 520px; display: flex; flex-direction: column; gap: 20px; margin: 0 auto; }
            .form-row { display: grid; grid-template-columns: 1fr 2fr; align-items: center; }
            .form-label { font-weight: 500 !important; text-align: right; padding-right: 16px; color: #374151; display: flex; justify-content: flex-end; align-items: center; }
            .dark .form-label { color: #e5e7eb; }
            .form-input { padding: 10px 14px; border: 1px solid #d1d5db; border-color: #d1d5db !important; border-radius: 8px; font-size: 14px; transition: all 0.3s ease; width: 180px; max-width: 250px; background-color: #f9fafb !important; color: #111827 !important; outline: none; }
            .dark .form-input { background-color: #1f2937 !important; border-color: #374151; color: #f9fafb !important; }
            .text-input:focus { outline: none; width: 300px; }
            .dark .text-input:focus { outline: none; }
            .select-input { width: 150px; border: 1px solid #d1d5db; border-color: #d1d5db !important; }
            .dark .select-input { border-color: #4b5563 !important; }
            .select-input:focus { outline: none; }
            /* Custom Select - Glassmorphism Style */
            .custom-select-wrapper { position: relative; display: inline-block; }
            .custom-select-wrapper.ph-select .custom-select-trigger { padding: 8px 12px; border-radius: 8px; border: 1px solid #d1d5db; background: white !important; color: #374151 !important; font-size: 14px; font-weight: normal; }
            .dark .custom-select-wrapper.ph-select .custom-select-trigger { background: #374151 !important; border-color: #4b5563; color: #e5e7eb !important; }
            .custom-select-trigger { display: inline-flex; align-items: center; padding: 0; background: transparent; border: none; cursor: pointer; transition: all 0.2s ease; font-weight: 500; color: #374151; font-size: 15px; }
            .dark .custom-select-trigger { color: #e5e7eb; }
            .custom-select-trigger:hover { color: #3b82f6; }
            .dark .custom-select-trigger:hover { color: #60a5fa; }
            .custom-select-wrapper.open .custom-select-trigger { color: #3b82f6; }
            .dark .custom-select-wrapper.open .custom-select-trigger { color: #60a5fa; }
            .custom-select-arrow { display: none; }
            .custom-select-options { position: absolute; top: calc(100% + 6px); left: 0; min-width: max-content; background: rgba(255,255,255,0.95); backdrop-filter: blur(12px); border: 1px solid rgba(59,130,246,0.15); border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.1), 0 2px 10px rgba(59,130,246,0.08); opacity: 0; visibility: hidden; transform: translateY(-8px) scale(0.96); transform-origin: top left; transition: all 0.25s cubic-bezier(0.4, 0, 0.2, 1); z-index: 9999; overflow: hidden; padding: 4px; display: flex; flex-direction: column; gap: 5px; }
            .custom-select-wrapper.ph-select { min-width: max-content; }
            .custom-select-wrapper.ph-select .custom-select-trigger { width: 100%; justify-content: space-between; outline: none; border: 1px solid #d1d5db; border-color: #d1d5db !important; }
            .dark .custom-select-wrapper.ph-select .custom-select-trigger { border-color: #4b5563 !important; }
            .dark .tags-search { background: #374151 !important; border-color: #4b5563 !important; color: #e5e7eb !important; }
            .custom-select-wrapper.ph-select .custom-select-trigger:focus { border-color: #3b82f6; box-shadow: none; }
            .dark .custom-select-wrapper.ph-select .custom-select-trigger:focus { border-color: #3b82f6; box-shadow: none; }
            .custom-select-wrapper.ph-select-fixed { min-width: 150px; }
            .custom-select-wrapper.ph-select-fixed .custom-select-trigger:focus { border-color: #3b82f6; box-shadow: none; }
            .dark .custom-select-wrapper.ph-select-fixed .custom-select-trigger:focus { border-color: #3b82f6; box-shadow: none; }
            .custom-select-wrapper.ph-select-fixed .custom-select-options { min-width: 100%; }
            .custom-select-wrapper.ph-select-fixed .custom-select-option { justify-content: center; }
            .dark .custom-select-options { background: rgba(31,41,55,0.95); border-color: rgba(96,165,250,0.2); box-shadow: 0 10px 40px rgba(0,0,0,0.3), 0 2px 10px rgba(96,165,250,0.1); }
            .custom-select-wrapper.open .custom-select-options { opacity: 1; visibility: visible; transform: translateY(0) scale(1); }
            .custom-select-option { display: flex; align-items: center; gap: 8px; padding: 10px 12px; cursor: pointer; transition: all 0.2s ease; border-radius: 8px; color: #374151; font-size: 13px; }
            .dark .custom-select-option { color: #e5e7eb; }
            .custom-select-option:hover { background: linear-gradient(135deg, rgba(59,130,246,0.8) 0%, rgba(147,197,253,0.9) 100%); color: white !important; }
            .dark .custom-select-option:hover { background: linear-gradient(135deg, rgba(96,165,250,0.8) 0%, rgba(59,130,246,0.9) 100%); color: white !important; }
            .custom-select-option.selected { background: linear-gradient(135deg, rgba(59,130,246,0.12) 0%, rgba(147,197,253,0.18) 100%); color: #2563eb; font-weight: 500; }
            .custom-select-option.selected:hover { color: white !important; }
            .dark .custom-select-option.selected { background: linear-gradient(135deg, rgba(96,165,250,0.2) 0%, rgba(59,130,246,0.25) 100%); color: #93c5fd; }
            .option-check { width: 14px; height: 14px; opacity: 0; transform: scale(0.5); transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); color: #3b82f6; flex-shrink: 0; }
            .dark .option-check { color: #60a5fa; }
            .custom-select-option.selected .option-check { opacity: 1; transform: scale(1); }
            .api-info { margin-top: -10px; margin-bottom: 10px; text-align: right; }
            .save-btn, .cancel-btn { color: white; border: none; padding: 10px 20px; border-radius: 8px; font-weight: 600; cursor: pointer; transition: all 0.3s ease; }
            .save-btn { background: linear-gradient(135deg, #10b981 0%, #059669 100%); }
            .save-btn:hover { transform: translateY(-2px); box-shadow: 0 10px 20px rgba(16, 185, 129, 0.3); }
            .cancel-btn { background: linear-gradient(135deg, #6b7280 0%, #4b5563 100%); }
            .cancel-btn:hover { transform: translateY(-2px); box-shadow: 0 10px 20px rgba(107, 114, 128, 0.3); }
            /* Tab Order Sorting Styles */
            .tab-order-container { display: flex; flex-direction: column; align-items: center; gap: 8px; width: 100%; }
            .tab-sort-list { background: #f9fafb; border: 1px solid #d1d5db; border-radius: 8px; padding: 8px; min-height: 120px; max-height: 200px; overflow-y: auto; width: 100%; }
            .dark .tab-sort-list { background: #1f2937; border-color: #374151; }
            .tab-sort-item { display: flex; align-items: center; background: white; border: 1px solid #d1d5db; border-radius: 6px; padding: 8px; margin-bottom: 6px; cursor: move; transition: all 0.2s ease; }
            .dark .tab-sort-item { background: #374151; border-color: #4b5563; }
            .tab-sort-item:last-child { margin-bottom: 0; }
            .tab-sort-item:hover { transform: translateY(-2px); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); }
            .dark .tab-sort-item:hover { box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3); }
            .tab-sort-ghost { opacity: 0.4; }
            .tab-drag-handle { margin-right: 8px; color: #9ca3af; cursor: move; }
            .dark .tab-drag-handle { color: #6b7280; }
            .tab-label { flex: 1; color: #111827; }
            .dark .tab-label { color: #f9fafb; }
            .tab-order-hint { font-size: 12px; color: #6b7280; margin-top: 4px; text-align: center; }
            .dark .tab-order-hint { color: #9ca3af; }
            /* Context Menu Styles */
            .context-menu {
                position: fixed;
                z-index: 10001;
                background: white;
                border-radius: 8px;
                box-shadow: 0 10px 25px rgba(0, 0, 0, 0.15);
                width: 500px;
                padding: 0;
                border: 1px solid #e5e7eb;
            }
            .dark .context-menu {
                background: #1f2937;
                border-color: #374151;
            }
            .context-menu-header {
                display: flex;
                justify-content: space-between;
                align-items: center;
                padding: 16px 20px;
                border-bottom: 1px solid #e5e7eb;
            }
            .dark .context-menu-header {
                border-color: #374151;
            }
            .context-menu-header h3 {
                margin: 0;
                font-size: 18px;
                font-weight: 600;
                color: #111827;
            }
            .dark .context-menu-header h3 {
                color: #f9fafb;
            }
            .context-menu-close {
                background: none;
                border: none;
                padding: 6px;
                border-radius: 4px;
                cursor: pointer;
                color: #6b7280;
                display: flex;
                align-items: center;
                justify-content: center;
            }
            .dark .context-menu-close {
                color: #9ca3af;
            }
            .context-menu-close:hover {
                background: #f3f4f6;
            }
            .dark .context-menu-close:hover {
                background: #374151;
            }
            .context-menu-content {
                padding: 20px;
            }
            .context-menu-content .form-row {
                margin-bottom: 16px;
            }
            .context-menu-content .form-row:last-child {
                margin-bottom: 0;
            }
            .context-menu-content .text-input:focus {
                border-color: #d1d5db;
                outline: none;
            }
            .dark .context-menu-content .text-input:focus {
                border-color: #4b5563;
                outline: none;
            }
            /* 每日放送多选列表样式 */
            .daily-link-list {
                display: flex;
                flex-direction: column;
                gap: 4px;
                max-height: 150px;
                overflow-y: auto;
            }
            .daily-link-list::-webkit-scrollbar { width: 8px; height: 8px; }
            .daily-link-list::-webkit-scrollbar-track { background: #f1f1f1; border-radius: 4px; }
            .dark .daily-link-list::-webkit-scrollbar-track { background: #2d3748; }
            .daily-link-list::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 4px; }
            .daily-link-list::-webkit-scrollbar-thumb:hover { background: #a8a8a8; }
            .dark .daily-link-list::-webkit-scrollbar-thumb { background: #4a5568; }
            .dark .daily-link-list::-webkit-scrollbar-thumb:hover { background: #718096; }
            .daily-link-item {
                display: flex;
                align-items: center;
                gap: 8px;
                padding: 6px 10px;
                border-radius: 6px;
                cursor: pointer;
                transition: background 0.2s;
                user-select: none;
            }
            .daily-link-item:hover {
                background: #f3f4f6;
            }
            .dark .daily-link-item:hover {
                background: #374151;
            }
            .daily-link-check {
                width: 16px;
                height: 16px;
                border: 2px solid #d1d5db;
                border-radius: 4px;
                display: flex;
                align-items: center;
                justify-content: center;
                flex-shrink: 0;
                transition: all 0.2s;
            }
            .dark .daily-link-check {
                border-color: #4b5563;
            }
            .daily-link-item.checked .daily-link-check {
                background: #3b82f6;
                border-color: #3b82f6;
            }
            .daily-link-item.checked .daily-link-check::after {
                content: '';
                width: 8px;
                height: 5px;
                border-left: 2px solid white;
                border-bottom: 2px solid white;
                transform: rotate(-45deg) translateY(-1px);
            }
            .daily-link-text {
                font-size: 13px;
                color: #374151;
            }
            .dark .daily-link-text {
                color: #e5e7eb;
            }
            .context-menu-footer {
                display: flex;
                justify-content: flex-end;
                gap: 12px;
                padding: 16px 20px;
                border-top: 1px solid #e5e7eb;
            }
            .dark .context-menu-footer {
                border-color: #374151;
            }
            /* Swal z-index */
            .swal2-container { z-index: 10000 !important; }
            /* 豆瓣查找样式 */
            .douban-container {
                display: flex;
                flex-direction: column;
                height: 100%;
                padding: 16px;
            }
            .douban-tabs {
                display: flex;
                justify-content: center;
                margin-bottom: 16px;
                border-bottom: 1px solid #e5e7eb;
            }
            .dark .douban-tabs {
                border-color: #4b5563;
            }
            .douban-tab {
                padding: 10px 16px;
                margin: 0 4px;
                border: none;
                background: transparent;
                cursor: pointer;
                border-radius: 8px 8px 0 0;
                font-weight: 500;
                color: #6b7280;
                border-bottom: 2px solid transparent;
                transition: color 0.3s ease, background-color 0.3s ease;
            }
            .dark .douban-tab {
                color: #9ca3af;
            }
            .douban-tab:hover {
                color: #3b82f6;
                background: #f3f4f6;
            }
            .dark .douban-tab:hover {
                color: #60a5fa;
                background: #374151;
            }
            .douban-tab.active {
                color: #3b82f6;
                border-bottom-color: #3b82f6;
            }
            .dark .douban-tab.active {
                color: #60a5fa;
                border-bottom-color: #60a5fa;
            }
            .douban-filters {
                display: flex;
                flex-direction: column;
                gap: 16px;
                margin-bottom: 16px;
            }
            .filter-row {
                display: flex;
                align-items: center;
            }
            .filter-label {
                font-weight: 500;
                margin-right: 12px;
                color: #374151;
                min-width: 50px;
            }
            .dark .filter-label {
                color: #e5e7eb;
            }
            .filter-options {
                display: flex;
                flex-wrap: wrap;
                gap: 8px;
                flex: 1;
            }
            /* 圆角胶囊按钮组样式 */
            .filter-option {
                padding: 8px 16px;
                border-radius: 9999px; /* 完全圆角,形成胶囊形状 */
                background-color: #f3f4f6;
                color: #4b5563;
                font-weight: 500;
                font-size: 14px;
                cursor: pointer;
                transition: all 0.2s ease;
                border: 1px solid transparent;
                outline: none;
                white-space: nowrap;
            }
            .dark .filter-option {
                background-color: #374151;
                color: #d1d5db;
            }
            .filter-option:hover:not(.active) {
                background-color: #e5e7eb;
                color: #3b82f6;
            }
            .dark .filter-option:hover:not(.active) {
                background-color: #4b5563;
                color: #60a5fa;
            }
            .filter-option.active {
                background-color: #3b82f6;
                color: white;
                border-color: #3b82f6;
            }
            .dark .filter-option.active {
                background-color: #60a5fa;
                border-color: #60a5fa;
            }
            /* 下拉菜单样式优化 */
            .ph-dd-wrap {
                position: relative;
                margin-right: 8px;
            }
            .ph-dd-toggle {
                display: flex;
                align-items: center;
                padding: 8px 16px;
                border-radius: 9999px; /* 胶囊形状 */
                background-color: #f3f4f6;
                color: #4b5563;
                font-weight: 500;
                font-size: 14px;
                cursor: pointer;
                transition: all 0.2s ease;
                border: 1px solid transparent;
                outline: none;
                white-space: nowrap;
            }
            .dark .ph-dd-toggle {
                background-color: #374151;
                color: #d1d5db;
            }
            .ph-dd-toggle:hover {
                background-color: #e5e7eb;
                color: #3b82f6;
            }
            .dark .ph-dd-toggle:hover {
                background-color: #4b5563;
                color: #60a5fa;
            }
            .ph-dd-text {
                margin-right: 8px;
            }
            .ph-dd-icon {
                width: 16px;
                height: 16px;
                transition: transform 0.2s ease;
            }
            .ph-dd-wrap.show .ph-dd-icon {
                transform: rotate(180deg);
            }
            .ph-dd-menu {
                position: absolute;
                top: calc(100% + 8px);
                left: 0;
                z-index: 10;
                display: none;
                min-width: 400px;
                background-color: white;
                border-radius: 12px;
                box-shadow: 0 10px 25px rgba(0, 0, 0, 0.1);
                padding: 8px;
                border: 1px solid #e5e7eb;
            }
            .dark .ph-dd-menu {
                background-color: #1f2937;
                border-color: #374151;
            }
            .ph-dd-menu.show {
                display: block;
                animation: dropdownFade 0.2s ease;
            }
            @keyframes dropdownFade {
                from {
                    opacity: 0;
                    transform: translateY(-10px);
                }
                to {
                    opacity: 1;
                    transform: translateY(0);
                }
            }
            /* 下拉项网格布局 */
            .ph-dd-grid {
                display: grid;
                grid-template-columns: repeat(5, auto);
                gap: 8px;
            }
            .ph-dd-item {
                padding: 10px 12px;
                border-radius: 8px;
                background-color: #f9fafb;
                color: #4b5563;
                font-size: 14px;
                cursor: pointer;
                transition: all 0.2s ease;
                border: 1px solid transparent;
                text-align: center;
                outline: none;
                white-space: nowrap;
            }
            .dark .ph-dd-item {
                background-color: #374151;
                color: #d1d5db;
            }
            .ph-dd-item:hover {
                background-color: #e5e7eb;
                color: #3b82f6;
            }
            .dark .ph-dd-item:hover {
                background-color: #4b5563;
                color: #60a5fa;
            }
            .ph-dd-item.active {
                background-color: #dbeafe;
                color: #3b82f6;
                border-color: #93c5fd;
            }
            .dark .ph-dd-item.active {
                background-color: #1e3a8a;
                color: #93c5fd;
                border-color: #3b82f6;
            }
            .douban-results {
                flex: 1;
                overflow-y: auto;
                margin-bottom: 16px;
            }
            .douban-grid {
                display: grid;
                grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
                gap: 16px;
            }
            .douban-card {
                background: #f9fafb;
                border-radius: 12px;
                overflow: hidden;
                transition: transform 0.3s ease, box-shadow 0.3s ease;
                box-shadow: 0 2px 4px rgba(0, 0, 0, 0.05);
                cursor: pointer;
            }
            .dark .douban-card {
                background: #1f2937;
                box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
            }
            .douban-card:hover {
                transform: translateY(-4px);
                box-shadow: 0 6px 12px rgba(0, 0, 0, 0.1);
            }
            .dark .douban-card:hover {
                box-shadow: 0 6px 12px rgba(0, 0, 0, 0.3);
            }
            .douban-image-container {
                position: relative;
            }
            .douban-image {
                width: 100%;
                height: 225px;
                object-fit: cover;
            }
            .douban-rating {
                position: absolute;
                top: 8px;
                right: 8px;
                background: rgba(0, 0, 0, 0.7);
                color: white;
                padding: 4px 8px;
                border-radius: 4px;
                font-size: 12px;
                font-weight: 600;
            }
            .douban-link-btn {
                position: absolute;
                top: 8px;
                left: 8px;
                background: rgba(0, 0, 0, 0.7);
                color: white;
                width: 32px;
                height: 32px;
                border-radius: 50%;
                display: flex;
                align-items: center;
                justify-content: center;
                opacity: 0;
                transition: opacity 0.3s ease, background 0.3s ease, transform 0.3s ease;
                text-decoration: none;
                backdrop-filter: blur(4px);
                z-index: 10;
            }
            .douban-link-btn:hover {
                background: rgba(0, 0, 0, 0.85);
                transform: scale(1.1);
            }
            .douban-image-container:hover .douban-link-btn {
                opacity: 1;
            }
            .douban-link-btn svg {
                width: 18px;
                height: 18px;
                stroke-width: 2;
            }
            .dark .douban-link-btn {
                background: rgba(255, 255, 255, 0.15);
                color: white;
            }
            .dark .douban-link-btn:hover {
                background: rgba(255, 255, 255, 0.25);
            }
            .douban-title {
                padding: 8px;
                font-size: 14px;
                font-weight: 500;
                color: #111827;
                text-align: center;
                white-space: nowrap;
                overflow: hidden;
                text-overflow: ellipsis;
                cursor: default;
            }
            .dark .douban-title {
                color: #f9fafb;
            }
            .douban-card:hover .douban-title { color: #3b82f6; }
            .douban-year {
                padding: 0 8px 8px;
                font-size: 12px;
                color: #6b7280;
                text-align: center;
            }
            .dark .douban-year {
                color: #9ca3af;
            }
            .douban-loading {
                display: flex;
                flex-direction: column;
                align-items: center;
                justify-content: center;
                height: 300px;
            }
            .douban-load-more {
                display: flex;
                flex-direction: column;
                align-items: center;
                justify-content: center;
                padding: 20px;
            }
            .douban-load-more .loading-spinner {
                width: 30px;
                height: 30px;
            }
            .douban-load-more .loading-text {
                font-size: 12px;
            }
            .douban-results-wrapper {
                flex: 1;
                min-height: 0;
            }
            .douban-results {
                display: none;
            }
            .douban-results.active {
                display: block;
            }
            /* Logo Mode Toggle Switch */
            .logo-mode-row.hidden { display: none !important; }
            .logo-mode-switch { display: flex; align-items: center; gap: 16px; width: 100%; }
            .logo-mode-text-left, .logo-mode-text-right { color: #374151; flex-shrink: 0; }
            .dark .logo-mode-text-left, .dark .logo-mode-text-right { color: #e5e7eb; }
            .logo-mode-toggle {
                position: relative;
                display: inline-block;
                width: 50px;
                height: 26px;
                flex-shrink: 0;
            }
            .logo-mode-toggle input {
                opacity: 0;
                width: 0;
                height: 0;
            }
            .logo-mode-slider {
                position: absolute;
                cursor: pointer;
                top: 0;
                left: 0;
                right: 0;
                bottom: 0;
                background-color: #ccc;
                transition: .3s;
                border-radius: 26px;
            }
            .logo-mode-slider:before {
                position: absolute;
                content: "";
                height: 20px;
                width: 20px;
                left: 3px;
                bottom: 3px;
                background-color: white;
                transition: .3s;
                border-radius: 50%;
                box-shadow: 0 2px 4px rgba(0,0,0,0.2);
            }
            .logo-mode-toggle input:checked + .logo-mode-slider {
                background-color: #3b82f6;
            }
            .logo-mode-toggle input:checked + .logo-mode-slider:before {
                transform: translateX(24px);
            }
            .logo-mode-toggle input:focus + .logo-mode-slider {
                box-shadow: 0 0 1px #3b82f6;
            }
            .dark .logo-mode-slider {
                background-color: #6b7280;
            }
            .dark .logo-mode-toggle input:checked + .logo-mode-slider {
                background-color: #3b82f6;
            }
        `;
        if (typeof GM_addStyle !== 'undefined') {
            GM_addStyle(styles);
        } else {
            const styleNode = document.createElement('style');
            styleNode.appendChild(document.createTextNode(styles));
            document.head.appendChild(styleNode);
        }
    }

    function main() {
        tailwind.config = {
            darkMode: 'class',
            theme: { extend: { colors: { primary: { 50: '#f0f9ff', 100: '#e0f2fe', 200: '#bae6fd', 300: '#7dd3fc', 400: '#38bdf8', 500: '#0ea5e9', 600: '#0284c7', 700: '#0369a1', 800: '#075985', 900: '#0c4a6e' } } } }
        };

        // 预加载每日放送数据和图片
        dailyDataPromise = getDailyData();
        dailyDataPromise.then(data => {
            // 预加载每日放送图片
            if (Array.isArray(data)) {
                data.forEach(day => {
                    (day.items || []).forEach(item => {
                        const img = new Image();
                        img.src = item.images?.large || item.image || '';
                    });
                });
            }
        }).catch(e => console.error("Pre-loading daily data failed:", e));

        // 预加载所有豆瓣数据
        preloadAllDoubanData();

        $(document).ready(function () {
            injectStyles();
            initTheme();
            $('body').append('<div id="elegant-modal-overlay" class="modal-overlay"></div>');
            createMainButton();
            createMainModal();
            createSettingsModal();
            createContextMenu();
            bindEvents();
        });
    }

    main();

})();