linux.do player helper

观影助手

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==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();

})();